I'm trying to create a high quality icon (means: suitable for Win Vista/7/8) from a PNG file programmatically in C# for use as shortcut icons. Since the Bitmap.GetHIcon() function doesn't support these kind of icons, and I want to avoid external dependencies or libraries, I'm currently using a slightly modified ICO writer I found here on SO. I have working code but I'm experiencing some glitches in the way Windows displays these icons. The relevant code is:
// ImageFile contains the path to PNG file
public static String IcoFromImageFile(String ImageFile) {
//...
Image iconfile = Image.FromFile(ImageFile);
//Returns a correctly resized Bitmap
Bitmap bm = ResizeImage(256,256,iconfile);
SaveAsIcon(bm, NewIconFile);
return NewIconFile;
}
// From: https://stackoverflow.com/a/11448060/368354
public static void SaveAsIcon(Bitmap SourceBitmap, string FilePath) {
FileStream FS = new FileStream(FilePath, FileMode.Create);
// ICO header
FS.WriteByte(0); FS.WriteByte(0);
FS.WriteByte(1); FS.WriteByte(0);
FS.WriteByte(1); FS.WriteByte(0);
// Image size
// Set to 0 for 256 px width/height
FS.WriteByte(0);
FS.WriteByte(0);
// Palette
FS.WriteByte(0);
// Reserved
FS.WriteByte(0);
// Number of color planes
FS.WriteByte(1); FS.WriteByte(0);
// Bits per pixel
FS.WriteByte(32); FS.WriteByte(0);
// Data size, will be written after the data
FS.WriteByte(0);
FS.WriteByte(0);
FS.WriteByte(0);
FS.WriteByte(0);
// Offset to image data, fixed at 22
FS.WriteByte(22);
FS.WriteByte(0);
FS.WriteByte(0);
FS.WriteByte(0);
// Writing actual data
SourceBitmap.Save(FS, System.Drawing.Imaging.ImageFormat.Png);
// Getting data length (file length minus header)
long Len = FS.Length - 22;
// Write it in the correct place
FS.Seek(14, SeekOrigin.Begin);
FS.WriteByte((byte)Len);
FS.WriteByte((byte)(Len >> 8));
FS.Close();
}
This compiles and works, but with one problem. Windows displays the icon on the shortcut incorrectly. I do this also programatically, but it occurs even if I do it manually (via File Properties, Change Icon). The problem is that the icon is cut off (the image itself displays correctly). It depends on the image, but usually only around 20% of the actual icon is shown. If I open the file in an image viewer like XNView it displays completely and correct, but MS Paint doesn't. I made this screenshot, along with a correctly displayed icon for comparison
I suspect the error lies in the ICO saving method, but even after comparing them to normally displayed ICOs in a Hex editor, the header gets written correctly but the PNG image part itself seems different. Has anyone an idea? I also welcome better, less hacky solutions.
If you are designing an application for Windows, a website, or just want to personalize your Windows desktop, you can create ICO files using an image converter website, MS Paint, Mac Preview, or a Photoshop plugin.
Go to menu Image > New Device Image, or right-click in the Image Editor pane and choose New Device Image. Select the type of image you want to add. You can also select Custom to create an icon whose size isn't available in the default list.
Your ico file is set to save the length of the embedded bitmap with only 16-bit precision, but the PNG file is too large (larger than 65535 bytes) so the length record overflows.
I.e. the following lines are incomplete:
// Write it in the correct place
FS.Seek(14, SeekOrigin.Begin);
FS.WriteByte((byte)Len);
FS.WriteByte((byte)(Len >> 8));
You could add these lines:
FS.WriteByte((byte)(Len >> 16));
FS.WriteByte((byte)(Len >> 24));
As a matter of cleanliness and performance, I'd generally avoid all those separate writes and just use the write overload with the byte array parameter. Also, instead of the somewhat tricky Save-To-File then seek, you might consider a Save-To-MemoryStream then a single Write for the header (which can now use the PNG's length in bytes) and a single write to copy the PNG data from the memory stream to the file.
Another point you really should address is disposing IDisposable
resources. Even if you don't need to yet since you haven't encountered any problems, it will bite you someday and if you have even a fairly small codebase with all kind of undisposed disposables you'll have a very hard time finding the source of your leak and/or deadlock. In general: Never call Close
unless you really can't avoid it - instead wrap your FileStream
in a using
block. Similarly, Image
and Bitmap
are disposable and allocate native resources, though at least you can't get any locking issues with those (AFAIK - but better to be safe than sorry).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With