Click here to Skip to main content
Click here to Skip to main content
Go to top

Dynamically Generating Icons (safely)

, 27 Nov 2006
Rate this:
Please Sign up or sign in to vote.
How to dynamically generate an icon in a restricted security zone, as with "No touch deployment" WinForms.

Introduction

Creating images dynamically in .NET is very easy, using the managed GDI+ methods. However, when it comes to creating icons dynamically, things are not always so straightforward. I was working on a Smart Client/"No Touch Deployment" application that would sit in the tray and display some statistics. I figured it would be simple enough: add the NotifyIcon component to a Form, set the Form’s ShowInTaskbar property to false, and use the Timer component to trigger periodic updates to the NotifyIcon’s Icon property with an image created from the latest data. Little did I know how difficult it would be to update the icon programmatically. Fortunately, I was able to create a working solution that I will provide in this article.

First thoughts

I realize I could have stored a bunch of different pre-built icons as resources in my executable and swapped them out appropriately. This was not an acceptable solution, as there were too many different possible icon images.

I wanted to be able to generate the images dynamically, using the GDI+ methods exposed by the System.Drawing.Graphics object. Unfortunately, there is no way to get a Graphics object for an Icon. I assumed I could use Graphics.FromImage() but realized the Icon class does not derive from Image. I then tried Graphics.FromHdc() and Graphics.FromHwnd() with the Icon.Handle, and I only got OutOfMemory Exceptions.

Handles are "unsafe"

So, my next thought was to use the Bitmap class to create an image, and then copy the image to the Icon. I noted that the Bitmap class has a GetHIcon() method that returns a handle to an Icon. I could just pair that with the Icon’s .FromHandle() factory method to create the icon from the bitmap. And I was right… almost.

Bitmap bmp = new Bitmap(16, 16);
using (Graphics g = Graphics.FromImage(bmp))
    g.FillEllipse(Brushes.Red, 0, 0, 16, 16);
notifyIcon1.Icon = Icon.FromHandle(bmp.GetHicon());

This approach worked perfectly, running from the IDE. So I built the executable, copied it to my webserver, and launched it from a web page. Boom! Setting the new icon caused a System.Security.SecurityException. Apparently, the Icon.FromHandle() method demands System.Security.Permissions.SecurityPermission with the UnmanagedCode SecurityPermissionFlag. Since my goal is to create a “No Touch Deployment” application, I cannot use unmanaged code, which means I cannot use handles. If your application will not be launched from a web page, or you are able to configure the security zones of your clients to allow UnmanagedCode, you do not need to read any further. The code above will work for your solution.

Not all ImageFormats are created equal

Since I cannot use the Icon.FromHandle() method, I had to find another way to create an instance of an Icon. It turns out the Icon class has a constructor that takes a Stream parameter. I could save the Bitmap to a stream, and then load that stream into my Icon. The Bitmap.Save() method takes two parameters: the target stream and an ImageFormat enumeration that specifies how the image will be represented as bits (we want the Icon format).

// bmp contains the dynamically generated image
MemoryStream buffer = new MemoryStream();
bmp.Save(buffer, ImageFormat.Icon);
// reset the stream back to the beginning so that it can be read
buffer.Position = 0; 
notifyIcon1.Icon = new Icon(buffer);

Unfortunately, when you run this code, you get a bit of a confusing error:

System.ArgumentNullException: Value cannot be null.
Parameter name: encoder
   at System.Drawing.Image.Save(Stream stream, ImageCodecInfo encoder,
EncoderParameters encoderParams)
   at System.Drawing.Image.Save(Stream stream, ImageFormat format)

At this point, I was stumped. I posted to the Windows Forms newsgroups and was directed to a knowledge base article that explains that GDI+ does not include the necessary encoder to save as ImageFormat.Icon. A Microsoft representative responded saying the only solution was to convert the image into an Icon stream myself. Details of the icon file format could be found on MSDN. So, that is what I did.

The Solution

The Framework supports saving a Bitmap to a stream using ImageFormat.Bmp, which saves the bits in the Device Independent Bitmap format. I was able to find the specifications for the DIB format on a game programming site and in the Platform SDK.

For my solution, I created a BitmapHolder class that holds the contents of the bitmap in structures as they are defined in the documentation. I also created an IconHolder class that holds the contents of an icon resource using the documented icon structures. Both classes need to be able to load and save their contents using a stream, so I added the following methods:

