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 #include <gtest/gtest.h>
18 #include <gmock/gmock.h>
19
20 #include "ultrahdr/gainmapmath.h"
21
22 namespace ultrahdr {
23
24 class GainMapMathTest : public testing::Test {
25 public:
26 GainMapMathTest();
27 ~GainMapMathTest();
28
ComparisonEpsilon()29 float ComparisonEpsilon() { return 1e-4f; }
LuminanceEpsilon()30 float LuminanceEpsilon() { return 1e-2f; }
YuvConversionEpsilon()31 float YuvConversionEpsilon() { return 1.0f / (255.0f * 2.0f); }
32
Yuv420(uint8_t y,uint8_t u,uint8_t v)33 Color Yuv420(uint8_t y, uint8_t u, uint8_t v) {
34 return {{{static_cast<float>(y) * (1 / 255.0f), static_cast<float>(u - 128) * (1 / 255.0f),
35 static_cast<float>(v - 128) * (1 / 255.0f)}}};
36 }
37
P010(uint16_t y,uint16_t u,uint16_t v)38 Color P010(uint16_t y, uint16_t u, uint16_t v) {
39 return {{{static_cast<float>(y - 64) * (1 / 876.0f),
40 static_cast<float>(u - 64) * (1 / 896.0f) - 0.5f,
41 static_cast<float>(v - 64) * (1 / 896.0f) - 0.5f}}};
42 }
43
44 // Using int16_t allows for testing fixed-point implementations.
45 struct Pixel {
46 int16_t y;
47 int16_t u;
48 int16_t v;
49 };
50
getYuv420Pixel_uint(uhdr_raw_image_t * image,size_t x,size_t y)51 Pixel getYuv420Pixel_uint(uhdr_raw_image_t* image, size_t x, size_t y) {
52 uint8_t* luma_data = reinterpret_cast<uint8_t*>(image->planes[UHDR_PLANE_Y]);
53 size_t luma_stride = image->stride[UHDR_PLANE_Y];
54 uint8_t* cb_data = reinterpret_cast<uint8_t*>(image->planes[UHDR_PLANE_U]);
55 size_t cb_stride = image->stride[UHDR_PLANE_U];
56 uint8_t* cr_data = reinterpret_cast<uint8_t*>(image->planes[UHDR_PLANE_V]);
57 size_t cr_stride = image->stride[UHDR_PLANE_V];
58
59 size_t pixel_y_idx = x + y * luma_stride;
60 size_t pixel_cb_idx = x / 2 + (y / 2) * cb_stride;
61 size_t pixel_cr_idx = x / 2 + (y / 2) * cr_stride;
62
63 uint8_t y_uint = luma_data[pixel_y_idx];
64 uint8_t u_uint = cb_data[pixel_cb_idx];
65 uint8_t v_uint = cr_data[pixel_cr_idx];
66
67 return {y_uint, u_uint, v_uint};
68 }
69
Map(uint8_t e)70 float Map(uint8_t e) { return static_cast<float>(e) / 255.0f; }
71
ColorMin(Color e1,Color e2)72 Color ColorMin(Color e1, Color e2) {
73 return {{{fminf(e1.r, e2.r), fminf(e1.g, e2.g), fminf(e1.b, e2.b)}}};
74 }
75
ColorMax(Color e1,Color e2)76 Color ColorMax(Color e1, Color e2) {
77 return {{{fmaxf(e1.r, e2.r), fmaxf(e1.g, e2.g), fmaxf(e1.b, e2.b)}}};
78 }
79
RgbBlack()80 Color RgbBlack() { return {{{0.0f, 0.0f, 0.0f}}}; }
RgbWhite()81 Color RgbWhite() { return {{{1.0f, 1.0f, 1.0f}}}; }
82
RgbRed()83 Color RgbRed() { return {{{1.0f, 0.0f, 0.0f}}}; }
RgbGreen()84 Color RgbGreen() { return {{{0.0f, 1.0f, 0.0f}}}; }
RgbBlue()85 Color RgbBlue() { return {{{0.0f, 0.0f, 1.0f}}}; }
86
YuvBlack()87 Color YuvBlack() { return {{{0.0f, 0.0f, 0.0f}}}; }
YuvWhite()88 Color YuvWhite() { return {{{1.0f, 0.0f, 0.0f}}}; }
89
SrgbYuvRed()90 Color SrgbYuvRed() { return {{{0.2126f, -0.11457f, 0.5f}}}; }
SrgbYuvGreen()91 Color SrgbYuvGreen() { return {{{0.7152f, -0.38543f, -0.45415f}}}; }
SrgbYuvBlue()92 Color SrgbYuvBlue() { return {{{0.0722f, 0.5f, -0.04585f}}}; }
93
P3YuvRed()94 Color P3YuvRed() { return {{{0.299f, -0.16874f, 0.5f}}}; }
P3YuvGreen()95 Color P3YuvGreen() { return {{{0.587f, -0.33126f, -0.41869f}}}; }
P3YuvBlue()96 Color P3YuvBlue() { return {{{0.114f, 0.5f, -0.08131f}}}; }
97
Bt2100YuvRed()98 Color Bt2100YuvRed() { return {{{0.2627f, -0.13963f, 0.5f}}}; }
Bt2100YuvGreen()99 Color Bt2100YuvGreen() { return {{{0.6780f, -0.36037f, -0.45979f}}}; }
Bt2100YuvBlue()100 Color Bt2100YuvBlue() { return {{{0.0593f, 0.5f, -0.04021f}}}; }
101
102 //////////////////////////////////////////////////////////////////////////////
103 // Reference values for when using fixed-point arithmetic.
104
RgbBlackPixel()105 Pixel RgbBlackPixel() { return {0, 0, 0}; }
RgbWhitePixel()106 Pixel RgbWhitePixel() { return {255, 255, 255}; }
107
RgbRedPixel()108 Pixel RgbRedPixel() { return {255, 0, 0}; }
RgbGreenPixel()109 Pixel RgbGreenPixel() { return {0, 255, 0}; }
RgbBluePixel()110 Pixel RgbBluePixel() { return {0, 0, 255}; }
111
YuvBlackPixel()112 Pixel YuvBlackPixel() { return {0, 0, 0}; }
YuvWhitePixel()113 Pixel YuvWhitePixel() { return {255, 0, 0}; }
114
SrgbYuvRedPixel()115 Pixel SrgbYuvRedPixel() { return {54, -29, 128}; }
SrgbYuvGreenPixel()116 Pixel SrgbYuvGreenPixel() { return {182, -98, -116}; }
SrgbYuvBluePixel()117 Pixel SrgbYuvBluePixel() { return {18, 128, -12}; }
118
P3YuvRedPixel()119 Pixel P3YuvRedPixel() { return {76, -43, 128}; }
P3YuvGreenPixel()120 Pixel P3YuvGreenPixel() { return {150, -84, -107}; }
P3YuvBluePixel()121 Pixel P3YuvBluePixel() { return {29, 128, -21}; }
122
Bt2100YuvRedPixel()123 Pixel Bt2100YuvRedPixel() { return {67, -36, 128}; }
Bt2100YuvGreenPixel()124 Pixel Bt2100YuvGreenPixel() { return {173, -92, -117}; }
Bt2100YuvBluePixel()125 Pixel Bt2100YuvBluePixel() { return {15, 128, -10}; }
126
SrgbYuvToLuminance(Color yuv_gamma,LuminanceFn luminanceFn)127 float SrgbYuvToLuminance(Color yuv_gamma, LuminanceFn luminanceFn) {
128 Color rgb_gamma = srgbYuvToRgb(yuv_gamma);
129 Color rgb = srgbInvOetf(rgb_gamma);
130 float luminance_scaled = luminanceFn(rgb);
131 return luminance_scaled * kSdrWhiteNits;
132 }
133
P3YuvToLuminance(Color yuv_gamma,LuminanceFn luminanceFn)134 float P3YuvToLuminance(Color yuv_gamma, LuminanceFn luminanceFn) {
135 Color rgb_gamma = p3YuvToRgb(yuv_gamma);
136 Color rgb = srgbInvOetf(rgb_gamma);
137 float luminance_scaled = luminanceFn(rgb);
138 return luminance_scaled * kSdrWhiteNits;
139 }
140
Bt2100YuvToLuminance(Color yuv_gamma,ColorTransformFn hdrInvOetf,ColorTransformFn gamutConversionFn,LuminanceFn luminanceFn,float scale_factor)141 float Bt2100YuvToLuminance(Color yuv_gamma, ColorTransformFn hdrInvOetf,
142 ColorTransformFn gamutConversionFn, LuminanceFn luminanceFn,
143 float scale_factor) {
144 Color rgb_gamma = bt2100YuvToRgb(yuv_gamma);
145 Color rgb = hdrInvOetf(rgb_gamma);
146 rgb = gamutConversionFn(rgb);
147 float luminance_scaled = luminanceFn(rgb);
148 return luminance_scaled * scale_factor;
149 }
150
Recover(Color yuv_gamma,float gain,uhdr_gainmap_metadata_ext_t * metadata)151 Color Recover(Color yuv_gamma, float gain, uhdr_gainmap_metadata_ext_t* metadata) {
152 Color rgb_gamma = srgbYuvToRgb(yuv_gamma);
153 Color rgb = srgbInvOetf(rgb_gamma);
154 return applyGain(rgb, gain, metadata);
155 }
156
Yuv420Image()157 uhdr_raw_image_t Yuv420Image() {
158 static uint8_t pixels[] = {
159 // Y
160 0x00,
161 0x10,
162 0x20,
163 0x30,
164 0x01,
165 0x11,
166 0x21,
167 0x31,
168 0x02,
169 0x12,
170 0x22,
171 0x32,
172 0x03,
173 0x13,
174 0x23,
175 0x33,
176 // U
177 0xA0,
178 0xA1,
179 0xA2,
180 0xA3,
181 // V
182 0xB0,
183 0xB1,
184 0xB2,
185 0xB3,
186 };
187 uhdr_raw_image_t img;
188 img.cg = UHDR_CG_BT_709;
189 img.ct = UHDR_CT_SRGB;
190 img.range = UHDR_CR_FULL_RANGE;
191 img.fmt = UHDR_IMG_FMT_12bppYCbCr420;
192 img.w = 4;
193 img.h = 4;
194 img.planes[UHDR_PLANE_Y] = pixels;
195 img.planes[UHDR_PLANE_U] = pixels + 16;
196 img.planes[UHDR_PLANE_V] = pixels + 16 + 4;
197 img.stride[UHDR_PLANE_Y] = 4;
198 img.stride[UHDR_PLANE_U] = 2;
199 img.stride[UHDR_PLANE_V] = 2;
200 return img;
201 }
202
Yuv420Image32x4()203 uhdr_raw_image_t Yuv420Image32x4() {
204 // clang-format off
205 static uint8_t pixels[] = {
206 // Y
207 0x0, 0x10, 0x20, 0x30, 0x1, 0x11, 0x21, 0x31, 0x2, 0x12, 0x22, 0x32, 0x3, 0x13, 0x23, 0x33,
208 0x4, 0x14, 0x24, 0x34, 0x5, 0x15, 0x25, 0x35, 0x6, 0x16, 0x26, 0x36, 0x7, 0x17, 0x27, 0x37,
209 0x8, 0x18, 0x28, 0x38, 0x9, 0x19, 0x29, 0x39, 0xa, 0x1a, 0x2a, 0x3a, 0xb, 0x1b, 0x2b, 0x3b,
210 0xc, 0x1c, 0x2c, 0x3c, 0xd, 0x1d, 0x2d, 0x3d, 0xe, 0x1e, 0x2e, 0x3e, 0xf, 0x1f, 0x2f, 0x3f,
211 0x10, 0x20, 0x30, 0x40, 0x11, 0x21, 0x31, 0x41, 0x12, 0x22, 0x32, 0x42, 0x13, 0x23, 0x33, 0x43,
212 0x14, 0x24, 0x34, 0x44, 0x15, 0x25, 0x35, 0x45, 0x16, 0x26, 0x36, 0x46, 0x17, 0x27, 0x37, 0x47,
213 0x18, 0x28, 0x38, 0x48, 0x19, 0x29, 0x39, 0x49, 0x1a, 0x2a, 0x3a, 0x4a, 0x1b, 0x2b, 0x3b, 0x4b,
214 0x1c, 0x2c, 0x3c, 0x4c, 0x1d, 0x2d, 0x3d, 0x4d, 0x1e, 0x2e, 0x3e, 0x4e, 0x1f, 0x2f, 0x3f, 0x4f,
215 // U
216 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF,
217 0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBB, 0xBB, 0xBC, 0xBD, 0xBE, 0xBF,
218 // V
219 0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCC, 0xCB, 0xCC, 0xCD, 0xCE, 0xCF,
220 0xD0, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDD, 0xDD, 0xDC, 0xDD, 0xDE, 0xDF,
221 };
222 // clang-format on
223 uhdr_raw_image_t img;
224 img.cg = UHDR_CG_BT_709;
225 img.ct = UHDR_CT_SRGB;
226 img.range = UHDR_CR_FULL_RANGE;
227 img.fmt = UHDR_IMG_FMT_12bppYCbCr420;
228 img.w = 32;
229 img.h = 4;
230 img.planes[UHDR_PLANE_Y] = pixels;
231 img.planes[UHDR_PLANE_U] = pixels + 128;
232 img.planes[UHDR_PLANE_V] = pixels + 128 + 32;
233 img.stride[UHDR_PLANE_Y] = 32;
234 img.stride[UHDR_PLANE_U] = 16;
235 img.stride[UHDR_PLANE_V] = 16;
236 return img;
237 }
238
Yuv420Colors()239 Color (*Yuv420Colors())[4] {
240 static Color colors[4][4] = {
241 {
242 Yuv420(0x00, 0xA0, 0xB0),
243 Yuv420(0x10, 0xA0, 0xB0),
244 Yuv420(0x20, 0xA1, 0xB1),
245 Yuv420(0x30, 0xA1, 0xB1),
246 },
247 {
248 Yuv420(0x01, 0xA0, 0xB0),
249 Yuv420(0x11, 0xA0, 0xB0),
250 Yuv420(0x21, 0xA1, 0xB1),
251 Yuv420(0x31, 0xA1, 0xB1),
252 },
253 {
254 Yuv420(0x02, 0xA2, 0xB2),
255 Yuv420(0x12, 0xA2, 0xB2),
256 Yuv420(0x22, 0xA3, 0xB3),
257 Yuv420(0x32, 0xA3, 0xB3),
258 },
259 {
260 Yuv420(0x03, 0xA2, 0xB2),
261 Yuv420(0x13, 0xA2, 0xB2),
262 Yuv420(0x23, 0xA3, 0xB3),
263 Yuv420(0x33, 0xA3, 0xB3),
264 },
265 };
266 return colors;
267 }
268
269 uhdr_raw_image_t P010Image() {
270 static uint16_t pixels[] = {
271 // Y
272 0x00 << 6,
273 0x10 << 6,
274 0x20 << 6,
275 0x30 << 6,
276 0x01 << 6,
277 0x11 << 6,
278 0x21 << 6,
279 0x31 << 6,
280 0x02 << 6,
281 0x12 << 6,
282 0x22 << 6,
283 0x32 << 6,
284 0x03 << 6,
285 0x13 << 6,
286 0x23 << 6,
287 0x33 << 6,
288 // UV
289 0xA0 << 6,
290 0xB0 << 6,
291 0xA1 << 6,
292 0xB1 << 6,
293 0xA2 << 6,
294 0xB2 << 6,
295 0xA3 << 6,
296 0xB3 << 6,
297 };
298 uhdr_raw_image_t img;
299 img.cg = UHDR_CG_BT_709;
300 img.ct = UHDR_CT_HLG;
301 img.range = UHDR_CR_LIMITED_RANGE;
302 img.fmt = UHDR_IMG_FMT_24bppYCbCrP010;
303 img.w = 4;
304 img.h = 4;
305 img.planes[UHDR_PLANE_Y] = pixels;
306 img.planes[UHDR_PLANE_UV] = pixels + 16;
307 img.planes[UHDR_PLANE_V] = nullptr;
308 img.stride[UHDR_PLANE_Y] = 4;
309 img.stride[UHDR_PLANE_UV] = 4;
310 img.stride[UHDR_PLANE_V] = 0;
311 return img;
312 }
313
__anon7bd5a46c0202null314 Color (*P010Colors())[4] {
315 static Color colors[4][4] = {
316 {
317 P010(0x00, 0xA0, 0xB0),
318 P010(0x10, 0xA0, 0xB0),
319 P010(0x20, 0xA1, 0xB1),
320 P010(0x30, 0xA1, 0xB1),
321 },
322 {
323 P010(0x01, 0xA0, 0xB0),
324 P010(0x11, 0xA0, 0xB0),
325 P010(0x21, 0xA1, 0xB1),
326 P010(0x31, 0xA1, 0xB1),
327 },
328 {
329 P010(0x02, 0xA2, 0xB2),
330 P010(0x12, 0xA2, 0xB2),
331 P010(0x22, 0xA3, 0xB3),
332 P010(0x32, 0xA3, 0xB3),
333 },
334 {
335 P010(0x03, 0xA2, 0xB2),
336 P010(0x13, 0xA2, 0xB2),
337 P010(0x23, 0xA3, 0xB3),
338 P010(0x33, 0xA3, 0xB3),
339 },
340 };
341 return colors;
342 }
343
344 uhdr_raw_image_t MapImage() {
345 static uint8_t pixels[] = {
346 0x00, 0x10, 0x20, 0x30, 0x01, 0x11, 0x21, 0x31,
347 0x02, 0x12, 0x22, 0x32, 0x03, 0x13, 0x23, 0x33,
348 };
349
350 uhdr_raw_image_t img;
351 img.cg = UHDR_CG_UNSPECIFIED;
352 img.ct = UHDR_CT_UNSPECIFIED;
353 img.range = UHDR_CR_UNSPECIFIED;
354 img.fmt = UHDR_IMG_FMT_8bppYCbCr400;
355 img.w = 4;
356 img.h = 4;
357 img.planes[UHDR_PLANE_Y] = pixels;
358 img.planes[UHDR_PLANE_U] = nullptr;
359 img.planes[UHDR_PLANE_V] = nullptr;
360 img.stride[UHDR_PLANE_Y] = 4;
361 img.stride[UHDR_PLANE_U] = 0;
362 img.stride[UHDR_PLANE_V] = 0;
363 return img;
364 }
365
__anon7bd5a46c0302null366 float (*MapValues())[4] {
367 static float values[4][4] = {
368 {
369 Map(0x00),
370 Map(0x10),
371 Map(0x20),
372 Map(0x30),
373 },
374 {
375 Map(0x01),
376 Map(0x11),
377 Map(0x21),
378 Map(0x31),
379 },
380 {
381 Map(0x02),
382 Map(0x12),
383 Map(0x22),
384 Map(0x32),
385 },
386 {
387 Map(0x03),
388 Map(0x13),
389 Map(0x23),
390 Map(0x33),
391 },
392 };
393 return values;
394 }
395
396 protected:
397 virtual void SetUp();
398 virtual void TearDown();
399 };
400
GainMapMathTest()401 GainMapMathTest::GainMapMathTest() {}
~GainMapMathTest()402 GainMapMathTest::~GainMapMathTest() {}
403
SetUp()404 void GainMapMathTest::SetUp() {}
TearDown()405 void GainMapMathTest::TearDown() {}
406
407 #define EXPECT_RGB_EQ(e1, e2) \
408 EXPECT_FLOAT_EQ((e1).r, (e2).r); \
409 EXPECT_FLOAT_EQ((e1).g, (e2).g); \
410 EXPECT_FLOAT_EQ((e1).b, (e2).b)
411
412 #define EXPECT_RGB_NEAR(e1, e2) \
413 EXPECT_NEAR((e1).r, (e2).r, ComparisonEpsilon()); \
414 EXPECT_NEAR((e1).g, (e2).g, ComparisonEpsilon()); \
415 EXPECT_NEAR((e1).b, (e2).b, ComparisonEpsilon())
416
417 #define EXPECT_RGB_CLOSE(e1, e2) \
418 EXPECT_NEAR((e1).r, (e2).r, ComparisonEpsilon() * 10.0f); \
419 EXPECT_NEAR((e1).g, (e2).g, ComparisonEpsilon() * 10.0f); \
420 EXPECT_NEAR((e1).b, (e2).b, ComparisonEpsilon() * 10.0f)
421
422 #define EXPECT_YUV_EQ(e1, e2) \
423 EXPECT_FLOAT_EQ((e1).y, (e2).y); \
424 EXPECT_FLOAT_EQ((e1).u, (e2).u); \
425 EXPECT_FLOAT_EQ((e1).v, (e2).v)
426
427 #define EXPECT_YUV_NEAR(e1, e2) \
428 EXPECT_NEAR((e1).y, (e2).y, ComparisonEpsilon()); \
429 EXPECT_NEAR((e1).u, (e2).u, ComparisonEpsilon()); \
430 EXPECT_NEAR((e1).v, (e2).v, ComparisonEpsilon())
431
432 // Due to -ffp-contract=fast being enabled by default with GCC, allow some
433 // margin when comparing fused and unfused floating-point operations.
434 #define EXPECT_YUV_BETWEEN(e, min, max) \
435 EXPECT_THAT((e).y, testing::AllOf(testing::Ge((min).y - ComparisonEpsilon()), \
436 testing::Le((max).y + ComparisonEpsilon()))); \
437 EXPECT_THAT((e).u, testing::AllOf(testing::Ge((min).u - ComparisonEpsilon()), \
438 testing::Le((max).u + ComparisonEpsilon()))); \
439 EXPECT_THAT((e).v, testing::AllOf(testing::Ge((min).v - ComparisonEpsilon()), \
440 testing::Le((max).v + ComparisonEpsilon())))
441
442 // TODO: a bunch of these tests can be parameterized.
443
TEST_F(GainMapMathTest,ColorConstruct)444 TEST_F(GainMapMathTest, ColorConstruct) {
445 Color e1 = {{{0.1f, 0.2f, 0.3f}}};
446
447 EXPECT_FLOAT_EQ(e1.r, 0.1f);
448 EXPECT_FLOAT_EQ(e1.g, 0.2f);
449 EXPECT_FLOAT_EQ(e1.b, 0.3f);
450
451 EXPECT_FLOAT_EQ(e1.y, 0.1f);
452 EXPECT_FLOAT_EQ(e1.u, 0.2f);
453 EXPECT_FLOAT_EQ(e1.v, 0.3f);
454 }
455
TEST_F(GainMapMathTest,ColorAddColor)456 TEST_F(GainMapMathTest, ColorAddColor) {
457 Color e1 = {{{0.1f, 0.2f, 0.3f}}};
458
459 Color e2 = e1 + e1;
460 EXPECT_FLOAT_EQ(e2.r, e1.r * 2.0f);
461 EXPECT_FLOAT_EQ(e2.g, e1.g * 2.0f);
462 EXPECT_FLOAT_EQ(e2.b, e1.b * 2.0f);
463
464 e2 += e1;
465 EXPECT_FLOAT_EQ(e2.r, e1.r * 3.0f);
466 EXPECT_FLOAT_EQ(e2.g, e1.g * 3.0f);
467 EXPECT_FLOAT_EQ(e2.b, e1.b * 3.0f);
468 }
469
TEST_F(GainMapMathTest,ColorAddFloat)470 TEST_F(GainMapMathTest, ColorAddFloat) {
471 Color e1 = {{{0.1f, 0.2f, 0.3f}}};
472
473 Color e2 = e1 + 0.1f;
474 EXPECT_FLOAT_EQ(e2.r, e1.r + 0.1f);
475 EXPECT_FLOAT_EQ(e2.g, e1.g + 0.1f);
476 EXPECT_FLOAT_EQ(e2.b, e1.b + 0.1f);
477
478 e2 += 0.1f;
479 EXPECT_FLOAT_EQ(e2.r, e1.r + 0.2f);
480 EXPECT_FLOAT_EQ(e2.g, e1.g + 0.2f);
481 EXPECT_FLOAT_EQ(e2.b, e1.b + 0.2f);
482 }
483
TEST_F(GainMapMathTest,ColorSubtractColor)484 TEST_F(GainMapMathTest, ColorSubtractColor) {
485 Color e1 = {{{0.1f, 0.2f, 0.3f}}};
486
487 Color e2 = e1 - e1;
488 EXPECT_FLOAT_EQ(e2.r, 0.0f);
489 EXPECT_FLOAT_EQ(e2.g, 0.0f);
490 EXPECT_FLOAT_EQ(e2.b, 0.0f);
491
492 e2 -= e1;
493 EXPECT_FLOAT_EQ(e2.r, -e1.r);
494 EXPECT_FLOAT_EQ(e2.g, -e1.g);
495 EXPECT_FLOAT_EQ(e2.b, -e1.b);
496 }
497
TEST_F(GainMapMathTest,ColorSubtractFloat)498 TEST_F(GainMapMathTest, ColorSubtractFloat) {
499 Color e1 = {{{0.1f, 0.2f, 0.3f}}};
500
501 Color e2 = e1 - 0.1f;
502 EXPECT_FLOAT_EQ(e2.r, e1.r - 0.1f);
503 EXPECT_FLOAT_EQ(e2.g, e1.g - 0.1f);
504 EXPECT_FLOAT_EQ(e2.b, e1.b - 0.1f);
505
506 e2 -= 0.1f;
507 EXPECT_FLOAT_EQ(e2.r, e1.r - 0.2f);
508 EXPECT_FLOAT_EQ(e2.g, e1.g - 0.2f);
509 EXPECT_FLOAT_EQ(e2.b, e1.b - 0.2f);
510 }
511
TEST_F(GainMapMathTest,ColorMultiplyFloat)512 TEST_F(GainMapMathTest, ColorMultiplyFloat) {
513 Color e1 = {{{0.1f, 0.2f, 0.3f}}};
514
515 Color e2 = e1 * 2.0f;
516 EXPECT_FLOAT_EQ(e2.r, e1.r * 2.0f);
517 EXPECT_FLOAT_EQ(e2.g, e1.g * 2.0f);
518 EXPECT_FLOAT_EQ(e2.b, e1.b * 2.0f);
519
520 e2 *= 2.0f;
521 EXPECT_FLOAT_EQ(e2.r, e1.r * 4.0f);
522 EXPECT_FLOAT_EQ(e2.g, e1.g * 4.0f);
523 EXPECT_FLOAT_EQ(e2.b, e1.b * 4.0f);
524 }
525
TEST_F(GainMapMathTest,ColorDivideFloat)526 TEST_F(GainMapMathTest, ColorDivideFloat) {
527 Color e1 = {{{0.1f, 0.2f, 0.3f}}};
528
529 Color e2 = e1 / 2.0f;
530 EXPECT_FLOAT_EQ(e2.r, e1.r / 2.0f);
531 EXPECT_FLOAT_EQ(e2.g, e1.g / 2.0f);
532 EXPECT_FLOAT_EQ(e2.b, e1.b / 2.0f);
533
534 e2 /= 2.0f;
535 EXPECT_FLOAT_EQ(e2.r, e1.r / 4.0f);
536 EXPECT_FLOAT_EQ(e2.g, e1.g / 4.0f);
537 EXPECT_FLOAT_EQ(e2.b, e1.b / 4.0f);
538 }
539
TEST_F(GainMapMathTest,SrgbLuminance)540 TEST_F(GainMapMathTest, SrgbLuminance) {
541 EXPECT_FLOAT_EQ(srgbLuminance(RgbBlack()), 0.0f);
542 EXPECT_FLOAT_EQ(srgbLuminance(RgbWhite()), 1.0f);
543 EXPECT_FLOAT_EQ(srgbLuminance(RgbRed()), 0.2126f);
544 EXPECT_FLOAT_EQ(srgbLuminance(RgbGreen()), 0.7152f);
545 EXPECT_FLOAT_EQ(srgbLuminance(RgbBlue()), 0.0722f);
546 }
547
TEST_F(GainMapMathTest,SrgbYuvToRgb)548 TEST_F(GainMapMathTest, SrgbYuvToRgb) {
549 Color rgb_black = srgbYuvToRgb(YuvBlack());
550 EXPECT_RGB_NEAR(rgb_black, RgbBlack());
551
552 Color rgb_white = srgbYuvToRgb(YuvWhite());
553 EXPECT_RGB_NEAR(rgb_white, RgbWhite());
554
555 Color rgb_r = srgbYuvToRgb(SrgbYuvRed());
556 EXPECT_RGB_NEAR(rgb_r, RgbRed());
557
558 Color rgb_g = srgbYuvToRgb(SrgbYuvGreen());
559 EXPECT_RGB_NEAR(rgb_g, RgbGreen());
560
561 Color rgb_b = srgbYuvToRgb(SrgbYuvBlue());
562 EXPECT_RGB_NEAR(rgb_b, RgbBlue());
563 }
564
TEST_F(GainMapMathTest,SrgbRgbToYuv)565 TEST_F(GainMapMathTest, SrgbRgbToYuv) {
566 Color yuv_black = srgbRgbToYuv(RgbBlack());
567 EXPECT_YUV_NEAR(yuv_black, YuvBlack());
568
569 Color yuv_white = srgbRgbToYuv(RgbWhite());
570 EXPECT_YUV_NEAR(yuv_white, YuvWhite());
571
572 Color yuv_r = srgbRgbToYuv(RgbRed());
573 EXPECT_YUV_NEAR(yuv_r, SrgbYuvRed());
574
575 Color yuv_g = srgbRgbToYuv(RgbGreen());
576 EXPECT_YUV_NEAR(yuv_g, SrgbYuvGreen());
577
578 Color yuv_b = srgbRgbToYuv(RgbBlue());
579 EXPECT_YUV_NEAR(yuv_b, SrgbYuvBlue());
580 }
581
TEST_F(GainMapMathTest,SrgbRgbYuvRoundtrip)582 TEST_F(GainMapMathTest, SrgbRgbYuvRoundtrip) {
583 Color rgb_black = srgbYuvToRgb(srgbRgbToYuv(RgbBlack()));
584 EXPECT_RGB_NEAR(rgb_black, RgbBlack());
585
586 Color rgb_white = srgbYuvToRgb(srgbRgbToYuv(RgbWhite()));
587 EXPECT_RGB_NEAR(rgb_white, RgbWhite());
588
589 Color rgb_r = srgbYuvToRgb(srgbRgbToYuv(RgbRed()));
590 EXPECT_RGB_NEAR(rgb_r, RgbRed());
591
592 Color rgb_g = srgbYuvToRgb(srgbRgbToYuv(RgbGreen()));
593 EXPECT_RGB_NEAR(rgb_g, RgbGreen());
594
595 Color rgb_b = srgbYuvToRgb(srgbRgbToYuv(RgbBlue()));
596 EXPECT_RGB_NEAR(rgb_b, RgbBlue());
597 }
598
TEST_F(GainMapMathTest,SrgbTransferFunction)599 TEST_F(GainMapMathTest, SrgbTransferFunction) {
600 EXPECT_FLOAT_EQ(srgbInvOetf(0.0f), 0.0f);
601 EXPECT_NEAR(srgbInvOetf(0.02f), 0.00154f, ComparisonEpsilon());
602 EXPECT_NEAR(srgbInvOetf(0.04045f), 0.00313f, ComparisonEpsilon());
603 EXPECT_NEAR(srgbInvOetf(0.5f), 0.21404f, ComparisonEpsilon());
604 EXPECT_FLOAT_EQ(srgbInvOetf(1.0f), 1.0f);
605 }
606
TEST_F(GainMapMathTest,P3Luminance)607 TEST_F(GainMapMathTest, P3Luminance) {
608 EXPECT_FLOAT_EQ(p3Luminance(RgbBlack()), 0.0f);
609 EXPECT_FLOAT_EQ(p3Luminance(RgbWhite()), 1.0f);
610 EXPECT_FLOAT_EQ(p3Luminance(RgbRed()), 0.20949f);
611 EXPECT_FLOAT_EQ(p3Luminance(RgbGreen()), 0.72160f);
612 EXPECT_FLOAT_EQ(p3Luminance(RgbBlue()), 0.06891f);
613 }
614
TEST_F(GainMapMathTest,P3YuvToRgb)615 TEST_F(GainMapMathTest, P3YuvToRgb) {
616 Color rgb_black = p3YuvToRgb(YuvBlack());
617 EXPECT_RGB_NEAR(rgb_black, RgbBlack());
618
619 Color rgb_white = p3YuvToRgb(YuvWhite());
620 EXPECT_RGB_NEAR(rgb_white, RgbWhite());
621
622 Color rgb_r = p3YuvToRgb(P3YuvRed());
623 EXPECT_RGB_NEAR(rgb_r, RgbRed());
624
625 Color rgb_g = p3YuvToRgb(P3YuvGreen());
626 EXPECT_RGB_NEAR(rgb_g, RgbGreen());
627
628 Color rgb_b = p3YuvToRgb(P3YuvBlue());
629 EXPECT_RGB_NEAR(rgb_b, RgbBlue());
630 }
631
TEST_F(GainMapMathTest,P3RgbToYuv)632 TEST_F(GainMapMathTest, P3RgbToYuv) {
633 Color yuv_black = p3RgbToYuv(RgbBlack());
634 EXPECT_YUV_NEAR(yuv_black, YuvBlack());
635
636 Color yuv_white = p3RgbToYuv(RgbWhite());
637 EXPECT_YUV_NEAR(yuv_white, YuvWhite());
638
639 Color yuv_r = p3RgbToYuv(RgbRed());
640 EXPECT_YUV_NEAR(yuv_r, P3YuvRed());
641
642 Color yuv_g = p3RgbToYuv(RgbGreen());
643 EXPECT_YUV_NEAR(yuv_g, P3YuvGreen());
644
645 Color yuv_b = p3RgbToYuv(RgbBlue());
646 EXPECT_YUV_NEAR(yuv_b, P3YuvBlue());
647 }
648
TEST_F(GainMapMathTest,P3RgbYuvRoundtrip)649 TEST_F(GainMapMathTest, P3RgbYuvRoundtrip) {
650 Color rgb_black = p3YuvToRgb(p3RgbToYuv(RgbBlack()));
651 EXPECT_RGB_NEAR(rgb_black, RgbBlack());
652
653 Color rgb_white = p3YuvToRgb(p3RgbToYuv(RgbWhite()));
654 EXPECT_RGB_NEAR(rgb_white, RgbWhite());
655
656 Color rgb_r = p3YuvToRgb(p3RgbToYuv(RgbRed()));
657 EXPECT_RGB_NEAR(rgb_r, RgbRed());
658
659 Color rgb_g = p3YuvToRgb(p3RgbToYuv(RgbGreen()));
660 EXPECT_RGB_NEAR(rgb_g, RgbGreen());
661
662 Color rgb_b = p3YuvToRgb(p3RgbToYuv(RgbBlue()));
663 EXPECT_RGB_NEAR(rgb_b, RgbBlue());
664 }
TEST_F(GainMapMathTest,Bt2100Luminance)665 TEST_F(GainMapMathTest, Bt2100Luminance) {
666 EXPECT_FLOAT_EQ(bt2100Luminance(RgbBlack()), 0.0f);
667 EXPECT_FLOAT_EQ(bt2100Luminance(RgbWhite()), 1.0f);
668 EXPECT_FLOAT_EQ(bt2100Luminance(RgbRed()), 0.2627f);
669 EXPECT_FLOAT_EQ(bt2100Luminance(RgbGreen()), 0.6780f);
670 EXPECT_FLOAT_EQ(bt2100Luminance(RgbBlue()), 0.0593f);
671 }
672
TEST_F(GainMapMathTest,Bt2100YuvToRgb)673 TEST_F(GainMapMathTest, Bt2100YuvToRgb) {
674 Color rgb_black = bt2100YuvToRgb(YuvBlack());
675 EXPECT_RGB_NEAR(rgb_black, RgbBlack());
676
677 Color rgb_white = bt2100YuvToRgb(YuvWhite());
678 EXPECT_RGB_NEAR(rgb_white, RgbWhite());
679
680 Color rgb_r = bt2100YuvToRgb(Bt2100YuvRed());
681 EXPECT_RGB_NEAR(rgb_r, RgbRed());
682
683 Color rgb_g = bt2100YuvToRgb(Bt2100YuvGreen());
684 EXPECT_RGB_NEAR(rgb_g, RgbGreen());
685
686 Color rgb_b = bt2100YuvToRgb(Bt2100YuvBlue());
687 EXPECT_RGB_NEAR(rgb_b, RgbBlue());
688 }
689
TEST_F(GainMapMathTest,Bt2100RgbToYuv)690 TEST_F(GainMapMathTest, Bt2100RgbToYuv) {
691 Color yuv_black = bt2100RgbToYuv(RgbBlack());
692 EXPECT_YUV_NEAR(yuv_black, YuvBlack());
693
694 Color yuv_white = bt2100RgbToYuv(RgbWhite());
695 EXPECT_YUV_NEAR(yuv_white, YuvWhite());
696
697 Color yuv_r = bt2100RgbToYuv(RgbRed());
698 EXPECT_YUV_NEAR(yuv_r, Bt2100YuvRed());
699
700 Color yuv_g = bt2100RgbToYuv(RgbGreen());
701 EXPECT_YUV_NEAR(yuv_g, Bt2100YuvGreen());
702
703 Color yuv_b = bt2100RgbToYuv(RgbBlue());
704 EXPECT_YUV_NEAR(yuv_b, Bt2100YuvBlue());
705 }
706
TEST_F(GainMapMathTest,Bt2100RgbYuvRoundtrip)707 TEST_F(GainMapMathTest, Bt2100RgbYuvRoundtrip) {
708 Color rgb_black = bt2100YuvToRgb(bt2100RgbToYuv(RgbBlack()));
709 EXPECT_RGB_NEAR(rgb_black, RgbBlack());
710
711 Color rgb_white = bt2100YuvToRgb(bt2100RgbToYuv(RgbWhite()));
712 EXPECT_RGB_NEAR(rgb_white, RgbWhite());
713
714 Color rgb_r = bt2100YuvToRgb(bt2100RgbToYuv(RgbRed()));
715 EXPECT_RGB_NEAR(rgb_r, RgbRed());
716
717 Color rgb_g = bt2100YuvToRgb(bt2100RgbToYuv(RgbGreen()));
718 EXPECT_RGB_NEAR(rgb_g, RgbGreen());
719
720 Color rgb_b = bt2100YuvToRgb(bt2100RgbToYuv(RgbBlue()));
721 EXPECT_RGB_NEAR(rgb_b, RgbBlue());
722 }
723
TEST_F(GainMapMathTest,YuvColorGamutConversion)724 TEST_F(GainMapMathTest, YuvColorGamutConversion) {
725 const std::array<Color, 5> SrgbYuvColors{YuvBlack(), YuvWhite(), SrgbYuvRed(), SrgbYuvGreen(),
726 SrgbYuvBlue()};
727
728 const std::array<Color, 5> P3YuvColors{YuvBlack(), YuvWhite(), P3YuvRed(), P3YuvGreen(),
729 P3YuvBlue()};
730
731 const std::array<Color, 5> Bt2100YuvColors{YuvBlack(), YuvWhite(), Bt2100YuvRed(),
732 Bt2100YuvGreen(), Bt2100YuvBlue()};
733 /*
734 * Each tuple contains three elements.
735 * 0. An array containing 9 coefficients needed to perform the color gamut conversion
736 * 1. Array of colors to be used as test input
737 * 2. Array of colors to used as reference output
738 */
739 const std::array<std::tuple<const std::array<float, 9>&, const std::array<Color, 5>,
740 const std::array<Color, 5>>,
741 6>
742 coeffs_setup_expected{{
743 {kYuvBt709ToBt601, SrgbYuvColors, P3YuvColors},
744 {kYuvBt709ToBt2100, SrgbYuvColors, Bt2100YuvColors},
745 {kYuvBt601ToBt709, P3YuvColors, SrgbYuvColors},
746 {kYuvBt601ToBt2100, P3YuvColors, Bt2100YuvColors},
747 {kYuvBt2100ToBt709, Bt2100YuvColors, SrgbYuvColors},
748 {kYuvBt2100ToBt601, Bt2100YuvColors, P3YuvColors},
749 }};
750
751 for (const auto& [coeffs, input, expected] : coeffs_setup_expected) {
752 for (size_t color_idx = 0; color_idx < SrgbYuvColors.size(); ++color_idx) {
753 const Color input_color = input.at(color_idx);
754 const Color output_color = yuvColorGamutConversion(input_color, coeffs);
755
756 EXPECT_YUV_NEAR(expected.at(color_idx), output_color);
757 }
758 }
759 }
760
761 #if (defined(UHDR_ENABLE_INTRINSICS) && (defined(__ARM_NEON__) || defined(__ARM_NEON)))
TEST_F(GainMapMathTest,YuvConversionNeon)762 TEST_F(GainMapMathTest, YuvConversionNeon) {
763 const std::array<Pixel, 5> SrgbYuvColors{YuvBlackPixel(), YuvWhitePixel(), SrgbYuvRedPixel(),
764 SrgbYuvGreenPixel(), SrgbYuvBluePixel()};
765
766 const std::array<Pixel, 5> P3YuvColors{YuvBlackPixel(), YuvWhitePixel(), P3YuvRedPixel(),
767 P3YuvGreenPixel(), P3YuvBluePixel()};
768
769 const std::array<Pixel, 5> Bt2100YuvColors{YuvBlackPixel(), YuvWhitePixel(), Bt2100YuvRedPixel(),
770 Bt2100YuvGreenPixel(), Bt2100YuvBluePixel()};
771
772 struct InputSamples {
773 std::array<uint8_t, 8> y;
774 std::array<int16_t, 8> u;
775 std::array<int16_t, 8> v;
776 };
777
778 struct ExpectedSamples {
779 std::array<int16_t, 8> y;
780 std::array<int16_t, 8> u;
781 std::array<int16_t, 8> v;
782 };
783
784 // Each tuple contains three elements.
785 // 0. A pointer to the coefficients that will be passed to the Neon implementation
786 // 1. Input pixel/color array
787 // 2. The expected results
788 const std::array<
789 std::tuple<const int16_t*, const std::array<Pixel, 5>, const std::array<Pixel, 5>>, 6>
790 coeffs_setup_correct{{
791 {kYuv709To601_coeffs_neon, SrgbYuvColors, P3YuvColors},
792 {kYuv709To2100_coeffs_neon, SrgbYuvColors, Bt2100YuvColors},
793 {kYuv601To709_coeffs_neon, P3YuvColors, SrgbYuvColors},
794 {kYuv601To2100_coeffs_neon, P3YuvColors, Bt2100YuvColors},
795 {kYuv2100To709_coeffs_neon, Bt2100YuvColors, SrgbYuvColors},
796 {kYuv2100To601_coeffs_neon, Bt2100YuvColors, P3YuvColors},
797 }};
798
799 for (const auto& [coeff_ptr, input, expected] : coeffs_setup_correct) {
800 const int16x8_t coeffs = vld1q_s16(coeff_ptr);
801 InputSamples input_values;
802 ExpectedSamples expected_values;
803 for (size_t sample_idx = 0; sample_idx < 8; ++sample_idx) {
804 size_t ring_idx = sample_idx % input.size();
805 input_values.y.at(sample_idx) = static_cast<uint8_t>(input.at(ring_idx).y);
806 input_values.u.at(sample_idx) = input.at(ring_idx).u;
807 input_values.v.at(sample_idx) = input.at(ring_idx).v;
808
809 expected_values.y.at(sample_idx) = expected.at(ring_idx).y;
810 expected_values.u.at(sample_idx) = expected.at(ring_idx).u;
811 expected_values.v.at(sample_idx) = expected.at(ring_idx).v;
812 }
813
814 const uint8x8_t y_neon = vld1_u8(input_values.y.data());
815 const int16x8_t u_neon = vld1q_s16(input_values.u.data());
816 const int16x8_t v_neon = vld1q_s16(input_values.v.data());
817
818 const int16x8x3_t neon_result = yuvConversion_neon(y_neon, u_neon, v_neon, coeffs);
819
820 const int16x8_t y_neon_result = neon_result.val[0];
821 const int16x8_t u_neon_result = neon_result.val[1];
822 const int16x8_t v_neon_result = neon_result.val[2];
823
824 const Pixel result0 = {vgetq_lane_s16(y_neon_result, 0), vgetq_lane_s16(u_neon_result, 0),
825 vgetq_lane_s16(v_neon_result, 0)};
826
827 const Pixel result1 = {vgetq_lane_s16(y_neon_result, 1), vgetq_lane_s16(u_neon_result, 1),
828 vgetq_lane_s16(v_neon_result, 1)};
829
830 const Pixel result2 = {vgetq_lane_s16(y_neon_result, 2), vgetq_lane_s16(u_neon_result, 2),
831 vgetq_lane_s16(v_neon_result, 2)};
832
833 const Pixel result3 = {vgetq_lane_s16(y_neon_result, 3), vgetq_lane_s16(u_neon_result, 3),
834 vgetq_lane_s16(v_neon_result, 3)};
835
836 const Pixel result4 = {vgetq_lane_s16(y_neon_result, 4), vgetq_lane_s16(u_neon_result, 4),
837 vgetq_lane_s16(v_neon_result, 4)};
838
839 const Pixel result5 = {vgetq_lane_s16(y_neon_result, 5), vgetq_lane_s16(u_neon_result, 5),
840 vgetq_lane_s16(v_neon_result, 5)};
841
842 const Pixel result6 = {vgetq_lane_s16(y_neon_result, 6), vgetq_lane_s16(u_neon_result, 6),
843 vgetq_lane_s16(v_neon_result, 6)};
844
845 const Pixel result7 = {vgetq_lane_s16(y_neon_result, 7), vgetq_lane_s16(u_neon_result, 7),
846 vgetq_lane_s16(v_neon_result, 7)};
847
848 EXPECT_NEAR(result0.y, expected_values.y.at(0), 1);
849 EXPECT_NEAR(result0.u, expected_values.u.at(0), 1);
850 EXPECT_NEAR(result0.v, expected_values.v.at(0), 1);
851
852 EXPECT_NEAR(result1.y, expected_values.y.at(1), 1);
853 EXPECT_NEAR(result1.u, expected_values.u.at(1), 1);
854 EXPECT_NEAR(result1.v, expected_values.v.at(1), 1);
855
856 EXPECT_NEAR(result2.y, expected_values.y.at(2), 1);
857 EXPECT_NEAR(result2.u, expected_values.u.at(2), 1);
858 EXPECT_NEAR(result2.v, expected_values.v.at(2), 1);
859
860 EXPECT_NEAR(result3.y, expected_values.y.at(3), 1);
861 EXPECT_NEAR(result3.u, expected_values.u.at(3), 1);
862 EXPECT_NEAR(result3.v, expected_values.v.at(3), 1);
863
864 EXPECT_NEAR(result4.y, expected_values.y.at(4), 1);
865 EXPECT_NEAR(result4.u, expected_values.u.at(4), 1);
866 EXPECT_NEAR(result4.v, expected_values.v.at(4), 1);
867
868 EXPECT_NEAR(result5.y, expected_values.y.at(5), 1);
869 EXPECT_NEAR(result5.u, expected_values.u.at(5), 1);
870 EXPECT_NEAR(result5.v, expected_values.v.at(5), 1);
871
872 EXPECT_NEAR(result6.y, expected_values.y.at(6), 1);
873 EXPECT_NEAR(result6.u, expected_values.u.at(6), 1);
874 EXPECT_NEAR(result6.v, expected_values.v.at(6), 1);
875
876 EXPECT_NEAR(result7.y, expected_values.y.at(7), 1);
877 EXPECT_NEAR(result7.u, expected_values.u.at(7), 1);
878 EXPECT_NEAR(result7.v, expected_values.v.at(7), 1);
879 }
880 }
881 #endif
882
TEST_F(GainMapMathTest,TransformYuv420)883 TEST_F(GainMapMathTest, TransformYuv420) {
884 auto input = Yuv420Image();
885 const size_t buf_size = input.w * input.h * 3 / 2;
886 std::unique_ptr<uint8_t[]> out_buf = std::make_unique<uint8_t[]>(buf_size);
887 uint8_t* luma = out_buf.get();
888 uint8_t* cb = luma + input.w * input.h;
889 uint8_t* cr = cb + input.w * input.h / 4;
890
891 const std::array<std::array<float, 9>, 6> conversion_coeffs = {
892 kYuvBt709ToBt601, kYuvBt709ToBt2100, kYuvBt601ToBt709,
893 kYuvBt601ToBt2100, kYuvBt2100ToBt709, kYuvBt2100ToBt601};
894
895 for (size_t coeffs_idx = 0; coeffs_idx < conversion_coeffs.size(); ++coeffs_idx) {
896 auto output = Yuv420Image();
897 memcpy(luma, input.planes[UHDR_PLANE_Y], input.w * input.h);
898 memcpy(cb, input.planes[UHDR_PLANE_U], input.w * input.h / 4);
899 memcpy(cr, input.planes[UHDR_PLANE_V], input.w * input.h / 4);
900 output.planes[UHDR_PLANE_Y] = luma;
901 output.planes[UHDR_PLANE_U] = cb;
902 output.planes[UHDR_PLANE_V] = cr;
903
904 // Perform a color gamut conversion to the entire 4:2:0 image.
905 transformYuv420(&output, conversion_coeffs.at(coeffs_idx));
906
907 for (size_t y = 0; y < input.h; y += 2) {
908 for (size_t x = 0; x < input.w; x += 2) {
909 Pixel out1 = getYuv420Pixel_uint(&output, x, y);
910 Pixel out2 = getYuv420Pixel_uint(&output, x + 1, y);
911 Pixel out3 = getYuv420Pixel_uint(&output, x, y + 1);
912 Pixel out4 = getYuv420Pixel_uint(&output, x + 1, y + 1);
913
914 Color in1 = getYuv420Pixel(&input, x, y);
915 Color in2 = getYuv420Pixel(&input, x + 1, y);
916 Color in3 = getYuv420Pixel(&input, x, y + 1);
917 Color in4 = getYuv420Pixel(&input, x + 1, y + 1);
918
919 in1 = yuvColorGamutConversion(in1, conversion_coeffs.at(coeffs_idx));
920 in2 = yuvColorGamutConversion(in2, conversion_coeffs.at(coeffs_idx));
921 in3 = yuvColorGamutConversion(in3, conversion_coeffs.at(coeffs_idx));
922 in4 = yuvColorGamutConversion(in4, conversion_coeffs.at(coeffs_idx));
923
924 // Clamp and reduce to uint8_t from float.
925 uint8_t expect_y1 = static_cast<uint8_t>(CLIP3((in1.y * 255.0f + 0.5f), 0, 255));
926 uint8_t expect_y2 = static_cast<uint8_t>(CLIP3((in2.y * 255.0f + 0.5f), 0, 255));
927 uint8_t expect_y3 = static_cast<uint8_t>(CLIP3((in3.y * 255.0f + 0.5f), 0, 255));
928 uint8_t expect_y4 = static_cast<uint8_t>(CLIP3((in4.y * 255.0f + 0.5f), 0, 255));
929
930 // Allow an absolute difference of 1 to allow for implmentations using a fixed-point
931 // approximation.
932 EXPECT_NEAR(expect_y1, out1.y, 1);
933 EXPECT_NEAR(expect_y2, out2.y, 1);
934 EXPECT_NEAR(expect_y3, out3.y, 1);
935 EXPECT_NEAR(expect_y4, out4.y, 1);
936
937 Color expect_uv = (in1 + in2 + in3 + in4) / 4.0f;
938
939 uint8_t expect_u =
940 static_cast<uint8_t>(CLIP3((expect_uv.u * 255.0f + 128.0f + 0.5f), 0, 255));
941 uint8_t expect_v =
942 static_cast<uint8_t>(CLIP3((expect_uv.v * 255.0f + 128.0f + 0.5f), 0, 255));
943
944 EXPECT_NEAR(expect_u, out1.u, 1);
945 EXPECT_NEAR(expect_u, out2.u, 1);
946 EXPECT_NEAR(expect_u, out3.u, 1);
947 EXPECT_NEAR(expect_u, out4.u, 1);
948
949 EXPECT_NEAR(expect_v, out1.v, 1);
950 EXPECT_NEAR(expect_v, out2.v, 1);
951 EXPECT_NEAR(expect_v, out3.v, 1);
952 EXPECT_NEAR(expect_v, out4.v, 1);
953 }
954 }
955 }
956 }
957
958 #if (defined(UHDR_ENABLE_INTRINSICS) && (defined(__ARM_NEON__) || defined(__ARM_NEON)))
TEST_F(GainMapMathTest,TransformYuv420Neon)959 TEST_F(GainMapMathTest, TransformYuv420Neon) {
960 const std::array<std::pair<const int16_t*, const std::array<float, 9>>, 6> fixed_floating_coeffs{
961 {{kYuv709To601_coeffs_neon, kYuvBt709ToBt601},
962 {kYuv709To2100_coeffs_neon, kYuvBt709ToBt2100},
963 {kYuv601To709_coeffs_neon, kYuvBt601ToBt709},
964 {kYuv601To2100_coeffs_neon, kYuvBt601ToBt2100},
965 {kYuv2100To709_coeffs_neon, kYuvBt2100ToBt709},
966 {kYuv2100To601_coeffs_neon, kYuvBt2100ToBt601}}};
967
968 for (const auto& [neon_coeffs_ptr, floating_point_coeffs] : fixed_floating_coeffs) {
969 uhdr_raw_image_t input = Yuv420Image32x4();
970 const size_t buf_size = input.w * input.h * 3 / 2;
971 std::unique_ptr<uint8_t[]> out_buf = std::make_unique<uint8_t[]>(buf_size);
972 uint8_t* luma = out_buf.get();
973 uint8_t* cb = luma + input.w * input.h;
974 uint8_t* cr = cb + input.w * input.h / 4;
975
976 uhdr_raw_image_t output = Yuv420Image32x4();
977 memcpy(luma, input.planes[UHDR_PLANE_Y], input.w * input.h);
978 memcpy(cb, input.planes[UHDR_PLANE_U], input.w * input.h / 4);
979 memcpy(cr, input.planes[UHDR_PLANE_V], input.w * input.h / 4);
980 output.planes[UHDR_PLANE_Y] = luma;
981 output.planes[UHDR_PLANE_U] = cb;
982 output.planes[UHDR_PLANE_V] = cr;
983
984 transformYuv420_neon(&output, neon_coeffs_ptr);
985
986 for (size_t y = 0; y < input.h / 2; ++y) {
987 for (size_t x = 0; x < input.w / 2; ++x) {
988 const Pixel out1 = getYuv420Pixel_uint(&output, x * 2, y * 2);
989 const Pixel out2 = getYuv420Pixel_uint(&output, x * 2 + 1, y * 2);
990 const Pixel out3 = getYuv420Pixel_uint(&output, x * 2, y * 2 + 1);
991 const Pixel out4 = getYuv420Pixel_uint(&output, x * 2 + 1, y * 2 + 1);
992
993 Color in1 = getYuv420Pixel(&input, x * 2, y * 2);
994 Color in2 = getYuv420Pixel(&input, x * 2 + 1, y * 2);
995 Color in3 = getYuv420Pixel(&input, x * 2, y * 2 + 1);
996 Color in4 = getYuv420Pixel(&input, x * 2 + 1, y * 2 + 1);
997
998 in1 = yuvColorGamutConversion(in1, floating_point_coeffs);
999 in2 = yuvColorGamutConversion(in2, floating_point_coeffs);
1000 in3 = yuvColorGamutConversion(in3, floating_point_coeffs);
1001 in4 = yuvColorGamutConversion(in4, floating_point_coeffs);
1002
1003 const Color expect_uv = (in1 + in2 + in3 + in4) / 4.0f;
1004
1005 const uint8_t expect_y1 = static_cast<uint8_t>(CLIP3(in1.y * 255.0f + 0.5f, 0, 255));
1006 const uint8_t expect_y2 = static_cast<uint8_t>(CLIP3(in2.y * 255.0f + 0.5f, 0, 255));
1007 const uint8_t expect_y3 = static_cast<uint8_t>(CLIP3(in3.y * 255.0f + 0.5f, 0, 255));
1008 const uint8_t expect_y4 = static_cast<uint8_t>(CLIP3(in4.y * 255.0f + 0.5f, 0, 255));
1009
1010 const uint8_t expect_u =
1011 static_cast<uint8_t>(CLIP3(expect_uv.u * 255.0f + 128.0f + 0.5f, 0, 255));
1012 const uint8_t expect_v =
1013 static_cast<uint8_t>(CLIP3(expect_uv.v * 255.0f + 128.0f + 0.5f, 0, 255));
1014
1015 // Due to the Neon version using a fixed-point approximation, this can result in an off by
1016 // one error compared with the standard floating-point version.
1017 EXPECT_NEAR(expect_y1, out1.y, 1);
1018 EXPECT_NEAR(expect_y2, out2.y, 1);
1019 EXPECT_NEAR(expect_y3, out3.y, 1);
1020 EXPECT_NEAR(expect_y4, out4.y, 1);
1021
1022 EXPECT_NEAR(expect_u, out1.u, 1);
1023 EXPECT_NEAR(expect_u, out2.u, 1);
1024 EXPECT_NEAR(expect_u, out3.u, 1);
1025 EXPECT_NEAR(expect_u, out4.u, 1);
1026
1027 EXPECT_NEAR(expect_v, out1.v, 1);
1028 EXPECT_NEAR(expect_v, out2.v, 1);
1029 EXPECT_NEAR(expect_v, out3.v, 1);
1030 EXPECT_NEAR(expect_v, out4.v, 1);
1031 }
1032 }
1033 }
1034 }
1035 #endif
1036
TEST_F(GainMapMathTest,HlgOetf)1037 TEST_F(GainMapMathTest, HlgOetf) {
1038 EXPECT_FLOAT_EQ(hlgOetf(0.0f), 0.0f);
1039 EXPECT_NEAR(hlgOetf(0.04167f), 0.35357f, ComparisonEpsilon());
1040 EXPECT_NEAR(hlgOetf(0.08333f), 0.5f, ComparisonEpsilon());
1041 EXPECT_NEAR(hlgOetf(0.5f), 0.87164f, ComparisonEpsilon());
1042 EXPECT_FLOAT_EQ(hlgOetf(1.0f), 1.0f);
1043
1044 Color e = {{{0.04167f, 0.08333f, 0.5f}}};
1045 Color e_gamma = {{{0.35357f, 0.5f, 0.87164f}}};
1046 EXPECT_RGB_NEAR(hlgOetf(e), e_gamma);
1047 }
1048
TEST_F(GainMapMathTest,HlgInvOetf)1049 TEST_F(GainMapMathTest, HlgInvOetf) {
1050 EXPECT_FLOAT_EQ(hlgInvOetf(0.0f), 0.0f);
1051 EXPECT_NEAR(hlgInvOetf(0.25f), 0.02083f, ComparisonEpsilon());
1052 EXPECT_NEAR(hlgInvOetf(0.5f), 0.08333f, ComparisonEpsilon());
1053 EXPECT_NEAR(hlgInvOetf(0.75f), 0.26496f, ComparisonEpsilon());
1054 EXPECT_FLOAT_EQ(hlgInvOetf(1.0f), 1.0f);
1055
1056 Color e_gamma = {{{0.25f, 0.5f, 0.75f}}};
1057 Color e = {{{0.02083f, 0.08333f, 0.26496f}}};
1058 EXPECT_RGB_NEAR(hlgInvOetf(e_gamma), e);
1059 }
1060
TEST_F(GainMapMathTest,HlgTransferFunctionRoundtrip)1061 TEST_F(GainMapMathTest, HlgTransferFunctionRoundtrip) {
1062 EXPECT_FLOAT_EQ(hlgInvOetf(hlgOetf(0.0f)), 0.0f);
1063 EXPECT_NEAR(hlgInvOetf(hlgOetf(0.04167f)), 0.04167f, ComparisonEpsilon());
1064 EXPECT_NEAR(hlgInvOetf(hlgOetf(0.08333f)), 0.08333f, ComparisonEpsilon());
1065 EXPECT_NEAR(hlgInvOetf(hlgOetf(0.5f)), 0.5f, ComparisonEpsilon());
1066 EXPECT_FLOAT_EQ(hlgInvOetf(hlgOetf(1.0f)), 1.0f);
1067 }
1068
TEST_F(GainMapMathTest,PqOetf)1069 TEST_F(GainMapMathTest, PqOetf) {
1070 EXPECT_FLOAT_EQ(pqOetf(0.0f), 0.0f);
1071 EXPECT_NEAR(pqOetf(0.01f), 0.50808f, ComparisonEpsilon());
1072 EXPECT_NEAR(pqOetf(0.5f), 0.92655f, ComparisonEpsilon());
1073 EXPECT_NEAR(pqOetf(0.99f), 0.99895f, ComparisonEpsilon());
1074 EXPECT_FLOAT_EQ(pqOetf(1.0f), 1.0f);
1075
1076 Color e = {{{0.01f, 0.5f, 0.99f}}};
1077 Color e_gamma = {{{0.50808f, 0.92655f, 0.99895f}}};
1078 EXPECT_RGB_NEAR(pqOetf(e), e_gamma);
1079 }
1080
TEST_F(GainMapMathTest,PqInvOetf)1081 TEST_F(GainMapMathTest, PqInvOetf) {
1082 EXPECT_FLOAT_EQ(pqInvOetf(0.0f), 0.0f);
1083 EXPECT_NEAR(pqInvOetf(0.01f), 2.31017e-7f, ComparisonEpsilon());
1084 EXPECT_NEAR(pqInvOetf(0.5f), 0.00922f, ComparisonEpsilon());
1085 EXPECT_NEAR(pqInvOetf(0.99f), 0.90903f, ComparisonEpsilon());
1086 EXPECT_FLOAT_EQ(pqInvOetf(1.0f), 1.0f);
1087
1088 Color e_gamma = {{{0.01f, 0.5f, 0.99f}}};
1089 Color e = {{{2.31017e-7f, 0.00922f, 0.90903f}}};
1090 EXPECT_RGB_NEAR(pqInvOetf(e_gamma), e);
1091 }
1092
TEST_F(GainMapMathTest,PqInvOetfLUT)1093 TEST_F(GainMapMathTest, PqInvOetfLUT) {
1094 for (size_t idx = 0; idx < kPqInvOETFNumEntries; idx++) {
1095 float value = static_cast<float>(idx) / static_cast<float>(kPqInvOETFNumEntries - 1);
1096 EXPECT_FLOAT_EQ(pqInvOetf(value), pqInvOetfLUT(value));
1097 }
1098 }
1099
TEST_F(GainMapMathTest,HlgInvOetfLUT)1100 TEST_F(GainMapMathTest, HlgInvOetfLUT) {
1101 for (size_t idx = 0; idx < kHlgInvOETFNumEntries; idx++) {
1102 float value = static_cast<float>(idx) / static_cast<float>(kHlgInvOETFNumEntries - 1);
1103 EXPECT_FLOAT_EQ(hlgInvOetf(value), hlgInvOetfLUT(value));
1104 }
1105 }
1106
TEST_F(GainMapMathTest,pqOetfLUT)1107 TEST_F(GainMapMathTest, pqOetfLUT) {
1108 for (size_t idx = 0; idx < kPqOETFNumEntries; idx++) {
1109 float value = static_cast<float>(idx) / static_cast<float>(kPqOETFNumEntries - 1);
1110 EXPECT_FLOAT_EQ(pqOetf(value), pqOetfLUT(value));
1111 }
1112 }
1113
TEST_F(GainMapMathTest,hlgOetfLUT)1114 TEST_F(GainMapMathTest, hlgOetfLUT) {
1115 for (size_t idx = 0; idx < kHlgOETFNumEntries; idx++) {
1116 float value = static_cast<float>(idx) / static_cast<float>(kHlgOETFNumEntries - 1);
1117 EXPECT_FLOAT_EQ(hlgOetf(value), hlgOetfLUT(value));
1118 }
1119 }
1120
TEST_F(GainMapMathTest,srgbInvOetfLUT)1121 TEST_F(GainMapMathTest, srgbInvOetfLUT) {
1122 for (size_t idx = 0; idx < kSrgbInvOETFNumEntries; idx++) {
1123 float value = static_cast<float>(idx) / static_cast<float>(kSrgbInvOETFNumEntries - 1);
1124 EXPECT_FLOAT_EQ(srgbInvOetf(value), srgbInvOetfLUT(value));
1125 }
1126 }
1127
TEST_F(GainMapMathTest,applyGainLUT)1128 TEST_F(GainMapMathTest, applyGainLUT) {
1129 for (float boost = 1.5; boost <= 12; boost++) {
1130 uhdr_gainmap_metadata_ext_t metadata;
1131
1132 metadata.min_content_boost = 1.0f / boost;
1133 metadata.max_content_boost = boost;
1134 metadata.gamma = 1.0f;
1135 metadata.hdr_capacity_max = metadata.max_content_boost;
1136 metadata.hdr_capacity_min = metadata.min_content_boost;
1137 GainLUT gainLUT(&metadata);
1138 float weight = (log2(boost) - log2(metadata.hdr_capacity_min)) /
1139 (log2(metadata.hdr_capacity_max) - log2(metadata.hdr_capacity_min));
1140 weight = CLIP3(weight, 0.0f, 1.0f);
1141 GainLUT gainLUTWithBoost(&metadata, weight);
1142 for (size_t idx = 0; idx < kGainFactorNumEntries; idx++) {
1143 float value = static_cast<float>(idx) / static_cast<float>(kGainFactorNumEntries - 1);
1144 EXPECT_RGB_NEAR(applyGain(RgbBlack(), value, &metadata),
1145 applyGainLUT(RgbBlack(), value, gainLUT, &metadata));
1146 EXPECT_RGB_NEAR(applyGain(RgbWhite(), value, &metadata),
1147 applyGainLUT(RgbWhite(), value, gainLUT, &metadata));
1148 EXPECT_RGB_NEAR(applyGain(RgbRed(), value, &metadata),
1149 applyGainLUT(RgbRed(), value, gainLUT, &metadata));
1150 EXPECT_RGB_NEAR(applyGain(RgbGreen(), value, &metadata),
1151 applyGainLUT(RgbGreen(), value, gainLUT, &metadata));
1152 EXPECT_RGB_NEAR(applyGain(RgbBlue(), value, &metadata),
1153 applyGainLUT(RgbBlue(), value, gainLUT, &metadata));
1154 EXPECT_RGB_NEAR(applyGain(RgbBlack(), value, &metadata, weight),
1155 applyGainLUT(RgbBlack(), value, gainLUTWithBoost, &metadata));
1156 EXPECT_RGB_NEAR(applyGain(RgbWhite(), value, &metadata, weight),
1157 applyGainLUT(RgbWhite(), value, gainLUTWithBoost, &metadata));
1158 EXPECT_RGB_NEAR(applyGain(RgbRed(), value, &metadata, weight),
1159 applyGainLUT(RgbRed(), value, gainLUTWithBoost, &metadata));
1160 EXPECT_RGB_NEAR(applyGain(RgbGreen(), value, &metadata, weight),
1161 applyGainLUT(RgbGreen(), value, gainLUTWithBoost, &metadata));
1162 EXPECT_RGB_NEAR(applyGain(RgbBlue(), value, &metadata, weight),
1163 applyGainLUT(RgbBlue(), value, gainLUTWithBoost, &metadata));
1164 }
1165 }
1166
1167 for (float boost = 1.5; boost <= 12; boost++) {
1168 uhdr_gainmap_metadata_ext_t metadata;
1169
1170 metadata.min_content_boost = 1.0f;
1171 metadata.max_content_boost = boost;
1172 metadata.gamma = 1.0f;
1173 metadata.hdr_capacity_max = metadata.max_content_boost;
1174 metadata.hdr_capacity_min = metadata.min_content_boost;
1175 GainLUT gainLUT(&metadata);
1176 float weight = (log2(boost) - log2(metadata.hdr_capacity_min)) /
1177 (log2(metadata.hdr_capacity_max) - log2(metadata.hdr_capacity_min));
1178 weight = CLIP3(weight, 0.0f, 1.0f);
1179 GainLUT gainLUTWithBoost(&metadata, weight);
1180 for (size_t idx = 0; idx < kGainFactorNumEntries; idx++) {
1181 float value = static_cast<float>(idx) / static_cast<float>(kGainFactorNumEntries - 1);
1182 EXPECT_RGB_NEAR(applyGain(RgbBlack(), value, &metadata),
1183 applyGainLUT(RgbBlack(), value, gainLUT, &metadata));
1184 EXPECT_RGB_NEAR(applyGain(RgbWhite(), value, &metadata),
1185 applyGainLUT(RgbWhite(), value, gainLUT, &metadata));
1186 EXPECT_RGB_NEAR(applyGain(RgbRed(), value, &metadata),
1187 applyGainLUT(RgbRed(), value, gainLUT, &metadata));
1188 EXPECT_RGB_NEAR(applyGain(RgbGreen(), value, &metadata),
1189 applyGainLUT(RgbGreen(), value, gainLUT, &metadata));
1190 EXPECT_RGB_NEAR(applyGain(RgbBlue(), value, &metadata),
1191 applyGainLUT(RgbBlue(), value, gainLUT, &metadata));
1192 EXPECT_RGB_NEAR(applyGain(RgbBlack(), value, &metadata, weight),
1193 applyGainLUT(RgbBlack(), value, gainLUTWithBoost, &metadata));
1194 EXPECT_RGB_NEAR(applyGain(RgbWhite(), value, &metadata, weight),
1195 applyGainLUT(RgbWhite(), value, gainLUTWithBoost, &metadata));
1196 EXPECT_RGB_NEAR(applyGain(RgbRed(), value, &metadata, weight),
1197 applyGainLUT(RgbRed(), value, gainLUTWithBoost, &metadata));
1198 EXPECT_RGB_NEAR(applyGain(RgbGreen(), value, &metadata, weight),
1199 applyGainLUT(RgbGreen(), value, gainLUTWithBoost, &metadata));
1200 EXPECT_RGB_NEAR(applyGain(RgbBlue(), value, &metadata, weight),
1201 applyGainLUT(RgbBlue(), value, gainLUTWithBoost, &metadata));
1202 }
1203 }
1204
1205 for (float boost = 1.5; boost <= 12; boost++) {
1206 uhdr_gainmap_metadata_ext_t metadata;
1207
1208 metadata.min_content_boost = 1.0f / powf(boost, 1.0f / 3.0f);
1209 metadata.max_content_boost = boost;
1210 metadata.gamma = 1.0f;
1211 metadata.hdr_capacity_max = metadata.max_content_boost;
1212 metadata.hdr_capacity_min = metadata.min_content_boost;
1213 GainLUT gainLUT(&metadata);
1214 float weight = (log2(boost) - log2(metadata.hdr_capacity_min)) /
1215 (log2(metadata.hdr_capacity_max) - log2(metadata.hdr_capacity_min));
1216 weight = CLIP3(weight, 0.0f, 1.0f);
1217 GainLUT gainLUTWithBoost(&metadata, weight);
1218 for (size_t idx = 0; idx < kGainFactorNumEntries; idx++) {
1219 float value = static_cast<float>(idx) / static_cast<float>(kGainFactorNumEntries - 1);
1220 EXPECT_RGB_NEAR(applyGain(RgbBlack(), value, &metadata),
1221 applyGainLUT(RgbBlack(), value, gainLUT, &metadata));
1222 EXPECT_RGB_NEAR(applyGain(RgbWhite(), value, &metadata),
1223 applyGainLUT(RgbWhite(), value, gainLUT, &metadata));
1224 EXPECT_RGB_NEAR(applyGain(RgbRed(), value, &metadata),
1225 applyGainLUT(RgbRed(), value, gainLUT, &metadata));
1226 EXPECT_RGB_NEAR(applyGain(RgbGreen(), value, &metadata),
1227 applyGainLUT(RgbGreen(), value, gainLUT, &metadata));
1228 EXPECT_RGB_NEAR(applyGain(RgbBlue(), value, &metadata),
1229 applyGainLUT(RgbBlue(), value, gainLUT, &metadata));
1230 EXPECT_RGB_NEAR(applyGain(RgbBlack(), value, &metadata, weight),
1231 applyGainLUT(RgbBlack(), value, gainLUTWithBoost, &metadata));
1232 EXPECT_RGB_NEAR(applyGain(RgbWhite(), value, &metadata, weight),
1233 applyGainLUT(RgbWhite(), value, gainLUTWithBoost, &metadata));
1234 EXPECT_RGB_NEAR(applyGain(RgbRed(), value, &metadata, weight),
1235 applyGainLUT(RgbRed(), value, gainLUTWithBoost, &metadata));
1236 EXPECT_RGB_NEAR(applyGain(RgbGreen(), value, &metadata, weight),
1237 applyGainLUT(RgbGreen(), value, gainLUTWithBoost, &metadata));
1238 EXPECT_RGB_NEAR(applyGain(RgbBlue(), value, &metadata, weight),
1239 applyGainLUT(RgbBlue(), value, gainLUTWithBoost, &metadata));
1240 }
1241 }
1242 }
1243
TEST_F(GainMapMathTest,PqTransferFunctionRoundtrip)1244 TEST_F(GainMapMathTest, PqTransferFunctionRoundtrip) {
1245 EXPECT_FLOAT_EQ(pqInvOetf(pqOetf(0.0f)), 0.0f);
1246 EXPECT_NEAR(pqInvOetf(pqOetf(0.01f)), 0.01f, ComparisonEpsilon());
1247 EXPECT_NEAR(pqInvOetf(pqOetf(0.5f)), 0.5f, ComparisonEpsilon());
1248 EXPECT_NEAR(pqInvOetf(pqOetf(0.99f)), 0.99f, ComparisonEpsilon());
1249 EXPECT_FLOAT_EQ(pqInvOetf(pqOetf(1.0f)), 1.0f);
1250 }
1251
TEST_F(GainMapMathTest,ColorConversionLookup)1252 TEST_F(GainMapMathTest, ColorConversionLookup) {
1253 EXPECT_EQ(getGamutConversionFn(UHDR_CG_BT_709, UHDR_CG_UNSPECIFIED), nullptr);
1254 EXPECT_EQ(getGamutConversionFn(UHDR_CG_BT_709, UHDR_CG_BT_709), identityConversion);
1255 EXPECT_EQ(getGamutConversionFn(UHDR_CG_BT_709, UHDR_CG_DISPLAY_P3), p3ToBt709);
1256 EXPECT_EQ(getGamutConversionFn(UHDR_CG_BT_709, UHDR_CG_BT_2100), bt2100ToBt709);
1257
1258 EXPECT_EQ(getGamutConversionFn(UHDR_CG_DISPLAY_P3, UHDR_CG_UNSPECIFIED), nullptr);
1259 EXPECT_EQ(getGamutConversionFn(UHDR_CG_DISPLAY_P3, UHDR_CG_BT_709), bt709ToP3);
1260 EXPECT_EQ(getGamutConversionFn(UHDR_CG_DISPLAY_P3, UHDR_CG_DISPLAY_P3), identityConversion);
1261 EXPECT_EQ(getGamutConversionFn(UHDR_CG_DISPLAY_P3, UHDR_CG_BT_2100), bt2100ToP3);
1262
1263 EXPECT_EQ(getGamutConversionFn(UHDR_CG_BT_2100, UHDR_CG_UNSPECIFIED), nullptr);
1264 EXPECT_EQ(getGamutConversionFn(UHDR_CG_BT_2100, UHDR_CG_BT_709), bt709ToBt2100);
1265 EXPECT_EQ(getGamutConversionFn(UHDR_CG_BT_2100, UHDR_CG_DISPLAY_P3), p3ToBt2100);
1266 EXPECT_EQ(getGamutConversionFn(UHDR_CG_BT_2100, UHDR_CG_BT_2100), identityConversion);
1267
1268 EXPECT_EQ(getGamutConversionFn(UHDR_CG_UNSPECIFIED, UHDR_CG_UNSPECIFIED), nullptr);
1269 EXPECT_EQ(getGamutConversionFn(UHDR_CG_UNSPECIFIED, UHDR_CG_BT_709), nullptr);
1270 EXPECT_EQ(getGamutConversionFn(UHDR_CG_UNSPECIFIED, UHDR_CG_DISPLAY_P3), nullptr);
1271 EXPECT_EQ(getGamutConversionFn(UHDR_CG_UNSPECIFIED, UHDR_CG_BT_2100), nullptr);
1272 }
1273
TEST_F(GainMapMathTest,EncodeGain)1274 TEST_F(GainMapMathTest, EncodeGain) {
1275 float min_boost = log2(1.0f / 4.0f);
1276 float max_boost = log2(4.0f);
1277 float gamma = 1.0f;
1278
1279 EXPECT_EQ(affineMapGain(computeGain(0.0f, 1.0f), min_boost, max_boost, 1.0f), 128);
1280 EXPECT_EQ(affineMapGain(computeGain(1.0f, 0.0f), min_boost, max_boost, 1.0f), 0);
1281 EXPECT_EQ(affineMapGain(computeGain(0.5f, 0.0f), min_boost, max_boost, 1.0f), 0);
1282 EXPECT_EQ(affineMapGain(computeGain(1.0f, 1.0), min_boost, max_boost, 1.0f), 128);
1283
1284 EXPECT_EQ(affineMapGain(computeGain(1.0f, 4.0f), min_boost, max_boost, 1.0f), 255);
1285 EXPECT_EQ(affineMapGain(computeGain(1.0f, 5.0f), min_boost, max_boost, 1.0f), 255);
1286 EXPECT_EQ(affineMapGain(computeGain(4.0f, 1.0f), min_boost, max_boost, 1.0f), 0);
1287 EXPECT_EQ(affineMapGain(computeGain(4.0f, 0.5f), min_boost, max_boost, 1.0f), 0);
1288 EXPECT_EQ(affineMapGain(computeGain(1.0f, 2.0f), min_boost, max_boost, 1.0f), 191);
1289 EXPECT_EQ(affineMapGain(computeGain(2.0f, 1.0f), min_boost, max_boost, 1.0f), 64);
1290
1291 min_boost = log2(1.0f / 2.0f);
1292 max_boost = log2(2.0f);
1293
1294 EXPECT_EQ(affineMapGain(computeGain(1.0f, 2.0f), min_boost, max_boost, 1.0f), 255);
1295 EXPECT_EQ(affineMapGain(computeGain(2.0f, 1.0f), min_boost, max_boost, 1.0f), 0);
1296 EXPECT_EQ(affineMapGain(computeGain(1.0f, 1.41421f), min_boost, max_boost, 1.0f), 191);
1297 EXPECT_EQ(affineMapGain(computeGain(1.41421f, 1.0f), min_boost, max_boost, 1.0f), 64);
1298
1299 min_boost = log2(1.0f / 8.0f);
1300 max_boost = log2(8.0f);
1301
1302 EXPECT_EQ(affineMapGain(computeGain(1.0f, 8.0f), min_boost, max_boost, 1.0f), 255);
1303 EXPECT_EQ(affineMapGain(computeGain(8.0f, 1.0f), min_boost, max_boost, 1.0f), 0);
1304 EXPECT_EQ(affineMapGain(computeGain(1.0f, 2.82843f), min_boost, max_boost, 1.0f), 191);
1305 EXPECT_EQ(affineMapGain(computeGain(2.82843f, 1.0f), min_boost, max_boost, 1.0f), 64);
1306
1307 min_boost = log2(1.0f);
1308 max_boost = log2(8.0f);
1309
1310 EXPECT_EQ(affineMapGain(computeGain(0.0f, 0.0f), min_boost, max_boost, 1.0f), 0);
1311 EXPECT_EQ(affineMapGain(computeGain(1.0f, 0.0f), min_boost, max_boost, 1.0f), 0);
1312 EXPECT_EQ(affineMapGain(computeGain(1.0f, 1.0f), min_boost, max_boost, 1.0f), 0);
1313 EXPECT_EQ(affineMapGain(computeGain(1.0f, 8.0f), min_boost, max_boost, 1.0f), 255);
1314 EXPECT_EQ(affineMapGain(computeGain(1.0f, 4.0f), min_boost, max_boost, 1.0f), 170);
1315 EXPECT_EQ(affineMapGain(computeGain(1.0f, 2.0f), min_boost, max_boost, 1.0f), 85);
1316
1317 min_boost = log2(1.0f / 2.0f);
1318 max_boost = log2(8.0f);
1319
1320 EXPECT_EQ(affineMapGain(computeGain(0.0f, 0.0f), min_boost, max_boost, 1.0f), 64);
1321 EXPECT_EQ(affineMapGain(computeGain(1.0f, 0.0f), min_boost, max_boost, 1.0f), 0);
1322 EXPECT_EQ(affineMapGain(computeGain(1.0f, 1.0f), min_boost, max_boost, 1.0f), 64);
1323 EXPECT_EQ(affineMapGain(computeGain(1.0f, 8.0f), min_boost, max_boost, 1.0f), 255);
1324 EXPECT_EQ(affineMapGain(computeGain(1.0f, 4.0f), min_boost, max_boost, 1.0f), 191);
1325 EXPECT_EQ(affineMapGain(computeGain(1.0f, 2.0f), min_boost, max_boost, 1.0f), 128);
1326 EXPECT_EQ(affineMapGain(computeGain(1.0f, 0.7071f), min_boost, max_boost, 1.0f), 32);
1327 EXPECT_EQ(affineMapGain(computeGain(1.0f, 0.5f), min_boost, max_boost, 1.0f), 0);
1328 }
1329
TEST_F(GainMapMathTest,ApplyGain)1330 TEST_F(GainMapMathTest, ApplyGain) {
1331 uhdr_gainmap_metadata_ext_t metadata;
1332
1333 metadata.min_content_boost = 1.0f / 4.0f;
1334 metadata.max_content_boost = 4.0f;
1335 metadata.hdr_capacity_max = metadata.max_content_boost;
1336 metadata.hdr_capacity_min = metadata.min_content_boost;
1337 metadata.offset_sdr = 0.0f;
1338 metadata.offset_hdr = 0.0f;
1339 metadata.gamma = 1.0f;
1340
1341 EXPECT_RGB_NEAR(applyGain(RgbBlack(), 0.0f, &metadata), RgbBlack());
1342 EXPECT_RGB_NEAR(applyGain(RgbBlack(), 0.5f, &metadata), RgbBlack());
1343 EXPECT_RGB_NEAR(applyGain(RgbBlack(), 1.0f, &metadata), RgbBlack());
1344
1345 EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.0f, &metadata), RgbWhite() / 4.0f);
1346 EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.25f, &metadata), RgbWhite() / 2.0f);
1347 EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.5f, &metadata), RgbWhite());
1348 EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.75f, &metadata), RgbWhite() * 2.0f);
1349 EXPECT_RGB_NEAR(applyGain(RgbWhite(), 1.0f, &metadata), RgbWhite() * 4.0f);
1350
1351 metadata.max_content_boost = 2.0f;
1352 metadata.min_content_boost = 1.0f / 2.0f;
1353 metadata.hdr_capacity_max = metadata.max_content_boost;
1354 metadata.hdr_capacity_min = metadata.min_content_boost;
1355
1356 EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.0f, &metadata), RgbWhite() / 2.0f);
1357 EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.25f, &metadata), RgbWhite() / 1.41421f);
1358 EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.5f, &metadata), RgbWhite());
1359 EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.75f, &metadata), RgbWhite() * 1.41421f);
1360 EXPECT_RGB_NEAR(applyGain(RgbWhite(), 1.0f, &metadata), RgbWhite() * 2.0f);
1361
1362 metadata.max_content_boost = 8.0f;
1363 metadata.min_content_boost = 1.0f / 8.0f;
1364 metadata.hdr_capacity_max = metadata.max_content_boost;
1365 metadata.hdr_capacity_min = metadata.min_content_boost;
1366
1367 EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.0f, &metadata), RgbWhite() / 8.0f);
1368 EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.25f, &metadata), RgbWhite() / 2.82843f);
1369 EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.5f, &metadata), RgbWhite());
1370 EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.75f, &metadata), RgbWhite() * 2.82843f);
1371 EXPECT_RGB_NEAR(applyGain(RgbWhite(), 1.0f, &metadata), RgbWhite() * 8.0f);
1372
1373 metadata.max_content_boost = 8.0f;
1374 metadata.min_content_boost = 1.0f;
1375 metadata.hdr_capacity_max = metadata.max_content_boost;
1376 metadata.hdr_capacity_min = metadata.min_content_boost;
1377
1378 EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.0f, &metadata), RgbWhite());
1379 EXPECT_RGB_NEAR(applyGain(RgbWhite(), 1.0f / 3.0f, &metadata), RgbWhite() * 2.0f);
1380 EXPECT_RGB_NEAR(applyGain(RgbWhite(), 2.0f / 3.0f, &metadata), RgbWhite() * 4.0f);
1381 EXPECT_RGB_NEAR(applyGain(RgbWhite(), 1.0f, &metadata), RgbWhite() * 8.0f);
1382
1383 metadata.max_content_boost = 8.0f;
1384 metadata.min_content_boost = 0.5f;
1385 metadata.hdr_capacity_max = metadata.max_content_boost;
1386 metadata.hdr_capacity_min = metadata.min_content_boost;
1387
1388 EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.0f, &metadata), RgbWhite() / 2.0f);
1389 EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.25f, &metadata), RgbWhite());
1390 EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.5f, &metadata), RgbWhite() * 2.0f);
1391 EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.75f, &metadata), RgbWhite() * 4.0f);
1392 EXPECT_RGB_NEAR(applyGain(RgbWhite(), 1.0f, &metadata), RgbWhite() * 8.0f);
1393
1394 Color e = {{{0.0f, 0.5f, 1.0f}}};
1395 metadata.max_content_boost = 4.0f;
1396 metadata.min_content_boost = 1.0f / 4.0f;
1397 metadata.hdr_capacity_max = metadata.max_content_boost;
1398 metadata.hdr_capacity_min = metadata.min_content_boost;
1399
1400 EXPECT_RGB_NEAR(applyGain(e, 0.0f, &metadata), e / 4.0f);
1401 EXPECT_RGB_NEAR(applyGain(e, 0.25f, &metadata), e / 2.0f);
1402 EXPECT_RGB_NEAR(applyGain(e, 0.5f, &metadata), e);
1403 EXPECT_RGB_NEAR(applyGain(e, 0.75f, &metadata), e * 2.0f);
1404 EXPECT_RGB_NEAR(applyGain(e, 1.0f, &metadata), e * 4.0f);
1405 }
1406
TEST_F(GainMapMathTest,GetYuv420Pixel)1407 TEST_F(GainMapMathTest, GetYuv420Pixel) {
1408 auto image = Yuv420Image();
1409 Color(*colors)[4] = Yuv420Colors();
1410
1411 for (size_t y = 0; y < 4; ++y) {
1412 for (size_t x = 0; x < 4; ++x) {
1413 EXPECT_YUV_NEAR(getYuv420Pixel(&image, x, y), colors[y][x]);
1414 }
1415 }
1416 }
1417
TEST_F(GainMapMathTest,GetP010Pixel)1418 TEST_F(GainMapMathTest, GetP010Pixel) {
1419 auto image = P010Image();
1420 Color(*colors)[4] = P010Colors();
1421
1422 for (size_t y = 0; y < 4; ++y) {
1423 for (size_t x = 0; x < 4; ++x) {
1424 EXPECT_YUV_NEAR(getP010Pixel(&image, x, y), colors[y][x]);
1425 }
1426 }
1427 }
1428
TEST_F(GainMapMathTest,SampleYuv420)1429 TEST_F(GainMapMathTest, SampleYuv420) {
1430 auto image = Yuv420Image();
1431 Color(*colors)[4] = Yuv420Colors();
1432
1433 static const size_t kMapScaleFactor = 2;
1434 for (size_t y = 0; y < 4 / kMapScaleFactor; ++y) {
1435 for (size_t x = 0; x < 4 / kMapScaleFactor; ++x) {
1436 Color min = {{{1.0f, 1.0f, 1.0f}}};
1437 Color max = {{{-1.0f, -1.0f, -1.0f}}};
1438
1439 for (size_t dy = 0; dy < kMapScaleFactor; ++dy) {
1440 for (size_t dx = 0; dx < kMapScaleFactor; ++dx) {
1441 Color e = colors[y * kMapScaleFactor + dy][x * kMapScaleFactor + dx];
1442 min = ColorMin(min, e);
1443 max = ColorMax(max, e);
1444 }
1445 }
1446
1447 // Instead of reimplementing the sampling algorithm, confirm that the
1448 // sample output is within the range of the min and max of the nearest
1449 // points.
1450 EXPECT_YUV_BETWEEN(sampleYuv420(&image, kMapScaleFactor, x, y), min, max);
1451 }
1452 }
1453 }
1454
TEST_F(GainMapMathTest,SampleP010)1455 TEST_F(GainMapMathTest, SampleP010) {
1456 auto image = P010Image();
1457 Color(*colors)[4] = P010Colors();
1458
1459 static const size_t kMapScaleFactor = 2;
1460 for (size_t y = 0; y < 4 / kMapScaleFactor; ++y) {
1461 for (size_t x = 0; x < 4 / kMapScaleFactor; ++x) {
1462 Color min = {{{1.0f, 1.0f, 1.0f}}};
1463 Color max = {{{-1.0f, -1.0f, -1.0f}}};
1464
1465 for (size_t dy = 0; dy < kMapScaleFactor; ++dy) {
1466 for (size_t dx = 0; dx < kMapScaleFactor; ++dx) {
1467 Color e = colors[y * kMapScaleFactor + dy][x * kMapScaleFactor + dx];
1468 min = ColorMin(min, e);
1469 max = ColorMax(max, e);
1470 }
1471 }
1472
1473 // Instead of reimplementing the sampling algorithm, confirm that the
1474 // sample output is within the range of the min and max of the nearest
1475 // points.
1476 EXPECT_YUV_BETWEEN(sampleP010(&image, kMapScaleFactor, x, y), min, max);
1477 }
1478 }
1479 }
1480
TEST_F(GainMapMathTest,SampleMap)1481 TEST_F(GainMapMathTest, SampleMap) {
1482 auto image = MapImage();
1483 float(*values)[4] = MapValues();
1484
1485 static const size_t kMapScaleFactor = 2;
1486 ShepardsIDW idwTable(kMapScaleFactor);
1487 for (size_t y = 0; y < 4 * kMapScaleFactor; ++y) {
1488 for (size_t x = 0; x < 4 * kMapScaleFactor; ++x) {
1489 size_t x_base = x / kMapScaleFactor;
1490 size_t y_base = y / kMapScaleFactor;
1491
1492 float min = 1.0f;
1493 float max = -1.0f;
1494
1495 min = fmin(min, values[y_base][x_base]);
1496 max = fmax(max, values[y_base][x_base]);
1497 if (y_base + 1 < 4) {
1498 min = fmin(min, values[y_base + 1][x_base]);
1499 max = fmax(max, values[y_base + 1][x_base]);
1500 }
1501 if (x_base + 1 < 4) {
1502 min = fmin(min, values[y_base][x_base + 1]);
1503 max = fmax(max, values[y_base][x_base + 1]);
1504 }
1505 if (y_base + 1 < 4 && x_base + 1 < 4) {
1506 min = fmin(min, values[y_base + 1][x_base + 1]);
1507 max = fmax(max, values[y_base + 1][x_base + 1]);
1508 }
1509
1510 // Instead of reimplementing the sampling algorithm, confirm that the
1511 // sample output is within the range of the min and max of the nearest
1512 // points.
1513 EXPECT_THAT(sampleMap(&image, kMapScaleFactor, x, y),
1514 testing::AllOf(testing::Ge(min), testing::Le(max)));
1515 EXPECT_EQ(sampleMap(&image, kMapScaleFactor, x, y, idwTable),
1516 sampleMap(&image, kMapScaleFactor, x, y));
1517 }
1518 }
1519 }
1520
TEST_F(GainMapMathTest,ColorToRgba1010102)1521 TEST_F(GainMapMathTest, ColorToRgba1010102) {
1522 EXPECT_EQ(colorToRgba1010102(RgbBlack()), 0x3 << 30);
1523 EXPECT_EQ(colorToRgba1010102(RgbWhite()), 0xFFFFFFFF);
1524 EXPECT_EQ(colorToRgba1010102(RgbRed()), 0x3 << 30 | 0x3ff);
1525 EXPECT_EQ(colorToRgba1010102(RgbGreen()), 0x3 << 30 | 0x3ff << 10);
1526 EXPECT_EQ(colorToRgba1010102(RgbBlue()), 0x3 << 30 | 0x3ff << 20);
1527
1528 Color e_gamma = {{{0.1f, 0.2f, 0.3f}}};
1529 EXPECT_EQ(colorToRgba1010102(e_gamma),
1530 0x3 << 30 | static_cast<uint32_t>(0.1f * static_cast<float>(0x3ff) + 0.5) |
1531 static_cast<uint32_t>(0.2f * static_cast<float>(0x3ff) + 0.5) << 10 |
1532 static_cast<uint32_t>(0.3f * static_cast<float>(0x3ff) + 0.5) << 20);
1533 }
1534
TEST_F(GainMapMathTest,ColorToRgbaF16)1535 TEST_F(GainMapMathTest, ColorToRgbaF16) {
1536 EXPECT_EQ(colorToRgbaF16(RgbBlack()), ((uint64_t)0x3C00) << 48);
1537 EXPECT_EQ(colorToRgbaF16(RgbWhite()), 0x3C003C003C003C00);
1538 EXPECT_EQ(colorToRgbaF16(RgbRed()), (((uint64_t)0x3C00) << 48) | ((uint64_t)0x3C00));
1539 EXPECT_EQ(colorToRgbaF16(RgbGreen()), (((uint64_t)0x3C00) << 48) | (((uint64_t)0x3C00) << 16));
1540 EXPECT_EQ(colorToRgbaF16(RgbBlue()), (((uint64_t)0x3C00) << 48) | (((uint64_t)0x3C00) << 32));
1541
1542 Color e_gamma = {{{0.1f, 0.2f, 0.3f}}};
1543 EXPECT_EQ(colorToRgbaF16(e_gamma), 0x3C0034CD32662E66);
1544 }
1545
TEST_F(GainMapMathTest,Float32ToFloat16)1546 TEST_F(GainMapMathTest, Float32ToFloat16) {
1547 EXPECT_EQ(floatToHalf(0.1f), 0x2E66);
1548 EXPECT_EQ(floatToHalf(0.0f), 0x0);
1549 EXPECT_EQ(floatToHalf(1.0f), 0x3C00);
1550 EXPECT_EQ(floatToHalf(-1.0f), 0xBC00);
1551 EXPECT_EQ(floatToHalf(0x1.fffffep127f), 0x7FFF); // float max
1552 EXPECT_EQ(floatToHalf(-0x1.fffffep127f), 0xFFFF); // float min
1553 EXPECT_EQ(floatToHalf(0x1.0p-126f), 0x0); // float zero
1554 }
1555
TEST_F(GainMapMathTest,GenerateMapLuminanceSrgb)1556 TEST_F(GainMapMathTest, GenerateMapLuminanceSrgb) {
1557 EXPECT_FLOAT_EQ(SrgbYuvToLuminance(YuvBlack(), srgbLuminance), 0.0f);
1558 EXPECT_FLOAT_EQ(SrgbYuvToLuminance(YuvWhite(), srgbLuminance), kSdrWhiteNits);
1559 EXPECT_NEAR(SrgbYuvToLuminance(SrgbYuvRed(), srgbLuminance),
1560 srgbLuminance(RgbRed()) * kSdrWhiteNits, LuminanceEpsilon());
1561 EXPECT_NEAR(SrgbYuvToLuminance(SrgbYuvGreen(), srgbLuminance),
1562 srgbLuminance(RgbGreen()) * kSdrWhiteNits, LuminanceEpsilon());
1563 EXPECT_NEAR(SrgbYuvToLuminance(SrgbYuvBlue(), srgbLuminance),
1564 srgbLuminance(RgbBlue()) * kSdrWhiteNits, LuminanceEpsilon());
1565 }
1566
TEST_F(GainMapMathTest,GenerateMapLuminanceSrgbP3)1567 TEST_F(GainMapMathTest, GenerateMapLuminanceSrgbP3) {
1568 EXPECT_FLOAT_EQ(SrgbYuvToLuminance(YuvBlack(), p3Luminance), 0.0f);
1569 EXPECT_FLOAT_EQ(SrgbYuvToLuminance(YuvWhite(), p3Luminance), kSdrWhiteNits);
1570 EXPECT_NEAR(SrgbYuvToLuminance(SrgbYuvRed(), p3Luminance), p3Luminance(RgbRed()) * kSdrWhiteNits,
1571 LuminanceEpsilon());
1572 EXPECT_NEAR(SrgbYuvToLuminance(SrgbYuvGreen(), p3Luminance),
1573 p3Luminance(RgbGreen()) * kSdrWhiteNits, LuminanceEpsilon());
1574 EXPECT_NEAR(SrgbYuvToLuminance(SrgbYuvBlue(), p3Luminance),
1575 p3Luminance(RgbBlue()) * kSdrWhiteNits, LuminanceEpsilon());
1576 }
1577
TEST_F(GainMapMathTest,GenerateMapLuminanceSrgbBt2100)1578 TEST_F(GainMapMathTest, GenerateMapLuminanceSrgbBt2100) {
1579 EXPECT_FLOAT_EQ(SrgbYuvToLuminance(YuvBlack(), bt2100Luminance), 0.0f);
1580 EXPECT_FLOAT_EQ(SrgbYuvToLuminance(YuvWhite(), bt2100Luminance), kSdrWhiteNits);
1581 EXPECT_NEAR(SrgbYuvToLuminance(SrgbYuvRed(), bt2100Luminance),
1582 bt2100Luminance(RgbRed()) * kSdrWhiteNits, LuminanceEpsilon());
1583 EXPECT_NEAR(SrgbYuvToLuminance(SrgbYuvGreen(), bt2100Luminance),
1584 bt2100Luminance(RgbGreen()) * kSdrWhiteNits, LuminanceEpsilon());
1585 EXPECT_NEAR(SrgbYuvToLuminance(SrgbYuvBlue(), bt2100Luminance),
1586 bt2100Luminance(RgbBlue()) * kSdrWhiteNits, LuminanceEpsilon());
1587 }
1588
TEST_F(GainMapMathTest,GenerateMapLuminanceHlg)1589 TEST_F(GainMapMathTest, GenerateMapLuminanceHlg) {
1590 EXPECT_FLOAT_EQ(Bt2100YuvToLuminance(YuvBlack(), hlgInvOetf, identityConversion, bt2100Luminance,
1591 kHlgMaxNits),
1592 0.0f);
1593 EXPECT_FLOAT_EQ(Bt2100YuvToLuminance(YuvWhite(), hlgInvOetf, identityConversion, bt2100Luminance,
1594 kHlgMaxNits),
1595 kHlgMaxNits);
1596 EXPECT_NEAR(Bt2100YuvToLuminance(Bt2100YuvRed(), hlgInvOetf, identityConversion, bt2100Luminance,
1597 kHlgMaxNits),
1598 bt2100Luminance(RgbRed()) * kHlgMaxNits, LuminanceEpsilon());
1599 EXPECT_NEAR(Bt2100YuvToLuminance(Bt2100YuvGreen(), hlgInvOetf, identityConversion,
1600 bt2100Luminance, kHlgMaxNits),
1601 bt2100Luminance(RgbGreen()) * kHlgMaxNits, LuminanceEpsilon());
1602 EXPECT_NEAR(Bt2100YuvToLuminance(Bt2100YuvBlue(), hlgInvOetf, identityConversion, bt2100Luminance,
1603 kHlgMaxNits),
1604 bt2100Luminance(RgbBlue()) * kHlgMaxNits, LuminanceEpsilon());
1605 }
1606
TEST_F(GainMapMathTest,GenerateMapLuminancePq)1607 TEST_F(GainMapMathTest, GenerateMapLuminancePq) {
1608 EXPECT_FLOAT_EQ(
1609 Bt2100YuvToLuminance(YuvBlack(), pqInvOetf, identityConversion, bt2100Luminance, kPqMaxNits),
1610 0.0f);
1611 EXPECT_FLOAT_EQ(
1612 Bt2100YuvToLuminance(YuvWhite(), pqInvOetf, identityConversion, bt2100Luminance, kPqMaxNits),
1613 kPqMaxNits);
1614 EXPECT_NEAR(Bt2100YuvToLuminance(Bt2100YuvRed(), pqInvOetf, identityConversion, bt2100Luminance,
1615 kPqMaxNits),
1616 bt2100Luminance(RgbRed()) * kPqMaxNits, LuminanceEpsilon());
1617 EXPECT_NEAR(Bt2100YuvToLuminance(Bt2100YuvGreen(), pqInvOetf, identityConversion, bt2100Luminance,
1618 kPqMaxNits),
1619 bt2100Luminance(RgbGreen()) * kPqMaxNits, LuminanceEpsilon());
1620 EXPECT_NEAR(Bt2100YuvToLuminance(Bt2100YuvBlue(), pqInvOetf, identityConversion, bt2100Luminance,
1621 kPqMaxNits),
1622 bt2100Luminance(RgbBlue()) * kPqMaxNits, LuminanceEpsilon());
1623 }
1624
TEST_F(GainMapMathTest,ApplyMap)1625 TEST_F(GainMapMathTest, ApplyMap) {
1626 uhdr_gainmap_metadata_ext_t metadata;
1627
1628 metadata.min_content_boost = 1.0f / 8.0f;
1629 metadata.max_content_boost = 8.0f;
1630 metadata.offset_sdr = 0.0f;
1631 metadata.offset_hdr = 0.0f;
1632 metadata.gamma = 1.0f;
1633
1634 EXPECT_RGB_EQ(Recover(YuvWhite(), 1.0f, &metadata), RgbWhite() * 8.0f);
1635 EXPECT_RGB_EQ(Recover(YuvBlack(), 1.0f, &metadata), RgbBlack());
1636 EXPECT_RGB_CLOSE(Recover(SrgbYuvRed(), 1.0f, &metadata), RgbRed() * 8.0f);
1637 EXPECT_RGB_CLOSE(Recover(SrgbYuvGreen(), 1.0f, &metadata), RgbGreen() * 8.0f);
1638 EXPECT_RGB_CLOSE(Recover(SrgbYuvBlue(), 1.0f, &metadata), RgbBlue() * 8.0f);
1639
1640 EXPECT_RGB_EQ(Recover(YuvWhite(), 0.75f, &metadata), RgbWhite() * sqrt(8.0f));
1641 EXPECT_RGB_EQ(Recover(YuvBlack(), 0.75f, &metadata), RgbBlack());
1642 EXPECT_RGB_CLOSE(Recover(SrgbYuvRed(), 0.75f, &metadata), RgbRed() * sqrt(8.0f));
1643 EXPECT_RGB_CLOSE(Recover(SrgbYuvGreen(), 0.75f, &metadata), RgbGreen() * sqrt(8.0f));
1644 EXPECT_RGB_CLOSE(Recover(SrgbYuvBlue(), 0.75f, &metadata), RgbBlue() * sqrt(8.0f));
1645
1646 EXPECT_RGB_EQ(Recover(YuvWhite(), 0.5f, &metadata), RgbWhite());
1647 EXPECT_RGB_EQ(Recover(YuvBlack(), 0.5f, &metadata), RgbBlack());
1648 EXPECT_RGB_CLOSE(Recover(SrgbYuvRed(), 0.5f, &metadata), RgbRed());
1649 EXPECT_RGB_CLOSE(Recover(SrgbYuvGreen(), 0.5f, &metadata), RgbGreen());
1650 EXPECT_RGB_CLOSE(Recover(SrgbYuvBlue(), 0.5f, &metadata), RgbBlue());
1651
1652 EXPECT_RGB_EQ(Recover(YuvWhite(), 0.25f, &metadata), RgbWhite() / sqrt(8.0f));
1653 EXPECT_RGB_EQ(Recover(YuvBlack(), 0.25f, &metadata), RgbBlack());
1654 EXPECT_RGB_CLOSE(Recover(SrgbYuvRed(), 0.25f, &metadata), RgbRed() / sqrt(8.0f));
1655 EXPECT_RGB_CLOSE(Recover(SrgbYuvGreen(), 0.25f, &metadata), RgbGreen() / sqrt(8.0f));
1656 EXPECT_RGB_CLOSE(Recover(SrgbYuvBlue(), 0.25f, &metadata), RgbBlue() / sqrt(8.0f));
1657
1658 EXPECT_RGB_EQ(Recover(YuvWhite(), 0.0f, &metadata), RgbWhite() / 8.0f);
1659 EXPECT_RGB_EQ(Recover(YuvBlack(), 0.0f, &metadata), RgbBlack());
1660 EXPECT_RGB_CLOSE(Recover(SrgbYuvRed(), 0.0f, &metadata), RgbRed() / 8.0f);
1661 EXPECT_RGB_CLOSE(Recover(SrgbYuvGreen(), 0.0f, &metadata), RgbGreen() / 8.0f);
1662 EXPECT_RGB_CLOSE(Recover(SrgbYuvBlue(), 0.0f, &metadata), RgbBlue() / 8.0f);
1663
1664 metadata.max_content_boost = 8.0f;
1665 metadata.min_content_boost = 1.0f;
1666
1667 EXPECT_RGB_EQ(Recover(YuvWhite(), 1.0f, &metadata), RgbWhite() * 8.0f);
1668 EXPECT_RGB_EQ(Recover(YuvWhite(), 2.0f / 3.0f, &metadata), RgbWhite() * 4.0f);
1669 EXPECT_RGB_EQ(Recover(YuvWhite(), 1.0f / 3.0f, &metadata), RgbWhite() * 2.0f);
1670 EXPECT_RGB_EQ(Recover(YuvWhite(), 0.0f, &metadata), RgbWhite());
1671
1672 metadata.max_content_boost = 8.0f;
1673 metadata.min_content_boost = 0.5f;
1674
1675 EXPECT_RGB_EQ(Recover(YuvWhite(), 1.0f, &metadata), RgbWhite() * 8.0f);
1676 EXPECT_RGB_EQ(Recover(YuvWhite(), 0.75, &metadata), RgbWhite() * 4.0f);
1677 EXPECT_RGB_EQ(Recover(YuvWhite(), 0.5f, &metadata), RgbWhite() * 2.0f);
1678 EXPECT_RGB_EQ(Recover(YuvWhite(), 0.25f, &metadata), RgbWhite());
1679 EXPECT_RGB_EQ(Recover(YuvWhite(), 0.0f, &metadata), RgbWhite() / 2.0f);
1680 }
1681
1682 } // namespace ultrahdr
1683