Coverage Report

Created: 2026-09-11 06:36

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libavif/apps/shared/avifutil.c
Line
Count
Source
1
// Copyright 2019 Joe Drago. All rights reserved.
2
// SPDX-License-Identifier: BSD-2-Clause
3
4
#include "avifutil.h"
5
6
#include <assert.h>
7
#include <ctype.h>
8
#include <stdio.h>
9
#include <stdlib.h>
10
#include <string.h>
11
12
#include "avifjpeg.h"
13
#include "avifpng.h"
14
#include "y4m.h"
15
16
char * avifFileFormatToString(avifAppFileFormat format)
17
0
{
18
0
    switch (format) {
19
0
        case AVIF_APP_FILE_FORMAT_UNKNOWN:
20
0
            return "unknown";
21
0
        case AVIF_APP_FILE_FORMAT_AVIF:
22
0
            return "AVIF";
23
0
        case AVIF_APP_FILE_FORMAT_JPEG:
24
0
            return "JPEG";
25
0
        case AVIF_APP_FILE_FORMAT_PNG:
26
0
            return "PNG";
27
0
        case AVIF_APP_FILE_FORMAT_Y4M:
28
0
            return "Y4M";
29
0
    }
30
0
    return "unknown";
31
0
}
32
33
// |a| and |b| hold int32_t values. The int64_t type is used so that we can negate INT32_MIN without
34
// overflowing int32_t.
35
static int64_t calcGCD(int64_t a, int64_t b)
36
0
{
37
0
    if (a < 0) {
38
0
        a *= -1;
39
0
    }
40
0
    if (b < 0) {
41
0
        b *= -1;
42
0
    }
43
0
    while (b != 0) {
44
0
        int64_t r = a % b;
45
0
        a = b;
46
0
        b = r;
47
0
    }
48
0
    return a;
49
0
}
50
51
static void printClapFraction(const char * name, int32_t n, int32_t d)
52
0
{
53
0
    printf("%s: %d/%d", name, n, d);
54
0
    if (d != 0) {
55
0
        int64_t gcd = calcGCD(n, d);
56
0
        if (gcd > 1) {
57
0
            int32_t rn = (int32_t)(n / gcd);
58
0
            int32_t rd = (int32_t)(d / gcd);
59
0
            printf(" (%d/%d)", rn, rd);
60
0
        }
61
0
    }
62
0
}
63
64
static void avifImageDumpInternal(const avifImage * avif, uint32_t gridCols, uint32_t gridRows, avifBool alphaPresent, avifProgressiveState progressiveState)
65
0
{
66
0
    uint32_t width = avif->width;
67
0
    uint32_t height = avif->height;
68
0
    if (gridCols && gridRows) {
69
0
        width *= gridCols;
70
0
        height *= gridRows;
71
0
    }
72
0
    printf(" * Resolution     : %ux%u\n", width, height);
73
0
    printf(" * Bit Depth      : %u\n", avif->depth);
74
0
    printf(" * Format         : %s\n", avifPixelFormatToString(avif->yuvFormat));
75
0
    if (avif->yuvFormat == AVIF_PIXEL_FORMAT_YUV420) {
76
0
        printf(" * Chroma Sam. Pos: %u\n", avif->yuvChromaSamplePosition);
77
0
    }
78
0
    printf(" * Alpha          : %s\n", alphaPresent ? (avif->alphaPremultiplied ? "Premultiplied" : "Not premultiplied") : "Absent");
79
0
    printf(" * Range          : %s\n", (avif->yuvRange == AVIF_RANGE_FULL) ? "Full" : "Limited");
80
81
0
    printf(" * Color Primaries: %u\n", avif->colorPrimaries);
82
0
    printf(" * Transfer Char. : %u\n", avif->transferCharacteristics);
83
0
    printf(" * Matrix Coeffs. : %u\n", avif->matrixCoefficients);
84
85
0
    if (avif->icc.size != 0) {
86
0
        printf(" * ICC Profile    : Present (%" AVIF_FMT_ZU " bytes)\n", avif->icc.size);
87
0
    } else {
88
0
        printf(" * ICC Profile    : Absent\n");
89
0
    }
90
0
    if (avif->xmp.size != 0) {
91
0
        printf(" * XMP Metadata   : Present (%" AVIF_FMT_ZU " bytes)\n", avif->xmp.size);
92
0
    } else {
93
0
        printf(" * XMP Metadata   : Absent\n");
94
0
    }
95
0
    if (avif->exif.size != 0) {
96
0
        printf(" * Exif Metadata  : Present (%" AVIF_FMT_ZU " bytes)\n", avif->exif.size);
97
0
    } else {
98
0
        printf(" * Exif Metadata  : Absent\n");
99
0
    }
100
101
0
    if (avif->transformFlags == AVIF_TRANSFORM_NONE) {
102
0
        printf(" * Transformations: None\n");
103
0
    } else {
104
0
        printf(" * Transformations:\n");
105
106
0
        if (avif->transformFlags & AVIF_TRANSFORM_PASP) {
107
0
            printf("    * pasp (Aspect Ratio)  : %d/%d\n", (int)avif->pasp.hSpacing, (int)avif->pasp.vSpacing);
108
0
        }
109
0
        if (avif->transformFlags & AVIF_TRANSFORM_CLAP) {
110
0
            printf("    * clap (Clean Aperture): ");
111
0
            printClapFraction("W", (int32_t)avif->clap.widthN, (int32_t)avif->clap.widthD);
112
0
            printf(", ");
113
0
            printClapFraction("H", (int32_t)avif->clap.heightN, (int32_t)avif->clap.heightD);
114
0
            printf(", ");
115
0
            printClapFraction("hOff", (int32_t)avif->clap.horizOffN, (int32_t)avif->clap.horizOffD);
116
0
            printf(", ");
117
0
            printClapFraction("vOff", (int32_t)avif->clap.vertOffN, (int32_t)avif->clap.vertOffD);
118
0
            printf("\n");
119
120
0
            avifCropRect cropRect;
121
0
            avifDiagnostics diag;
122
0
            avifDiagnosticsClearError(&diag);
123
0
            avifBool validClap = avifCropRectFromCleanApertureBox(&cropRect, &avif->clap, avif->width, avif->height, &diag);
124
0
            if (validClap) {
125
0
                printf("      * Valid, derived crop rect: X: %d, Y: %d, W: %d, H: %d%s\n",
126
0
                       cropRect.x,
127
0
                       cropRect.y,
128
0
                       cropRect.width,
129
0
                       cropRect.height,
130
0
                       avifCropRectRequiresUpsampling(&cropRect, avif->yuvFormat) ? " (upsample before cropping)" : "");
131
0
            } else {
132
0
                printf("      * Invalid: %s\n", diag.error);
133
0
            }
134
0
        }
135
0
        if (avif->transformFlags & AVIF_TRANSFORM_IROT) {
136
0
            printf("    * irot (Rotation)      : %u\n", avif->irot.angle);
137
0
        }
138
0
        if (avif->transformFlags & AVIF_TRANSFORM_IMIR) {
139
0
            printf("    * imir (Mirror)        : %u (%s)\n", avif->imir.axis, (avif->imir.axis == 0) ? "top-to-bottom" : "left-to-right");
140
0
        }
141
0
    }
142
0
    printf(" * Progressive    : %s\n", avifProgressiveStateToString(progressiveState));
143
0
    if (avif->clli.maxCLL > 0 || avif->clli.maxPALL > 0) {
144
0
        printf(" * CLLI           : %hu, %hu\n", avif->clli.maxCLL, avif->clli.maxPALL);
145
0
    }
146
147
0
    printf(" * Gain map       : ");
148
0
    avifImage * gainMapImage = avif->gainMap ? avif->gainMap->image : NULL;
149
0
    if (gainMapImage != NULL) {
150
0
        uint32_t gainMapWidth = gainMapImage->width;
151
0
        uint32_t gainMapHeight = gainMapImage->height;
152
0
        if (gridCols && gridRows) {
153
0
            gainMapWidth *= gridCols;
154
0
            gainMapHeight *= gridRows;
155
0
        }
156
0
        printf("%ux%u pixels, %u bit, %s, %s Range, Matrix Coeffs. %u, Base Headroom %.2f (%s), Alternate Headroom %.2f (%s)\n",
157
0
               gainMapWidth,
158
0
               gainMapHeight,
159
0
               gainMapImage->depth,
160
0
               avifPixelFormatToString(gainMapImage->yuvFormat),
161
0
               (gainMapImage->yuvRange == AVIF_RANGE_FULL) ? "Full" : "Limited",
162
0
               gainMapImage->matrixCoefficients,
163
0
               avif->gainMap->baseHdrHeadroom.d == 0 ? 0
164
0
                                                     : (double)avif->gainMap->baseHdrHeadroom.n / avif->gainMap->baseHdrHeadroom.d,
165
0
               (avif->gainMap->baseHdrHeadroom.n == 0) ? "SDR" : "HDR",
166
0
               avif->gainMap->alternateHdrHeadroom.d == 0
167
0
                   ? 0
168
0
                   : (double)avif->gainMap->alternateHdrHeadroom.n / avif->gainMap->alternateHdrHeadroom.d,
169
0
               (avif->gainMap->alternateHdrHeadroom.n == 0) ? "SDR" : "HDR");
170
0
        printf(" * Alternate image:\n");
171
0
        printf("    * Color Primaries: %u\n", avif->gainMap->altColorPrimaries);
172
0
        printf("    * Transfer Char. : %u\n", avif->gainMap->altTransferCharacteristics);
173
0
        printf("    * Matrix Coeffs. : %u\n", avif->gainMap->altMatrixCoefficients);
174
0
        if (avif->gainMap->altICC.size != 0) {
175
0
            printf("    * ICC Profile    : Present (%" AVIF_FMT_ZU " bytes)\n", avif->gainMap->altICC.size);
176
0
        } else {
177
0
            printf("    * ICC Profile    : Absent\n");
178
0
        }
179
0
        if (avif->gainMap->altDepth) {
180
0
            printf("    * Bit Depth      : %u\n", avif->gainMap->altDepth);
181
0
        }
182
0
        if (avif->gainMap->altPlaneCount) {
183
0
            printf("    * Planes         : %u\n", avif->gainMap->altPlaneCount);
184
0
        }
185
0
        if (avif->gainMap->altCLLI.maxCLL > 0 || avif->gainMap->altCLLI.maxPALL > 0) {
186
0
            printf("    * CLLI           : %hu, %hu\n", avif->gainMap->altCLLI.maxCLL, avif->gainMap->altCLLI.maxPALL);
187
0
        }
188
0
        printf("\n");
189
0
    } else if (avif->gainMap != NULL) {
190
0
        printf("Present (but ignored)\n");
191
0
    } else {
192
0
        printf("Absent\n");
193
0
    }
194
0
}
195
196
void avifImageDump(const avifImage * avif, uint32_t gridCols, uint32_t gridRows, avifProgressiveState progressiveState)
197
0
{
198
0
    const avifBool alphaPresent = avif->alphaPlane && (avif->alphaRowBytes > 0);
199
0
    avifImageDumpInternal(avif, gridCols, gridRows, alphaPresent, progressiveState);
200
0
}
201
202
void avifContainerDump(const avifDecoder * decoder)
203
0
{
204
0
    avifImageDumpInternal(decoder->image, 0, 0, decoder->alphaPresent, decoder->progressiveState);
205
0
    if (decoder->imageSequenceTrackPresent) {
206
0
        if (decoder->repetitionCount == AVIF_REPETITION_COUNT_INFINITE) {
207
0
            printf(" * Repeat Count   : Infinite\n");
208
0
        } else if (decoder->repetitionCount == AVIF_REPETITION_COUNT_UNKNOWN) {
209
0
            printf(" * Repeat Count   : Unknown\n");
210
0
        } else {
211
0
            printf(" * Repeat Count   : %d\n", decoder->repetitionCount);
212
0
        }
213
0
    }
214
0
}
215
216
void avifPrintVersions(void)
217
0
{
218
0
    char codecVersions[256];
219
0
    avifCodecVersions(codecVersions);
220
0
    printf("Version: %s (%s)\n", avifVersion(), codecVersions);
221
222
0
    unsigned int libyuvVersion = avifLibYUVVersion();
223
0
    if (libyuvVersion == 0) {
224
0
        printf("libyuv : unavailable\n");
225
0
    } else {
226
0
        printf("libyuv : available (%u)\n", libyuvVersion);
227
0
    }
228
229
0
    printf("\n");
230
0
}
231
232
avifAppFileFormat avifGuessFileFormat(const char * filename)
233
0
{
234
    // Guess from the file header
235
0
    FILE * f = fopen(filename, "rb");
236
0
    if (f) {
237
0
        uint8_t headerBuffer[144];
238
0
        size_t bytesRead = fread(headerBuffer, 1, sizeof(headerBuffer), f);
239
0
        fclose(f);
240
241
0
        if (bytesRead > 0) {
242
            // If the file could be read, use the first bytes to guess the file format.
243
0
            return avifGuessBufferFileFormat(headerBuffer, bytesRead);
244
0
        }
245
0
    }
246
247
    // If we get here, the file header couldn't be read for some reason. Guess from the extension.
248
249
0
    const char * fileExt = strrchr(filename, '.');
250
0
    if (!fileExt) {
251
0
        return AVIF_APP_FILE_FORMAT_UNKNOWN;
252
0
    }
253
0
    ++fileExt; // skip past the dot
254
255
0
    char lowercaseExt[8]; // This only needs to fit up to "jpeg", so this is plenty
256
0
    const size_t fileExtLen = strlen(fileExt);
257
0
    if (fileExtLen >= sizeof(lowercaseExt)) { // >= accounts for NULL terminator
258
0
        return AVIF_APP_FILE_FORMAT_UNKNOWN;
259
0
    }
260
261
0
    for (size_t i = 0; i < fileExtLen; ++i) {
262
0
        lowercaseExt[i] = (char)tolower((unsigned char)fileExt[i]);
263
0
    }
264
0
    lowercaseExt[fileExtLen] = 0;
265
266
0
    if (!strcmp(lowercaseExt, "avif")) {
267
0
        return AVIF_APP_FILE_FORMAT_AVIF;
268
0
    } else if (!strcmp(lowercaseExt, "y4m")) {
269
0
        return AVIF_APP_FILE_FORMAT_Y4M;
270
0
    } else if (!strcmp(lowercaseExt, "jpg") || !strcmp(lowercaseExt, "jpeg")) {
271
0
        return AVIF_APP_FILE_FORMAT_JPEG;
272
0
    } else if (!strcmp(lowercaseExt, "png")) {
273
0
        return AVIF_APP_FILE_FORMAT_PNG;
274
0
    }
275
0
    return AVIF_APP_FILE_FORMAT_UNKNOWN;
276
0
}
277
278
avifAppFileFormat avifGuessBufferFileFormat(const uint8_t * data, size_t size)
279
1.13k
{
280
1.13k
    if (size == 0) {
281
0
        return AVIF_APP_FILE_FORMAT_UNKNOWN;
282
0
    }
283
284
1.13k
    avifROData header;
285
1.13k
    header.data = data;
286
1.13k
    header.size = size;
287
288
1.13k
    if (avifPeekCompatibleFileType(&header)) {
289
759
        return AVIF_APP_FILE_FORMAT_AVIF;
290
759
    }
291
292
374
    static const uint8_t signatureJPEG[2] = { 0xFF, 0xD8 };
293
374
    static const uint8_t signaturePNG[8] = { 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A };
294
374
    static const uint8_t signatureY4M[9] = { 0x59, 0x55, 0x56, 0x34, 0x4D, 0x50, 0x45, 0x47, 0x32 }; // "YUV4MPEG2"
295
374
    struct avifHeaderSignature
296
374
    {
297
374
        avifAppFileFormat format;
298
374
        const uint8_t * magic;
299
374
        size_t magicSize;
300
374
    } signatures[] = { { AVIF_APP_FILE_FORMAT_JPEG, signatureJPEG, sizeof(signatureJPEG) },
301
374
                       { AVIF_APP_FILE_FORMAT_PNG, signaturePNG, sizeof(signaturePNG) },
302
374
                       { AVIF_APP_FILE_FORMAT_Y4M, signatureY4M, sizeof(signatureY4M) } };
303
374
    const size_t signaturesCount = sizeof(signatures) / sizeof(signatures[0]);
304
305
693
    for (size_t signatureIndex = 0; signatureIndex < signaturesCount; ++signatureIndex) {
306
671
        const struct avifHeaderSignature * const signature = &signatures[signatureIndex];
307
671
        if (header.size < signature->magicSize) {
308
0
            continue;
309
0
        }
310
671
        if (!memcmp(header.data, signature->magic, signature->magicSize)) {
311
352
            return signature->format;
312
352
        }
313
671
    }
314
315
22
    return AVIF_APP_FILE_FORMAT_UNKNOWN;
316
374
}
317
318
avifAppFileFormat avifReadImage(const char * filename,
319
                                avifAppFileFormat inputFormat,
320
                                avifPixelFormat requestedFormat,
321
                                int requestedDepth,
322
                                avifChromaDownsampling chromaDownsampling,
323
                                avifBool ignoreColorProfile,
324
                                avifBool ignoreExif,
325
                                avifBool ignoreXMP,
326
                                avifBool ignoreAlpha,
327
                                avifBool ignoreGainMap,
328
                                uint32_t imageSizeLimit,
329
                                avifImage * image,
330
                                uint32_t * outDepth,
331
                                avifAppSourceTiming * sourceTiming,
332
                                struct y4mFrameIterator ** frameIter)
