Alpha-Blitting

First, let's start with an example to show why we want to use alpha-blitting. The image on the left is a standard drop-shadow, drawn darkening a white background. This is done directly, with a single-bit transparency: on or off. The image on the right uses an "alpha" channel to represent the shadow as a dark spot with varying opacity. They are both shown on a white and a grey background. Which do you think looks better?

Example
Alpha compositions: Drop shadows on two backgrounds (capture)

Okay, so the above is really just a screenshot of the actual effect, as IE doesn't yet display these correctly. I guess this means you can outdo IE, by properly implementing the one on the right.

Just take me to the algorithm! I don't care if I understand it! Or scroll on for an explanation.

Theory Behind Alpha Blitting

There are two big pieces to the idea of alpha blitting. If you're good at guessing your way to an A on multiple choice tests, you'll probably already have guessed them: alpha opacity and blitting. I'm going to talk about blitting first, because it's simpler. You've already done it. You may wish to jump ahead to the idea of alpha opacity instead.

Blitting

Blitting is really easy. You make a copy of an image in another place. You do it really fast. This is what allows you to draw a mouse cursor, or characters of a font, or just about anything you see in a graphical user environment. The RefreshScreen of MP3 was a form of blitting.

There's just one trick to a general blitting function: your source and destination formats may not be the same. For instance, when you go to draw the characters of the font to the screen, if you try to draw 16*16 pixels directly in order, the result will be awful.

Image Widths

Why? Simple. The widths of the source and destination images aren't actually 16. Nope, neither of them. In MP3, this kind of situation came up comparing the 4*4 piece with the 14*26 board, and again drawing the 14*26 board to the 80*25 screen. In each of those cases, however, the source width corresponded to the width you wanted to draw.

@ABC of font.png, with a box around A

In this MP, this won't always be the case. Back to the example of the font. In the image you can see a small section of the actual png, enlarged to be extra-visible. I've drawn a blue box around the section we want to copy.

Once we find the address corresponding to the upper-left pixel of that box (which is easy--just see the section on drawing text) and the address for the upper-left pixel of our destination, we're home free. Almost.

Source Width

The address upper-left corner pixel (0,0) of the letter A is 1040. If you have to ask how I got that, you haven't read the text section yet. (Have I given you the link enough yet?) Through a little deduction, you can easily realize that (1,0) is at 1040+1. But what about (0,1)? That takes a bit more specialized knowledge--how wide is the image? (Hint: it's 2048 pixels wide.) So do you now know where (0,1) of the letter A is? 1040+2048? Good.

Whoops! Wrong. But that's because I lied--the upper left corner is really at 4160 (1040*4), and the width is really 8192 (2048*4), because each pixel takes 4 bytes to describe. Don't you forget it!

Destination Width

How about drawing it to the screen? Simple. Lets say we've found that address 41024 (0A040h) or (16,16) is where we want to start. The pixel (17,16) is pretty obviously at 0A044h (remember the 4 bytes per pixel), but where is (16,17)?

Again, consider the width: 640*4. So pixel (16,17) is at 41024+640*4, or 0A040h+0A00h. Think you can properly align all the pixels from the source to the destination yet? I hope so! Now it's time to talk about the meaning of that dreaded word "alpha."

Alpha Opacity

A lot of people think of transparency when they hear the word "alpha" in the context of graphics. They're right, sorta. Alpha really refers to the opacity of a pixel, which is derived generally from the coverage of a pixel.

Figure A: Outlined Coverage of a Triangle on a pixel grid

What do I mean by the coverage of a pixel? Simple. Let's say you're drawing a triangle. The bottom and the left of the triangle are horizontal and vertical, respectively. The remaining side is at 45°. Now fill this triangle with a blue color. Now draw it to the screen. The outline, zoomed in, is shown in Figure A.

Uh-oh. Where are the 45° pixels? For that matter, if all these lines go through the centers of pixels, chances are the left and bottom and upper-right include a lot of half-covered pixels; the lower-left corner has a one-quarter covered pixel and the two remaining corners have about a one-eighth covered pixel, as visualized in Figure A. Imagine how bad this gets when you slide the triangle up by a third of a pixel.

The Over Operation

So how do you turn this into actual pixels? The simplest way, and the way we're using, is called the Over Operation. To draw color A with coverage α over color B, the resulting color C is calculated as follows:

Cr = (αr * Ar) + ((1-αr) * Br)
Cg = (αg * Ag) + ((1-αg) * Bg)
Cb = (αb * Ab) + ((1-αb) * Bb)

Or more succinctly, as we only have one α, apply this to each channel independently:

C = (α * A) + ((1-α) * B)
Figure B: Triangle Drawn with Over Compositions, alpha by coverage

When we apply this to the case as listed above, with coverage values one, one-half, one-quarter, and one-eighth, we get something that looks like Figure B, only not quite so zoomed in, and no grid.

Things like this are where alpha opacity values originally come from. Don't worry about how to calculate the alpha value, as we'll just be using images that come with an alpha value for you. Concentrate on how to apply it using the over operator.

Here's how the value for the lower-left corner pixel was calculated, assuming a white background (B) upon which you draw a pure blue pixel with 25% coverage:

C = (α * A) + ((1-α) * B)
C = (0.25 * {0.0, 0.0, 1.0}) + (0.75 * {1.0, 1.0, 1.0})
C = {0.0, 0.0, 0.25} + {0.75, 0.75, 0.75}
C = {0.75, 0.75, 1.0}

As you can see, the math is simple. But it does involve multiplications, and hence takes a long time. In the next section, we cover an algorithm optimized to use MMX instructions, thus calculating all four channels of two pixels at once.

The Actual Algorithm

The MMX enhanced algorithm does exactly what the sections above describe, only faster. And the code is harder to read. Before that, though, let's make one important optimization. Remember how multiplying takes a lot more time than adding? Well, here's what we can do:

C = (α * A) + ((1-α) * B)
C = (α * A) + B - (α * B)
C = (α * (A-B)) + B

Note how we changed two multiplies and two additions/subtractions into one multiply and two additions/subtractions. This will speed things up noticeably. Now that we've simplified our equation, let's harness the power of MMX (one of the few things it's good at!) for our algorithm.

Here's the pseudocode, adapted from Intel's very own code to perform Alpha Compositing into a 15 bit image. (Note: since the result in their code is 15 bits, and ours is 32, the code itself would likely be more confusing than helpful.)

So, what does this do? It pleases a former TA immensely, as this algorithm uses PUNPCKLBW--his favorite MMX opcode. No really!

More importantly, though, it allows you to calculate the alpha compositing of two pixels in under the time it would have taken you to composite two channels without MMX. You'll really get to see this when you calculate it per channel when drawing the Anti Aliased Line.