blob: b72d42b88e453a25f2d52f1016dde4b990e1562f [file] [log] [blame]
// Copyright 2022 Google LLC
// SPDX-License-Identifier: BSD-2-Clause
#include <array>
#include <tuple>
#include "avif/avif.h"
#include "aviftest_helpers.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;
//------------------------------------------------------------------------------
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"));
}
// 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 auto 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 auto 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 auto 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 auto 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 auto 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 auto 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 auto 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 auto 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 auto 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
}
constexpr size_t kChecksumOffset = 0x54;
// Verify we wrote correct hash in generated ICC profile.
TEST(PngTest, 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);
}
//------------------------------------------------------------------------------
} // 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();
}