关于C#生成多尺寸图标ICO的多种方式

瞑空凌 2025/2/13 发布 · 2025/2/15 更新 · 857 次阅读

代码来源:deepseek
注:这真是神器,虽然一天只能用两三次,但不用自己动手试错,实在是太方便了,一两次最多四五次代码就新鲜出炉,还不用找资料,最花时间的反而是因为自己看不懂代码,所以需要用其他ai来辅助自己理解意思。下面代码高亮来源syntaxhighlighter

说明:以下代码均可执行,已本地测试过。虽然说不出个所以然,毕竟没学过,全靠AI给力。

此为测试所用素材,均为PS导出的png


1.代码一

png格式生成多尺寸ICO
//.cs  文件类型,便于外部编辑时使用
// 引用必要的命名空间
using System.Windows.Forms;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Collections.Generic;
using System.Linq;
 
// Quicker将会调用的函数。可以根据需要修改返回值类型。
public static void Exec(Quicker.Public.IStepContext context)
{
    //var oldValue = context.GetVarValue("varName");  // 读取动作里的变量值
    //MessageBox.Show(oldValue as string);
    //context.SetVarValue("varName", "从脚本输出的内容。"); // 向变量里输出值
    //MessageBox.Show("Hello World!");
    // 加载不同尺寸的Bitmap
    var images = new List<Bitmap>
    {
         new Bitmap(@"D:\SoftwareCache\Microsoft VS Code\图标\合成\16x16.png"),
         new Bitmap(@"D:\SoftwareCache\Microsoft VS Code\图标\合成\32x32.png"),
         new Bitmap(@"D:\SoftwareCache\Microsoft VS Code\图标\合成\48x48.png")
    };
    // 生成ICO文件
    IconCombiner.CreateIcon(images, @"D:\SoftwareCache\Microsoft VS Code\图标\合成\output.ico");
}
 
public class IconCombiner
{
    public static void CreateIcon(IEnumerable<Bitmap> images, string outputPath)
    {
        var bitmaps = images.ToList();
        if (bitmaps.Count == 0)
            throw new ArgumentException("No images provided.");
 
        using (var stream = new FileStream(outputPath, FileMode.Create))
        using (var writer = new BinaryWriter(stream))
        {
            // 写入ICO文件头
            writer.Write((ushort)0); // 保留字
            writer.Write((ushort)1); // 类型:ICO
            writer.Write((ushort)bitmaps.Count); // 图像数量
 
            int currentOffset = 6 + 16 * bitmaps.Count; // 第一个图像数据偏移
            var entries = new List<byte[]>();
            var imageDatas = new List<byte[]>();
 
            foreach (var bmp in bitmaps)
            {
                // 转换为32位ARGB格式确保透明度
                using (var convertedBmp = ConvertTo32bppArgb(bmp))
                using (var ms = new MemoryStream())
                {
                    convertedBmp.Save(ms, ImageFormat.Png);
                    byte[] pngData = ms.ToArray();
                    imageDatas.Add(pngData);
 
                    // 创建目录项
                    byte width = GetIconDimension(convertedBmp.Width);
                    byte height = GetIconDimension(convertedBmp.Height);
                    uint size = (uint)pngData.Length;
                    uint offset = (uint)currentOffset;
 
                    using (var entryMs = new MemoryStream())
                    using (var entryWriter = new BinaryWriter(entryMs))
                    {
                        entryWriter.Write(width);
                        entryWriter.Write(height);
                        entryWriter.Write((byte)0); // 调色板数量(无调色板)
                        entryWriter.Write((byte)0); // 保留
                        entryWriter.Write((ushort)1); // 颜色平面数
                        entryWriter.Write((ushort)32); // 每像素位数(32位ARGB)
                        entryWriter.Write(size);
                        entryWriter.Write(offset);
                        entries.Add(entryMs.ToArray());
                    }
 
                    currentOffset += (int)size;
                }
            }
 
            // 写入所有目录项
            foreach (var entry in entries)
                writer.Write(entry);
 
            // 写入所有PNG图像数据
            foreach (var data in imageDatas)
                writer.Write(data);
        }
    }
 
    private static byte GetIconDimension(int dimension)
    {
        return (byte)(dimension >= 256 ? 0 : dimension);
    }
 
