Drawing disabled images / icons / buttons
If you've ever needed to draw images or icons as if they are disabled ("grayed out"), you will find the Win32 API to be a disappointment. DrawState with DST_ICON | DSS_DISABLED works but the images look much worse than the standard disabled images on Vista. And DrawThemeIcon with PBS_DISABLED does nothing special.
After a quick search, I found a simple function that draws a very nice disabled icon (from here: http://dvinogradov.blogspot.com/2007_01_01_archive.html). For our needs I had to support transparency for a given color (instead of icons using the alpha channel), so I customized that function to support this form of transparency.
If your source icon is actually using alpha transparency instead of a single-color transparency mechanism, then just skip the FillRect call. This function could be enhanced to detect that condition, but I'll leave that as an exercise for the reader.
And so I give you DrawDisabledIcon which will treat magenta (RGB(255,0,255)) as the transparent color:
//
// DrawDisabledIcon
//
// DrawState does a terrible job and looks much worse than a standard button looks on Vista
// so we use this custom alpha blending function to draw disabled icons
// and it matches the standard toolbar implementation pretty well. The bonus is that our custom
// draw icons now actually look better than standard ones on XP.
//
static void DrawDisabledIcon(HDC dc, CRect rect, HICON hIcon)
{
WTL::CDC dcMem(CreateCompatibleDC(dc));
BITMAPINFO bmi;
ZeroMemory(&bmi, sizeof(BITMAPINFO));
bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
bmi.bmiHeader.biWidth = rect.Width();
bmi.bmiHeader.biHeight = rect.Height();
bmi.bmiHeader.biPlanes = 1;
bmi.bmiHeader.biBitCount = 32;
bmi.bmiHeader.biCompression = BI_RGB;
bmi.bmiHeader.biSizeImage = bmi.bmiHeader.biWidth * bmi.bmiHeader.biHeight * 4;
VOID *pvBits;
WTL::CBitmap Bitmap(::CreateDIBSection(dcMem, &bmi, DIB_RGB_COLORS, &pvBits, NULL, 0));
WTL::CBitmapHandle PrevBitmap(dcMem.SelectBitmap(Bitmap));
// Fill the background with the transparency color so we can make it transparent later.
CRect rect2(0, 0, rect.Width(), rect.Height());
WTL::CBrush brush;
brush.CreateSolidBrush(RGB(255,0,255));
FillRect(dcMem, &rect2, brush);
// Actually draw the icon.
::DrawIconEx(dcMem, 0, 0, hIcon, rect.Width(), rect.Height(), 0, 0, DI_NORMAL);
// convert to grayscale
for (unsigned char *p = (unsigned char*)pvBits, *end = p + bmi.bmiHeader.biSizeImage; p < end; p += 4)
{
// Make RGB(255,0,255) transparent.
if( (p[2] == 255) && (p[1] == 0) && (p[0] == 255) )
{
p[0] = p[1] = p[2] = p[3] = 0;
}
else
{
// Gray = 0.3*R + 0.59*G + 0.11*B
p[0] = p[1] = p[2] = (static_cast<unsigned int>(p[2]) * 77 +
static_cast<unsigned int>(p[1]) * 150 +
static_cast<unsigned int>(p[0]) * 28) >> 8;
// And the Alpha channel
p[3] = 255;
}
}
BLENDFUNCTION BlendFunction;
BlendFunction.BlendOp = AC_SRC_OVER;
BlendFunction.BlendFlags = 0;
// Respect the full transparency in the source.
BlendFunction.AlphaFormat = AC_SRC_ALPHA;
// Make everything else about 50% transparent
BlendFunction.SourceConstantAlpha = 0x7F;
// use bitmap alpha
AlphaBlend(dc, rect.left, rect.top,
bmi.bmiHeader.biWidth,bmi.bmiHeader.biHeight,
dcMem, 0, 0,
bmi.bmiHeader.biWidth, bmi.bmiHeader.biHeight,
BlendFunction);
dcMem.SelectBitmap(PrevBitmap);
}
