图片压缩到指定体积(格式与内容不变)
这个技能解决什么
把体积超标的 jpg / jpeg / png 压到目标体积(默认 1MB)以下,用于投稿、报名、上传附件的场景。
四条硬约束,任何时候都不违背:
- 格式不变 — jpg 出 jpg、jpeg 出 jpeg、png 出 png,不做格式转码。
- 画面内容不变 — 不裁切、不加水印、不加边框、不做任何内容编辑。
- 原图只读 — 结果写到独立目录,源文件一个字节都不动,更没有删除。
- 优先保画质 — 只要 JPEG 质量还能维持在 82 以上就达标,就绝不把质量压到 82 以下;宁可等比缩小分辨率来换体积。
> jpg/jpeg 本身就是有损格式,「压到 1MB 以下」与「像素级无损」对照片类图片不可能同时成立。本技能的定位是视觉无损:构图、主体、清晰度观感保持一致。若用户坚持像素级无损,如实告知只能做 PNG 无损优化,多半达不到 1MB。
环境要求
必须用本机受管 Python 运行(已装 Pillow 12.3.0):
C:\Users\ZhuanZ(无密码)\.workbuddy\binaries\python\envs\default\Scripts\python.exe
不要用裸 python(PATH 里的解释器可能没有 Pillow),不要执行 pip install。本机 Git Bash 缺 coreutils,所有文件操作改用 Python,不要写依赖 ls / head / tail 的脚本。
使用流程
- 确认口径(仅在信息缺失时问,用户已说明则直接执行):
- 目标体积 — 未说明则取 1MB。
- 输入是单张还是文件夹 — 由用户给的路径直接判断,不必问。
- 是否允许等比缩小分辨率 — 默认允许,短边下限 1600px。
- 输出位置 — 默认输出到独立目录,不覆盖原图(若用户要求原地覆盖,必须先警告并要求明确确认,且先备份)。 - 执行脚本,脚本自带护栏:输出目录与源目录相同时直接拒绝执行。
- 读取报告,按
method字段向用户解释每张图做了什么处理。 - 汇报结果:给达标数量、体积变化、被降级处理的图片清单。
命令
# 文件夹批量(最常用)
"<PY>" "<SKILL>/scripts/compress_image.py" "D:/photos" --out-dir "D:/photos_out"
# 单张
"<PY>" "<SKILL>/scripts/compress_image.py" "D:/a.jpg"
# 更小体积 / 更严格保持分辨率
"<PY>" "<SKILL>/scripts/compress_image.py" "D:/photos" --target-mb 0.5
"<PY>" "<SKILL>/scripts/compress_image.py" "D:/photos" --min-side 2400
<PY> 与 <SKILL> 的绝对路径见上文「环境要求」与本技能目录。
参数
| 参数 | 默认 | 说明 |
|---|---|---|
input |
必填 | 图片文件或文件夹(仅识别 jpg / jpeg / png,大小写不敏感) |
--target-mb |
1.0 |
目标体积上限 |
--margin |
0.94 |
余量系数,实际压到 理论值 × 0.94,避免卡在边界被判超标 |
--min-side |
1600 |
等比缩放的短边下限;调高=更保分辨率但更难达标 |
--quality-floor |
82 |
JPEG 优先保证的质量下限,一般不要调低 |
--min-quality |
55 |
兜底可接受的最低 JPEG 质量,仅在缩到下限仍超标时启用 |
--scale-step |
0.9 |
逐级缩放步长 |
--out-dir |
<输入同级>/compressed |
输出目录;等于源目录时脚本拒绝执行 |
--no-recursive |
关 | 只处理顶层文件 |
--no-copy-ok |
关 | 默认会把已达标的小图原样复制过去,加此项则不复制 |
--dry-run |
关 | 只分析不落盘 |
--jobs |
自动(≤4) | 并行进程数 |
--json |
<输出目录>/_compress_report.json |
报告路径 |
--no-report |
关 | 不写报告 |
--quiet |
关 | 只输出摘要 |
压缩策略
JPEG(有 quality 参数)
| 阶段 | 做法 | 结果标记 |
|---|---|---|
| 1 | 原尺寸探测质量 82,达标则在 [82,95] 二分取最高质量 | quality-only |
| 2 | 按 0.9 逐级等比缩小,每级先探测 82,达标再二分取最优质量 | downscale+quality |
| 3 | 缩到短边下限仍超标,在最低尺寸下从 55 向上取最高可达标质量 | min-side+quality |
| 4 | 仍超标,输出最优结果并如实标记 | min-side+min-quality,ok=false |
PNG(没有 quality 参数,只能靠尺寸与颜色数)
| 阶段 | 做法 | 结果标记 |
|---|---|---|
| 1 | 无损重编码(optimize + compress_level=9) | png-lossless |
| 2 | 无损等比缩小 | png-downscale-lossless |
| 3 | 调色板量化,颜色数 256 → 16 逐档尝试,取能达标的最大颜色数 | png-quantize-N |
| 4 | 量化 + 继续缩小尺寸(下限为 --min-side 的一半) |
png-quantize-N+downscale |
体积已在目标以内的图完全不重编码,原样复制,记为 untouched / already_ok。
汇报要求
读 _compress_report.json,向用户交代这几件事,不要只丢一句「压好了」:
- 达标数量、总体积变化(
summary.src_bytes→summary.out_bytes)。 - 被有损处理的图片:
method含downscale或png-quantize的,逐张说明是缩了分辨率还是降了颜色,建议用户肉眼复核。 status=not_reached的图片必须单独说明,并给出三个可选出路:放宽--min-side、提高--target-mb、或接受转成 JPG。status=failed的图片说明原因(多为文件损坏)。- 输出目录的绝对路径。
常见追问与应对
- 「再压小一点」 → 加
--target-mb 0.5。 - 「别缩分辨率」 → 提高
--min-side到原始短边值,或对齐说明:不缩分辨率就必须靠降质量,可能到 55 仍不达标,届时只能如实报告失败。 - 「PNG 怎么压完变颜色了」 → 解释 PNG 是无损格式,压体积只能缩尺寸或减颜色数;若内容其实是照片,转 JPG 效果远好于硬压 PNG(但那是格式变化,需用户点头)。
- 「能不能更清晰一点」 → 提高
--target-mb是最直接的办法,其次提高--min-side。 - 「原图怎么没了」 → 不可能发生。脚本只读源文件;唯一一次删文件的机会是用户自己手动清理输出目录。
已知限制(如实告知用户)
- 带渐变透明的 PNG(RGBA)走量化路径时,透明层级会减少(实测 256 级 → 约 96 级),边缘可能出现轻微硬边;这是 PNG 格式的固有代价,不是可以绕过的 bug。
- 短边原本就小于
--min-side的图,没有缩图空间,只能靠降质量或减颜色达标。 - 极端高细节大图可能出现「缩到下限仍超标」,脚本不会为了达标无限破坏画质,而是如实标记
not_reached。 - 动图(多帧 PNG / GIF)会被跳过,避免破坏动画内容。
- CMYK 色彩模式的 JPEG 会被转成 RGB 输出,颜色可能有轻微偏移。
更多格式原理、极端情况处理与实测数据见 references/limits-and-tuning.md。
安全红线
- 绝不覆盖、移动或删除源文件。
--out-dir与源目录相同时必须拒绝(脚本已内置),不要为了让脚本跑起来而绕过这道护栏。- 用户要求原地覆盖时,先用醒目提示说明风险,做备份后再执行。
- 不要把「压到体积达标」当成唯一目标:画质被破坏到肉眼看得出问题时,宁可如实报告
not_reached,也不交一张糊图。