    private static Bitmap ConvertTo32bppArgb(Bitmap image)
    {
        Bitmap clone = new Bitmap(image.Width, image.Height, PixelFormat.Format32bppArgb);
        using (Graphics gr = Graphics.FromImage(clone))
        {
            gr.DrawImage(image, new Rectangle(0, 0, clone.Width, clone.Height));
        }
        return clone;
    }
}

ICO效果图以及在PE图标组效果图:

2.代码二

bmp格式生成多尺寸ICO(方式一)
//.cs  文件类型,便于外部编辑时使用
// 引用必要的命名空间
using System.Windows.Forms;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
 
// Quicker将会调用的函数。可以根据需要修改返回值类型。
public static void Exec(Quicker.Public.IStepContext context)
{
    //var oldValue = context.GetVarValue("varName");  // 读取动作里的变量值
    //MessageBox.Show(oldValue as string);
    //context.SetVarValue("varName", "从脚本输出的内容。"); // 向变量里输出值
    //MessageBox.Show("Hello World!");
    // 加载不同尺寸的Bitmap
    var bitmaps = new List<Bitmap>
    {
        new Bitmap(@"D:\SoftwareCache\Microsoft VS Code\图标\合成\16x16.png"),
        new Bitmap(@"D:\SoftwareCache\Microsoft VS Code\图标\合成\32x32.png"),
        new Bitmap(@"D:\SoftwareCache\Microsoft VS Code\图标\合成\48x48.png")
    };
    
    // 合成ICO文件
    IcoConverter.SaveAsLegacyIco(bitmaps, @"D:\SoftwareCache\Microsoft VS Code\图标\合成\bitok.ico");
 
}
 
public static class IcoConverter
{
    // ICO文件头结构
    [StructLayout(LayoutKind.Sequential)]
    private struct IconDir
    {
        public ushort Reserved; // 必须为0
        public ushort Type;     // 1=ICO, 2=CUR
        public ushort Count;    // 图像数量
    }
 
    // ICO目录项结构
    [StructLayout(LayoutKind.Sequential)]
    private struct IconDirEntry
    {
        public byte Width;      // 宽度(0=256)
        public byte Height;     // 高度(0=256)
        public byte ColorCount; // 调色板颜色数(0=无调色板)
        public byte Reserved;   // 必须为0
        public ushort Planes;   // 颜色平面数(必须为1)
        public ushort BitCount; // 位深度(如32)
        public uint BytesInRes; // 图像数据总大小
        public uint ImageOffset;// 数据偏移量
    }
 
    /// <summary>
    /// 生成包含多个BMP格式图像的ICO文件
    /// </summary>
    public static void SaveAsLegacyIco(IEnumerable<Bitmap> bitmaps, string outputPath)
    {
        using (var stream = new FileStream(outputPath, FileMode.Create))
        using (var writer = new BinaryWriter(stream))
        {
            var entries = new List<IconDirEntry>();
            var imageDataList = new List<byte[]>();
 
            // 构建每个图像的BMP数据和目录项
            uint currentOffset = (uint)(Marshal.SizeOf(typeof(IconDir)) + (uint)(Marshal.SizeOf(typeof(IconDirEntry)) * bitmaps.Count()));
            foreach (var bmp in bitmaps)
            {
                var entry = new IconDirEntry();
                var imageData = GenerateBmpEntry(bmp, ref entry);
                entry.ImageOffset = currentOffset;
 
                entries.Add(entry);
                imageDataList.Add(imageData);
                currentOffset += (uint)imageData.Length;
            }
 
            // 写入ICO文件头
            writer.Write((ushort)0);             // Reserved
            writer.Write((ushort)1);             // Type=ICO
            writer.Write((ushort)entries.Count); // Count
 
            // 写入目录项
            foreach (var entry in entries)
            {
                writer.Write(entry.Width);
                writer.Write(entry.Height);
                writer.Write(entry.ColorCount);
                writer.Write(entry.Reserved);
                writer.Write(entry.Planes);
                writer.Write(entry.BitCount);
                writer.Write(entry.BytesInRes);
                writer.Write(entry.ImageOffset);
            }
 
            // 写入图像数据
            foreach (var data in imageDataList)
            {
                writer.Write(data);
            }
        }
    }
 
