Skip to main content

Crate resopt

Crate resopt 

Source
Expand description

Resource discovery and recoverable, pixel-verified optimization plans.

The first backend recompresses static catalog PNGs without changing their filenames, decoded samples, or non-IDAT chunks. Plans measure source bytes, not compiled catalog or App Store download sizes.

§resopt

面向 Apple 项目的资源检测与优化工具,提供 Rust 库和命令行接口。

scan 清点资源目录内外的资源,识别图片的实际编码;analyze 真正解码图片,检查透明像素, 试算不同格式与质量的候选,并生成带预览的报告。现有 plan / apply / restore 提供严格无损 PNG 的可恢复修改流程。 Numi 独立负责资源代码生成,resopt 不依赖它。

§GitHub 二进制与自动发布

推送与 Cargo.toml 版本一致的 v<版本> tag,会先执行三平台 CI,再编译并发布:

  • macOS Apple Silicon:aarch64-apple-darwin(macOS 13+)。
  • macOS Intel:x86_64-apple-darwin(macOS 13+)。
  • Linux x86_64:x86_64-unknown-linux-gnu(Ubuntu 22.04 构建)。
  • Windows x86_64:x86_64-pc-windows-msvc

GitHub Releases 提供压缩包和 SHA256SUMS。 解压后将 resopt(Windows 为 resopt.exe)放到 PATH 即可;下载二进制无需安装 Rust。 JPEG/HEIC 编码仍仅支持 macOS;其它平台可清点资源并执行严格无损 PNG 流程。

发布前更新并提交 Cargo.tomlCargo.lock 中的版本,确认 CI 通过,再创建并推送 tag:

# 示例:仅在 Cargo.toml 已更新为 0.3.0 时使用这个版本号
 git tag -a v0.3.0 -m 'resopt 0.3.0'
 git push origin v0.3.0

版本不匹配或任意测试/构建失败时不会发布。v0.3.0-rc.1 这类 tag 会标记为预发布。 工作流使用 GitHub 自动提供的 GITHUB_TOKEN;无需额外发布密钥,不会自动发布 crates.io。 重跑不会覆盖已存在的 GitHub Release;上传失败若留下草稿,应先检查草稿再重试。

§安装

crates.io 包名为 resopt-cli,安装后的命令仍是 resopt

cargo install resopt-cli --locked
resopt doctor

从源码安装时,在仓库根目录执行:

cargo install --path . --locked
resopt doctor

构建需要 Rust 1.88+ 和 C 编译器(用于 libdeflate)。严格无损 PNG 后端内嵌 Oxipng。 JPEG/HEIC 分析目前使用 macOS ImageIO 与 CoreGraphics,不需要额外安装 sips、FFmpeg 或 Swift 工具链。 其他平台可以清点资源,并使用原有的 PNG 计划与应用流程。

部分受限沙箱会阻止系统 HEIC 编码器。正式分析前会实际试编码 JPEG 和 HEIC;不可用时会明确报错, 不会把“没有成功编码”伪装成“没有优化空间”。--probe-only 可用于不编码的检测。

§全资源扫描

resopt scan /path/to/project
resopt scan /path/to/project --json > inventory.json

扫描范围包括:

  • .xcassets 引用的资源、目录元数据、未引用文件和暂不支持的资源节点中的文件。
  • 目录外的图片、音视频、SVGA/VAP 等动效、字体、压缩包、数据文件、本地化文件和未分类文件。
  • Pods、Carthage、node_modules 中的资源文件。

图片依据文件头识别 PNG、JPEG、HEIC/HEIF、WebP、GIF、TIFF、BMP、AVIF 等格式, 并报告扩展名与编码不一致的情况。例如,扩展名为 .png 的 WebP 会进入 WebP 分析路径。 无法从文件头识别的格式会先按扩展名分类,后续解码失败会单独报告。

