使用方法
- 点「选择 PNG 文件」挑一张本地
.png(上限 10 MB = 10485760 字节)。PNG转ICO转换器会先读前 8 字节核对 PNG 签名89 50 4E 47 0D 0A 1A 0A,不是 PNG 会当场拒绝并报出读到的字节。 - 选「输出档位」。默认 6 档(16 / 32 / 48 / 64 / 128 / 256),档数决定
ICONDIRENTRY的个数,也就决定头部是 102 还是 54、86 字节。 - 选「缩放方式」。照片与渐变选平滑插值,像素画与线条图标选邻近像素,后者不插值、边缘更硬,缩到 16 px 时不会糊成一团。
- 点「转换并生成 ICO」或按 Enter。结果区 8 格给出源图尺寸、输出档位、ICO 头部字节、ICO 总体积、同档 BMP 路径体积、PNG / BMP 体积比、256 档的宽高字段写法与当前缩放方式。
- 点「下载 .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 px | 256 px | 拒绝:提示当前长边与 4096 上限 |
| 源图面积 | ≤ 1600 万像素 | 6.5 万像素 | 拒绝:算出所需画布内存与上限,要求先缩尺寸 |
| 输出档位 | 3 / 5 / 6 档,含 16 与 32 | 6 档 | 非法值回退到 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 换更小体积。
- 图片尺寸查询——只想知道源图宽高与体积时先查它,不必先转换。