|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
IntroductionCreating 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 First thoughtsI 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 Handles are "unsafe"So, my next thought was to use the 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 Not all ImageFormats are created equalSince I cannot use the // 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 The SolutionThe Framework supports saving a For my solution, I created a 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 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 I tested the 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 public static IconHolder BitmapToIconHolder(BitmapHolder bmp)
This method does all of the real work. I added some overloads to the 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 codeAll 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 ConstraintsNote 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:
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
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||