文件清点不等于构建目标分析。 清单可能包含未被 App 打包的文件,压缩包内部也不会自动展开。 已知源码、构建配置和工具文件会排除;VCS、构建缓存、嵌套 worktree、工具配置目录不会遍历。 报告列出实际排除的目录以及源码/工具文件的排除数量。符号链接不跟随,不可读文件会产生诊断。

如需兼容旧版的“仅列出资源目录引用文件”结果:

resopt scan /path/to/project --catalog-only --json

§图片分析:JPEG 与 HEIC

# 默认试算所有尺寸的图片,不再设置 50 KiB 门槛。
resopt analyze /path/to/project --out /tmp/resopt-analysis

# 显式指定质量档位与并发数。
resopt analyze /path/to/project --out /tmp/resopt-analysis-2 \
  --qualities 75,85,95 --jobs 2

# 只解码检查格式、尺寸、帧数和透明像素,不进行编码。
resopt analyze /path/to/project --out /tmp/resopt-probe --probe-only

# 要求候选 Alpha 与原图完全一致。
resopt analyze /path/to/project --out /tmp/resopt-exact-alpha --max-alpha-error 0

输出目录必须尚不存在,父目录必须存在,且必须位于待扫描项目之外。 默认 min_input_bytes = 0,因此小图片也会尝试;如有需要,可显式传入 --min-input-bytes

§格式选择

实际像素状态比较的格式
没有透明像素,包括“带 Alpha 通道但 Alpha 全满”JPEG、HEIC;原格式为 PNG 时另比较严格无损 PNG
有透明或半透明像素HEIC;原格式为 PNG 时另比较严格无损 PNG;不生成 JPEG 候选
已经是 HEIC同样解码检查透明度,并按上述规则重新试算

默认 JPEG/HEIC 质量档位为 75、85、95。数值是编码器质量参数,既不是体积节省比例, 也不是可以跨格式直接比较的视觉质量分数。JPEG/HEIC 候选明确标为有损。

每个候选都会重新解码,验证尺寸、方向、帧数和透明状态,并计算:

  • 在统一 sRGB 预乘 Alpha 像素上的 RGB 平均绝对误差(MAE,按 0–255 标度显示)。
  • 同一像素空间上的 PSNR;像素相同时显示无穷大。
  • 单个像素最大的 Alpha 误差(0–1 标度)。

这些指标帮助筛选,不替代视觉审阅。完全透明像素的隐藏 RGB 不影响此处的有损画质比较。 HEIC 有损编码可能让 Alpha 相差一个 8 位量化级;默认上限为 1/255 + 0.000001, 允许这一级量化及浮点计算误差,同时拒绝整体透明状态的变化。 --max-alpha-error 0 要求 Alpha 精确一致;该设置不会改变原有严格无损 PNG 的像素保留约束。

§报告与候选文件

输出目录包含:

  • analysis.json:全部资源、检测状态、问题、每个格式/质量的实际体积和误差。
  • report.html:可离线查看的中文报告,含原图与候选缩略图;点击可打开原尺寸文件。 报告支持搜索、原格式筛选、排序和分页,采用 KiB/MiB 自动单位,并可切换方案与透明背景。
  • originals/candidates/previews/:有体积收益的有效候选及对应原图和预览。

较大、超出 Alpha 上限或编码失败的候选仍在 JSON/HTML 中记录原因,但不会被推荐为可采用结果。 smallest_candidate 只表示通过结构与 Alpha 检查后体积最小的候选,不表示画质已经验收。 总节省量按每个文件仅取一个最小候选计算;有损候选可能采用不同质量档位。

分析不会修改项目。 报告不是 apply 可执行的计划。 传统 plan/apply/restore 命令仍面向严格无损 PNG。分析报告的逐张应用与跨格式替换通过下述 serve 流程执行。

§刷新已有报告界面

resopt report /tmp/resopt-analysis

读取同目录的 analysis.json 并更新 report.html,不重新编码,不改动测量数据或候选文件。 HTML 内嵌交互所需的数据与脚本,无需网络或额外前端构建步骤。

