blob: d714814f87acfee8e582e50cca7be02f903974f2 [file] [log] [blame]
// Copyright 2022 Google LLC. All rights reserved.
// SPDX-License-Identifier: BSD-2-Clause
#include <assert.h>
#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "avif/avif.h"
//------------------------------------------------------------------------------
// ICC color profiles are not checked by libavif so the content does not matter.
// This is a truncated widespread ICC color profile.
static const uint8_t sampleICC[] = { 0x00, 0x00, 0x02, 0x0c, 0x6c, 0x63, 0x6d, 0x73, 0x02, 0x10, 0x00, 0x00,
0x6d, 0x6e, 0x74, 0x72, 0x52, 0x47, 0x42, 0x20, 0x58, 0x59, 0x5a, 0x20 };
static const size_t sampleICCSize = sizeof(sampleICC) / sizeof(sampleICC[0]);
// Exif bytes are partially checked by libavif. This is a truncated widespread Exif metadata chunk.
static const uint8_t sampleExif[] = { 0xff, 0x1, 0x45, 0x78, 0x69, 0x76, 0x32, 0xff, 0xe1, 0x12, 0x5a, 0x45,
0x78, 0x69, 0x66, 0x0, 0x0, 0x49, 0x49, 0x2a, 0x0, 0x8, 0x0, 0x0 };
static const size_t sampleExifSize = sizeof(sampleExif) / sizeof(sampleExif[0]);
// XMP bytes are not checked by libavif so the content does not matter.
// This is a truncated widespread XMP metadata chunk.
static const uint8_t sampleXMP[] = { 0x3c, 0x3f, 0x78, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x20, 0x62, 0x65,
0x67, 0x69, 0x6e, 0x3d, 0x22, 0xef, 0xbb, 0xbf, 0x22, 0x20, 0x69, 0x64 };
static const size_t sampleXMPSize = sizeof(sampleXMP) / sizeof(sampleXMP[0]);
//------------------------------------------------------------------------------
// TODO: Move these functions to a tests/common/helper.c shared with avifgridapitest.c
// Fills a plane with a repeating gradient.
static void fillPlane(int width, int height, int depth, uint8_t * row, uint32_t rowBytes)
{
assert((depth == 8) || (depth == 10) || (depth == 12)); // Values allowed by AV1.
const int maxValuePlusOne = 1 << depth;
for (int y = 0; y < height; ++y) {
if (depth == 8) {
memset(row, y % maxValuePlusOne, width);
} else {
for (int x = 0; x < width; ++x) {
((uint16_t *)row)[x] = (uint16_t)(y % maxValuePlusOne);
}
}
row += rowBytes;
}
}
// Creates an image where the pixel values are defined but do not matter.
// Returns false in case of memory failure.
static avifBool createImage(int width, int height, int depth, avifPixelFormat yuvFormat, avifBool createAlpha, avifImage ** image)
{
*image = avifImageCreate(width, height, depth, yuvFormat);
if (!*image) {
printf("ERROR: avifImageCreate() failed\n");
return AVIF_FALSE;
}
avifImageAllocatePlanes(*image, createAlpha ? AVIF_PLANES_ALL : AVIF_PLANES_YUV);
avifPixelFormatInfo formatInfo;
avifGetPixelFormatInfo((*image)->yuvFormat, &formatInfo);
uint32_t uvWidth = ((*image)->width + formatInfo.chromaShiftX) >> formatInfo.chromaShiftX;
uint32_t uvHeight = ((*image)->height + formatInfo.chromaShiftY) >> formatInfo.chromaShiftY;
const int planeCount = formatInfo.monochrome ? 1 : AVIF_PLANE_COUNT_YUV;
for (int plane = 0; plane < planeCount; ++plane) {
fillPlane((plane == AVIF_CHAN_Y) ? (*image)->width : uvWidth,
(plane == AVIF_CHAN_Y) ? (*image)->height : uvHeight,
(*image)->depth,
(*image)->yuvPlanes[plane],
(*image)->yuvRowBytes[plane]);
}
if (createAlpha) {
fillPlane((*image)->width, (*image)->height, (*image)->depth, (*image)->alphaPlane, (*image)->alphaRowBytes);
}
return AVIF_TRUE;
}
static avifBool createImage1x1(avifImage ** image)
{
return createImage(/*width=*/1, /*height=*/1, /*depth=*/10, AVIF_PIXEL_FORMAT_YUV444, /*createAlpha=*/AVIF_TRUE, image);
}
//------------------------------------------------------------------------------
// Encodes the image. Returns false in case of failure.
static avifBool encode(const avifImage * image, avifRWData * output)
{
avifBool success = AVIF_FALSE;
avifEncoder * encoder = avifEncoderCreate();
if (!encoder) {
printf("ERROR: avifEncoderCreate() failed\n");
goto cleanup;
}
encoder->speed = AVIF_SPEED_FASTEST;
if (avifEncoderWrite(encoder, image, output) != AVIF_RESULT_OK) {
printf("ERROR: avifEncoderWrite() failed\n");
goto cleanup;
}
success = AVIF_TRUE;
cleanup:
if (encoder) {
avifEncoderDestroy(encoder);
}
return success;
}
// Decodes the data. Returns false in case of failure.
static avifBool decode(const avifRWData * encodedAvif, avifImage ** image)
{
avifBool success = AVIF_FALSE;
*image = avifImageCreateEmpty();
avifDecoder * const decoder = avifDecoderCreate();
if (!*image || !decoder) {
printf("ERROR: memory allocation failed\n");
goto cleanup;
}
if (avifDecoderReadMemory(decoder, *image, encodedAvif->data, encodedAvif->size) != AVIF_RESULT_OK) {
printf("ERROR: avifDecoderReadMemory() failed\n");
goto cleanup;
}
success = AVIF_TRUE;
cleanup:
if (decoder) {
avifDecoderDestroy(decoder);
}
return success;
}
//------------------------------------------------------------------------------
// Returns true if the decoded output metadata matches the input metadata.
static avifBool metadataIsEqual(const avifRWData * inputItem, const avifRWData * outputItem)
{
return (outputItem->size == inputItem->size) && (memcmp(outputItem->data, inputItem->data, inputItem->size) == 0);
}
// Encodes, decodes then compares the metadata of the input and decoded images.
static avifBool encodeDecode(const avifImage * image)
{
avifBool success = AVIF_FALSE;
avifRWData encodedAvif = AVIF_DATA_EMPTY;
avifImage * decodedImage = NULL;
if (!encode(image, &encodedAvif)) {
goto cleanup;
}
if (!decode(&encodedAvif, &decodedImage)) {
goto cleanup;
}
if (!metadataIsEqual(&image->icc, &decodedImage->icc)) {
printf("ERROR: icc mismatch\n");
goto cleanup;
}
if (!metadataIsEqual(&image->exif, &decodedImage->exif)) {
printf("ERROR: Exif metadata mismatch\n");
goto cleanup;
}
if (!metadataIsEqual(&image->xmp, &decodedImage->xmp)) {
printf("ERROR: XMP metadata mismatch\n");
goto cleanup;
}
success = AVIF_TRUE;
cleanup:
avifRWDataFree(&encodedAvif);
if (decodedImage) {
avifImageDestroy(decodedImage);
}
return success;
}
//------------------------------------------------------------------------------
// Encodes, decodes then verifies that the output metadata matches the input
// metadata defined by the arguments.
static avifBool encodeDecodeMetadataItems(avifBool useICC, avifBool useExif, avifBool useXMP)
{
avifBool success = AVIF_FALSE;
avifImage * image = NULL;
if (!createImage1x1(&image)) {
goto cleanup;
}
if (useICC) {
avifImageSetProfileICC(image, sampleICC, sampleICCSize);
}
if (useExif) {
avifImageSetMetadataExif(image, sampleExif, sampleExifSize);
}
if (useXMP) {
avifImageSetMetadataXMP(image, sampleXMP, sampleXMPSize);
}
if (!encodeDecode(image)) {
goto cleanup;
}
success = AVIF_TRUE;
cleanup:
if (image) {
avifImageDestroy(image);
}
return success;
}
int main(void)
{
if (!encodeDecodeMetadataItems(/*useICC=*/AVIF_TRUE, /*useExif=*/AVIF_FALSE, /*useXMP=*/AVIF_FALSE)) {
return EXIT_FAILURE;
}
if (!encodeDecodeMetadataItems(/*useICC=*/AVIF_FALSE, /*useExif=*/AVIF_TRUE, /*useXMP=*/AVIF_FALSE)) {
return EXIT_FAILURE;
}
if (!encodeDecodeMetadataItems(/*useICC=*/AVIF_FALSE, /*useExif=*/AVIF_FALSE, /*useXMP=*/AVIF_TRUE)) {
return EXIT_FAILURE;
}
// TODO: Negative test
// TODO: Multi xmp test
printf("Test passed.\n");
return EXIT_SUCCESS;
}