    /// <summary>
    /// 生成单个BMP格式的ICO图像数据(含掩码)
    /// </summary>
    private static byte[] GenerateBmpEntry(Bitmap bmp, ref IconDirEntry entry)
    {
        // 转换到32bpp ARGB格式(确保Alpha通道存在)
        var bitmap32bpp = new Bitmap(bmp.Width, bmp.Height, PixelFormat.Format32bppArgb);
        using (var g = Graphics.FromImage(bitmap32bpp))
        {
            g.CompositingMode = System.Drawing.Drawing2D.CompositingMode.SourceCopy;
            // 明确指定绘制尺寸防止缩放
            g.DrawImage(bmp, new Rectangle(0, 0, bmp.Width, bmp.Height));
        }
 
        // 获取BMP数据(移除BITMAPFILEHEADER)
        var bmpBytes = GetBmpDataWithoutFileHeader(bitmap32bpp);
        var maskBytes = GenerateMaskData(bitmap32bpp);
        
        // 填充目录项信息
        entry.Width = (byte)(bitmap32bpp.Width >= 256 ? 0 : bitmap32bpp.Width);
        entry.Height = (byte)(bitmap32bpp.Height >= 256 ? 0 : bitmap32bpp.Height);
        entry.ColorCount = 0; // 无调色板
        entry.Reserved = 0;
        entry.Planes = 1;
        entry.BitCount = 32; // ARGB
        entry.BytesInRes = (uint)(bmpBytes.Length + maskBytes.Length);
 
        // 合并BMP数据和掩码数据
        var imageData = new byte[bmpBytes.Length + maskBytes.Length];
        Buffer.BlockCopy(bmpBytes, 0, imageData, 0, bmpBytes.Length);
        Buffer.BlockCopy(maskBytes, 0, imageData, bmpBytes.Length, maskBytes.Length);
 
        return imageData;
    }
 
    /// <summary>
    /// 获取不带文件头的BMP数据(仅包含BITMAPINFOHEADER和像素)
    /// </summary>
    private static byte[] GetBmpDataWithoutFileHeader(Bitmap bmp)
    {
        // 克隆位图并设置正确的高度方向
        using (var cloneBmp = new Bitmap(bmp))
        {
            //cloneBmp.RotateFlip(RotateFlipType.Rotate180FlipX); // 反转Y轴,此方式不用翻转,不清楚缘由
            using (var ms = new MemoryStream())
            {
                cloneBmp.Save(ms, ImageFormat.Bmp);
                var fullData = ms.ToArray();
                // 移除BITMAPFILEHEADER (14 bytes)
                byte[] dataWithoutHeader = new byte[fullData.Length - 14];
                Buffer.BlockCopy(fullData, 14, dataWithoutHeader, 0, dataWithoutHeader.Length);
                int height = BitConverter.ToInt32(dataWithoutHeader, 8);//获取原本数据的像素高度
                // 将高度设为两倍表示包含掩码
                byte[] negativeHeight = BitConverter.GetBytes(height*2);//Height*2 (包含掩码)
                Buffer.BlockCopy(negativeHeight, 0, dataWithoutHeader, 8, 4);
                return dataWithoutHeader;
            }
        }
    }
 
    /// <summary>
    /// 生成AND掩码位图(1位/像素)----原本代码看不咋懂,所以找AI理解后微改并加上注释
    /// </summary>
    private static byte[] GenerateMaskData(Bitmap bmp)
    {
        //int maskStride = ((bmp.Width + 31) / 32) * 4;//32位图像是指一个像素的数据由32个bit构成
        int maskStride = (int)Math.Ceiling(bmp.Width / 8.0);//一个掩码字节八个bit可以储存八个像素的透明状态,每个bit表示一个像素是否透明,也因此掩码字节数量(步长)必须包含当前行全部像素,因此改用进一法更直观
        byte[] mask = new byte[maskStride * bmp.Height];
        for (int y = 0; y < bmp.Height; y++)
        {
            for (int x = 0; x < bmp.Width; x++)
            {
                var color = bmp.GetPixel(x, y);
                bool isTransparent = color.A < 128;//将alpha值范围一分为二简单的区分透明和不透明的像素
                if (isTransparent)
                {
                    int offset = y * maskStride + (x / 8);//获取用来表示对应像素所在掩码字节的索引(int自动去除小数)
                    // 将掩码位设置为1(对应透明区域)
                    mask[offset] |= (byte)(1 << (7 - (x % 8)));//生成一个指定位为1的字节,与索引对应字节执行位或运算,将指定bit位置的1写入到数组索引的字节中。先生成1的二进制数,%取模获取余数,然后从右往左移,右侧补零,所以需要7减取获取需要移动位置的位数。。。1 在二进制中是 0000 0001,因此只能左移。位与像素对应是从左往右。
                }
            }
        }
        return mask;
    }
}

