Click here to Skip to main content
Click here to Skip to main content

Dynamically Generating Icons (safely)

By , 27 Nov 2006
 

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

About the Author

Joshua Flanagan
Web Developer
United States United States
Member
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.

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
QuestionGreat stuff, many thanksmemberChris L Rae1 Oct '11 - 3:11 
I've integrated this code into Wallpapr, an open-source project I've been working on. Couldn't work out why the built in API calls were reducing my pictures to 4 bits. Many thanks indeed!
GeneralAnother easy solutionmemberbillhao2 May '10 - 21:45 
http://www.eggheadcafe.com/articles/createiconsforsystemtray.asp[^]
GeneralRe: Another easy solutionmemberPeter_Smithson16 Aug '11 - 23:11 
This looks a lot like the "short way" mentioned by someone else and already covered by the article. The key bit of code in that page you link to is "Icon.FromHandle(bm.GetHicon());". The author mentioned already that this can't be used.
GeneralResolution limit trial and error [modified]memberSysLord13 Feb '10 - 11:50 
At this position I used a 1 instead of the 3. Now it seems to work for 32x32 images.
//TODO: fix assumption that icon is 16px wide
//skip some bytes so that scanline ends on a long barrier
bytePosAND += 1;  //instead of 3
 
I do not really understand especially this part, as we skip bytes, from which I thought would be needed:
16x16px => 256 pixels
with pixelsperbytes=2 and 8 bytes per row. We write bytes 01..45..67 ...
This would be 12 pixels per row... WTF | :WTF:
 
With this code, my simple cpu usage tool works, which is great, but I am really missing comments.

-- Modified Saturday, February 13, 2010 5:57 PM

GeneralRe: Resolution limit trial and error [modified]memberdedel15 Feb '10 - 19:42 
Try my short way.
 
No import of 4 extra source files and I hope it is clear what the code do.
modified on Tuesday, February 16, 2010 1:49 AM

GeneralA short waymemberdedel11 Feb '10 - 3:49 
A short way is the following code :
Icon oldIcon  = notifyIcon1.Icon;
notifyIcon1.Icon = Icon.FromHandle(bmp.GetHicon());
NativeMethods.DestroyIcon(new HandleRef(this, oldIcon.Handle));
oldIcon.Dispose();
bmp.Dispose();
NativeMethods.DestroyIcon is a native methode like :
[DllImport("user32.dll", EntryPoint = "DestroyIcon", CharSet = CharSet.Auto, ExactSpelling = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool DestroyIcon(HandleRef hIcon);
It works (no new GDI handles), you can use icons/bitmaps > 16x16 and it is much more shorter Wink | ;-)
GeneralRe: A short waymemberOttO Sch16 May '11 - 9:54 
Joshua already covered "your" short way, but the article is about 'no touch deployment' with which you cannot use Icon.FromHandle() methode because of lack of permission.
Generalthanksmembercostin v8 Feb '10 - 5:34 
very nice thank you, much cleaner than icon fromhandle/ GetHIcon
GeneralThanks - works greatmemberFaxedHead25 Sep '09 - 23:29 
Just wanted to say thanks for the article - works perfectly straight out of the box Smile | :)
QuestionCan this code be used to save Image to .ico?memberSukhjinder_K22 Dec '07 - 6:28 
Hi I've created a 16x16 Bitmap (and drawn onto it) and would like to save it in .ico format. Can I do that using your code.
 
Thanks
Sukhjinder
GeneralIt works perfectly well, but one question!memberIngenious00121 Nov '07 - 0:05 
Hello my friend,
 
I think your source is very good to use (ok, nothing is perfect). I come from the C++ corner so I'm a bit a beginner in C#. I try to follow the concept of using C# in a safe mode as it was intended for. So that's why I like your code so much, there is no unsafe parts.
 
I do have one question though, and that is the size limit of 16x16 pixels. You posted your article in 2004, is there any improvements regarding to the limited pixelsize? I programm microcontrollers, write Visual C++ apps and now writing C# apps, but it's a bit too much for me to figure out how to make icons of maximum 128x128 pixels, so I hope you or someone else have a solution for it based on your source?
 
Thank you very much.
 
**We are an example of helping each other**

GeneralThanksmembersebepsiz15 Apr '07 - 6:06 
I think i can use this in my commercial code ha? I think that because of comment that you wrote in your code. Thanks so much man. And I saw that your code seems to work faster than classical GetHIcon method. Your way is problemless. Happy coding...
 
The way i choose is the way i code.
GeneralRe: ThanksmemberJoshua Flanagan15 Apr '07 - 17:07 
Yes, you can use it for whatever you like, commercial or not. I can't remember what the code said, but I consider the code in the public domain. Do whatever you want with it. It would be nice (tho not required) to leave a credit to me and link to this article in your code comments.
GeneralKudosmemberdeletethisprofile14 Apr '06 - 7:00 
I'm trying to see if I have the horsepower to make 32x32, 256 work, and I have a new appreciation for what you had to go through to get this done. You did what no one had done.
 
-toddmo
QuestionDid you ever get this code working for icon larger than 16x16?memberBill Seddon13 Dec '05 - 2:28 
I'm looking for something like your code but that works with larger images. I thought I'd write and ask to see if the work has been done already before launching in myself.
 
Regards
 
Bill Seddon
bill.seddon@lyquidity.com

AnswerRe: Did you ever get this code working for icon larger than 16x16?memberJoshua Flanagan13 Dec '05 - 3:26 
I have not done the work or heard from anyone that has. If you do it, it'd be great if you posted a link to it (or send it to me and I'll update the article).

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

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