Please support our sponsor:
Welcome To DirectX 4 VB! Multimedia Visual Basic at its best...




DirectDraw: Alpha Blending with vbDABL
By: David Goodlad [Email] [Web: The Black Hole, TheNexus]
Written: July 2, 2000
Download:
vbDABL.zip (279kb)


Introduction
Many people wonder how to do alpha blending with DirectDraw in Visual Basic. If they've got a fair bit of knowledge, they'll try to do it using GetLockedArray along with CopyMemory. Yes, this method will work just fine. The only problem: it's *sloooow*. My solution: vbDABL: visual basic Directx Alpha Blending Library. This is an open-sourced DLL written mainly in inline assembly, with an outside 'shell' of C++, which provides various highly optimized functions to a Visual Basic programmer which perform alpha blending. Currently, it requires an MMX processor (most new Pentium processors, from about the P-233 up to the PIIIs) to run due to the use of packed data functions in the 64-bit registers that MMX provides. But, with this little restriction, which is basically negligible nowadays, a *massive* speed increase is gained. So, with this little bit of background info, let's get started...

Getting and Installing vbDABL
You can get vbDABL 0.20, which is the most recent release as of this writing, from my website which is linked above. It comes in a fairly small .ZIP file. When you unzip it, you will get two different directories: 'library' and 'sample'. What you should really be interested in is the 'library' directory, as it contains the precompiled DLL as well as the source code. All that you need to do if you don't want to recompile the DLL yourself (which you shouldn't need to do), is copy the \library\precompiled\Release\vbDABL.dll file to your \windows\system directory on Windows9x, or your \winnt\system32 directory on Windows NT (including 2000).

Once this is done, you will have to tell Visual Basic about the exported functions, as there is no Type Library for the DLL as there is for DirectX. Do so by adding the following function declarations to the '(Declarations)' section of a .BAS Module.
Public Declare Function vbDABLalphablend16 Lib "vbDABL" (ByVal iMode As Integer, ByVal bColorKey As Integer, _ 
     ByRef sPtr As Any, ByRef dPtr As Any, ByVal iAlphaVal As Integer, ByVal iWidth As Integer, ByVal iHeight As Integer, _
     ByVal isPitch As Integer, ByVal idPitch As Integer, ByVal iColorKey As Integer) As Integer

Public Declare Function vbDABLcolorblend16555 Lib "vbDABL" (ByRef sPtr As Any, ByRef dPtr As Any, ByVal alpha_val%, _
     ByVal Width%, ByVal Height%, ByVal sPitch%, ByVal dPitch%, ByVal rVal%, ByVal gVal%, ByVal bVal%) As Long

Public Declare Function vbDABLcolorblend16565 Lib "vbDABL" (ByRef sPtr As Any, ByRef dPtr As Any, ByVal alpha_val%, _    
     ByVal Width%, ByVal Height%, ByVal sPitch%, ByVal dPitch%, ByVal rVal%, ByVal gVal%, ByVal bVal%) As Long

Public Declare Function vbDABLcolorblend16555ck Lib "vbDABL" (ByRef sPtr As Any, ByRef dPtr As Any, ByVal alpha_val%, _
     ByVal Width%, ByVal Height%, ByVal sPitch%, ByVal dPitch%, ByVal rVal%, ByVal gVal%, ByVal bVal%) As Long

Public Declare Function vbDABLcolorblend16565ck Lib "vbDABL" (ByRef sPtr As Any, ByRef dPtr As Any, ByVal alpha_val%, _
     ByVal Width%, ByVal Height%, ByVal sPitch%, ByVal dPitch%, ByVal rVal%, ByVal gVal%, ByVal bVal%) As Long

For now, we will ignore the four 'colorblend' functions, and simply examine the plain old 'vbDABLalphablend16' function.

