blob: bb058d336aa6326ac1229ede9413dfa0cc15c6b2 [file] [log] [blame]
maryla-uc50a54142023-08-29 15:37:16 +02001// Copyright 2023 Google LLC
2// SPDX-License-Identifier: BSD-2-Clause
3
maryla-ucbc6c2c52023-09-06 11:29:06 +02004#include <cmath>
maryla-uc50a54142023-08-29 15:37:16 +02005#include <fstream>
6
7#include "avif/avif.h"
maryla-ucbc6c2c52023-09-06 11:29:06 +02008#include "avif/gainmap.h"
maryla-uc50a54142023-08-29 15:37:16 +02009#include "avif/internal.h"
10#include "aviftest_helpers.h"
11#include "gtest/gtest.h"
12
13namespace libavif {
14namespace {
15
maryla-uc50a54142023-08-29 15:37:16 +020016void CheckGainMapMetadataMatches(const avifGainMapMetadata& lhs,
17 const avifGainMapMetadata& rhs) {
18 EXPECT_EQ(lhs.baseRenditionIsHDR, rhs.baseRenditionIsHDR);
19 EXPECT_EQ(lhs.hdrCapacityMinN, rhs.hdrCapacityMinN);
20 EXPECT_EQ(lhs.hdrCapacityMinD, rhs.hdrCapacityMinD);
21 EXPECT_EQ(lhs.hdrCapacityMaxN, rhs.hdrCapacityMaxN);
22 EXPECT_EQ(lhs.hdrCapacityMaxD, rhs.hdrCapacityMaxD);
23 for (int c = 0; c < 3; ++c) {
24 SCOPED_TRACE(c);
25 EXPECT_EQ(lhs.offsetSdrN[c], rhs.offsetSdrN[c]);
26 EXPECT_EQ(lhs.offsetSdrD[c], rhs.offsetSdrD[c]);
27 EXPECT_EQ(lhs.offsetHdrN[c], rhs.offsetHdrN[c]);
28 EXPECT_EQ(lhs.offsetHdrD[c], rhs.offsetHdrD[c]);
29 EXPECT_EQ(lhs.gainMapGammaN[c], rhs.gainMapGammaN[c]);
30 EXPECT_EQ(lhs.gainMapGammaD[c], rhs.gainMapGammaD[c]);
31 EXPECT_EQ(lhs.gainMapMinN[c], rhs.gainMapMinN[c]);
32 EXPECT_EQ(lhs.gainMapMinD[c], rhs.gainMapMinD[c]);
33 EXPECT_EQ(lhs.gainMapMaxN[c], rhs.gainMapMaxN[c]);
34 EXPECT_EQ(lhs.gainMapMaxD[c], rhs.gainMapMaxD[c]);
35 }
36}
37
38avifGainMapMetadata GetTestGainMapMetadata(bool base_rendition_is_hdr) {
39 avifGainMapMetadata metadata;
40 metadata.baseRenditionIsHDR = base_rendition_is_hdr;
41 metadata.hdrCapacityMinN = 1;
42 metadata.hdrCapacityMinD = 1;
43 metadata.hdrCapacityMaxN = 16;
44 metadata.hdrCapacityMaxD = 2;
45 for (int c = 0; c < 3; ++c) {
46 metadata.offsetSdrN[c] = 10 * c;
47 metadata.offsetSdrD[c] = 1000;
48 metadata.offsetHdrN[c] = 20 * c;
49 metadata.offsetHdrD[c] = 1000;
50 metadata.gainMapGammaN[c] = 1;
51 metadata.gainMapGammaD[c] = c + 1;
52 metadata.gainMapMinN[c] = 1;
53 metadata.gainMapMinD[c] = c + 1;
54 metadata.gainMapMaxN[c] = 10 + c + 1;
55 metadata.gainMapMaxD[c] = c + 1;
56 }
57 return metadata;
58}
59
maryla-ucd80be862023-09-05 17:58:51 +020060testutil::AvifImagePtr CreateTestImageWithGainMap(bool base_rendition_is_hdr) {
maryla-uc50a54142023-08-29 15:37:16 +020061 testutil::AvifImagePtr image =
62 testutil::CreateImage(/*width=*/12, /*height=*/34, /*depth=*/10,
63 AVIF_PIXEL_FORMAT_YUV420, AVIF_PLANES_ALL);
maryla-ucd80be862023-09-05 17:58:51 +020064 if (image == nullptr) {
65 return {nullptr, nullptr};
66 }
67 image->transferCharacteristics =
68 (avifTransferCharacteristics)(base_rendition_is_hdr
69 ? AVIF_TRANSFER_CHARACTERISTICS_SMPTE2084
70 : AVIF_TRANSFER_CHARACTERISTICS_SRGB);
maryla-uc50a54142023-08-29 15:37:16 +020071 testutil::FillImageGradient(image.get());
maryla-uc50a54142023-08-29 15:37:16 +020072 testutil::AvifImagePtr gain_map =
73 testutil::CreateImage(/*width=*/6, /*height=*/17, /*depth=*/8,
74 AVIF_PIXEL_FORMAT_YUV420, AVIF_PLANES_YUV);
maryla-ucd80be862023-09-05 17:58:51 +020075 if (gain_map == nullptr) {
76 return {nullptr, nullptr};
77 }
maryla-uc50a54142023-08-29 15:37:16 +020078 testutil::FillImageGradient(gain_map.get());
maryla-uc50a54142023-08-29 15:37:16 +020079 image->gainMap.image = gain_map.release(); // 'image' now owns the gain map.
maryla-ucd80be862023-09-05 17:58:51 +020080 image->gainMap.metadata = GetTestGainMapMetadata(base_rendition_is_hdr);
81
82 if (base_rendition_is_hdr) {
83 image->clli.maxCLL = 10;
84 image->clli.maxPALL = 5;
85 } else {
86 // Even though this is attached to the gain map, it represents the clli
87 // information of the tone mapped image.
88 image->gainMap.image->clli.maxCLL = 10;
89 image->gainMap.image->clli.maxPALL = 5;
90 }
91
92 return image;
93}
94
95TEST(GainMapTest, EncodeDecodeBaseImageSdr) {
96 testutil::AvifImagePtr image =
97 CreateTestImageWithGainMap(/*base_rendition_is_hdr=*/false);
maryla-uc50a54142023-08-29 15:37:16 +020098
99 testutil::AvifEncoderPtr encoder(avifEncoderCreate(), avifEncoderDestroy);
100 ASSERT_NE(encoder, nullptr);
101 testutil::AvifRwData encoded;
102 avifResult result = avifEncoderWrite(encoder.get(), image.get(), &encoded);
103 ASSERT_EQ(result, AVIF_RESULT_OK)
104 << avifResultToString(result) << " " << encoder->diag.error;
105
106 testutil::AvifImagePtr decoded(avifImageCreateEmpty(), avifImageDestroy);
107 ASSERT_NE(decoded, nullptr);
108 testutil::AvifDecoderPtr decoder(avifDecoderCreate(), avifDecoderDestroy);
109 ASSERT_NE(decoder, nullptr);
maryla-uc64109782023-09-13 14:05:46 +0200110 decoder->enableDecodingGainMap = AVIF_TRUE;
111 decoder->enableParsingGainMapMetadata = AVIF_TRUE;
maryla-uc50a54142023-08-29 15:37:16 +0200112 result = avifDecoderReadMemory(decoder.get(), decoded.get(), encoded.data,
113 encoded.size);
114 ASSERT_EQ(result, AVIF_RESULT_OK)
115 << avifResultToString(result) << " " << decoder->diag.error;
116
117 // Verify that the input and decoded images are close.
118 EXPECT_GT(testutil::GetPsnr(*image, *decoded), 40.0);
119 // Verify that the gain map is present and matches the input.
120 EXPECT_TRUE(decoder->gainMapPresent);
121 ASSERT_NE(decoded->gainMap.image, nullptr);
122 EXPECT_GT(testutil::GetPsnr(*image->gainMap.image, *decoded->gainMap.image),
123 40.0);
124 EXPECT_EQ(decoded->gainMap.image->matrixCoefficients,
125 image->gainMap.image->matrixCoefficients);
126 EXPECT_EQ(decoded->gainMap.image->clli.maxCLL,
127 image->gainMap.image->clli.maxCLL);
128 EXPECT_EQ(decoded->gainMap.image->clli.maxPALL,
129 image->gainMap.image->clli.maxPALL);
maryla-ucd80be862023-09-05 17:58:51 +0200130 CheckGainMapMetadataMatches(decoded->gainMap.metadata,
131 image->gainMap.metadata);
maryla-uc50a54142023-08-29 15:37:16 +0200132
133 // Uncomment the following to save the encoded image as an AVIF file.
134 // std::ofstream("/tmp/avifgainmaptest_basesdr.avif", std::ios::binary)
135 // .write(reinterpret_cast<char*>(encoded.data), encoded.size);
136}
137
138TEST(GainMapTest, EncodeDecodeBaseImageHdr) {
139 testutil::AvifImagePtr image =
maryla-ucd80be862023-09-05 17:58:51 +0200140 CreateTestImageWithGainMap(/*base_rendition_is_hdr=*/true);
maryla-uc50a54142023-08-29 15:37:16 +0200141
142 testutil::AvifEncoderPtr encoder(avifEncoderCreate(), avifEncoderDestroy);
143 ASSERT_NE(encoder, nullptr);
144 testutil::AvifRwData encoded;
145 avifResult result = avifEncoderWrite(encoder.get(), image.get(), &encoded);
146 ASSERT_EQ(result, AVIF_RESULT_OK)
147 << avifResultToString(result) << " " << encoder->diag.error;
148
149 testutil::AvifImagePtr decoded(avifImageCreateEmpty(), avifImageDestroy);
150 ASSERT_NE(decoded, nullptr);
151 testutil::AvifDecoderPtr decoder(avifDecoderCreate(), avifDecoderDestroy);
maryla-uc64109782023-09-13 14:05:46 +0200152 decoder->enableDecodingGainMap = AVIF_TRUE;
153 decoder->enableParsingGainMapMetadata = AVIF_TRUE;
maryla-uc50a54142023-08-29 15:37:16 +0200154 ASSERT_NE(decoder, nullptr);
155 result = avifDecoderReadMemory(decoder.get(), decoded.get(), encoded.data,
156 encoded.size);
157 ASSERT_EQ(result, AVIF_RESULT_OK)
158 << avifResultToString(result) << " " << decoder->diag.error;
159
160 // Verify that the input and decoded images are close.
161 EXPECT_GT(testutil::GetPsnr(*image, *decoded), 40.0);
162 // Verify that the gain map is present and matches the input.
163 EXPECT_TRUE(decoder->gainMapPresent);
164 ASSERT_NE(decoded->gainMap.image, nullptr);
165 EXPECT_GT(testutil::GetPsnr(*image->gainMap.image, *decoded->gainMap.image),
166 40.0);
167 EXPECT_EQ(decoded->clli.maxCLL, image->clli.maxCLL);
168 EXPECT_EQ(decoded->clli.maxPALL, image->clli.maxPALL);
maryla-ucd80be862023-09-05 17:58:51 +0200169 CheckGainMapMetadataMatches(decoded->gainMap.metadata,
170 image->gainMap.metadata);
maryla-uc50a54142023-08-29 15:37:16 +0200171
172 // Uncomment the following to save the encoded image as an AVIF file.
173 // std::ofstream("/tmp/avifgainmaptest_basehdr.avif", std::ios::binary)
174 // .write(reinterpret_cast<char*>(encoded.data), encoded.size);
175}
176
177TEST(GainMapTest, EncodeDecodeGrid) {
178 std::vector<testutil::AvifImagePtr> cells;
179 std::vector<const avifImage*> cell_ptrs;
180 std::vector<const avifImage*> gain_map_ptrs;
181 constexpr int kGridCols = 2;
182 constexpr int kGridRows = 2;
183
184 avifGainMapMetadata gain_map_metadata =
Yannis Guyon877e4d82023-08-30 14:14:34 +0200185 GetTestGainMapMetadata(/*base_rendition_is_hdr=*/true);
maryla-uc50a54142023-08-29 15:37:16 +0200186
187 for (int i = 0; i < kGridCols * kGridRows; ++i) {
188 testutil::AvifImagePtr image =
189 testutil::CreateImage(/*width=*/64, /*height=*/100, /*depth=*/10,
190 AVIF_PIXEL_FORMAT_YUV444, AVIF_PLANES_ALL);
191 ASSERT_NE(image, nullptr);
192 image->transferCharacteristics =
193 AVIF_TRANSFER_CHARACTERISTICS_SMPTE2084; // PQ
194 testutil::FillImageGradient(image.get());
195 testutil::AvifImagePtr gain_map =
196 testutil::CreateImage(/*width=*/64, /*height=*/100, /*depth=*/8,
197 AVIF_PIXEL_FORMAT_YUV420, AVIF_PLANES_YUV);
198 ASSERT_NE(gain_map, nullptr);
199 testutil::FillImageGradient(gain_map.get());
200 // 'image' now owns the gain map.
201 image->gainMap.image = gain_map.release();
202 // all cells must have the same metadata
203 image->gainMap.metadata = gain_map_metadata;
204
205 cell_ptrs.push_back(image.get());
206 gain_map_ptrs.push_back(image->gainMap.image);
207 cells.push_back(std::move(image));
208 }
209
210 testutil::AvifEncoderPtr encoder(avifEncoderCreate(), avifEncoderDestroy);
211 ASSERT_NE(encoder, nullptr);
212 testutil::AvifRwData encoded;
213 avifResult result =
214 avifEncoderAddImageGrid(encoder.get(), kGridCols, kGridRows,
215 cell_ptrs.data(), AVIF_ADD_IMAGE_FLAG_SINGLE);
216 ASSERT_EQ(result, AVIF_RESULT_OK)
217 << avifResultToString(result) << " " << encoder->diag.error;
218 result = avifEncoderFinish(encoder.get(), &encoded);
219 ASSERT_EQ(result, AVIF_RESULT_OK)
220 << avifResultToString(result) << " " << encoder->diag.error;
221
222 testutil::AvifImagePtr decoded(avifImageCreateEmpty(), avifImageDestroy);
223 ASSERT_NE(decoded, nullptr);
224 testutil::AvifDecoderPtr decoder(avifDecoderCreate(), avifDecoderDestroy);
225 ASSERT_NE(decoder, nullptr);
maryla-uc64109782023-09-13 14:05:46 +0200226 decoder->enableDecodingGainMap = AVIF_TRUE;
227 decoder->enableParsingGainMapMetadata = AVIF_TRUE;
maryla-uc50a54142023-08-29 15:37:16 +0200228 result = avifDecoderReadMemory(decoder.get(), decoded.get(), encoded.data,
229 encoded.size);
230 ASSERT_EQ(result, AVIF_RESULT_OK)
231 << avifResultToString(result) << " " << decoder->diag.error;
232
233 testutil::AvifImagePtr merged = testutil::CreateImage(
234 static_cast<int>(decoded->width), static_cast<int>(decoded->height),
235 decoded->depth, decoded->yuvFormat, AVIF_PLANES_ALL);
236 ASSERT_EQ(testutil::MergeGrid(kGridCols, kGridRows, cell_ptrs, merged.get()),
237 AVIF_RESULT_OK);
238
239 testutil::AvifImagePtr merged_gain_map =
240 testutil::CreateImage(static_cast<int>(decoded->gainMap.image->width),
241 static_cast<int>(decoded->gainMap.image->height),
242 decoded->gainMap.image->depth,
243 decoded->gainMap.image->yuvFormat, AVIF_PLANES_YUV);
244 ASSERT_EQ(testutil::MergeGrid(kGridCols, kGridRows, gain_map_ptrs,
245 merged_gain_map.get()),
246 AVIF_RESULT_OK);
247
248 // Verify that the input and decoded images are close.
249 ASSERT_GT(testutil::GetPsnr(*merged, *decoded), 40.0);
250 // Verify that the gain map is present and matches the input.
251 EXPECT_TRUE(decoder->gainMapPresent);
252 ASSERT_NE(decoded->gainMap.image, nullptr);
253 ASSERT_GT(testutil::GetPsnr(*merged_gain_map, *decoded->gainMap.image), 40.0);
maryla-ucd80be862023-09-05 17:58:51 +0200254 CheckGainMapMetadataMatches(decoded->gainMap.metadata, gain_map_metadata);
maryla-uc50a54142023-08-29 15:37:16 +0200255
256 // Uncomment the following to save the encoded image as an AVIF file.
257 // std::ofstream("/tmp/avifgainmaptest_grid.avif", std::ios::binary)
258 // .write(reinterpret_cast<char*>(encoded.data), encoded.size);
259}
260
261TEST(GainMapTest, InvalidGrid) {
262 std::vector<testutil::AvifImagePtr> cells;
263 std::vector<const avifImage*> cell_ptrs;
264 constexpr int kGridCols = 2;
265 constexpr int kGridRows = 2;
266
267 avifGainMapMetadata gain_map_metadata =
Yannis Guyon877e4d82023-08-30 14:14:34 +0200268 GetTestGainMapMetadata(/*base_rendition_is_hdr=*/true);
maryla-uc50a54142023-08-29 15:37:16 +0200269
270 for (int i = 0; i < kGridCols * kGridRows; ++i) {
271 testutil::AvifImagePtr image =
272 testutil::CreateImage(/*width=*/64, /*height=*/100, /*depth=*/10,
273 AVIF_PIXEL_FORMAT_YUV444, AVIF_PLANES_ALL);
274 ASSERT_NE(image, nullptr);
275 image->transferCharacteristics =
276 AVIF_TRANSFER_CHARACTERISTICS_SMPTE2084; // PQ
277 testutil::FillImageGradient(image.get());
278 testutil::AvifImagePtr gain_map =
279 testutil::CreateImage(/*width=*/64, /*height=*/100, /*depth=*/8,
280 AVIF_PIXEL_FORMAT_YUV420, AVIF_PLANES_YUV);
281 ASSERT_NE(gain_map, nullptr);
282 testutil::FillImageGradient(gain_map.get());
283 // 'image' now owns the gain map.
284 image->gainMap.image = gain_map.release();
285 // all cells must have the same metadata
286 image->gainMap.metadata = gain_map_metadata;
287
288 cell_ptrs.push_back(image.get());
289 cells.push_back(std::move(image));
290 }
291
292 testutil::AvifEncoderPtr encoder(avifEncoderCreate(), avifEncoderDestroy);
293 ASSERT_NE(encoder, nullptr);
294 testutil::AvifRwData encoded;
295
296 avifResult result;
297
298 // Invalid: one cell has the wrong size.
299 cells[1]->gainMap.image->height = 90;
300 result =
301 avifEncoderAddImageGrid(encoder.get(), kGridCols, kGridRows,
302 cell_ptrs.data(), AVIF_ADD_IMAGE_FLAG_SINGLE);
303 EXPECT_EQ(result, AVIF_RESULT_INVALID_IMAGE_GRID)
304 << avifResultToString(result) << " " << encoder->diag.error;
305 cells[1]->gainMap.image->height = cells[0]->gainMap.image->height; // Revert.
306
307 // Invalid: one cell has a different depth.
308 cells[1]->gainMap.image->depth = 12;
309 result =
310 avifEncoderAddImageGrid(encoder.get(), kGridCols, kGridRows,
311 cell_ptrs.data(), AVIF_ADD_IMAGE_FLAG_SINGLE);
312 EXPECT_EQ(result, AVIF_RESULT_INVALID_IMAGE_GRID)
313 << avifResultToString(result) << " " << encoder->diag.error;
314 cells[1]->gainMap.image->depth = cells[0]->gainMap.image->depth; // Revert.
315
316 // Invalid: one cell has different gain map metadata.
317 cells[1]->gainMap.metadata.gainMapGammaN[0] = 42;
318 result =
319 avifEncoderAddImageGrid(encoder.get(), kGridCols, kGridRows,
320 cell_ptrs.data(), AVIF_ADD_IMAGE_FLAG_SINGLE);
321 EXPECT_EQ(result, AVIF_RESULT_INVALID_IMAGE_GRID)
322 << avifResultToString(result) << " " << encoder->diag.error;
323 cells[1]->gainMap.metadata.gainMapGammaN[0] =
324 cells[0]->gainMap.metadata.gainMapGammaN[0]; // Revert.
325}
326
327TEST(GainMapTest, SequenceNotSupported) {
328 testutil::AvifImagePtr image =
329 testutil::CreateImage(/*width=*/64, /*height=*/100, /*depth=*/10,
330 AVIF_PIXEL_FORMAT_YUV444, AVIF_PLANES_ALL);
331 ASSERT_NE(image, nullptr);
332 image->transferCharacteristics =
333 AVIF_TRANSFER_CHARACTERISTICS_SMPTE2084; // PQ
334 testutil::FillImageGradient(image.get());
335 testutil::AvifImagePtr gain_map =
336 testutil::CreateImage(/*width=*/64, /*height=*/100, /*depth=*/8,
337 AVIF_PIXEL_FORMAT_YUV420, AVIF_PLANES_YUV);
338 ASSERT_NE(gain_map, nullptr);
339 testutil::FillImageGradient(gain_map.get());
340 // 'image' now owns the gain map.
341 image->gainMap.image = gain_map.release();
342
343 testutil::AvifEncoderPtr encoder(avifEncoderCreate(), avifEncoderDestroy);
344 ASSERT_NE(encoder, nullptr);
345 testutil::AvifRwData encoded;
346 // Add a first frame.
347 avifResult result =
348 avifEncoderAddImage(encoder.get(), image.get(),
349 /*durationInTimescales=*/2, AVIF_ADD_IMAGE_FLAG_NONE);
350 ASSERT_EQ(result, AVIF_RESULT_OK)
351 << avifResultToString(result) << " " << encoder->diag.error;
352 // Add a second frame.
353 result =
354 avifEncoderAddImage(encoder.get(), image.get(),
355 /*durationInTimescales=*/2, AVIF_ADD_IMAGE_FLAG_NONE);
356 // Image sequences with gain maps are not supported.
357 ASSERT_EQ(result, AVIF_RESULT_NOT_IMPLEMENTED)
358 << avifResultToString(result) << " " << encoder->diag.error;
359}
360
maryla-ucd80be862023-09-05 17:58:51 +0200361TEST(GainMapTest, IgnoreGainMap) {
362 testutil::AvifImagePtr image =
363 CreateTestImageWithGainMap(/*base_rendition_is_hdr=*/false);
364 ASSERT_NE(image, nullptr);
365
366 testutil::AvifEncoderPtr encoder(avifEncoderCreate(), avifEncoderDestroy);
367 ASSERT_NE(encoder, nullptr);
368 testutil::AvifRwData encoded;
369 avifResult result = avifEncoderWrite(encoder.get(), image.get(), &encoded);
370 ASSERT_EQ(result, AVIF_RESULT_OK)
371 << avifResultToString(result) << " " << encoder->diag.error;
372
maryla-uc64109782023-09-13 14:05:46 +0200373 // Decode image, with enableDecodingGainMap false by default.
maryla-ucd80be862023-09-05 17:58:51 +0200374 testutil::AvifImagePtr decoded(avifImageCreateEmpty(), avifImageDestroy);
375 ASSERT_NE(decoded, nullptr);
376 testutil::AvifDecoderPtr decoder(avifDecoderCreate(), avifDecoderDestroy);
377 ASSERT_NE(decoder, nullptr);
378 result = avifDecoderReadMemory(decoder.get(), decoded.get(), encoded.data,
379 encoded.size);
380 ASSERT_EQ(result, AVIF_RESULT_OK)
381 << avifResultToString(result) << " " << decoder->diag.error;
382
383 // Verify that the input and decoded images are close.
384 EXPECT_GT(testutil::GetPsnr(*image, *decoded), 40.0);
385 // Verify that the gain map was detected...
386 EXPECT_TRUE(decoder->gainMapPresent);
maryla-uc64109782023-09-13 14:05:46 +0200387 // ... but not decoded because enableDecodingGainMap is false by default.
maryla-ucd80be862023-09-05 17:58:51 +0200388 EXPECT_EQ(decoded->gainMap.image, nullptr);
389 // Check that the gain map metadata was not populated either.
390 CheckGainMapMetadataMatches(decoded->gainMap.metadata, avifGainMapMetadata());
391}
392
maryla-uc64109782023-09-13 14:05:46 +0200393TEST(GainMapTest, IgnoreGainMapButReadMetadata) {
394 testutil::AvifImagePtr image =
395 CreateTestImageWithGainMap(/*base_rendition_is_hdr=*/false);
396 ASSERT_NE(image, nullptr);
397
398 testutil::AvifEncoderPtr encoder(avifEncoderCreate(), avifEncoderDestroy);
399 ASSERT_NE(encoder, nullptr);
400 testutil::AvifRwData encoded;
401 avifResult result = avifEncoderWrite(encoder.get(), image.get(), &encoded);
402 ASSERT_EQ(result, AVIF_RESULT_OK)
403 << avifResultToString(result) << " " << encoder->diag.error;
404
405 // Decode image, with enableDecodingGainMap false by default.
406 testutil::AvifImagePtr decoded(avifImageCreateEmpty(), avifImageDestroy);
407 ASSERT_NE(decoded, nullptr);
408 testutil::AvifDecoderPtr decoder(avifDecoderCreate(), avifDecoderDestroy);
409 ASSERT_NE(decoder, nullptr);
410 decoder->enableParsingGainMapMetadata = AVIF_TRUE; // Read gain map metadata.
411 result = avifDecoderReadMemory(decoder.get(), decoded.get(), encoded.data,
412 encoded.size);
413 ASSERT_EQ(result, AVIF_RESULT_OK)
414 << avifResultToString(result) << " " << decoder->diag.error;
415
416 // Verify that the input and decoded images are close.
417 EXPECT_GT(testutil::GetPsnr(*image, *decoded), 40.0);
418 // Verify that the gain map was detected...
419 EXPECT_TRUE(decoder->gainMapPresent);
420 // ... but not decoded because enableDecodingGainMap is false by default.
421 EXPECT_EQ(decoded->gainMap.image, nullptr);
422 // Check that the gain map metadata WAS populated.
423 CheckGainMapMetadataMatches(decoded->gainMap.metadata,
424 image->gainMap.metadata);
425}
426
maryla-ucd80be862023-09-05 17:58:51 +0200427TEST(GainMapTest, IgnoreColorAndAlpha) {
428 testutil::AvifImagePtr image =
429 CreateTestImageWithGainMap(/*base_rendition_is_hdr=*/false);
430 ASSERT_NE(image, nullptr);
431
432 testutil::AvifEncoderPtr encoder(avifEncoderCreate(), avifEncoderDestroy);
433 ASSERT_NE(encoder, nullptr);
434 testutil::AvifRwData encoded;
435 avifResult result = avifEncoderWrite(encoder.get(), image.get(), &encoded);
436 ASSERT_EQ(result, AVIF_RESULT_OK)
437 << avifResultToString(result) << " " << encoder->diag.error;
438
439 testutil::AvifImagePtr decoded(avifImageCreateEmpty(), avifImageDestroy);
440 ASSERT_NE(decoded, nullptr);
441 testutil::AvifDecoderPtr decoder(avifDecoderCreate(), avifDecoderDestroy);
442 ASSERT_NE(decoder, nullptr);
443 // Decode just the gain map.
444 decoder->ignoreColorAndAlpha = AVIF_TRUE;
maryla-uc64109782023-09-13 14:05:46 +0200445 decoder->enableDecodingGainMap = AVIF_TRUE;
446 decoder->enableParsingGainMapMetadata = AVIF_TRUE;
maryla-ucd80be862023-09-05 17:58:51 +0200447 result = avifDecoderReadMemory(decoder.get(), decoded.get(), encoded.data,
448 encoded.size);
449 ASSERT_EQ(result, AVIF_RESULT_OK)
450 << avifResultToString(result) << " " << decoder->diag.error;
451
452 // Main image metadata is available.
453 EXPECT_EQ(decoder->image->width, 12u);
454 EXPECT_EQ(decoder->image->height, 34u);
455 // But pixels are not.
456 EXPECT_EQ(decoder->image->yuvRowBytes[0], 0u);
457 EXPECT_EQ(decoder->image->yuvRowBytes[1], 0u);
458 EXPECT_EQ(decoder->image->yuvRowBytes[2], 0u);
459 EXPECT_EQ(decoder->image->alphaRowBytes, 0u);
460 // The gain map was decoded.
461 EXPECT_TRUE(decoder->gainMapPresent);
462 ASSERT_NE(decoded->gainMap.image, nullptr);
463 EXPECT_GT(testutil::GetPsnr(*image->gainMap.image, *decoded->gainMap.image),
464 40.0);
465 CheckGainMapMetadataMatches(decoded->gainMap.metadata,
466 image->gainMap.metadata);
467}
468
469TEST(GainMapTest, IgnoreAll) {
470 testutil::AvifImagePtr image =
471 CreateTestImageWithGainMap(/*base_rendition_is_hdr=*/false);
472 ASSERT_NE(image, nullptr);
473
474 testutil::AvifEncoderPtr encoder(avifEncoderCreate(), avifEncoderDestroy);
475 ASSERT_NE(encoder, nullptr);
476 testutil::AvifRwData encoded;
477 avifResult result = avifEncoderWrite(encoder.get(), image.get(), &encoded);
478 ASSERT_EQ(result, AVIF_RESULT_OK)
479 << avifResultToString(result) << " " << encoder->diag.error;
480
maryla-ucd80be862023-09-05 17:58:51 +0200481 testutil::AvifDecoderPtr decoder(avifDecoderCreate(), avifDecoderDestroy);
482 ASSERT_NE(decoder, nullptr);
483 // Ignore both the main image and the gain map.
484 decoder->ignoreColorAndAlpha = AVIF_TRUE;
maryla-uc64109782023-09-13 14:05:46 +0200485 decoder->enableDecodingGainMap = AVIF_FALSE;
486 // But do read the gain map metadata.
487 decoder->enableParsingGainMapMetadata = AVIF_TRUE;
488
489 // Parsing just the header should work.
490 ASSERT_EQ(avifDecoderSetIOMemory(decoder.get(), encoded.data, encoded.size),
491 AVIF_RESULT_OK);
492 ASSERT_EQ(avifDecoderParse(decoder.get()), AVIF_RESULT_OK);
493
494 EXPECT_TRUE(decoder->gainMapPresent);
495 CheckGainMapMetadataMatches(decoder->image->gainMap.metadata,
496 image->gainMap.metadata);
497 ASSERT_EQ(decoder->image->gainMap.image, nullptr);
498
499 // But trying to access the next image should give an error because both
500 // ignoreColorAndAlpha and enableDecodingGainMap are set.
501 ASSERT_EQ(avifDecoderNextImage(decoder.get()), AVIF_RESULT_NO_CONTENT);
maryla-ucd80be862023-09-05 17:58:51 +0200502}
503
504TEST(GainMapTest, NoGainMap) {
505 // Create a simple image without a gain map.
506 testutil::AvifImagePtr image =
507 testutil::CreateImage(/*width=*/12, /*height=*/34, /*depth=*/10,
508 AVIF_PIXEL_FORMAT_YUV420, AVIF_PLANES_ALL);
509 ASSERT_NE(image, nullptr);
510 image->transferCharacteristics = AVIF_TRANSFER_CHARACTERISTICS_SRGB;
511 testutil::FillImageGradient(image.get());
512 testutil::AvifEncoderPtr encoder(avifEncoderCreate(), avifEncoderDestroy);
513 ASSERT_NE(encoder, nullptr);
514 testutil::AvifRwData encoded;
515 avifResult result = avifEncoderWrite(encoder.get(), image.get(), &encoded);
516 ASSERT_EQ(result, AVIF_RESULT_OK)
517 << avifResultToString(result) << " " << encoder->diag.error;
518
519 testutil::AvifImagePtr decoded(avifImageCreateEmpty(), avifImageDestroy);
520 ASSERT_NE(decoded, nullptr);
521 testutil::AvifDecoderPtr decoder(avifDecoderCreate(), avifDecoderDestroy);
522 ASSERT_NE(decoder, nullptr);
523 // Enable gain map decoding.
maryla-uc64109782023-09-13 14:05:46 +0200524 decoder->enableDecodingGainMap = AVIF_TRUE;
525 decoder->enableParsingGainMapMetadata = AVIF_TRUE;
maryla-ucd80be862023-09-05 17:58:51 +0200526 result = avifDecoderReadMemory(decoder.get(), decoded.get(), encoded.data,
527 encoded.size);
528 ASSERT_EQ(result, AVIF_RESULT_OK)
529 << avifResultToString(result) << " " << decoder->diag.error;
530
531 // Verify that the input and decoded images are close.
532 EXPECT_GT(testutil::GetPsnr(*image, *decoded), 40.0);
533 // Verify that no gain map was found.
534 EXPECT_FALSE(decoder->gainMapPresent);
535 EXPECT_EQ(decoded->gainMap.image, nullptr);
536 CheckGainMapMetadataMatches(decoded->gainMap.metadata, avifGainMapMetadata());
537}
538
maryla-ucbc6c2c52023-09-06 11:29:06 +0200539#define EXPECT_FRACTION_NEAR(numerator, denominator, expected) \
540 EXPECT_NEAR(std::abs((double)numerator / denominator), expected, \
541 expected * 0.001);
542
543TEST(GainMapTest, Convert) {
544 avifGainMapMetadataDouble metadata_double = {};
545 metadata_double.gainMapMin[0] = 1.0;
546 metadata_double.gainMapMin[1] = 1.1;
547 metadata_double.gainMapMin[2] = 1.2;
548 metadata_double.gainMapMax[0] = 10.0;
549 metadata_double.gainMapMax[1] = 10.1;
550 metadata_double.gainMapMax[2] = 10.2;
551 metadata_double.gainMapGamma[0] = 1.0;
552 metadata_double.gainMapGamma[1] = 1.0;
553 metadata_double.gainMapGamma[2] = 1.2;
554 metadata_double.offsetSdr[0] = 1.0 / 32.0;
555 metadata_double.offsetSdr[1] = 1.0 / 64.0;
556 metadata_double.offsetSdr[2] = 1.0 / 128.0;
557 metadata_double.offsetHdr[0] = 0.004564;
558 metadata_double.offsetHdr[1] = 0.0;
559 metadata_double.hdrCapacityMin = 1.0;
560 metadata_double.hdrCapacityMax = 10.0;
561 metadata_double.baseRenditionIsHDR = AVIF_TRUE;
562
563 avifGainMapMetadata metadata = {};
564 ASSERT_TRUE(
565 avifGainMapMetadataDoubleToFractions(&metadata, &metadata_double));
566
567 for (int i = 0; i < 3; ++i) {
568 EXPECT_FRACTION_NEAR(metadata.gainMapMinN[i], metadata.gainMapMinD[i],
569 metadata_double.gainMapMin[i]);
570 EXPECT_FRACTION_NEAR(metadata.gainMapMaxN[i], metadata.gainMapMaxD[i],
571 metadata_double.gainMapMax[i]);
572 EXPECT_FRACTION_NEAR(metadata.gainMapGammaN[i], metadata.gainMapGammaD[i],
573 metadata_double.gainMapGamma[i]);
574 EXPECT_FRACTION_NEAR(metadata.offsetSdrN[i], metadata.offsetSdrD[i],
575 metadata_double.offsetSdr[i]);
576 EXPECT_FRACTION_NEAR(metadata.offsetHdrN[i], metadata.offsetHdrD[i],
577 metadata_double.offsetHdr[i]);
578 }
579 EXPECT_FRACTION_NEAR(metadata.hdrCapacityMinN, metadata.hdrCapacityMinD,
580 metadata_double.hdrCapacityMin);
581 EXPECT_FRACTION_NEAR(metadata.hdrCapacityMaxN, metadata.hdrCapacityMaxD,
582 metadata_double.hdrCapacityMax);
583 EXPECT_EQ(metadata.baseRenditionIsHDR, metadata_double.baseRenditionIsHDR);
584}
585
586TEST(GainMapTest, Invalid) {
587 avifGainMapMetadataDouble metadata_double = {};
588 metadata_double.gainMapGamma[0] = -42; // A negative value is invalid!
589 avifGainMapMetadata metadata = {};
590 ASSERT_FALSE(
591 avifGainMapMetadataDoubleToFractions(&metadata, &metadata_double));
592}
593
maryla-uc50a54142023-08-29 15:37:16 +0200594} // namespace
595} // namespace libavif