583 lines
27 KiB
C
583 lines
27 KiB
C
// Copyright 2020 Joe Drago. All rights reserved.
|
|
// SPDX-License-Identifier: BSD-2-Clause
|
|
|
|
#include "avif/avif.h"
|
|
|
|
#include <inttypes.h>
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#define MAX_DRIFT 5
|
|
|
|
#define NEXTARG() \
|
|
if (((argIndex + 1) == argc) || (argv[argIndex + 1][0] == '-')) { \
|
|
fprintf(stderr, "%s requires an argument.", arg); \
|
|
return 1; \
|
|
} \
|
|
arg = argv[++argIndex]
|
|
|
|
// avifyuv:
|
|
// The goal here isn't to get perfect matches, as some codepoints will drift due to depth rescaling and/or YUV conversion.
|
|
// The "Matches"/"NoMatches" is just there as a quick visual confirmation when scanning the results.
|
|
// If you choose a more friendly starting color instead of orange (red, perhaps), you get considerably more matches,
|
|
// except in the cases where it doesn't make sense (going to RGB/BGR will forget the alpha / make it opaque).
|
|
|
|
static const char * rgbFormatToString(avifRGBFormat format)
|
|
{
|
|
switch (format) {
|
|
case AVIF_RGB_FORMAT_RGB:
|
|
return "RGB ";
|
|
case AVIF_RGB_FORMAT_RGBA:
|
|
return "RGBA";
|
|
case AVIF_RGB_FORMAT_ARGB:
|
|
return "ARGB";
|
|
case AVIF_RGB_FORMAT_BGR:
|
|
return "BGR ";
|
|
case AVIF_RGB_FORMAT_BGRA:
|
|
return "BGRA";
|
|
case AVIF_RGB_FORMAT_ABGR:
|
|
return "ABGR";
|
|
case AVIF_RGB_FORMAT_RGB_565:
|
|
return "RGB_565";
|
|
case AVIF_RGB_FORMAT_COUNT:
|
|
break;
|
|
}
|
|
return "Unknown";
|
|
}
|
|
|
|
typedef struct avifCICP
|
|
{
|
|
avifColorPrimaries cp;
|
|
avifTransferCharacteristics tc;
|
|
avifMatrixCoefficients mc;
|
|
} avifCICP;
|
|
|
|
int main(int argc, char * argv[])
|
|
{
|
|
(void)argc;
|
|
(void)argv;
|
|
|
|
printf("avif version: %s\n", avifVersion());
|
|
|
|
int mode = 0;
|
|
avifBool verbose = AVIF_FALSE;
|
|
|
|
int argIndex = 1;
|
|
while (argIndex < argc) {
|
|
const char * arg = argv[argIndex];
|
|
|
|
if (!strcmp(arg, "-m") || !strcmp(arg, "--mode")) {
|
|
NEXTARG();
|
|
if (!strcmp(arg, "limited")) {
|
|
mode = 0;
|
|
} else if (!strcmp(arg, "drift")) {
|
|
mode = 1;
|
|
} else if (!strcmp(arg, "rgb")) {
|
|
mode = 2;
|
|
} else if (!strcmp(arg, "premultiply")) {
|
|
mode = 3;
|
|
} else {
|
|
mode = atoi(arg);
|
|
}
|
|
} else if (!strcmp(arg, "-v") || !strcmp(arg, "--verbose")) {
|
|
verbose = AVIF_TRUE;
|
|
}
|
|
|
|
++argIndex;
|
|
}
|
|
|
|
const uint32_t yuvDepths[] = { 8, 10, 12 };
|
|
const int yuvDepthsCount = (int)(sizeof(yuvDepths) / sizeof(yuvDepths[0]));
|
|
const uint32_t rgbDepths[] = { 8, 10, 12 };
|
|
const int rgbDepthsCount = (int)(sizeof(rgbDepths) / sizeof(rgbDepths[0]));
|
|
const avifRange ranges[2] = { AVIF_RANGE_FULL, AVIF_RANGE_LIMITED };
|
|
|
|
if (mode == 0) {
|
|
// Limited to full conversion roundtripping test
|
|
|
|
uint32_t depth = 8;
|
|
int maxChannel = (1 << depth) - 1;
|
|
for (int i = 0; i <= maxChannel; ++i) {
|
|
int li = avifFullToLimitedY(depth, i);
|
|
int fi = avifLimitedToFullY(depth, li);
|
|
const char * prefix = "x";
|
|
if (i == fi) {
|
|
prefix = ".";
|
|
}
|
|
printf("%s %d -> %d -> %d\n", prefix, i, li, fi);
|
|
}
|
|
} else if (mode == 1) {
|
|
// Calculate maximum codepoint drift on different combinations of depth and CICPs
|
|
const avifCICP cicpList[] = {
|
|
{ AVIF_COLOR_PRIMARIES_BT709, AVIF_TRANSFER_CHARACTERISTICS_SRGB, AVIF_MATRIX_COEFFICIENTS_BT709 },
|
|
{ AVIF_COLOR_PRIMARIES_BT709, AVIF_TRANSFER_CHARACTERISTICS_SRGB, AVIF_MATRIX_COEFFICIENTS_BT601 },
|
|
{ AVIF_COLOR_PRIMARIES_BT709, AVIF_TRANSFER_CHARACTERISTICS_SRGB, AVIF_MATRIX_COEFFICIENTS_BT2020_NCL },
|
|
{ AVIF_COLOR_PRIMARIES_BT709, AVIF_TRANSFER_CHARACTERISTICS_SRGB, AVIF_MATRIX_COEFFICIENTS_IDENTITY },
|
|
{ AVIF_COLOR_PRIMARIES_BT709, AVIF_TRANSFER_CHARACTERISTICS_SRGB, AVIF_MATRIX_COEFFICIENTS_YCGCO },
|
|
{ AVIF_COLOR_PRIMARIES_SMPTE432, AVIF_TRANSFER_CHARACTERISTICS_SRGB, AVIF_MATRIX_COEFFICIENTS_CHROMA_DERIVED_NCL },
|
|
};
|
|
const int cicpCount = (int)(sizeof(cicpList) / sizeof(cicpList[0]));
|
|
|
|
for (int rgbDepthIndex = 0; rgbDepthIndex < rgbDepthsCount; ++rgbDepthIndex) {
|
|
uint32_t rgbDepth = rgbDepths[rgbDepthIndex];
|
|
for (int yuvDepthIndex = 0; yuvDepthIndex < yuvDepthsCount; ++yuvDepthIndex) {
|
|
uint32_t yuvDepth = yuvDepths[yuvDepthIndex];
|
|
if (yuvDepth < rgbDepth) {
|
|
// skip it
|
|
continue;
|
|
}
|
|
|
|
for (int cicpIndex = 0; cicpIndex < cicpCount; ++cicpIndex) {
|
|
const avifCICP * cicp = &cicpList[cicpIndex];
|
|
for (int rangeIndex = 0; rangeIndex < 2; ++rangeIndex) {
|
|
avifRange range = ranges[rangeIndex];
|
|
|
|
// YCgCo with limited range is not implemented now
|
|
if (range == AVIF_RANGE_LIMITED && cicp->mc == AVIF_MATRIX_COEFFICIENTS_YCGCO) {
|
|
printf(" * RGB depth: %d, YUV depth: %d, colorPrimaries: %d, transferCharas: %d, matrixCoeffs: %d, range: Limited\n"
|
|
" * Skipped: currently not supported.\n",
|
|
rgbDepth,
|
|
yuvDepth,
|
|
cicp->cp,
|
|
cicp->tc,
|
|
cicp->mc);
|
|
continue;
|
|
}
|
|
|
|
int dim = 1 << rgbDepth;
|
|
int maxDrift = 0;
|
|
|
|
avifImage * image = avifImageCreate(dim, dim, yuvDepth, AVIF_PIXEL_FORMAT_YUV444);
|
|
if (!image) {
|
|
fprintf(stderr, "ERROR: Out of memory\n");
|
|
return 1;
|
|
}
|
|
image->colorPrimaries = cicp->cp;
|
|
image->transferCharacteristics = cicp->tc;
|
|
image->matrixCoefficients = cicp->mc;
|
|
image->yuvRange = range;
|
|
if (avifImageAllocatePlanes(image, AVIF_PLANES_YUV) != AVIF_RESULT_OK) {
|
|
avifImageDestroy(image);
|
|
printf("ERROR: Out of memory\n");
|
|
return 1;
|
|
}
|
|
|
|
avifRGBImage srcRGB;
|
|
avifRGBImageSetDefaults(&srcRGB, image);
|
|
srcRGB.format = AVIF_RGB_FORMAT_RGB;
|
|
srcRGB.depth = rgbDepth;
|
|
|
|
avifRGBImage dstRGB;
|
|
avifRGBImageSetDefaults(&dstRGB, image);
|
|
dstRGB.format = AVIF_RGB_FORMAT_RGB;
|
|
dstRGB.depth = rgbDepth;
|
|
|
|
if ((avifRGBImageAllocatePixels(&srcRGB) != AVIF_RESULT_OK)) {
|
|
avifImageDestroy(image);
|
|
fprintf(stderr, "ERROR: Out of memory\n");
|
|
return 1;
|
|
}
|
|
if ((avifRGBImageAllocatePixels(&dstRGB) != AVIF_RESULT_OK)) {
|
|
avifRGBImageFreePixels(&srcRGB);
|
|
avifImageDestroy(image);
|
|
fprintf(stderr, "ERROR: Out of memory\n");
|
|
return 1;
|
|
}
|
|
|
|
uint64_t driftPixelCounts[MAX_DRIFT];
|
|
for (int i = 0; i < MAX_DRIFT; ++i) {
|
|
driftPixelCounts[i] = 0;
|
|
}
|
|
|
|
for (int r = 0; r < dim; ++r) {
|
|
if (verbose) {
|
|
printf("[%4d/%4d] RGB depth: %d, YUV depth: %d, colorPrimaries: %d, transferCharas: %d, matrixCoeffs: %d, range: %s\r",
|
|
r + 1,
|
|
dim,
|
|
rgbDepth,
|
|
yuvDepth,
|
|
cicp->cp,
|
|
cicp->tc,
|
|
cicp->mc,
|
|
range == AVIF_RANGE_FULL ? "Full" : "Limited");
|
|
}
|
|
|
|
for (int g = 0; g < dim; ++g) {
|
|
uint8_t * row = &srcRGB.pixels[g * srcRGB.rowBytes];
|
|
for (int b = 0; b < dim; ++b) {
|
|
if (rgbDepth == 8) {
|
|
uint8_t * pixel = &row[b * sizeof(uint8_t) * 3];
|
|
pixel[0] = (uint8_t)r;
|
|
pixel[1] = (uint8_t)g;
|
|
pixel[2] = (uint8_t)b;
|
|
} else {
|
|
uint16_t * pixel = (uint16_t *)&row[b * sizeof(uint16_t) * 3];
|
|
pixel[0] = (uint16_t)r;
|
|
pixel[1] = (uint16_t)g;
|
|
pixel[2] = (uint16_t)b;
|
|
}
|
|
}
|
|
}
|
|
|
|
avifImageRGBToYUV(image, &srcRGB);
|
|
avifImageYUVToRGB(image, &dstRGB);
|
|
|
|
for (int y = 0; y < dim; ++y) {
|
|
const uint8_t * srcRow = &srcRGB.pixels[y * srcRGB.rowBytes];
|
|
const uint8_t * dstRow = &dstRGB.pixels[y * dstRGB.rowBytes];
|
|
for (int x = 0; x < dim; ++x) {
|
|
int drift = 0;
|
|
if (rgbDepth == 8) {
|
|
const uint8_t * srcPixel = &srcRow[x * sizeof(uint8_t) * 3];
|
|
const uint8_t * dstPixel = &dstRow[x * sizeof(uint8_t) * 3];
|
|
|
|
const int driftR = abs((int)srcPixel[0] - (int)dstPixel[0]);
|
|
if (drift < driftR) {
|
|
drift = driftR;
|
|
}
|
|
const int driftG = abs((int)srcPixel[1] - (int)dstPixel[1]);
|
|
if (drift < driftG) {
|
|
drift = driftG;
|
|
}
|
|
const int driftB = abs((int)srcPixel[2] - (int)dstPixel[2]);
|
|
if (drift < driftB) {
|
|
drift = driftB;
|
|
}
|
|
} else {
|
|
const uint16_t * srcPixel = (const uint16_t *)&srcRow[x * sizeof(uint16_t) * 3];
|
|
const uint16_t * dstPixel = (const uint16_t *)&dstRow[x * sizeof(uint16_t) * 3];
|
|
|
|
const int driftR = abs((int)srcPixel[0] - (int)dstPixel[0]);
|
|
if (drift < driftR) {
|
|
drift = driftR;
|
|
}
|
|
const int driftG = abs((int)srcPixel[1] - (int)dstPixel[1]);
|
|
if (drift < driftG) {
|
|
drift = driftG;
|
|
}
|
|
const int driftB = abs((int)srcPixel[2] - (int)dstPixel[2]);
|
|
if (drift < driftB) {
|
|
drift = driftB;
|
|
}
|
|
}
|
|
|
|
if (drift < MAX_DRIFT) {
|
|
++driftPixelCounts[drift];
|
|
if (maxDrift < drift) {
|
|
maxDrift = drift;
|
|
}
|
|
} else {
|
|
fprintf(stderr,
|
|
"ERROR: Encountered a drift greater than or equal to MAX_DRIFT(%d): %d\n",
|
|
MAX_DRIFT,
|
|
drift);
|
|
return 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (verbose) {
|
|
printf("\n");
|
|
}
|
|
|
|
printf(" * RGB depth: %d, YUV depth: %d, colorPrimaries: %d, transferCharas: %d, matrixCoeffs: %d, range: %s, maxDrift: %2d\n",
|
|
rgbDepth,
|
|
yuvDepth,
|
|
cicp->cp,
|
|
cicp->tc,
|
|
cicp->mc,
|
|
range == AVIF_RANGE_FULL ? "Full" : "Limited",
|
|
maxDrift);
|
|
|
|
const uint64_t totalPixelCount = (uint64_t)dim * dim * dim;
|
|
for (int i = 0; i < MAX_DRIFT; ++i) {
|
|
if (verbose && (driftPixelCounts[i] > 0)) {
|
|
printf(" * drift: %2d -> %12" PRIu64 " / %12" PRIu64 " pixels (%.2f %%)\n",
|
|
i,
|
|
driftPixelCounts[i],
|
|
totalPixelCount,
|
|
(double)driftPixelCounts[i] * 100.0 / (double)totalPixelCount);
|
|
}
|
|
}
|
|
|
|
avifRGBImageFreePixels(&srcRGB);
|
|
avifRGBImageFreePixels(&dstRGB);
|
|
avifImageDestroy(image);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
} else if (mode == 2) {
|
|
// Stress test all RGB depths
|
|
|
|
uint32_t originalWidth = 32;
|
|
uint32_t originalHeight = 32;
|
|
avifBool showAllResults = AVIF_TRUE;
|
|
|
|
avifImage * image = avifImageCreate(originalWidth, originalHeight, 8, AVIF_PIXEL_FORMAT_YUV444);
|
|
if (!image) {
|
|
fprintf(stderr, "ERROR: Out of memory\n");
|
|
return 1;
|
|
}
|
|
|
|
for (int yuvDepthIndex = 0; yuvDepthIndex < yuvDepthsCount; ++yuvDepthIndex) {
|
|
uint32_t yuvDepth = yuvDepths[yuvDepthIndex];
|
|
|
|
avifRGBImage srcRGB;
|
|
avifRGBImageSetDefaults(&srcRGB, image);
|
|
srcRGB.depth = yuvDepth;
|
|
if (avifRGBImageAllocatePixels(&srcRGB) != AVIF_RESULT_OK) {
|
|
avifImageDestroy(image);
|
|
fprintf(stderr, "ERROR: Out of memory\n");
|
|
return 1;
|
|
}
|
|
if (yuvDepth > 8) {
|
|
float maxChannelF = (float)((1 << yuvDepth) - 1);
|
|
for (uint32_t j = 0; j < srcRGB.height; ++j) {
|
|
for (uint32_t i = 0; i < srcRGB.width; ++i) {
|
|
uint16_t * pixel = (uint16_t *)&srcRGB.pixels[(8 * i) + (srcRGB.rowBytes * j)];
|
|
pixel[0] = (uint16_t)maxChannelF; // R
|
|
pixel[1] = (uint16_t)(maxChannelF * 0.5f); // G
|
|
pixel[2] = 0; // B
|
|
pixel[3] = (uint16_t)(maxChannelF * 0.5f); // A
|
|
}
|
|
}
|
|
} else {
|
|
for (uint32_t j = 0; j < srcRGB.height; ++j) {
|
|
for (uint32_t i = 0; i < srcRGB.width; ++i) {
|
|
uint8_t * pixel = &srcRGB.pixels[(4 * i) + (srcRGB.rowBytes * j)];
|
|
pixel[0] = 255; // R
|
|
pixel[1] = 128; // G
|
|
pixel[2] = 0; // B
|
|
pixel[3] = 128; // A
|
|
}
|
|
}
|
|
}
|
|
|
|
const uint32_t depths[4] = { 8, 10, 12, 16 };
|
|
for (int depthIndex = 0; depthIndex < 4; ++depthIndex) {
|
|
uint32_t rgbDepth = depths[depthIndex];
|
|
for (int rangeIndex = 0; rangeIndex < 2; ++rangeIndex) {
|
|
avifRange yuvRange = ranges[rangeIndex];
|
|
const avifRGBFormat rgbFormats[6] = { AVIF_RGB_FORMAT_RGB, AVIF_RGB_FORMAT_RGBA, AVIF_RGB_FORMAT_ARGB,
|
|
AVIF_RGB_FORMAT_BGR, AVIF_RGB_FORMAT_BGRA, AVIF_RGB_FORMAT_ABGR };
|
|
for (int rgbFormatIndex = 0; rgbFormatIndex < 6; ++rgbFormatIndex) {
|
|
avifRGBFormat rgbFormat = rgbFormats[rgbFormatIndex];
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
avifImageFreePlanes(image, AVIF_PLANES_ALL);
|
|
image->depth = yuvDepth;
|
|
image->yuvRange = yuvRange;
|
|
avifImageRGBToYUV(image, &srcRGB);
|
|
|
|
avifRGBImage intermediateRGB;
|
|
avifRGBImageSetDefaults(&intermediateRGB, image);
|
|
intermediateRGB.depth = rgbDepth;
|
|
intermediateRGB.format = rgbFormat;
|
|
if (avifRGBImageAllocatePixels(&intermediateRGB) != AVIF_RESULT_OK) {
|
|
avifRGBImageFreePixels(&srcRGB);
|
|
avifImageDestroy(image);
|
|
fprintf(stderr, "ERROR: Out of memory\n");
|
|
return 1;
|
|
}
|
|
avifImageYUVToRGB(image, &intermediateRGB);
|
|
|
|
avifImageFreePlanes(image, AVIF_PLANES_ALL);
|
|
avifImageRGBToYUV(image, &intermediateRGB);
|
|
|
|
avifRGBImage dstRGB;
|
|
avifRGBImageSetDefaults(&dstRGB, image);
|
|
dstRGB.depth = yuvDepth;
|
|
if (avifRGBImageAllocatePixels(&dstRGB) != AVIF_RESULT_OK) {
|
|
avifRGBImageFreePixels(&intermediateRGB);
|
|
avifRGBImageFreePixels(&srcRGB);
|
|
avifImageDestroy(image);
|
|
fprintf(stderr, "ERROR: Out of memory\n");
|
|
return 1;
|
|
}
|
|
avifImageYUVToRGB(image, &dstRGB);
|
|
|
|
avifBool moveOn = AVIF_FALSE;
|
|
for (uint32_t j = 0; j < originalHeight; ++j) {
|
|
if (moveOn)
|
|
break;
|
|
for (uint32_t i = 0; i < originalWidth; ++i) {
|
|
if (yuvDepth > 8) {
|
|
uint16_t * srcPixel = (uint16_t *)&srcRGB.pixels[(8 * i) + (srcRGB.rowBytes * j)];
|
|
uint16_t * dstPixel = (uint16_t *)&dstRGB.pixels[(8 * i) + (dstRGB.rowBytes * j)];
|
|
avifBool matches = (memcmp(srcPixel, dstPixel, 8) == 0);
|
|
if (showAllResults || !matches) {
|
|
printf("yuvDepth:%2d rgbFormat:%s rgbDepth:%2d yuvRange:%7s (%d,%d) [%7s] (%d, %d, %d, %d) -> (%d, %d, %d, %d)\n",
|
|
yuvDepth,
|
|
rgbFormatToString(rgbFormat),
|
|
rgbDepth,
|
|
(yuvRange == AVIF_RANGE_LIMITED) ? "Limited" : "Full",
|
|
i,
|
|
j,
|
|
matches ? "Match" : "NoMatch",
|
|
srcPixel[0],
|
|
srcPixel[1],
|
|
srcPixel[2],
|
|
srcPixel[3],
|
|
dstPixel[0],
|
|
dstPixel[1],
|
|
dstPixel[2],
|
|
dstPixel[3]);
|
|
moveOn = AVIF_TRUE;
|
|
break;
|
|
}
|
|
} else {
|
|
uint8_t * srcPixel = &srcRGB.pixels[(4 * i) + (srcRGB.rowBytes * j)];
|
|
uint8_t * dstPixel = &dstRGB.pixels[(4 * i) + (dstRGB.rowBytes * j)];
|
|
avifBool matches = (memcmp(srcPixel, dstPixel, 4) == 0);
|
|
if (showAllResults || !matches) {
|
|
printf("yuvDepth:%2d rgbFormat:%s rgbDepth:%2d yuvRange:%7s (%d,%d) [%7s] (%d, %d, %d, %d) -> (%d, %d, %d, %d)\n",
|
|
yuvDepth,
|
|
rgbFormatToString(rgbFormat),
|
|
rgbDepth,
|
|
(yuvRange == AVIF_RANGE_LIMITED) ? "Limited" : "Full",
|
|
i,
|
|
j,
|
|
matches ? "Match" : "NoMatch",
|
|
srcPixel[0],
|
|
srcPixel[1],
|
|
srcPixel[2],
|
|
srcPixel[3],
|
|
dstPixel[0],
|
|
dstPixel[1],
|
|
dstPixel[2],
|
|
dstPixel[3]);
|
|
moveOn = AVIF_TRUE;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
avifRGBImageFreePixels(&intermediateRGB);
|
|
avifRGBImageFreePixels(&dstRGB);
|
|
|
|
// ----------------------------------------------------------------------
|
|
}
|
|
}
|
|
}
|
|
|
|
avifRGBImageFreePixels(&srcRGB);
|
|
}
|
|
avifImageDestroy(image);
|
|
} else if (mode == 3) {
|
|
// alpha premultiply roundtrip test
|
|
const uint32_t depths[4] = { 8, 10, 12, 16 };
|
|
uint64_t driftPixelCounts[MAX_DRIFT];
|
|
for (int depthIndex = 0; depthIndex < 4; ++depthIndex) {
|
|
uint32_t rgbDepth = depths[depthIndex];
|
|
uint32_t size = 1 << rgbDepth;
|
|
|
|
avifRGBImage rgb;
|
|
memset(&rgb, 0, sizeof(rgb));
|
|
rgb.alphaPremultiplied = AVIF_TRUE;
|
|
rgb.pixels = NULL;
|
|
rgb.format = AVIF_RGB_FORMAT_RGBA;
|
|
rgb.width = size;
|
|
rgb.height = 1;
|
|
rgb.depth = rgbDepth;
|
|
|
|
int maxDrift = 0;
|
|
for (int i = 0; i < MAX_DRIFT; ++i) {
|
|
driftPixelCounts[i] = 0;
|
|
}
|
|
if (avifRGBImageAllocatePixels(&rgb) != AVIF_RESULT_OK) {
|
|
fprintf(stderr, "ERROR: Out of memory\n");
|
|
return 1;
|
|
}
|
|
|
|
for (uint32_t a = 0; a < size; ++a) {
|
|
// meaningful premultiplied RGB value can't exceed A value, so stop at R = A
|
|
for (uint32_t r = 0; r <= a; ++r) {
|
|
if (rgbDepth == 8) {
|
|
uint8_t * pixel = &rgb.pixels[r * sizeof(uint8_t) * 4];
|
|
pixel[0] = (uint8_t)r;
|
|
pixel[1] = 0;
|
|
pixel[2] = 0;
|
|
pixel[3] = (uint8_t)a;
|
|
} else {
|
|
uint16_t * pixel = (uint16_t *)&rgb.pixels[r * sizeof(uint16_t) * 4];
|
|
pixel[0] = (uint16_t)r;
|
|
pixel[1] = 0;
|
|
pixel[2] = 0;
|
|
pixel[3] = (uint16_t)a;
|
|
}
|
|
}
|
|
|
|
rgb.width = a + 1;
|
|
avifRGBImageUnpremultiplyAlpha(&rgb);
|
|
avifRGBImagePremultiplyAlpha(&rgb);
|
|
|
|
for (uint32_t r = 0; r <= a; ++r) {
|
|
if (rgbDepth == 8) {
|
|
uint8_t * pixel = &rgb.pixels[r * sizeof(uint8_t) * 4];
|
|
int drift = abs((int)pixel[0] - (int)r);
|
|
if (drift >= MAX_DRIFT) {
|
|
fprintf(stderr,
|
|
"ERROR: Premultiply round-trip difference greater than or equal to MAX_DRIFT(%d): RGB depth: %d, src: %d, dst: %d, alpha: %d.\n",
|
|
MAX_DRIFT,
|
|
rgbDepth,
|
|
pixel[0],
|
|
r,
|
|
a);
|
|
return 1;
|
|
}
|
|
if (maxDrift < drift) {
|
|
maxDrift = drift;
|
|
}
|
|
++driftPixelCounts[drift];
|
|
} else {
|
|
uint16_t * pixel = (uint16_t *)&rgb.pixels[r * sizeof(uint16_t) * 4];
|
|
int drift = abs((int)pixel[0] - (int)r);
|
|
if (drift >= MAX_DRIFT) {
|
|
fprintf(stderr,
|
|
"ERROR: Premultiply round-trip difference greater than or equal to MAX_DRIFT(%d): RGB depth: %d, src: %d, dst: %d, alpha: %d.\n",
|
|
MAX_DRIFT,
|
|
rgbDepth,
|
|
pixel[0],
|
|
r,
|
|
a);
|
|
return 1;
|
|
}
|
|
if (maxDrift < drift) {
|
|
maxDrift = drift;
|
|
}
|
|
++driftPixelCounts[drift];
|
|
}
|
|
}
|
|
if (verbose) {
|
|
printf("[%5d/%5d] RGB depth: %d\r", a + 1, size, rgbDepth);
|
|
}
|
|
}
|
|
|
|
if (verbose) {
|
|
printf("\n");
|
|
}
|
|
|
|
printf(" * RGB depth: %d, maxDrift: %2d\n", rgbDepth, maxDrift);
|
|
|
|
avifRGBImageFreePixels(&rgb);
|
|
const uint64_t totalPixelCount = (uint64_t)(size + 1) * size / 2;
|
|
for (int i = 0; i < MAX_DRIFT; ++i) {
|
|
if (verbose && (driftPixelCounts[i] > 0)) {
|
|
printf(" * drift: %2d -> %12" PRIu64 " / %12" PRIu64 " pixels (%.2f %%)\n",
|
|
i,
|
|
driftPixelCounts[i],
|
|
totalPixelCount,
|
|
(double)driftPixelCounts[i] * 100.0 / (double)totalPixelCount);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return 0;
|
|
}
|