在 Mac 上压缩 ICNS 文件——拆解 ICNS 容器内的 PNG 切片结构,了解为什么 iconutil 不够用,以及 Zipic 如何用自研 pngoptim 引擎逐片优化应用图标。
每个 macOS 应用的 .app 包里都藏着一个 .icns 文件。Finder 在 Dock、Spotlight、文件选择器里画出的那个图标,就是从这个文件读的。大多数开发者在 Xcode 生成完就再也不碰它——问题就出在这里。
第三方应用的 .icns 文件通常在 500 KB 到 1 MB 以上,因为 iconutil 只是把源 PNG 原封不动地塞进容器,不做任何压缩。Apple 自家系统应用用 JPEG 2000 编码内部切片,所以能控制在 65 KB 以内;但第三方工具链默认输出 PNG,压缩率为零。其中 1024×1024 的 Retina 切片一片就能占掉全部体积的 60–85%。这份臃肿会渗透到 .app 包、DMG 安装盘,以及——如果你用 Sparkle——每次发给用户的增量更新补丁里。
这篇文章把 ICNS 格式拆开看,解释为什么 iconutil 和 Image2Icon 都无法缩减体积,再走一遍 Zipic 如何用自研的 pngoptim Rust 引擎对 ICNS 做逐 PNG 切片压缩。本文是 pngoptim 引擎系列的第二篇——第一篇讲了 APNG 压缩,pngoptim 深入解析 讲引擎内部架构。
ICNS 不是一张图——它是一个 容器。可以把它理解成一个扁平归档:每个条目存储一个特定像素尺寸的图标。文件以 8 字节头部开头:4 字节魔术字 icns 加上 4 字节大端序的文件总长。此后每个元素由 4 字节 OSType 编码 + 4 字节长度 + 原始图像数据组成。
一个由 iconutil 构建的现代 .icns 最多包含 10 个 PNG 切片:
| 槽位 | 像素尺寸 | OSType | 用途 |
|---|---|---|---|
| 16×16 | 16×16 | icp4 | Finder 列表视图、菜单栏 |
| 16×16@2x | 32×32 | ic11 | Retina 列表视图 |
| 32×32 | 32×32 | icp5 | Finder 分栏视图 |
| 32×32@2x | 64×64 | ic12 | Retina 分栏视图 |
| 128×128 | 128×128 | ic07 | Finder 图标视图 |
| 128×128@2x | 256×256 | ic13 | Retina 图标视图 |
| 256×256 | 256×256 | ic08 | 大图标视图 |
| 256×256@2x | 512×512 | ic14 | Retina 大图标视图 |
| 512×512 | 512×512 | ic09 | 全尺寸预览 |
| 512×512@2x | 1024×1024 | ic10 | Retina 全尺寸 / App Store |
注意 ic10:它实际存储 1024×1024 像素,但代表 512×512 点(@2x 密度)。这就是为什么 .iconset 文件夹里会出现 [email protected] 这个名字——文件本身其实是 1024 像素的正方形。
Retina 之前的旧 ICNS 还会携带历史遗留 chunk——1-bit 掩码(ICN#、ics#)、4/8-bit 索引色(icl4、ics8)、PackBits RLE 压缩的 24-bit RGB(il32 + l8mk)。现代 macOS 渲染时不读这些数据,它们只为兼容 Mac OS 9 和早期 OS X 而保留。如果源 .icns 仍然带着这些历史包袱,Zipic 在重新打包时会清除掉。
这个格式没有 Apple 发布的独立规范。最完整的公开参考是 Wikipedia 的 Apple Icon Image format 条目;Apple 在其 High Resolution Guidelines 和 iconutil man page 中记录了 .iconset 工作流。
ICNS 遍布 macOS:
MyApp.app/Contents/Resources/AppIcon.icns,由 Info.plist 的 CFBundleIconFile 键指向。这是最核心的用途。.VolumeIcon.icns。如果 DMG 图标是 900 KB 而应用本身才 15 MB,安装包仅因一个装饰性文件就多了 6% 的体积。CFBundleDocumentTypes 注册自定义文档图标。一套丰富的文档图标可以额外增加数百 KB。/System/Library/CoreServices/CoreTypes.bundle/ 里存储了数百个 ICNS,对应文件夹类型、磁盘类型和文件关联。体积直接影响三件事:
bsdiff 逐文件生成二进制差分。PNG 数据结构噪声大——像素微调就会产生大量差异。臃肿的 ICNS 一旦在版本间变动,就可能主导整个补丁包的大小。Sparkle 官方文档明确提醒:“Do not make unnecessary modifications to files.”Apple 的 iconutil 做两件事:把 .iconset 文件夹转成 .icns(iconutil -c icns)和把 .icns 拆回 .iconset(iconutil -c iconset)。功能到此为止。
iconutil 做不到 的事:
iconutil 会原封不动地打包。这就是为什么第三方应用的 iconutil 生成的 ICNS 比 Apple 系统图标大得多:Apple 系统图标内部用 JPEG 2000 编码,而 iconutil 只输出 PNG。
Image2Icon(img2icnsapp.com)填的是另一个空缺——它能从一张源图自动生成全部 10 个尺寸变体,支持模板和多种导出格式。但它关心的是 生成,不是 压缩。它不会对生成的 PNG 切片做有损量化。
空缺很明显:整条流水线上没有任何环节会在 PNG 打包进 ICNS 之前做优化。Zipic 补的就是这个缺。
Zipic 把 ICNS 当作三阶段流水线来处理:
iconutil -c iconset 把源 .icns 的每个 PNG 切片提取到临时 .iconset 文件夹。.iconset 调用 iconutil -c icns,重新组装 ICNS 容器。结果:每个切片都被单独优化,容器结构合法,macOS 渲染出来的图标和以前一模一样——只是文件更小了。
ICNS 压缩属于 Zipic Pro 功能。点击主窗口左下角的 压缩设置:
| 设置 | 推荐值 | 原因 |
|---|---|---|
| 压缩等级 | 2–3 | 图标多为扁平配色加硬边缘,2–3 级在保留细节的同时能减掉 40–80% 的 PNG 重量 |
| 保存格式 | 保持原格式 | ICNS 内部必须是 PNG,没有”把 ICNS 转成 WebP”这回事 |
| 尺寸调整 | 关闭 | 绝对不要改变图标尺寸——.iconset 约定要求精确到像素 |
| 保存位置 | 覆盖原文件或输出到同级文件夹 | 自动化构建用覆盖;手动 A/B 对比用同级文件夹 |
把 .icns 文件(或者直接把应用包的 Contents/Resources/ 文件夹)拖进主窗口。Zipic 检测到 ICNS 格式后自动拆包、用 pngoptim 逐切片压缩、再重新打包。点击缩略图打开对比预览,确认每个尺寸下图标依然锐利。
Electron 应用是重灾区。Claude 桌面版的 electron.icns 有 1,084 KB,包含 6 个未优化的 PNG 切片。其中最大的 [email protected](1024×1024 像素)单片就有 735 KB。
经过 Zipic 处理后:
| 切片 | 压缩前 | 压缩后 | 缩减 |
|---|---|---|---|
| 512×512@2x(1024 px) | 735 KB | ~102 KB | 86% |
| 512×512 | 246 KB | ~34 KB | 86% |
| 256×256 | 77 KB | ~12 KB | 84% |
| 128×128 | 22 KB | ~4 KB | 80% |
| 32×32 | 2.2 KB | ~0.8 KB | 62% |
| 16×16 | 0.8 KB | ~0.6 KB | 32% |
PNG 切片合计从 1,083 KB 降到约 155 KB——原始图像数据缩减 85%。重新打包后的 ICNS 大约 150–200 KB,具体取决于 iconutil 在拆包/重新打包过程中重建了多少遗留 chunk。即使是 50–100 KB 范围内已经比较合理的原生 macOS 应用图标,通常也能再省 20–40%,因为设计师从 Figma 等工具导出 PNG 时不会跑量化器。
在 Copy Resources 步骤之后加一个 Run Script 构建阶段:
ICNS_PATH="${BUILT_PRODUCTS_DIR}/${CONTENTS_FOLDER_PATH}/Resources/AppIcon.icns"
if [ -f "$ICNS_PATH" ]; then
open "zipic://compress?url=${ICNS_PATH}&format=original&level=3&location=original"
fi
这通过 Zipic 的 URL Scheme 触发就地压缩。Zipic 异步运行,不阻塞构建。
如果你的 CI 把图标导出到共享文件夹,可以用 Zipic 的 文件夹监控 盯住那个目录。每个新 .icns 文件都会自动触发压缩,使用你保存的预设,无需手动干预。
如果你通过 Sparkle 分发更新,在签名更新包之前压缩 ICNS 意味着:
.zip 或 .dmg)bsdiff 的差分更紧凑| 格式 | 平台 | 尺寸范围 | 压缩方式 | Retina 支持 | 工具 |
|---|---|---|---|---|---|
| ICNS | macOS | 16–1024 px,含 @2x | 每切片 PNG 或 JPEG 2000 | 支持 | iconutil、Zipic |
| ICO | Windows | 16–256 px | 每槽 BMP 或 PNG | 有限 | 各类编辑器 |
| Asset Catalog(.car) | iOS / macOS(现代) | 按槽位 PNG/PDF | actool 重打包 | 支持 | Xcode |
| Adaptive Icon | Android | 108×108 dp | 前景/背景分层 | 通过密度分桶 | Android Studio |
| Favicon | Web | 16–512 px | ICO、PNG 或 SVG | 通过 manifest | 任意图片工具 |
ICNS 是唯一一个你能打开容器、逐切片独立优化、再重新打包的格式——Zipic 正是利用了这个特性。
Zipic 会改变 ICNS 的容器结构吗?
不会。Zipic 用 Apple 自己的 iconutil 做拆包和重新打包,所以输出的 .icns 在结构上和 Xcode 生成的完全一致——只是内部的 PNG 更小。
压缩后图标在 Dock 或 Finder 里看起来会不一样吗? 压缩等级 2–3 时,在图标显示尺寸下差异不可见。有损 PNG 压缩引入的量化痕迹在 128×128 及以下分辨率是亚像素级的。用 Zipic 的对比预览确认后再提交即可。
能压缩不是自己构建的应用的 .icns 吗?
可以。把任意 .icns 拖进 Zipic 就行。这对精简 DMG 卷标图标或自定义文件夹图标很实用。但要注意:修改已签名应用包里的图标会让代码签名失效——只对你自己控制的包做压缩,然后重新签名。
为什么 ICNS 压缩是 Pro 功能? ICNS 压缩依赖 pngoptim 引擎,它与 zipic-jpeg、gifoptim、svgo-swift、pdfoptim 一样,属于 Pro 订阅资助的格式专属引擎。免费版覆盖常规图片压缩面。
Zipic 能处理遗留 ICNS chunk(RLE、JPEG 2000)吗? Zipic 只对现代 ICNS 文件内的 PNG 切片做优化。遗留 chunk(PackBits RLE、1-bit 掩码、JPEG 2000)在拆包/重新打包过程中会被保留但不单独优化——现代 macOS 渲染时也不使用这些数据。
和手动对 iconset 跑 pngquant 比呢?
思路一样,步骤更少。你 可以 自己跑 iconutil -c iconset → pngquant *.png → iconutil -c icns。Zipic 用 pngoptim(速度更快、体积更小)把这条流水线自动化成一次拖放操作,还帮你处理了临时目录清理和 .iconset 文件命名约定。
应用图标是用户最先看到的东西,却是开发者最后才想起优化的对象。下载 Zipic,设一个等级 3 的预设,把 .icns 拖进去。pngoptim 引擎负责逐切片压缩,iconutil 负责重新打包。ICNS 压缩是 Pro 功能——下载即享 7 天完整 Pro 体验。查看 定价。

Zipic 用 Google 的 libwebp 处理 WebP,但在发现 libavif 无法正确保留 iPhone HDR 照片信息后,自研了 avifoptim。两个截然不同的工程决策背后的逻辑。

gifski 是 Mac 上把视频转成 GIF 的好手,但它压不了已有的 GIF、不会批量处理、也不监控目录。本文讲的是 gifski 之外,Mac 上 GIF 工作流该靠谁。

Zipic 免费版和 Pro 到底差在哪?逐项对比每天 25 次压缩上限、AVIF/PDF/SVG 高级格式、文件夹监控、预设管理和三档价格,帮你判断免费版够不够用、什么时候该升级 Pro。