I’ve finally started some serious work on a library for an 8×8 RGB LED matrix and intend to post some stuff in here as things progress. Today’s is lines, and some introduction.

Intro: now, I know this seems a bit strange. Why start with lines and not with pixels? Well, the pixels theme is for an Instructables article that is in the making and will be present in the very first version of the library – the one that’s just to make things work. While I’m testing it I’m slowly building a bigger one that will be used in later projects. And this one is loosely based on the good old BGI or Borland Graphics Interface.

I started programming in the blessed times of DOS and Turbo Pascal when the screen was a black canvas and if you wanted it to display something, you actually drew on it, using pixels, lines, circles and suchlike. Ah, the good old times. Thus, when I was thinking about the ways to put my new 8×8 screens to use, I remembered these. And decided to return the good old times of egavga.bgi.

I will be putting stuff on the blog as it happens in my workshop. Like, I was playing around with lines recently – so here you go. These posts won’t be in any (chrono)logical order, sorry. And I will be returning and be changing them. In some distant future, they will all finally reach the desired level of perfectness, I’ll collect them, re-shuffle, structure and put out as articles. But for the time being, I want to write stuff while it’s still hot. And I really, really don’t want to spend a lot of time on it – I prefer coding in my spare time, not editing articles, as I have enough of that while working.

Thus, lines.

Let’s understand the limitations of our hardware. First, it’s 8 by 8 pixels. Most of the graphics routines are written for bigger resolutions. Thus, a lot of stuff that looks cool on your computer screen won’t be so here, as we’ll soon see. Second, our controlling chip is not a powerhouse, so we need to avoid using floating point operations and try to limit any hard math to the bare minimum. Third, LEDs are not pixels, their brightness doesn’t change linearly. On the bright side, at least the first limitation can be turned to our advantage.

There is the Bresenham algorithm for drawing lines. It’s fast, there is an integer-only version, and it’s well-documented. I like this one:

//Bresenham line void line(int x0, int y0, int x1, int y1){ int dx = abs(x1-x0), sx = x0<x1 ? 1 : -1; int dy = abs(y1-y0), sy = y0<y1 ? 1 : -1; int err = (dx>dy ? dx : -dy)/2, e2; for(;;){ setRGBpointXY(x0,y0,4000, 000, 1000); if (x0==x1 && y0==y1) { break; } e2 = err; if (e2 >-dx) { err -= dy; x0 += sx; } if (e2 < dy) { err += dx; y0 += sy; } } }

Let’s try it out!

Oops.

A line that may be a bit aliased (or ‘stair-stepped’) on a computer screen becomes almost a bunch of random pixels on an 8×8 matrix. We need to smooth it out a bit.

Now, there is a Xiaolin Wu’s algorithm for drawing antialiased lines. It’s a bit heavy on floating point operations, so frankly I didn’t even try it out. There’s another way.

Our ‘screen’ is just 8 by 8 pixels. The Bresenham algorithm is made for bigger resolutions. So why don’t we simply project a bigger resolution onto our small matrix? After all, we don’t have to make a full ‘virtual screen’ and keep it in memory, we just trick the line function into thinking it deals with a larger display. We’ll have to make it calculate more than one ‘pixel’ for each of our LEDs and then blend the results for each LED together somehow. So the `line()`

function remains almost the same, but we change the ‘setPixel’ routine into a ‘blendIn’ routine.

To do the blending we must first have some way of getting the current R, G, B values of each LED. Thankfully, the `getPixel()`

function is just a variation of `putPixel()`

that I already have.

I started with a 256×256 resolution. It’s a good number, x and y can fit into a byte, and it’s dividable by 8, so we actually have 32×32 ‘virtual pixels’ in every physical RGB LED. Here’s the first iteration of the `addPixel()`

function:

void addPixel256(int x, int y, int pixVal){ uint8_t x0 = x/32, y0 = y/32; uint16_t addVal = min(getPixel((x0)*3, y0) + pixVal/32, 4095); setRGBpointXY(x0, y0, addVal, 0, 0); }

And the `line()`

looks like this:

void line256(int x0, int y0, int x1, int y1){ int dx = abs(x1-x0), sx = x0<x1 ? 1 : -1; int dy = abs(y1-y0), sy = y0<y1 ? 1 : -1; int err = (dx>dy ? dx : -dy)/2, e2; clearAll(); for(;;){ addPixel256(x0,y0,4000); //addPixel256(x0*3+32,y0,1000); //addPixel256(x0*3+64,y0,1000); if (x0==x1 && y0==y1) { switchData(); break; } e2 = err; if (e2 >-dx) { err -= dy; x0 += sx; } if (e2 < dy) { err += dx; y0 += sy; } } }

Note that at this point I’m content with just red color, as I’m still experimenting. The result looks good but is rather slow. Here’s a simple animation going through all the 32 steps per LED, no delays:

Thankfully, speeding things up is pretty easy, as 32 steps per LED is a bit excessive and we can skip some.

There are other problems though. First, the ‘LEDs not pixels’ one. What we have as a result is basically a nice, but a thick line, like 2 pixels thick. That’s because we’re simply adding up numbers, and an LED at 50% brightness looks just a tiny bit dimmer than 100%. Some manual adjustment is needed, and 32 steps are just too much for that.

Second, note how the lines in the animation above tend to ‘stick’ to the sides when they run parallel to them. This can be seen even better here:

The reason for this is that our current algorithm doesn’t take into account how close our ‘virtual’ line is to the edge of the physical one. Just to illustrate:

All of these lines will look exactly the same when projected on an 8×8 LED screen because while being different, they all go through the same larger ‘pixels’, that is, LEDs. Note that pink squares on the second graph are not covered by any lines at all, so they always stay dark. This too must be corrected.

Thus, enter the 40×40 screen. Why the multiplier of 5? Because I liked the idea of having a center ‘maximum brightness’ pixel and two pixels spilling some of their brightness onto neighbors. Another reason: this central pixel is really helpful in a lot of situations. For example, I can ditch all the other options and just draw lines from an LED to an LED, not even bothering with all these invisible pixels; everything will be done by the library. At this moment I don’t know the exact way to implement the library, so I’m keeping every idea.

In any case, here’s the code. Note that I extracted the divisors into defines to make the changes easier:

#define SLINE_DIV_NEXT_FAR 150 //default 150 #define SLINE_DIV_NEXT_CLOSE 33 //default 33 #define SLINE_DIV_OUTER 18 //default 18 #define SLINE_DIV_INNER 8 //default 8 #define SLINE_DIV_CENTER 5 //in fact, this should be removed, as it's always /5 void line41(int x0, int y0, int x1, int y1, int red = 5000, int green = 5000, int blue = 5000){ int dx = abs(x1-x0), sx = x0<x1 ? 1 : -1; int dy = abs(y1-y0), sy = y0<y1 ? 1 : -1; int err = (dx>dy ? dx : -dy)/2, e2; bool steep = (dy>dx); if (red==5000) {red = activeColor[0]; green = activeColor[1]; blue = activeColor[2];} for(;;){ if (steep){ switch (x0%5){ case 0: addPixel41(x0,y0,red/SLINE_DIV_OUTER, green/SLINE_DIV_OUTER, blue/SLINE_DIV_OUTER); addPixel41(x0-1,y0,red/SLINE_DIV_NEXT_CLOSE, green/SLINE_DIV_NEXT_CLOSE, blue/SLINE_DIV_NEXT_CLOSE); break; case 4: addPixel41(x0,y0,red/SLINE_DIV_OUTER, green/SLINE_DIV_OUTER, blue/SLINE_DIV_OUTER); addPixel41(x0+1,y0,red/SLINE_DIV_NEXT_CLOSE, green/SLINE_DIV_NEXT_CLOSE, blue/SLINE_DIV_NEXT_CLOSE); break; case 1: addPixel41(x0,y0,red/SLINE_DIV_INNER, green/SLINE_DIV_INNER, blue/SLINE_DIV_INNER); addPixel41(x0-1,y0,red/SLINE_DIV_NEXT_FAR, green/SLINE_DIV_NEXT_FAR, blue/SLINE_DIV_NEXT_FAR); break; case 3: addPixel41(x0,y0,red/SLINE_DIV_INNER, green/SLINE_DIV_INNER, blue/SLINE_DIV_INNER); addPixel41(x0+1,y0,red/SLINE_DIV_NEXT_FAR, green/SLINE_DIV_NEXT_FAR, blue/SLINE_DIV_NEXT_FAR); break; case 2: addPixel41(x0,y0,red/SLINE_DIV_CENTER, green/SLINE_DIV_CENTER, blue/SLINE_DIV_CENTER); break; default: break; } } else { switch (y0%5){ case 0: addPixel41(x0,y0,red/SLINE_DIV_OUTER, green/SLINE_DIV_OUTER, blue/SLINE_DIV_OUTER); addPixel41(x0,y0-1,red/SLINE_DIV_NEXT_CLOSE, green/SLINE_DIV_NEXT_CLOSE, blue/SLINE_DIV_NEXT_CLOSE); break; case 4: addPixel41(x0,y0,red/SLINE_DIV_OUTER, green/SLINE_DIV_OUTER, blue/SLINE_DIV_OUTER); addPixel41(x0,y0+1,red/SLINE_DIV_NEXT_CLOSE, green/SLINE_DIV_NEXT_CLOSE, blue/SLINE_DIV_NEXT_CLOSE); break; case 1: addPixel41(x0,y0,red/SLINE_DIV_INNER, green/SLINE_DIV_INNER, blue/SLINE_DIV_INNER); addPixel41(x0,y0-1,red/SLINE_DIV_NEXT_FAR, green/SLINE_DIV_NEXT_FAR, blue/SLINE_DIV_NEXT_FAR); break; case 3: addPixel41(x0,y0,red/SLINE_DIV_INNER, green/SLINE_DIV_INNER, blue/SLINE_DIV_INNER); addPixel41(x0,y0+1,red/SLINE_DIV_NEXT_FAR, green/SLINE_DIV_NEXT_FAR, blue/SLINE_DIV_NEXT_FAR); break; case 2: addPixel41(x0,y0,red/SLINE_DIV_CENTER, green/SLINE_DIV_CENTER, blue/SLINE_DIV_CENTER); break; default: break; } } if (x0==x1 && y0==y1) { break; } e2 = err; if (e2 >-dx) { err -= dy; x0 += sx; } if (e2 < dy) { err += dx; y0 += sy; } } }

And this looks better:

However, while I was writing all this on a train, I had a thought: look, what about a 6-pixel resolution? I can make the center a bit wider to avoid the too big overspill. Or 4 pixels? Or 8? Thus, three new versions were born.

I’m playing around with them. At this time the only thing I’m sure is that I will keep at least two in the library – the basic Bresenham one and one or two of the smooth ones. We’ll see which ones make it.

Everything’s nice so far, but what about black lines? It’s not a problem with the straightforward Bresenham algorithm, as it simply replaces the pixels values with the new ones. The smooth version, on the other hand, adds new values to the existing ones.

So, time to do some subtraction. I’m using a variety of `setwritemode()`

function from the BGI. In fact, my writeMode does more stuff with the Bresenham line and simple pixels, but that’s not for this post. With smooth lines it just adds if the `writeMode == 0`

and substracts if it’s anything else. Here’s the new `addPixel()`

for the eight-steps-per-LED line:

void addPixel48(int x, int y, int red, int green, int blue){ if (inRange(x, 0, 63) && inRange (y, 0, 63)){ uint8_t x0 = x>>3, y0 = y>>3; if (!writeMode) { red = min(getPixel(x0 + x0 + x0, y0) + red, 4095); green = min(getPixel(x0 + x0 + x0 +1, y0) + green, 4095); blue = min(getPixel(x0 + x0 + x0 +2, y0) + blue, 4095); } else { red = max((int)getPixel(x0 + x0 + x0, y0) - red, 0); green = max((int)getPixel(x0 + x0 + x0 +1, y0) - green, 0); blue = max((int)getPixel(x0 + x0 + x0 +2, y0) - blue, 0); } setRGBpointXY(x0, y0, red, green, blue); } }

To draw a smooth black line you need to specify the white color (4095, 4095, 4095). This gives some interesting opportunities, as you can provide different colors, subtract particular color channels and so on.

Finally, why so much fuss about the lines? After all, it’s just an 8×8 ‘screen’, you can easily draw stuff here pixel by pixel. Well, lines give two opportunities. First, animations. Second, they can make some nice random patterns, and do it much faster than any noise algorithm. And the random ‘black’ lines, with some color manipulation, give interesting fade-out effects. Definitely better than just random pixels, unlike the bigger screens.

In the next episodes:

- – Speed optimization
- – Circles
- – WriteModes
- – Masks
- – Video pages
- – Palettes
- – And other stuff

Stay tuned. And if you have any questions or comments, please drop by my Facebook page.