blob: 44f0d118b01828b1eec2bc5b7e297c09c02f39d6 [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.
// If image is set to nullptr, it is because AVIF_MATRIX_COEFFICIENTS_IDENTITY
// is requested with AVIF_PIXEL_FORMAT_YUV420.
void ReadImageSimple(const std::string& file_path, avifPixelFormat pixel_format,
avifMatrixCoefficients matrix_coefficients,
avifBool ignore_icc, ImagePtr& image) {
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);
}
// Checks whether an image is grayscale. ASSERT_NO_FATAL_FAILURE should be used
// when calling it.
void GetIsGray(const std::string& path, bool& is_gray) {
ImagePtr image(avifImageCreateEmpty());
ASSERT_NE(image, nullptr);
image->matrixCoefficients = AVIF_MATRIX_COEFFICIENTS_UNSPECIFIED;
ASSERT_NE(
AVIF_APP_FILE_FORMAT_UNKNOWN,
avifReadImage(path.c_str(), AVIF_PIXEL_FORMAT_NONE, /*requestedDepth=*/0,
/*chromaDownsampling=*/AVIF_CHROMA_DOWNSAMPLING_AUTOMATIC,
/*ignoreColorProfile=*/true, /*ignoreExif=*/true,
/*ignoreXMP=*/true,
/*allowChangingCicp=*/true, /*ignoreGainMap=*/true,
AVIF_DEFAULT_IMAGE_SIZE_LIMIT, image.get(),
/*outDepth=*/nullptr, /*sourceTiming=*/nullptr,
/*frameIter=*/nullptr));
is_gray = image->yuvFormat == AVIF_PIXEL_FORMAT_YUV400;
}
// The test parameters are: the file path, the matrix coefficients and the pixel
// format.
class EncodeDecodeMemory
: public testing::TestWithParam<std::tuple<std::string, int, int>> {};
// Tests encode/decode round trips in memory.
TEST_P(EncodeDecodeMemory, RoundTrip) {
const std::string& file_path =
std::string(data_path) + 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()));
// Check if the input image is grayscale.
bool gt_is_gray = false;
ASSERT_NO_FATAL_FAILURE(GetIsGray(file_path, gt_is_gray));
// Ignore ICC when going from RGB to gray or gray to RGB.
avifBool ignore_icc;
if (gt_is_gray && pixel_format != AVIF_PIXEL_FORMAT_YUV400 &&
pixel_format != AVIF_PIXEL_FORMAT_NONE) {
ignore_icc = AVIF_TRUE;
} else if (!gt_is_gray && pixel_format == AVIF_PIXEL_FORMAT_YUV400) {
ignore_icc = AVIF_TRUE;
} else {
ignore_icc = AVIF_FALSE;
}
// Read a ground truth image but do not care about the matrix coefficients: we
// just want data.
avifMatrixCoefficients gt_matrix_coefficients;
if (gt_is_gray) {
// gray to gray or RGB does not require MC.
gt_matrix_coefficients = AVIF_MATRIX_COEFFICIENTS_UNSPECIFIED;
} else if (pixel_format != AVIF_PIXEL_FORMAT_YUV400) {
// RGB to RGB is done with identity to be lossless.
gt_matrix_coefficients = AVIF_MATRIX_COEFFICIENTS_IDENTITY;
} else {
// RGB to gray depends on the MC so use the input one.
gt_matrix_coefficients = matrix_coefficients;
}
ImagePtr image;
ASSERT_NO_FATAL_FAILURE(ReadImageSimple(
file_path, pixel_format, gt_matrix_coefficients, ignore_icc, image));
// ReadImageSimple does not set image and does not trigger an assert for the
// unsupported case of AVIF_MATRIX_COEFFICIENTS_IDENTITY + 420 only. Hence
// stop the test here.
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;
image->matrixCoefficients = matrix_coefficients;
avifResult result = avifEncoderWrite(encoder.get(), image.get(), &encoded);
if (image->matrixCoefficients == 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 memory.
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));
}
INSTANTIATE_TEST_SUITE_P(
EncodeDecodeMemoryInstantiation, EncodeDecodeMemory,
testing::Combine(
testing::Values("paris_icc_exif_xmp.png", "paris_exif_xmp_icc.jpg",
"kodim03_grayscale_gamma1.6.png"),
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();
}