333
0
{
334
0
    if (inputFormat == AVIF_APP_FILE_FORMAT_UNKNOWN) {
335
0
        inputFormat = avifGuessFileFormat(filename);
336
0
    }
337
338
0
    if (inputFormat == AVIF_APP_FILE_FORMAT_Y4M) {
339
0
        if (!y4mRead(filename, ignoreAlpha, imageSizeLimit, image, sourceTiming, frameIter)) {
340
0
            return AVIF_APP_FILE_FORMAT_UNKNOWN;
341
0
        }
342
0
        if (outDepth) {
343
0
            *outDepth = image->depth;
344
0
        }
345
0
    } else if (inputFormat == AVIF_APP_FILE_FORMAT_JPEG) {
346
        // imageSizeLimit is also used to limit Exif and XMP metadata here.
347
0
        if (!avifJPEGRead(filename, image, requestedFormat, requestedDepth, chromaDownsampling, ignoreColorProfile, ignoreExif, ignoreXMP, ignoreGainMap, imageSizeLimit)) {
348
0
            return AVIF_APP_FILE_FORMAT_UNKNOWN;
349
0
        }
350
0
        if (outDepth) {
351
0
            *outDepth = 8;
352
0
        }
353
0
    } else if (inputFormat == AVIF_APP_FILE_FORMAT_PNG) {
354
0
        if (!avifPNGRead(filename, image, requestedFormat, requestedDepth, chromaDownsampling, ignoreColorProfile, ignoreExif, ignoreXMP, ignoreAlpha, imageSizeLimit, outDepth)) {
355
0
            return AVIF_APP_FILE_FORMAT_UNKNOWN;
356
0
        }
357
0
    } else if (inputFormat == AVIF_APP_FILE_FORMAT_UNKNOWN) {
358
0
        fprintf(stderr, "Unrecognized file format for input file: %s\n", filename);
359
0
        return AVIF_APP_FILE_FORMAT_UNKNOWN;
360
0
    } else {
361
0
        fprintf(stderr, "Unsupported file format %s for input file: %s\n", avifFileFormatToString(inputFormat), filename);
362
0
        return AVIF_APP_FILE_FORMAT_UNKNOWN;
363
0
    }
364
0
    return inputFormat;
365
0
}
366
367
avifBool avifReadEntireFile(const char * filename, avifRWData * raw)
368
0
{
369
0
    FILE * f = fopen(filename, "rb");
370
0
    if (!f) {
371
0
        return AVIF_FALSE;
372
0
    }
373
374
0
    fseek(f, 0, SEEK_END);
375
0
    long pos = ftell(f);
376
0
    if (pos <= 0) {
377
0
        fclose(f);
378
0
        return AVIF_FALSE;
379
0
    }
380
0
    size_t fileSize = (size_t)pos;
381
0
    fseek(f, 0, SEEK_SET);
382
383
0
    if (avifRWDataRealloc(raw, fileSize) != AVIF_RESULT_OK) {
384
0
        fclose(f);
385
0
        return AVIF_FALSE;
386
0
    }
387
0
    size_t bytesRead = fread(raw->data, 1, fileSize, f);
388
0
    fclose(f);
389
390
0
    if (bytesRead != fileSize) {
391
0
        avifRWDataFree(raw);
392
0
        return AVIF_FALSE;
393
0
    }
394
0
    return AVIF_TRUE;
395
0
}
396
397
void avifImageFixXMP(avifImage * image)
398
0
{
399
    // Zero bytes are forbidden in UTF-8 XML: https://en.wikipedia.org/wiki/Valid_characters_in_XML
400
    // Keeping zero bytes in XMP may lead to issues at encoding or decoding.
401
    // For example, the PNG specification forbids null characters in XMP. See avifPNGWrite().
402
    // The XMP Specification Part 3 says "When XMP is encoded as UTF-8,
403
    // there are no zero bytes in the XMP packet" for GIF.
404
405
    // Consider a single trailing null character following a non-null character
406
    // as a programming error. Leave other null characters as is.
407
    // See the discussion at https://github.com/AOMediaCodec/libavif/issues/1333.
408
0
    if (image->xmp.size >= 2 && image->xmp.data[image->xmp.size - 1] == '\0' && image->xmp.data[image->xmp.size - 2] != '\0') {
409
0
        --image->xmp.size;
410
0
    }
411
0
}
412
413
void avifDumpDiagnostics(const avifDiagnostics * diag)
414
0
{
415
0
    if (!*diag->error) {
416
0
        return;
417
0
    }
418
419
0
    printf("Diagnostics:\n");
420
0
    printf(" * %s\n", diag->error);
421
0
}
422
423
// ---------------------------------------------------------------------------
424
// avifQueryCPUCount (separated into OS implementations)
425
426
#if defined(_WIN32)
427
428
// Windows
429
430
#include <windows.h>
431
432
int avifQueryCPUCount(void)
433
{
434
    int numCPU;
435
    SYSTEM_INFO sysinfo;
436
    GetSystemInfo(&sysinfo);
437
    numCPU = sysinfo.dwNumberOfProcessors;
438
    return numCPU;
439
}
440
441
#elif defined(__APPLE__)
442
443
// Apple
444
445
#include <sys/sysctl.h>
446
447
int avifQueryCPUCount(void)
448
{
449
    int mib[4];
450
    int numCPU;
451
    size_t len = sizeof(numCPU);
452
453
    /* set the mib for hw.ncpu */
454
    mib[0] = CTL_HW;
455
    mib[1] = HW_AVAILCPU; // alternatively, try HW_NCPU;
456
457
    /* get the number of CPUs from the system */
458
    sysctl(mib, 2, &numCPU, &len, NULL, 0);
459
460
    if (numCPU < 1) {
461
        mib[1] = HW_NCPU;
462
        sysctl(mib, 2, &numCPU, &len, NULL, 0);
463
        if (numCPU < 1)
464
            numCPU = 1;
465
    }
466
    return numCPU;
467
}
468
469
#elif defined(__EMSCRIPTEN__)
470
471
// Emscripten
472
473
int avifQueryCPUCount(void)
474
{
475
    return 1;
476
}
477
478
#else
479
480
// POSIX
481
482
#include <unistd.h>
483
484
int avifQueryCPUCount(void)
485
0
{
486
0
    int numCPU = (int)sysconf(_SC_NPROCESSORS_ONLN);
487
0
    return (numCPU > 0) ? numCPU : 1;
488
0
}
489
490
#endif
491
492
// Returns the best cell size for a given horizontal or vertical dimension.
493
avifBool avifGetBestCellSize(const char * dimensionStr, uint32_t numPixels, uint32_t numCells, avifBool isSubsampled, uint32_t * cellSize)
494
0
{
495
0
    assert(numPixels);
496
0
    assert(numCells);
497
498
    // ISO/IEC 23008-12:2017, Section 6.6.2.3.1:
499
    //   The reconstructed image is formed by tiling the input images into a grid with a column width
500
    //   (potentially excluding the right-most column) equal to tile_width and a row height (potentially
501
    //   excluding the bottom-most row) equal to tile_height, without gap or overlap, and then
502
    //   trimming on the right and the bottom to the indicated output_width and output_height.
503
    // The priority could be to use a cell size that is a multiple of 64, but there is not always a valid one,
504
    // even though it is recommended by MIAF. Just use ceil(numPixels/numCells) for simplicity and to avoid
505
    // as much padding in the right-most and bottom-most cells as possible.
506
    // Use uint64_t computation to avoid a potential uint32_t overflow.
507
0
    *cellSize = (uint32_t)(((uint64_t)numPixels + numCells - 1) / numCells);
508
509
    // ISO/IEC 23000-22:2019, Section 7.3.11.4.2:
510
    //   - the tile_width shall be greater than or equal to 64, and should be a multiple of 64
511
    //   - the tile_height shall be greater than or equal to 64, and should be a multiple of 64
512
0
    if (*cellSize < 64) {
513
0
        *cellSize = 64;
514
0
        if ((uint64_t)(numCells - 1) * *cellSize >= (uint64_t)numPixels) {
515
            // Some cells would be entirely off-canvas.
516
0
            fprintf(stderr, "ERROR: There are too many cells %s (%u) to have at least 64 pixels per cell.\n", dimensionStr, numCells);
517
0
            return AVIF_FALSE;
518
0
        }
519
0
    }
520
521
    // The maximum AV1 frame size is 65536 pixels inclusive.
522
0
    if (*cellSize > 65536) {
523
0
        fprintf(stderr, "ERROR: Cell size %u is bigger %s than the maximum frame size 65536.\n", *cellSize, dimensionStr);
524
0
        return AVIF_FALSE;
525
0
    }
526
527
    // ISO/IEC 23000-22:2019, Section 7.3.11.4.2:
528
    //   - when the images are in the 4:2:2 chroma sampling format the horizontal tile offsets and widths,
529
    //     and the output width, shall be even numbers;
530
    //   - when the images are in the 4:2:0 chroma sampling format both the horizontal and vertical tile
531
    //     offsets and widths, and the output width and height, shall be even numbers.
532
0
    if (isSubsampled && (*cellSize & 1)) {
533
0
        ++*cellSize;
534
0
        if ((uint64_t)(numCells - 1) * *cellSize >= (uint64_t)numPixels) {
535
            // Some cells would be entirely off-canvas.
536
0
            fprintf(stderr, "ERROR: Odd cell size %u is forbidden on a %s subsampled image.\n", *cellSize - 1, dimensionStr);
537
0
            return AVIF_FALSE;
538
0
        }
539
0
    }
540
541
    // Each pixel is covered by exactly one cell, and each cell contains at least one pixel.
542
0
    assert(((uint64_t)(numCells - 1) * *cellSize < (uint64_t)numPixels) && ((uint64_t)numCells * *cellSize >= (uint64_t)numPixels));
543
0
    return AVIF_TRUE;
544
0
}
545
546
avifBool avifImageSplitGrid(const avifImage * gridSplitImage, uint32_t gridCols, uint32_t gridRows, avifImage ** gridCells)
547
0
{
548
0
    avifBool success = AVIF_FALSE;
549
0
    uint32_t cellWidth, cellHeight;
550
0
    avifPixelFormatInfo formatInfo;
551
0
    avifGetPixelFormatInfo(gridSplitImage->yuvFormat, &formatInfo);
552
0
    const avifBool isSubsampledX = !formatInfo.monochrome && formatInfo.chromaShiftX;
553
0
    const avifBool isSubsampledY = !formatInfo.monochrome && formatInfo.chromaShiftY;
554
0
    if (!avifGetBestCellSize("horizontally", gridSplitImage->width, gridCols, isSubsampledX, &cellWidth) ||
555
0
        !avifGetBestCellSize("vertically", gridSplitImage->height, gridRows, isSubsampledY, &cellHeight)) {
556
0
        return AVIF_FALSE;
557
0
    }
558
0
    const avifBool hasGainMap = gridSplitImage->gainMap && gridSplitImage->gainMap->image;
559
560
0
    uint32_t createdCells = 0;
561
0
    for (uint32_t gridY = 0; gridY < gridRows; ++gridY) {
562
0
        for (uint32_t gridX = 0; gridX < gridCols; ++gridX) {
563
0
            uint32_t gridIndex = gridX + (gridY * gridCols);
564
0
            avifImage * cellImage = avifImageCreateEmpty();
565
0
            if (!cellImage) {
566
0
                fprintf(stderr, "ERROR: Cell creation failed: out of memory\n");
567
0
                goto cleanup;
568
0
            }
569
0
            gridCells[gridIndex] = cellImage;
570
0
            assert(gridIndex == createdCells);
571
0
            createdCells++;
572
573
0
            avifCropRect cellRect = { gridX * cellWidth, gridY * cellHeight, cellWidth, cellHeight };
574
0
            if (cellRect.x + cellRect.width > gridSplitImage->width) {
575
0
                cellRect.width = gridSplitImage->width - cellRect.x;
576
0
            }
577
0
            if (cellRect.y + cellRect.height > gridSplitImage->height) {
578
0
                cellRect.height = gridSplitImage->height - cellRect.y;
579
0
            }
580
0
            const avifResult copyResult = avifImageSetViewRect(cellImage, gridSplitImage, &cellRect);
581
0
            if (copyResult != AVIF_RESULT_OK) {
582
0
                fprintf(stderr, "ERROR: Cell creation failed: %s\n", avifResultToString(copyResult));
583
0
                goto cleanup;
584
0
            }
585
586
0
            if (hasGainMap) {
587
0
                cellImage->gainMap = avifGainMapCreate();
588
0
                if (!cellImage->gainMap) {
589
0
                    fprintf(stderr, "ERROR: Gain map creation failed: out of memory\n");
590
0
                    goto cleanup;
591
0
                }
592
                // Copy gain map metadata.
593
0
                memcpy(cellImage->gainMap, gridSplitImage->gainMap, sizeof(avifGainMap));
594
0
                cellImage->gainMap->altICC.data = NULL; // Copied later in this function.
595
0
                cellImage->gainMap->altICC.size = 0;
596
0
                cellImage->gainMap->image = NULL; // Set later in this function.
597
0
            }
598
0
        }
599
0
    }
600
601
0
    if (hasGainMap) {
602
0
        avifImage ** gainMapGridCells = NULL;
603
0
        gainMapGridCells = (avifImage **)calloc(gridCols * gridRows, sizeof(avifImage *));
604
0
        if (!gainMapGridCells) {
605
0
            fprintf(stderr, "ERROR: Memory allocation failed for gain map grid cells\n");
606
0
            goto cleanup;
607
0
        }
608
0
        if (!avifImageSplitGrid(gridSplitImage->gainMap->image, gridCols, gridRows, gainMapGridCells)) {
609
0
            free(gainMapGridCells);
610
0
            goto cleanup;
611
0
        }
612
613
0
        for (uint32_t gridIndex = 0; gridIndex < gridCols * gridRows; ++gridIndex) {
614
            // Ownership of the gain map cell is transferred.
615
0
            gridCells[gridIndex]->gainMap->image = gainMapGridCells[gridIndex];
616
0
        }
617
0
        free(gainMapGridCells);
618
0
    }
619
620
    // Copy over metadata blobs to the first cell since avifImageSetViewRect() does not copy any
621
    // properties that require an allocation.
622
0
    avifImage * firstCell = gridCells[0];
623
0
    if (gridSplitImage->icc.size > 0) {
624
0
        const avifResult result = avifImageSetProfileICC(firstCell, gridSplitImage->icc.data, gridSplitImage->icc.size);
625
0
        if (result != AVIF_RESULT_OK) {
626
0
            fprintf(stderr, "ERROR: Failed to set ICC profile on grid cell: %s\n", avifResultToString(result));
627
0
            goto cleanup;
628
0
        }
629
0
    }
630
0
    if (gridSplitImage->exif.size > 0) {
631
0
        const avifResult result = avifRWDataSet(&firstCell->exif, gridSplitImage->exif.data, gridSplitImage->exif.size);
632
0
        if (result != AVIF_RESULT_OK) {
633
0
            fprintf(stderr, "ERROR: Failed to set Exif metadata on grid cell: %s\n", avifResultToString(result));
634
0
            goto cleanup;
635
0
        }
636
0
    }
637
0
    if (gridSplitImage->xmp.size > 0) {
638
0
        const avifResult result = avifImageSetMetadataXMP(firstCell, gridSplitImage->xmp.data, gridSplitImage->xmp.size);
639
0
        if (result != AVIF_RESULT_OK) {
640
0
            fprintf(stderr, "ERROR: Failed to set XMP metadata on grid cell: %s\n", avifResultToString(result));
641
0
            goto cleanup;
642
0
        }
643
0
    }
644
0
    if (gridSplitImage->gainMap && gridSplitImage->gainMap->image && gridSplitImage->gainMap->altICC.size > 0) {
645
0
        for (uint32_t i = 0; i < gridCols * gridRows; ++i) {
646
0
            avifImage * cellImage = gridCells[i];
647
0
            const avifResult result =
648
0
                avifRWDataSet(&cellImage->gainMap->altICC, gridSplitImage->gainMap->altICC.data, gridSplitImage->gainMap->altICC.size);
649
0
            if (result != AVIF_RESULT_OK) {
650
0
                fprintf(stderr, "ERROR: Failed to set ICC profile on gain map grid cell: %s\n", avifResultToString(result));
651
0
                goto cleanup;
652
0
            }
653
0
        }
654
0
    }
655
656
0
    success = AVIF_TRUE;
657
658
0
cleanup:
659
0
    if (!success) {
660
0
        for (uint32_t i = 0; i < createdCells; ++i) {
661
0
            avifImageDestroy(gridCells[i]);
662
0
            gridCells[i] = NULL;
663
0
        }
664
0
    }
665
0
    return success;
666
0
}
667
668
void avifRGBImageSetViewRect(avifRGBImage * dstImage, const avifRGBImage * srcImage, const avifCropRect * cropRect)
669
0
{
670
0
    memset(dstImage, 0, sizeof(avifRGBImage));
671
0
    dstImage->width = cropRect->width;
672
0
    dstImage->height = cropRect->height;
673
0
    dstImage->depth = srcImage->depth;
674
0
    dstImage->format = srcImage->format;
675
0
    dstImage->alphaPremultiplied = srcImage->alphaPremultiplied;
676
0
    dstImage->isFloat = srcImage->isFloat;
677
0
    const uint32_t bytesPerPixel = avifRGBImagePixelSize(srcImage);
678
    // This should not overflow if cropRect is a valid crop of the image.
679
0
    const size_t offset = (size_t)cropRect->y * srcImage->rowBytes + (size_t)cropRect->x * bytesPerPixel;
680
0
    dstImage->pixels = srcImage->pixels + offset;
681
0
    dstImage->rowBytes = srcImage->rowBytes;
682
0
}
683
684
// NOTE: this saves the rotated pixels to a different image. Rotating an image in place is possible, but can be non trivial depending on the angle.
685
// A 90° rotation can be implemented as a transposition operation followed by mirroring.
686
// It's the transposition step that is non trivial for non-square images, see https://en.wikipedia.org/wiki/In-place_matrix_transposition
687
avifResult avifRGBImageRotate(avifRGBImage * dstImage, const avifRGBImage * srcImage, const avifImageRotation * rotation)
688
0
{
689
0
    const uint32_t bytesPerPixel = avifRGBImagePixelSize(srcImage);
690
0
    const uint8_t angle = rotation->angle;
691
0
    const uint32_t newWidth = (angle == 0 || angle == 2) ? srcImage->width : srcImage->height;
692
0
    const uint32_t newHeight = (angle == 0 || angle == 2) ? srcImage->height : srcImage->width;
693
0
    *dstImage = *srcImage;
694
0
    dstImage->width = newWidth;
695
0
    dstImage->height = newHeight;
696
0
    dstImage->pixels = NULL;
697
0
    avifResult result = avifRGBImageAllocatePixels(dstImage);
698
0
    if (result != AVIF_RESULT_OK) {
699
0
        return result;
700
0
    }
701
702
0
    if (rotation->angle == 0) {
703
0
        const size_t bytesPerRow = (size_t)bytesPerPixel * srcImage->width;
704
        // 0 degrees. Just copy the rows as is.
705
0
        for (uint32_t j = 0; j < srcImage->height; ++j) {
706
0
            memcpy(dstImage->pixels + ((size_t)j * dstImage->rowBytes), srcImage->pixels + ((size_t)j * srcImage->rowBytes), bytesPerRow);
707
0
        }
708
0
    } else if (rotation->angle == 1) {
709
        // 90 degrees anti-clockwise.
710
0
        for (uint32_t j = 0; j < srcImage->height; ++j) {
711
0
            for (uint32_t i = 0; i < srcImage->width; ++i) {
712
                // Source pixel at (i, j) goes to destination pixel at (j, srcImage->width - 1 - i).
713
0
                memcpy(dstImage->pixels + ((size_t)(srcImage->width - 1 - i) * dstImage->rowBytes) + ((size_t)j * bytesPerPixel),
714
0
                       srcImage->pixels + ((size_t)j * srcImage->rowBytes) + ((size_t)i * bytesPerPixel),
715
0
                       bytesPerPixel);
716
0
            }
717
0
        }
718
0
    } else if (rotation->angle == 2) {
719
        // 180 degrees.
720
0
        for (uint32_t j = 0; j < srcImage->height; ++j) {
721
0
            for (uint32_t i = 0; i < srcImage->width; ++i) {
722
                // Source pixel at (i, j) goes to destination pixel at (srcImage->width - 1 - i, srcImage->height - 1 - j).
723
0
                memcpy(dstImage->pixels + ((size_t)(srcImage->height - 1 - j) * dstImage->rowBytes) +
724
0
                           ((size_t)(srcImage->width - 1 - i) * bytesPerPixel),
725
0
                       srcImage->pixels + ((size_t)j * srcImage->rowBytes) + ((size_t)i * bytesPerPixel),
726
0
                       bytesPerPixel);
727
0
            }
728
0
        }
729
0
    } else if (rotation->angle == 3) {
730
        // 90 degrees clockwise.
731
0
        for (uint32_t j = 0; j < srcImage->height; ++j) {
732
0
            for (uint32_t i = 0; i < srcImage->width; ++i) {
733
                // Source pixel at (i, j) goes to destination pixel at (srcImage->width - 1 - i, j).
734
0
                memcpy(dstImage->pixels + ((size_t)i * dstImage->rowBytes) + ((size_t)(srcImage->height - 1 - j) * bytesPerPixel),
735
0
                       srcImage->pixels + ((size_t)j * srcImage->rowBytes) + ((size_t)i * bytesPerPixel),
736
0
                       bytesPerPixel);
737
0
            }
738
0
        }
739
0
    } else {
740
0
        return AVIF_RESULT_INVALID_ARGUMENT; // Invalid angle.
741
0
    }
742
0
    return AVIF_RESULT_OK;
743
0
}
744
745
avifResult avifRGBImageMirror(avifRGBImage * image, const avifImageMirror * mirror)
746
0
{
747
0
    if (mirror->axis == 0) { // Horizontal axis.
748
0
        const uint32_t bytesPerPixel = avifRGBImagePixelSize(image);
749
        // May be less than image->rowBytes e.g. if image is a cropped view.
750
0
        const size_t bytesPerRowToMove = (size_t)bytesPerPixel * image->width;
751
        // Top-to-bottom
752
0
        uint8_t * tempRow = (uint8_t *)avifAlloc(bytesPerRowToMove);
753
0
        if (!tempRow) {
754
0
            return AVIF_RESULT_OUT_OF_MEMORY;
755
0
        }
756
0
        for (uint32_t y = 0; y < image->height / 2; ++y) {
757
0
            uint8_t * row1 = &image->pixels[(size_t)y * image->rowBytes];
758
0
            uint8_t * row2 = &image->pixels[(size_t)(image->height - 1 - y) * image->rowBytes];
759
0
            memcpy(tempRow, row1, bytesPerRowToMove);
760
0
            memcpy(row1, row2, bytesPerRowToMove);
761
0
            memcpy(row2, tempRow, bytesPerRowToMove);
762
0
        }
763
0
        avifFree(tempRow);
764
0
    } else if (mirror->axis == 1) { // Vertical axis.
765
0
        const uint32_t bytesPerPixel = avifRGBImagePixelSize(image);
766
0
        uint8_t tempPixel[8]; // Max pixel size should be 8 bytes (RGBA 16-bit).
767
0
        if (bytesPerPixel > sizeof(tempPixel)) {
768
0
            return AVIF_RESULT_INVALID_ARGUMENT;
769
0
        }
770
0
        for (uint32_t y = 0; y < image->height; ++y) {
771
0
            uint8_t * row = &image->pixels[(size_t)y * image->rowBytes];
772
0
            for (uint32_t x = 0; x < image->width / 2; ++x) {
773
0
                uint8_t * pixel1 = &row[(size_t)x * bytesPerPixel];
774
0
                uint8_t * pixel2 = &row[(size_t)(image->width - 1 - x) * bytesPerPixel];
775
0
                memcpy(tempPixel, pixel1, bytesPerPixel);
776
0
                memcpy(pixel1, pixel2, bytesPerPixel);
777
0
                memcpy(pixel2, tempPixel, bytesPerPixel);
778
0
            }
779
0
        }
780
0
    } else {
781
0
        return AVIF_RESULT_INVALID_ARGUMENT; // Invalid axis value.
782
0
    }
783
784
0
    return AVIF_RESULT_OK;
785
0
}
786
787
avifResult avifApplyTransforms(avifRGBImage * dstView, avifRGBImage * srcImage, const avifImage * avif)
788
0
{
789
    // ISO/IEC 23000-22 (MIAF), Section 7.3.6.7:
790
    //  These properties, if used, shall be indicated to be applied in the following order:
791
    //  clean aperture first, then rotation, then mirror.
792
0
    *dstView = *srcImage;
793
0
    if (avif->transformFlags & AVIF_TRANSFORM_CLAP) {
794
0
        avifCropRect cropRect;
795
0
        avifDiagnostics diag;
796
0
        if (avifCropRectFromCleanApertureBox(&cropRect, &avif->clap, avif->width, avif->height, &diag) &&
797
0
            (cropRect.x != 0 || cropRect.y != 0 || cropRect.width != avif->width || cropRect.height != avif->height)) {
798
0
            avifRGBImageSetViewRect(dstView, srcImage, &cropRect);
799
0
        } else {
800
0
            fprintf(stderr, "Invalid clean aperture box\n");
801
0
            return AVIF_RESULT_INVALID_ARGUMENT;
802
0
        }
803
0
    }
804
0
    if (avif->transformFlags & AVIF_TRANSFORM_IROT && avif->irot.angle != 0) {
805
0
        avifRGBImage tmpRgbImage;
806
0
        avifResult result = avifRGBImageRotate(&tmpRgbImage, dstView, &avif->irot);
807
0
        if (result != AVIF_RESULT_OK) {
808
0
            fprintf(stderr, "Failed to apply rotation\n");
809
0
            avifRGBImageFreePixels(&tmpRgbImage);
810
0
            return result;
811
0
        }
812
        // We assume that srcImage owned its pixels and free them before replacing it with tmpRgbImage.
813
0
        avifRGBImageFreePixels(srcImage);
814
0
        *srcImage = tmpRgbImage;
815
0
        *dstView = *srcImage;
816
0
    }
817
0
    if (avif->transformFlags & AVIF_TRANSFORM_IMIR) {
818
0
        avifResult result = avifRGBImageMirror(dstView, &avif->imir);
819
0
        if (result != AVIF_RESULT_OK) {
820
0
            fprintf(stderr, "Failed to apply mirror\n");
821
0
            return result;
822
0
        }
823
0
    }
824
0
    return AVIF_RESULT_OK;
825
0
}
826
827
avifResult avifImageCreateView(avifImage * dstImage, const avifImage * srcImage)
828
0
{
829
0
    avifResult res = AVIF_RESULT_OK;
830
0
    if (!dstImage || !srcImage) {
831
0
        return AVIF_RESULT_INVALID_ARGUMENT;
832
0
    }
833
834
0
    avifCropRect rect = { 0, 0, srcImage->width, srcImage->height };
835
0
    res = avifImageSetViewRect(dstImage, srcImage, &rect);
836
0
    if (res != AVIF_RESULT_OK) {
837
0
        return res;
838
0
    }
839
840
0
    if (srcImage->icc.size > 0) {
841
0
        res = avifImageSetProfileICC(dstImage, srcImage->icc.data, srcImage->icc.size);
842
0
        if (res != AVIF_RESULT_OK) {
843
0
            return res;
844
0
        }
845
0
    }
846
847
    // Using avifRWDataSet directly to match the internal avifImageCopy() behavior.
848
    // This avoids re-extracting Exif orientation into irot/imir on an already decoded image.
849
0
    res = avifRWDataSet(&dstImage->exif, srcImage->exif.data, srcImage->exif.size);
850
0
    if (res != AVIF_RESULT_OK) {
851
0
        return res;
852
0
    }
853
0
    res = avifImageSetMetadataXMP(dstImage, srcImage->xmp.data, srcImage->xmp.size);
854
0
    if (res != AVIF_RESULT_OK) {
855
0
        return res;
856
0
    }
857
858
0
    for (size_t i = 0; i < srcImage->numProperties; ++i) {
859
0
        if (memcmp(srcImage->properties[i].boxtype, "uuid", 4) == 0) {
860
0
            res = avifImageAddUUIDProperty(dstImage,
861
0
                                           srcImage->properties[i].usertype,
862
0
                                           srcImage->properties[i].boxPayload.data,
863
0
                                           srcImage->properties[i].boxPayload.size);
864
0
        } else {
865
0
            res = avifImageAddOpaqueProperty(dstImage,
866
0
                                             srcImage->properties[i].boxtype,
867
0
                                             srcImage->properties[i].boxPayload.data,
868
0
                                             srcImage->properties[i].boxPayload.size);
869
0
        }
870
0
        if (res != AVIF_RESULT_OK) {
871
0
            return res;
872
0
        }
873
0
    }
874
875
0
    if (srcImage->gainMap) {
876
0
        dstImage->gainMap = avifGainMapCreate();
877
0
        if (!dstImage->gainMap) {
878
0
            return AVIF_RESULT_OUT_OF_MEMORY;
879
0
        }
880
881
        // Copy all gain map scalars at once
882
0
        *dstImage->gainMap = *srcImage->gainMap;
883
        // Reset pointers to prevent shared ownership and double-free
884
0
        dstImage->gainMap->image = NULL;
885
0
        dstImage->gainMap->altICC.data = NULL;
886
0
        dstImage->gainMap->altICC.size = 0;
887
888
0
        if (srcImage->gainMap->altICC.size > 0) {
889
0
            res = avifRWDataSet(&dstImage->gainMap->altICC, srcImage->gainMap->altICC.data, srcImage->gainMap->altICC.size);
890
0
            if (res != AVIF_RESULT_OK) {
891
0
                return res;
892
0
            }
893
0
        }
894
895
0
        if (srcImage->gainMap->image) {
896
0
            dstImage->gainMap->image = avifImageCreateEmpty();
897
0
            if (!dstImage->gainMap->image) {
898
0
                return AVIF_RESULT_OUT_OF_MEMORY;
899
0
            }
900
0
            res = avifImageCreateView(dstImage->gainMap->image, srcImage->gainMap->image);
901
0
            if (res != AVIF_RESULT_OK) {
902
0
                return res;
903
0
            }
904
0
        }
905
0
    }
906
907
0
    return AVIF_RESULT_OK;
908
0
}