PNG转ICO转换器

选一张本地 PNG,按 16/32/48/64/128/256 六档重绘,每档用 canvas.toDataURL('image/png') 导出的 PNG 字节直接内嵌进 ICO 容器——头部的 ICONDIR(6 字节)与每档 ICONDIRENTRY(16 字节)由页面逐字节手写,偏移逐档累加。

16 px
32 px
48 px
源图尺寸—
输出档位(边长 px)—
ICO 头部字节—
ICO 总体积—
同档 BMP 路径总长—
PNG / BMP 体积比—
256 档宽高字段—
缩放方式—

本页走 PNG-in-ICO 路径:每档图像数据区就是一段完整的 PNG(含 8 字节签名 89 50 4E 47 0D 0A 1A 0A、IHDR、IDAT、IEND),容器只是把各档 PNG 首尾拼起来再加一段目录。ICONDIRENTRY 的 8 个字段全部按小端序写,偏移从 6 + 16 × 档数 开始逐档累加。

ICO 各尺寸档的目录字段与两种负载体积对照(默认 12 行)
边长宽高字段值32bpp 像素区AND 掩码BMP 数据区常见用途
8 px825632328极小的列表标记
10 px1040040480旧式工具条
12 px1257648664菜单项图标
14 px1478456880树形控件节点
16 px161024641128浏览器标签页与书签
20 px201600801720高分屏标签页
22 px221936882064部分 Linux 桌面
24 px242304962440macOS 标签页与 Spotlight
32 px3240961284264任务栏与地址栏
40 px4064003206760文件管理器小图标
48 px4892163849640Windows 收藏与大图标
64 px641638451216936桌面快捷方式

上表列的是未压缩 BMP/DIB 路径的固定开销,与图片内容无关,可直接当公式核对;本页实际走 PNG 路径,成品体积见上方结果区(内容相关,随图片而变)。

使用方法

  1. 点「选择 PNG 文件」挑一张本地 .png(上限 10 MB = 10485760 字节)。PNG转ICO转换器会先读前 8 字节核对 PNG 签名 89 50 4E 47 0D 0A 1A 0A,不是 PNG 会当场拒绝并报出读到的字节。
  2. 选「输出档位」。默认 6 档(16 / 32 / 48 / 64 / 128 / 256),档数决定 ICONDIRENTRY 的个数,也就决定头部是 102 还是 54、86 字节。
  3. 选「缩放方式」。照片与渐变选平滑插值,像素画与线条图标选邻近像素,后者不插值、边缘更硬,缩到 16 px 时不会糊成一团。
  4. 点「转换并生成 ICO」或按 Enter。结果区 8 格给出源图尺寸、输出档位、ICO 头部字节、ICO 总体积、同档 BMP 路径体积、PNG / BMP 体积比、256 档的宽高字段写法与当前缩放方式。
  5. 点「下载 .ico」拿走容器;下方对照表默认 12 行,「加载更多」补足到 20 行,「复制对照表」给出 TSV,「导出 CSV」带 BOM,Excel 打开不乱码。

这个PNG转ICO转换器能解决什么问题

一是把一个多尺寸图标文件一次做出来。浏览器标签页只吃 16 px,任务栏与高分屏吃 32 px,Windows 收藏与 Alt-Tab 会取 48 px 以上。用画图软件逐个尺寸导出、再手工把 PNG 拼进 ICO 容器,是一件机械且容易把偏移算错的事;PNG转ICO转换器把六个档位一次画完并按容器规范拼好。

二是保住透明通道不被白底淹没。ICO 容器允许每档带 alpha,本页内嵌的 PNG 就是 color type 6(RGBA),圆角与镂空区域不会被填成白色。很多在线转换器为了体积会退回 8bpp 调色板并把透明压成不透明,导入后图标四角会出现白块。

三是看清楚「体积到底花在哪」。ICO 的体积 = 目录开销(6 + 16 × 档数 字节)+ 各档图像数据区之和。目录开销小的可以忽略,真正决定体积的是图像数据区。本页把两种路径并列摆出来:同一组档位用未压缩 BMP 要 370070 字节,用 PNG 内嵌只有 1965 字节——差了两个数量级。

四是确认自己拿到的是不是真 ICO。把 PNG 直接改名成 .ico 是最常见的误操作,那样文件开头还是 PNG 签名,Windows 读不出目录结构。本页产出的文件头 6 字节固定是 00 00 01 00 加档数,可以自己用十六进制编辑器核对。

本页不做这些:不做从文字或 emoji 现场画图标(那是favicon生成器)、不做尺寸缩放(要改像素尺寸用图片尺寸调整)、不做格式互转(PNG 转 WebP / JPG 另有页面)、也不做 ICO 反解回 PNG。

原理与变量:ICO 容器结构与本页的字节级写法

第一步,认识容器的三段结构。ICO 文件从前往后是:① ICONDIR 固定 6 字节(idReserved 2 字节恒为 0、idType 2 字节恒为 1、idCount 2 字节是档数);② idCount 个 ICONDIRENTRY,每个固定 16 字节;③ 各档的图像数据区首尾相接。所以档数是 n 时头部恒为 6 + 16n 字节:3 档 54 字节、5 档 86 字节、6 档 102 字节。

第二步,16 字节目录项的字段布局。依次是 bWidth 1 字节、bHeight 1 字节、bColorCount 1 字节、bReserved 1 字节、wPlanes 2 字节、wBitCount 2 字节、dwBytesInRes 4 字节、dwImageOffset 4 字节。宽高只有 1 字节,256 写不进去,规范约定用 0 表示 256——这就是上表 256 px 那行「宽高字段值 = 0」的由来,它不代表宽度真的是 0。本页用 DataView.setUint8/setUint16/setUint32 写这些字段,第三个参数统一传 true 指定小端序;漏掉这个参数在多字节字段上会得到完全不同的字节。

第三步,偏移必须逐档累加。dwImageOffset 是数据区在文件中的绝对起始位置,不是相对上一档的增量。第一档的偏移恒等于头部长度 102(6 档时);第二档的偏移 = 102 + 第一档 PNG 的字节数;依此类推。实测本页示例的六档偏移是 102 / 188 / 302 / 436 / 604 / 963,末档偏移加其长度正好等于文件总长 1965 字节。这里是整个格式最容易写错的地方——偏移差 1 字节,Windows 就整个文件读不出图标。

第四步,PNG 载荷为什么能直接内嵌。早期 ICO 里每一档必须是 BMP/DIB:BITMAPINFOHEADER 40 字节,之后是 XOR 像素区(32bpp 时 宽 × 高 × 4 字节),再之后是 1 bit/像素的 AND 掩码(每行按 4 字节对齐)。按这个公式,16 px 一档是 40 + 1024 + 64 = 1128 字节,256 px 一档是 40 + 262144 + 8192 = 270376 字节——全部来自上表。而 BITMAPINFOHEADER 的 biHeight 要写成真实高度的 2 倍(XOR 图 + AND 掩码两段叠起来的高度),这也是经典坑点。从 Windows Vista 起,容器允许某档的数据区直接是一段完整的 PNG;本页就走这条路,示例 16 px 一档只有 86 字节,是 BMP 的 1/13。

第五步,本页的像素路径。每档新建一个边长为 N 的离屏 canvas,设 imageSmoothingEnabled(对应上面的缩放方式),再 drawImage(源, 0, 0, N, N),然后 canvas.toDataURL('image/png') 得到 data:image/png;base64,....,按 RFC 4648 把逗号后的 base64 解码成字节数组即可。基64 每 3 字节原始数据变 4 个字符,所以解码前后有 3/4 的换算关系,不能直接把字符数当成字节数。

第六步,尺寸不必是 2 的幂。bWidth / bHeight 是 1 字节,合法区间是 1 ~ 256 的任意整数;上表里的 20、24、40、72、96、100、144、180 都不是 2 的幂,Windows 照常加载。真正的硬边界只有两条:一是不超过 256(否则无法用 1 字节表示),二是单档数据区不宜超过 65535 字节(资源加载器的历史限制)。至于「≤256 色要量化到 1/4/8bpp」只影响 BMP 路径的调色板选择,本页走 PNG 路径不涉及——PNG 自己决定用调色板还是真彩。

完整示例(数值仅作演示)

示例条件与页面打开时的默认值完全一致:内置示例图 256 × 256、6 档(16 / 32 / 48 / 64 / 128 / 256)、平滑插值。示例图是页面用 canvas 程序生成的一张蓝底白方块,不涉及任何网络请求;换成你自己上传的 PNG 时,载荷体积会随内容变化。

  • ICO 头部 102 字节:6 + 16 × 6,六个目录项。换成 3 档是 54 字节,5 档是 86 字节。
  • 六档 PNG 载荷 86 / 114 / 134 / 168 / 359 / 1002 字节,合计 1863 字节;加 102 字节头部,ICO 总长 1965 字节(1.92 KB)。
  • 逐档偏移 102 / 188 / 302 / 436 / 604 / 963:每档偏移 = 上一档偏移 + 上一档长度,末档 963 + 1002 = 1965,与文件总长一致。
  • 256 档的宽高字段写 0,其余五档分别写 16 / 32 / 48 / 64 / 128;wBitCount 六档统一写 32。
  • 同档位改用未压缩 BMP 路径要 370070 字节(1128 + 4264 + 9640 + 16936 + 67624 + 270376,再加 102 字节头),是 PNG 路径的 188 倍;PNG 路径只占其 0.53%。

把结果区与上表对照看:16 px 那一行 BMP 数据区写的是 1128 字节,正好等于示例里 16 px PNG 载荷 86 字节的 13 倍多——同一张 16 × 16 的图,两种路线的体积差就是这么来的。

输入范围与对照表

每个字段的取值范围与越界动作如下,右列是填错时会看到的具体提示:

字段 / 条件允许范围默认值越界或填错时的动作
源文件体积≤ 10 MB(10485760 字节)示例图约 1 KB拒绝:提示实测体积与 10 MB 上限,要求先压缩
文件格式前 8 字节须为 89 50 4E 47 0D 0A 1A 0A内置 PNG 示例拒绝:报出实际读到的 8 字节,指出多半是改了后缀名
源图边长≤ 4096 px256 px拒绝:提示当前长边与 4096 上限
源图面积≤ 1600 万像素6.5 万像素拒绝:算出所需画布内存与上限,要求先缩尺寸
输出档位3 / 5 / 6 档,含 16 与 326 档非法值回退到 6 档并在提示里说明
缩放方式平滑插值 / 邻近像素平滑插值不涉及越界,切换后需重新点「转换」

下面两组是常见的尺寸与用途对照,尺寸栏里的非 2 的幂取值同样合法:

  • 16 × 16:浏览器标签页、书签栏,是唯一一个「必须存在」的档位。
  • 32 × 32:高分屏标签页、地址栏;也是 Windows 资源管理器列表视图的常见取值。
  • 48 × 48:Windows 任务栏、网站收藏缩略图,Alt-Tab 切换时也会取这一档。
  • 64 / 72 / 96 / 128:桌面快捷方式、大图标视图、PWA 启动画面,属于「越大越清晰」的档位。
  • 256 × 256:容器里的最大档,宽高字段必须写 0;也是体积增长最快的一档(BMP 路径要 270376 字节)。

边界与精度

  • 256 是尺寸的硬上限,不是「建议值」。因为 bWidth 只有 1 字节,能表达 0 ~ 255,规范拿 0 去代表 256。想在 ICO 里放 512 px 的图在格式上做不到,需要大图请另出 PNG 交给 manifest 引用。
  • 单档数据区建议不超过 65535 字节。dwBytesInRes 是 4 字节、理论上能到 4 GB,但 Windows 资源加载器对单个图标资源有 64 KB 量级的历史限制。示例六档最大也只有 1002 字节,余量充足;如果你上传的是高细节照片,256 档的 PNG 可能上千 KB,此时应减少档位或先把源图压小。
  • 缩放是有损的,插值方式会改变像素。平滑插值会把相邻像素做加权平均,边缘出现过渡色;邻近像素直接取最近的一个源像素,边缘硬但会丢细节。两者都不是「正确/错误」,而是取决于图像类型:照片用平滑、像素画与线条图标用邻近。
  • canvas 的 alpha 会被保留,但存在 1 级舍入。2D 上下文内部按预乘 alpha 存像素,drawImage 放大或缩小时半透明边缘会做一次乘除往返,可能产生 ±1 的通道误差。这是画布模型的固有行为,不是本页引入的损失。
  • 目录开销随档数线性增长,但相对很小。每增加一档多 16 字节:3 档 54、5 档 86、6 档 102 字节。相比动辄上千字节的载荷,档数带来的开销可以忽略,所以选档位应该按「需要哪种显示尺寸」来定,而不是为了省那几十字节。
  • 文件体积上限 10 MB、边长 4096 px、面积 1600 万像素是三条独立阈值,按先到者生效。面积上限的存在是因为缩放前要先解码成位图,1600 万像素的 RGBA 缓冲约 61 MB,再乘上档位画布可能把标签页推爆。所以超限时页面在分配画布之前就拒绝。
  • 偏移与长度必须自洽,这是唯一能让文件「生成成功却读不出来」的原因。PNG转ICO转换器在写完容器后会回读校验:idCount 是否等于档数、每档 dwImageOffset 指向的位置前 4 字节是否正好是 PNG 签名、末档偏移加长度是否等于总长。不一致就报错而不是静默给出坏文件。

常见问题

我把 PNG 直接改名成 .ico,为什么 Windows 显示不出来?

因为改后缀名不改变文件内容。ICO 是一个容器,文件头必须是 ICONDIR(前 2 字节 00 00、第 3~4 字节 01 00),后面跟每个档位 16 字节的目录项;而 PNG 文件头是 89 50 4E 47,两者完全不同。系统按容器结构去解析,读到 89 50 就认为这不是图标。正确做法是用本页把 PNG 装进容器:目录写 ICO 的头,图像数据区才是 PNG 字节。

ICO 里放 PNG 不是不合规吗,不是只能用 BMP?

那是 Windows XP 时代的限制。从 Vista 起,ICO 容器允许某一档的图像数据区直接是一段完整的 PNG,Windows 7/10/11 都正常识别,浏览器 favicon 也照样读。代价是极老的系统(XP 及更早)可能不认 PNG 档,遇到这种兼容需求可以改用 BMP 路径——但体积会涨一到两个数量级,本页示例 256 px 一档从 1002 字节变成 270376 字节,正好 270 倍。

图标尺寸必须是 2 的幂吗?我想用 24 或 96。

不必须,这是流传很广的误解,源头是早期纹理对 2 的幂的偏好。bWidth / bHeight 各只有 1 字节,合法范围是 1 ~ 256 的任意整数;上表里的 20、24、40、72、96、100、144、180 都被真实图标用过,Windows 照常加载。唯一需要注意的是 256——它写不进 1 字节,规范约定用 0 代替,所以「0」不是宽度为零而是二百五十六。

为什么 256 档的宽高字段在表里写的是 0?

因为字段只有 1 字节,能存的整数是 0 ~ 255,256 溢出。规范因此约定:宽或高等于 256 时,字段写 0。本页在写目录项时判断 边长 > 255 就写 0,回读校验里会专门核对这一条。如果你拿别的工具生成的 ico 发现某档显示为 0,那多半就是 256 档,不是文件损坏。

生成的 ico 双击打不开,提示损坏?

绝大多数情况是 dwImageOffset 算错。偏移是数据区在文件里的绝对位置,第一档从 6 + 16 × 档数 开始,之后每档加上前一档的长度;写成相对增量、或者忘了把头部长度算进去,整个文件就废了。第二个常见原因是 dwBytesInRes 写成了字符串长度而不是解码后的字节数——base64 有 4/3 的膨胀,必须取解码后的长度。本页写完容器后会回读每档偏移并核对 PNG 签名,就是为了避免这两种错。

透明背景会丢吗?

不会。本页内嵌的 PNG 由 canvas 导出,color type 一般是 6(RGBA),alpha 通道完整保留,圆角与镂空不会变成白块。要留意两点:一是预览区的棋盘格底是页面画上去的,用来显式区分透明与白色,不是图标自带的白底;二是如果源图本身就没有 alpha(例如从 JPG 转来的 PNG),那它转出来自然也不透明,这与本页无关。

平滑插值和邻近像素该怎么选?

看图像类型。照片、渐变、带柔和阴影的图标用平滑插值,缩小时不会出现锯齿;像素画、线框图标、笔画很细的标志用邻近像素,因为它不做加权平均,边缘不会糊成灰边。判断方法很简单:把图缩到 16 px 后如果边缘出现一圈脏灰,说明该换邻近像素;如果整体变成锯齿状楼梯,说明该换平滑插值。本页默认平滑插值,可在结果区看到当前用的是哪一种。

上传的图片会被传到你服务器上吗?

不会。整个流程是 File → createObjectURL → Image 解码 → 离屏 canvas drawImage → toDataURL → 手写容器字节,全部在浏览器内存里完成。模板里没有 fetch、没有 XMLHttpRequest,也不加载任何外部脚本;可以在开发者工具的 Network 面板核实,转换过程不会产生任何请求。断网也能正常用。

相关工具

PNG转ICO转换器只做 PNG → ICO 这一个方向,下面这些页面负责相邻的几件事。

  • favicon生成器——不用素材图,直接用文字或 emoji 现场画多尺寸站点图标并给出 head 代码。
  • 图片尺寸调整——源图边长超过 4096 px 时先压下来,再回来转 ICO。
  • 图片裁剪——图标要求方形,先裁成正方形再转,比例才不会被拉伸。
  • 按宽高比裁剪图片——按 1:1 比例裁切,比手工框选更准。
  • PNG转JPG——不需要透明、只想压体积时的另一条路。
  • PNG转WebP转换器——网站里用 WebP 替代 PNG 换更小体积。
  • 图片尺寸查询——只想知道源图宽高与体积时先查它,不必先转换。