ICO效果图以及在PE图标组效果图:

3.代码三

bmp格式生成多尺寸ICO(方式二)
//.cs  文件类型,便于外部编辑时使用
// 引用必要的命名空间
using System.Windows.Forms;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
 
// Quicker将会调用的函数。可以根据需要修改返回值类型。
public static void Exec(Quicker.Public.IStepContext context)
{
    //var oldValue = context.GetVarValue("varName");  // 读取动作里的变量值
    //MessageBox.Show(oldValue as string);
    //context.SetVarValue("varName", "从脚本输出的内容。"); // 向变量里输出值
    //MessageBox.Show("Hello World!");
    var bitmaps = new List<Bitmap>
    {
         new Bitmap(@"D:\SoftwareCache\Microsoft VS Code\图标\合成\16x16.png"),
         new Bitmap(@"D:\SoftwareCache\Microsoft VS Code\图标\合成\32x32.png"),
         new Bitmap(@"D:\SoftwareCache\Microsoft VS Code\图标\合成\48x48.png")
    };
    IcoConverter.SaveAsLegacyIco(bitmaps, @"D:\SoftwareCache\Microsoft VS Code\图标\合成\bitmap.ico");
 
}
 
public static class IcoConverter
{
    // ICO文件头结构 2
    [StructLayout(LayoutKind.Sequential)]
    private struct IconDir
    {
        public ushort Reserved;
        public ushort Type;
        public ushort Count;
    }
 
    // ICO目录项结构 2
    [StructLayout(LayoutKind.Sequential, Pack = 1)]
    private struct IconDirEntry
    {
        public byte Width;
        public byte Height;
        public byte ColorCount;
        public byte Reserved;
        public ushort Planes;
        public ushort BitCount;
        public uint BytesInRes;
        public uint ImageOffset;
    }
 
    /// <summary>
    /// 将多个BMP合并为多帧ICO文件
    /// </summary>
    public static void SaveAsLegacyIco(IEnumerable<Bitmap> bitmaps, string outputPath)
    {
        var entries = new List<IconDirEntry>();
        var imageDataList = new List<byte[]>();
        uint currentOffset = 6 + (uint)(16 * bitmaps.Count()); // 头部长度 2
 
        // 生成所有图标的BMP数据
        foreach (var bmp in bitmaps)
        {
            var data = GetBitmapData(bmp);
            var entry = new IconDirEntry
            {
                Width = (byte)(bmp.Width >= 256 ? 0 : bmp.Width),
                Height = (byte)(bmp.Height >= 256 ? 0 : bmp.Height),
                ColorCount = 0,
                Planes = 1,
                BitCount = 32,
                BytesInRes = (uint)data.Length,
                ImageOffset = currentOffset
            };
            
            entries.Add(entry);
            imageDataList.Add(data);
            currentOffset += (uint)data.Length;
        }
 
        // 写入文件
        using (var fs = new FileStream(outputPath, FileMode.Create))
        using (var writer = new BinaryWriter(fs))
        {
            // 写入ICO头部 2
            writer.Write((ushort)0); // Reserved
            writer.Write((ushort)1); // Type=ICO
            writer.Write((ushort)entries.Count); // Image count
 
            // 写入目录项
            foreach (var entry in entries)
            {
                writer.Write(entry.Width);
                writer.Write(entry.Height);
                writer.Write(entry.ColorCount);
                writer.Write(entry.Reserved);
                writer.Write(entry.Planes);
                writer.Write(entry.BitCount);
                writer.Write(entry.BytesInRes);
                writer.Write(entry.ImageOffset);
            }
 
            // 写入图像数据 11
            foreach (var data in imageDataList)
            {
                writer.Write(data);
            }
        }
    }
 
