Skip to content

Commit c7e1428

Browse files
committed
Added HLG conversion.
1 parent a8f8343 commit c7e1428

File tree

4 files changed

+110
-11
lines changed

4 files changed

+110
-11
lines changed

src/lib/mrvCore/mrvImage.h

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,45 @@ namespace mrv
2424

2525
namespace
2626
{
27+
// HLG (BT.2100) -> Linear light (normalized to 10,000 nits = 1.0)
28+
inline float hlg_to_linear(float hlg, const float L_W = 100.F)
29+
{
30+
// Clamp to [0, 1] to avoid NANs and invalid ranges
31+
hlg = std::clamp(hlg, 0.0f, 1.0f);
32+
33+
// HLG EOTF Constants (BT.2100-2)
34+
// L_W is the System White level (100 nits).
35+
// The final result is normalized to L_PEAK = 10000 nits.
36+
const double a = 0.17883277;
37+
const double b = 1.0 - 4.0 * a; // ~0.28466892
38+
const double c = 0.5 - 2.0 * a; // ~0.14233446
39+
40+
// Scale factor to convert the output (normalized to L_W) to L_PEAK (10000 nits)
41+
// L_W / L_PEAK = 100 / 10000 = 0.01
42+
const double scale_factor = L_W / 10000.F;
43+
44+
double L; // Linear light value, normalized to 10000 nits = 1.0
45+
46+
// Low-luminance segment (0 <= V <= 0.5)
47+
if (hlg <= 0.5)
48+
{
49+
// E_nits = L_W * 3 * V^2
50+
// L = (L_W / 10000) * 3 * V^2 = 0.03 * V^2
51+
L = 3.0 * std::pow(hlg, 2.0) * scale_factor;
52+
}
53+
// High-luminance segment (0.5 < V <= 1.0)
54+
else
55+
{
56+
// E_nits = (L_W / 12) * (exp((V - c) / a) + b)
57+
// L = (L_W / 10000) * (E_nits / L_W) * L_W / 12 * (exp... + b)
58+
// L = (0.01 / 12.0) * (exp((V - c) / a) + b)
59+
const double exp_term = std::exp((hlg - c) / a);
60+
L = (exp_term + b) * scale_factor / 12.0;
61+
}
62+
63+
return static_cast<float>(L);
64+
}
65+
2766
// PQ (ST2084) → Linear light (normalized to 10,000 nits = 1.0)
2867
inline float pq_to_linear(float pq)
2968
{

src/lib/mrvFl/mrvSaveImageVk.cpp

Lines changed: 40 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -97,9 +97,9 @@ namespace mrv
9797
}
9898

9999
const auto& hdrOptions = ui->uiView->getHDROptions();
100+
const auto& hdr = hdrOptions.hdrData;
100101
if (hdrOptions.passthru || hdrOptions.tonemap)
101102
{
102-
const auto& hdr = hdrOptions.hdrData;
103103
std::stringstream s;
104104
s << hdr.primaries[0].x << " " << hdr.primaries[0].y
105105
<< hdr.primaries[1].x << " " << hdr.primaries[1].y
@@ -757,6 +757,8 @@ namespace mrv
757757
int numChannels = image::getChannelCount(type);
758758

759759
int channelCount = numChannels;
760+
761+
// We don't consider the alpha channel for conversion.
760762
if (numChannels == 2 || numChannels == 4)
761763
channelCount = numChannels - 1;
762764

@@ -782,10 +784,24 @@ namespace mrv
782784
p += offset;
783785

784786
// 1. Apply inverse EOTF.
785-
*p = pq_to_linear(*p);
787+
switch(hdr.eotf)
788+
{
789+
case image::EOTF_BT2100_HLG:
790+
*p = hlg_to_linear(*p);
791+
792+
// 2. Apply Luminance Scaling based on Source MaxCLL (e.g., 1000 nits)
793+
*p *= SCALE_FACTOR;
794+
break;
795+
case image::EOTF_BT2020:
796+
case image::EOTF_BT2100_PQ:
797+
*p = pq_to_linear(*p);
786798

787-
// 2. Apply Luminance Scaling based on Source MaxCLL (e.g., 1000 nits)
788-
*p *= SCALE_FACTOR;
799+
// 2. Apply Luminance Scaling based on Source MaxCLL (e.g., 1000 nits)
800+
*p *= SCALE_FACTOR;
801+
break;
802+
default:
803+
break;
804+
}
789805
}
790806
else if (type == image::PixelType::RGBA_F16 ||
791807
type == image::PixelType::RGB_F16)
@@ -795,10 +811,27 @@ namespace mrv
795811

796812
// 1. Convert to float and apply inverse EOTF.
797813
float tmp = *p;
798-
tmp = pq_to_linear(tmp);
814+
815+
switch(hdr.eotf)
816+
{
817+
case image::EOTF_BT2100_HLG:
818+
tmp = hlg_to_linear(tmp);
819+
820+
// 2. Apply Luminance Scaling based on Source MaxCLL (e.g., 1000 nits)
821+
tmp *= SCALE_FACTOR;
822+
823+
break;
824+
case image::EOTF_BT2020:
825+
case image::EOTF_BT2100_PQ:
826+
tmp = pq_to_linear(tmp);
827+
828+
// 2. Apply Luminance Scaling based on Source MaxCLL (e.g., 1000 nits)
829+
tmp *= SCALE_FACTOR;
830+
break;
831+
default:
832+
break;
833+
}
799834

800-
// 2. Apply Luminance Scaling based on Source MaxCLL (e.g., 1000 nits)
801-
tmp *= SCALE_FACTOR;
802835

803836
// 3. Convert back to half
804837
*p = tmp;

src/lib/mrvFl/mrvSaveMovieVk.cpp

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,8 @@ namespace mrv
191191
s << speed;
192192
ioOptions["OpenEXR/Speed"] = s.str();
193193
}
194-
194+
195+
const bool use_pq = true;
195196
const auto& hdrOptions = ui->uiView->getHDROptions();
196197
if (hdrOptions.passthru || hdrOptions.tonemap)
197198
{
@@ -1163,13 +1164,16 @@ namespace mrv
11631164
int numChannels = image::getChannelCount(type);
11641165

11651166
int channelCount = numChannels;
1167+
1168+
// We don't consider the alpha channel for conversion.
11661169
if (numChannels == 2 || numChannels == 4)
11671170
channelCount = numChannels - 1;
11681171

11691172
const float MAX_CLL_SOURCE = hdrOptions.hdrData.maxCLL;
11701173
const float SCALE_FACTOR = 10000.0f / MAX_CLL_SOURCE;
11711174
const size_t w = outputImage->getWidth();
11721175
const size_t h = outputImage->getHeight();
1176+
const auto& hdr = hdrOptions.hdrData;
11731177
for (size_t y = 0; y < h; ++y)
11741178
{
11751179
const size_t y_stride = y * w * numChannels;
@@ -1187,7 +1191,18 @@ namespace mrv
11871191
p += offset;
11881192

11891193
// 1. Apply inverse EOTF.
1190-
*p = pq_to_linear(*p);
1194+
switch(hdr.eotf)
1195+
{
1196+
case image::EOTF_BT2100_HLG:
1197+
*p = hlg_to_linear(*p);
1198+
break;
1199+
case image::EOTF_BT2020:
1200+
case image::EOTF_BT2100_PQ:
1201+
*p = pq_to_linear(*p);
1202+
break;
1203+
default:
1204+
break;
1205+
}
11911206

11921207
// 2. Apply Luminance Scaling based on Source MaxCLL (e.g., 1000 nits)
11931208
*p *= SCALE_FACTOR;
@@ -1200,7 +1215,19 @@ namespace mrv
12001215

12011216
// 1. Convert to float and apply inverse EOTF.
12021217
float tmp = *p;
1203-
tmp = pq_to_linear(tmp);
1218+
1219+
switch(hdr.eotf)
1220+
{
1221+
case image::EOTF_BT2100_HLG:
1222+
tmp = hlg_to_linear(tmp);
1223+
break;
1224+
case image::EOTF_BT2020:
1225+
case image::EOTF_BT2100_PQ:
1226+
tmp = pq_to_linear(tmp);
1227+
break;
1228+
default:
1229+
break;
1230+
}
12041231

12051232
// 2. Apply Luminance Scaling based on Source MaxCLL (e.g., 1000 nits)
12061233
tmp *= SCALE_FACTOR;

tlRender/lib/tlIO/OpenEXRWrite.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,7 @@ namespace tl
170170
if (_hasChromaticities)
171171
{
172172
addChromaticities(header, _chromaticities);
173-
addWhiteLuminance(header, 100.0F);
173+
addWhiteLuminance(header, 1.F);
174174
Imath::V2f adoptedNeutral(_chromaticities.white.x,
175175
_chromaticities.white.y);
176176
addAdoptedNeutral(header, adoptedNeutral);

0 commit comments

Comments
 (0)