blob: 498906c5abc8d168551443a6c9ac13deab5d5046 [file] [log] [blame]
// Copyright 2022 Google LLC
// SPDX-License-Identifier: BSD-2-Clause
#include <string>
#include "avif/avif.h"
#include "aviftest_helpers.h"
#include "avifutil.h"
#include "gtest/gtest.h"
#include "iccmaker.h"
namespace avif {
namespace {
// Used to pass the data folder path to the GoogleTest suites.
const char* data_path = nullptr;
//------------------------------------------------------------------------------
// Generic tests
bool AreSamplesEqualForAllReadSettings(const char* file_name1,
const char* file_name2) {
constexpr bool kIgnoreMetadata = true;
for (avifPixelFormat requested_format :
{AVIF_PIXEL_FORMAT_YUV444, AVIF_PIXEL_FORMAT_YUV422,
AVIF_PIXEL_FORMAT_YUV420, AVIF_PIXEL_FORMAT_YUV400}) {
for (int requested_depth : {8, 10, 12, 16}) {
for (avifChromaDownsampling chroma_downsampling :
{AVIF_CHROMA_DOWNSAMPLING_AUTOMATIC,
AVIF_CHROMA_DOWNSAMPLING_FASTEST,
AVIF_CHROMA_DOWNSAMPLING_BEST_QUALITY,
AVIF_CHROMA_DOWNSAMPLING_AVERAGE}) {
const ImagePtr image1 = testutil::ReadImage(
data_path, file_name1, requested_format, requested_depth,
chroma_downsampling, kIgnoreMetadata, kIgnoreMetadata,
kIgnoreMetadata);
const ImagePtr image2 = testutil::ReadImage(
data_path, file_name2, requested_format, requested_depth,
chroma_downsampling, kIgnoreMetadata, kIgnoreMetadata,
kIgnoreMetadata);
if (!image1 || !image2 || !testutil::AreImagesEqual(*image1, *image2)) {
return false;
}
}
}
}
return true;
}
TEST(JpegTest, ReadAllSubsamplingsAndAllBitDepths) {
EXPECT_TRUE(AreSamplesEqualForAllReadSettings(
"paris_exif_xmp_icc.jpg", "paris_exif_orientation_5.jpg"));
}
TEST(PngTest, ReadAllSubsamplingsAndAllBitDepths) {
EXPECT_TRUE(AreSamplesEqualForAllReadSettings(
"paris_icc_exif_xmp.png", "paris_icc_exif_xmp_at_end.png"));
}
//------------------------------------------------------------------------------
// PNG color metadata handling tests
// Verify we can read a PNG file with PNG_COLOR_TYPE_PALETTE and a tRNS chunk.
TEST(PngTest, PaletteColorTypeWithTrnsChunk) {
const ImagePtr image = testutil::ReadImage(data_path, "draw_points.png",
AVIF_PIXEL_FORMAT_YUV444, 8);
ASSERT_NE(image, nullptr);
EXPECT_EQ(image->width, 33u);
EXPECT_EQ(image->height, 11u);
EXPECT_NE(image->alphaPlane, nullptr);
}
// Verify we can read a PNG file with PNG_COLOR_TYPE_RGB and a tRNS chunk
// after a PLTE chunk.
TEST(PngTest, RgbColorTypeWithTrnsAfterPlte) {
const ImagePtr image = testutil::ReadImage(
data_path, "circle-trns-after-plte.png", AVIF_PIXEL_FORMAT_YUV444, 8);
ASSERT_NE(image, nullptr);
EXPECT_EQ(image->width, 100u);
EXPECT_EQ(image->height, 60u);
EXPECT_NE(image->alphaPlane, nullptr);
}
// Verify we can read a PNG file with PNG_COLOR_TYPE_RGB and a tRNS chunk
// before a PLTE chunk. libpng considers the tRNS chunk as invalid and ignores
// it, so the decoded image should have no alpha.
TEST(PngTest, RgbColorTypeWithTrnsBeforePlte) {
const ImagePtr image = testutil::ReadImage(
data_path, "circle-trns-before-plte.png", AVIF_PIXEL_FORMAT_YUV444, 8);
ASSERT_NE(image, nullptr);
EXPECT_EQ(image->width, 100u);
EXPECT_EQ(image->height, 60u);
EXPECT_EQ(image->alphaPlane, nullptr);
}
constexpr size_t kColorProfileSize = 376;
constexpr size_t kGrayProfileSize = 275;
// Verify we can read a color PNG file tagged as gamma 2.2 through gAMA chunk,
// and set transfer characteristics correctly.
TEST(PngTest, ColorGamma22) {
const ImagePtr image = testutil::ReadImage(data_path, "ffffcc-gamma2.2.png");
ASSERT_NE(image, nullptr);
// gamma 2.2 should match BT470M
EXPECT_EQ(image->transferCharacteristics,
AVIF_TRANSFER_CHARACTERISTICS_BT470M);
// should not generate ICC profile
EXPECT_EQ(image->icc.size, 0u);
}
// Verify that color info does not get overwritten if allow_changing_cicp is
// false.
TEST(PngTest, ColorGamma22ForbitChangingCicp) {
const ImagePtr image = testutil::ReadImage(
data_path, "ffffcc-gamma2.2.png",
/*requested_format=*/AVIF_PIXEL_FORMAT_NONE,
/*requested_depth=*/0, AVIF_CHROMA_DOWNSAMPLING_AUTOMATIC,
/*ignore_icc=*/false, /*ignore_exif*/ false,
/*ignore_xmp=*/false, /*allow_changing_cicp=*/false);
ASSERT_NE(image, nullptr);
// Color info should still be unspecified even if file gamma is 2.2
EXPECT_EQ(image->colorPrimaries, AVIF_COLOR_PRIMARIES_UNSPECIFIED);
EXPECT_EQ(image->transferCharacteristics,
AVIF_TRANSFER_CHARACTERISTICS_UNSPECIFIED);
// should not generate ICC profile
EXPECT_EQ(image->icc.size, 0u);
}
// Verify we can read a color PNG file tagged as gamma 1.6 through gAMA chunk,
// and generate a color profile for it.
TEST(PngTest, ColorGamma16) {
const ImagePtr image = testutil::ReadImage(data_path, "ffffcc-gamma1.6.png");
ASSERT_NE(image, nullptr);
// if ICC profile generated, CP and TC should be set to unspecified
EXPECT_EQ(image->colorPrimaries, AVIF_COLOR_PRIMARIES_UNSPECIFIED);
EXPECT_EQ(image->transferCharacteristics,
AVIF_TRANSFER_CHARACTERISTICS_UNSPECIFIED);
// should generate a color profile
EXPECT_EQ(image->icc.size, kColorProfileSize);
// Generated profile is tested in test_cmd_icc_profile.sh
}
// Verify we can read a gray PNG file tagged as gamma 2.2 through gAMA chunk,
// and set transfer characteristics correctly.
TEST(PngTest, GrayGamma22) {
const ImagePtr image = testutil::ReadImage(data_path, "ffffff-gamma2.2.png",
AVIF_PIXEL_FORMAT_YUV400);
ASSERT_NE(image, nullptr);
// gamma 2.2 should match BT470M
EXPECT_EQ(image->transferCharacteristics,
AVIF_TRANSFER_CHARACTERISTICS_BT470M);
// should not generate ICC profile
EXPECT_EQ(image->icc.size, 0u);
}
// Verify we can read a gray PNG file tagged as gamma 1.6 through gAMA chunk,
// and generate a gray profile for it.
TEST(PngTest, GrayGamma16) {
const ImagePtr image = testutil::ReadImage(data_path, "ffffff-gamma1.6.png",
AVIF_PIXEL_FORMAT_YUV400);
ASSERT_NE(image, nullptr);
// if ICC profile generated, CP and TC should be set to unspecified
EXPECT_EQ(image->colorPrimaries, AVIF_COLOR_PRIMARIES_UNSPECIFIED);
EXPECT_EQ(image->transferCharacteristics,
AVIF_TRANSFER_CHARACTERISTICS_UNSPECIFIED);
// should generate a gray profile
EXPECT_EQ(image->icc.size, kGrayProfileSize);
// Generated profile is tested in test_cmd_icc_profile.sh
}
// Verify we can read a color PNG file tagged as sRGB through sRGB chunk,
// and set color primaries and transfer characteristics correctly.
TEST(PngTest, SRGBTagged) {
const ImagePtr image = testutil::ReadImage(data_path, "ffffcc-srgb.png");
ASSERT_NE(image, nullptr);
// should set to BT709 primaries and SRGB transfer
EXPECT_EQ(image->colorPrimaries, AVIF_COLOR_PRIMARIES_BT709);
EXPECT_EQ(image->transferCharacteristics, AVIF_TRANSFER_CHARACTERISTICS_SRGB);
// should not generate ICC profile
EXPECT_EQ(image->icc.size, 0u);
}
// Verify we are not generating profile if asked to ignore it.
TEST(PngTest, IgnoreProfile) {
const ImagePtr image = testutil::ReadImage(
data_path, "ffffcc-gamma1.6.png", AVIF_PIXEL_FORMAT_NONE, 0,
AVIF_CHROMA_DOWNSAMPLING_AUTOMATIC, true);
ASSERT_NE(image, nullptr);
// should be left unspecified
EXPECT_EQ(image->colorPrimaries, AVIF_COLOR_PRIMARIES_UNSPECIFIED);
EXPECT_EQ(image->transferCharacteristics, AVIF_COLOR_PRIMARIES_UNSPECIFIED);
// should not generate ICC profile
EXPECT_EQ(image->icc.size, 0u);
}
// Verify we can read a PNG file tagged as gamma 2.2 through gAMA chunk
// and BT709 primaries through cHRM chunk,
// and set color primaries and transfer characteristics correctly.
TEST(PngTest, BT709Gamma22) {
const ImagePtr image =
testutil::ReadImage(data_path, "ArcTriomphe-cHRM-orig.png");
ASSERT_NE(image, nullptr);
// primaries should match BT709
EXPECT_EQ(image->colorPrimaries, AVIF_COLOR_PRIMARIES_BT709);
// gamma 2.2 should match BT470M
EXPECT_EQ(image->transferCharacteristics,
AVIF_TRANSFER_CHARACTERISTICS_BT470M);
// should not generate ICC profile
EXPECT_EQ(image->icc.size, 0u);
}
// Verify we can read a PNG file tagged as gamma 2.2 through gAMA chunk
// and BT709 primaries with red and green swapped through cHRM chunk,
// and generate a color profile for it.
TEST(PngTest, BT709SwappedGamma22) {
const ImagePtr image =
testutil::ReadImage(data_path, "ArcTriomphe-cHRM-red-green-swap.png");
ASSERT_NE(image, nullptr);
// if ICC profile generated, CP and TC should be set to unspecified
EXPECT_EQ(image->colorPrimaries, AVIF_COLOR_PRIMARIES_UNSPECIFIED);
EXPECT_EQ(image->transferCharacteristics,
AVIF_TRANSFER_CHARACTERISTICS_UNSPECIFIED);
// should generate a color profile
EXPECT_EQ(image->icc.size, kColorProfileSize);
// Generated profile is tested in test_cmd_icc_profile.sh
}
//------------------------------------------------------------------------------
// ICC metadata tests
constexpr size_t kChecksumOffset = 0x54;
// Verify we wrote correct hash in generated ICC profile.
TEST(ICCTest, GeneratedICCHash) {
float primariesCoords[8];
avifColorPrimariesGetValues(AVIF_COLOR_PRIMARIES_BT709, primariesCoords);
testutil::AvifRwData icc;
EXPECT_EQ(avifGenerateRGBICC(&icc, 2.2f, primariesCoords), AVIF_TRUE);
// Steps to generate this checksum:
// - memset 16 bytes starting from icc.data + kChecksumOffset to 0
// - write `icc` to file
// - run `md5sum` with the written file
const uint8_t expectedChecksumRGB[] = {
// 89b06c4cc611c3110c022e06e6a0f81b
0x89, 0xb0, 0x6c, 0x4c, 0xc6, 0x11, 0xc3, 0x11,
0x0c, 0x02, 0x2e, 0x06, 0xe6, 0xa0, 0xf8, 0x1b,
};
EXPECT_EQ(memcmp(icc.data + kChecksumOffset, expectedChecksumRGB,
sizeof(expectedChecksumRGB)),
0);
EXPECT_EQ(avifGenerateGrayICC(&icc, 2.2f, primariesCoords), AVIF_TRUE);
// Steps to generate this checksum:
// - memset 16 bytes starting from icc.data + kChecksumOffset to 0
// - write `icc` to file
// - run `md5sum` with the written file
const uint8_t expectedChecksumGray[] = {
// 7610e64f148ebe4d00cafa56cf45aea0
0x76, 0x10, 0xe6, 0x4f, 0x14, 0x8e, 0xbe, 0x4d,
0x00, 0xca, 0xfa, 0x56, 0xcf, 0x45, 0xae, 0xa0,
};
EXPECT_EQ(memcmp(icc.data + kChecksumOffset, expectedChecksumGray,
sizeof(expectedChecksumGray)),
0);
}
//------------------------------------------------------------------------------
// Memory management tests
TEST(ImageSizeLimitTest, AllFormats) {
for (const uint32_t image_size_limit :
{1u, std::numeric_limits<uint32_t>::max()}) {
for (const char* filename :
{"paris_exif_xmp_icc.jpg", "paris_icc_exif_xmp.png",
"cosmos1650_yuv444_10bpc_p3pq.y4m"}) {
const std::string filepath = std::string(data_path) + filename;
ImagePtr image(avifImageCreateEmpty());
ASSERT_NE(image, nullptr);
const avifAppFileFormat format = avifReadImage(
filepath.c_str(), AVIF_PIXEL_FORMAT_NONE, /*requestedDepth=*/0,
AVIF_CHROMA_DOWNSAMPLING_AUTOMATIC, /*ignoreColorProfile=*/true,
/*ignoreExif=*/true, /*ignoreXMP=*/true, /*allowChangingCicp=*/true,
/*ignoreGainMap=*/true, image_size_limit, image.get(),
/*outDepth=*/nullptr, /*sourceTiming=*/nullptr,
/*frameIter=*/nullptr);
if (image_size_limit == 1) {
EXPECT_EQ(format, AVIF_APP_FILE_FORMAT_UNKNOWN);
} else {
EXPECT_NE(format, AVIF_APP_FILE_FORMAT_UNKNOWN);
}
}
}
}
//------------------------------------------------------------------------------
} // 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();
}