§在报告页面优化图片

resopt serve /tmp/resopt-analysis
# 可选固定端口,默认自动分配空闲端口
resopt serve /tmp/resopt-analysis --port 8417

打开终端输出的 http://127.0.0.1:<端口>/ 地址,选择图片与候选,点击「优化这张图片」, 核对格式、质量和节省体积后确认。JPEG/HEIC 需要明确确认有损优化;页面也提供「恢复原图」。 直接打开静态 HTML 仍只供审阅,写入由本地 Rust 服务完成。按 Ctrl-C 停止服务。

  • 支持无损 PNG、JPEG/HEIC 候选。Asset Catalog 跨格式替换会更新所有匹配的 Contents.json rendition 文件名,保留其它字段;恢复时还原原始 JSON 字节。
  • 散落图片仅支持同格式替换;需要改扩展名的候选暂不应用,因为代码、工程文件或运行时可能引用原文件名。
  • AppIcon、拉伸图片和多帧图片保留现有限制。候选会再次解码或严格验证 PNG,检查源文件哈希、尺寸、方向和 Alpha。
  • 服务启动时固定候选哈希,拒绝运行期间被替换的候选;服务重新启动后会重新验证所选候选。
  • 原始字节、候选和操作记录保存在报告目录的 operations/ 中,需要恢复时请保留整个报告目录。 操作中断后可重启服务恢复;对同一个 Contents.json 的多次转换须按后做先恢复的顺序操作。 恢复拒绝覆盖后续人工修改。每个文件原子替换,跨文件操作通过持久记录恢复,并非单个原子事务。
  • 服务只监听 127.0.0.1,写入接口检查 Host、Origin 和随机会话令牌,不接受客户端传入的文件路径。
  • 概览体积与图片对比保留分析时的快照;已应用状态单独展示,需要新的整体统计时重新运行 analyze

页面支持「跟随系统/浅色/深色」主题并记住选择,图片的棋盘/白色/深色预览背景独立设置。 页面结构由 Maud 在 Rust 中生成,CSS 和浏览器交互脚本编译进 CLI;不需要 WebAssembly 或前端构建工具。

§分析边界

  • 每张图片输入上限为 64 MiB,统一浮点解码缓冲上限为 64 MiB;超限会明确报告。
  • 多帧图片记录帧数和首帧检测信息,但不会转成单帧 JPEG/HEIC。
  • AppIcon、已识别的 resizing 资源会解码检查,但不会生成格式转换候选。
  • 音视频、动效、字体、压缩包、矢量图、数据文件等纳入清单,状态为 inventory_only, 原因注明对应优化后端尚未实现;不会重新编码这些类型或修改容器内部内容。
  • JPEG/HEIC 检查不承诺原始元数据的字节级保留;严格无损 PNG 则保留非 IDAT 块及其顺序。
  • 有错误、跳过项或不支持的类型时,报告会保留它们。命令成功不代表每个文件都已优化。

§严格无损 PNG:计划、应用和恢复

resopt plan /path/to/project --out /tmp/resopt-review
resopt apply /tmp/resopt-review
resopt restore /tmp/resopt-review

这个既有流程仍只处理资源目录引用的静态 PNG,文件名和资源名不变。 规划时优化发生在内存中,只写入新的计划目录;每个候选经过独立 PNG 解码, 逐字节比较像素数据,并检查全部非 IDAT 块及其相对于首个 IDAT 的顺序。 完全透明像素的隐藏 RGB 也保留,位深、颜色类型、调色板和隔行扫描设置不变。

默认策略与 resopt.example.toml 一致:

png_level = 2
min_input_bytes = 51200
min_savings_bytes = 1024
min_savings_percent = 1.0
resopt plan /path/to/project --policy resopt.example.toml --out /tmp/resopt-review-2

png_level 是优化力度而非视觉质量;文件必须变小并同时达到两个收益门槛。 只有传入 --policy 才加载配置;未知字段会报错。这个策略与 analyze --qualities 的有损试算相互独立。

