blob: c4f46bd97cd161524472afc3dd351de68c8e600f [file] [log] [blame]
Alec Mouri465b2962021-10-08 16:22:21 -07001/*
2 * Copyright 2021 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#include <tonemap/tonemap.h>
18
Alec Mouri4049b532021-10-15 20:59:33 -070019#include <algorithm>
Alec Mouri465b2962021-10-08 16:22:21 -070020#include <cstdint>
21#include <mutex>
22#include <type_traits>
23
24namespace android::tonemap {
25
26namespace {
27
28// Flag containing the variant of tone map algorithm to use.
29enum class ToneMapAlgorithm {
Alec Mouri5184f412021-10-14 18:13:49 -070030 AndroidO, // Default algorithm in place since Android O,
31 Android13, // Algorithm used in Android 13.
Alec Mouri465b2962021-10-08 16:22:21 -070032};
33
Alec Mouri5184f412021-10-14 18:13:49 -070034static const constexpr auto kToneMapAlgorithm = ToneMapAlgorithm::Android13;
Alec Mouri465b2962021-10-08 16:22:21 -070035
36static const constexpr auto kTransferMask =
37 static_cast<int32_t>(aidl::android::hardware::graphics::common::Dataspace::TRANSFER_MASK);
38static const constexpr auto kTransferST2084 =
39 static_cast<int32_t>(aidl::android::hardware::graphics::common::Dataspace::TRANSFER_ST2084);
40static const constexpr auto kTransferHLG =
41 static_cast<int32_t>(aidl::android::hardware::graphics::common::Dataspace::TRANSFER_HLG);
42
43template <typename T, std::enable_if_t<std::is_trivially_copyable<T>::value, bool> = true>
44std::vector<uint8_t> buildUniformValue(T value) {
45 std::vector<uint8_t> result;
46 result.resize(sizeof(value));
47 std::memcpy(result.data(), &value, sizeof(value));
48 return result;
49}
50
51class ToneMapperO : public ToneMapper {
52public:
53 std::string generateTonemapGainShaderSkSL(
54 aidl::android::hardware::graphics::common::Dataspace sourceDataspace,
55 aidl::android::hardware::graphics::common::Dataspace destinationDataspace) override {
56 const int32_t sourceDataspaceInt = static_cast<int32_t>(sourceDataspace);
57 const int32_t destinationDataspaceInt = static_cast<int32_t>(destinationDataspace);
58
59 std::string program;
60 // Define required uniforms
61 program.append(R"(
62 uniform float in_libtonemap_displayMaxLuminance;
63 uniform float in_libtonemap_inputMaxLuminance;
64 )");
65 switch (sourceDataspaceInt & kTransferMask) {
66 case kTransferST2084:
67 case kTransferHLG:
68 switch (destinationDataspaceInt & kTransferMask) {
69 case kTransferST2084:
70 program.append(R"(
71 float libtonemap_ToneMapTargetNits(vec3 xyz) {
72 return xyz.y;
73 }
74 )");
75 break;
76 case kTransferHLG:
77 // PQ has a wider luminance range (10,000 nits vs. 1,000 nits) than HLG, so
78 // we'll clamp the luminance range in case we're mapping from PQ input to
79 // HLG output.
80 program.append(R"(
81 float libtonemap_ToneMapTargetNits(vec3 xyz) {
82 return clamp(xyz.y, 0.0, 1000.0);
83 }
84 )");
85 break;
86 default:
87 // Here we're mapping from HDR to SDR content, so interpolate using a
88 // Hermitian polynomial onto the smaller luminance range.
89 program.append(R"(
90 float libtonemap_ToneMapTargetNits(vec3 xyz) {
91 float maxInLumi = in_libtonemap_inputMaxLuminance;
92 float maxOutLumi = in_libtonemap_displayMaxLuminance;
93
94 float nits = xyz.y;
95
96 // if the max input luminance is less than what we can
97 // output then no tone mapping is needed as all color
98 // values will be in range.
99 if (maxInLumi <= maxOutLumi) {
100 return xyz.y;
101 } else {
102
103 // three control points
104 const float x0 = 10.0;
105 const float y0 = 17.0;
106 float x1 = maxOutLumi * 0.75;
107 float y1 = x1;
108 float x2 = x1 + (maxInLumi - x1) / 2.0;
109 float y2 = y1 + (maxOutLumi - y1) * 0.75;
110
111 // horizontal distances between the last three
112 // control points
113 float h12 = x2 - x1;
114 float h23 = maxInLumi - x2;
115 // tangents at the last three control points
116 float m1 = (y2 - y1) / h12;
117 float m3 = (maxOutLumi - y2) / h23;
118 float m2 = (m1 + m3) / 2.0;
119
120 if (nits < x0) {
121 // scale [0.0, x0] to [0.0, y0] linearly
122 float slope = y0 / x0;
123 return nits * slope;
124 } else if (nits < x1) {
125 // scale [x0, x1] to [y0, y1] linearly
126 float slope = (y1 - y0) / (x1 - x0);
127 nits = y0 + (nits - x0) * slope;
128 } else if (nits < x2) {
129 // scale [x1, x2] to [y1, y2] using Hermite interp
130 float t = (nits - x1) / h12;
131 nits = (y1 * (1.0 + 2.0 * t) + h12 * m1 * t) *
132 (1.0 - t) * (1.0 - t) +
133 (y2 * (3.0 - 2.0 * t) +
134 h12 * m2 * (t - 1.0)) * t * t;
135 } else {
136 // scale [x2, maxInLumi] to [y2, maxOutLumi] using
137 // Hermite interp
138 float t = (nits - x2) / h23;
139 nits = (y2 * (1.0 + 2.0 * t) + h23 * m2 * t) *
140 (1.0 - t) * (1.0 - t) + (maxOutLumi *
141 (3.0 - 2.0 * t) + h23 * m3 *
142 (t - 1.0)) * t * t;
143 }
144 }
145
146 return nits;
147 }
148 )");
149 break;
150 }
151 break;
152 default:
153 switch (destinationDataspaceInt & kTransferMask) {
154 case kTransferST2084:
155 case kTransferHLG:
156 // Map from SDR onto an HDR output buffer
157 // Here we use a polynomial curve to map from [0, displayMaxLuminance] onto
158 // [0, maxOutLumi] which is hard-coded to be 3000 nits.
159 program.append(R"(
160 float libtonemap_ToneMapTargetNits(vec3 xyz) {
161 const float maxOutLumi = 3000.0;
162
163 const float x0 = 5.0;
164 const float y0 = 2.5;
165 float x1 = in_libtonemap_displayMaxLuminance * 0.7;
166 float y1 = maxOutLumi * 0.15;
167 float x2 = in_libtonemap_displayMaxLuminance * 0.9;
168 float y2 = maxOutLumi * 0.45;
169 float x3 = in_libtonemap_displayMaxLuminance;
170 float y3 = maxOutLumi;
171
172 float c1 = y1 / 3.0;
173 float c2 = y2 / 2.0;
174 float c3 = y3 / 1.5;
175
176 float nits = xyz.y;
177
178 if (nits <= x0) {
179 // scale [0.0, x0] to [0.0, y0] linearly
180 float slope = y0 / x0;
181 return nits * slope;
182 } else if (nits <= x1) {
183 // scale [x0, x1] to [y0, y1] using a curve
184 float t = (nits - x0) / (x1 - x0);
185 nits = (1.0 - t) * (1.0 - t) * y0 +
186 2.0 * (1.0 - t) * t * c1 + t * t * y1;
187 } else if (nits <= x2) {
188 // scale [x1, x2] to [y1, y2] using a curve
189 float t = (nits - x1) / (x2 - x1);
190 nits = (1.0 - t) * (1.0 - t) * y1 +
191 2.0 * (1.0 - t) * t * c2 + t * t * y2;
192 } else {
193 // scale [x2, x3] to [y2, y3] using a curve
194 float t = (nits - x2) / (x3 - x2);
195 nits = (1.0 - t) * (1.0 - t) * y2 +
196 2.0 * (1.0 - t) * t * c3 + t * t * y3;
197 }
198
199 return nits;
200 }
201 )");
202 break;
203 default:
204 // For completeness, this is tone-mapping from SDR to SDR, where this is
205 // just a no-op.
206 program.append(R"(
207 float libtonemap_ToneMapTargetNits(vec3 xyz) {
208 return xyz.y;
209 }
210 )");
211 break;
212 }
213 break;
214 }
215
216 program.append(R"(
217 float libtonemap_LookupTonemapGain(vec3 linearRGB, vec3 xyz) {
218 if (xyz.y <= 0.0) {
219 return 1.0;
220 }
221 return libtonemap_ToneMapTargetNits(xyz) / xyz.y;
222 }
223 )");
224 return program;
225 }
226
227 std::vector<ShaderUniform> generateShaderSkSLUniforms(const Metadata& metadata) override {
228 std::vector<ShaderUniform> uniforms;
229
230 uniforms.reserve(2);
231
232 uniforms.push_back({.name = "in_libtonemap_displayMaxLuminance",
233 .value = buildUniformValue<float>(metadata.displayMaxLuminance)});
234 uniforms.push_back({.name = "in_libtonemap_inputMaxLuminance",
235 .value = buildUniformValue<float>(metadata.contentMaxLuminance)});
Alec Mouri5184f412021-10-14 18:13:49 -0700236 return uniforms;
237 }
Alec Mouri4049b532021-10-15 20:59:33 -0700238
Alec Mouri196b0f22022-03-04 22:13:48 +0000239 std::vector<Gain> lookupTonemapGain(
Alec Mouri4049b532021-10-15 20:59:33 -0700240 aidl::android::hardware::graphics::common::Dataspace sourceDataspace,
241 aidl::android::hardware::graphics::common::Dataspace destinationDataspace,
Alec Mouri196b0f22022-03-04 22:13:48 +0000242 const std::vector<Color>& colors, const Metadata& metadata) override {
243 std::vector<Gain> gains;
244 gains.reserve(colors.size());
Alec Mouri4049b532021-10-15 20:59:33 -0700245
Alec Mouri196b0f22022-03-04 22:13:48 +0000246 for (const auto [_, xyz] : colors) {
247 if (xyz.y <= 0.0) {
248 gains.push_back(1.0);
249 continue;
250 }
251 const int32_t sourceDataspaceInt = static_cast<int32_t>(sourceDataspace);
252 const int32_t destinationDataspaceInt = static_cast<int32_t>(destinationDataspace);
Alec Mouri4049b532021-10-15 20:59:33 -0700253
Alec Mouri196b0f22022-03-04 22:13:48 +0000254 double targetNits = 0.0;
255 switch (sourceDataspaceInt & kTransferMask) {
256 case kTransferST2084:
257 case kTransferHLG:
258 switch (destinationDataspaceInt & kTransferMask) {
259 case kTransferST2084:
260 targetNits = xyz.y;
261 break;
262 case kTransferHLG:
263 // PQ has a wider luminance range (10,000 nits vs. 1,000 nits) than HLG,
264 // so we'll clamp the luminance range in case we're mapping from PQ
265 // input to HLG output.
266 targetNits = std::clamp(xyz.y, 0.0f, 1000.0f);
267 break;
268 default:
269 // Here we're mapping from HDR to SDR content, so interpolate using a
270 // Hermitian polynomial onto the smaller luminance range.
Alec Mouri4049b532021-10-15 20:59:33 -0700271
Alec Mouri196b0f22022-03-04 22:13:48 +0000272 targetNits = xyz.y;
273 // if the max input luminance is less than what we can output then
274 // no tone mapping is needed as all color values will be in range.
275 if (metadata.contentMaxLuminance > metadata.displayMaxLuminance) {
276 // three control points
277 const double x0 = 10.0;
278 const double y0 = 17.0;
279 double x1 = metadata.displayMaxLuminance * 0.75;
280 double y1 = x1;
281 double x2 = x1 + (metadata.contentMaxLuminance - x1) / 2.0;
282 double y2 = y1 + (metadata.displayMaxLuminance - y1) * 0.75;
Alec Mouri4049b532021-10-15 20:59:33 -0700283
Alec Mouri196b0f22022-03-04 22:13:48 +0000284 // horizontal distances between the last three control points
285 double h12 = x2 - x1;
286 double h23 = metadata.contentMaxLuminance - x2;
287 // tangents at the last three control points
288 double m1 = (y2 - y1) / h12;
289 double m3 = (metadata.displayMaxLuminance - y2) / h23;
290 double m2 = (m1 + m3) / 2.0;
291
292 if (targetNits < x0) {
293 // scale [0.0, x0] to [0.0, y0] linearly
294 double slope = y0 / x0;
295 targetNits *= slope;
296 } else if (targetNits < x1) {
297 // scale [x0, x1] to [y0, y1] linearly
298 double slope = (y1 - y0) / (x1 - x0);
299 targetNits = y0 + (targetNits - x0) * slope;
300 } else if (targetNits < x2) {
301 // scale [x1, x2] to [y1, y2] using Hermite interp
302 double t = (targetNits - x1) / h12;
303 targetNits = (y1 * (1.0 + 2.0 * t) + h12 * m1 * t) * (1.0 - t) *
304 (1.0 - t) +
305 (y2 * (3.0 - 2.0 * t) + h12 * m2 * (t - 1.0)) * t * t;
306 } else {
307 // scale [x2, maxInLumi] to [y2, maxOutLumi] using Hermite
308 // interp
309 double t = (targetNits - x2) / h23;
310 targetNits = (y2 * (1.0 + 2.0 * t) + h23 * m2 * t) * (1.0 - t) *
311 (1.0 - t) +
312 (metadata.displayMaxLuminance * (3.0 - 2.0 * t) +
313 h23 * m3 * (t - 1.0)) *
314 t * t;
315 }
316 }
317 break;
318 }
319 break;
320 default:
321 // source is SDR
322 switch (destinationDataspaceInt & kTransferMask) {
323 case kTransferST2084:
324 case kTransferHLG: {
325 // Map from SDR onto an HDR output buffer
326 // Here we use a polynomial curve to map from [0, displayMaxLuminance]
327 // onto [0, maxOutLumi] which is hard-coded to be 3000 nits.
328 const double maxOutLumi = 3000.0;
329
330 double x0 = 5.0;
331 double y0 = 2.5;
332 double x1 = metadata.displayMaxLuminance * 0.7;
333 double y1 = maxOutLumi * 0.15;
334 double x2 = metadata.displayMaxLuminance * 0.9;
335 double y2 = maxOutLumi * 0.45;
336 double x3 = metadata.displayMaxLuminance;
337 double y3 = maxOutLumi;
338
339 double c1 = y1 / 3.0;
340 double c2 = y2 / 2.0;
341 double c3 = y3 / 1.5;
342
343 targetNits = xyz.y;
344
345 if (targetNits <= x0) {
Alec Mouri4049b532021-10-15 20:59:33 -0700346 // scale [0.0, x0] to [0.0, y0] linearly
347 double slope = y0 / x0;
348 targetNits *= slope;
Alec Mouri196b0f22022-03-04 22:13:48 +0000349 } else if (targetNits <= x1) {
350 // scale [x0, x1] to [y0, y1] using a curve
351 double t = (targetNits - x0) / (x1 - x0);
352 targetNits = (1.0 - t) * (1.0 - t) * y0 + 2.0 * (1.0 - t) * t * c1 +
353 t * t * y1;
354 } else if (targetNits <= x2) {
355 // scale [x1, x2] to [y1, y2] using a curve
356 double t = (targetNits - x1) / (x2 - x1);
357 targetNits = (1.0 - t) * (1.0 - t) * y1 + 2.0 * (1.0 - t) * t * c2 +
358 t * t * y2;
Alec Mouri4049b532021-10-15 20:59:33 -0700359 } else {
Alec Mouri196b0f22022-03-04 22:13:48 +0000360 // scale [x2, x3] to [y2, y3] using a curve
361 double t = (targetNits - x2) / (x3 - x2);
362 targetNits = (1.0 - t) * (1.0 - t) * y2 + 2.0 * (1.0 - t) * t * c3 +
363 t * t * y3;
Alec Mouri4049b532021-10-15 20:59:33 -0700364 }
Alec Mouri196b0f22022-03-04 22:13:48 +0000365 } break;
366 default:
367 // For completeness, this is tone-mapping from SDR to SDR, where this is
368 // just a no-op.
369 targetNits = xyz.y;
370 break;
371 }
372 }
373 gains.push_back(targetNits / xyz.y);
Alec Mouri4049b532021-10-15 20:59:33 -0700374 }
Alec Mouri196b0f22022-03-04 22:13:48 +0000375 return gains;
Alec Mouri4049b532021-10-15 20:59:33 -0700376 }
Alec Mouri5184f412021-10-14 18:13:49 -0700377};
Alec Mouri465b2962021-10-08 16:22:21 -0700378
Alec Mouri5184f412021-10-14 18:13:49 -0700379class ToneMapper13 : public ToneMapper {
Alec Mouri4049b532021-10-15 20:59:33 -0700380private:
381 double OETF_ST2084(double nits) {
382 nits = nits / 10000.0;
383 double m1 = (2610.0 / 4096.0) / 4.0;
384 double m2 = (2523.0 / 4096.0) * 128.0;
385 double c1 = (3424.0 / 4096.0);
386 double c2 = (2413.0 / 4096.0) * 32.0;
387 double c3 = (2392.0 / 4096.0) * 32.0;
388
389 double tmp = std::pow(nits, m1);
390 tmp = (c1 + c2 * tmp) / (1.0 + c3 * tmp);
391 return std::pow(tmp, m2);
392 }
393
394 double OETF_HLG(double nits) {
395 nits = nits / 1000.0;
396 const double a = 0.17883277;
397 const double b = 0.28466892;
398 const double c = 0.55991073;
399 return nits <= 1.0 / 12.0 ? std::sqrt(3.0 * nits) : a * std::log(12.0 * nits - b) + c;
400 }
401
Alec Mouri5184f412021-10-14 18:13:49 -0700402public:
403 std::string generateTonemapGainShaderSkSL(
404 aidl::android::hardware::graphics::common::Dataspace sourceDataspace,
405 aidl::android::hardware::graphics::common::Dataspace destinationDataspace) override {
406 const int32_t sourceDataspaceInt = static_cast<int32_t>(sourceDataspace);
407 const int32_t destinationDataspaceInt = static_cast<int32_t>(destinationDataspace);
408
409 std::string program;
410 // Input uniforms
411 program.append(R"(
412 uniform float in_libtonemap_displayMaxLuminance;
413 uniform float in_libtonemap_inputMaxLuminance;
414 )");
415 switch (sourceDataspaceInt & kTransferMask) {
416 case kTransferST2084:
Alec Mouri5184f412021-10-14 18:13:49 -0700417 switch (destinationDataspaceInt & kTransferMask) {
418 case kTransferST2084:
419 program.append(R"(
420 float libtonemap_ToneMapTargetNits(float maxRGB) {
421 return maxRGB;
422 }
423 )");
424 break;
425 case kTransferHLG:
426 // PQ has a wider luminance range (10,000 nits vs. 1,000 nits) than HLG, so
427 // we'll clamp the luminance range in case we're mapping from PQ input to
428 // HLG output.
429 program.append(R"(
430 float libtonemap_ToneMapTargetNits(float maxRGB) {
431 return clamp(maxRGB, 0.0, 1000.0);
432 }
433 )");
434 break;
435
436 default:
Alec Mouri5184f412021-10-14 18:13:49 -0700437 program.append(R"(
Alec Mouri5a493722022-01-26 16:43:02 -0800438 float libtonemap_OETFTone(float channel) {
439 channel = channel / 10000.0;
440 float m1 = (2610.0 / 4096.0) / 4.0;
441 float m2 = (2523.0 / 4096.0) * 128.0;
442 float c1 = (3424.0 / 4096.0);
443 float c2 = (2413.0 / 4096.0) * 32.0;
444 float c3 = (2392.0 / 4096.0) * 32.0;
445
446 float tmp = pow(channel, float(m1));
447 tmp = (c1 + c2 * tmp) / (1.0 + c3 * tmp);
448 return pow(tmp, float(m2));
449 }
450
Alec Mouri5184f412021-10-14 18:13:49 -0700451 float libtonemap_ToneMapTargetNits(float maxRGB) {
452 float maxInLumi = in_libtonemap_inputMaxLuminance;
453 float maxOutLumi = in_libtonemap_displayMaxLuminance;
454
455 float nits = maxRGB;
456
457 float x1 = maxOutLumi * 0.65;
458 float y1 = x1;
459
460 float x3 = maxInLumi;
461 float y3 = maxOutLumi;
462
463 float x2 = x1 + (x3 - x1) * 4.0 / 17.0;
464 float y2 = maxOutLumi * 0.9;
465
466 float greyNorm1 = libtonemap_OETFTone(x1);
467 float greyNorm2 = libtonemap_OETFTone(x2);
468 float greyNorm3 = libtonemap_OETFTone(x3);
469
470 float slope1 = 0;
471 float slope2 = (y2 - y1) / (greyNorm2 - greyNorm1);
472 float slope3 = (y3 - y2 ) / (greyNorm3 - greyNorm2);
473
474 if (nits < x1) {
475 return nits;
476 }
477
478 if (nits > maxInLumi) {
479 return maxOutLumi;
480 }
481
482 float greyNits = libtonemap_OETFTone(nits);
483
484 if (greyNits <= greyNorm2) {
485 nits = (greyNits - greyNorm2) * slope2 + y2;
486 } else if (greyNits <= greyNorm3) {
487 nits = (greyNits - greyNorm3) * slope3 + y3;
488 } else {
489 nits = maxOutLumi;
490 }
491
492 return nits;
493 }
494 )");
495 break;
496 }
497 break;
Alec Mouri5a493722022-01-26 16:43:02 -0800498 case kTransferHLG:
499 switch (destinationDataspaceInt & kTransferMask) {
500 // HLG -> HDR does not tone-map at all
501 case kTransferST2084:
502 case kTransferHLG:
503 program.append(R"(
504 float libtonemap_ToneMapTargetNits(float maxRGB) {
505 return maxRGB;
506 }
507 )");
508 break;
509 default:
510 // libshaders follows BT2100 OOTF, but with a nominal peak display luminance
511 // of 1000 nits. Renormalize to max display luminance if we're tone-mapping
512 // down to SDR, as libshaders normalizes all SDR output from [0,
513 // maxDisplayLumins] -> [0, 1]
514 program.append(R"(
515 float libtonemap_ToneMapTargetNits(float maxRGB) {
516 return maxRGB * in_libtonemap_displayMaxLuminance / 1000.0;
517 }
518 )");
519 break;
520 }
521 break;
Alec Mouri5184f412021-10-14 18:13:49 -0700522 default:
523 // Inverse tone-mapping and SDR-SDR mapping is not supported.
524 program.append(R"(
525 float libtonemap_ToneMapTargetNits(float maxRGB) {
526 return maxRGB;
527 }
528 )");
529 break;
530 }
531
532 program.append(R"(
533 float libtonemap_LookupTonemapGain(vec3 linearRGB, vec3 xyz) {
534 float maxRGB = max(linearRGB.r, max(linearRGB.g, linearRGB.b));
535 if (maxRGB <= 0.0) {
536 return 1.0;
537 }
538 return libtonemap_ToneMapTargetNits(maxRGB) / maxRGB;
539 }
540 )");
541 return program;
542 }
543
544 std::vector<ShaderUniform> generateShaderSkSLUniforms(const Metadata& metadata) override {
545 // Hardcode the max content luminance to a "reasonable" level
546 static const constexpr float kContentMaxLuminance = 4000.f;
547 std::vector<ShaderUniform> uniforms;
548 uniforms.reserve(2);
549 uniforms.push_back({.name = "in_libtonemap_displayMaxLuminance",
550 .value = buildUniformValue<float>(metadata.displayMaxLuminance)});
551 uniforms.push_back({.name = "in_libtonemap_inputMaxLuminance",
552 .value = buildUniformValue<float>(kContentMaxLuminance)});
Alec Mouri465b2962021-10-08 16:22:21 -0700553 return uniforms;
554 }
Alec Mouri4049b532021-10-15 20:59:33 -0700555
Alec Mouri196b0f22022-03-04 22:13:48 +0000556 std::vector<Gain> lookupTonemapGain(
Alec Mouri4049b532021-10-15 20:59:33 -0700557 aidl::android::hardware::graphics::common::Dataspace sourceDataspace,
558 aidl::android::hardware::graphics::common::Dataspace destinationDataspace,
Alec Mouri196b0f22022-03-04 22:13:48 +0000559 const std::vector<Color>& colors, const Metadata& metadata) override {
560 std::vector<Gain> gains;
561 gains.reserve(colors.size());
Alec Mouri4049b532021-10-15 20:59:33 -0700562
Alec Mouri196b0f22022-03-04 22:13:48 +0000563 // Precompute constants for HDR->SDR tonemapping parameters
564 constexpr double maxInLumi = 4000;
565 const double maxOutLumi = metadata.displayMaxLuminance;
Alec Mouri4049b532021-10-15 20:59:33 -0700566
Alec Mouri196b0f22022-03-04 22:13:48 +0000567 const double x1 = maxOutLumi * 0.65;
568 const double y1 = x1;
Alec Mouri4049b532021-10-15 20:59:33 -0700569
Alec Mouri196b0f22022-03-04 22:13:48 +0000570 const double x3 = maxInLumi;
571 const double y3 = maxOutLumi;
Alec Mouri4049b532021-10-15 20:59:33 -0700572
Alec Mouri196b0f22022-03-04 22:13:48 +0000573 const double x2 = x1 + (x3 - x1) * 4.0 / 17.0;
574 const double y2 = maxOutLumi * 0.9;
Alec Mouri4049b532021-10-15 20:59:33 -0700575
Alec Mouri196b0f22022-03-04 22:13:48 +0000576 const double greyNorm1 = OETF_ST2084(x1);
577 const double greyNorm2 = OETF_ST2084(x2);
578 const double greyNorm3 = OETF_ST2084(x3);
Alec Mouri4049b532021-10-15 20:59:33 -0700579
Alec Mouri196b0f22022-03-04 22:13:48 +0000580 const double slope2 = (y2 - y1) / (greyNorm2 - greyNorm1);
581 const double slope3 = (y3 - y2) / (greyNorm3 - greyNorm2);
Alec Mouri4049b532021-10-15 20:59:33 -0700582
Alec Mouri196b0f22022-03-04 22:13:48 +0000583 for (const auto [linearRGB, _] : colors) {
584 double maxRGB = std::max({linearRGB.r, linearRGB.g, linearRGB.b});
Alec Mouri4049b532021-10-15 20:59:33 -0700585
Alec Mouri196b0f22022-03-04 22:13:48 +0000586 if (maxRGB <= 0.0) {
587 gains.push_back(1.0);
588 continue;
589 }
Alec Mouri4049b532021-10-15 20:59:33 -0700590
Alec Mouri196b0f22022-03-04 22:13:48 +0000591 const int32_t sourceDataspaceInt = static_cast<int32_t>(sourceDataspace);
592 const int32_t destinationDataspaceInt = static_cast<int32_t>(destinationDataspace);
Alec Mouri4049b532021-10-15 20:59:33 -0700593
Alec Mouri196b0f22022-03-04 22:13:48 +0000594 double targetNits = 0.0;
595 switch (sourceDataspaceInt & kTransferMask) {
596 case kTransferST2084:
597 switch (destinationDataspaceInt & kTransferMask) {
598 case kTransferST2084:
599 targetNits = maxRGB;
Alec Mouri4049b532021-10-15 20:59:33 -0700600 break;
Alec Mouri196b0f22022-03-04 22:13:48 +0000601 case kTransferHLG:
602 // PQ has a wider luminance range (10,000 nits vs. 1,000 nits) than HLG,
603 // so we'll clamp the luminance range in case we're mapping from PQ
604 // input to HLG output.
605 targetNits = std::clamp(maxRGB, 0.0, 1000.0);
Alec Mouri4049b532021-10-15 20:59:33 -0700606 break;
Alec Mouri196b0f22022-03-04 22:13:48 +0000607 default:
608 targetNits = maxRGB;
609 if (targetNits < x1) {
610 break;
611 }
Alec Mouri4049b532021-10-15 20:59:33 -0700612
Alec Mouri196b0f22022-03-04 22:13:48 +0000613 if (targetNits > maxInLumi) {
614 targetNits = maxOutLumi;
615 break;
616 }
Alec Mouri4049b532021-10-15 20:59:33 -0700617
Alec Mouri196b0f22022-03-04 22:13:48 +0000618 const double greyNits = OETF_ST2084(targetNits);
619
620 if (greyNits <= greyNorm2) {
621 targetNits = (greyNits - greyNorm2) * slope2 + y2;
622 } else if (greyNits <= greyNorm3) {
623 targetNits = (greyNits - greyNorm3) * slope3 + y3;
624 } else {
625 targetNits = maxOutLumi;
626 }
627 break;
628 }
629 break;
630 case kTransferHLG:
631 switch (destinationDataspaceInt & kTransferMask) {
632 case kTransferST2084:
633 case kTransferHLG:
634 targetNits = maxRGB;
635 break;
636 default:
637 targetNits = maxRGB * metadata.displayMaxLuminance / 1000.0;
638 break;
639 }
640 break;
641 default:
642 targetNits = maxRGB;
643 break;
644 }
645
646 gains.push_back(targetNits / maxRGB);
Alec Mouri4049b532021-10-15 20:59:33 -0700647 }
Alec Mouri196b0f22022-03-04 22:13:48 +0000648 return gains;
Alec Mouri4049b532021-10-15 20:59:33 -0700649 }
Alec Mouri465b2962021-10-08 16:22:21 -0700650};
651
652} // namespace
653
654ToneMapper* getToneMapper() {
655 static std::once_flag sOnce;
656 static std::unique_ptr<ToneMapper> sToneMapper;
657
658 std::call_once(sOnce, [&] {
659 switch (kToneMapAlgorithm) {
660 case ToneMapAlgorithm::AndroidO:
661 sToneMapper = std::unique_ptr<ToneMapper>(new ToneMapperO());
662 break;
Alec Mouri5184f412021-10-14 18:13:49 -0700663 case ToneMapAlgorithm::Android13:
664 sToneMapper = std::unique_ptr<ToneMapper>(new ToneMapper13());
Alec Mouri465b2962021-10-08 16:22:21 -0700665 }
666 });
667
668 return sToneMapper.get();
669}
Alec Mouri196b0f22022-03-04 22:13:48 +0000670} // namespace android::tonemap