blob: cde23a4526bbb09791060a5cc695f37ec6d8bd0d [file] [log] [blame]
// Copyright 2022 Google LLC. All rights reserved.
// SPDX-License-Identifier: BSD-2-Clause
#include "avif/avif.h"
#include "y4m.h"
#include <cassert>
#include <cinttypes>
#include <cstdio>
#include <cstring>
#include <sstream>
#include <tuple>
#include "gtest/gtest.h"
using testing::Combine;
using testing::Values;
namespace
{
//------------------------------------------------------------------------------
// Returns true if image1 and image2 are identical.
bool compareYUVA(const avifImage * image1, const avifImage * image2)
{
if (image1->width != image2->width || image1->height != image2->height || image1->depth != image2->depth ||
image1->yuvFormat != image2->yuvFormat || image1->yuvRange != image2->yuvRange) {
printf("ERROR: input mismatch\n");
return false;
}
assert(image1->width * image1->height > 0);
avifPixelFormatInfo formatInfo;
avifGetPixelFormatInfo(image1->yuvFormat, &formatInfo);
const uint32_t uvWidth = (image1->width + formatInfo.chromaShiftX) >> formatInfo.chromaShiftX;
const uint32_t uvHeight = (image1->height + formatInfo.chromaShiftY) >> formatInfo.chromaShiftY;
const int planeCount = formatInfo.monochrome ? 1 : AVIF_PLANE_COUNT_YUV;
for (int plane = 0; plane < planeCount; ++plane) {
const uint32_t widthByteCount =
((plane == AVIF_CHAN_Y) ? image1->width : uvWidth) * ((image1->depth > 8) ? sizeof(uint16_t) : sizeof(uint8_t));
const uint32_t height = (plane == AVIF_CHAN_Y) ? image1->height : uvHeight;
const uint8_t * row1 = image1->yuvPlanes[plane];
const uint8_t * row2 = image2->yuvPlanes[plane];
for (uint32_t y = 0; y < height; ++y) {
if (memcmp(row1, row2, widthByteCount) != 0) {
printf("ERROR: different px at row %" PRIu32 ", channel %" PRIu32 "\n", y, plane);
return false;
}
row1 += image1->yuvRowBytes[plane];
row2 += image2->yuvRowBytes[plane];
}
}
if (image1->alphaPlane || image2->alphaPlane) {
if (!image1->alphaPlane || !image2->alphaPlane || image1->alphaPremultiplied != image2->alphaPremultiplied) {
printf("ERROR: input mismatch\n");
return false;
}
const uint32_t widthByteCount = image1->width * ((image1->depth > 8) ? sizeof(uint16_t) : sizeof(uint8_t));
const uint8_t * row1 = image1->alphaPlane;
const uint8_t * row2 = image2->alphaPlane;
for (uint32_t y = 0; y < image1->height; ++y) {
if (memcmp(row1, row2, widthByteCount) != 0) {
printf("ERROR: different px at row %" PRIu32 ", alpha\n", y);
return false;
}
row1 += image1->alphaRowBytes;
row2 += image2->alphaRowBytes;
}
}
return true;
}
//------------------------------------------------------------------------------
// Fills each plane of the image with the maximum allowed value.
void fillPlanes(avifImage * image)
{
const uint16_t yuvValue = (image->yuvRange == AVIF_RANGE_LIMITED) ? (235 << (image->depth - 8)) : ((1 << image->depth) - 1);
avifPixelFormatInfo formatInfo;
avifGetPixelFormatInfo(image->yuvFormat, &formatInfo);
const int planeCount = formatInfo.monochrome ? 1 : AVIF_PLANE_COUNT_YUV;
for (int plane = 0; plane < planeCount; ++plane) {
if (image->yuvPlanes[plane]) {
const uint32_t planeWidth =
(plane == AVIF_CHAN_Y) ? image->width : ((image->width + formatInfo.chromaShiftX) >> formatInfo.chromaShiftX);
const uint32_t planeHeight =
(plane == AVIF_CHAN_Y) ? image->height : ((image->height + formatInfo.chromaShiftY) >> formatInfo.chromaShiftY);
for (uint32_t y = 0; y < planeHeight; ++y) {
uint8_t * const row = image->yuvPlanes[plane] + y * image->yuvRowBytes[plane];
if (image->depth == 8) {
memset(row, yuvValue, planeWidth);
} else {
for (uint32_t x = 0; x < planeWidth; ++x) {
((uint16_t *)row)[x] = yuvValue;
}
}
}
}
}
if (image->alphaPlane) {
const uint16_t alphaValue = (1 << image->depth) - 1;
for (uint32_t y = 0; y < image->height; ++y) {
uint8_t * const row = image->alphaPlane + y * image->alphaRowBytes;
if (image->depth == 8) {
memset(row, alphaValue, image->width);
} else {
for (uint32_t x = 0; x < image->width; ++x) {
((uint16_t *)row)[x] = alphaValue;
}
}
}
}
}
// Creates an image and encodes then decodes it as a y4m file.
bool encodeDecodeY4m(uint32_t width,
uint32_t height,
uint32_t depth,
avifPixelFormat yuvFormat,
avifRange yuvRange,
bool createAlpha,
const char filePath[])
{
bool success = false;
avifImage * image = avifImageCreateEmpty();
avifImage * decoded = avifImageCreateEmpty();
if (!image || !decoded) {
printf("ERROR: avifImageCreate() failed\n");
goto cleanup;
}
image->width = width;
image->height = height;
image->depth = depth;
image->yuvFormat = yuvFormat;
image->yuvRange = yuvRange;
avifImageAllocatePlanes(image, createAlpha ? AVIF_PLANES_ALL : AVIF_PLANES_YUV);
fillPlanes(image);
if (!y4mWrite(filePath, image)) {
printf("ERROR: y4mWrite() failed\n");
goto cleanup;
}
if (!y4mRead(filePath, decoded, /*sourceTiming=*/NULL, /*iter=*/NULL)) {
printf("ERROR: y4mRead() failed\n");
goto cleanup;
}
if (!compareYUVA(image, decoded)) {
goto cleanup;
}
success = true;
cleanup:
if (image) {
avifImageDestroy(image);
}
if (decoded) {
avifImageDestroy(decoded);
}
return success;
}
//------------------------------------------------------------------------------
class Y4mTest
: public testing::TestWithParam<std::tuple</*width=*/int, /*height=*/int, /*bitDepth=*/int, /*yuvFormat=*/avifPixelFormat, /*yuvRange=*/avifRange, /*createAlpha=*/bool>>
{
};
TEST_P(Y4mTest, EncodeDecode)
{
const int width = std::get<0>(GetParam());
const int height = std::get<1>(GetParam());
const int bitDepth = std::get<2>(GetParam());
const avifPixelFormat yuvFormat = std::get<3>(GetParam());
const avifRange yuvRange = std::get<4>(GetParam());
const bool createAlpha = std::get<5>(GetParam());
std::ostringstream filePath;
filePath << testing::TempDir() << "avify4mtest_" << width << "_" << height << "_" << bitDepth << "_" << yuvFormat << "_"
<< yuvRange << "_" << createAlpha;
EXPECT_TRUE(encodeDecodeY4m(width, height, bitDepth, yuvFormat, yuvRange, createAlpha, filePath.str().c_str()));
}
INSTANTIATE_TEST_SUITE_P(OpaqueCombinations,
Y4mTest,
Combine(/*width=*/Values(1, 2, 3),
/*height=*/Values(1, 2, 3),
/*depths=*/Values(8, 10, 12),
Values(AVIF_PIXEL_FORMAT_YUV444, AVIF_PIXEL_FORMAT_YUV422, AVIF_PIXEL_FORMAT_YUV420, AVIF_PIXEL_FORMAT_YUV400),
Values(AVIF_RANGE_LIMITED, AVIF_RANGE_FULL),
/*createAlpha=*/Values(false)));
// Writing alpha is currently only supported in 8bpc YUV444.
INSTANTIATE_TEST_SUITE_P(AlphaCombinations,
Y4mTest,
Combine(/*width=*/Values(1, 2, 3),
/*height=*/Values(1, 2, 3),
/*depths=*/Values(8),
Values(AVIF_PIXEL_FORMAT_YUV444),
Values(AVIF_RANGE_LIMITED, AVIF_RANGE_FULL),
/*createAlpha=*/Values(true)));
} // namespace