blob: bb058d336aa6326ac1229ede9413dfa0cc15c6b2 [file] [log] [blame]
// Copyright 2023 Google LLC
// SPDX-License-Identifier: BSD-2-Clause
#include <cmath>
#include <fstream>
#include "avif/avif.h"
#include "avif/gainmap.h"
#include "avif/internal.h"
#include "aviftest_helpers.h"
#include "gtest/gtest.h"
namespace libavif {
namespace {
void CheckGainMapMetadataMatches(const avifGainMapMetadata& lhs,
const avifGainMapMetadata& rhs) {
EXPECT_EQ(lhs.baseRenditionIsHDR, rhs.baseRenditionIsHDR);
EXPECT_EQ(lhs.hdrCapacityMinN, rhs.hdrCapacityMinN);
EXPECT_EQ(lhs.hdrCapacityMinD, rhs.hdrCapacityMinD);
EXPECT_EQ(lhs.hdrCapacityMaxN, rhs.hdrCapacityMaxN);
EXPECT_EQ(lhs.hdrCapacityMaxD, rhs.hdrCapacityMaxD);
for (int c = 0; c < 3; ++c) {
SCOPED_TRACE(c);
EXPECT_EQ(lhs.offsetSdrN[c], rhs.offsetSdrN[c]);
EXPECT_EQ(lhs.offsetSdrD[c], rhs.offsetSdrD[c]);
EXPECT_EQ(lhs.offsetHdrN[c], rhs.offsetHdrN[c]);
EXPECT_EQ(lhs.offsetHdrD[c], rhs.offsetHdrD[c]);
EXPECT_EQ(lhs.gainMapGammaN[c], rhs.gainMapGammaN[c]);
EXPECT_EQ(lhs.gainMapGammaD[c], rhs.gainMapGammaD[c]);
EXPECT_EQ(lhs.gainMapMinN[c], rhs.gainMapMinN[c]);
EXPECT_EQ(lhs.gainMapMinD[c], rhs.gainMapMinD[c]);
EXPECT_EQ(lhs.gainMapMaxN[c], rhs.gainMapMaxN[c]);
EXPECT_EQ(lhs.gainMapMaxD[c], rhs.gainMapMaxD[c]);
}
}
avifGainMapMetadata GetTestGainMapMetadata(bool base_rendition_is_hdr) {
avifGainMapMetadata metadata;
metadata.baseRenditionIsHDR = base_rendition_is_hdr;
metadata.hdrCapacityMinN = 1;
metadata.hdrCapacityMinD = 1;
metadata.hdrCapacityMaxN = 16;
metadata.hdrCapacityMaxD = 2;
for (int c = 0; c < 3; ++c) {
metadata.offsetSdrN[c] = 10 * c;
metadata.offsetSdrD[c] = 1000;
metadata.offsetHdrN[c] = 20 * c;
metadata.offsetHdrD[c] = 1000;
metadata.gainMapGammaN[c] = 1;
metadata.gainMapGammaD[c] = c + 1;
metadata.gainMapMinN[c] = 1;
metadata.gainMapMinD[c] = c + 1;
metadata.gainMapMaxN[c] = 10 + c + 1;
metadata.gainMapMaxD[c] = c + 1;
}
return metadata;
}
testutil::AvifImagePtr CreateTestImageWithGainMap(bool base_rendition_is_hdr) {
testutil::AvifImagePtr image =
testutil::CreateImage(/*width=*/12, /*height=*/34, /*depth=*/10,
AVIF_PIXEL_FORMAT_YUV420, AVIF_PLANES_ALL);
if (image == nullptr) {
return {nullptr, nullptr};
}
image->transferCharacteristics =
(avifTransferCharacteristics)(base_rendition_is_hdr
? AVIF_TRANSFER_CHARACTERISTICS_SMPTE2084
: AVIF_TRANSFER_CHARACTERISTICS_SRGB);
testutil::FillImageGradient(image.get());
testutil::AvifImagePtr gain_map =
testutil::CreateImage(/*width=*/6, /*height=*/17, /*depth=*/8,
AVIF_PIXEL_FORMAT_YUV420, AVIF_PLANES_YUV);
if (gain_map == nullptr) {
return {nullptr, nullptr};
}
testutil::FillImageGradient(gain_map.get());
image->gainMap.image = gain_map.release(); // 'image' now owns the gain map.
image->gainMap.metadata = GetTestGainMapMetadata(base_rendition_is_hdr);
if (base_rendition_is_hdr) {
image->clli.maxCLL = 10;
image->clli.maxPALL = 5;
} else {
// Even though this is attached to the gain map, it represents the clli
// information of the tone mapped image.
image->gainMap.image->clli.maxCLL = 10;
image->gainMap.image->clli.maxPALL = 5;
}
return image;
}
TEST(GainMapTest, EncodeDecodeBaseImageSdr) {
testutil::AvifImagePtr image =
CreateTestImageWithGainMap(/*base_rendition_is_hdr=*/false);
testutil::AvifEncoderPtr encoder(avifEncoderCreate(), avifEncoderDestroy);
ASSERT_NE(encoder, nullptr);
testutil::AvifRwData encoded;
avifResult result = avifEncoderWrite(encoder.get(), image.get(), &encoded);
ASSERT_EQ(result, AVIF_RESULT_OK)
<< avifResultToString(result) << " " << encoder->diag.error;
testutil::AvifImagePtr decoded(avifImageCreateEmpty(), avifImageDestroy);
ASSERT_NE(decoded, nullptr);
testutil::AvifDecoderPtr decoder(avifDecoderCreate(), avifDecoderDestroy);
ASSERT_NE(decoder, nullptr);
decoder->enableDecodingGainMap = AVIF_TRUE;
decoder->enableParsingGainMapMetadata = AVIF_TRUE;
result = avifDecoderReadMemory(decoder.get(), decoded.get(), encoded.data,
encoded.size);
ASSERT_EQ(result, AVIF_RESULT_OK)
<< avifResultToString(result) << " " << decoder->diag.error;
// Verify that the input and decoded images are close.
EXPECT_GT(testutil::GetPsnr(*image, *decoded), 40.0);
// Verify that the gain map is present and matches the input.
EXPECT_TRUE(decoder->gainMapPresent);
ASSERT_NE(decoded->gainMap.image, nullptr);
EXPECT_GT(testutil::GetPsnr(*image->gainMap.image, *decoded->gainMap.image),
40.0);
EXPECT_EQ(decoded->gainMap.image->matrixCoefficients,
image->gainMap.image->matrixCoefficients);
EXPECT_EQ(decoded->gainMap.image->clli.maxCLL,
image->gainMap.image->clli.maxCLL);
EXPECT_EQ(decoded->gainMap.image->clli.maxPALL,
image->gainMap.image->clli.maxPALL);
CheckGainMapMetadataMatches(decoded->gainMap.metadata,
image->gainMap.metadata);
// Uncomment the following to save the encoded image as an AVIF file.
// std::ofstream("/tmp/avifgainmaptest_basesdr.avif", std::ios::binary)
// .write(reinterpret_cast<char*>(encoded.data), encoded.size);
}
TEST(GainMapTest, EncodeDecodeBaseImageHdr) {
testutil::AvifImagePtr image =
CreateTestImageWithGainMap(/*base_rendition_is_hdr=*/true);
testutil::AvifEncoderPtr encoder(avifEncoderCreate(), avifEncoderDestroy);
ASSERT_NE(encoder, nullptr);
testutil::AvifRwData encoded;
avifResult result = avifEncoderWrite(encoder.get(), image.get(), &encoded);
ASSERT_EQ(result, AVIF_RESULT_OK)
<< avifResultToString(result) << " " << encoder->diag.error;
testutil::AvifImagePtr decoded(avifImageCreateEmpty(), avifImageDestroy);
ASSERT_NE(decoded, nullptr);
testutil::AvifDecoderPtr decoder(avifDecoderCreate(), avifDecoderDestroy);
decoder->enableDecodingGainMap = AVIF_TRUE;
decoder->enableParsingGainMapMetadata = AVIF_TRUE;
ASSERT_NE(decoder, nullptr);
result = avifDecoderReadMemory(decoder.get(), decoded.get(), encoded.data,
encoded.size);
ASSERT_EQ(result, AVIF_RESULT_OK)
<< avifResultToString(result) << " " << decoder->diag.error;
// Verify that the input and decoded images are close.
EXPECT_GT(testutil::GetPsnr(*image, *decoded), 40.0);
// Verify that the gain map is present and matches the input.
EXPECT_TRUE(decoder->gainMapPresent);
ASSERT_NE(decoded->gainMap.image, nullptr);
EXPECT_GT(testutil::GetPsnr(*image->gainMap.image, *decoded->gainMap.image),
40.0);
EXPECT_EQ(decoded->clli.maxCLL, image->clli.maxCLL);
EXPECT_EQ(decoded->clli.maxPALL, image->clli.maxPALL);
CheckGainMapMetadataMatches(decoded->gainMap.metadata,
image->gainMap.metadata);
// Uncomment the following to save the encoded image as an AVIF file.
// std::ofstream("/tmp/avifgainmaptest_basehdr.avif", std::ios::binary)
// .write(reinterpret_cast<char*>(encoded.data), encoded.size);
}
TEST(GainMapTest, EncodeDecodeGrid) {
std::vector<testutil::AvifImagePtr> cells;
std::vector<const avifImage*> cell_ptrs;
std::vector<const avifImage*> gain_map_ptrs;
constexpr int kGridCols = 2;
constexpr int kGridRows = 2;
avifGainMapMetadata gain_map_metadata =
GetTestGainMapMetadata(/*base_rendition_is_hdr=*/true);
for (int i = 0; i < kGridCols * kGridRows; ++i) {
testutil::AvifImagePtr image =
testutil::CreateImage(/*width=*/64, /*height=*/100, /*depth=*/10,
AVIF_PIXEL_FORMAT_YUV444, AVIF_PLANES_ALL);
ASSERT_NE(image, nullptr);
image->transferCharacteristics =
AVIF_TRANSFER_CHARACTERISTICS_SMPTE2084; // PQ
testutil::FillImageGradient(image.get());
testutil::AvifImagePtr gain_map =
testutil::CreateImage(/*width=*/64, /*height=*/100, /*depth=*/8,
AVIF_PIXEL_FORMAT_YUV420, AVIF_PLANES_YUV);
ASSERT_NE(gain_map, nullptr);
testutil::FillImageGradient(gain_map.get());
// 'image' now owns the gain map.
image->gainMap.image = gain_map.release();
// all cells must have the same metadata
image->gainMap.metadata = gain_map_metadata;
cell_ptrs.push_back(image.get());
gain_map_ptrs.push_back(image->gainMap.image);
cells.push_back(std::move(image));
}
testutil::AvifEncoderPtr encoder(avifEncoderCreate(), avifEncoderDestroy);
ASSERT_NE(encoder, nullptr);
testutil::AvifRwData encoded;
avifResult result =
avifEncoderAddImageGrid(encoder.get(), kGridCols, kGridRows,
cell_ptrs.data(), AVIF_ADD_IMAGE_FLAG_SINGLE);
ASSERT_EQ(result, AVIF_RESULT_OK)
<< avifResultToString(result) << " " << encoder->diag.error;
result = avifEncoderFinish(encoder.get(), &encoded);
ASSERT_EQ(result, AVIF_RESULT_OK)
<< avifResultToString(result) << " " << encoder->diag.error;
testutil::AvifImagePtr decoded(avifImageCreateEmpty(), avifImageDestroy);
ASSERT_NE(decoded, nullptr);
testutil::AvifDecoderPtr decoder(avifDecoderCreate(), avifDecoderDestroy);
ASSERT_NE(decoder, nullptr);
decoder->enableDecodingGainMap = AVIF_TRUE;
decoder->enableParsingGainMapMetadata = AVIF_TRUE;
result = avifDecoderReadMemory(decoder.get(), decoded.get(), encoded.data,
encoded.size);
ASSERT_EQ(result, AVIF_RESULT_OK)
<< avifResultToString(result) << " " << decoder->diag.error;
testutil::AvifImagePtr merged = testutil::CreateImage(
static_cast<int>(decoded->width), static_cast<int>(decoded->height),
decoded->depth, decoded->yuvFormat, AVIF_PLANES_ALL);
ASSERT_EQ(testutil::MergeGrid(kGridCols, kGridRows, cell_ptrs, merged.get()),
AVIF_RESULT_OK);
testutil::AvifImagePtr merged_gain_map =
testutil::CreateImage(static_cast<int>(decoded->gainMap.image->width),
static_cast<int>(decoded->gainMap.image->height),
decoded->gainMap.image->depth,
decoded->gainMap.image->yuvFormat, AVIF_PLANES_YUV);
ASSERT_EQ(testutil::MergeGrid(kGridCols, kGridRows, gain_map_ptrs,
merged_gain_map.get()),
AVIF_RESULT_OK);
// Verify that the input and decoded images are close.
ASSERT_GT(testutil::GetPsnr(*merged, *decoded), 40.0);
// Verify that the gain map is present and matches the input.
EXPECT_TRUE(decoder->gainMapPresent);
ASSERT_NE(decoded->gainMap.image, nullptr);
ASSERT_GT(testutil::GetPsnr(*merged_gain_map, *decoded->gainMap.image), 40.0);
CheckGainMapMetadataMatches(decoded->gainMap.metadata, gain_map_metadata);
// Uncomment the following to save the encoded image as an AVIF file.
// std::ofstream("/tmp/avifgainmaptest_grid.avif", std::ios::binary)
// .write(reinterpret_cast<char*>(encoded.data), encoded.size);
}
TEST(GainMapTest, InvalidGrid) {
std::vector<testutil::AvifImagePtr> cells;
std::vector<const avifImage*> cell_ptrs;
constexpr int kGridCols = 2;
constexpr int kGridRows = 2;
avifGainMapMetadata gain_map_metadata =
GetTestGainMapMetadata(/*base_rendition_is_hdr=*/true);
for (int i = 0; i < kGridCols * kGridRows; ++i) {
testutil::AvifImagePtr image =
testutil::CreateImage(/*width=*/64, /*height=*/100, /*depth=*/10,
AVIF_PIXEL_FORMAT_YUV444, AVIF_PLANES_ALL);
ASSERT_NE(image, nullptr);
image->transferCharacteristics =
AVIF_TRANSFER_CHARACTERISTICS_SMPTE2084; // PQ
testutil::FillImageGradient(image.get());
testutil::AvifImagePtr gain_map =
testutil::CreateImage(/*width=*/64, /*height=*/100, /*depth=*/8,
AVIF_PIXEL_FORMAT_YUV420, AVIF_PLANES_YUV);
ASSERT_NE(gain_map, nullptr);
testutil::FillImageGradient(gain_map.get());
// 'image' now owns the gain map.
image->gainMap.image = gain_map.release();
// all cells must have the same metadata
image->gainMap.metadata = gain_map_metadata;
cell_ptrs.push_back(image.get());
cells.push_back(std::move(image));
}
testutil::AvifEncoderPtr encoder(avifEncoderCreate(), avifEncoderDestroy);
ASSERT_NE(encoder, nullptr);
testutil::AvifRwData encoded;
avifResult result;
// Invalid: one cell has the wrong size.
cells[1]->gainMap.image->height = 90;
result =
avifEncoderAddImageGrid(encoder.get(), kGridCols, kGridRows,
cell_ptrs.data(), AVIF_ADD_IMAGE_FLAG_SINGLE);
EXPECT_EQ(result, AVIF_RESULT_INVALID_IMAGE_GRID)
<< avifResultToString(result) << " " << encoder->diag.error;
cells[1]->gainMap.image->height = cells[0]->gainMap.image->height; // Revert.
// Invalid: one cell has a different depth.
cells[1]->gainMap.image->depth = 12;
result =
avifEncoderAddImageGrid(encoder.get(), kGridCols, kGridRows,
cell_ptrs.data(), AVIF_ADD_IMAGE_FLAG_SINGLE);
EXPECT_EQ(result, AVIF_RESULT_INVALID_IMAGE_GRID)
<< avifResultToString(result) << " " << encoder->diag.error;
cells[1]->gainMap.image->depth = cells[0]->gainMap.image->depth; // Revert.
// Invalid: one cell has different gain map metadata.
cells[1]->gainMap.metadata.gainMapGammaN[0] = 42;
result =
avifEncoderAddImageGrid(encoder.get(), kGridCols, kGridRows,
cell_ptrs.data(), AVIF_ADD_IMAGE_FLAG_SINGLE);
EXPECT_EQ(result, AVIF_RESULT_INVALID_IMAGE_GRID)
<< avifResultToString(result) << " " << encoder->diag.error;
cells[1]->gainMap.metadata.gainMapGammaN[0] =
cells[0]->gainMap.metadata.gainMapGammaN[0]; // Revert.
}
TEST(GainMapTest, SequenceNotSupported) {
testutil::AvifImagePtr image =
testutil::CreateImage(/*width=*/64, /*height=*/100, /*depth=*/10,
AVIF_PIXEL_FORMAT_YUV444, AVIF_PLANES_ALL);
ASSERT_NE(image, nullptr);
image->transferCharacteristics =
AVIF_TRANSFER_CHARACTERISTICS_SMPTE2084; // PQ
testutil::FillImageGradient(image.get());
testutil::AvifImagePtr gain_map =
testutil::CreateImage(/*width=*/64, /*height=*/100, /*depth=*/8,
AVIF_PIXEL_FORMAT_YUV420, AVIF_PLANES_YUV);
ASSERT_NE(gain_map, nullptr);
testutil::FillImageGradient(gain_map.get());
// 'image' now owns the gain map.
image->gainMap.image = gain_map.release();
testutil::AvifEncoderPtr encoder(avifEncoderCreate(), avifEncoderDestroy);
ASSERT_NE(encoder, nullptr);
testutil::AvifRwData encoded;
// Add a first frame.
avifResult result =
avifEncoderAddImage(encoder.get(), image.get(),
/*durationInTimescales=*/2, AVIF_ADD_IMAGE_FLAG_NONE);
ASSERT_EQ(result, AVIF_RESULT_OK)
<< avifResultToString(result) << " " << encoder->diag.error;
// Add a second frame.
result =
avifEncoderAddImage(encoder.get(), image.get(),
/*durationInTimescales=*/2, AVIF_ADD_IMAGE_FLAG_NONE);
// Image sequences with gain maps are not supported.
ASSERT_EQ(result, AVIF_RESULT_NOT_IMPLEMENTED)
<< avifResultToString(result) << " " << encoder->diag.error;
}
TEST(GainMapTest, IgnoreGainMap) {
testutil::AvifImagePtr image =
CreateTestImageWithGainMap(/*base_rendition_is_hdr=*/false);
ASSERT_NE(image, nullptr);
testutil::AvifEncoderPtr encoder(avifEncoderCreate(), avifEncoderDestroy);
ASSERT_NE(encoder, nullptr);
testutil::AvifRwData encoded;
avifResult result = avifEncoderWrite(encoder.get(), image.get(), &encoded);
ASSERT_EQ(result, AVIF_RESULT_OK)
<< avifResultToString(result) << " " << encoder->diag.error;
// Decode image, with enableDecodingGainMap false by default.
testutil::AvifImagePtr decoded(avifImageCreateEmpty(), avifImageDestroy);
ASSERT_NE(decoded, nullptr);
testutil::AvifDecoderPtr decoder(avifDecoderCreate(), avifDecoderDestroy);
ASSERT_NE(decoder, nullptr);
result = avifDecoderReadMemory(decoder.get(), decoded.get(), encoded.data,
encoded.size);
ASSERT_EQ(result, AVIF_RESULT_OK)
<< avifResultToString(result) << " " << decoder->diag.error;
// Verify that the input and decoded images are close.
EXPECT_GT(testutil::GetPsnr(*image, *decoded), 40.0);
// Verify that the gain map was detected...
EXPECT_TRUE(decoder->gainMapPresent);
// ... but not decoded because enableDecodingGainMap is false by default.
EXPECT_EQ(decoded->gainMap.image, nullptr);
// Check that the gain map metadata was not populated either.
CheckGainMapMetadataMatches(decoded->gainMap.metadata, avifGainMapMetadata());
}
TEST(GainMapTest, IgnoreGainMapButReadMetadata) {
testutil::AvifImagePtr image =
CreateTestImageWithGainMap(/*base_rendition_is_hdr=*/false);
ASSERT_NE(image, nullptr);
testutil::AvifEncoderPtr encoder(avifEncoderCreate(), avifEncoderDestroy);
ASSERT_NE(encoder, nullptr);
testutil::AvifRwData encoded;
avifResult result = avifEncoderWrite(encoder.get(), image.get(), &encoded);
ASSERT_EQ(result, AVIF_RESULT_OK)
<< avifResultToString(result) << " " << encoder->diag.error;
// Decode image, with enableDecodingGainMap false by default.
testutil::AvifImagePtr decoded(avifImageCreateEmpty(), avifImageDestroy);
ASSERT_NE(decoded, nullptr);
testutil::AvifDecoderPtr decoder(avifDecoderCreate(), avifDecoderDestroy);
ASSERT_NE(decoder, nullptr);
decoder->enableParsingGainMapMetadata = AVIF_TRUE; // Read gain map metadata.
result = avifDecoderReadMemory(decoder.get(), decoded.get(), encoded.data,
encoded.size);
ASSERT_EQ(result, AVIF_RESULT_OK)
<< avifResultToString(result) << " " << decoder->diag.error;
// Verify that the input and decoded images are close.
EXPECT_GT(testutil::GetPsnr(*image, *decoded), 40.0);
// Verify that the gain map was detected...
EXPECT_TRUE(decoder->gainMapPresent);
// ... but not decoded because enableDecodingGainMap is false by default.
EXPECT_EQ(decoded->gainMap.image, nullptr);
// Check that the gain map metadata WAS populated.
CheckGainMapMetadataMatches(decoded->gainMap.metadata,
image->gainMap.metadata);
}
TEST(GainMapTest, IgnoreColorAndAlpha) {
testutil::AvifImagePtr image =
CreateTestImageWithGainMap(/*base_rendition_is_hdr=*/false);
ASSERT_NE(image, nullptr);
testutil::AvifEncoderPtr encoder(avifEncoderCreate(), avifEncoderDestroy);
ASSERT_NE(encoder, nullptr);
testutil::AvifRwData encoded;
avifResult result = avifEncoderWrite(encoder.get(), image.get(), &encoded);
ASSERT_EQ(result, AVIF_RESULT_OK)
<< avifResultToString(result) << " " << encoder->diag.error;
testutil::AvifImagePtr decoded(avifImageCreateEmpty(), avifImageDestroy);
ASSERT_NE(decoded, nullptr);
testutil::AvifDecoderPtr decoder(avifDecoderCreate(), avifDecoderDestroy);
ASSERT_NE(decoder, nullptr);
// Decode just the gain map.
decoder->ignoreColorAndAlpha = AVIF_TRUE;
decoder->enableDecodingGainMap = AVIF_TRUE;
decoder->enableParsingGainMapMetadata = AVIF_TRUE;
result = avifDecoderReadMemory(decoder.get(), decoded.get(), encoded.data,
encoded.size);
ASSERT_EQ(result, AVIF_RESULT_OK)
<< avifResultToString(result) << " " << decoder->diag.error;
// Main image metadata is available.
EXPECT_EQ(decoder->image->width, 12u);
EXPECT_EQ(decoder->image->height, 34u);
// But pixels are not.
EXPECT_EQ(decoder->image->yuvRowBytes[0], 0u);
EXPECT_EQ(decoder->image->yuvRowBytes[1], 0u);
EXPECT_EQ(decoder->image->yuvRowBytes[2], 0u);
EXPECT_EQ(decoder->image->alphaRowBytes, 0u);
// The gain map was decoded.
EXPECT_TRUE(decoder->gainMapPresent);
ASSERT_NE(decoded->gainMap.image, nullptr);
EXPECT_GT(testutil::GetPsnr(*image->gainMap.image, *decoded->gainMap.image),
40.0);
CheckGainMapMetadataMatches(decoded->gainMap.metadata,
image->gainMap.metadata);
}
TEST(GainMapTest, IgnoreAll) {
testutil::AvifImagePtr image =
CreateTestImageWithGainMap(/*base_rendition_is_hdr=*/false);
ASSERT_NE(image, nullptr);
testutil::AvifEncoderPtr encoder(avifEncoderCreate(), avifEncoderDestroy);
ASSERT_NE(encoder, nullptr);
testutil::AvifRwData encoded;
avifResult result = avifEncoderWrite(encoder.get(), image.get(), &encoded);
ASSERT_EQ(result, AVIF_RESULT_OK)
<< avifResultToString(result) << " " << encoder->diag.error;
testutil::AvifDecoderPtr decoder(avifDecoderCreate(), avifDecoderDestroy);
ASSERT_NE(decoder, nullptr);
// Ignore both the main image and the gain map.
decoder->ignoreColorAndAlpha = AVIF_TRUE;
decoder->enableDecodingGainMap = AVIF_FALSE;
// But do read the gain map metadata.
decoder->enableParsingGainMapMetadata = AVIF_TRUE;
// Parsing just the header should work.
ASSERT_EQ(avifDecoderSetIOMemory(decoder.get(), encoded.data, encoded.size),
AVIF_RESULT_OK);
ASSERT_EQ(avifDecoderParse(decoder.get()), AVIF_RESULT_OK);
EXPECT_TRUE(decoder->gainMapPresent);
CheckGainMapMetadataMatches(decoder->image->gainMap.metadata,
image->gainMap.metadata);
ASSERT_EQ(decoder->image->gainMap.image, nullptr);
// But trying to access the next image should give an error because both
// ignoreColorAndAlpha and enableDecodingGainMap are set.
ASSERT_EQ(avifDecoderNextImage(decoder.get()), AVIF_RESULT_NO_CONTENT);
}
TEST(GainMapTest, NoGainMap) {
// Create a simple image without a gain map.
testutil::AvifImagePtr image =
testutil::CreateImage(/*width=*/12, /*height=*/34, /*depth=*/10,
AVIF_PIXEL_FORMAT_YUV420, AVIF_PLANES_ALL);
ASSERT_NE(image, nullptr);
image->transferCharacteristics = AVIF_TRANSFER_CHARACTERISTICS_SRGB;
testutil::FillImageGradient(image.get());
testutil::AvifEncoderPtr encoder(avifEncoderCreate(), avifEncoderDestroy);
ASSERT_NE(encoder, nullptr);
testutil::AvifRwData encoded;
avifResult result = avifEncoderWrite(encoder.get(), image.get(), &encoded);
ASSERT_EQ(result, AVIF_RESULT_OK)
<< avifResultToString(result) << " " << encoder->diag.error;
testutil::AvifImagePtr decoded(avifImageCreateEmpty(), avifImageDestroy);
ASSERT_NE(decoded, nullptr);
testutil::AvifDecoderPtr decoder(avifDecoderCreate(), avifDecoderDestroy);
ASSERT_NE(decoder, nullptr);
// Enable gain map decoding.
decoder->enableDecodingGainMap = AVIF_TRUE;
decoder->enableParsingGainMapMetadata = AVIF_TRUE;
result = avifDecoderReadMemory(decoder.get(), decoded.get(), encoded.data,
encoded.size);
ASSERT_EQ(result, AVIF_RESULT_OK)
<< avifResultToString(result) << " " << decoder->diag.error;
// Verify that the input and decoded images are close.
EXPECT_GT(testutil::GetPsnr(*image, *decoded), 40.0);
// Verify that no gain map was found.
EXPECT_FALSE(decoder->gainMapPresent);
EXPECT_EQ(decoded->gainMap.image, nullptr);
CheckGainMapMetadataMatches(decoded->gainMap.metadata, avifGainMapMetadata());
}
#define EXPECT_FRACTION_NEAR(numerator, denominator, expected) \
EXPECT_NEAR(std::abs((double)numerator / denominator), expected, \
expected * 0.001);
TEST(GainMapTest, Convert) {
avifGainMapMetadataDouble metadata_double = {};
metadata_double.gainMapMin[0] = 1.0;
metadata_double.gainMapMin[1] = 1.1;
metadata_double.gainMapMin[2] = 1.2;
metadata_double.gainMapMax[0] = 10.0;
metadata_double.gainMapMax[1] = 10.1;
metadata_double.gainMapMax[2] = 10.2;
metadata_double.gainMapGamma[0] = 1.0;
metadata_double.gainMapGamma[1] = 1.0;
metadata_double.gainMapGamma[2] = 1.2;
metadata_double.offsetSdr[0] = 1.0 / 32.0;
metadata_double.offsetSdr[1] = 1.0 / 64.0;
metadata_double.offsetSdr[2] = 1.0 / 128.0;
metadata_double.offsetHdr[0] = 0.004564;
metadata_double.offsetHdr[1] = 0.0;
metadata_double.hdrCapacityMin = 1.0;
metadata_double.hdrCapacityMax = 10.0;
metadata_double.baseRenditionIsHDR = AVIF_TRUE;
avifGainMapMetadata metadata = {};
ASSERT_TRUE(
avifGainMapMetadataDoubleToFractions(&metadata, &metadata_double));
for (int i = 0; i < 3; ++i) {
EXPECT_FRACTION_NEAR(metadata.gainMapMinN[i], metadata.gainMapMinD[i],
metadata_double.gainMapMin[i]);
EXPECT_FRACTION_NEAR(metadata.gainMapMaxN[i], metadata.gainMapMaxD[i],
metadata_double.gainMapMax[i]);
EXPECT_FRACTION_NEAR(metadata.gainMapGammaN[i], metadata.gainMapGammaD[i],
metadata_double.gainMapGamma[i]);
EXPECT_FRACTION_NEAR(metadata.offsetSdrN[i], metadata.offsetSdrD[i],
metadata_double.offsetSdr[i]);
EXPECT_FRACTION_NEAR(metadata.offsetHdrN[i], metadata.offsetHdrD[i],
metadata_double.offsetHdr[i]);
}
EXPECT_FRACTION_NEAR(metadata.hdrCapacityMinN, metadata.hdrCapacityMinD,
metadata_double.hdrCapacityMin);
EXPECT_FRACTION_NEAR(metadata.hdrCapacityMaxN, metadata.hdrCapacityMaxD,
metadata_double.hdrCapacityMax);
EXPECT_EQ(metadata.baseRenditionIsHDR, metadata_double.baseRenditionIsHDR);
}
TEST(GainMapTest, Invalid) {
avifGainMapMetadataDouble metadata_double = {};
metadata_double.gainMapGamma[0] = -42; // A negative value is invalid!
avifGainMapMetadata metadata = {};
ASSERT_FALSE(
avifGainMapMetadataDoubleToFractions(&metadata, &metadata_double));
}
} // namespace
} // namespace libavif