Virtual Developer Workshop: Containerized Development with Docker
Environment: VB5, VB6
Visual Basic allows us programmers to develop highly graphical applications easily. Even bitmaps and text can be added in a matter of seconds. We can access the API and get even more elaborate multimedia applications. Even complex animation and games are within our grasp once we know a little about Visual Basic.
The only way that Visual Basic offers is to read each pixel and determine whether its color matches the transparent color, usually by iterating it through a for loop. This is a very tedious process, though, and unless the picture is smaller than an average postage stamp, the function will spend several minutes executing.
Obviously, this method cannot be used, but let's consider what it does anyway. Comparing each pixel's color to the transparent color? Isn't that what we HAVE to do? We simply have to compare each pixel for this to be possible, so we must find a faster way. The problem is that comparing colors is a major task for the CPU. Comparing two sets of 16 bit colors means that the CPU must make 32 comparisons to make sure! If you have a 640x480 pixel image, this would amount to 9,830,400 compare operations. If you want it with 32-bit color, you would double it. And, on top of this, it has the bothersome job of copying each pixel individually. This is a bit simplified, but you still get the idea. This is hard and time-consuming work for the computer.
We have to reduce the amount of data. We need to get back to the basics, a map of zeros and ones; we need a mask. And because this is a graphic image, this will appear as a black and white image. Once we understand this concept, we can apply some boolean math; we can use AND, XOR, OR, NOT, and so forth. The only information the processor has to deal with is ones and zeroes, and this, ladies and gentlemen, this is what a processor is really good at.
Creating a mask can be accomplished by a little trick of the API. First, we load a bitmap where a specific color, usually some kind of ghastly pink, marks the transparent part. Then, we set the bitmap's background color, using the API, to this transparent color. Now, we create a monochrome bitmap, and copy the source into it. The result is quite neat. Everything in the transparent color is WHITE; anything else is BLACK. The mask is completed! But beware, there are more tricks with the black and white bitmap, so if you want to avoid weird bugs, I suggest that you copy the mask into an ordinary bitmap prior to using it.
So, how do we use the mask? The answer lies in the the BitBlt-functions OpCodes. There are a whole lot of these, but the two most important ones to us are vbMergePaint (OR) and vbSrcAnd (AND). When you use the first one with a mask, everything that is BLACK will be painted and the WHITE will remain. If you use the latter, it is the other way around.
Now, the inner workings of BitBlt. The API and boolean math can be a lot to swallow at one gulp, and a full walktrough would take several pages. I suggest reading the KB and insert breakpoints to watch what happens if anything is unclear. The description here may seem shallow, but the code is well commented and pretty short so you should get by it.
We start by preparing the background. We need to punch out a white "hole" in the background were we want the source graphics to be pasted in. This is accomplished by ORing the mask with the target using vbMergePaint.
Now we must clean the foreground. We need to punch out the transparent part and make it white. This is exactly as with the background, except that we want the opposite part to be "whited out." Can we do this? Sure, just invert the mask first! Then, we can repeat the success we had with vbMergePaint.
To finish it off, we can simply AND foreground into the background, and call it a job well done!
Using this function is pretty straightforward. Just pass the hDC of the target and and source bitmap, enter the desired coordinates, and off you go! An example project is included if you are still doubting. Good Luck!
Sub TransparentBlt(dsthDC As Long, srchDC As Long, X As Integer, _ Y As Integer, Width As Integer, _ Height As Integer, TransColor As Long) Dim maskDC As Long 'DC for the mask Dim tempDC As Long 'DC for temporary data Dim hMaskBmp As Long 'Bitmap for mask Dim hTempBmp As Long 'Bitmap for temporary data 'First, create some DC's. These are our gateways to associated 'bitmaps in RAM maskDC = CreateCompatibleDC(dsthDC) tempDC = CreateCompatibleDC(dsthDC) 'Then, we need the bitmaps. Note that we create a monochrome 'bitmap here! 'This is a trick we use for creating a mask fast enough. hMaskBmp = CreateBitmap(Width, Height, 1, 1, ByVal 0&) hTempBmp = CreateCompatibleBitmap(dsthDC, Width, Height) 'Then we can assign the bitmaps to the DCs hMaskBmp = SelectObject(maskDC, hMaskBmp) hTempBmp = SelectObject(tempDC, hTempBmp) 'Now we can create a mask. First, we set the background color 'to the transparent color; then we copy the image into the 'monochrome bitmap. 'When we are done, we reset the background color of the 'original source. TransColor = SetBkColor(srchDC, TransColor) BitBlt maskDC, 0, 0, Width, Height, srchDC, 0, 0, vbSrcCopy TransColor = SetBkColor(srchDC, TransColor) 'The first we do with the mask is to MergePaint it into the 'destination. 'This will punch a WHITE hole in the background exactly were 'we want the graphics to be painted in. BitBlt tempDC, 0, 0, Width, Height, maskDC, 0, 0, vbSrcCopy BitBlt dsthDC, X, Y, Width, Height, tempDC, 0, 0, vbMergePaint 'Now we delete the transparent part of our source image. To do 'this, we must invert the mask and MergePaint it into the 'source image. The transparent area will now appear as WHITE. BitBlt maskDC, 0, 0, Width, Height, maskDC, 0, 0, vbNotSrcCopy BitBlt tempDC, 0, 0, Width, Height, srchDC, 0, 0, vbSrcCopy BitBlt tempDC, 0, 0, Width, Height, maskDC, 0, 0, vbMergePaint 'Both target and source are clean. All we have to do is to AND 'them together! BitBlt dsthDC, X, Y, Width, Height, tempDC, 0, 0, vbSrcAnd 'Now all we have to do is to clean up after us and free system 'resources.. DeleteObject (hMaskBmp) DeleteObject (hTempBmp) DeleteDC (maskDC) DeleteDC (tempDC) End Sub