toolgarden.xyz
EN
浏览器工具开发ICOICNS二进制格式Canvas

怎么在浏览器生成 ICO 和 ICNS 图标文件

从多尺寸 Canvas 渲染到 ICO 目录、DIB 像素、ICNS 大端序 Chunk 和 ZIP 导出,完整实现无需服务端的图标生成。

ToolGarden 推荐的工具优先在浏览器本地运行,文件和文本不必上传到服务器,适合更注重安全隐私的日常处理。

发布于 2026年7月22日约 12 分钟阅读作者 ToolGarden

ICO 和 ICNS 是容器格式,不是改了扩展名的 PNG。它们会存储多个显示尺寸,使用不同字节序与目录结构,并对透明度、行顺序和尺寸元数据有明确要求。

浏览器可以解码源图,把统一裁剪与缩放变换应用到所有目标尺寸,编码 PNG 预览,为较小 ICO 层写入 Windows DIB,再构造 macOS ICNS Chunk,同时导出组合文件与独立尺寸包。

所有目标尺寸使用同一个标准化变换

通过 Object URL 加载图片,先校验文件字节和解码后像素数,只在编辑期间保留图片元素。标准化 transform 包含 scale、x/y offset 与圆角半径,预览和每个输出分辨率都使用完全相同的构图。

对每个配置尺寸创建独立正方形 Canvas,需要时裁剪圆角,开启高质量平滑,先适配源图,再叠加用户缩放与偏移,最后同时读取 ImageData 与 PNG 字节。

格式常见尺寸容器内容
ICO16 到 256小尺寸使用 DIB,256 使用 PNG
ICNS16 到 1024带类型的 PNG Chunk
PNG ZIP16 到 1024独立 PNG 文件

正确写入 Windows DIB 像素

小尺寸 ICO 可以使用 40 字节 BITMAPINFOHEADER,后面跟 32 位像素和 AND mask。存储高度需要乘以二,因为同时包括 XOR 颜色位图与 mask。像素通道是 BGRA 而不是 Canvas RGBA,行顺序则是从下到上。

每一行 AND mask 都要补齐到 32 位边界。使用完整 alpha 时 mask 可以全零,但它的字节和 stride 仍然属于 DIB。256px 条目通常直接保存 PNG,可以避开 ICO 单字节宽高上限。

  • DIB 数字使用小端序。
  • 把 Canvas 从上到下的行反转成位图从下到上。
  • 交换红蓝通道,生成 BGRA。
  • ICO 目录中 256 的宽高字段写成零。

构造 ICO 目录与数据偏移

ICO 从六字节 header 开始,后面每张图片占一个 16 字节目录项。目录项描述宽高、颜色信息、payload 长度和绝对 offset,全部 payload 则依次拼在目录之后。

offset 要从完整目录总长度开始累计。任何一个字节长度写错,都会让后面全部图片错位,即使单独的 PNG 或 DIB 数据本身正确,整个图标也可能无法读取。

const directorySize = 6 + entries.length * 16;
const header = new Uint8Array(directorySize);
const view = new DataView(header.buffer);

view.setUint16(0, 0, true);              // reserved
view.setUint16(2, 1, true);              // ICO image type
view.setUint16(4, entries.length, true); // image count

entries.forEach((entry, index) => {
  const offset = 6 + index * 16;
  header[offset] = entry.size >= 256 ? 0 : entry.size;
  header[offset + 1] = entry.size >= 256 ? 0 : entry.size;
  view.setUint16(offset + 6, 32, true);
  view.setUint32(offset + 8, entry.bytes.byteLength, true);
  view.setUint32(offset + 12, entry.dataOffset, true);
});

使用大端序 Typed Chunk 构造 ICNS

ICNS 以 ASCII 字符 icns 和容器总长度开头。每个图像表示是一个 Chunk,包含四字符类型、Chunk 长度与 payload。现代尺寸可以在对应类型下直接存储 PNG 数据。

与 ICO 相反,ICNS 长度是大端序。维护尺寸到 Chunk Type 的映射,明确忽略不支持的尺寸,写 header 前先计算总字节,并按稳定顺序拼接 Chunk。

function createIcnsChunk(type: string, png: Uint8Array) {
  const chunk = new Uint8Array(8 + png.byteLength);
  const view = new DataView(chunk.buffer);
  writeAscii(chunk, 0, type);
  view.setUint32(4, chunk.byteLength, false); // ICNS uses big-endian lengths
  chunk.set(png, 8);
  return chunk;
}

const chunks = entries.map(entry =>
  createIcnsChunk(chunkTypeForSize(entry.size), entry.pngBytes)
);
const header = createIcnsHeader(8 + sumByteLengths(chunks));
return new Blob([header, ...chunks], { type: 'image/icns' });

同时提供组合文件和可检查归档

主 ICO 或 ICNS 应包含全部支持尺寸。单尺寸 ICO、ICNS 或 PNG ZIP 适合调试,也能满足部分平台要求独立资产的场景。两种产物应来自同一份渲染 entry,避免视觉漂移。

源图、组合输出和 ZIP 输出需要分别追踪 Object URL 生命周期。用户更换源图、格式或变换后 revoke 旧 URL,组件卸载时全部释放。

必须在真实消费者中测试

预览 Canvas 无法验证容器。ICO 应在 Windows Explorer、快捷方式、浏览器 favicon 和图标检查器中测试;ICNS 则要用 macOS Finder 或图标工具检查。重点关注 16px 可读性、透明、圆角和高分辨率层。

不要把小源图放大后称为多分辨率质量。大尺寸输出仍然只有有限源细节,而小尺寸图标往往需要比自动缩放更简单的形状和更强对比度。

总结

浏览器图标生成同时涉及视觉渲染与二进制格式工程。标准化一份变换,渲染全部目标尺寸,遵守 ICO DIB 行与通道规则,为 ICNS 切换字节序,准确计算 offset,并在真实操作系统消费者中验证。

常见问题

Q.把 PNG 扩展名改成 ICO 可以吗?

不可以。ICO 包含 header 与图像目录,还可能装入多个 PNG 或 DIB payload。改名只改变文件名,不会改变容器字节。

Q.为什么 ICO 目录里的 256px 尺寸写成 0?

ICO 的宽高字段只有一个字节,格式规定零代表 256,从而在不扩展字段的情况下支持这个特殊尺寸。

Q.为什么 ICO DIB 像素行要从下往上?

ICO 使用的传统位图表示遵循 Windows DIB 约定。正高度位图的第一行代表底部,与 Canvas ImageData 相反。

Q.为什么 ICNS 和 ICO 的字节序不同?

它们是两个独立平台格式。ICO 结构中的整数使用小端序,ICNS 容器与 Chunk 长度则使用大端序。