public void Open(Stream stream){}
public void Save(Stream stream){}

These methods just needed to read or write to the underlying structures that made up the image format. The reading and writing was simplified by adding these methods to each structure:

public void Populate(BinaryReader br){}
public void Save(BinaryWriter bw){}

These methods read/write the structure fields in the order in which they are supposed to appear in the stream. To illustrate the simplicity, I have included the implementations for the BITMAPINFOHEADER struct that is used by both the icon and the bitmap format.

public void Populate(BinaryReader br)
{
    biSize = br.ReadUInt32();
    biWidth = br.ReadInt32();
    biHeight = br.ReadInt32();
    biPlanes = br.ReadUInt16();
    biBitCount = br.ReadUInt16();
    biCompression = br.ReadUInt32();
    biSizeImage = br.ReadUInt32();
    biXPelsPerMeter = br.ReadInt32();
    biYPelsPerMeter = br.ReadInt32();
    biClrUsed = br.ReadUInt32();
    biClrImportant = br.ReadUInt32();
}
public void Save(BinaryWriter bw)
{
    bw.Write(biSize);
    bw.Write(biWidth);
    bw.Write(biHeight);
    bw.Write(biPlanes);
    bw.Write(biBitCount);
    bw.Write(biCompression);
    bw.Write(biSizeImage);
    bw.Write(biXPelsPerMeter);
    bw.Write(biYPelsPerMeter);
    bw.Write(biClrUsed);
    bw.Write(biClrImportant);
}

The Open methods on the holder classes simply create a BinaryReader on the stream, and then pass it to each of their structures’ Populate methods. The Save methods on the holder classes do the same with a BinaryWriter and their structures’ Save methods.

I tested the BitmapHolder class by loading bitmap files from disk using Open(), peeking at the data in memory in debug mode, and then saving them to another filename using Save(). Once everything was working correctly, the saved file would look exactly like the original file. I repeated this process with the IconHolder class to confirm it was operating correctly.

At this point, I was confident that my holder classes could read and write to their respective formats. Now, the trick was to use the contents of a BitmapHolder class to populate an IconHolder class. I created a Converter helper class with a static method to perform the conversion:

public static IconHolder BitmapToIconHolder(BitmapHolder bmp)

This method does all of the real work. I added some overloads to the Converter class to make it more user-friendly. With the final solution, creating a dynamic icon is as easy as:

Bitmap bmp = new Bitmap(16, 16, PixelFormat.Format24bppRgb);
using (Graphics g = Graphics.FromImage(bmp))
{
    g.FillEllipse(Brushes.Red, 0, 0, 16, 16);
    g.FillRectangle(Brushes.White, 4, 6, 8, 4);
}
notifyIcon1.Icon = Converter.BitmapToIcon(bmp);

Using the code

All of the necessary code can be found in the demo project, in a subfolder named FlimFlan.IconEncoder. Copy that folder to your own project folder, and include the files in your project. I didn't feel the need to compile it into its own separate assembly, but you can. Create your Bitmap using the managed GDI+ functions, and then call FlimFlan.IconEncoder.Converter.BitmapToIcon(bitmap) to return a new Icon.

Constraints

Note that the implementation of the conversion is not complete for all scenarios. I implemented just enough to solve my specific problem. It currently has the following constraints:

  • The source Bitmap must use PixelFormat.Format24BppRgb or PixelFormat.Format32bppArgb
  • The source Bitmap must use at most 256 colors when using Format24BppRgb
  • The source Bitmap must be 16x16 pixels
  • The target Icon must be 16x16 pixels
  • The pixel in the lower left corner (0, 15) is used to determine the transparency color

I tried to comment the sections of codes where these assumptions were made, so the conversion could be made more robust. If you add support for more color depths or image sizes, please send me the code, and I will update the article and give you credit.

History

  • 20 May 2004 - Initial release.
  • 27 Nov 2004 - Updated code: thanks to Pavel Janda for adding support for 32bpp bitmaps.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

Share

About the Author

Joshua Flanagan
Web Developer
United States United States
I have been writing code for over 10 years now and still can't seem to kick the habit. When I'm not haunted by visions of bitshift operators and curly braces in my head, I like to read comic books and play with my little girl.

Comments and Discussions

 
Generalthanks Pinmembercostin v8-Feb-10 5:34 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web01 | 2.8.140926.1 | Last Updated 27 Nov 2006
Article Copyright 2004 by Joshua Flanagan
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid