@realsee/hdr-toolbox
v1.1.3
Published
Compose, extract, and convert Android Ultra HDR JPEG images
Readme
@realsee/hdr-toolbox
用 TypeScript 合成、分解和转换 Android Ultra HDR JPEG。工具遵循 Android Ultra HDR Image Format v1.1 的 JPEG/R 封装:
- 主图是普通 SDR JPEG,旧版查看器可直接显示。
- 主图携带 GContainer XMP,声明
Primary和GainMap两个资源。 - gain map JPEG 携带 Adobe HDR Gain Map XMP。
- MPF APP2 索引主图与第二张 JPEG;两张 JPEG 紧密拼接。
当前写入 Ultra HDR v1 XMP + MPF。v1.1 中提到的 ISO 21496-1 双重元数据属于增强的跨平台兼容建议,本工具暂不写入 ISO 21496-1 APP2。
环境与构建
需要 Node.js 20 或 22。@realsee/equirect-toolbox 使用原生 WebGL 依赖,因此不要使用 Node 24 运行全景命令。
npm install
npm run build
node dist/cli.js --help
# 安装为包后也可直接运行:hdr-toolbox --help配置
输入配置可以是示例中的 JSON,也可以是标准 HDR gain map XMP。JSON 数值使用线性 boost:
{
"max_content_boost": 813.30419921875,
"min_content_boost": 1,
"gamma": 1,
"offset_sdr": 0,
"offset_hdr": 0,
"hdr_capacity_min": 1,
"hdr_capacity_max": 1000,
"use_base_color_space": true
}写入 JSON 时使用下划线(snake_case)key,数值和布尔值都写成原生 JSON 类型。读取时同时兼容下划线和驼峰(camelCase,如 maxContentBoost)两种写法,方便兼容旧文件;数值也接受字符串形式(如 "1"、"1.0"),会按数字解析。写入 XMP 时,max_content_boost、min_content_boost、hdr_capacity_min 和 hdr_capacity_max 会自动转换为标准要求的 log2 值。分解时,.json 输出线性值(下划线 key),.xmp 输出标准 XMP。
CLI
1. SDR + gain map + cfg → Ultra HDR
hdr-toolbox compose \
--sdr examples/cube/0_f.jpg \
--gainmap examples/cube/0_f_gainmap.jpg \
--config examples/cube/0_gainmap_cfg.json \
--output output/0_f.uhdr.jpg合成只改写 JPEG metadata marker,不会重编码 SDR 或 gain map 像素。若 SDR 未携带规范要求的 ICC profile,会无重编码地补入 sRGB ICC marker。
2. Ultra HDR → SDR + gain map + cfg
hdr-toolbox extract \
--input output/0_f.uhdr.jpg \
--sdr output/0_f.jpg \
--gainmap output/0_f_gainmap.jpg \
--config output/0_gainmap_cfg.json将 --config 改成 .xmp 后缀即可输出原生 XMP 表达。
3. cubemap → equirect Ultra HDR
hdr-toolbox cube-to-equirect \
--input-dir examples/cube \
--prefix 0 \
--output output/0_equirect.uhdr.jpg六面映射如下:
| 文件面名 | cubemap key | 方向 |
| -------- | ----------- | ------- |
| r | px | 右 / +X |
| l | nx | 左 / -X |
| u | py | 上 / +Y |
| d | ny | 下 / -Y |
| f | pz | 前 / +Z |
| b | nz | 后 / -Z |
SDR 文件名为 <prefix>_[r,l,u,d,f,b].jpg。gain map 同时兼容 <prefix>_<face>_gainmap.jpg 和示例里的 <prefix>_<face>.gainmap.jpg。配置自动读取输入目录下的 <prefix>_gainmap_cfg.json(找不到时回退 <prefix>_gainmap_cfg.xmp)。六面必须是同尺寸、正方形且边长为 2 的幂。输出尺寸是面宽的 4×2 倍。
4. 线性 HDR(EXR / Radiance HDR)↔ Ultra HDR
convert 在线性浮点 HDR 文件和 Ultra HDR JPEG 之间互转,方向由输入/输出扩展名推断:一侧是线性 HDR(.exr 或 .hdr/.pic),另一侧是 Ultra HDR(.jpg/.jpeg)。
# Ultra HDR → EXR:按增益图重建线性 HDR,写成 32-bit float EXR
hdr-toolbox convert --input examples/ultra-hdr.jpg --output output/ultra-hdr.exr
# Ultra HDR → Radiance HDR:同样的重建,写成 RGBE 编码的 .hdr(体积更小)
hdr-toolbox convert --input examples/ultra-hdr.jpg --output output/ultra-hdr.hdr
# EXR / HDR → Ultra HDR:色调映射出 SDR 基图,反算增益图,封装成 Ultra HDR
hdr-toolbox convert --input output/ultra-hdr.exr --output output/from-exr.uhdr.jpg
hdr-toolbox convert --input output/ultra-hdr.hdr --output output/from-hdr.uhdr.jpg支持两种线性 HDR 容器:
- EXR(OpenEXR):读取支持 HALF/FLOAT 通道、NO_COMPRESSION 与 ZIP/ZIPS 压缩;写入固定为单层、未压缩的 scanline FLOAT RGB,任意 EXR 读取器都能打开。
- Radiance HDR(RGBE,
.hdr/.pic):读取支持 flat、旧式与新式 RLE 编码;写入新式 RLE。RGBE 的共享指数带来 ~0.4% 的量化,但体积明显小于未压缩 EXR。
Ultra HDR → 线性 HDR 按 Adobe/Android 增益图恢复公式逐像素重建,在满显示余量(weight = 1)下还原完整 HDR 意图:hdr = (sdr_linear + offset_sdr) * 2^lerp(log2(min), log2(max), gain^(1/gamma)) - offset_hdr。
线性 HDR → Ultra HDR 先把线性 HDR 色调映射成 SDR 基图,再把 HDR/SDR 比值编码成增益图。默认用曝光融合(fusion):从单张 HDR 合成多档虚拟曝光,按 Mertens 权重(对比度 × 饱和度 × 曝光适中度)在拉普拉斯金字塔上融合——这是一种隐式的局部色调映射,能同时保住暗部和高光细节且不产生光晕。也可切回全局 reinhard 曲线。可选项(仅此方向生效):
| 选项 | 说明 |
| --------------------- | --------------------------------------------------------- |
| --tone-map | SDR 色调映射:fusion(默认,局部)或 reinhard(全局) |
| --ev-step | 曝光融合的档间距(stops,默认 2) |
| --exposure-count | 曝光融合的合成曝光数,奇数(默认 5) |
| --max-content-boost | 要编码的线性 HDR 余量,默认取输入中的最大线性值 |
| --sdr-quality | SDR 基图 JPEG 质量(1-100,默认 95) |
| --gainmap-quality | 增益图 JPEG 质量(1-100,默认 95) |
曝光融合是全图金字塔运算,对大图(如 8K 全景)内存和耗时都明显高于
reinhard;离线转换可接受,追求速度可用--tone-map reinhard。
5. equirect Ultra HDR → cubemap
hdr-toolbox equirect-to-cube \
--input output/0_equirect.uhdr.jpg \
--output-dir output/cube \
--prefix 0输出 6 张 <prefix>_<face>.jpg、6 张 <prefix>_<face>_gainmap.jpg 和 <prefix>_gainmap_cfg.json。
TypeScript API
import { composeUltraHdr, extractUltraHdr } from "@realsee/hdr-toolbox";
await composeUltraHdr({
sdr: "sdr.jpg",
gainMap: "gainmap.jpg",
config: "gainmap_cfg.json",
output: "image.uhdr.jpg",
});
await extractUltraHdr({
input: "image.uhdr.jpg",
sdr: "sdr.jpg",
gainMap: "gainmap.jpg",
config: "gainmap_cfg.xmp",
});线性 HDR 互转(EXR / Radiance HDR,方向中立的函数名;旧的 exrToUltraHdr / ultraHdrToExr 作为别名保留):
import { linearHdrToUltraHdr, ultraHdrToLinearHdr } from "@realsee/hdr-toolbox";
// 输出格式由扩展名推断:.exr 或 .hdr
await ultraHdrToLinearHdr({ input: "image.uhdr.jpg", output: "image.hdr" });
await linearHdrToUltraHdr({
input: "image.exr",
output: "image.uhdr.jpg",
maxContentBoost: 8, // 可选,不填则按输入峰值自动推断
});底层的编解码函数 readExr / writeExr 和 readHdr / writeHdr 也一并导出,都以交错的线性浮点 RGB(LinearHdrImage)作为统一的像素表示。
校验
npm run typecheck
npm test
npm run lint
npm run format:check
npm run build