vbDABLalphablend16 parameters
I think the best way to get started is to look over each of the parameters of this function:

  • iMode: The only possible values for this parameter are '555' or '565'. It specifies how each pixel is formatted in memory. In 16-bit mode, there are two possible ways: 555, where there are 5 red bits, 5 green bits, and 5 blue bits, and 565 where there are 5 red bits, 6 green bits, and 5 blue bits. I'll show you how to detect this a little bit later...
  • bColorKey: This parameter tells vbDABL whether to use colorkeying (a transparent color which will not be copied) or not. If this is a '0' (zero), colorkying will NOT be used, any other number will enable colorkeying.
  • sPtr As Any: The 'pointer' to, or memory address of, the DirectDraw surface to be used as the source for the alpha blending. It is passed 'ByRef', so what you actually pass as the parameter is NOT a pointer, but what truly gets to the function IS. I'll explain how this works in more detail later...
  • dPtr As Any: The pointer to the DirectDraw surface to be used as the destination for the alpha blending. All the comments about sPtr apply to this parameter as well.
  • iAlphaVal: This is the 'alpha' value to use for the blend. Basically, it is a number between 0 and 255, with 0 making the resulting pixels (which end up on the destination surface) equal to the destination pixels, so there is no change at all, and 255 making the resulting pixels equal to the source pixels, essentially a plain blit from source to destination. Therefore, the closer you get to 255, the more the resulting pixel resembles the source pixel rather than the destination pixel. This may become clearer to you after I discuss the algorithm used for the blending later.
  • iWidth: The width (in pixels) of the area you want to blend.
  • iHeight: The height (once again, in pixels) of the area you want to blend.
  • isPitch: This is the 'pitch' of the source surface, as defined by DirectDraw. Basically it's the number of bytes per row of pixels in memory. There is a better definition and explanation in DirectX SDK documentation. All you need to know is that you can get it out of the DDSURFACEDESC2 structure filled out for you when you lock your surface.
  • idPitch: The pitch of the destination surface... All the isPitch stuff applies here as well.
  • iColorKey: Hmm, similar to the 'bColorKey' parameter, except that this one starts with an 'i'. The way I named many of the parameters is to use the first letter of the variable type as the first letter of the parameter; thus bColorKey is treated as a boolean (even though it's actually an integer...), and this one is a true integer. It is only used if bColorKey is not 0, therefore only if colorkeying is enabled. It is the numerical value of the color that you want to be transparent in your blend (if you aren't using colorkeying, just leave it as 0 or something, it really doesn't matter). It behaves the same way as a colorkey in a normal DirectDraw blit.


So How do I Use this!?
Okay, we've gotten through the boring technical stuff... But now you're wondering about how we actually use vbDABL to do alpha blending. You can't just call the function without setting some things up first, because it will have no way to access whatever memory you tell it to (you can't even get a pointer to the surface without setting stuff up properly!). So, what do we have to do to set everything up? Lock your surfaces, that's what! :) You should probably already be aware of this function and how it works, so I won't go into detail about it... Here's the code that will surround your call to vbDABL:
Dim rEmptyRect As RECT, dArray() As Byte, sArray() As Byte

ddsDestination.Lock rEmptyRect, ddsdDestination, DDLOCK_WAIT, 0
ddsSource.Lock rEmptyRect, ddsdSource, DDLOCK_WAIT, 0

ddsDestination.GetLockedArray dArray()
ddsSource.GetLockedArray sArray()

'Call to vbDABL goes here...

ddsSource.Unlock rEmptyRect
ddsDestination.Unlock rEmptyRect

As you can see, we've also used the GetLockedArray method of each of our surfaces to get an array of bytes which represents the raw data in the surface. Something important to note, though, is to NEVER, EVER forget to put the calls to Unlock in. If you do, you'll most likely end up with a crashed computer and have to physically turn it off and then on again... Don't say I didn't warn you! :) (If you must know, I've done this several times myself, so save yourself the trouble and learn from my mistakes!)

Now that we've locked our surfaces, and have locked arrays for each, we have everything that we need to make a call to vbDABLalphablend16. The locked arrays will be used as the pointers to the surfaces (yes, that's right.), and the pitches of the surfaces are in their respective DDSURFACEDESC2 structures. So, before I go on discussing how to use these things in the call properly, I will give you an example to look at and then refer back to:

Call vbDABLalphablend16 (555, 0, sArray(sX + sX, sY), dArray(dX + dX, dY), 200, 64, 32, ddsdSource.lPitch, ddsdDestination.lPitch, 0)

Okay, there's a few confusing things here. Firstly, there's my arbitrary decision to use 555 mode (the first parameter...). I will once again have to say I'm going to leave that till later. Secondly, there's the use of the arrays as pointers. It's a little difficult to explain why it works, but I'll explain what you have to do to MAKE it work. You pass the (x+x,y)'th element of your array to the function. Yes, confusing. I use x+x because there are two bytes per 16-bit pixel, right? (and a multiplication by 2 is slower than an addition!) The y just remains unchanged. The x and y values are the pixel coordinates of the first (ie: top left) pixel to be blended by vbDABL. Hopefully this is clear enough, it's difficult to make it any clearer. If you follow the example when making your calls, you will be fine.

Deciding Which Mode to Use
Finally I get to this part! :) The problem with 16-bit mode is that you can't divide 16 bits between three color channels evenly. Because of this, there has been debate in the video hardware industry as to how to divide up the available bits for each color. At first, it was generally agreed that in actuality only 15 bits should be used, to allow for an even distribution of the bits: 555 mode. But recently, the move has been made to use all 16 bits: 565 mode, giving the green channel double the degrees of accuracy of the two others. It doesn't give green more saturation, it just gives the green channel basically 'in-between' values. If you don't really understand, don't worry, I just like to give a bit of background info about why this works. It's not really necessary to know :)

Anyways, because of the differences between the two modes, vbDABL has to know which mode is being used by the client's video hardware. To figure this out is actually quite easy: you use the 'bitmask' of the green channel. It will be different for each of the different modes: &H3E0; in 555 mode, and &H7E0; in 565 mode. These are hexadecimal numbers, that's why they're formatted weird :) So, what I recommend doing is directly after your DirectDraw initialization, grab a DDSURFACEDESC2 structure from your surface, and store what mode the client is using in a global variable. That way, you can simply pass the value of this variable to vbDABL; no need to do a check each time you call it (when you're doing alphablending, speed is something you should always be thinking about). This is a simple implementation of the detection scheme I just described:

'Right after DirectDraw initialization...

Dim ddsdTemp As DDSURFACEDESC2

ddsPrimary.GetSurfaceDesc ddsdTemp
If ddsdTemp.ddpfPixelFormat.lGBitMask = &H3E0 Then
  ClientVidMode = 555
ElseIf ddsdTemp.ddpfPixelFormat.lGBitMask = &H7E0 Then
  ClientVidMode = 565
Else
  MsgBox "Client Video mode isn't 555 or 565, something's wrong."
  End
End If

Then, you would pass the ClientVidMode variable as the first parameter to vbDABL, and you'd be set!

What about those colorblend functions?
Now that you can do basic alpha blending, you're probably wondering what else there is to it? What are these colorblend functions that we declared earlier? Basically, they allow you to do a basic alpha blend with a twist: instead of blending the source and destination pixels and putting the result to the destination, you blend the source pixels with a specified color, and put the result to the destination; the destination pixels get overwritten and are never used in the blend! By doing this, you can achieve some nice effects, with the added benefit of a slight speed increase over the plain alpha blend as well as not having to have another surface simply full of a solid color. One obvious use of this function would be to 'colorize' a sprite: for example, in Final Fantasy 3 the characters would fade between a greenish tinge and their regular colors when they were poisoned. I have recreated this effect easily using the colorblend functions, as shown with the two pictures below. The first one is the original sprite graphic (ripped from FF3, sorry Square! hehe), and the second is with that graphic used as a replacement for the foreground.bmp in the vbdabl sample, with the colorblend color changed to (0,255,0) - all green.



Nice effect huh? You can fade the amount of green by changing the alpha value; simple as that! A second use for colorblend requires a bit of thought to discover because you first have to realize that the same surface can be passed as both the destination AND the source! In this way, you can basically do what would normally would require a plain alpha blend with the source surface being of a solid color and the destination being the stuff that you want the color blended on. Here's a picture straight out of the vbDABL sample project:



