关联及 AI 说明:本账号与图叮产品有关。本文正文与 Python 示例使用 GPT-6.1 辅助生成;文中的合成文件和检查结果于 2026-10-28 在本地实际运行,不包含客户文件,未做 Windows 真机兼容性验收。
把 logo.png 改名为 logo.ico,有些查看器仍然会显示预览,但这只能说明查看器识别到了图片,不能说明它已经变成 ICO 容器。反过来,ICO 目录里某个宽高字节是 0,也不能直接判定文件损坏:256px 有特殊的目录表示。
这两个现象经常混在一起。下面用一份只读 Python 脚本,把"文件名""目录结构"和"帧图像"分开检查。它不负责修图,也不把结构通过写成视觉或系统兼容性通过。
1. 先看容器,不看扩展名
ICO 文件由 6 字节头部、若干 16 字节目录项和图像数据组成。头部至少要检查三个小端整数:保留字段为 0,类型为 1,帧数大于 0。
目录项里记录宽、高、数据字节数和从文件起点计算的偏移。若有 4 帧,数据区不能从第 6 字节就开始,因为前面还需要容纳 6 + 4 × 16 = 70 字节的头部和目录。
所以,最先能抓到的错误不是"画面发糊",而是以下几类:
- PNG 内容只改了扩展名;
- 文件头或目录被截断;
- 帧数为 0;
- 图像偏移指向目录内部;
- 图像数据长度超出文件范围。
2. 为什么256px帧的目录宽高都是0
目录的宽和高各只有一个字节。对 16、32、48 这些尺寸,字段直接保存对应数值;256px 则用 0 表示。因此读目录时要还原为 width = stored_width or 256,高度同理。
这里说的是目录字段,不是任何位置出现 0 都合法。帧数、长度、偏移仍然必须成立。
还要分清两类帧:
- DIB 图像:本次 32bpp、40 字节 BITMAPINFOHEADER 样例的高度是图像与掩码合计的高度,256px 图像在该字段写 512;
- PNG 图像:从 PNG 签名开始,宽高读 IHDR,不套用 DIB 的高度除以二规则。
Microsoft 的图标格式资料给出了目录与图像的结构;其后续文章也说明了 PNG 图像并不是加一个 DIB 头后再存放。旧格式文档有历史语境,不能拿里面的 Windows 95/NT 行为当成今天所有系统的验收结论。
3. 一份只读检查脚本
将下面内容保存为 check_ico.py,用 Python 3 运行,不需要安装第三方依赖。它只读取传入文件,不修改文件,也不调用网络。
python
"""Read-only ICO directory/header check; not a full decoder or compatibility test."""
import json
import struct
import sys
from pathlib import Path
PNG_SIGNATURE = b"\x89PNG\r\n\x1a\n"
def inspect_ico(data):
if data.startswith(PNG_SIGNATURE):
raise ValueError("PNG data is not an ICO container; renaming does not convert it")
if len(data) < 6:
raise ValueError("truncated ICO header")
reserved, kind, count = struct.unpack_from("<HHH", data)
if reserved != 0 or kind != 1 or count == 0:
raise ValueError("invalid ICO header")
directory_end = 6 + count * 16
if directory_end > len(data):
raise ValueError("truncated ICO directory")
frames = []
for i in range(count):
w, h, colors, r, planes, bits, length, offset = struct.unpack_from(
"<BBBBHHII", data, 6 + 16 * i
)
if r != 0 or length == 0 or offset < directory_end or offset + length > len(data):
raise ValueError(f"frame {i}: invalid reserved/length/range")
width, height = w or 256, h or 256
blob = data[offset:offset + length]
if blob.startswith(PNG_SIGNATURE):
if len(blob) < 33 or blob[8:12] != b"\x00\x00\x00\x0d" or blob[12:16] != b"IHDR":
raise ValueError(f"frame {i}: truncated/invalid PNG IHDR")
payload_width, payload_height = struct.unpack_from(">II", blob, 16)
encoding = "PNG"
else:
if len(blob) < 40 or struct.unpack_from("<I", blob)[0] != 40:
raise ValueError(f"frame {i}: this demo only supports 40-byte DIB headers")
payload_width, dib_height = struct.unpack_from("<ii", blob, 4)
if dib_height <= 0 or dib_height % 2:
raise ValueError(f"frame {i}: unexpected DIB height")
payload_height = dib_height // 2
encoding = "DIB"
if (payload_width, payload_height) != (width, height):
raise ValueError(f"frame {i}: directory/payload dimensions disagree")
frames.append({"width": width, "height": height, "encoding": encoding,
"bytes": length, "offset": offset})
return {"status": "header_and_ranges_checked", "file_bytes": len(data), "frames": frames}
if __name__ == "__main__":
if len(sys.argv) != 2:
raise SystemExit("Usage: python3 check_ico.py FILE.ico")
try:
result = inspect_ico(Path(sys.argv[1]).read_bytes())
print(json.dumps(result, indent=2, ensure_ascii=False))
except (OSError, ValueError, struct.error) as exc:
print(str(exc), file=sys.stderr)
raise SystemExit(1)
运行方式:
bash
python3 check_ico.py your-icon.ico
正常返回的是 header_and_ranges_checked,刻意不命名为 valid_icon:这个脚本只检查目录、范围和尺寸头字段,不是完整 ICO 解码器。
4. 本地合成文件的实际结果
这次用一张程序生成的 RGBA 图样构造 16、32、48、256px 四个 DIB 帧,然后读取目录;输入是透明背景上的紫色方块,不是真实 Logo 或用户文件。ICO 容器由图像工具现有写入器生成,Python 检查脚本独立读取其字节。
| 帧尺寸 | 目录宽字节 | 目录高字节 | 帧数据字节数 | 起始偏移 |
|---|---|---|---|---|
| 16×16 | 16 | 16 | 1128 | 70 |
| 32×32 | 32 | 32 | 4264 | 1198 |
| 48×48 | 48 | 48 | 9640 | 5462 |
| 256×256 | 0 | 0 | 270376 | 15102 |
文件总计 285478 字节。最后一帧的 15102 + 270376 = 285478,恰好到文件末尾;这证明的是该样例的数据范围一致,不证明每个像素都能正确显示。
另做了一份单 PNG 帧 ICO,检查结果正确区分为 PNG,不使用 DIB 的双倍高度解释。
接着构造负例:把 PNG 仅改名为 ICO、截断最后一个字节、将帧数改成 0、把偏移改为 6、让目录宽度与图像头不一致。检查脚本均返回错误。连同正常 DIB、PNG 帧、256px 字段和短头部等情况,本地 11 项单元测试通过。
这些测试是格式字段测试,不是性能基准,也不是 Windows 全版本兼容性测试。它们不证明浏览器、安装器或系统 Shell 必然选中某一帧。
5. 脚本明确不检查什么
为了让示例保持可读,脚本只接受这里使用的 40 字节 DIB 头和 PNG IHDR,遇到其他合法格式也会拒绝。它不检查 PNG CRC、不完整解压像素、不验证全部 DIB 像素和掩码字节、也不判断多帧之间是否重叠或存在恶意构造。
因此不要把它用于不可信文件的安全审计,更不能因为返回成功就跳过完整图像库的解码检查。处理外部文件还应限制输入大小,并在适当的隔离环境中运行完整解码器。
结构检查以后,还要在目标软件中检查三个问题:
- 16px、32px 等实际显示尺寸下,轮廓和细线是否仍清楚;
- 深浅背景上是否有白边、黑边或异常透明区域;
- 应用实际加载的是新文件,还是仍在使用旧路径或旧缓存。
把"文件名正确""容器字段正确""能解码""目标环境显示正确"分成四层,会比看到一个缩略图就宣布完成更可靠。
参考资料
- Microsoft Learn,历史格式资料 Icons:ICO 头部、目录项、DIB 图像及掩码结构。本文只引用格式部分,不沿用其中过时的系统选择行为。
- Microsoft The Old New Thing,The evolution of the ICO file format, part 4: PNG images:PNG 帧从 PNG 签名开始,不添加 BITMAPINFOHEADER 和单色掩码。
- ICO file format:目录尺寸字段中 0 代表 256 的说明。
以上来源与本地实验核对于 2026-10-28。正文不含产品注册链接、付费链接、二维码或私信引流。