Highlighting and Shadowing Image List Images
Demonstrates how to produce XP Explorer-style icon highlighting effects using managed and unmanaged code.
For some reason, the System.Windows.Forms.ImageList does not provide a Draw method that allows an image to be drawn highlighted or selected, even though this is part of the basic functionality of the underlying ComCtl32 ImageList. This article provides a method of drawing selected and lightened images using Managed Code and also describes how to do it using unmanaged methods.
Highlighting Icons with Managed Code
There are two techniques you can use from Managed Code to create highlighting type effects:
I'll cover these in turn.
Gamma correction was developed to make it easier to adjust colours displayed on Cathode Ray Tube (CRT) displays. CRT displays produce a light intensity (luminance) proportional to the input voltage raised to a power. Since no two CRTs are exactly alike in their luminance characteristic, a way of adjusting the input image so the displayed colours match a reference is needed. This is done by adjusting the colours to be displayed by a power which is termed gamma.
Since gamma lightens or darkens the colours in an image it can be used for image processing effects as well as the normal colour profile adjustment. Gamma is normally restricted to the range 1/5 to 5, where a value less than 1 lightens the image, a value of 1 leaves the image unaffected and an value greater than 1 darkens the image:
Gamma correction in the .NET framework is provided by using the ImageAttributes class from the System.Drawing.Imaging namespace. Here's an example of how to use it to draw an image in VB.NET:
' Set up the gamma: Dim imageAttr As System.Drawing.Imaging.ImageAttributes = _ New System.Drawing.Imaging.ImageAttributes() imageAttr.SetGamma(0.5) ' Draw the image with the gamma applied: Dim img As Image = iconImageList.Images(iconIndex) graphics.DrawImage( _ img, _ destRect, _ 1, 1, iconImageList.ImageSize.Width, iconImageList.ImageSize.Height, _ GraphicsUnit.Pixel, imageAttr) ' Done. imageAttr.Dispose()
As the .NET Framework drawing code is based on GDI+, all drawing can take advantage of alpha blending. Unfortunately, there are two problems with this in terms of highlighting an icon:
Despite these limitations, it's possible to hack things to highlight just the coloured area of an icon. The technique works as follows:
Here's the VB.NET code which achieves this:
' Create an offscreen bitmap for working on. This is one bigger than ' the icon so we know for sure that the top row of pixels will be ' transparent Dim bm As Bitmap = New Bitmap( _ iconImageList.ImageSize.Width, iconImageList.ImageSize.Height + 1 _ ) Dim gfx As Graphics = graphics.FromImage(bm) ' Set the background colour to a colour that "won't" appear in the icon: Dim br As Brush = New SolidBrush(Color.FromArgb(254, 253, 254)) gfx.FillRectangle(br, 0, 0, bm.Width, bm.Height) br.Dispose() ' Draw the icon starting at the second row in the bitmap: iconImageList.Draw(gfx, 0, 1, iconIndex) ' Overdraw with the highlight colour: br = New SolidBrush(Color.FromArgb(highlightAmount, highlightColor)) gfx.FillRectangle(br, 0, 0, bm.Width, bm.Height) br.Dispose() gfx.Dispose() ' Now set up a colour mapping from the colour of the pixel ' at 0,0 to transparent: Dim imageAttr As System.Drawing.Imaging.ImageAttributes = _ New System.Drawing.Imaging.ImageAttributes() Dim map(0) As System.Drawing.Imaging.ColorMap map(0) = New System.Drawing.Imaging.ColorMap() map(0).OldColor = bm.GetPixel(0, 0) map(0).NewColor = Color.FromArgb(0, 0, 0, 0) imageAttr.SetRemapTable(map) If (gamma <> 1.0) Then imageAttr.SetGamma(1.0) End If ' Draw the image with the colour mapping, so that only the ' portion of the image with the new colour over the top ' gets mapped: graphics.DrawImage(bm, destRect, 1, 1, _ iconImageList.ImageSize.Width, iconImageList.ImageSize.Height, _ GraphicsUnit.Pixel, imageAttr) ' Done. imageAttr.Dispose() bm.Dispose()
Examples of the result of applying this effect are shown below:
You can combine the results of applying these effects in various ways, for example, a shadowed icon effect can easily be created by drawing a version of the icon which has been strongly alpha-colourised with the shadow colour underneath an icon.
Highlighting ImageList Icons with Unmanaged Code
The ComCtl32.DLL ImageList API provides ImageList_Draw, ImageList_DrawEx and ImageList_DrawIndirect methods, all of which can be used to highlight an icon.
If you link to v6.0 of ComCtl32.DLL using a Manifest on a Windows XP machine, highlighting is achieved using alpha blending. Earlier versions achieve the highlighting by using a dithered brush to draw over the icon.
This code demonstrates how to use the -Ex variety of the drawing routine to draw highlighted and cut icons:
Private Declare Function ImageList_DrawEx Lib "comctl32" ( _ ByVal hIml As IntPtr, _ ByVal i As Integer, _ ByVal hdcDst As IntPtr, _ ByVal x As Integer, _ ByVal y As Integer, _ ByVal dx As Integer, _ ByVal dy As Integer, _ ByVal rgbBk As Integer, _ ByVal rgbFg As Integer, _ ByVal fStyle As Integer) As Integer Private Const ILD_NORMAL As Integer = 0 Private Const ILD_TRANSPARENT As Integer = 1 Private Const ILD_BLEND25 As Integer = 2 Private Const ILD_SELECTED As Integer = 4 Private Const ILD_MASK As Integer = &H10 Private Const ILD_IMAGE As Integer = &H20
Here's how you would use this to draw highlighted and cut icons:
Dim hDC As IntPtr = gfx.GetHdc() Dim hIml As IntPtr = ilsIcons.Handle Dim rgbFg As Integer ' Highlighted: rgbFg = Color.FromKnownColor(KnownColor.Highlight) ImageList_DrawEx _ hIml, iconIndex, hdc, x, y, 0, 0, _ -1, rgbFg, _ ILD_TRANSPARENT or ILD_SELECTED ' Cut rgbFg = Color.White ImageList_DrawEx _ hIml, iconIndex, hdc, x, y, 0, 0, _ -1, rgbFg, _ ILD_TRANSPARENT or ILD_SELECTED gfx.ReleaseHdc(hDC);
This article has demonstrated how to work around the lack of icon highlighting in the .NET Framework API using either managed or unmanaged techniques.