Ultra HDR 与 Gainmap增益图

1. 核心思想:SDR + 增益图 = HDR

传统 JPEG/PNG 每个像素亮度值只有 0-255,对应 SDR(标准动态范围,约 200 nits 上限)。
HDR 显示需要更高亮度(可达 1000+ nits)。

Gainmap 方案的核心是:

HDR 像素 = SDR 像素 × 增益系数

增益系数由一张独立的灰度图(Gainmap)编码。这样同一个文件:

  • SDR 屏幕上直接显示 SDR 基图
  • HDR 屏幕上将 SDR 基图与 Gainmap 合成,还原 HDR 图像

2. 直观理解:两张图叠加

原始 JPEG(SDR 基图)          Gainmap(增益图)
┌─────────────────┐          ┌─────────────────┐
│  天空:灰色      │          │  天空:白色      │  ← 白 = 需要大幅提亮
│  人脸:肤色      │    ×     │  人脸:深灰      │  ← 深灰 = 轻微提亮
│  阴影:黑色      │          │  阴影:黑色      │  ← 黑 = 不提亮
└─────────────────┘          └─────────────────┘
         ↓ HDR 屏幕上叠加
┌─────────────────┐
│  天空:刺眼亮白  │  ← 2000 nits
│  人脸:自然肤色  │  ← 300 nits
│  阴影:纯黑      │  ← 0 nits
└─────────────────┘

Gainmap 像素越白(值越大)→ 叠加后越亮
Gainmap 像素越黑(值越小)→ 几乎不改变亮度


3. 增益倍数的计算:对数空间插值

为什么用对数空间?

增益倍数的映射(Gainmap 像素值 → 实际增益倍数)使用对数空间线性插值,而不是线性插值。

因为人眼对亮度的感知是对数的——"增亮一倍"在任何亮度区间感觉差不多:

  • 10 → 20 nits,感觉"亮了一倍"
  • 1000 → 2000 nits,同样感觉"亮了一倍"

使用对数映射后,Gainmap 的每一个像素级别的差异对人眼代表均匀的感知变化,精度分配更合理。如果用线性映射,低增益区间会被大量像素值浪费在人眼几乎感觉不到的细微差异上。

映射公式

设 Gainmap 像素归一化值为 t∈[0,1]t \in [0, 1]t[0,1](原始 0-255 除以 255):

对数空间线性插值:

log⁡(gain)=log⁡(ratioMin)+t⋅[log⁡(ratioMax)−log⁡(ratioMin)]\log(\text{gain}) = \log(\text{ratioMin}) + t \cdot [\log(\text{ratioMax}) - \log(\text{ratioMin})]log(gain)=log(ratioMin)+t[log(ratioMax)log(ratioMin)]

等价的幂次形式(底数无关):

gain=ratioMin1−t⋅ratioMaxt\text{gain} = \text{ratioMin}^{1-t} \cdot \text{ratioMax}^{t}gain=ratioMin1tratioMaxt

注意:底数(eee、2、10)不影响结果,因为换底后系数在分子分母同时约掉。实际代码使用幂次形式,完全不含对数底数。

对比线性插值(ratioMin=1, ratioMax=8, t=0.5)

插值方式 t=0.5 时的增益
线性插值 (1+8)/2=4.5(1+8)/2 = 4.5(1+8)/2=4.5
对数插值 8≈2.83\sqrt{8} \approx 2.838 2.83(几何平均数)
gain
 8 |                                          ● 对数
   |                                   ●
   |                              ●
4.5|                         ○ 线性(t=0.5)
   |                    ●
   |              ●
2.83|         ● 对数(t=0.5)
   |    ●
 1 |●
   +----+----+----+----+----+----+----+----+--→ t
   0                  0.5                  1

4. gamma:Gainmap 像素的编码校正

Gainmap 图像存储时经过了 gamma 压缩(类似 sRGB),读取时需要做 gamma 还原(线性化),指数取倒数:

t=pixel1/γt = \text{pixel}^{1/\gamma}t=pixel1/γ

注意方向:gamma编码时用的指数,解码时取倒数 1/γ1/\gamma1/γ
pixel^γ 是压缩(变暗),pixel^(1/γ) 是还原(变亮)。

完整映射:

gain=ratioMin1−pixel1/γ⋅ratioMaxpixel1/γ\text{gain} = \text{ratioMin}^{1 - \text{pixel}^{1/\gamma}} \cdot \text{ratioMax}^{\text{pixel}^{1/\gamma}}gain=ratioMin1pixel1/γratioMaxpixel1/γ

gamma 值 含义
1.0 像素值直接作为 ttt,不做处理
2.2 像素值已做 gamma 压缩(偏暗),解码后 ttt 被拉高(亮部细节更多)
< 1.0 像素值已做反向压缩(偏亮),解码后 ttt 被压低(暗部细节更多)

5. 设备自适应:minDisplayRatioForHdrTransition

HDR 效果需要在不同能力的设备上平滑过渡,而不是非 HDR 即 SDR 的二元切换。

定义两个阈值:

字段 含义
minDisplayRatioForHdrTransitiondmin⁡d_{\min}dmin 低于此值:完全显示 SDR,不应用 Gainmap
displayRatioForFullHdrdmax⁡d_{\max}dmax 高于此值:完全应用 Gainmap,显示完整 HDR

设当前设备亮度比为 ddd,计算混合权重 www

w=clamp(log⁡(d)−log⁡(dmin⁡)log⁡(dmax⁡)−log⁡(dmin⁡), 0, 1)w = \text{clamp}\left(\frac{\log(d) - \log(d_{\min})}{\log(d_{\max}) - \log(d_{\min})},\ 0,\ 1\right)w=clamp(log(dmax)log(dmin)log(d)log(dmin), 0, 1)

