blob: ad8857542e5c341b274bf41ac5a282b1dba97484 [file] [log] [blame]
// Copyright 2023 Google LLC
// SPDX-License-Identifier: BSD-2-Clause
#include <algorithm>
#include <cstring>
#include <tuple>
#include "aviftest_helpers.h"
#include "gtest/gtest.h"
namespace avif {
namespace {
enum class InputType { kStillImage, kGrid, kAnimation };
// Generates a valid AVIF stream with the video range flag set to the same value
// in the "colr" box and in the AV1 OBU payload (in the "mdat" box).
avifResult GenerateEncodedData(avifRange range, InputType input_type,
testutil::AvifRwData* encoded) {
ImagePtr image =
testutil::CreateImage(/*width=*/64, /*height=*/64, /*depth=*/8,
AVIF_PIXEL_FORMAT_YUV420, AVIF_PLANES_ALL, range);
AVIF_CHECKERR(image != nullptr, AVIF_RESULT_OUT_OF_MEMORY);
testutil::FillImageGradient(image.get()); // Pixel values do not matter.
EncoderPtr encoder(avifEncoderCreate());
AVIF_CHECKERR(encoder != nullptr, AVIF_RESULT_OUT_OF_MEMORY);
if (input_type == InputType::kStillImage) {
AVIF_CHECKRES(avifEncoderWrite(encoder.get(), image.get(), encoded));
} else if (input_type == InputType::kGrid) {
const avifImage* cellImages[2] = {image.get(), image.get()};
AVIF_CHECKRES(avifEncoderAddImageGrid(encoder.get(), /*gridCols=*/2,
/*gridRows=*/1, cellImages,
AVIF_ADD_IMAGE_FLAG_SINGLE));
AVIF_CHECKRES(avifEncoderFinish(encoder.get(), encoded));
} else {
assert(input_type == InputType::kAnimation);
AVIF_CHECKRES(avifEncoderAddImage(encoder.get(), image.get(),
/*durationInTimescales=*/1,
AVIF_ADD_IMAGE_FLAG_NONE));
AVIF_CHECKRES(avifEncoderAddImage(encoder.get(), image.get(),
/*durationInTimescales=*/1,
AVIF_ADD_IMAGE_FLAG_NONE));
AVIF_CHECKRES(avifEncoderFinish(encoder.get(), encoded));
}
return AVIF_RESULT_OK;
}
class RangeTest
: public testing::TestWithParam<std::tuple<avifRange, InputType>> {};
TEST_P(RangeTest, DifferentVideoRangeInColrAndMdat) {
const avifRange obu_range = std::get<0>(GetParam());
const InputType input_type = std::get<1>(GetParam());
testutil::AvifRwData encoded;
ASSERT_EQ(GenerateEncodedData(obu_range, input_type, &encoded),
AVIF_RESULT_OK);
// Set full_range_flag in the "colr" box to a different value.
// This creates an invalid bitstream according to AV1-ISOBMFF v1.2.0 but
// libavif still allows it.
const avifRange colr_range =
(obu_range == AVIF_RANGE_LIMITED) ? AVIF_RANGE_FULL : AVIF_RANGE_LIMITED;
const uint8_t kColrBoxTag[] = "colr";
uint8_t* colr_box = std::search(encoded.data, encoded.data + encoded.size,
kColrBoxTag, kColrBoxTag + 4);
do {
ASSERT_GT(colr_box, encoded.data + 4);
ASSERT_LT(colr_box, encoded.data + encoded.size);
const uint32_t colr_box_size = (colr_box[-4] << 24) | (colr_box[-3] << 16) |
(colr_box[-2] << 8) | colr_box[-1];
ASSERT_EQ(colr_box_size, 19u);
ASSERT_LT(colr_box + colr_box_size - 4, encoded.data + encoded.size);
ASSERT_TRUE(std::equal(colr_box + 4, colr_box + 8,
reinterpret_cast<const uint8_t*>("nclx")));
// full_range_flag(1bit)=colr_range, reserved(7bits)=0
colr_box[colr_box_size - 5] = static_cast<uint8_t>(colr_range << 7);
colr_box = std::search(colr_box + 4, encoded.data + encoded.size,
kColrBoxTag, kColrBoxTag + 4);
} while (colr_box != encoded.data + encoded.size);
// Section 12.1.5.1 of ISO 14496-12 (ISOBMFF) says:
// If colour information is supplied in both this [colr] box, and also in
// the video bitstream, this box takes precedence, and over-rides the
// information in the bitstream.
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);
ASSERT_EQ(decoded->yuvRange, colr_range);
}
TEST_P(RangeTest, MissingColr) {
const avifRange obu_range = std::get<0>(GetParam());
const InputType input_type = std::get<1>(GetParam());
testutil::AvifRwData encoded;
ASSERT_EQ(GenerateEncodedData(obu_range, input_type, &encoded),
AVIF_RESULT_OK);
// Remove the "colr" box (by replacing it with a placeholder).
// This creates an invalid bitstream according to AV1-ISOBMFF v1.2.0 but
// libavif still allows it.
const uint8_t kColrBoxTag[] = "colr";
uint8_t* colr_box = std::search(encoded.data, encoded.data + encoded.size,
kColrBoxTag, kColrBoxTag + 4);
do {
ASSERT_LT(colr_box + 4, encoded.data + encoded.size);
std::memcpy(colr_box, "free", 4);
colr_box = std::search(colr_box + 4, encoded.data + encoded.size,
kColrBoxTag, kColrBoxTag + 4);
} while (colr_box != encoded.data + encoded.size);
// Make sure the AV1 OBU range is kept.
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);
ASSERT_EQ(decoded->yuvRange, obu_range);
}
INSTANTIATE_TEST_SUITE_P(
All, RangeTest,
testing::Combine(testing::Values(AVIF_RANGE_LIMITED, AVIF_RANGE_FULL),
testing::Values(InputType::kStillImage, InputType::kGrid,
InputType::kAnimation)));
} // namespace
} // namespace avif