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 字节:
| 字段 | 大小 | 类型 | 说明 |
|---|---|---|---|
idReserved | 2 字节 | WORD | 保留字段,必须为 0 |
idType | 2 字节 | WORD | 资源类型,1 = 图标(ICO),2 = 光标(CUR) |
idCount | 2 字节 | WORD | 文件中包含的图像数量 |
用十六进制表示的一个典型 ICO 头部:
00 00 // idReserved = 0
01 00 // idType = 1 (ICO)
03 00 // idCount = 3 (包含3个图像)
2.3 ICONDIRENTRY 目录条目
每个图像对应一个目录条目,每个条目固定 16 字节:
| 字段 | 大小 | 类型 | 说明 |
|---|---|---|---|
bWidth | 1 字节 | BYTE | 图像宽度(0 表示 256) |
bHeight | 1 字节 | BYTE | 图像高度(0 表示 256) |
bColorCount | 1 字节 | BYTE | 颜色数量(0 表示 >= 256 色) |
bReserved | 1 字节 | BYTE | 保留,必须为 0 |
wPlanes | 2 字节 | WORD | 颜色平面数(0 或 1) |
wBitCount | 2 字节 | WORD | 每像素位数(1, 4, 8, 16, 24, 32) |
dwBytesInRes | 4 字节 | DWORD | 图像数据的字节大小 |
dwImageOffset | 4 字节 | 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 字节):
| 字段 | 大小 | 说明 |
|---|---|---|
biSize | 4 字节 | 头部大小,固定为 40 |
biWidth | 4 字节 | 图像宽度 |
biHeight | 4 字节 | 图像高度的两倍(包含 AND 掩码) |
biPlanes | 2 字节 | 颜色平面数,固定为 1 |
biBitCount | 2 字节 | 每像素位数 |
biCompression | 4 字节 | 压缩方式,BI_RGB = 0 |
biSizeImage | 4 字节 | 图像数据大小 |
biXPelsPerMeter | 4 字节 | 水平分辨率 |
biYPelsPerMeter | 4 字节 | 垂直分辨率 |
biClrUsed | 4 字节 | 使用的颜色数 |
biClrImportant | 4 字节 | 重要颜色数 |
注意: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 功能对比
| 特性 | ICO | PNG |
|---|---|---|
| 多尺寸支持 | 支持(一个文件多个尺寸) | 不支持(一个文件一个尺寸) |
| 透明通道 | 支持(AND Mask 或 32-bit BGRA) | 支持(Alpha 通道) |
| 压缩方式 | 无压缩 / PNG 压缩 | 无损压缩 |
| 最大尺寸 | 256×256 | 无限制 |
| 浏览器兼容性 | 极好(包括 IE6+) | 好(IE9+ 完全支持) |
| 动画支持 | 不支持 | 不支持(APNG 除外) |
| 文件体积 | 多尺寸时较大 | 单尺寸时较小 |
| 矢量格式 | 不支持 | 不支持 |
3.2 适用场景
| 场景 | 推荐格式 | 原因 |
|---|---|---|
| 传统浏览器兼容 | ICO | 所有浏览器都支持 |
| 现代浏览器标签页 | PNG | 更好的压缩率 |
| 多尺寸分发 | ICO | 一个文件包含多个尺寸 |
| 高分辨率显示 | PNG / SVG | PNG 质量好,SVG 可缩放 |
| Apple Touch Icon | PNG | iOS 要求 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×16 | 1.2 KB |
| 16×16 + 32×32 | 5.1 KB |
| 16×16 + 32×32 + 48×48 | 12.3 KB |
| 16~64 | 26.5 KB |
| 16~128 | 67.8 KB |
| 16~256(256 用 PNG 压缩) | 80-100 KB |
| 16~256(全部 BMP) | 275+ KB |
5.2 优化策略
- 只包含必要的尺寸:不需要为所有尺寸都创建 ICO 图像
- 大尺寸使用 PNG 压缩:256×256 使用 PNG 可以显著减小体积
- 使用 24-bit 颜色:如果不需要 Alpha 通道,24-bit 比 32-bit 节省空间
- 使用调色板:对于简单图标,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 的内部结构有助于:
- 更好地使用 Favicon 生成工具:了解工具在做什么
- 优化文件大小:知道哪些参数影响体积
- 解决兼容性问题:理解为什么某些浏览器行为异常
- 开发自定义工具:用 JavaScript 实现 ICO 编解码
我们的在线 Favicon 生成工具正是基于对 ICO 格式的深入理解,在浏览器端直接完成图像到 ICO 的转换,无需服务器参与,既保护了用户隐私,又提供了即时的转换体验。