Combine System.Drawing.Bitmap[] -> Icon

帅比萌擦擦* 提交于 2021-01-20 09:14:43

问题


Splitting an icon into its Bitmap parts is easy:

Bitmap icon16 = new Icon(combinedIcon, new Size(16, 16)).ToBitmap()

But how do you merge multiple Bitmap objects into one Icon?

Bitmap icon16, icon32, icon64;
Icon combinedIcon = [...]


I'm not that clear about the Icon object in general. It is indeed a set of multiple images. When loading it, you can take separate it into its Bitmap parts. But I don't see any method to create a multi-icon. It also seems strange to not being able to iterate, add or remove Bitmap parts in an obvious fashion, like having a collection of bitmaps.


回答1:


The Icon class in .Net is very rudimentary, and doesn't even get close to giving access to all features of the actual icon format. It's probably best to construct the icon as byte stream, and then load that as icon.

I looked into the format a while back, and it actually accepts png data as internal images. Do note that said images cannot have a width or height of more than 256 pixels, and the amount of images in the file is saved in two bytes, so it cannot exceed Int16.MaxValue, or 0xFFFF, or 65535.

The code should look something like this:

public static Icon ConvertImagesToIco(Image[] images)
{
    if (images == null)
        throw new ArgumentNullException("images");
    Int32 imgCount = images.Length;
    if (imgCount == 0)
        throw new ArgumentException("No images given!", "images");
    if (imgCount > 0xFFFF)
        throw new ArgumentException("Too many images!", "images");
    using (MemoryStream ms = new MemoryStream())
    using (BinaryWriter iconWriter = new BinaryWriter(ms))
    {
        Byte[][] frameBytes = new Byte[imgCount][];
        // 0-1 reserved, 0
        iconWriter.Write((Int16)0);
        // 2-3 image type, 1 = icon, 2 = cursor
        iconWriter.Write((Int16)1);
        // 4-5 number of images
        iconWriter.Write((Int16)imgCount);
        // Calculate header size for first image data offset.
        Int32 offset = 6 + (16 * imgCount);
        for (Int32 i = 0; i < imgCount; ++i)
        {
            // Get image data
            Image curFrame = images[i];
            if (curFrame.Width > 256 || curFrame.Height > 256)
                throw new ArgumentException("Image too large!", "images");
            // for these three, 0 is interpreted as 256,
            // so the cast reducing 256 to 0 is no problem.
            Byte width = (Byte)curFrame.Width;
            Byte height = (Byte)curFrame.Height;
            Byte colors = (Byte)curFrame.Palette.Entries.Length;
            Int32 bpp;
            Byte[] frameData;
            using (MemoryStream pngMs = new MemoryStream())
            {
                curFrame.Save(pngMs, ImageFormat.Png);
                frameData = pngMs.ToArray();
            }
            // Get the colour depth to save in the icon info. This needs to be
            // fetched explicitly, since png does not support certain types
            // like 16bpp, so it will convert to the nearest valid on save.
            Byte colDepth = frameData[24];
            Byte colType = frameData[25];
            // I think .Net saving only supports colour types 2, 3 and 6 anyway.
            switch (colType)
            {
                case 2: bpp = 3 * colDepth; break; // RGB
                case 6: bpp = 4 * colDepth; break; // ARGB
                default: bpp = colDepth; break; // Indexed & greyscale
            }
            frameBytes[i] = frameData;
            Int32 imageLen = frameData.Length;
            // Write image entry
            // 0 image width. 
            iconWriter.Write(width);
            // 1 image height.
            iconWriter.Write(height);
            // 2 number of colors.
            iconWriter.Write(colors);
            // 3 reserved
            iconWriter.Write((Byte)0);
            // 4-5 color planes
            iconWriter.Write((Int16)0);
            // 6-7 bits per pixel
            iconWriter.Write((Int16)bpp);
            // 8-11 size of image data
            iconWriter.Write(imageLen);
            // 12-15 offset of image data
            iconWriter.Write(offset);
            offset += imageLen;
        }
        for (Int32 i = 0; i < imgCount; i++)
        {
            // Write image data
            // png data must contain the whole png data file
            iconWriter.Write(frameBytes[i]);
        }
        iconWriter.Flush();
        ms.Position = 0;
        return new Icon(ms);
    }
}

Note that unlike other System.Drawing image formats, the Icon class does not require the stream to stay open; it just reads its bytes from the stream and leaves it at that.

The png colour depth info can be found here and here, btw.

If you want to have the icon file as byte array, or want to write it to disc, you can of course adapt this code to return a byte array, or even just to make it write the stuff to a Stream given as argument rather than creating the MemoryStream internally.



来源:https://stackoverflow.com/questions/54801185/combine-system-drawing-bitmap-icon

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!