    /// <summary>
    /// 将Bitmap转为ICO兼容的BMP格式数据
    /// </summary>
    private static byte[] GetBitmapData(Bitmap bmp)
    {
         // 确保位图格式为32bppARGB
         var formattedBmp = new Bitmap(bmp.Width, bmp.Height, PixelFormat.Format32bppArgb);
         using (var g = Graphics.FromImage(formattedBmp))
         {
             g.DrawImage(bmp, new Rectangle(0, 0, bmp.Width, bmp.Height));
         }
         // 获取位图数据
         var bmpData = formattedBmp.LockBits(
             new Rectangle(0, 0, formattedBmp.Width, formattedBmp.Height),
             ImageLockMode.ReadOnly,
             formattedBmp.PixelFormat);
         try
         {
             // 构造BMP信息头
             var infoHeader = new byte[40];
             using (var ms = new MemoryStream(infoHeader))
             using (var writer = new BinaryWriter(ms))
             {
                 writer.Write(40);               // Header size
                 writer.Write(bmp.Width);       // Width
                 writer.Write(bmp.Height * 2);   // Height*2 (包含掩码)
                 writer.Write((ushort)1);        // Planes
                 writer.Write((ushort)32);       // BitCount
                 writer.Write(0);                // Compression
                 writer.Write(0);                // ImageSize
                 writer.Write(0);                // XPelsPerMeter
                 writer.Write(0);                // YPelsPerMeter
                 writer.Write(0);                // ColorsUsed
                 writer.Write(0);                // ColorsImportant
             }
             // 计算像素数据的大小
             int stride = bmpData.Stride;
             int height = bmpData.Height;
             int pixelDataSize = stride * height;
             // 创建缓冲区,包含信息头和像素数据
             var buffer = new byte[infoHeader.Length + pixelDataSize];
             // 复制信息头到缓冲区
             Buffer.BlockCopy(infoHeader, 0, buffer, 0, infoHeader.Length);
             // 逐行逆序复制像素数据(解决镜像问题)
             IntPtr ptr = bmpData.Scan0;
             int offset = infoHeader.Length;
             byte[] pixelData = new byte[pixelDataSize];
             // 将像素数据复制到临时数组
             Marshal.Copy(ptr, pixelData, 0, pixelDataSize);
             // 逐行逆序复制到缓冲区
             for (int y = height - 1; y >= 0; y--)
             {
                 int sourceIndex = y * stride;
                 Buffer.BlockCopy(pixelData, sourceIndex, buffer, offset, stride);
                 offset += stride;
             }
             return buffer;
         }
         finally
         {
             formattedBmp.UnlockBits(bmpData);
         }
     }
}

ICO效果图以及在PE图标组效果图:

2-3.完美版bmp格式ICO代码(含有二三代码特点,并在deepseek的优化下,掩码完美配对)

完美版bmp格式ICO生成方式
//.cs  文件类型,便于外部编辑时使用
// 引用必要的命名空间
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
 
// Quicker将会调用的函数。可以根据需要修改返回值类型。
public static void Exec(Quicker.Public.IStepContext context)
{
    //var oldValue = context.GetVarValue("varName");  // 读取动作里的变量值
    //MessageBox.Show(oldValue as string);
    //context.SetVarValue("varName", "从脚本输出的内容。"); // 向变量里输出值
    //MessageBox.Show("Hello World!");
    List<string> Paths = new List<string> {
        @"D:\SoftwareCache\Microsoft VS Code\图标\合成\16X16.png",
        @"D:\SoftwareCache\Microsoft VS Code\图标\合成\32X32.png",
        @"D:\SoftwareCache\Microsoft VS Code\图标\合成\48X48.png"
    };
    IcoConverter.SaveAsLegacyIco(Paths,@"D:\SoftwareCache\Microsoft VS Code\图标\合成\test.ico");
}
//bmp格式写入
public static class IcoConverter
{
    // ICO文件头结构 2
    [StructLayout(LayoutKind.Sequential)]
    private struct IconDir
    {
        public ushort Reserved;
        public ushort Type;
        public ushort Count;
    }
 
    // ICO目录项结构 2
    [StructLayout(LayoutKind.Sequential, Pack = 1)]
    private struct IconDirEntry
    {
        public byte Width;
        public byte Height;
        public byte ColorCount;
        public byte Reserved;
        public ushort Planes;
        public ushort BitCount;
        public uint BytesInRes;
        public uint ImageOffset;
    }
 
