Combine System.Drawing.Bitmap[] -> Icon

前端 未结 1 1597
孤街浪徒
孤街浪徒 2021-01-25 19:14

Splitting an icon into its Bitmap parts is easy:

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

But h

相关标签:
1条回答
  • 2021-01-25 19:29

    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.

    0 讨论(0)
提交回复
热议问题