blob: e94eef4f212672fda3264b2d4dc6b44c41c434bb [file] [log] [blame] [edit]
// Copyright 2025 Google LLC
// SPDX-License-Identifier: BSD-2-Clause
#include "avif/avif.h"
#include "aviftest_helpers.h"
#include "gtest/gtest.h"
namespace avif {
namespace {
// Used to pass the data folder path to the GoogleTest suites.
const char* data_path = nullptr;
//------------------------------------------------------------------------------
TEST(TransformTest, ClapIrotImir) {
if (!testutil::Av1EncoderAvailable() || !testutil::Av1DecoderAvailable()) {
GTEST_SKIP() << "AV1 codec unavailable, skip test.";
}
avifDiagnostics diag{};
ImagePtr image =
testutil::CreateImage(/*width=*/12, /*height=*/34, /*depth=*/10,
AVIF_PIXEL_FORMAT_YUV444, AVIF_PLANES_ALL);
ASSERT_NE(image, nullptr);
testutil::FillImageGradient(image.get()); // The pixels do not matter.
image->transformFlags |= AVIF_TRANSFORM_CLAP;
const avifCropRect rect{/*x=*/4, /*y=*/6, /*width=*/8, /*height=*/10};
ASSERT_TRUE(avifCleanApertureBoxFromCropRect(
&image->clap, &rect, image->width, image->height, &diag));
image->transformFlags |= AVIF_TRANSFORM_IROT;
image->irot.angle = 1;
image->transformFlags |= AVIF_TRANSFORM_IMIR;
image->imir.axis = 1;
// Encode.
EncoderPtr encoder(avifEncoderCreate());
ASSERT_NE(encoder, nullptr);
encoder->speed = AVIF_SPEED_FASTEST;
testutil::AvifRwData encoded;
ASSERT_EQ(avifEncoderWrite(encoder.get(), image.get(), &encoded),
AVIF_RESULT_OK);
// Decode.
ImagePtr decoded(avifImageCreateEmpty());
ASSERT_NE(decoded, nullptr);
DecoderPtr decoder(avifDecoderCreate());
ASSERT_NE(decoder, nullptr);
ASSERT_EQ(avifDecoderReadMemory(decoder.get(), decoded.get(), encoded.data,
encoded.size),
AVIF_RESULT_OK);
EXPECT_EQ(decoded->transformFlags, image->transformFlags);
EXPECT_EQ(decoded->clap.widthN, image->clap.widthN);
EXPECT_EQ(decoded->clap.widthD, image->clap.widthD);
EXPECT_EQ(decoded->clap.heightN, image->clap.heightN);
EXPECT_EQ(decoded->clap.heightD, image->clap.heightD);
EXPECT_EQ(decoded->clap.horizOffN, image->clap.horizOffN);
EXPECT_EQ(decoded->clap.horizOffD, image->clap.horizOffD);
EXPECT_EQ(decoded->clap.vertOffN, image->clap.vertOffN);
EXPECT_EQ(decoded->clap.vertOffD, image->clap.vertOffD);
EXPECT_EQ(decoded->irot.angle, image->irot.angle);
EXPECT_EQ(decoded->imir.axis, image->imir.axis);
}
TEST(TransformTest, ClapIrotImirNonEssential) {
// Invalid file with non-essential transformative properties.
const std::string path =
std::string(data_path) + "clap_irot_imir_non_essential.avif";
DecoderPtr decoder(avifDecoderCreate());
ASSERT_NE(decoder, nullptr);
ASSERT_EQ(avifDecoderSetIOFile(decoder.get(), path.c_str()), AVIF_RESULT_OK);
ASSERT_EQ(avifDecoderParse(decoder.get()), AVIF_RESULT_BMFF_PARSE_FAILED);
}
TEST(TransformTest, ClopIrotImor) {
// File with a non-essential unrecognized property 'clop', an essential
// transformation property 'irot', and a non-essential unrecognized property
// 'imor'.
const std::string path = std::string(data_path) + "clop_irot_imor.avif";
DecoderPtr decoder(avifDecoderCreate());
ASSERT_NE(decoder, nullptr);
ASSERT_EQ(avifDecoderSetIOFile(decoder.get(), path.c_str()), AVIF_RESULT_OK);
ASSERT_EQ(avifDecoderParse(decoder.get()), AVIF_RESULT_OK);
// 'imor' should be ignored as it is after a transformative property in the
// 'ipma' association order. libavif still surfaces it because this constraint
// is relaxed in Amd2 of HEIF ISO/IEC 23008-12.
// See https://github.com/MPEGGroup/FileFormat/issues/113.
ASSERT_EQ(decoder->image->numProperties, 2u);
const avifImageItemProperty& clop = decoder->image->properties[0];
EXPECT_EQ(std::string(clop.boxtype, clop.boxtype + 4), "clop");
const avifImageItemProperty& imor = decoder->image->properties[1];
EXPECT_EQ(std::string(imor.boxtype, imor.boxtype + 4), "imor");
}
TEST(TransformTest, IrotAlphaEncodedDecoded) {
if (!testutil::Av1EncoderAvailable() || !testutil::Av1DecoderAvailable()) {
GTEST_SKIP() << "AV1 codec unavailable, skip test.";
}
ImagePtr image = testutil::ReadImage(data_path, "abc.png");
image->transformFlags |= AVIF_TRANSFORM_IROT;
image->irot.angle = 1;
// Encode.
EncoderPtr encoder(avifEncoderCreate());
ASSERT_NE(encoder, nullptr);
encoder->quality = AVIF_QUALITY_LOSSLESS;
encoder->speed = AVIF_SPEED_FASTEST;
testutil::AvifRwData encoded;
ASSERT_EQ(avifEncoderWrite(encoder.get(), image.get(), &encoded),
AVIF_RESULT_OK);
// Decode.
ImagePtr decoded(avifImageCreateEmpty());
ASSERT_NE(decoded, nullptr);
DecoderPtr decoder(avifDecoderCreate());
ASSERT_NE(decoder, nullptr);
ASSERT_EQ(avifDecoderReadMemory(decoder.get(), decoded.get(), encoded.data,
encoded.size),
AVIF_RESULT_OK);
// Note that rav1e does not support lossless encoding as of v0.8.0.
// Otherwise AreImagesEqual() could be used here.
EXPECT_TRUE(testutil::AreImagesSimilar(*image, *decoded));
// Check with existing correct AVIF file.
const std::string ref_path =
std::string(data_path) + "abc_color_irot_alpha_irot.avif";
ImagePtr ref_decoded(avifImageCreateEmpty());
ASSERT_NE(ref_decoded, nullptr);
DecoderPtr ref_decoder(avifDecoderCreate());
ASSERT_NE(ref_decoder, nullptr);
ASSERT_EQ(avifDecoderReadFile(ref_decoder.get(), ref_decoded.get(),
ref_path.c_str()),
AVIF_RESULT_OK);
// Note that rav1e does not support lossless encoding as of v0.8.0.
// Otherwise AreImagesEqual() could be used here.
EXPECT_TRUE(testutil::AreImagesSimilar(*decoded, *ref_decoded));
// The rendering of the images should be compared but that is outside the
// scope of libavif.
}
TEST(TransformTest, AlphaIrotNoIrot) {
if (!testutil::Av1DecoderAvailable()) {
GTEST_SKIP() << "AV1 codec unavailable, skip test.";
}
const std::string ref_path =
std::string(data_path) + "abc_color_irot_alpha_irot.avif";
ImagePtr ref_decoded(avifImageCreateEmpty());
ASSERT_NE(ref_decoded, nullptr);
DecoderPtr ref_decoder(avifDecoderCreate());
ASSERT_NE(ref_decoder, nullptr);
ASSERT_EQ(avifDecoderReadFile(ref_decoder.get(), ref_decoded.get(),
ref_path.c_str()),
AVIF_RESULT_OK);
// Read a file missing an 'irot' property associated with the alpha auxiliary
// image item. While this should not lead to the same output, libavif still
// decodes it the same way because libavif used to encode it without that
// association.
const std::string path =
std::string(data_path) + "abc_color_irot_alpha_NOirot.avif";
ImagePtr decoded(avifImageCreateEmpty());
ASSERT_NE(decoded, nullptr);
DecoderPtr decoder(avifDecoderCreate());
ASSERT_NE(decoder, nullptr);
ASSERT_EQ(avifDecoderReadFile(decoder.get(), decoded.get(), path.c_str()),
AVIF_RESULT_OK);
EXPECT_TRUE(testutil::AreImagesEqual(*decoded, *ref_decoded));
}
//------------------------------------------------------------------------------
} // 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();
}