The red rectangle in the bottom-right of the picture is an area that has been 'colorblended' by using the background surface as both the destination and the source.

That's neat, but how do I use it??
It's quite simple, and very similar to the plain alpha blend. The only difference is that instead of having a single function that allows you to choose between the two modes, and whether or not to colorkey, there are four different functions; one for each combination. The next release of vbDABL will rememdy this for sure though :)

The parameters for each of the colorblend functions are very similar to the plain alphablend one. The only differences are that the first two parameters of the plain alphablend function are not present for the colorblend functions (they're not needed), and there's three extra parameters for specifying the color to blend with. These three represent the different color channels: red, green, and blue, and each have a range of 0-255. The thing to note about them, though, is that if you pass '5' as the red value, it will be interpreted as 0. This is because there are only 5 bits for red, so the values are always shifted to the right 3 bits (basically divided by 2^3, or 8). Therefore, there are only 256/8=32 degrees of accuracy for each color (with the exception of green in 565 mode; there are 64 degrees of accuracy for it in this case), so any number between 0-7 will be treated as 0, 8-15 will be treated as 8, etc. This is a limitation placed on us by the video mode, not by any calculations that vbDABL does. This is also the reason for the odd 'jumps' in color that you might notice in the vbDABL sample project, where when you've chosen to have the alpha value fade back and forth from 0-255, you see the color suddenly jump from one tone to another, not smoothly change.

Anyways, I apologize once again for all the theory, but it's a good thing to know when you're dealing with 16-bit mode and alpha blending. I won't discuss in any detail how to call the colorblend functions, as they're basically the same as the basic alphablend function with the few differences described above. Look at the sample that comes with vbDABL for some working code if you need it.

So how does this alpha blending actually work anyways?
Well, it's some very basic algebra actually. Here is the basic formula used for alpha blending:

FinalPixel = (SourcePixel * AlphaValue) + (DestinationPixel * (1 - AlphaValue))

This involves an alpha value between 0 and 1, using floating point numbers, which is SLOW. Integer math is much faster, so I use values from 0-255. Also, with some messing around with the formula, you find that you can get rid of one of the multiplications (which is the big bottleneck... a multiplication takes 40-50 times as long as an addition, subtraction, or bit shift!) and end up with this:

FinalPixel = (AlphaValue * (Source - Destination)) / 256 + Destination

The only problem left is that in asm it's annoying to deal with signed numbers (numbers that can be negative) when you are trying to maintain the right number of bits, etc etc. So, to ensure that the subtraction in the middle there never results in a negative, we add 256 to that inner bracket, and by algebraic rules, we subtract (AlphaValue * 256) / 256. We can cancel out the multiplication and division, and we finally end up with:

FinalPixel = (AlphaValue * (Source + 256 - Destination)) / 256 + Destination - AlphaValue

Voila! That's the exact formula that I used in vbDABL to do all the alpha blending (though with the colorblending the 'destination' color, which is the one specified by the user, remains constant, so there are some pre-calculations that can be done to give a slight speed boost...)

Wrapping Things Up...
Well, hopefully by now you should understand how to use vbDABL pretty well. If you need to look at some source code, I suggest opening up the sample project that comes in the same ZIP as the DLL. That should give you an idea of how it all comes together, though it's not extremely well commented yet. Most of the relevant code is in modDirectX under the 'RenderLoop' function.

If you have any questions/comments/suggestions/CONSTRUCTIVE criticism, please feel free to email me!

David Goodlad
 - webmaster: the black hole
   http://blackhole.thenexus.bc.ca/
   dgoodlad@junction.net

DirectX 4 VB © 2000 Jack Hoxley. All rights reserved.
Reproduction of this site and it's contents, in whole or in part, is prohibited,
except where explicitly stated otherwise.
Design by Mateo
Contact Webmaster
This site is hosted by Exhedra Solutions, Inc., the parent company of RentACoder.com and PlanetSourceCode.com