    /// <summary>
    /// 将多个BMP合并为多帧ICO文件
    /// </summary>
    public static void SaveAsLegacyIco(IEnumerable<string> bitmapPaths, string outputPath)
    {
        var entries = new List<IconDirEntry>();
        var imageDataList = new List<byte[]>();
        uint currentOffset = 6 + (uint)(16 * bitmapPaths.Count()); // 头部长度 2
 
        // 生成所有图标的BMP数据
        foreach (string bmpPath in bitmapPaths)
        {    
            using (Bitmap bmp = new Bitmap(bmpPath)) //保证使用后即释放资源
            {
                var data = GetBitmapData(bmp);
                var entry = new IconDirEntry
                {
                    Width = (byte)(bmp.Width >= 256 ? 0 : bmp.Width),
                    Height = (byte)(bmp.Height >= 256 ? 0 : bmp.Height),
                    ColorCount = 0,
                    Planes = 1,
                    BitCount = 32,
                    BytesInRes = (uint)data.Length,
                    ImageOffset = currentOffset
                };
                
                entries.Add(entry);
                imageDataList.Add(data);
                currentOffset += (uint)data.Length;
            }
        }
 
        // 写入文件
        using (var fs = new FileStream(outputPath, FileMode.Create))
        using (var writer = new BinaryWriter(fs))
        {
            // 写入ICO头部 2
            writer.Write((ushort)0); // Reserved
            writer.Write((ushort)1); // Type=ICO
            writer.Write((ushort)entries.Count); // Image count
 
            // 写入目录项
            foreach (var entry in entries)
            {
                writer.Write(entry.Width);
                writer.Write(entry.Height);
                writer.Write(entry.ColorCount);
                writer.Write(entry.Reserved);
                writer.Write(entry.Planes);
                writer.Write(entry.BitCount);
                writer.Write(entry.BytesInRes);
                writer.Write(entry.ImageOffset);
            }
 
            // 写入图像数据 11
            foreach (var data in imageDataList)
            {
                writer.Write(data);
            }
        }
    }
 
    /// <summary>
    /// 将Bitmap转为ICO兼容的BMP格式数据
    /// </summary>
    private static byte[] GetBitmapData(Bitmap bmp)
    {
        var formattedBmp = new Bitmap(bmp.Width, bmp.Height, PixelFormat.Format32bppArgb);
        using (var g = Graphics.FromImage(formattedBmp))
        {
            g.DrawImage(bmp, new Rectangle(0, 0, bmp.Width, bmp.Height));
        }
    
        var bmpData = formattedBmp.LockBits(
            new Rectangle(0, 0, formattedBmp.Width, formattedBmp.Height),
            ImageLockMode.ReadOnly,
            formattedBmp.PixelFormat);
    
        try
        {
            int width = bmp.Width;
            int height = bmp.Height;
            int stride = bmpData.Stride;
    
            // BMP信息头
            var infoHeader = new byte[40];
            using (var ms = new MemoryStream(infoHeader))
            using (var writer = new BinaryWriter(ms))
            {
                writer.Write(40);               // 信息头大小
                writer.Write(width);            // 宽度
                writer.Write(height * 2);       // 高度(颜色+掩码)
                writer.Write((ushort)1);        // 位面数
                writer.Write((ushort)32);       // 位深
                writer.Write(0);                // 压缩方式(无压缩)
                writer.Write(stride * height + ((width + 31) / 32 * 4) * height); // 图像数据大小
                writer.Write(0);                // 水平分辨率
                writer.Write(0);                // 垂直分辨率
                writer.Write(0);                // 调色板颜色数
                writer.Write(0);                // 重要颜色数
            }
    
            // 计算掩码参数
            int maskStride = (width + 31) / 32 * 4;
            int maskDataSize = maskStride * height;
            int colorDataSize = stride * height;
            var buffer = new byte[infoHeader.Length + colorDataSize + maskDataSize];
    
            // 复制信息头
            Buffer.BlockCopy(infoHeader, 0, buffer, 0, infoHeader.Length);
    
            // 复制颜色数据(逆序行)
            byte[] colorData = new byte[colorDataSize];
            Marshal.Copy(bmpData.Scan0, colorData, 0, colorDataSize);
            int colorOffset = infoHeader.Length;
            for (int y = 0; y < height; y++)
            {
                int srcIndex = y * stride;
                int destIndex = colorOffset + (height - 1 - y) * stride;
                Buffer.BlockCopy(colorData, srcIndex, buffer, destIndex, stride);
            }
    
            // 生成并复制掩码数据
            byte[] maskData = GenerateMaskData(colorData, width, height, stride, maskStride);
            Buffer.BlockCopy(maskData, 0, buffer, infoHeader.Length + colorDataSize, maskDataSize);
    
            return buffer;
        }
        finally
        {
            formattedBmp.UnlockBits(bmpData);
            formattedBmp.Dispose();
        }
    }
    /// <summary>
    /// 生成AND掩码位图(1位/像素)
    /// </summary>
    private static byte[] GenerateMaskData(byte[] colorData, int width, int height, int colorStride, int maskStride)
    {
        byte[] maskData = new byte[maskStride * height];
    
        for (int y = 0; y < height; y++)
        {
            int srcY = y; // 原图的行  
            byte[] maskRow = new byte[maskStride];
            int bitPos = 7;
            int byteIndex = 0;
    
            for (int x = 0; x < width; x++)
            {
                int pixelIndex = srcY * colorStride + x * 4;
                byte alpha = colorData[pixelIndex + 3]; // Alpha通道
    
                if (alpha == 0)
                    maskRow[byteIndex] |= (byte)(1 << bitPos);
    
                if (--bitPos < 0)
                {
                    bitPos = 7;
                    byteIndex++;
                }
            }
    
            // 掩码行逆序存储
            int destY = height - 1 - y;
            Buffer.BlockCopy(maskRow, 0, maskData, destY * maskStride, maskStride);
        }
    
        return maskData;
    }
}

