Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Creating a high quality ico file programmatically

Tags:

c#

.net-4.0

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

enter image description here

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.

like image 811
Lennart Avatar asked Jan 04 '13 12:01

Lennart


People also ask

What programs can create ICO files?

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.

How do I create a .ICO file in Windows 10?

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.


1 Answers

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).

like image 107
Eamon Nerbonne Avatar answered Oct 27 '22 14:10

Eamon Nerbonne