GraphicC – Who’s line is it?

test_linespan

After pixels, drawing lines are probably the next most useful primitive in a graphics system.  Perhaps bitblt is THE most useful, but lines are certain up there.  There are a few things going on in the picture above, so I’m going to go through them bit by bit.

This is the code that does it:

#include "test_common.h"

void test_linespan()
{
	size_t width = 320;
	size_t height = 240;
	pb_rgba pb;
	pb_rgba_init(&pb, width, height);


	// Set a white background
	raster_rgba_rect_fill(&pb, 0, 0, width, height, pWhite);

	// do a horizontal fade
	int color1 = RGBA(127, 102, 0, 255);
	int color2 = RGBA(0, 127, 212, 255);

	for (size_t row = 0; row < 100; row++) {
		int x1 = 0;
		int x2 = width - 1;
		raster_rgba_hline_span(&pb, x1, color1, x2, color2, row);
	}

	// Draw a button looking thing
	// black
	raster_rgba_hline(&pb, 20, 20, 80, pBlack);
	raster_rgba_hline(&pb, 20, 21, 80, pDarkGray);
	raster_rgba_hline(&pb, 20, 22, 80, pLightGray);


	// light gray rect
	raster_rgba_rect_fill(&pb, 20, 23, 80, 24, pLightGray);

	// fade to black
	for (size_t col = 20; col < 100; col++) {
		raster_rgba_vline_span(&pb, 46, pLightGray, 77, pBlack, col);
	}

	// Draw some blended lines atop the whole
	for (size_t row = 35; row < 120; row++){
		raster_rgba_hline_blend(&pb, 45, row, 100, RGBA(0, 153, 153, 203));
	}

	// Now we have a simple image, so write it to a file
	int err = write_PPM("test_linespan.ppm", &pb);

}



int main(int argc, char **argv)
{
	test_linespan();

	return 0;
}

In most graphics systems, there are three kinds of lines;

  • horizontal
  • vertical
  • sloped

Along with the kind of line, there are typically various attributes such as thickness, color,
and in the case of joined lines, what kind of joinery (butted, mitered, rounded).

At the various lowest level though, there are simply lines, and the things I focus on are slopes and colors.

There are two basic lines kinds that are of particular interest right off the bat, those are horizontal and vertical.  They are special cases because their drawing can be easily optimized, and if you have any acceleration hardware, these are most likely included.  This kinds of brings up another design desire.

  • Can be implemented in an fpga

But, that’s for a much later time.  For now, how about those horizontal and vertical lines?
If what you want is a solid line, of a single color, ignoring any transparency, then you want a simple hline:

int raster_rgba_hline(pb_rgba *pb, unsigned int x, unsigned int y, unsigned int length, int value)
{
	size_t terminus = x + length;
	x = x < 0 ? 0 : x;
	terminus = terminus - x;

	unsigned int * data = &((unsigned int *)pb->data)[y*pb->pixelpitch+x];
	size_t count = 1;
	for (size_t idx = 0; idx < terminus; idx++)
	{
		*data = value;
		data++;
	}

	return 0;
}

This makes a simple rectangle easy to implement as well:

#define raster_rgba_rect_fill(pb, x1, y1, width, height, value) for (size_t idx = 0; idx < height; idx++){raster_rgba_hline(pb, x1, y1 + idx, width, value);	}

Simple single color horizontal lines can be drawn quickly, often times using some form of memset
on the host platform. Even in the case where a tight inner loop is used, what appears to be
fairly slow can be quite fast depending on how your compiler optimizes things.

There is a corresponding ‘vline’ routine that does similar.

int raster_rgba_vline(pb_rgba *pb, unsigned int x, unsigned int y, unsigned int length, int value)
{
    unsigned int * data = &((unsigned int *)pb->data)[y*pb->frame.width + x];
    size_t count = 1;
    while (count <= length) {
        *data = value;
        data += pb->pixelpitch;
        count++;
    }

    return 0;
}

Here, the same basic assignment is used, after determining the starting offset of the first
pixel. The pointer is advanced by the pixelpitch, which in this case is the count of 32-bit
integer values per row. If you’re rendering into a framebuffer that doesn’t conform to this,
then this offset can be adjusted accordingly.

Horizontal and vertical lines of a solid color are nice, and can go a long way towards satisfying
some very basic drawing needs. But, soon enough you’ll want those lines to do a little bit more
work for you.

void raster_rgba_hline_span(pb_rgba *pb, int x1, int color1, int x2, int color2, int y)
{
    int xdiff = x2 - x1;
    if (xdiff == 0)
        return;

    int c1rd = GET_R(color1);
    int c1gr = GET_G(color1);
    int c1bl = GET_B(color1);

    int c2rd = GET_R(color2);
    int c2gr = GET_G(color2);
    int c2bl = GET_B(color2);

    int rdx = c2rd - c1rd;
    int gdx = c2gr - c1gr;
    int bdx = c2bl - c1bl;

    float factor = 0.0f;
    float factorStep = 1.0f / (float)xdiff;

    // draw each pixel in the span
    for (int x = x1; x < x2; x++) {
        int rd = c1rd + (int)(rdx*factor);
        int gr = c1gr + (int)(gdx*factor);
        int bl = c1bl + (int)(bdx*factor);
        int fg = RGBA(rd, gr, bl, 255);
        pb_rgba_set_pixel(pb, x, y, fg);

        factor += factorStep;
    }
}

Let’s imagine you want to draw that fade from green to blue. You could do it pixel by pixel,
but it’s nice to have a simple line drawing routine that does it for you.

The ‘raster_rgba_hline_span’ routine takes two colors, as well as the x1, x2, and y values. It
will draw a horizontal line between x1 and x2 (inclusive), going from color1, to color2 in a
linear fashion. Same goes for the corresponding vline_span routine. This is nice and simple.
You could modify it a bit to change the parameters that are passed in, and fuss with trying to
optimize the loop in various ways, but this basically works.

Great, we have solid lines, line spans with color fading, what about transparency?

The individual pixels are represented with 32-bits each. There are 8-bits each for R, G, and B
color components. The ‘A’ portion of the color is not used by the frame buffer, but rather used
to represent an opacity value. The opacity just determines how much of the color will be shown
if this color is blended with another color.

In the picture above, the turquoise rectangle is blended with the pixels that are below, rather
than just copying its pixels atop the others.

In order to do that, we want to draw some horizontal lines, but we want to blend the colors
instead of copy them:

// Draw some blended lines atop the whole
for (size_t row = 35; row < 120; row++)
{
    raster_rgba_hline_blend(&pb, 45, row, 100, RGBA(0, 153, 153, 203));
}

The ‘raster_rgba_hline_blend’ routine does the needful. It takes a color with an alpha (opacity)
value of 203, and blends that with whatever is already in the specified frame buffer. It looks
like this:

#define blender(bg, fg, a) ((uint8_t)((fg*a+bg*(255-a)) / 255))

#define blend_color(bg, fg) RGBA(                \
    blender(GET_R(bg), GET_R(fg), GET_A(fg)),    \
    blender(GET_G(bg), GET_G(fg), GET_A(fg)),    \
    blender(GET_B(bg), GET_B(fg), GET_A(fg)),    255)


int raster_rgba_hline_blend(pb_rgba *pb, unsigned int x, unsigned int y, unsigned int length, int value)
{
    size_t terminus = x + length;
    x = x < 0 ? 0 : x;
    terminus = terminus - x;

    unsigned int * data = &((unsigned int *)pb->data)[y*pb->pixelpitch + x];
    size_t count = 1;
    for (size_t idx = 0; idx < terminus; idx++)
    {
        int bg = *data;
        int fg = value;

        *data = blend_color(bg, fg);
        data++;
    }

    return 0;
}

The use of macros here keeps the operations straight, and provides easy routines that can be used elsewhere.
It’s a pretty straight forward process though. Each time through the inner loop, the ‘bg’
represents the ‘background’ color, which is taken from the pixel buffer. The ‘fg’ value
represents the color that the user passed in. The new value is a blend of the two, using
the alpha component of the foreground color.

This is a fairly basic routine that can be adapted in many ways. For example, instead of
using the alpha value from the foreground color, you could just pass in a fixed alpha value
to be applied. This is useful when doing something like a ‘fade to black’, without having to
alter each of the source colors directly.

This particular routine only works with solid colors. As such, it is just like the plain
hline routine. So why not have a flag on the hline routine that says to do a blend or not?
Better still, why not have a parameter which is an operation to perform between the bg and fg
pixel values? This is how a lot of libraries work, including good ol’ GDI. I think that overly
complicates matters though. Creating a general purpose routine will introduce a giant switch
statement, or a bunch of function pointers, or something else unworldly. For now, I know I need
only two operators, SRCOPY, SRCOVER, and that’s it, I don’t currently find need for more exotic
operators. But of course, anything can be built atop or easily changed, so who knows.

And that’s about it for the first two kinds of line drawing routines. The third kind, where the
slope is neither vertical nor horizontal, is a little more work, and thus will be saved for
another time.

Woot! Raise the roof up, horizontal and vertical lines, along with pixel copying. What more
could you possibly expect from a basic low level graphics system? Well, for the longest time,
not much more than this, but there’s still some modern conveniences to discuss, such as those
sloped lines, and some more primitives such as triangles.

Advertisements


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s