HDR JXL 图像任意倍率缩小(Lanczos-3),保持格式与色彩语义不变。
适用源:parametric PQ/HLG 原生 HDR JXL(BT.2020 + intensity_target,如相机/手机 HDR 导出)。
不是 Ultra HDR gain-map(那类走 ultrahdr-thumb)。
- "把这张 HDR JXL 缩到 XX% / 长边 XXXX,格式不变" → 本工具
- 需要 HDR JXL 缩略图但必须保留 HDR 语义(HDR 屏上真提亮)
- 任意倍率(非 2 倍整数),输出长宽自动钳偶数
# 依赖: brew jpeg-xl(pkg-config libjxl)
/Users/macbook/Documents/HermesAgent/tools/jxl-hdr-scale/build.sh # 首次/改码后编译
TOOL=/Users/macbook/Documents/HermesAgent/tools/jxl-hdr-scale/bin/jxl_hdr_resize
# 按长边指定输出分辨率(最常用;长宽自动钳偶数)
$TOOL in.jxl out.jxl 2016
# 按倍率(长宽同乘,0<scale<1,只做缩小)
$TOOL in.jxl out.jxl --scale 0.37
# 质量参数(可任意顺序,默认 --distance 1.0 --effort 7 --compress_boxes 1)
$TOOL in.jxl out.jxl 2016 --distance 2.0 --effort 10 # 更省体积
$TOOL in.jxl out.jxl 2016 --distance 0 --effort 3 # 数学无损(体积最大、编码最慢)
$TOOL in.jxl out.jxl 2016 --compress_boxes 0 # box 不压缩(raw 透传,同源文件格式)
# 辅助:解 F32 默认输出到 PFM(自检/对比用,单行头 "PF w h 1.0")
$TOOL in.jxl --decode out.pfm质量参数语义:
--distance D(025,butteraugli 距离):0=数学无损(内部自动切 UINT16 输入 +3=明显省体积肉眼难辨;>4 开始可见。SetFrameLossless,因 F32 输入不支持 distance=0);1=视觉无损(默认,与原图同档); 2⚠️ distance=0 对 16-bit parametric HDR 源不可用(2026-09 实测:brew libjxl 0.12JxlEncoderSetFrameLossless直接返回 error),工具会报清晰错误并建议 0.1~1。 这类源想要"近无损"就用--distance 0.1。--effort N(1~10):编码算力档,越高同体积质量越好、越慢。- 实测体积基线(4032×2268 → 长边 2016,同一 Lanczos 缩放产物): d=1.0/e7 ≈ 620KB;d=0.5/e7 ≈ 964KB;d=2.0/e10 ≈ 398KB。
--compress_boxes 0|1(默认 1,同 cjxl 同名参数):控制 Exif/XMP 容器 box 是否
Brotli 压缩(libjxl 的 JxlEncoderAddBox 第 5 参 compress_box)。
- 1 = 写成
brobbox:XMP 14110B→2967B、Exif 1034B→568B 落盘(brotli 无损, 解压后与原文件 payload 字节一致,2026-09 实测)。 - 0 = raw 透传,容器里直接是
Exif/xmlbox,与多数源文件格式相同。 - 两者对解码器等价(
JxlDecoderSetDecompressBoxes(TRUE)统一还原)。
输出 = 原 JXL 的:同 JxlColorEncoding(如 2100/PQ/D65)+ 原 intensity_target/min_nits
- 原
bits_per_sample(16) + Exif/XMP 容器 box 原样透传,只有 xsize/ysize 变小。
- 结构:
jxlinfo out.jxl— 2100/PQ/intensity_target/16-bit 与原图一致; 容器 box 里Exif/xml长度与原图相等(大端 len 扫描)。 - 亮度语义(关键):
bin/render_srgb in.jxl /tmp/a.ppm && bin/render_srgb out.jxl /tmp/b.ppm(decoder 原生 tone-map 三件套压 sRGB U8),numpy 对齐比:- 输出图 vs 原图 box/插值降到同尺寸的均值差 <~5/255(2 倍档实测 mean diff≈3.5、亮度相关 0.99)
- 整图亮度均值偏差 <1%
- 若均值对但相关性异常 → 查重采样,别怀疑色彩链。
- 肉眼:HDR 屏打开 out.jxl 看高光(最终裁决,程序替代不了)。
render_srgb = §6b A1 三件套(SetDesiredIntensityTarget(itarget)+SetCms(*JxlGetDefaultCms())
+SetOutputColorProfile(sRGB) 全在 JXL_DEC_COLOR_ENCODING 事件内),已编译进 bin/。
- 解码不设 SetOutputColorProfile,F32 默认输出对 parametric PQ 源 = scene 码值 (0~1,1.0=10000nit)。运行时断言 peak∈(0.2,1.0),越界报警。
- 直接在 PQ 码值域做 Lanczos-3 可分离重采样(核 sinc(x)·sinc(x/3),半径 3,边界 clamp)。 理由:PQ 单调,2~4× 缩小照片值域内斜率平缓,码值域插值均值偏差实测 ~0.01%; 免去 PQ↔linear 往返两次舍入。极端高对比图(>10000nit 级 + 大黑场)才需改线性光域。
- 编码 F32 输入 + 原 JxlColorEncoding 原样 SetColorEncoding → 无中间色彩转换、 无 tone-map、无 gamut 漂移;intensity_target 随 JxlBasicInfo 原值写回。
- Exif/XMP 手动拆 ISO-BMFF box 透传(
JxlEncoderUseBoxes+AddBox, contents 不含 box 头;Exif payload 含 4 字节00000000type 字段,原样带)。
cjxl无--resize参数;libjxl 0.12 公开 dylib 不导出JxlImageScaler*→ 手写 Lanczos。- 容器 box 扫描:box len 是大端 uint32;
memcpy到 little-endian 主机读出 335MB 垃圾导致静默跳过 (症状 = exif=0 B 但程序不报错)。类型是大写"Exif"/"xml ",不是小写。 - ISO-BMFF box 头 =
[4 len][4 type]共 8 字节,没有 MP4 扩展里那种 ver/flags/uid 四件套; JXL 容器里 payload 直接从偏移 8 起(但 Exif payload 内部自己带 4 字节 type 字段)。 JxlDecoderGetBasicInfo/GetColorAsEncodedProfile返回JXL_DEC_SUCCESS(0)= 成功; 写!ret判断会把成功当失败(libjxl 0.12 这些函数返回 status 不是 bool)。- 全量输入解码前必须
JxlDecoderCloseInput,否则 GetBasicInfo 永远 NEED_MORE_INPUT。 - Exif/XMP 必须在
AddImageFrame之前UseBoxes+AddBox,最后CloseInput同时关 boxes。 JxlEncoderProcessOutput用指针式接口(uint8_t** next_out, size_t* avail), SUCCESS 时next_out指向末尾,flush 后循环;不是"写满就 NEED_MORE_OUTPUT"。--decode出的 PFM 头是单行PF w h 1.0\n(标准 PFM 是三行,脚本解析时注意)。- 只做缩小(scale≥1 拒跑):放大 Lanczos 只造平滑不出细节,且体积变大,无收益。
--distance 0(数学无损)对 16-bit parametric HDR 源不可用:brew libjxl 0.12 下JxlEncoderSetFrameLossless返回 error(工具已捕获并给出 0.1~1 建议)。 F32 输入本身也不支持 distance=0(须切 UINT16 +JxlBitDepth{type=FROM_CODESTREAM}结构体指针, 0.12 签名是const JxlBitDepth*不是枚举值)。- 质量体积基线(4032×2268→长边 2016 同 Lanczos 产物):d1.0/e7≈620KB、d0.5/e7≈964KB、 d2.0/e10≈398KB。distance 是体积主控,effort 只做同档微调。
- SDR JXL(itarget=0 或整数输出)也能跑(默认 F32 就是线性 sRGB 语义),但 HDR 保真是主要场景。
| 目标 | 输出 | sRGB 渲染均值对照 | 备注 |
|---|---|---|---|
| 长边 2016 | 2016×1134 | 71.16 vs ref 71.18,|diff| mean 3.55 | 亮度相关 0.990 |
| 长边 1500 | 1500×844 | 结构+box 验证通过 | 任意比例冒烟 |
| 长边 1001 | 1002×564 | 结构+box 验证通过 | 奇数钳偶 |