-
Notifications
You must be signed in to change notification settings - Fork 214
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Do not allow for monochrome + identity matrix #2667
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,8 @@ | ||
// Copyright 2023 Google LLC | ||
// SPDX-License-Identifier: BSD-2-Clause | ||
|
||
#include <tuple> | ||
|
||
#include "avif/avif.h" | ||
#include "aviftest_helpers.h" | ||
#include "avifutil.h" | ||
|
@@ -12,86 +14,146 @@ namespace { | |
// Used to pass the data folder path to the GoogleTest suites. | ||
const char* data_path = nullptr; | ||
|
||
// Read image losslessly, using identiy MC. | ||
ImagePtr ReadImageLossless(const std::string& path, | ||
avifPixelFormat requested_format, | ||
avifBool ignore_icc) { | ||
ImagePtr image(avifImageCreateEmpty()); | ||
image->matrixCoefficients = AVIF_MATRIX_COEFFICIENTS_IDENTITY; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You check |
||
if (!image || | ||
avifReadImage(path.c_str(), requested_format, /*requested_depth=*/0, | ||
/*chroma_downsampling=*/AVIF_CHROMA_DOWNSAMPLING_AUTOMATIC, | ||
ignore_icc, /*ignore_exif=*/false, /*ignore_xmp=*/false, | ||
/*allow_changing_cicp=*/false, /*ignore_gain_map=*/false, | ||
AVIF_DEFAULT_IMAGE_SIZE_LIMIT, image.get(), | ||
/*outDepth=*/nullptr, /*sourceTiming=*/nullptr, | ||
/*frameIter=*/nullptr) == AVIF_APP_FILE_FORMAT_UNKNOWN) { | ||
return nullptr; | ||
} | ||
return image; | ||
} | ||
|
||
class LosslessTest | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add a comment about what the parameters are. Tuples are convenient in tests, but aren't self documenting like structs. |
||
: public testing::TestWithParam<std::tuple<std::string, int, int>> {}; | ||
|
||
// Tests encode/decode round trips under different matrix coefficients. | ||
TEST(BasicTest, EncodeDecodeMatrixCoefficients) { | ||
for (const auto& file_name : | ||
{"paris_icc_exif_xmp.png", "paris_exif_xmp_icc.jpg"}) { | ||
// Read a ground truth image with default matrix coefficients. | ||
const std::string file_path = std::string(data_path) + file_name; | ||
const ImagePtr ground_truth_image = | ||
testutil::ReadImage(data_path, file_name); | ||
|
||
for (auto matrix_coefficient : | ||
{AVIF_MATRIX_COEFFICIENTS_IDENTITY, AVIF_MATRIX_COEFFICIENTS_YCGCO, | ||
AVIF_MATRIX_COEFFICIENTS_YCGCO_RE, | ||
AVIF_MATRIX_COEFFICIENTS_YCGCO_RO}) { | ||
// Read a ground truth image but ask for certain matrix coefficients. | ||
ImagePtr image(avifImageCreateEmpty()); | ||
ASSERT_NE(image, nullptr); | ||
image->matrixCoefficients = (avifMatrixCoefficients)matrix_coefficient; | ||
const avifAppFileFormat file_format = avifReadImage( | ||
file_path.c_str(), | ||
/*requestedFormat=*/AVIF_PIXEL_FORMAT_NONE, | ||
/*requestedDepth=*/0, | ||
/*chromaDownsampling=*/AVIF_CHROMA_DOWNSAMPLING_AUTOMATIC, | ||
/*ignoreColorProfile=*/false, /*ignoreExif=*/false, | ||
/*ignoreXMP=*/false, /*allowChangingCicp=*/true, | ||
/*ignoreGainMap=*/true, AVIF_DEFAULT_IMAGE_SIZE_LIMIT, image.get(), | ||
/*outDepth=*/nullptr, /*sourceTiming=*/nullptr, | ||
/*frameIter=*/nullptr); | ||
if (matrix_coefficient == AVIF_MATRIX_COEFFICIENTS_YCGCO_RO) { | ||
// AVIF_MATRIX_COEFFICIENTS_YCGCO_RO does not work because the input | ||
// depth is not odd. | ||
ASSERT_EQ(file_format, AVIF_APP_FILE_FORMAT_UNKNOWN); | ||
continue; | ||
} | ||
ASSERT_NE(file_format, AVIF_APP_FILE_FORMAT_UNKNOWN); | ||
|
||
// Encode. | ||
EncoderPtr encoder(avifEncoderCreate()); | ||
ASSERT_NE(encoder, nullptr); | ||
encoder->speed = AVIF_SPEED_FASTEST; | ||
encoder->quality = AVIF_QUALITY_LOSSLESS; | ||
encoder->qualityAlpha = AVIF_QUALITY_LOSSLESS; | ||
testutil::AvifRwData encoded; | ||
avifResult result = | ||
avifEncoderWrite(encoder.get(), image.get(), &encoded); | ||
ASSERT_EQ(result, AVIF_RESULT_OK) << avifResultToString(result); | ||
|
||
// Decode to RAM. | ||
ImagePtr decoded(avifImageCreateEmpty()); | ||
ASSERT_NE(decoded, nullptr); | ||
DecoderPtr decoder(avifDecoderCreate()); | ||
ASSERT_NE(decoder, nullptr); | ||
result = avifDecoderReadMemory(decoder.get(), decoded.get(), encoded.data, | ||
encoded.size); | ||
ASSERT_EQ(result, AVIF_RESULT_OK) << avifResultToString(result); | ||
|
||
// Convert to a temporary PNG and read back, to have default matrix | ||
// coefficients. | ||
const std::string temp_dir = testing::TempDir(); | ||
const std::string temp_file = "decoded_default.png"; | ||
const std::string tmp_path = temp_dir + temp_file; | ||
ASSERT_TRUE(testutil::WriteImage(decoded.get(), tmp_path.c_str())); | ||
const ImagePtr decoded_default = | ||
testutil::ReadImage(temp_dir.c_str(), temp_file.c_str()); | ||
|
||
// Verify that the ground truth and decoded images are the same. | ||
const bool are_images_equal = | ||
testutil::AreImagesEqual(*ground_truth_image, *decoded_default); | ||
|
||
if (matrix_coefficient == AVIF_MATRIX_COEFFICIENTS_IDENTITY || | ||
matrix_coefficient == AVIF_MATRIX_COEFFICIENTS_YCGCO_RE) { | ||
ASSERT_TRUE(are_images_equal); | ||
} else { | ||
// AVIF_MATRIX_COEFFICIENTS_YCGCO is not lossless because it does not | ||
// expand the input bit range for YCgCo values. | ||
ASSERT_FALSE(are_images_equal); | ||
} | ||
} | ||
TEST_P(LosslessTest, EncodeDecodeMatrixCoefficients) { | ||
const std::string& file_name = std::get<0>(GetParam()); | ||
const avifMatrixCoefficients matrix_coefficients = | ||
static_cast<avifMatrixCoefficients>(std::get<1>(GetParam())); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can the test just take the enum types as params to avoid all the casting? |
||
const avifPixelFormat pixel_format = | ||
static_cast<avifPixelFormat>(std::get<2>(GetParam())); | ||
// Ignore ICC when going from RGB to gray. | ||
vrabaud marked this conversation as resolved.
Show resolved
Hide resolved
|
||
const avifBool ignore_icc = | ||
pixel_format == AVIF_PIXEL_FORMAT_YUV400 ? AVIF_TRUE : AVIF_FALSE; | ||
|
||
// Read a ground truth image but ask for certain matrix coefficients. | ||
const std::string file_path = std::string(data_path) + file_name; | ||
ImagePtr image(avifImageCreateEmpty()); | ||
ASSERT_NE(image, nullptr); | ||
image->matrixCoefficients = matrix_coefficients; | ||
const avifAppFileFormat file_format = avifReadImage( | ||
file_path.c_str(), | ||
/*requestedFormat=*/pixel_format, | ||
/*requestedDepth=*/0, | ||
/*chromaDownsampling=*/AVIF_CHROMA_DOWNSAMPLING_AUTOMATIC, | ||
/*ignoreColorProfile=*/ignore_icc, /*ignoreExif=*/false, | ||
/*ignoreXMP=*/false, /*allowChangingCicp=*/true, | ||
/*ignoreGainMap=*/true, AVIF_DEFAULT_IMAGE_SIZE_LIMIT, image.get(), | ||
/*outDepth=*/nullptr, /*sourceTiming=*/nullptr, | ||
/*frameIter=*/nullptr); | ||
if (matrix_coefficients == AVIF_MATRIX_COEFFICIENTS_YCGCO_RO) { | ||
// AVIF_MATRIX_COEFFICIENTS_YCGCO_RO does not work because the input | ||
// depth is not odd. | ||
ASSERT_EQ(file_format, AVIF_APP_FILE_FORMAT_UNKNOWN); | ||
return; | ||
} | ||
if (matrix_coefficients == AVIF_MATRIX_COEFFICIENTS_IDENTITY && | ||
image->yuvFormat == AVIF_PIXEL_FORMAT_YUV420) { | ||
// 420 cannot be converted from RGB to YUV with | ||
// AVIF_MATRIX_COEFFICIENTS_IDENTITY due to a decision taken in | ||
// avifGetYUVColorSpaceInfo. | ||
ASSERT_EQ(file_format, AVIF_APP_FILE_FORMAT_UNKNOWN); | ||
return; | ||
} | ||
ASSERT_NE(file_format, AVIF_APP_FILE_FORMAT_UNKNOWN); | ||
|
||
// Encode. | ||
EncoderPtr encoder(avifEncoderCreate()); | ||
ASSERT_NE(encoder, nullptr); | ||
encoder->speed = AVIF_SPEED_FASTEST; | ||
encoder->quality = AVIF_QUALITY_LOSSLESS; | ||
testutil::AvifRwData encoded; | ||
avifResult result = avifEncoderWrite(encoder.get(), image.get(), &encoded); | ||
|
||
if (matrix_coefficients == AVIF_MATRIX_COEFFICIENTS_IDENTITY && | ||
image->yuvFormat != AVIF_PIXEL_FORMAT_YUV444) { | ||
// The AV1 spec does not allow identity with subsampling. | ||
ASSERT_EQ(result, AVIF_RESULT_INVALID_ARGUMENT); | ||
return; | ||
} | ||
ASSERT_EQ(result, AVIF_RESULT_OK) << avifResultToString(result); | ||
|
||
// Decode to RAM. | ||
ImagePtr decoded(avifImageCreateEmpty()); | ||
ASSERT_NE(decoded, nullptr); | ||
DecoderPtr decoder(avifDecoderCreate()); | ||
ASSERT_NE(decoder, nullptr); | ||
result = avifDecoderReadMemory(decoder.get(), decoded.get(), encoded.data, | ||
encoded.size); | ||
ASSERT_EQ(result, AVIF_RESULT_OK) << avifResultToString(result); | ||
|
||
// What we read should be what we encoded | ||
ASSERT_TRUE(testutil::AreImagesEqual(*image, *decoded)); | ||
|
||
// Convert to a temporary PNG and read back, to have default matrix | ||
// coefficients. | ||
const std::string tmp_path = | ||
testing::TempDir() + "decoded_EncodeDecodeMatrixCoefficients.png"; | ||
ASSERT_TRUE(testutil::WriteImage(decoded.get(), tmp_path.c_str())); | ||
const ImagePtr decoded_lossless = | ||
ReadImageLossless(tmp_path, pixel_format, ignore_icc); | ||
if (image->yuvFormat == AVIF_PIXEL_FORMAT_YUV420) { | ||
// 420 cannot be converted from RGB to YUV with | ||
// AVIF_MATRIX_COEFFICIENTS_IDENTITY due to a decision taken in | ||
// avifGetYUVColorSpaceInfo. | ||
ASSERT_EQ(decoded_lossless, nullptr); | ||
return; | ||
} | ||
ASSERT_NE(decoded_lossless, nullptr); | ||
|
||
// Verify that the ground truth and decoded images are the same. | ||
const ImagePtr ground_truth_lossless = | ||
ReadImageLossless(file_path, pixel_format, ignore_icc); | ||
vrabaud marked this conversation as resolved.
Show resolved
Hide resolved
|
||
ASSERT_NE(ground_truth_lossless, nullptr); | ||
const bool are_images_equal = | ||
testutil::AreImagesEqual(*ground_truth_lossless, *decoded_lossless); | ||
|
||
if (matrix_coefficients == AVIF_MATRIX_COEFFICIENTS_YCGCO) { | ||
// AVIF_MATRIX_COEFFICIENTS_YCGCO is not a lossless transform. | ||
ASSERT_FALSE(are_images_equal); | ||
} else if (pixel_format == AVIF_PIXEL_FORMAT_YUV400) { | ||
ASSERT_EQ(matrix_coefficients, AVIF_MATRIX_COEFFICIENTS_YCGCO_RE); | ||
// For gray images, information is lost. | ||
vrabaud marked this conversation as resolved.
Show resolved
Hide resolved
|
||
ASSERT_FALSE(are_images_equal); | ||
} else { | ||
ASSERT_TRUE(are_images_equal); | ||
} | ||
} | ||
|
||
INSTANTIATE_TEST_SUITE_P( | ||
LosslessTestInstantiation, LosslessTest, | ||
testing::Combine( | ||
testing::Values("paris_icc_exif_xmp.png", "paris_exif_xmp_icc.jpg"), | ||
testing::Values(static_cast<int>(AVIF_MATRIX_COEFFICIENTS_IDENTITY), | ||
static_cast<int>(AVIF_MATRIX_COEFFICIENTS_YCGCO), | ||
static_cast<int>(AVIF_MATRIX_COEFFICIENTS_YCGCO_RE), | ||
static_cast<int>(AVIF_MATRIX_COEFFICIENTS_YCGCO_RO)), | ||
testing::Values(static_cast<int>(AVIF_PIXEL_FORMAT_NONE), | ||
static_cast<int>(AVIF_PIXEL_FORMAT_YUV444), | ||
static_cast<int>(AVIF_PIXEL_FORMAT_YUV420), | ||
static_cast<int>(AVIF_PIXEL_FORMAT_YUV400)))); | ||
|
||
} // namespace | ||
} // namespace avif | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But this is checked for later? What path does this break?