Skip to content

Latest commit

 

History

1 Commit

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 

Repository files navigation

jxl-hdr-scale

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 输入 + SetFrameLossless,因 F32 输入不支持 distance=0);1=视觉无损(默认,与原图同档); 23=明显省体积肉眼难辨;>4 开始可见。 ⚠️ distance=0 对 16-bit parametric HDR 源不可用(2026-09 实测:brew libjxl 0.12 JxlEncoderSetFrameLossless 直接返回 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 = 写成 brob box:XMP 14110B→2967B、Exif 1034B→568B 落盘(brotli 无损, 解压后与原文件 payload 字节一致,2026-09 实测)。
  • 0 = raw 透传,容器里直接是 Exif/xml box,与多数源文件格式相同。
  • 两者对解码器等价(JxlDecoderSetDecompressBoxes(TRUE) 统一还原)。

输出 = 原 JXL 的:同 JxlColorEncoding(如 2100/PQ/D65)+ 原 intensity_target/min_nits

  • bits_per_sample(16) + Exif/XMP 容器 box 原样透传,只有 xsize/ysize 变小。

验证(每次跑完照做,别只看 jxlinfo)

  1. 结构jxlinfo out.jxl — 2100/PQ/intensity_target/16-bit 与原图一致; 容器 box 里 Exif/xml 长度与原图相等(大端 len 扫描)。
  2. 亮度语义(关键):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%
    • 若均值对但相关性异常 → 查重采样,别怀疑色彩链。
  3. 肉眼:HDR 屏打开 out.jxl 看高光(最终裁决,程序替代不了)。

render_srgb = §6b A1 三件套(SetDesiredIntensityTarget(itarget)+SetCms(*JxlGetDefaultCms()) +SetOutputColorProfile(sRGB) 全在 JXL_DEC_COLOR_ENCODING 事件内),已编译进 bin/。

原理(为什么这样保真)

  1. 解码不设 SetOutputColorProfile,F32 默认输出对 parametric PQ 源 = scene 码值 (0~1,1.0=10000nit)。运行时断言 peak∈(0.2,1.0),越界报警。
  2. 直接在 PQ 码值域做 Lanczos-3 可分离重采样(核 sinc(x)·sinc(x/3),半径 3,边界 clamp)。 理由:PQ 单调,2~4× 缩小照片值域内斜率平缓,码值域插值均值偏差实测 ~0.01%; 免去 PQ↔linear 往返两次舍入。极端高对比图(>10000nit 级 + 大黑场)才需改线性光域。
  3. 编码 F32 输入 + 原 JxlColorEncoding 原样 SetColorEncoding → 无中间色彩转换、 无 tone-map、无 gamut 漂移;intensity_target 随 JxlBasicInfo 原值写回。
  4. Exif/XMP 手动拆 ISO-BMFF box 透传(JxlEncoderUseBoxes + AddBox, contents 不含 box 头;Exif payload 含 4 字节 00000000 type 字段,原样带)。

踩坑(本机 brew jpeg-xl 0.12,2026-09 实测)

  • 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 保真是主要场景。

产物基线(ultra-hdr.JXL 4032×2268, 1000nit/2100/PQ/16bit)

目标 输出 sRGB 渲染均值对照 备注
长边 2016 2016×1134 71.16 vs ref 71.18,|diff| mean 3.55 亮度相关 0.990
长边 1500 1500×844 结构+box 验证通过 任意比例冒烟
长边 1001 1002×564 结构+box 验证通过 奇数钳偶

About

Lanczos-3 resizer for HDR JXL (BT.2020/PQ): target longest side, even dims, Exif/XMP box passthrough or Brotli compression, --distance/--effort tunables. libjxl C API, no cjxl.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages