1 /*
2 * Copyright 2022 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 #ifndef ULTRAHDR_GAINMAPMATH_H
18 #define ULTRAHDR_GAINMAPMATH_H
19
20 #include <array>
21 #include <cmath>
22 #include <cstring>
23
24 #include "ultrahdr_api.h"
25 #include "ultrahdr/ultrahdrcommon.h"
26 #include "ultrahdr/ultrahdr.h"
27 #include "ultrahdr/jpegr.h"
28
29 #if (defined(UHDR_ENABLE_INTRINSICS) && (defined(__ARM_NEON__) || defined(__ARM_NEON)))
30 #include <arm_neon.h>
31 #endif
32
33 #define CLIP3(x, min, max) ((x) < (min)) ? (min) : ((x) > (max)) ? (max) : (x)
34
35 namespace ultrahdr {
36
37 ////////////////////////////////////////////////////////////////////////////////
38 // Framework
39
40 // This aligns with the suggested default reference diffuse white from
41 // ISO/TS 22028-5
42 const float kSdrWhiteNits = 203.0f;
43 const float kHlgMaxNits = 1000.0f;
44 const float kPqMaxNits = 10000.0f;
45
46 static const float kMaxPixelFloat = 1.0f;
47
48 // Describes the tone-mapping operation & gain-map encoding parameters.
49 const float kHlgHeadroom = 1000.0f / 203.0f;
50
51 struct Color {
52 union {
53 struct {
54 float r;
55 float g;
56 float b;
57 };
58 struct {
59 float y;
60 float u;
61 float v;
62 };
63 };
64 };
65
66 typedef Color (*ColorTransformFn)(Color);
67 typedef float (*ColorCalculationFn)(Color);
68
clampPixelFloat(float value)69 static inline float clampPixelFloat(float value) {
70 return (value < 0.0f) ? 0.0f : (value > kMaxPixelFloat) ? kMaxPixelFloat : value;
71 }
clampPixelFloat(Color e)72 static inline Color clampPixelFloat(Color e) {
73 return {{{clampPixelFloat(e.r), clampPixelFloat(e.g), clampPixelFloat(e.b)}}};
74 }
75
76 // A transfer function mapping encoded values to linear values,
77 // represented by this 7-parameter piecewise function:
78 //
79 // linear = sign(encoded) * (c*|encoded| + f) , 0 <= |encoded| < d
80 // = sign(encoded) * ((a*|encoded| + b)^g + e), d <= |encoded|
81 //
82 // (A simple gamma transfer function sets g to gamma and a to 1.)
83 typedef struct TransferFunction {
84 float g, a, b, c, d, e, f;
85 } TransferFunction;
86
87 static constexpr TransferFunction kSRGB_TransFun = {
88 2.4f, (float)(1 / 1.055), (float)(0.055 / 1.055), (float)(1 / 12.92), 0.04045f, 0.0f, 0.0f};
89
90 static constexpr TransferFunction kLinear_TransFun = {1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f};
91
92 inline Color operator+=(Color& lhs, const Color& rhs) {
93 lhs.r += rhs.r;
94 lhs.g += rhs.g;
95 lhs.b += rhs.b;
96 return lhs;
97 }
98 inline Color operator-=(Color& lhs, const Color& rhs) {
99 lhs.r -= rhs.r;
100 lhs.g -= rhs.g;
101 lhs.b -= rhs.b;
102 return lhs;
103 }
104
105 inline Color operator+(const Color& lhs, const Color& rhs) {
106 Color temp = lhs;
107 return temp += rhs;
108 }
109 inline Color operator-(const Color& lhs, const Color& rhs) {
110 Color temp = lhs;
111 return temp -= rhs;
112 }
113
114 inline Color operator+=(Color& lhs, const float rhs) {
115 lhs.r += rhs;
116 lhs.g += rhs;
117 lhs.b += rhs;
118 return lhs;
119 }
120 inline Color operator-=(Color& lhs, const float rhs) {
121 lhs.r -= rhs;
122 lhs.g -= rhs;
123 lhs.b -= rhs;
124 return lhs;
125 }
126 inline Color operator*=(Color& lhs, const float rhs) {
127 lhs.r *= rhs;
128 lhs.g *= rhs;
129 lhs.b *= rhs;
130 return lhs;
131 }
132 inline Color operator/=(Color& lhs, const float rhs) {
133 lhs.r /= rhs;
134 lhs.g /= rhs;
135 lhs.b /= rhs;
136 return lhs;
137 }
138
139 inline Color operator+(const Color& lhs, const float rhs) {
140 Color temp = lhs;
141 return temp += rhs;
142 }
143 inline Color operator-(const Color& lhs, const float rhs) {
144 Color temp = lhs;
145 return temp -= rhs;
146 }
147 inline Color operator*(const Color& lhs, const float rhs) {
148 Color temp = lhs;
149 return temp *= rhs;
150 }
151 inline Color operator/(const Color& lhs, const float rhs) {
152 Color temp = lhs;
153 return temp /= rhs;
154 }
155
156 union FloatUIntUnion {
157 uint32_t fUInt;
158 float fFloat;
159 };
160
floatToHalf(float f)161 inline uint16_t floatToHalf(float f) {
162 FloatUIntUnion floatUnion;
163 floatUnion.fFloat = f;
164 // round-to-nearest-even: add last bit after truncated mantissa
165 const uint32_t b = floatUnion.fUInt + 0x00001000;
166
167 const int32_t e = (b & 0x7F800000) >> 23; // exponent
168 const uint32_t m = b & 0x007FFFFF; // mantissa
169
170 // sign : normalized : denormalized : saturate
171 return (b & 0x80000000) >> 16 | (e > 112) * ((((e - 112) << 10) & 0x7C00) | m >> 13) |
172 ((e < 113) & (e > 101)) * ((((0x007FF000 + m) >> (125 - e)) + 1) >> 1) |
173 (e > 143) * 0x7FFF;
174 }
175
176 constexpr int32_t kGainFactorPrecision = 10;
177 constexpr int32_t kGainFactorNumEntries = 1 << kGainFactorPrecision;
178 struct GainLUT {
GainLUTGainLUT179 GainLUT(ultrahdr_metadata_ptr metadata) {
180 for (int32_t idx = 0; idx < kGainFactorNumEntries; idx++) {
181 float value = static_cast<float>(idx) / static_cast<float>(kGainFactorNumEntries - 1);
182 float logBoost = log2(metadata->minContentBoost) * (1.0f - value) +
183 log2(metadata->maxContentBoost) * value;
184 mGainTable[idx] = exp2(logBoost);
185 }
186 }
187
GainLUTGainLUT188 GainLUT(ultrahdr_metadata_ptr metadata, float displayBoost) {
189 float boostFactor = displayBoost > 0 ? displayBoost / metadata->maxContentBoost : 1.0f;
190 for (int32_t idx = 0; idx < kGainFactorNumEntries; idx++) {
191 float value = static_cast<float>(idx) / static_cast<float>(kGainFactorNumEntries - 1);
192 float logBoost = log2(metadata->minContentBoost) * (1.0f - value) +
193 log2(metadata->maxContentBoost) * value;
194 mGainTable[idx] = exp2(logBoost * boostFactor);
195 }
196 }
197
~GainLUTGainLUT198 ~GainLUT() {}
199
getGainFactorGainLUT200 float getGainFactor(float gain) {
201 int32_t idx = static_cast<int32_t>(gain * (kGainFactorNumEntries - 1) + 0.5);
202 // TODO() : Remove once conversion modules have appropriate clamping in place
203 idx = CLIP3(idx, 0, kGainFactorNumEntries - 1);
204 return mGainTable[idx];
205 }
206
207 private:
208 float mGainTable[kGainFactorNumEntries];
209 };
210
211 struct ShepardsIDW {
ShepardsIDWShepardsIDW212 ShepardsIDW(int mapScaleFactor) : mMapScaleFactor{mapScaleFactor} {
213 const int size = mMapScaleFactor * mMapScaleFactor * 4;
214 mWeights = new float[size];
215 mWeightsNR = new float[size];
216 mWeightsNB = new float[size];
217 mWeightsC = new float[size];
218 fillShepardsIDW(mWeights, 1, 1);
219 fillShepardsIDW(mWeightsNR, 0, 1);
220 fillShepardsIDW(mWeightsNB, 1, 0);
221 fillShepardsIDW(mWeightsC, 0, 0);
222 }
~ShepardsIDWShepardsIDW223 ~ShepardsIDW() {
224 delete[] mWeights;
225 delete[] mWeightsNR;
226 delete[] mWeightsNB;
227 delete[] mWeightsC;
228 }
229
230 int mMapScaleFactor;
231 // Image :-
232 // p00 p01 p02 p03 p04 p05 p06 p07
233 // p10 p11 p12 p13 p14 p15 p16 p17
234 // p20 p21 p22 p23 p24 p25 p26 p27
235 // p30 p31 p32 p33 p34 p35 p36 p37
236 // p40 p41 p42 p43 p44 p45 p46 p47
237 // p50 p51 p52 p53 p54 p55 p56 p57
238 // p60 p61 p62 p63 p64 p65 p66 p67
239 // p70 p71 p72 p73 p74 p75 p76 p77
240
241 // Gain Map (for 4 scale factor) :-
242 // m00 p01
243 // m10 m11
244
245 // Gain sample of curr 4x4, right 4x4, bottom 4x4, bottom right 4x4 are used during
246 // reconstruction. hence table weight size is 4.
247 float* mWeights;
248 // TODO: check if its ok to mWeights at places
249 float* mWeightsNR; // no right
250 float* mWeightsNB; // no bottom
251 float* mWeightsC; // no right & bottom
252
253 float euclideanDistance(float x1, float x2, float y1, float y2);
254 void fillShepardsIDW(float* weights, int incR, int incB);
255 };
256
257 ////////////////////////////////////////////////////////////////////////////////
258 // sRGB transformations
259 // NOTE: sRGB has the same color primaries as BT.709, but different transfer
260 // function. For this reason, all sRGB transformations here apply to BT.709,
261 // except for those concerning transfer functions.
262
263 /*
264 * Calculate the luminance of a linear RGB sRGB pixel, according to
265 * IEC 61966-2-1/Amd 1:2003.
266 *
267 * [0.0, 1.0] range in and out.
268 */
269 float srgbLuminance(Color e);
270
271 /*
272 * Convert from OETF'd srgb RGB to YUV, according to ITU-R BT.709-6.
273 *
274 * BT.709 YUV<->RGB matrix is used to match expectations for DataSpace.
275 */
276 Color srgbRgbToYuv(Color e_gamma);
277
278 /*
279 * Convert from OETF'd srgb YUV to RGB, according to ITU-R BT.709-6.
280 *
281 * BT.709 YUV<->RGB matrix is used to match expectations for DataSpace.
282 */
283 Color srgbYuvToRgb(Color e_gamma);
284
285 /*
286 * Convert from srgb to linear, according to IEC 61966-2-1/Amd 1:2003.
287 *
288 * [0.0, 1.0] range in and out.
289 */
290 float srgbInvOetf(float e_gamma);
291 Color srgbInvOetf(Color e_gamma);
292 float srgbInvOetfLUT(float e_gamma);
293 Color srgbInvOetfLUT(Color e_gamma);
294
295 /*
296 * Convert from linear to srgb, according to IEC 61966-2-1/Amd 1:2003.
297 *
298 * [0.0, 1.0] range in and out.
299 */
300 float srgbOetf(float e);
301 Color srgbOetf(Color e);
302
303 constexpr int32_t kSrgbInvOETFPrecision = 10;
304 constexpr int32_t kSrgbInvOETFNumEntries = 1 << kSrgbInvOETFPrecision;
305
306 ////////////////////////////////////////////////////////////////////////////////
307 // Display-P3 transformations
308
309 /*
310 * Calculated the luminance of a linear RGB P3 pixel, according to SMPTE EG 432-1.
311 *
312 * [0.0, 1.0] range in and out.
313 */
314 float p3Luminance(Color e);
315
316 /*
317 * Convert from OETF'd P3 RGB to YUV, according to ITU-R BT.601-7.
318 *
319 * BT.601 YUV<->RGB matrix is used to match expectations for DataSpace.
320 */
321 Color p3RgbToYuv(Color e_gamma);
322
323 /*
324 * Convert from OETF'd P3 YUV to RGB, according to ITU-R BT.601-7.
325 *
326 * BT.601 YUV<->RGB matrix is used to match expectations for DataSpace.
327 */
328 Color p3YuvToRgb(Color e_gamma);
329
330 ////////////////////////////////////////////////////////////////////////////////
331 // BT.2100 transformations - according to ITU-R BT.2100-2
332
333 /*
334 * Calculate the luminance of a linear RGB BT.2100 pixel.
335 *
336 * [0.0, 1.0] range in and out.
337 */
338 float bt2100Luminance(Color e);
339
340 /*
341 * Convert from OETF'd BT.2100 RGB to YUV, according to ITU-R BT.2100-2.
342 *
343 * BT.2100 YUV<->RGB matrix is used to match expectations for DataSpace.
344 */
345 Color bt2100RgbToYuv(Color e_gamma);
346
347 /*
348 * Convert from OETF'd BT.2100 YUV to RGB, according to ITU-R BT.2100-2.
349 *
350 * BT.2100 YUV<->RGB matrix is used to match expectations for DataSpace.
351 */
352 Color bt2100YuvToRgb(Color e_gamma);
353
354 /*
355 * Convert from scene luminance to HLG.
356 *
357 * [0.0, 1.0] range in and out.
358 */
359 float hlgOetf(float e);
360 Color hlgOetf(Color e);
361 float hlgOetfLUT(float e);
362 Color hlgOetfLUT(Color e);
363
364 constexpr int32_t kHlgOETFPrecision = 16;
365 constexpr int32_t kHlgOETFNumEntries = 1 << kHlgOETFPrecision;
366
367 /*
368 * Convert from HLG to scene luminance.
369 *
370 * [0.0, 1.0] range in and out.
371 */
372 float hlgInvOetf(float e_gamma);
373 Color hlgInvOetf(Color e_gamma);
374 float hlgInvOetfLUT(float e_gamma);
375 Color hlgInvOetfLUT(Color e_gamma);
376
377 constexpr int32_t kHlgInvOETFPrecision = 12;
378 constexpr int32_t kHlgInvOETFNumEntries = 1 << kHlgInvOETFPrecision;
379
380 /*
381 * Convert from scene luminance to PQ.
382 *
383 * [0.0, 1.0] range in and out.
384 */
385 float pqOetf(float e);
386 Color pqOetf(Color e);
387 float pqOetfLUT(float e);
388 Color pqOetfLUT(Color e);
389
390 constexpr int32_t kPqOETFPrecision = 16;
391 constexpr int32_t kPqOETFNumEntries = 1 << kPqOETFPrecision;
392
393 /*
394 * Convert from PQ to scene luminance in nits.
395 *
396 * [0.0, 1.0] range in and out.
397 */
398 float pqInvOetf(float e_gamma);
399 Color pqInvOetf(Color e_gamma);
400 float pqInvOetfLUT(float e_gamma);
401 Color pqInvOetfLUT(Color e_gamma);
402
403 constexpr int32_t kPqInvOETFPrecision = 12;
404 constexpr int32_t kPqInvOETFNumEntries = 1 << kPqInvOETFPrecision;
405
406 ////////////////////////////////////////////////////////////////////////////////
407 // Color space conversions
408
409 /*
410 * Convert between color spaces with linear RGB data, according to ITU-R BT.2407 and EG 432-1.
411 *
412 * All conversions are derived from multiplying the matrix for XYZ to output RGB color gamut by the
413 * matrix for input RGB color gamut to XYZ. The matrix for converting from XYZ to an RGB gamut is
414 * always the inverse of the RGB gamut to XYZ matrix.
415 */
416 Color bt709ToP3(Color e);
417 Color bt709ToBt2100(Color e);
418 Color p3ToBt709(Color e);
419 Color p3ToBt2100(Color e);
420 Color bt2100ToBt709(Color e);
421 Color bt2100ToP3(Color e);
422
423 /*
424 * Identity conversion.
425 */
identityConversion(Color e)426 inline Color identityConversion(Color e) { return e; }
427
428 /*
429 * Get the conversion to apply to the HDR image for gain map generation
430 */
431 ColorTransformFn getHdrConversionFn(ultrahdr_color_gamut sdr_gamut, ultrahdr_color_gamut hdr_gamut);
432
433 /*
434 * Convert between YUV encodings, according to ITU-R BT.709-6, ITU-R BT.601-7, and ITU-R BT.2100-2.
435 *
436 * Bt.709 and Bt.2100 have well-defined YUV encodings; Display-P3's is less well defined, but is
437 * treated as Bt.601 by DataSpace, hence we do the same.
438 */
439 extern const std::array<float, 9> kYuvBt709ToBt601;
440 extern const std::array<float, 9> kYuvBt709ToBt2100;
441 extern const std::array<float, 9> kYuvBt601ToBt709;
442 extern const std::array<float, 9> kYuvBt601ToBt2100;
443 extern const std::array<float, 9> kYuvBt2100ToBt709;
444 extern const std::array<float, 9> kYuvBt2100ToBt601;
445
446 Color yuvColorGamutConversion(Color e_gamma, const std::array<float, 9>& coeffs);
447
448 #if (defined(UHDR_ENABLE_INTRINSICS) && (defined(__ARM_NEON__) || defined(__ARM_NEON)))
449
450 extern const int16_t kYuv709To601_coeffs_neon[8];
451 extern const int16_t kYuv709To2100_coeffs_neon[8];
452 extern const int16_t kYuv601To709_coeffs_neon[8];
453 extern const int16_t kYuv601To2100_coeffs_neon[8];
454 extern const int16_t kYuv2100To709_coeffs_neon[8];
455 extern const int16_t kYuv2100To601_coeffs_neon[8];
456
457 /*
458 * The Y values are provided at half the width of U & V values to allow use of the widening
459 * arithmetic instructions.
460 */
461 int16x8x3_t yuvConversion_neon(uint8x8_t y, int16x8_t u, int16x8_t v, int16x8_t coeffs);
462
463 void transformYuv420_neon(jr_uncompressed_ptr image, const int16_t* coeffs_ptr);
464
465 status_t convertYuv_neon(jr_uncompressed_ptr image, ultrahdr_color_gamut src_encoding,
466 ultrahdr_color_gamut dst_encoding);
467 #endif
468
469 /*
470 * Performs a color gamut transformation on an entire YUV420 image.
471 *
472 * Apply the transformation by determining transformed YUV for each of the 4 Y + 1 UV; each Y gets
473 * this result, and UV gets the averaged result.
474 *
475 * The chroma channels should be less than or equal to half the image's width and height
476 * respectively, since input is 4:2:0 subsampled.
477 */
478 void transformYuv420(jr_uncompressed_ptr image, const std::array<float, 9>& coeffs);
479
480 ////////////////////////////////////////////////////////////////////////////////
481 // Gain map calculations
482
483 /*
484 * Calculate the 8-bit unsigned integer gain value for the given SDR and HDR
485 * luminances in linear space, and the hdr ratio to encode against.
486 *
487 * Note: since this library always uses gamma of 1.0, offsetSdr of 0.0, and
488 * offsetHdr of 0.0, this function doesn't handle different metadata values for
489 * these fields.
490 */
491 uint8_t encodeGain(float y_sdr, float y_hdr, ultrahdr_metadata_ptr metadata);
492 uint8_t encodeGain(float y_sdr, float y_hdr, ultrahdr_metadata_ptr metadata,
493 float log2MinContentBoost, float log2MaxContentBoost);
494
495 /*
496 * Calculates the linear luminance in nits after applying the given gain
497 * value, with the given hdr ratio, to the given sdr input in the range [0, 1].
498 *
499 * Note: similar to encodeGain(), this function only supports gamma 1.0,
500 * offsetSdr 0.0, offsetHdr 0.0, hdrCapacityMin 1.0, and hdrCapacityMax equal to
501 * gainMapMax, as this library encodes.
502 */
503 Color applyGain(Color e, float gain, ultrahdr_metadata_ptr metadata);
504 Color applyGain(Color e, float gain, ultrahdr_metadata_ptr metadata, float displayBoost);
505 Color applyGainLUT(Color e, float gain, GainLUT& gainLUT);
506
507 /*
508 * Apply gain in R, G and B channels, with the given hdr ratio, to the given sdr input
509 * in the range [0, 1].
510 *
511 * Note: similar to encodeGain(), this function only supports gamma 1.0,
512 * offsetSdr 0.0, offsetHdr 0.0, hdrCapacityMin 1.0, and hdrCapacityMax equal to
513 * gainMapMax, as this library encodes.
514 */
515 Color applyGain(Color e, Color gain, ultrahdr_metadata_ptr metadata);
516 Color applyGain(Color e, Color gain, ultrahdr_metadata_ptr metadata, float displayBoost);
517 Color applyGainLUT(Color e, Color gain, GainLUT& gainLUT);
518
519 /*
520 * Helper for sampling from YUV 420 images.
521 */
522 Color getYuv420Pixel(jr_uncompressed_ptr image, size_t x, size_t y);
523
524 /*
525 * Helper for sampling from P010 images.
526 *
527 * Expect narrow-range image data for P010.
528 */
529 Color getP010Pixel(jr_uncompressed_ptr image, size_t x, size_t y);
530
531 /*
532 * Sample the image at the provided location, with a weighting based on nearby
533 * pixels and the map scale factor.
534 */
535 Color sampleYuv420(jr_uncompressed_ptr map, size_t map_scale_factor, size_t x, size_t y);
536
537 /*
538 * Sample the image at the provided location, with a weighting based on nearby
539 * pixels and the map scale factor.
540 *
541 * Expect narrow-range image data for P010.
542 */
543 Color sampleP010(jr_uncompressed_ptr map, size_t map_scale_factor, size_t x, size_t y);
544
545 /*
546 * Sample the gain value for the map from a given x,y coordinate on a scale
547 * that is map scale factor larger than the map size.
548 */
549 float sampleMap(jr_uncompressed_ptr map, float map_scale_factor, size_t x, size_t y);
550 float sampleMap(jr_uncompressed_ptr map, size_t map_scale_factor, size_t x, size_t y,
551 ShepardsIDW& weightTables);
552 Color sampleMap3Channel(jr_uncompressed_ptr map, float map_scale_factor, size_t x, size_t y,
553 bool has_alpha);
554 Color sampleMap3Channel(jr_uncompressed_ptr map, size_t map_scale_factor, size_t x, size_t y,
555 ShepardsIDW& weightTables, bool has_alpha);
556
557 /*
558 * Convert from Color to RGBA1010102.
559 *
560 * Alpha always set to 1.0.
561 */
562 uint32_t colorToRgba1010102(Color e_gamma);
563
564 /*
565 * Convert from Color to F16.
566 *
567 * Alpha always set to 1.0.
568 */
569 uint64_t colorToRgbaF16(Color e_gamma);
570
571 /*
572 * Helper for preparing encoder raw inputs for encoding
573 */
574 std::unique_ptr<uhdr_raw_image_ext_t> convert_raw_input_to_ycbcr(uhdr_raw_image_t* src);
575
576 /*
577 * Helper for converting float to fraction
578 */
579 bool floatToSignedFraction(float v, int32_t* numerator, uint32_t* denominator);
580 bool floatToUnsignedFraction(float v, uint32_t* numerator, uint32_t* denominator);
581
582 } // namespace ultrahdr
583
584 #endif // ULTRAHDR_GAINMAPMATH_H
585