Skip to content

Commit 783e884

Browse files
committed
feat(colors): display min and max luminance; online tf and primaries
1 parent 8c90138 commit 783e884

File tree

3 files changed

+51
-40
lines changed

3 files changed

+51
-40
lines changed

include/tev/ImageViewer.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -294,7 +294,6 @@ class ImageViewer : public nanogui::Screen {
294294

295295
// Color management
296296
nanogui::ref<nanogui::Texture> mDitherMatrix;
297-
nanogui::Matrix3f mDisplayColorMatrix = nanogui::Matrix3f(1.0f);
298297
};
299298

300299
} // namespace tev

src/ImageViewer.cpp

Lines changed: 50 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -68,11 +68,11 @@ ImageViewer::ImageViewer(
6868
auto ditherMatrix = nanogui::ditherMatrix(ditherScale);
6969
mDitherMatrix->upload((uint8_t*)ditherMatrix.data());
7070

71-
#if defined(NANOGUI_USE_OPENGL)
71+
# if defined(NANOGUI_USE_OPENGL)
7272
std::string preamble = "#version 110\n";
73-
#elif defined(NANOGUI_USE_GLES)
73+
# elif defined(NANOGUI_USE_GLES)
7474
std::string preamble = "#version 100\nprecision highp float; precision highp sampler2D;\n";
75-
#endif
75+
# endif
7676
auto vertexShader = preamble + R"glsl(
7777
uniform vec2 ditherScale;
7878
@@ -95,10 +95,13 @@ ImageViewer::ImageViewer(
9595
uniform sampler2D framebufferTexture;
9696
uniform sampler2D ditherMatrix;
9797
98-
uniform float displaySDRLevel;
98+
uniform float displaySdrWhiteLevel;
99+
uniform float minLuminance;
100+
uniform float maxLuminance;
101+
99102
uniform int outTransferFunction;
100103
uniform mat3 displayColorMatrix;
101-
uniform bool clipToLdr;
104+
uniform bool clipToUnitInterval;
102105
103106
#define CM_TRANSFER_FUNCTION_BT1886 1
104107
#define CM_TRANSFER_FUNCTION_GAMMA22 2
@@ -147,12 +150,6 @@ ImageViewer::ImageViewer(
147150
#define HLG_B 0.28466892
148151
#define HLG_C 0.55991073
149152
150-
#define SDR_MIN_LUMINANCE 0.2
151-
#define SDR_MAX_LUMINANCE 80.0
152-
#define HDR_MIN_LUMINANCE 0.005
153-
#define HDR_MAX_LUMINANCE 10000.0
154-
#define HLG_MAX_LUMINANCE 1000.0
155-
156153
#define M_E 2.718281828459045
157154
158155
vec3 mixb(vec3 a, vec3 b, bvec3 mask) {
@@ -166,14 +163,14 @@ ImageViewer::ImageViewer(
166163
return pow(
167164
(max(E - PQ_C1, vec3(0.0))) / max(PQ_C2 - PQ_C3 * E, vec3(1e-5)),
168165
vec3(PQ_INV_M1)
169-
) * 10000.0 / 203.0;
166+
);
170167
}
171168
172169
vec3 tfInvHLG(vec3 color) {
173170
bvec3 isLow = lessThanEqual(color.rgb, vec3(HLG_E_CUT));
174171
vec3 lo = color.rgb * color.rgb / 3.0;
175172
vec3 hi = (exp((color.rgb - HLG_C) / HLG_A) + HLG_B) / 12.0;
176-
return mixb(hi, lo, isLow) * 1000.0 / 203.0;
173+
return mixb(hi, lo, isLow);
177174
}
178175
179176
// Many transfer functions (including sRGB) follow the same pattern: a linear
@@ -213,7 +210,6 @@ ImageViewer::ImageViewer(
213210
// Forward transfer functions corresponding to the inverse functions above.
214211
// Inputs are assumed to have 1 == 80 nits with a scale factor pre-applied to adjust for SDR white!
215212
vec3 tfPQ(vec3 color) {
216-
color *= 80.0 / 10000.0;
217213
vec3 E = pow(max(color.rgb, vec3(0.0)), vec3(PQ_M1));
218214
return pow(
219215
(vec3(PQ_C1) + PQ_C2 * E) / max(vec3(1.0) + PQ_C3 * E, vec3(1e-5)),
@@ -222,7 +218,6 @@ ImageViewer::ImageViewer(
222218
}
223219
224220
vec3 tfHLG(vec3 color) {
225-
color *= 80.0 / 1000.0;
226221
bvec3 isLow = lessThanEqual(color.rgb, vec3(HLG_D_CUT));
227222
vec3 lo = sqrt(max(color.rgb, vec3(0.0)) * 3.0);
228223
vec3 hi = HLG_A * log(max(12.0 * color.rgb - HLG_B, vec3(0.0001))) + HLG_C;
@@ -323,22 +318,38 @@ ImageViewer::ImageViewer(
323318
}
324319
}
325320
321+
float transferWhiteLevel(int tf) {
322+
if (tf == CM_TRANSFER_FUNCTION_ST2084_PQ) {
323+
return 10000.0;
324+
} else if (tf == CM_TRANSFER_FUNCTION_HLG) {
325+
return 1000.0;
326+
} else if (tf == CM_TRANSFER_FUNCTION_BT1886) {
327+
return 100.0;
328+
} else {
329+
return 80.0;
330+
}
331+
}
332+
326333
vec3 dither(vec3 color) {
327334
return color + texture2D(ditherMatrix, fract(ditherUv)).r;
328335
}
329336
330337
void main() {
331338
vec4 color = texture2D(framebufferTexture, imageUv);
332-
color = vec4(
333-
fromLinearRGB(
334-
displayColorMatrix * (toLinearRGB(color.rgb, CM_TRANSFER_FUNCTION_EXT_SRGB) * displaySDRLevel / 80.0),
335-
outTransferFunction
336-
),
337-
color.a
338-
);
339339
340-
color.rgb = dither(color.rgb);
341-
if (clipToLdr) {
340+
// tev handles colors in linear sRGB with a scale that assumes SDR white corresponds to a value of 1. Hence, to convert to
341+
// absolute nits in the display's color space, we need to multiply by the SDR white level of the display, as well as its
342+
// color transform.
343+
vec3 nits = displayColorMatrix * (displaySdrWhiteLevel * toLinearRGB(color.rgb, CM_TRANSFER_FUNCTION_EXT_SRGB));
344+
345+
// Some displays perform strange tonemapping when provided with values outside of their luminance range. Make sure we don't
346+
// let this happen -- we strongly prefer hard clipping because we want the displayable colors to be preserved.
347+
if (maxLuminance > 0.0) {
348+
nits = clamp(nits, vec3(minLuminance), vec3(maxLuminance));
349+
}
350+
351+
color.rgb = dither(fromLinearRGB(nits / transferWhiteLevel(outTransferFunction), outTransferFunction));
352+
if (clipToUnitInterval) {
342353
color = clamp(color, vec4(0.0), vec4(1.0));
343354
}
344355
@@ -348,31 +359,25 @@ ImageViewer::ImageViewer(
348359

349360
try {
350361
m_cm_shader = new Shader(nullptr, "color_management", vertexShader, fragmentShader);
351-
} catch (const std::runtime_error& e) {
352-
fprintf(stderr, "Error creating color management shader: %s\n", e.what());
353-
}
362+
} catch (const std::runtime_error& e) { fprintf(stderr, "Error creating color management shader: %s\n", e.what()); }
354363

355364
uint32_t indices[3 * 2] = {0, 1, 2, 2, 3, 0};
356365
float positions[2 * 4] = {-1.f, -1.f, 1.f, -1.f, 1.f, 1.f, -1.f, 1.f};
357366

358367
m_cm_shader->set_buffer("indices", VariableType::UInt32, {3 * 2}, indices);
359368
m_cm_shader->set_buffer("position", VariableType::Float32, {4, 2}, positions);
360369
m_cm_shader->set_texture("ditherMatrix", mDitherMatrix);
361-
362-
const auto displayChroma = chromaFromWpPrimaries(m_display_primaries);
363-
mDisplayColorMatrix = inverse(toMatrix3(chromaToRec709Matrix(displayChroma)));
364-
m_cm_shader->set_uniform("displayColorMatrix", mDisplayColorMatrix);
365370
}
366371
#endif
367372

368-
auto tf = ituth273::fromWpTransfer(m_display_transfer_function);
373+
auto tf = ituth273::fromWpTransfer(glfwGetWindowTransfer(m_glfw_window));
369374
mSupportsHdr = m_float_buffer || tf == ituth273::ETransferCharacteristics::PQ || tf == ituth273::ETransferCharacteristics::HLG;
370375

371376
tlog::info() << fmt::format(
372377
"Obtained {} bit {} point frame buffer with primaries={} and transfer={}.{}",
373378
this->bits_per_sample(),
374379
m_float_buffer ? "float" : "fixed",
375-
wpPrimariesToString(m_display_primaries),
380+
wpPrimariesToString(glfwGetWindowPrimaries(m_glfw_window)),
376381
ituth273::toString(tf),
377382
mSupportsHdr ? " HDR display is supported." : " HDR is *not* supported."
378383
);
@@ -1371,12 +1376,19 @@ void ImageViewer::draw_contents() {
13711376

13721377
// Color management
13731378
if (m_cm_shader) {
1374-
float displaySdrLevel = m_display_sdr_level_override ? m_display_sdr_level_override.value() : glfwGetWindowSdrWhiteLevel(m_glfw_window);
1375-
m_cm_shader->set_uniform("displaySDRLevel", displaySdrLevel);
1376-
m_cm_shader->set_uniform("outTransferFunction", m_display_transfer_function);
1377-
m_cm_shader->set_uniform("displayColorMatrix", mDisplayColorMatrix);
1379+
float displaySdrWhiteLevel = m_display_sdr_white_level_override ? m_display_sdr_white_level_override.value() :
1380+
glfwGetWindowSdrWhiteLevel(m_glfw_window);
1381+
m_cm_shader->set_uniform("displaySdrWhiteLevel", displaySdrWhiteLevel);
1382+
m_cm_shader->set_uniform("outTransferFunction", (int)glfwGetWindowTransfer(m_glfw_window));
1383+
1384+
const auto displayChroma = chromaFromWpPrimaries(glfwGetWindowPrimaries(m_glfw_window));
1385+
const auto displayColorMatrix = inverse(toMatrix3(chromaToRec709Matrix(displayChroma)));
1386+
m_cm_shader->set_uniform("displayColorMatrix", displayColorMatrix);
1387+
1388+
m_cm_shader->set_uniform("minLuminance", glfwGetWindowMinLuminance(m_glfw_window));
1389+
m_cm_shader->set_uniform("maxLuminance", glfwGetWindowMaxLuminance(m_glfw_window));
13781390

1379-
m_cm_shader->set_uniform("clipToLdr", !m_float_buffer);
1391+
m_cm_shader->set_uniform("clipToUnitInterval", !m_float_buffer);
13801392

13811393
m_cm_shader->set_uniform("ditherScale", (1.0f / DITHER_MATRIX_SIZE) * Vector2f(m_fbsize[0], m_fbsize[1]));
13821394
m_cm_shader->set_texture("ditherMatrix", mDitherMatrix);

0 commit comments

Comments
 (0)