Resampling with Alpha
Use Alpha DIBSections to provide quality anti-aliased reduced bitmaps
If you want to change the size of a bitmap, you quickly find that standard techniques such as GDI's StretchBlt aren't particularly good and the result is usually very pixelated. Resampling is a technique which smooths the contribution of each pixel whilst changing the size, and although it takes more computation it provides a vastly better output. However, if you're trying to resize a bitmap with a transparent area, resampling can cause problems too because it tends to pull the "transparent" background colours into the image. This problem can be resolved by introducing alpha into the resampling method.
The Problem With Resampling and Transparent Bitmaps
Consider the following bitmap section:
Let's say you wanted to reduce it to 1/4 of its size. If you resampled it, you would end up with a new bitmap which was a combination of red and white. For the section above, you would normally end up with a single pixel which has 10/16 contribution from red and 6/16 contribution from white, or a pink-ish shade:
This technique works well. However, one time it isn't so good is if you're trying to use the result to draw over another image. If the original image's background colour (the white area) should have been regarded as transparent, then trying to draw the resampled version doesn't work properly:
Original image drawn transparently over a background
Resampled image drawn the same way
Clearly something isn't quite right. The resampled image has captured some of the background colour from the original image, and when it is drawn over the original background it no longer resembles the original drawn transparently. In larger images, you will normally see a "halo" appear around the image unless it was resampled with the background in place.
Deal With It *
To fix this problem, you can take advantage of an alpha channel when resampling the image. Rather than just resampling the pixels, you can then start modifying the resampled result so that where it should have elements of the background colour a proportion of the background will show through.
The first step is to set the alpha of all the background pixels to 0 (transparent), and then set all of the pixels to include to alpha 255 (opaque). If you take the alpha channel into account when resampling, the result works out like this instead:
If you apply the new resulting red colour with alpha, it blends with the background correctly:
Alpha resampled image rendered over background
* Deal With It is a great track from the Beastie Boys marvellous Aglio E Oglio EP. 1 min 58 of thrash which you can encode at 9.6kbps without losing any sound quality whatsoever.
Implementing The Algorithm
The simple case for implementing this algorithm occurs when the size of the resampled result is related to the size of the original by an integral divider in both directions. When that happens, each pixel in the original version only ever contributes towards one pixel in the result. As a consequence, the algorithm only ever has to visit each pixel in the source once to determine its contribution to the output. If on the other hand, the new size is a non-integral multiplier, a single pixel in the source can contribute to either 0,1,2 or 3 pixels in the destination depending on the multiplication factor.
Effect of different sizing ratios on resampling
One of the consequences of this is that for non-integral size ratios the resampling algorithm must build a temporary work area to calculate the contributions from each of the pixels. It is worth remembering this if you are using the algorithm - for example, often you can often add more transparent pixels to the source image to get an integral size. In any case, even if you prefer to use prime numbers for your image sizes you can still achieve a result and the algorithms typically both run in a sensible time.
The implementation of the simple resample algorithm for a DIB is shown below:
' Number of pixels in the result per pixel in ' the source: scaleX = m_tBI.bmiHeader.biWidth \ cDibTo.Width scaleY = m_tBI.bmiHeader.biHeight \ cDibTo.Height ' The number of pixels per output pixel is constant: lTotAlpha = scaleX * scaleY ' Number of scans to step in X in the source whilst ' building the result: xStep = (scaleX - 1) * 4 ' For each group of pixels in the source horizontally: For x = 0 To BytesPerScanLine - 4 Step 4 * scaleX yDest = 0 ' For each group of pixels in the source vertically: For y = 0 To m_tBI.bmiHeader.biHeight - 1 Step scaleY ' Start calculating the resampled values: lR = 0: lG = 0: lB = 0: lA = 0 lTotColour = 0 ' Loop through each source pixel which contributes ' to this output pixel: For i = 0 To xStep Step 4 For j = 0 To scaleY - 1 ' Get the alpha: lAMult = bDib(x + i + 3, y + j) ' If alpha is non-zero, add the result to the ' output: If (lAMult > 0) Then lB = lB + (bDib(x + i, y + j) * lAMult) \ 255 lG = lG + (bDib(x + i + 1, y + j) * lAMult) \ 255 lR = lR + (bDib(x + i + 2, y + j) * lAMult) \ 255 lTotColour = lTotColour + 1 End If lA = lA + lAMult Next j Next i ' Now we can calculate the resulting pixel and ' set it: If (lTotColour > 0) Then bDibTo(xDest, yDest) = (lB \ lTotColour) bDibTo(xDest + 1, yDest) = (lG \ lTotColour) bDibTo(xDest + 2, yDest) = (lR \ lTotColour) End If bDibTo(xDest + 3, yDest) = (lA \ lTotAlpha) yDest = yDest + 1 Next y xDest = xDest + 4 Next x
The sample code includes implementations of both the resampling algorithms and shows how to create a simple real-time animation using a resampled bitmap.
If you're working with bitmaps with transparent backgrounds and want to be able to resize them in a visually pleasing way, you should take advantage of the alpha channel. This article provides some code with good performance to allow you to do this easily.