应用前会检查整个批次的源文件、资源目录和候选哈希,逐文件写入前还会再次核对。 每个替换使用同目录临时文件与原子重命名,但整个批次不是单个原子事务。 原件预先保存,journal.jsonl 记录操作;保留整个计划目录即可在部分完成后恢复。 重复应用或恢复不会重复修改已符合目标的文件,后续用户编辑和过期目录元数据会阻止覆盖。 普通文件权限会保留,时间戳、扩展属性和硬链接关系不会保留。

操作期间不要并发编辑同一资源树。.lock 与项目根目录的 .resopt.lock 用于避免并发写入; 异常退出后,确认进程不再运行再清理遗留锁。移动项目后需要重新生成计划。

§与 Numi 配合

在目标项目根目录按需运行:

resopt apply /tmp/resopt-review && numi generate --workspace

当前无损 PNG 后端保留资源名,因此生成的访问代码通常不变;不会隐式执行 Numi。

§Rust API

通过 cargo add resopt-cli 添加依赖,Rust 库名仍为 resopt

use resopt::{AnalysisOptions, Policy, analyze, create_plan, inventory};

let resources = inventory("/path/to/project")?;
let analysis = analyze("/path/to/project", "/tmp/resopt-analysis", AnalysisOptions::default())?;
let lossless_plan = create_plan("/path/to/project", "/tmp/resopt-review", Policy::default())?;

原有 scan() Rust API 仍返回旧版资源目录引用清单;全资源入口为 inventory()。 命令均支持 --json,标准输出用于报告,标准错误用于进度与错误。 成功返回 0,操作失败返回 1,用法错误返回 2;消费报告时请检查状态与诊断字段。

§验证与体积口径

node --test tests/report-ui.cjs
cargo fmt --all --check
cargo clippy --all-targets -- -D warnings
cargo test --all-targets
cargo test --doc

报告脚本的语法、单位格式与资源 URL 检查使用 Node.js 内置测试工具(仅开发验证需要 Node.js)。 macOS 图片编码测试需要能够访问系统 HEIC 编码器;不应在阻止编码服务的沙箱中运行。 原有跨平台 PNG 测试继续保留;新增测试覆盖实际透明像素、透明 HEIC、已有 HEIC、错误扩展名、 资源目录外文件、小图片、损坏图片、候选路由及报告生成。

所有收益均为源文件字节数。 不证明 Assets.car、IPA、App Store 下载体积、解码速度或内存用量会改善。 资源是否进入目标 App,以及编译后的体积差异,需要单独验证。

§许可证

MIT。各依赖保留其许可证。未引入 Imagequant 或 GPL 依赖。

Structs§

AnalysisOptions
AnalysisReport
ApplyReport
Asset
Candidate
ImageCandidate
ImageDifference
ImageInfo
Inventory
Plan
Policy
Lossless PNG policy. Unknown fields are rejected (including lossy settings).
Resource
ResourceAnalysis
ResourceInventory

Functions§

analyze
Read-only analysis of all inventoried resources, with lossy candidates staged solely for review. This report is deliberately not an executable apply plan.
analyze_with_progress
apply
Apply a reviewed plan; no encoding or implicit approval occurs here.
create_plan
Creates a new, self-contained plan directory. Sources are never modified. A partial directory may remain if IO fails; it cannot apply without plan.json.
image_backend_available
inventory
Inventory all files other than recognized source/tooling files and build/VCS directories. Unknown files stay visible. This is not build-target resolution.
read_plan
refresh_report
Refresh presentation only. Does not encode, change JSON, or touch artifacts.
restore
Restore only files that still match this plan’s original or candidate hashes.
scan
Discover catalogs recursively without following symlinks or build caches. This inventories disk resources; it does not prove target membership.
serve
Serve an existing analysis on loopback. Port 0 chooses an available port. The printed URL is the entry point; terminate the process to stop serving.