blob: 7d498a22a85353ea44c5f70faf4aef1fa384f35d [file] [log] [blame]
// Copyright 2023 Google LLC
// SPDX-License-Identifier: BSD-2-Clause
#include <tuple>
#include "avif/avif.h"
#include "aviftest_helpers.h"
#include "avifutil.h"
#include "gtest/gtest.h"
namespace avif {
namespace {
// Used to pass the data folder path to the GoogleTest suites.
const char* data_path = nullptr;
// Tests that AVIF_MATRIX_COEFFICIENTS_YCGCO_RO does not work because the input
// depth is not odd.
TEST(LosslessTest, YCGCO_RO) {
const std::string file_path =
std::string(data_path) + "paris_icc_exif_xmp.png";
ImagePtr image(avifImageCreateEmpty());
ASSERT_NE(image, nullptr);
image->matrixCoefficients = AVIF_MATRIX_COEFFICIENTS_YCGCO_RO;
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);
ASSERT_EQ(file_format, AVIF_APP_FILE_FORMAT_UNKNOWN);
}
// Reads an image with a simpler API. ASSERT_NO_FATAL_FAILURE should be used
// when calling it.
void ReadImageSimple(const std::string& file_name, avifPixelFormat pixel_format,
avifMatrixCoefficients matrix_coefficients,
avifBool ignore_icc, ImagePtr& image) {
const std::string file_path = std::string(data_path) + file_name;
image.reset(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_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);
image.reset();
return;
}
ASSERT_NE(file_format, AVIF_APP_FILE_FORMAT_UNKNOWN);
}
// The test parameters are: the file path, the matrix coefficients and the pixel
// format.
class LosslessTest
: public testing::TestWithParam<std::tuple<std::string, int, int>> {};
// Tests encode/decode round trips.
TEST_P(LosslessTest, EncodeDecode) {
const std::string& file_name = std::get<0>(GetParam());
const avifMatrixCoefficients matrix_coefficients =
static_cast<avifMatrixCoefficients>(std::get<1>(GetParam()));
const avifPixelFormat pixel_format =
static_cast<avifPixelFormat>(std::get<2>(GetParam()));
// Ignore ICC when going from RGB to gray.
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.
ImagePtr image;
ASSERT_NO_FATAL_FAILURE(ReadImageSimple(
file_name, pixel_format, matrix_coefficients, ignore_icc, image));
if (image == nullptr) return;
// 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));
}
// Reads an image losslessly, using identiy MC.
ImagePtr ReadImageLossless(const std::string& path,
avifPixelFormat requested_format,
avifBool ignore_icc) {
ImagePtr image(avifImageCreateEmpty());
if (!image) return nullptr;
image->matrixCoefficients = AVIF_MATRIX_COEFFICIENTS_IDENTITY;
if (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;
}
// Tests encode/decode round trips under different matrix coefficients.
TEST_P(LosslessTest, EncodeDecodeMatrixCoefficients) {
const std::string& file_name = std::get<0>(GetParam());
const avifMatrixCoefficients matrix_coefficients =
static_cast<avifMatrixCoefficients>(std::get<1>(GetParam()));
const avifPixelFormat pixel_format =
static_cast<avifPixelFormat>(std::get<2>(GetParam()));
// Ignore ICC when going from RGB to gray.
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.
ImagePtr image;
ASSERT_NO_FATAL_FAILURE(ReadImageSimple(
file_name, pixel_format, matrix_coefficients, ignore_icc, image));
if (image == nullptr) return;
// Convert to a temporary PNG and read back losslessly.
const std::string tmp_path =
testing::TempDir() + "decoded_EncodeDecodeMatrixCoefficients.png";
ASSERT_TRUE(testutil::WriteImage(image.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(
std::string(data_path) + file_name, pixel_format, ignore_icc);
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 (matrix_coefficients == AVIF_MATRIX_COEFFICIENTS_YCGCO_RE &&
pixel_format == AVIF_PIXEL_FORMAT_YUV400) {
// For gray images, information is lost in YCGCO_RE.
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)),
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
int main(int argc, char** argv) {
::testing::InitGoogleTest(&argc, argv);
if (argc != 2) {
std::cerr << "There must be exactly one argument containing the path to "
"the test data folder"
<< std::endl;
return 1;
}
avif::data_path = argv[1];
return RUN_ALL_TESTS();
}