How to convert an image to an icon without losing transparency?
Asked Answered
H

1

14

I have PNG images which I need to convert to an icon before displaying it.

This is how I did it:

public Icon ImageToIcon(Image imgTest)
{
    Bitmap bitmap = new Bitmap(imgTest);
    Icon icoTest;

    IntPtr iPtr = bitmap.GetHicon();
    icoTest = (Icon) Icon.FromHandle(iPtr).Clone();

    return icoTest;
}

I lose transparency after doing this, alpha transparent images are not rendered as expected....can this be solved?

Hendrika answered 27/1, 2014 at 17:21 Comment(1)
I recently made my drawing libraries open source, which has a ToIcon extension method. Or the Icons.Combine method to create multi-image icons.Fibrinogen
R
34

No, there's a lot more to it. Icons have a pretty elaborate internal structure, optimized to work reasonably on 1980s hardware. An icon image has three bitmaps, one for the icon, a monochrome bitmap that indicates what parts of the image are transparent and another monochrome bitmap that indicates what parts are reversed. Generating those monochrome bitmaps is pretty painful, .NET doesn't support them. Nor does Bitmap.GetHicon() make an attempt at it. You'll need a library to do the work for you.

Vista gave some relief, it started supporting icons that contain a PNG image. You'll have a shot at generating it with your own code. Like this:

    public static Icon IconFromImage(Image img) {
        var ms = new System.IO.MemoryStream();
        var bw = new System.IO.BinaryWriter(ms);
        // Header
        bw.Write((short)0);   // 0 : reserved
        bw.Write((short)1);   // 2 : 1=ico, 2=cur
        bw.Write((short)1);   // 4 : number of images
        // Image directory
        var w = img.Width;
        if (w >= 256) w = 0;
        bw.Write((byte)w);    // 0 : width of image
        var h = img.Height;
        if (h >= 256) h = 0;
        bw.Write((byte)h);    // 1 : height of image
        bw.Write((byte)0);    // 2 : number of colors in palette
        bw.Write((byte)0);    // 3 : reserved
        bw.Write((short)0);   // 4 : number of color planes
        bw.Write((short)0);   // 6 : bits per pixel
        var sizeHere = ms.Position;
        bw.Write((int)0);     // 8 : image size
        var start = (int)ms.Position + 4;
        bw.Write(start);      // 12: offset of image data
        // Image data
        img.Save(ms, System.Drawing.Imaging.ImageFormat.Png);
        var imageSize = (int)ms.Position - start;
        ms.Seek(sizeHere, System.IO.SeekOrigin.Begin);
        bw.Write(imageSize);
        ms.Seek(0, System.IO.SeekOrigin.Begin);

        // And load it
        return new Icon(ms);
    }

Tested on .NET 4.5 and Windows 8.1. Beware of the possibility of "fringes" you'll see on PNG images with transparency on the edges. That only works well when the image is displayed on a well-known background color. Which, by design, an icon can never depend on. A dedicated icon editor will always be the only truly good way to get good looking icons.

Rave answered 27/1, 2014 at 18:55 Comment(3)
And for a Cursor, set the type to 2 and write the hotpoint X and Y coords instead of color plane count and BPP.Skilken
This function works great (still, on .NET 7). One note, the MemoryStream and the BinaryWriter both should have Dispose called on them otherwise they'll leak. Wrap them both in a using statement and they'll be disposed off after the return.Somniferous
It is just memory, already managed by the garbage collector.Rave

© 2022 - 2024 — McMap. All rights reserved.