ICO 格式详解:结构与原理

工具相关 ·

ICO(Icon File Format)是 Windows 操作系统使用的图标文件格式,也是 Web 开发中最传统的 Favicon 格式。尽管 ICO 格式已经诞生了几十年,它至今仍然是 Favicon 的标准格式之一,主要因为它的独特能力——在单个文件中存储多个尺寸和颜色深度的图像。

本文将深入解析 ICO 文件的内部结构,帮助你理解这种格式的工作原理,以及为什么它至今仍在 Web 开发中扮演重要角色。


一、ICO 格式概述

1.1 基本信息

属性说明
全称Icon File Format
文件扩展名.ico
MIME 类型image/x-icon
最初版本Windows 1.0 (1985)
最大尺寸256×256 像素
颜色深度支持 1-bit 到 32-bit
压缩方式无压缩 / PNG 压缩(256×256)

1.2 ICO 格式的核心优势

ICO 格式最大的优势在于它可以在一个文件中封装多个图像变体:

favicon.ico
├── 图像 1: 16×16,  32-bit color (完整像素数据)
├── 图像 2: 32×32,  32-bit color (完整像素数据)
├── 图像 3: 48×48,  32-bit color (完整像素数据)
├── 图像 4: 64×64,  32-bit color (完整像素数据)
├── 图像 5: 128×128, 32-bit color (PNG 压缩)
└── 图像 6: 256×256, 32-bit color (PNG 压缩)

浏览器会根据显示需求自动选择最合适的尺寸。


二、ICO 文件结构

ICO 文件由三个主要部分组成:

2.1 整体结构

┌────────────────────────────────────┐
│         ICONDIR (头部)             │  6 字节
├────────────────────────────────────┤
│      ICONDIRENTRY[0] (目录条目)     │  16 字节
│      ICONDIRENTRY[1]              │  16 字节
│      ICONDIRENTRY[2]              │  16 字节
│      ...                          │
├────────────────────────────────────┤
│      图像数据 0                    │  变长
│      图像数据 1                    │  变长
│      图像数据 2                    │  变长
│      ...                          │
└────────────────────────────────────┘

2.2 ICONDIR 头部结构

ICONDIR 是 ICO 文件的开头部分,固定 6 字节:

字段大小类型说明
idReserved2 字节WORD保留字段,必须为 0
idType2 字节WORD资源类型,1 = 图标(ICO),2 = 光标(CUR)
idCount2 字节WORD文件中包含的图像数量

用十六进制表示的一个典型 ICO 头部:

00 00    // idReserved = 0
01 00    // idType = 1 (ICO)
03 00    // idCount = 3 (包含3个图像)

2.3 ICONDIRENTRY 目录条目

每个图像对应一个目录条目,每个条目固定 16 字节:

字段大小类型说明
bWidth1 字节BYTE图像宽度(0 表示 256)
bHeight1 字节BYTE图像高度(0 表示 256)
bColorCount1 字节BYTE颜色数量(0 表示 >= 256 色)
bReserved1 字节BYTE保留,必须为 0
wPlanes2 字节WORD颜色平面数(0 或 1)
wBitCount2 字节WORD每像素位数(1, 4, 8, 16, 24, 32)
dwBytesInRes4 字节DWORD图像数据的字节大小
dwImageOffset4 字节DWORD图像数据在文件中的偏移量

以一个包含 3 个图像的 ICO 文件为例,目录条目的布局:

// 目录条目 0: 16x16, 32-bit
10       // bWidth = 16 (0x10)
10       // bHeight = 16 (0x10)
00       // bColorCount = 0 (>= 256色)
00       // bReserved = 0
01 00    // wPlanes = 1
20 00    // wBitCount = 32 (0x0020)
28 01 00 00  // dwBytesInRes = 296
16 00 00 00  // dwImageOffset = 22 (6 + 3×16)

// 目录条目 1: 32x32, 32-bit
20       // bWidth = 32 (0x20)
20       // bHeight = 32 (0x20)
00       // bColorCount = 0
00       // bReserved = 0
01 00    // wPlanes = 1
20 00    // wBitCount = 32
...

// 目录条目 2: 48x48, 32-bit
30       // bWidth = 48 (0x30)
30       // bHeight = 48 (0x30)
...

2.4 图像数据

ICO 文件中每个图像的数据格式取决于其大小:

小尺寸(

对于小于等于 48×48 的图像,数据使用 BMP(DIB)格式存储:

图像数据(BMP 格式)
┌─────────────────────────────┐
│  BITMAPINFOHEADER (40字节)   │  包含宽高、位数等信息
├─────────────────────────────┤
│  XOR Mask (像素数据)         │  颜色数据(BGRA)
├─────────────────────────────┤
│  AND Mask (掩码数据)         │  1-bit 透明度掩码
└─────────────────────────────┘

BITMAPINFOHEADER 结构(40 字节):

字段大小说明
biSize4 字节头部大小,固定为 40
biWidth4 字节图像宽度
biHeight4 字节图像高度的两倍(包含 AND 掩码)
biPlanes2 字节颜色平面数,固定为 1
biBitCount2 字节每像素位数
biCompression4 字节压缩方式,BI_RGB = 0
biSizeImage4 字节图像数据大小
biXPelsPerMeter4 字节水平分辨率
biYPelsPerMeter4 字节垂直分辨率
biClrUsed4 字节使用的颜色数
biClrImportant4 字节重要颜色数

注意:biHeight 是实际高度的两倍,因为 BMP 数据中同时包含了 XOR Mask(颜色数据)和 AND Mask(透明度掩码)。

大尺寸(>= 256×256):PNG 格式

从 Windows Vista 开始,大于等于 256×256 的图像可以使用 PNG 格式存储,以减小文件体积:

图像数据(PNG 格式)
┌─────────────────────────────┐
│  PNG Signature (8字节)       │  89 50 4E 47 0D 0A 1A 0A
├─────────────────────────────┤
│  PNG Chunk: IHDR            │
│  PNG Chunk: IDAT            │
│  PNG Chunk: ...             │
│  PNG Chunk: IEND            │
└─────────────────────────────┘

浏览器会检测到 PNG 签名并自动使用 PNG 解码器。


三、ICO 与 PNG 格式对比

3.1 功能对比

特性ICOPNG
多尺寸支持支持(一个文件多个尺寸)不支持(一个文件一个尺寸)
透明通道支持(AND Mask 或 32-bit BGRA)支持(Alpha 通道)
压缩方式无压缩 / PNG 压缩无损压缩
最大尺寸256×256无限制
浏览器兼容性极好(包括 IE6+)好(IE9+ 完全支持)
动画支持不支持不支持(APNG 除外)
文件体积多尺寸时较大单尺寸时较小
矢量格式不支持不支持

3.2 适用场景

场景推荐格式原因
传统浏览器兼容ICO所有浏览器都支持
现代浏览器标签页PNG更好的压缩率
多尺寸分发ICO一个文件包含多个尺寸
高分辨率显示PNG / SVGPNG 质量好,SVG 可缩放
Apple Touch IconPNGiOS 要求 PNG 格式
最小文件体积SVG矢量格式,通常最小

3.3 浏览器支持对比

浏览器              ICO    PNG    SVG
─────────────────────────────────────
Chrome              ✓      ✓      ✓ (v80+)
Firefox             ✓      ✓      ✓ (v41+)
Safari              ✓      ✓      ✓ (v13+)
Edge (Chromium)     ✓      ✓      ✓
Edge (Legacy)       ✓      ✓      ✗
IE 11               ✓      ✓      ✗
IE 9-10             ✓      ✓      ✗
IE 6-8              ✓      ✗      ✗

四、用 JavaScript 实现 ICO 编码

了解 ICO 格式的结构后,我们可以用 JavaScript 在浏览器端实现 ICO 编码。以下是核心实现思路:

4.1 基本流程

// 1. 使用 Canvas API 将图片缩放到目标尺寸
// 2. 提取像素数据
// 3. 按照 ICO 格式组装二进制数据
// 4. 生成 Blob 对象供下载

async function createICO(images) {
    // images: [{ size: 16, canvas: HTMLCanvasElement }, ...]
    
    // 步骤 1: 构建 ICO 头部
    const header = new ArrayBuffer(6);
    const headerView = new DataView(header);
    headerView.setUint16(0, 0, true);      // idReserved
    headerView.setUint16(2, 1, true);      // idType = 1 (ICO)
    headerView.setUint16(4, images.length, true); // idCount
    
    // 步骤 2: 计算每个图像数据的偏移量
    const dirSize = images.length * 16;
    let offset = 6 + dirSize;
    
    // 步骤 3: 构建目录条目和图像数据
    const entries = [];
    const dataList = [];
    
    for (const img of images) {
        const data = getBMPData(img.canvas, img.size);
        entries.push(createDirEntry(img.size, data.byteLength, offset));
        dataList.push(data);
        offset += data.byteLength;
    }
    
    // 步骤 4: 合并所有数据
    return combineBuffers(header, ...entries, ...dataList);
}

4.2 提取 Canvas 像素数据

function getPixelData(canvas) {
    const ctx = canvas.getContext('2d');
    const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
    // imageData.data 是一个 Uint8ClampedArray
    // 每 4 个元素表示一个像素: [R, G, B, A]
    return imageData;
}

4.3 BMP 数据编码

function createBMPData(canvas, size) {
    const ctx = canvas.getContext('2d');
    const imageData = ctx.getImageData(0, 0, size, size);
    const pixels = imageData.data;
    
    // BMP 格式: XOR mask (BGRA) + AND mask (1-bit alpha)
    const xorSize = size * size * 4;
    const andRowSize = Math.ceil(size / 32) * 4; // 32-bit 对齐
    const andSize = andRowSize * size;
    const headerSize = 40;
    
    const buffer = new ArrayBuffer(headerSize + xorSize + andSize);
    const view = new DataView(buffer);
    
    // 写入 BITMAPINFOHEADER
    view.setUint32(0, 40, true);          // biSize
    view.setInt32(4, size, true);         // biWidth
    view.setInt32(8, size * 2, true);     // biHeight (双倍)
    view.setUint16(12, 1, true);          // biPlanes
    view.setUint16(14, 32, true);         // biBitCount
    view.setUint32(16, 0, true);          // biCompression = BI_RGB
    view.setUint32(20, xorSize + andSize, true); // biSizeImage
    
    // 写入 XOR mask (BMP 数据从底部开始,需要翻转)
    let offset = headerSize;
    for (let y = size - 1; y >= 0; y--) {
        for (let x = 0; x < size; x++) {
            const srcIdx = (y * size + x) * 4;
            // BMP 使用 BGRA 顺序
            view.setUint8(offset++, pixels[srcIdx + 2]); // B
            view.setUint8(offset++, pixels[srcIdx + 1]); // G
            view.setUint8(offset++, pixels[srcIdx]);     // R
            view.setUint8(offset++, pixels[srcIdx + 3]); // A
        }
    }
    
    // 写入 AND mask (全 0,因为 Alpha 已在 XOR mask 中)
    // AND mask 全部为 0 即可(32-bit BMP 的透明度由 Alpha 通道控制)
    
    return buffer;
}

五、ICO 文件大小的考量

5.1 文件大小对比

以下是不同尺寸组合的 ICO 文件大致大小(32-bit 颜色):

包含尺寸大约文件大小
16×161.2 KB
16×16 + 32×325.1 KB
16×16 + 32×32 + 48×4812.3 KB
16~6426.5 KB
16~12867.8 KB
16~256(256 用 PNG 压缩)80-100 KB
16~256(全部 BMP)275+ KB

5.2 优化策略

  1. 只包含必要的尺寸:不需要为所有尺寸都创建 ICO 图像
  2. 大尺寸使用 PNG 压缩:256×256 使用 PNG 可以显著减小体积
  3. 使用 24-bit 颜色:如果不需要 Alpha 通道,24-bit 比 32-bit 节省空间
  4. 使用调色板:对于简单图标,8-bit(256 色)可以大幅减小体积

六、实际案例:解析一个 ICO 文件

让我们解析一个典型的 favicon.ico 文件的二进制内容:

文件偏移   十六进制              含义
──────────────────────────────────────────
0000      00 00                idReserved = 0
0002      01 00                idType = 1 (ICO)
0004      03 00                idCount = 3 (3个图像)

// 目录条目 0: 16x16
0006      10                   bWidth = 16
0007      10                   bHeight = 16
0008      00                   bColorCount = 0
0009      00                   bReserved = 0
000A      01 00                wPlanes = 1
000C      20 00                wBitCount = 32
000E      28 01 00 00         dwBytesInRes = 296
0012      36 00 00 00         dwImageOffset = 54

// 目录条目 1: 32x32
0016      20                   bWidth = 32
0017      20                   bHeight = 32
0018      00                   bColorCount = 0
0019      00                   bReserved = 0
001A      01 00                wPlanes = 1
001C      20 00                wBitCount = 32
001E      00 11 00 00         dwBytesInRes = 4352
0022      5E 01 00 00         dwImageOffset = 350

// 目录条目 2: 48x48
0026      30                   bWidth = 48
0027      30                   bHeight = 48
0028      00                   bColorCount = 0
0029      00                   bReserved = 0
002A      01 00                wPlanes = 1
002C      20 00                wBitCount = 32
002E      70 11 00 00         dwBytesInRes = 4464
0032      5E 11 00 00         dwImageOffset = 4510

// 然后是各图像的 BMP 数据...

七、ICO 格式的局限性

7.1 技术局限

局限说明
最大尺寸256×256 像素,无法满足更大图标需求
颜色深度最高 32-bit,不支持 HDR 等高级特性
无动画支持不像 GIF/APNG 可以支持动画
无矢量支持纯位图格式,无法无损缩放
编辑复杂修改需要专业工具

7.2 现代替代方案

需求推荐替代
可缩放图标SVG
高分辨率图标高分辨率 PNG
动画图标APNG / 视频
自适应主题SVG + CSS 变量
跨平台一致性多格式组合方案

八、总结

ICO 格式虽然是几十年前的设计,但它的多尺寸封装能力使其在 Favicon 领域至今仍具有不可替代的地位。理解 ICO 的内部结构有助于:

  1. 更好地使用 Favicon 生成工具:了解工具在做什么
  2. 优化文件大小:知道哪些参数影响体积
  3. 解决兼容性问题:理解为什么某些浏览器行为异常
  4. 开发自定义工具:用 JavaScript 实现 ICO 编解码

我们的在线 Favicon 生成工具正是基于对 ICO 格式的深入理解,在浏览器端直接完成图像到 ICO 的转换,无需服务器参与,既保护了用户隐私,又提供了即时的转换体验。


延伸阅读

阅读 13