PNG改成ICO为什么不能用:用Python检查多尺寸目录与256px帧

关联及 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 像素和掩码字节、也不判断多帧之间是否重叠或存在恶意构造。

因此不要把它用于不可信文件的安全审计,更不能因为返回成功就跳过完整图像库的解码检查。处理外部文件还应限制输入大小,并在适当的隔离环境中运行完整解码器。

结构检查以后,还要在目标软件中检查三个问题:

  1. 16px、32px 等实际显示尺寸下,轮廓和细线是否仍清楚;
  2. 深浅背景上是否有白边、黑边或异常透明区域;
  3. 应用实际加载的是新文件,还是仍在使用旧路径或旧缓存。

把"文件名正确""容器字段正确""能解码""目标环境显示正确"分成四层,会比看到一个缩略图就宣布完成更可靠。

参考资料

  • 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。正文不含产品注册链接、付费链接、二维码或私信引流。

相关推荐
长空任鸟飞_阿康1 小时前
如何设计支持 Skills 的 Agent?一套可扩展的四层架构
python·ai·架构·skills
滴啦嘟啦哒1 小时前
【具身笔记】【原理探究】一、对PI05源码的实践笔记
笔记·python·vla·具身
青峰讲运维1 小时前
OpenStack 存储深度解析|Cinder 块存储 + Swift 对象存储原理与完整实操(Yoga)
开发语言·openstack·swift
miofly1 小时前
GitHub 今日推荐|elephant-goldfish-model:先审查设计草案再写代码
python·开源·github
汤姆宝库1 小时前
具身智能深拆:VLA、世界模型与数据飞轮
开发语言·人工智能·php
云和数据.ChenGuang1 小时前
深入剖析 Python 的 MRO 规则:从 C3 线性化到实战踩坑
开发语言·python·1024程序员节
123_不打狼2 小时前
Squidiff:基于条件扩散模型预测细胞发育与扰动响应
开发语言·python·squidiff
databook2 小时前
用Python DSL 写轻量数据质检工具
python·数据分析
郝学胜-神的一滴2 小时前
AI 编程智能体 17:四步搞定专属AI智能体
大数据·人工智能·python·神经网络·算法·langchain·强化学习