www 场景 效果
w=0w = 0w=0 SDR 屏幕 gain0=1\text{gain}^0 = 1gain0=1,完全不增亮
w=1w = 1w=1 全 HDR 屏幕 gain1=gain\text{gain}^1 = \text{gain}gain1=gain,完全增亮
w=0.5w = 0.5w=0.5 中等能力设备 gain0.5=gain\text{gain}^{0.5} = \sqrt{\text{gain}}gain0.5=gain ,增亮一半

6. 完整公式

官方公式(ISO 21496-1 / Ultra HDR 规范,Skia 实现)

对每个颜色通道 c∈{R,G,B}c \in \{R, G, B\}c{R,G,B} 独立计算:

Step 1:Gainmap 线性化

tc=Gc1/γct_c = G_c^{1/\gamma_c}tc=Gc1/γc

其中 GcG_cGc 为 Gainmap 图像归一化像素值(0-1)。

Step 2:对数空间插值,得到最大可用增益

log⁡(gainc)=tc⋅[log⁡(ratioMaxc)−log⁡(ratioMinc)]+log⁡(ratioMinc)\log(\text{gain}_c) = t_c \cdot [\log(\text{ratioMax}_c) - \log(\text{ratioMin}_c)] + \log(\text{ratioMin}_c)log(gainc)=tc[log(ratioMaxc)log(ratioMinc)]+log(ratioMinc)

Step 3:按设备显示能力加权

w=clamp ⁣(log⁡(d)−log⁡(dmin⁡)log⁡(dmax⁡)−log⁡(dmin⁡), 0, 1)w = \text{clamp}\!\left(\frac{\log(d) - \log(d_{\min})}{\log(d_{\max}) - \log(d_{\min})},\ 0,\ 1\right)w=clamp(log(dmax)log(dmin)log(d)log(dmin), 0, 1)

gain_finalc=gainc w\text{gain\_final}_c = \text{gain}_c^{\ w}gain_finalc=gainc w

Step 4:合成 HDR 像素

HDRc=(SDRc+εsdr,c)⋅gain_finalc−εhdr,c\text{HDR}_c = (\text{SDR}_c + \varepsilon_{\text{sdr},c}) \cdot \text{gain\_final}_c - \varepsilon_{\text{hdr},c}HDRc=(SDRc+εsdr,c)gain_finalcεhdr,c


三阶段串联示意

G(Gainmap 像素 0-1)
   ↓ Step 1:gamma 线性化
   t = G^(1/γ)                             ← 还原编码时的 gamma 压缩
   ↓ Step 2:插值到增益空间
   gain = ratioMin^(1-t) × ratioMax^t      ← 该像素点"最多能亮多少倍"
   ↓ Step 3:按设备能力缩放
   gain_final = gain^w                     ← 当前设备实际应用多少
   ↓ Step 4:作用到 SDR 像素
   HDR = (SDR + εSdr) × gain_final − εHdr

合并 Step 1-3 为单一表达式:

gain_finalc=(ratioMinc1−Gc1/γc⋅ratioMaxcGc1/γc)w\text{gain\_final}_c = \left(\text{ratioMin}_c^{1 - G_c^{1/\gamma_c}} \cdot \text{ratioMax}_c^{G_c^{1/\gamma_c}}\right)^{w}gain_finalc=(ratioMinc1Gc1/γcratioMaxcGc1/γc)w

epsilon 是防止 log⁡(0)\log(0)log(0) 的极小偏移量(通常为 0 或 1/255),对视觉无影响。


7. Android Gainmap API(API 34+)

字段对应关系

android.graphics.Gainmap 字段 QGainmapInfo 字段 含义
gainmapContents mGainmapContent 增益图像素数据
displayRatioForFullHdr mfDisplayRatioForFullHdr 触发完整 HDR 的显示亮度比
minDisplayRatioForHdrTransition mfMinDisplayRatioForHdrTransition 开始 HDR 过渡的最低亮度比
ratioMax[R,G,B] mRatioMax Gainmap 最大增益
ratioMin[R,G,B] mRatioMin Gainmap 最小增益
gamma[R,G,B] mGamma Gainmap 编码 gamma
epsilonHdr[R,G,B] mEpsilonHdr HDR 侧防黑场偏移
epsilonSdr[R,G,B] mEpsilonSdr SDR 侧防黑场偏移

ratioMax/ratioMin/gamma/epsilon 均为 R/G/B 三通道独立值,各通道独立计算增益。

基本用法

// 读取 HDR 图片的 Gainmap
Bitmap hdrBitmap = BitmapFactory.decodeFile(path);
Gainmap gainmap = hdrBitmap.getGainmap();  // 从 JPEG XMP 或 HEIF 解析

// 重新组装 HDR Bitmap
Bitmap sdrBitmap = ...;
Gainmap gainmap = new Gainmap(gainmapPixels);
gainmap.setDisplayRatioForFullHdr(4.0f);
gainmap.setRatioMax(2.0f, 2.0f, 2.0f);
// setters 均为 3 个独立 float 参数(R, G, B),不是 float[]
sdrBitmap.setGainmap(gainmap);
// 此时 sdrBitmap 成为 HDR Bitmap,可在 HDR 显示器上正确渲染

8. 一句话总结

SDR 基图是"骨架",Gainmap 是"哪里需要发光的地图",metadata(ratioMax/ratioMin/gamma/epsilon)是"解读这张地图的说明书"。三者缺一不可才能还原 HDR 图像。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