Glow and Shadow Effects using Windows GDI
An article on creating glow and shadow effects using plain Windows GDI

Introduction
This article is about a simple but effective method of rendering soft-shadows and shape-glowing using plain Windows GDI. The goal here is to show the reader that not only GDI+ (from .NET) and different commercial 3rd-party graphical libraries are capable of doing this type of work, but it can also be done by means of applying simple image processing techniques on the selected image. I have found some resources here on The Code Project considering this topic but most of them are done using the new Microsoft GDI+ graphics library that is far away from the traditional Windows GDI API.
Background
Rendering glowing and soft-shadows probably looks like a hard thing to do, but it really isn't. We can start from the original bitmap and build the separate layers (new bitmaps) for, say shadow or glow. The point is that the original data (original bitmap) is drawn two times, and a simple blur technique is applied as an intermediate operation.
Shadows - Where Do They Come From?
Basically, from some source of light. They are the projection of an object on different surfaces. The original bitmap is copied (excluding the transparent pixels) to the new temporary bitmap. Usually some horizontal and vertical offsets are used to simulate shadow distance. This temporary bitmap is then low-pass filtered (usually blur kernel is applied) and copied to the final destination bitmap (excluding the transparent pixels). The original image is then again copied to the destination bitmap this time (excluding the transparent pixels). Here are the steps:
- Copy original bitmap to a new one of the same size but with the small offset (the shadow offset) and make all non-transparent pixels of the same color (the shadow color)
- Apply blurring on the shadow bitmap
- Render this new bitmap to the destination bitmap
- Render original bitmap to the destination bitmap (skipping all transparent pixels)
This method is explained through the CreateShadow(COLORREF transparentColor, COLORREF shadowColor)
function, as shown below:
void CreateShadow(COLORREF transparentColor, COLORREF shadowColor)
{
int i,j, k, l;
RECT rect = {0, 0, 300, 200};
// Create temporary DC and bitmap
HDC hDC = ::GetDC(NULL);
HDC hTempDC = ::CreateCompatibleDC(hDC);
HBITMAP hTempBitmap = CreateCompatibleBitmap(hDC, 300, 200);
HBITMAP hOldTempBitmap = (HBITMAP)::SelectObject(hTempDC, hTempBitmap);
HDC hTempDC2 = ::CreateCompatibleDC(hDC);
HBITMAP hTempBitmap2 = CreateCompatibleBitmap(hDC, 300, 200);
HBITMAP hOldTempBitmap2 = (HBITMAP)::SelectObject(hTempDC2, hTempBitmap2);
HDC hTempDC3 = ::CreateCompatibleDC(hDC);
HBITMAP hTempBitmap3 = CreateCompatibleBitmap(hDC, 300, 200);
HBITMAP hOldTempBitmap3 = (HBITMAP)::SelectObject(hTempDC3, hTempBitmap3);
::ReleaseDC(NULL, hDC);
// Clear background
HBRUSH hBgBrush = ::CreateSolidBrush(transparentColor);
::FillRect(hTempDC, &rect, hBgBrush);
::FillRect(hTempDC2, &rect, hBgBrush);
::BitBlt(hTempDC3, 0, 0, 300, 200, m_hBgDC, 0, 0, SRCCOPY);
::DeleteObject(hBgBrush);
// Create drawing on shadow DC
CreateDrawing(hTempDC2);
// Draw memory DC on temporary DC
int shadowOffset = 5;
::TransparentBlt(hTempDC, shadowOffset, shadowOffset, 300, 200,
hTempDC2, 0, 0, 300, 200, transparentColor);
// Get original bitmap
BITMAP bmpOrig;
GetObject(m_hMemBitmap, sizeof(BITMAP), &bmpOrig);
int sizeOrig = bmpOrig.bmWidthBytes * bmpOrig.bmHeight;
BYTE* pDataOrig = new BYTE[sizeOrig];
GetBitmapBits(m_hMemBitmap, sizeOrig, pDataOrig);
int bppOrig = bmpOrig.bmBitsPixel >> 3;
// Get source bitmap
BITMAP bmpSrc;
GetObject(hTempBitmap, sizeof(BITMAP), &bmpSrc);
int sizeSrc = bmpSrc.bmWidthBytes * bmpSrc.bmHeight;
BYTE* pDataSrc = new BYTE[sizeSrc];
GetBitmapBits(hTempBitmap, sizeSrc, pDataSrc);
int bppSrc = bmpSrc.bmBitsPixel >> 3;
// Get source2 bitmap
BITMAP bmpSrc2;
GetObject(hTempBitmap2, sizeof(BITMAP), &bmpSrc2);
int sizeSrc2 = bmpSrc2.bmWidthBytes * bmpSrc2.bmHeight;
BYTE* pDataSrc2 = new BYTE[sizeSrc2];
GetBitmapBits(hTempBitmap2, sizeSrc2, pDataSrc2);
int bppSrc2 = bmpSrc2.bmBitsPixel >> 3;
// Get source3 bitmap
BITMAP bmpSrc3;
GetObject(hTempBitmap3, sizeof(BITMAP), &bmpSrc3);
int sizeSrc3 = bmpSrc3.bmWidthBytes * bmpSrc3.bmHeight;
BYTE* pDataSrc3 = new BYTE[sizeSrc3];
GetBitmapBits(hTempBitmap3, sizeSrc3, pDataSrc3);
int bppSrc3 = bmpSrc3.bmBitsPixel >> 3;
// Get destination bitmap
BITMAP bmpDst;
GetObject(m_hShadowBitmap, sizeof(BITMAP), &bmpDst);
int sizeDst = bmpDst.bmWidthBytes * bmpDst.bmHeight;
BYTE* pDataDst = new BYTE[sizeDst];
GetBitmapBits(m_hShadowBitmap, sizeDst, pDataDst);
int bppDst = bmpDst.bmBitsPixel >> 3;
// Get transparent color
BYTE redTransparent = GetRValue(transparentColor);
BYTE greenTransparent = GetGValue(transparentColor);
BYTE blueTransparent = GetBValue(transparentColor);
// Get shadow color
BYTE redShadow = GetRValue(shadowColor);
BYTE greenShadow = GetGValue(shadowColor);
BYTE blueShadow = GetBValue(shadowColor);
// Copy source bitmap to destination bitmap using transparent color
int verticalOffset = 0;
int horizontalOffset;
int totalOffset;
BYTE red, green, blue;
for (i=0; i<bmpSrc.bmHeight; i++)
{
horizontalOffset = 0;
for (j=0; j<bmpSrc.bmWidth; j++)
{
// Calculate total offset
totalOffset = verticalOffset + horizontalOffset;
// Get source pixel
blue = pDataSrc[totalOffset];
green = pDataSrc[totalOffset+1];
red = pDataSrc[totalOffset+2];
// Check for transparent color
if ((red != redTransparent) || (green != greenTransparent) ||
(blue != blueTransparent))
{
// Set destination pixel
pDataSrc3[totalOffset] = blueShadow;
pDataSrc3[totalOffset+1] = greenShadow;
pDataSrc3[totalOffset+2] = redShadow;
}
// Increment horizontal offset
horizontalOffset += bppSrc;
}
// Increment vertical offset
verticalOffset += bmpSrc.bmWidthBytes;
}
// Create temporary bitmap
BYTE* pDataTemp = new BYTE[sizeDst];
memcpy(pDataTemp, pDataSrc3, sizeDst);
BYTE* pDataTemp2 = new BYTE[sizeDst];
memcpy(pDataTemp2, pDataSrc, sizeDst);
// Apply blur effect
int filterSize = 5;
int filterHalfSize = filterSize >> 1;
int filterX, filterY, filterOffset;
int resultRed, resultGreen, resultBlue;
int resultRed2, resultGreen2, resultBlue2;
verticalOffset = 0;
for (i=filterHalfSize; i<bmpDst.bmHeight-filterHalfSize; i++)
{
horizontalOffset = 0;
for (j=filterHalfSize; j<bmpDst.bmWidth-filterHalfSize; j++)
{
// Calculate total offset
totalOffset = verticalOffset + horizontalOffset;
if ((i>=filterHalfSize) && (i<bmpDst.bmHeight-filterHalfSize) &&
(j>=filterHalfSize) && (j<bmpDst.bmWidth-filterHalfSize))
{
// Clear result pixel
resultRed = resultGreen = resultBlue = 0;
resultRed2 = resultGreen2 = resultBlue2 = 0;
// Set vertical filter offset
filterY = verticalOffset;
// Apply filter
for (k=-filterHalfSize; k<=filterHalfSize; k++)
{
// Set horizontal filter offset
filterX = horizontalOffset;
for (l=-filterHalfSize; l<=filterHalfSize; l++)
{
// Calculate total filter offset
filterOffset = filterY + filterX;
// Calculate result pixel
resultBlue += pDataSrc3[filterOffset];
resultGreen += pDataSrc3[filterOffset+1];
resultRed += pDataSrc3[filterOffset+2];
resultBlue2 += pDataSrc[filterOffset];
resultGreen2 += pDataSrc[filterOffset+1];
resultRed2 += pDataSrc[filterOffset+2];
// Increment horizontal filter offset
filterX += bppDst;
}
// Increment vertical filter offset
filterY += bmpDst.bmWidthBytes;
}
// Set destination pixel
pDataTemp[totalOffset] = resultBlue / (filterSize*filterSize);
pDataTemp[totalOffset+1] = resultGreen / (filterSize*filterSize);
pDataTemp[totalOffset+2] = resultRed / (filterSize*filterSize);
pDataTemp2[totalOffset] = resultBlue2 / (filterSize*filterSize);
pDataTemp2[totalOffset+1] = resultGreen2 / (filterSize*filterSize);
pDataTemp2[totalOffset+2] = resultRed2 / (filterSize*filterSize);
}
// Increment horizontal offset
horizontalOffset += bppDst;
}
// Increment vertical offset
verticalOffset += bmpDst.bmWidthBytes;
}
// Copy shadow bitmap to destination bitmap
verticalOffset = 0;
double alpha=1.0, alpha_koef;
double shadow_default_intensity = (redShadow + greenShadow + blueShadow) / 3;
double shadow_intenzity, shadow_koef;
for (i=0; i<bmpDst.bmHeight; i++)
{
horizontalOffset = 0;
for (j=0; j<bmpDst.bmWidth; j++)
{
// Calculate total offset
totalOffset = verticalOffset + horizontalOffset;
// Check for transparent color
if ((pDataTemp2[totalOffset+2] != redTransparent) ||
(pDataTemp2[totalOffset+1] != greenTransparent) ||
(pDataTemp2[totalOffset] != blueTransparent))
{
// Calculate shadow transparency
shadow_intenzity = (pDataTemp2[totalOffset] +
pDataTemp2[totalOffset+1] + pDataTemp2[totalOffset+2]) / 3;
shadow_koef =
(shadow_intenzity - 255) / (shadow_default_intensity - 255);
if (fabs(shadow_koef) > 0.5)
shadow_koef = 0.5 * (fabs(shadow_koef) / shadow_koef);
if (shadow_default_intensity == 255)
alpha_koef = 0.0;
else
alpha_koef = alpha * shadow_koef;
// Calculate destination pixel
blue = (BYTE)(alpha_koef*pDataTemp[totalOffset] +
(1.0-alpha_koef)*pDataDst[totalOffset]);
green = (BYTE)(alpha_koef*pDataTemp[totalOffset+1] +
(1.0-alpha_koef)*pDataDst[totalOffset+1]);
red = (BYTE)(alpha_koef*pDataTemp[totalOffset+2] +
(1.0-alpha_koef)*pDataDst[totalOffset+2]);
// Set destination pixel
pDataSrc3[totalOffset] = blue;
pDataSrc3[totalOffset+1] = green;
pDataSrc3[totalOffset+2] = red;
}
else
{
// Set destination pixel
pDataSrc3[totalOffset] = pDataDst[totalOffset];
pDataSrc3[totalOffset+1] = pDataDst[totalOffset+1];
pDataSrc3[totalOffset+2] = pDataDst[totalOffset+2];
}
// Increment horizontal offset
horizontalOffset += bppDst;
}
// Increment vertical offset
verticalOffset += bmpDst.bmWidthBytes;
}
// Set destination bitmap
::SetBitmapBits(m_hShadowBitmap, sizeDst, pDataSrc3);
// Destroy buffers
delete pDataOrig;
delete pDataTemp;
delete pDataTemp2;
delete pDataSrc;
delete pDataSrc2;
delete pDataSrc3;
delete pDataDst;
// Destroy temporary DC and bitmap
if (hTempDC)
{
::SelectObject(hTempDC, hOldTempBitmap);
::DeleteDC(hTempDC);
::DeleteObject(hTempBitmap);
}
if (hTempDC2)
{
::SelectObject(hTempDC2, hOldTempBitmap2);
::DeleteDC(hTempDC2);
::DeleteObject(hTempBitmap2);
}
if (hTempDC3)
{
::SelectObject(hTempDC3, hOldTempBitmap3);
::DeleteDC(hTempDC3);
::DeleteObject(hTempBitmap3);
}
// Create drawing on shadow DC
CreateDrawing(m_hShadowDC);
}
Glowing - Where Am I Then?
A similar story goes for object glowing effect. It appears as if there is a shining aura around the object. The steps are almost the same:
- Copy original bitmap to a new one of the same size but with small offsets in all four directions (the glowing directions) and make all non-transparent pixels of the same color (the glowing color)
- Apply blurring on the glowing bitmap
- Render this new bitmap to the destination bitmap
- Render original bitmap to the destination bitmap (skipping all transparent pixels)
This method is explained through the CreateGlow(COLORREF transparentColor, COLORREF glowColor)
function, as shown below:
void CreateGlow(COLORREF transparentColor, COLORREF glowColor)
{
int i,j, k, l;
RECT rect = {0, 0, 300, 200};
// Create temporary DC and bitmap
HDC hDC = ::GetDC(NULL);
HDC hTempDC = ::CreateCompatibleDC(hDC);
HBITMAP hTempBitmap = CreateCompatibleBitmap(hDC, 300, 200);
HBITMAP hOldTempBitmap = (HBITMAP)::SelectObject(hTempDC, hTempBitmap);
HDC hTempDC2 = ::CreateCompatibleDC(hDC);
HBITMAP hTempBitmap2 = CreateCompatibleBitmap(hDC, 300, 200);
HBITMAP hOldTempBitmap2 = (HBITMAP)::SelectObject(hTempDC2, hTempBitmap2);
HDC hTempDC3 = ::CreateCompatibleDC(hDC);
HBITMAP hTempBitmap3 = CreateCompatibleBitmap(hDC, 300, 200);
HBITMAP hOldTempBitmap3 = (HBITMAP)::SelectObject(hTempDC3, hTempBitmap3);
::ReleaseDC(NULL, hDC);
// Clear background
HBRUSH hBgBrush = ::CreateSolidBrush(transparentColor);
::FillRect(hTempDC, &rect, hBgBrush);
::FillRect(hTempDC2, &rect, hBgBrush);
::BitBlt(hTempDC3, 0, 0, 300, 200, m_hBgDC, 0, 0, SRCCOPY);
::DeleteObject(hBgBrush);
// Create drawing on glow DC
CreateDrawing(hTempDC2);
// Draw memory DC on temporary DC
int glowingOffset = 4;
::TransparentBlt(hTempDC, -glowingOffset, 0, 300, 200, hTempDC2,
0, 0, 300, 200, transparentColor);
::TransparentBlt(hTempDC, glowingOffset, 0, 300, 200, hTempDC2,
0, 0, 300, 200, transparentColor);
::TransparentBlt(hTempDC, 0, -glowingOffset, 300, 200, hTempDC2,
0, 0, 300, 200, transparentColor);
::TransparentBlt(hTempDC, 0, glowingOffset, 300, 200, hTempDC2,
0, 0, 300, 200, transparentColor);
// Get original bitmap
BITMAP bmpOrig;
GetObject(m_hMemBitmap, sizeof(BITMAP), &bmpOrig);
int sizeOrig = bmpOrig.bmWidthBytes * bmpOrig.bmHeight;
BYTE* pDataOrig = new BYTE[sizeOrig];
GetBitmapBits(m_hMemBitmap, sizeOrig, pDataOrig);
int bppOrig = bmpOrig.bmBitsPixel >> 3;
// Get source bitmap
BITMAP bmpSrc;
GetObject(m_hMemBitmap, sizeof(BITMAP), &bmpSrc);
int sizeSrc = bmpSrc.bmWidthBytes * bmpSrc.bmHeight;
BYTE* pDataSrc = new BYTE[sizeSrc];
GetBitmapBits(hTempBitmap, sizeSrc, pDataSrc);
int bppSrc = bmpSrc.bmBitsPixel >> 3;
// Get source2 bitmap
BITMAP bmpSrc2;
GetObject(hTempBitmap2, sizeof(BITMAP), &bmpSrc2);
int sizeSrc2 = bmpSrc2.bmWidthBytes * bmpSrc2.bmHeight;
BYTE* pDataSrc2 = new BYTE[sizeSrc2];
GetBitmapBits(hTempBitmap2, sizeSrc2, pDataSrc2);
int bppSrc2 = bmpSrc2.bmBitsPixel >> 3;
// Get source3 bitmap
BITMAP bmpSrc3;
GetObject(hTempBitmap3, sizeof(BITMAP), &bmpSrc3);
int sizeSrc3 = bmpSrc3.bmWidthBytes * bmpSrc3.bmHeight;
BYTE* pDataSrc3 = new BYTE[sizeSrc3];
GetBitmapBits(hTempBitmap3, sizeSrc3, pDataSrc3);
int bppSrc3 = bmpSrc3.bmBitsPixel >> 3;
// Get destination bitmap
BITMAP bmpDst;
GetObject(m_hGlowBitmap, sizeof(BITMAP), &bmpDst);
int sizeDst = bmpDst.bmWidthBytes * bmpDst.bmHeight;
BYTE* pDataDst = new BYTE[sizeDst];
GetBitmapBits(m_hGlowBitmap, sizeDst, pDataDst);
int bppDst = bmpDst.bmBitsPixel >> 3;
// Get transparent color
BYTE redTransparent = GetRValue(transparentColor);
BYTE greenTransparent = GetGValue(transparentColor);
BYTE blueTransparent = GetBValue(transparentColor);
// Get glow color
BYTE redGlow = GetRValue(glowColor);
BYTE greenGlow = GetGValue(glowColor);
BYTE blueGlow = GetBValue(glowColor);
// Copy source bitmap to destination bitmap using transparent color
int verticalOffset = 0;
int horizontalOffset;
int totalOffset;
BYTE red, green, blue;
for (i=0; i<bmpSrc.bmHeight; i++)
{
horizontalOffset = 0;
for (j=0; j<bmpSrc.bmWidth; j++)
{
// Calculate total offset
totalOffset = verticalOffset + horizontalOffset;
// Get source pixel
blue = pDataSrc[totalOffset];
green = pDataSrc[totalOffset+1];
red = pDataSrc[totalOffset+2];
// Check for transparent color
if ((red != redTransparent) || (green != greenTransparent) ||
(blue != blueTransparent))
{
// Set destination pixel
pDataSrc3[totalOffset] = blueGlow;
pDataSrc3[totalOffset+1] = greenGlow;
pDataSrc3[totalOffset+2] = redGlow;
}
// Increment horizontal offset
horizontalOffset += bppSrc;
}
// Increment vertical offset
verticalOffset += bmpSrc.bmWidthBytes;
}
// Create temporary bitmap
BYTE* pDataTemp = new BYTE[sizeDst];
memcpy(pDataTemp, pDataSrc3, sizeDst);
BYTE* pDataTemp2 = new BYTE[sizeDst];
memcpy(pDataTemp2, pDataSrc, sizeDst);
// Apply blur effect
int filterSize = 11;
int filterHalfSize = filterSize >> 1;
int filterHorizontalOffset = filterHalfSize * bppDst;
int filterVerticalOffset = filterHalfSize * bmpSrc.bmWidthBytes;
int filterTotalOffset = filterVerticalOffset + filterHorizontalOffset;
int filterX, filterY, filterOffset;
int resultRed, resultGreen, resultBlue;
int resultRed2, resultGreen2, resultBlue2;
verticalOffset = 0;
for (i=filterHalfSize; i<bmpDst.bmHeight-filterHalfSize; i++)
{
horizontalOffset = 0;
for (j=filterHalfSize; j<bmpDst.bmWidth-filterHalfSize; j++)
{
// Calculate total offset
totalOffset = verticalOffset + horizontalOffset;
if ((i>=filterHalfSize) && (i<bmpDst.bmHeight-filterHalfSize) &&
(j>=filterHalfSize) && (j<bmpDst.bmWidth-filterHalfSize))
{
// Clear result pixel
resultRed = resultGreen = resultBlue = 0;
resultRed2 = resultGreen2 = resultBlue2 = 0;
// Set vertical filter offset
filterY = verticalOffset;
// Apply filter
for (k=-filterHalfSize; k<=filterHalfSize; k++)
{
// Set horizontal filter offset
filterX = horizontalOffset;
for (l=-filterHalfSize; l<=filterHalfSize; l++)
{
// Calculate total filter offset
filterOffset = filterY + filterX;
// Calculate result pixel
resultBlue += pDataSrc3[filterOffset];
resultGreen += pDataSrc3[filterOffset+1];
resultRed += pDataSrc3[filterOffset+2];
resultBlue2 += pDataSrc[filterOffset];
resultGreen2 += pDataSrc[filterOffset+1];
resultRed2 += pDataSrc[filterOffset+2];
// Increment horizontal filter offset
filterX += bppDst;
}
// Increment vertical filter offset
filterY += bmpDst.bmWidthBytes;
}
// Set destination pixel
pDataTemp[totalOffset+filterTotalOffset] =
resultBlue / (filterSize*filterSize);
pDataTemp[totalOffset+1+filterTotalOffset] =
resultGreen / (filterSize*filterSize);
pDataTemp[totalOffset+2+filterTotalOffset] =
resultRed / (filterSize*filterSize);
pDataTemp2[totalOffset+filterTotalOffset] =
resultBlue2 / (filterSize*filterSize);
pDataTemp2[totalOffset+1+filterTotalOffset] =
resultGreen2 / (filterSize*filterSize);
pDataTemp2[totalOffset+2+filterTotalOffset] =
resultRed2 / (filterSize*filterSize);
}
// Increment horizontal offset
horizontalOffset += bppDst;
}
// Increment vertical offset
verticalOffset += bmpDst.bmWidthBytes;
}
// Copy glow bitmap to destination bitmap
verticalOffset = 0;
double alpha=1.0, alpha_koef;
double glow_default_intensity = (redGlow + greenGlow + blueGlow) / 3;
double glow_intenzity, glow_koef;
for (i=0; i<bmpDst.bmHeight; i++)
{
horizontalOffset = 0;
for (j=0; j<bmpDst.bmWidth; j++)
{
// Calculate total offset
totalOffset = verticalOffset + horizontalOffset;
// Check for transparent color
if ((pDataTemp2[totalOffset+2] !=
redTransparent) || (pDataTemp2[totalOffset+1]
!= greenTransparent) || (pDataTemp2[totalOffset] != blueTransparent))
{
// Calculate glow transparency
glow_intenzity = (pDataTemp2[totalOffset] + pDataTemp2[totalOffset+1] +
pDataTemp2[totalOffset+2]) / 3;
glow_koef = (glow_intenzity - 255) / (glow_default_intensity - 255);
if (fabs(glow_koef) > 0.5)
glow_koef = 0.5 * (fabs(glow_koef) / glow_koef);
if (glow_default_intensity == 255)
alpha_koef = 0.0;
else
alpha_koef = alpha * glow_koef;
// Calculate destination pixel
blue = (BYTE)(alpha_koef*pDataTemp[totalOffset] +
(1.0-alpha_koef)*pDataDst[totalOffset]);
green = (BYTE)(alpha_koef*pDataTemp[totalOffset+1] +
(1.0-alpha_koef)*pDataDst[totalOffset+1]);
red = (BYTE)(alpha_koef*pDataTemp[totalOffset+2] +
(1.0-alpha_koef)*pDataDst[totalOffset+2]);
// Set destination pixel
pDataSrc3[totalOffset] = blue;
pDataSrc3[totalOffset+1] = green;
pDataSrc3[totalOffset+2] = red;
}
else
{
// Set destination pixel
pDataSrc3[totalOffset] = pDataDst[totalOffset];
pDataSrc3[totalOffset+1] = pDataDst[totalOffset+1];
pDataSrc3[totalOffset+2] = pDataDst[totalOffset+2];
}
// Increment horizontal offset
horizontalOffset += bppDst;
}
// Increment vertical offset
verticalOffset += bmpDst.bmWidthBytes;
}
// Set destination bitmap
::SetBitmapBits(m_hGlowBitmap, sizeDst, pDataSrc3);
// Destroy buffers
delete pDataOrig;
delete pDataTemp;
delete pDataTemp2;
delete pDataSrc;
delete pDataSrc2;
delete pDataSrc3;
delete pDataDst;
// Destroy temporary DC and bitmap
if (hTempDC)
{
::SelectObject(hTempDC, hOldTempBitmap);
::DeleteDC(hTempDC);
::DeleteObject(hTempBitmap);
}
if (hTempDC2)
{
::SelectObject(hTempDC2, hOldTempBitmap2);
::DeleteDC(hTempDC2);
::DeleteObject(hTempBitmap2);
}
if (hTempDC3)
{
::SelectObject(hTempDC3, hOldTempBitmap3);
::DeleteDC(hTempDC3);
::DeleteObject(hTempBitmap3);
}
// Create drawing on glow DC
CreateDrawing(m_hGlowDC);
}
So, Where is the Difference Then?
The algorithms are almost the same except for one thing: you make just one/two offset[s] (horizontal and/or vertical) when you create the shadow effect, but four offsets or more (in all possible directions) when you create the glowing effect. Applying the blur kernel is very important since it makes shadows and glowing appear more realistic on the final bitmap.
The Blurring Kernel
The blurring kernel in this example is the following one (also called the lowpass-filter):
{[1 1 1][1 1 1][1 1 1]} * 1/9
So, the sum of the 9 surrounding source pixels is averaged (divided with the number of pixels) and the result represents the color of the destination pixel.
Points of Interest
I am very interested in simple image processing techniques like the ones shown in this article, by using basic graphical APIs, like Windows GDI.
History
- 28th October, 2007: Initial post