Introduction
As time goes on and applications evolve, the end user expects a richer and more vibrant user interface. Not only is it visually appealing, a nice user interface also serves as a function. Better design and use of icons to represent tasks will not only make your application stand out but will also give the end user a better experience working with your applications. This is the reason why I decided to publish this article. Now, let's jump to the technical aspects...
We've all seen those nice toolbar icons Microsoft (c) uses in their products. They include shadows, 24 bit true colors, transparencies etc. These images are actually 24 bit images that contain an alpha channel which makes them 32 bit format. This format not only allows transparent pixels, but semi-transparent pixels as well. This allows images to blend in with their background nicely without the "jaggedness" you sometimes see with regular 24 bit transparent images. If you have ever tried using such icons or images in your applications, you have quickly found out that it's somewhat more of a challenge than you expected, and in fact there are some obstacles to overcome. Not to mention a bug that exists in the framework that will prevent you from displaying such images properly. In this article, I will try to explain what is needed to overcome these obstacles and also how to use my ImageList
class to fix the problem. From this point on, I will refer to both icons and images as just images and also refer to 24 bit alpha blended images as just 32 bit images.
The Problem
The bug I mentioned above, I believe, exists in the ImageList
component itself. If you try to add a 32 bit image using the Images.Add()
method of the ImageList
control, or try to add images using the Visual Studio IDE, you will find that the alpha channel becomes either corrupt or lost all together based on the raw format of the image file itself. An example of this would be to create a new Bitmap
object and load a 32 bit image such as a .png file, using the Bitmap
constructor like: Bitmap bm = new Bitmap( file );
, and then using the ImageList
. Add method to add the bitmap object to the ImageList
. Even though the bitmap's .IsAlphaFormat
property is true
, the image becomes corrupt in the process of adding to the ImageList
control, and if you try to use these images on controls, you will find that they are displayed incorrectly. My belief is that the problem exists in the way the .Add()
method extracts the alpha channel and copies it to the ImageList
from a bitmap structure. The reason why I came to this conclusion was because supplying a handle to an image with the .Add()
method seems to work. You will see I use this method for Icons (.ico files) since the Icon
class has a .FromHandle
method that returns a Windows icon.
Now, if you try using these added images for your controls, you will find that since the alpha channel has become corrupt, the images contain solid black for the areas where alpha blending was to take place. In cases where you add PNG files with alpha channel, you will find that alpha blending does occur but contains a subtle color error in the areas where alpha blending was to take place. You can see a good example of this in the included image above by looking at the zoomed IE and MSN icon on the left. This might not be an eye catching thing for these 32x32 pixel icons but when you have 16x16 icons, the color error is very apparent. In conclusion, the ImageList
control itself can hold 32 bit alpha blended images with no problems but the methods used to add those images are causing the corruption to occur.
The Fix
I have come up with a simple class with some static methods to perform the adding of 32 bit images to your ImageList
s. The class Imagelist
and its five methods allow you to add images correctly from various sources. These sources include an Image
or Icon
object, an external image file or icon file, an embedded image resource in your project, any external file icon or folder icon, and any file system extension icon. Each of these have examples and explanations below. Various methods are used for adding images from the different sources, but for the main part, Win32 API is used. When adding images from external files or from an Image
object, basically what is done is a new bitmap is created using the CreateDIBSection()
API call which resides in the gdi32.dll, and creates a bitmap object and returns a handle to the bitmap object in memory. Then the image is copied into this area in memory with correct formatting. The bitmap is then loaded into the ImageList
using the ImageList_Add()
call which takes a handle to an ImageList
control and a handle to a bitmap object. When adding Icon
objects or files, you can add them correctly by using the Icon
object's .Handle
property. Once the images are loaded into the ImageList
control, you can use them normally for toolbars, listviews, etc. by assigning the indexer to a control. Also, all of its native methods hold and return correct values such as the .Count
property.
Before using the code
First and foremost, your C# application and control properties must be setup correctly before the images are displayed properly. Let's go over these first before moving onto the usage of the class. You will see below that the Application.EnableVisualStyles()
and Application.DoEvents()
must be made before a .Run
method. Another crucial property that must be set is the ImageList
ColorDepth
property, which must be set to ColorDepth.Depth32Bit
. You can of course set this property using the Visual Studio IDE. Also keep in mind that alpha blended images are only supported in Windows XP+ and .NET Framework 1.1+. This code will have no effect on earlier versions of Windows.
static void Main()
{
Application.EnableVisualStyles();
Application.DoEvents();
myImagelist.ColorDepth = ColorDepth.Depth32Bit;
Application.Run(new Form1());
}
Public Methods of the ImageList Class
public sealed class Imagelist
{
public static void AddFromImage( Image sourceImage,
ImageList destinationImagelist ){..}
public static void AddFromImage( Icon sourceIcon,
ImageList destinationImagelist ){..}
public static void AddFromFile( string fileName,
ImageList destinationImagelist ){..}
public static void AddIconOfFile( string fileName, IconSize iconSize,
bool selectedState, bool openState,
bool linkOverlay, ImageList destinationImagelist ){..}
public static void AddIconOfFile( string fileName,
ImageList destinationImagelist ){..}
public static void AddIconOfFileExt( string fileExtension,
IconSize iconSize, bool selectedState, bool openState,
bool linkOverlay , ImageList destinationImagelist ){..}
public static void AddIconOfFileExt( string fileExtension,
ImageList destinationImagelist ){..}
public static void AddEmbeddedResource( string resourceName,
ImageList destinationImagelist){..}
}
Now that those calls and properties have been made, we can move on to the usage of the static class methods....
Adding Image or Icon objects
Narbware.Imagelist.AddFromImage( Image.FromFile("image.png"),
myImagelist );
Bitmap myBitmap = new Bitmap( "image.png" );
Narbware.Imagelist.AddFromImage( myBitmap, myImagelist );
Icon myIcon = new Icon("icon.ico");
Narbware.Imagelist.AddFromImage( myIcon, myImagelist );
Adding images from embedded resources
The importance of this method is that you can use it to pre load images to the ImageList
from an embedded resource. This means no external image files are needed when wanting preloaded images in your ImageList
. This is because you cannot use the IDE to pre load images because that essentially uses the ImageList.Add()
method. An important step when adding embedded resources is after you have added the image resource to your project, you must set its Build Action property to 'Embedded Resource'. Once that is done, the code below will add that resource to your ImageList
.
Narbware.Imagelist.AddEmbeddedResource( "myApplicationName.image.png",
myImagelist );
Adding images from external files
This method saves some time when wanting to add images from files that reside outside of your project's directory. Since you don't have to create an Image
, load the file into the Image
, then add it to the ImageList
. Instead, you can just supply the path of the image you want to add.
Narbware.Imagelist.AddFromFile( "c:\\blah\image.png", myImagelist );
Narbware.Imagelist.AddFromFile( "c:\\icons\icon.ico", myImagelist );
Extracting and adding file or folder icons
This method extracts file or folder icons and adds them to ImageList
. This is a powerful method that the framework currently does not support and can be appreciated in any application dealing with the shell. This method has two overloads, a simple overload which, by default, will add a large sized icon to an ImageList
, and a second overload which gives you the ability to format the icon you want to extract. For example, adding the selected state of the icon, or adding the icon with a link overlay, specifying the size of the icon you want to extract etc...
Narbware.Imagelist.AddIconOfFile( "c:\\file.mpg",
myImagelist )
Narbware.Imagelist.AddIconOfFile( "c:\\file.mpg",
Narbware.IconSize.Small, false, false,
false, myImagelist );
Narbware.Imagelist.AddIconOfFile( "c:\mySpecialFolder",
Narbware.IconSize.Large, false, false,
false, myImagelist );
Extracting and adding file type icons (extensions)
This method is similar to the above AddIconOfFile()
method, instead you supply the method with the extension of the icon you want to extract from the system. All the rules from the above method hold for this method. Examples below...
Narbware.Imagelist.AddIconOfFileExt( "jpg", myImagelist );
Narbware.Imagelist.AddIconOfFileExt( "zip", Narbware.IconSize.Small,
false, false, false, myImagelist );
Points of Interest
Below is the private
method used to add the 32 bit images to ImageList
s. This is essentially the fix to the problem of the corrupt alpha channel. Note that a new Device Independent Bitmap (DIB) object is created with correct formatting based on the BITMAPINFO
structure, and the desired bitmap is then copied to this location and added to our ImageList
using Win32 API. The other overloaded Add()
method is used to extract file icons which is done by creating and passing a SHFileInfo
object and returning a handle to the file icon at which point it is added using the default ImageList
's Images.Add();
method.
private static void Add( Bitmap bm, ImageList il )
{
IntPtr hBitmap, ppvBits;
BITMAPINFO bmi = new BITMAPINFO();
if ( bm.Size != il.ImageSize )
{
bm = new Bitmap( bm, il.ImageSize.Width, il.ImageSize.Height );
}
bmi.biSize = 40;
bmi.biBitCount = 32;
bmi.biPlanes = 1;
bmi.biWidth = bm.Width;
bmi.biHeight = bm.Height;
bm.RotateFlip( RotateFlipType.RotateNoneFlipY );
hBitmap = CreateDIBSection( new IntPtr(0), bmi, 0,
out ppvBits, new IntPtr(0), 0 );
BitmapData bitmapData = bm.LockBits( new Rectangle( 0, 0,
bm.Width, bm.Height ), ImageLockMode.ReadOnly,
PixelFormat.Format32bppArgb );
RtlMoveMemory( ppvBits, bitmapData.Scan0,
bm.Height * bitmapData.Stride );
bm.UnlockBits( bitmapData );
ImageList_Add( il.Handle, hBitmap, new IntPtr(0) );
}
Conclusion
By using the ImageList
class and its methods of adding images from various sources, you are now be able to use 32 bit alpha blended images in your C# applications. Once the images have been added, you can continue to use the ImageList
control as usual. If you feel there are incorrect or misleading information in this article, please feel free to point them out. I'm somewhat of a beginner to C#, so I might not have everything down yet. Also, any comments or suggestions would be appreciated. Thanks.