ICO效果图以及在PE图标组效果图_以及在VS的显示效果

4.代码四(最直接粗暴的方式,因此有瑕疵)

用于参照的单尺寸ICO生成方式
//.cs  文件类型,便于外部编辑时使用
// 引用必要的命名空间
using System.Windows.Forms;
using System.Drawing;
using System.IO;
 
// Quicker将会调用的函数。可以根据需要修改返回值类型。
public static void Exec(Quicker.Public.IStepContext context)
{
    //var oldValue = context.GetVarValue("varName");  // 读取动作里的变量值
    //MessageBox.Show(oldValue as string);
    //context.SetVarValue("varName", "从脚本输出的内容。"); // 向变量里输出值
    //MessageBox.Show("Hello World!");
    BmpToIconConverter.ConvertBmpToIco(@"D:\SoftwareCache\Microsoft VS Code\图标\合成\16X16.png",@"D:\SoftwareCache\Microsoft VS Code\图标\合成\16x16.ico",16,16);
    BmpToIconConverter.ConvertBmpToIco(@"D:\SoftwareCache\Microsoft VS Code\图标\合成\32X32.png",@"D:\SoftwareCache\Microsoft VS Code\图标\合成\32X32.ico",32,32);
    BmpToIconConverter.ConvertBmpToIco(@"D:\SoftwareCache\Microsoft VS Code\图标\合成\48X48.png",@"D:\SoftwareCache\Microsoft VS Code\图标\合成\48x48.ico",48,48);
}
public class BmpToIconConverter
{
    public static void ConvertBmpToIco(string inputPath, string outputPath, int width, int height)
    {
        // 设置目标尺寸(如32x32)
        Size size = new Size(width, height);
 
        // 加载原始BMP文件
        using (Bitmap sourceBmp = new Bitmap(inputPath))
        {
            // 调整图像尺寸
            using (Bitmap resizedBmp = new Bitmap(sourceBmp, size))
            {
                // 从调整后的位图生成Icon对象
                using (Icon icon = Icon.FromHandle(resizedBmp.GetHicon()))
                {
                    // 保存为ICO文件
                    using (FileStream stream = new FileStream(outputPath, FileMode.Create))
                    {
                        icon.Save(stream);
                    }
                }
            }
        }
    }
}

ICO效果图以及在PE图标组效果图(有透明效果瑕疵,但是在桌面/资源管理器却显示正常):

bmp格式生成多尺寸ICO(方式三)___按上面修改出的多尺寸图标生成
//.cs  文件类型,便于外部编辑时使用
// 引用必要的命名空间
using System.Windows.Forms;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Runtime.InteropServices;
 
// Quicker将会调用的函数。可以根据需要修改返回值类型。
public static void Exec(Quicker.Public.IStepContext context)
{
    //var oldValue = context.GetVarValue("varName");  // 读取动作里的变量值
    //MessageBox.Show(oldValue as string);
    //context.SetVarValue("varName", "从脚本输出的内容。"); // 向变量里输出值
    //MessageBox.Show("Hello World!");
    var bitmaps = new List<Bitmap>
    {
         new Bitmap(@"D:\SoftwareCache\Microsoft VS Code\图标\合成\16x16.png"),
         new Bitmap(@"D:\SoftwareCache\Microsoft VS Code\图标\合成\32x32.png"),
        new Bitmap(@"D:\SoftwareCache\Microsoft VS Code\图标\合成\48x48.png")
    };
    // 合成ICO文件
    SaveMultiIcon(bitmaps, @"D:\SoftwareCache\Microsoft VS Code\图标\合成\icoOK.ico");
    
}
 
public class IcoEntry
{
    public byte Width;          // 图标宽度(0 表示 256)
    public byte Height;         // 图标高度(0 表示 256)
    public byte ColorCount;    // 颜色数(0 表示未使用)
    public byte Reserved;      // 保留字段(必须为 0)
    public ushort Planes;       // 颜色平面数(通常为 1)
    public ushort BitCount;    // 每像素位数(1, 4, 8, 24, 32)
    public uint BytesInRes;    // 图像数据大小
    public uint ImageOffset;   // 图像数据偏移量
}
 
public static void SaveMultiIcon(List<Bitmap> bitmaps, string outputPath)
{
    List<IcoEntry> entries = new List<IcoEntry>();
    List<byte[]> imageDatas = new List<byte[]>();
    uint currentOffset = 6 + (uint)(bitmaps.Count * 16); // ICO 头部 + 目录项大小
 
    foreach (Bitmap bmp in bitmaps)
    {
        IntPtr hIcon = IntPtr.Zero;
        try
        {
            hIcon = bmp.GetHicon();
            using (Icon icon = Icon.FromHandle(hIcon))
            using (MemoryStream ms = new MemoryStream())
            {
                icon.Save(ms); // 将 Icon 保存到内存流
                ms.Position = 0;
 
                using (BinaryReader reader = new BinaryReader(ms))
                {
                    // 读取 ICO 头部
                    reader.ReadUInt16(); // Reserved (必须为 0)
                    reader.ReadUInt16(); // Type (1 表示 ICO)
                    ushort count = reader.ReadUInt16(); // 图像数量(应始终为 1)
 
                    // 读取目录项
                    IcoEntry entry = new IcoEntry
                    {
                        Width = reader.ReadByte(),
                        Height = reader.ReadByte(),
                        ColorCount = reader.ReadByte(),
                        Reserved = reader.ReadByte(),
                        Planes = reader.ReadUInt16(),
                        BitCount = reader.ReadUInt16(),
                        BytesInRes = reader.ReadUInt32(),
                        ImageOffset = reader.ReadUInt32()
                    };
 
                    // 读取图像数据
                    ms.Position = entry.ImageOffset;
                    byte[] data = reader.ReadBytes((int)entry.BytesInRes);
 
                    // 更新当前 entry 的 ImageOffset
                    entry.ImageOffset = currentOffset;
                    currentOffset += entry.BytesInRes;
 
                    entries.Add(entry);
                    imageDatas.Add(data);
                }
            }
        }
        finally
        {
            if (hIcon != IntPtr.Zero)
                DestroyIcon(hIcon); // 清理图标句柄
        }
    }
 
    // 写入合并后的 ICO 文件
    using (var fs = new FileStream(outputPath, FileMode.Create))
    using (var writer = new BinaryWriter(fs))
    {
        // 写入 ICO 头部
        writer.Write((ushort)0); // Reserved
        writer.Write((ushort)1); // Type (1 表示 ICO)
        writer.Write((ushort)entries.Count); // 图像数量
 
        // 写入目录项
        foreach (var entry in entries)
        {
            writer.Write(entry.Width);
            writer.Write(entry.Height);
            writer.Write(entry.ColorCount);
            writer.Write(entry.Reserved);
            writer.Write(entry.Planes);
            writer.Write(entry.BitCount);
            writer.Write(entry.BytesInRes);
            writer.Write(entry.ImageOffset);
        }
 
        // 写入图像数据
        foreach (var data in imageDatas)
        {
            writer.Write(data);
        }
    }
}
 
[DllImport("user32.dll", SetLastError = true)]
private static extern bool DestroyIcon(IntPtr hIcon);

ICO效果图以及在PE图标组效果图(同样继承瑕疵):

· {{comment.createTimeStr}}
{{reply.votePoints}}
回复   – {{reply.createTimeStr}}
回复 x
标签
目录
相关操作