Coverage Report

Created: 2025-07-12 06:45

/src/libavif/apps/shared/avifutil.c
Line
Count
Source (jump to first uncovered line)
1
// Copyright 2019 Joe Drago. All rights reserved.
2
// SPDX-License-Identifier: BSD-2-Clause
3
4
#include "avifutil.h"
5
6
#include <ctype.h>
7
#include <stdio.h>
8
#include <string.h>
9
10
#include "avifjpeg.h"
11
#include "avifpng.h"
12
#include "y4m.h"
13
14
char * avifFileFormatToString(avifAppFileFormat format)
15
0
{
16
0
    switch (format) {
17
0
        case AVIF_APP_FILE_FORMAT_UNKNOWN:
18
0
            return "unknown";
19
0
        case AVIF_APP_FILE_FORMAT_AVIF:
20
0
            return "AVIF";
21
0
        case AVIF_APP_FILE_FORMAT_JPEG:
22
0
            return "JPEG";
23
0
        case AVIF_APP_FILE_FORMAT_PNG:
24
0
            return "PNG";
25
0
        case AVIF_APP_FILE_FORMAT_Y4M:
26
0
            return "Y4M";
27
0
    }
28
0
    return "unknown";
29
0
}
30
31
// |a| and |b| hold int32_t values. The int64_t type is used so that we can negate INT32_MIN without
32
// overflowing int32_t.
33
static int64_t calcGCD(int64_t a, int64_t b)
34
0
{
35
0
    if (a < 0) {
36
0
        a *= -1;
37
0
    }
38
0
    if (b < 0) {
39
0
        b *= -1;
40
0
    }
41
0
    while (b != 0) {
42
0
        int64_t r = a % b;
43
0
        a = b;
44
0
        b = r;
45
0
    }
46
0
    return a;
47
0
}
48
49
static void printClapFraction(const char * name, int32_t n, int32_t d)
50
0
{
51
0
    printf("%s: %d/%d", name, n, d);
52
0
    if (d != 0) {
53
0
        int64_t gcd = calcGCD(n, d);
54
0
        if (gcd > 1) {
55
0
            int32_t rn = (int32_t)(n / gcd);
56
0
            int32_t rd = (int32_t)(d / gcd);
57
0
            printf(" (%d/%d)", rn, rd);
58
0
        }
59
0
    }
60
0
}
61
62
static void avifImageDumpInternal(const avifImage * avif, uint32_t gridCols, uint32_t gridRows, avifBool alphaPresent, avifProgressiveState progressiveState)
63
0
{
64
0
    uint32_t width = avif->width;
65
0
    uint32_t height = avif->height;
66
0
    if (gridCols && gridRows) {
67
0
        width *= gridCols;
68
0
        height *= gridRows;
69
0
    }
70
0
    printf(" * Resolution     : %ux%u\n", width, height);
71
0
    printf(" * Bit Depth      : %u\n", avif->depth);
72
0
    printf(" * Format         : %s\n", avifPixelFormatToString(avif->yuvFormat));
73
0
    if (avif->yuvFormat == AVIF_PIXEL_FORMAT_YUV420) {
74
0
        printf(" * Chroma Sam. Pos: %u\n", avif->yuvChromaSamplePosition);
75
0
    }
76
0
    printf(" * Alpha          : %s\n", alphaPresent ? (avif->alphaPremultiplied ? "Premultiplied" : "Not premultiplied") : "Absent");
77
0
    printf(" * Range          : %s\n", (avif->yuvRange == AVIF_RANGE_FULL) ? "Full" : "Limited");
78
79
0
    printf(" * Color Primaries: %u\n", avif->colorPrimaries);
80
0
    printf(" * Transfer Char. : %u\n", avif->transferCharacteristics);
81
0
    printf(" * Matrix Coeffs. : %u\n", avif->matrixCoefficients);
82
83
0
    if (avif->icc.size != 0) {
84
0
        printf(" * ICC Profile    : Present (%" AVIF_FMT_ZU " bytes)\n", avif->icc.size);
85
0
    } else {
86
0
        printf(" * ICC Profile    : Absent\n");
87
0
    }
88
0
    if (avif->xmp.size != 0) {
89
0
        printf(" * XMP Metadata   : Present (%" AVIF_FMT_ZU " bytes)\n", avif->xmp.size);
90
0
    } else {
91
0
        printf(" * XMP Metadata   : Absent\n");
92
0
    }
93
0
    if (avif->exif.size != 0) {
94
0
        printf(" * Exif Metadata  : Present (%" AVIF_FMT_ZU " bytes)\n", avif->exif.size);
95
0
    } else {
96
0
        printf(" * Exif Metadata  : Absent\n");
97
0
    }
98
99
0
    if (avif->transformFlags == AVIF_TRANSFORM_NONE) {
100
0
        printf(" * Transformations: None\n");
101
0
    } else {
102
0
        printf(" * Transformations:\n");
103
104
0
        if (avif->transformFlags & AVIF_TRANSFORM_PASP) {
105
0
            printf("    * pasp (Aspect Ratio)  : %d/%d\n", (int)avif->pasp.hSpacing, (int)avif->pasp.vSpacing);
106
0
        }
107
0
        if (avif->transformFlags & AVIF_TRANSFORM_CLAP) {
108
0
            printf("    * clap (Clean Aperture): ");
109
0
            printClapFraction("W", (int32_t)avif->clap.widthN, (int32_t)avif->clap.widthD);
110
0
            printf(", ");
111
0
            printClapFraction("H", (int32_t)avif->clap.heightN, (int32_t)avif->clap.heightD);
112
0
            printf(", ");
113
0
            printClapFraction("hOff", (int32_t)avif->clap.horizOffN, (int32_t)avif->clap.horizOffD);
114
0
            printf(", ");
115
0
            printClapFraction("vOff", (int32_t)avif->clap.vertOffN, (int32_t)avif->clap.vertOffD);
116
0
            printf("\n");
117
118
0
            avifCropRect cropRect;
119
0
            avifDiagnostics diag;
120
0
            avifDiagnosticsClearError(&diag);
121
0
            avifBool validClap = avifCropRectFromCleanApertureBox(&cropRect, &avif->clap, avif->width, avif->height, &diag);
122
0
            if (validClap) {
123
0
                printf("      * Valid, derived crop rect: X: %d, Y: %d, W: %d, H: %d%s\n",
124
0
                       cropRect.x,
125
0
                       cropRect.y,
126
0
                       cropRect.width,
127
0
                       cropRect.height,
128
0
                       avifCropRectRequiresUpsampling(&cropRect, avif->yuvFormat) ? " (upsample before cropping)" : "");
129
0
            } else {
130
0
                printf("      * Invalid: %s\n", diag.error);
131
0
            }
132
0
        }
133
0
        if (avif->transformFlags & AVIF_TRANSFORM_IROT) {
134
0
            printf("    * irot (Rotation)      : %u\n", avif->irot.angle);
135
0
        }
136
0
        if (avif->transformFlags & AVIF_TRANSFORM_IMIR) {
137
0
            printf("    * imir (Mirror)        : %u (%s)\n", avif->imir.axis, (avif->imir.axis == 0) ? "top-to-bottom" : "left-to-right");
138
0
        }
139
0
    }
140
0
    printf(" * Progressive    : %s\n", avifProgressiveStateToString(progressiveState));
141
0
    if (avif->clli.maxCLL > 0 || avif->clli.maxPALL > 0) {
142
0
        printf(" * CLLI           : %hu, %hu\n", avif->clli.maxCLL, avif->clli.maxPALL);
143
0
    }
144
145
0
    printf(" * Gain map       : ");
146
0
    avifImage * gainMapImage = avif->gainMap ? avif->gainMap->image : NULL;
147
0
    if (gainMapImage != NULL) {
148
0
        printf("%ux%u pixels, %u bit, %s, %s Range, Matrix Coeffs. %u, Base Headroom %.2f (%s), Alternate Headroom %.2f (%s)\n",
149
0
               gainMapImage->width,
150
0
               gainMapImage->height,
151
0
               gainMapImage->depth,
152
0
               avifPixelFormatToString(gainMapImage->yuvFormat),
153
0
               (gainMapImage->yuvRange == AVIF_RANGE_FULL) ? "Full" : "Limited",
154
0
               gainMapImage->matrixCoefficients,
155
0
               avif->gainMap->baseHdrHeadroom.d == 0 ? 0
156
0
                                                     : (double)avif->gainMap->baseHdrHeadroom.n / avif->gainMap->baseHdrHeadroom.d,
157
0
               (avif->gainMap->baseHdrHeadroom.n == 0) ? "SDR" : "HDR",
158
0
               avif->gainMap->alternateHdrHeadroom.d == 0
159
0
                   ? 0
160
0
                   : (double)avif->gainMap->alternateHdrHeadroom.n / avif->gainMap->alternateHdrHeadroom.d,
161
0
               (avif->gainMap->alternateHdrHeadroom.n == 0) ? "SDR" : "HDR");
162
0
        printf(" * Alternate image:\n");
163
0
        printf("    * Color Primaries: %u\n", avif->gainMap->altColorPrimaries);
164
0
        printf("    * Transfer Char. : %u\n", avif->gainMap->altTransferCharacteristics);
165
0
        printf("    * Matrix Coeffs. : %u\n", avif->gainMap->altMatrixCoefficients);
166
0
        if (avif->gainMap->altICC.size != 0) {
167
0
            printf("    * ICC Profile    : Present (%" AVIF_FMT_ZU " bytes)\n", avif->gainMap->altICC.size);
168
0
        } else {
169
0
            printf("    * ICC Profile    : Absent\n");
170
0
        }
171
0
        if (avif->gainMap->altDepth) {
172
0
            printf("    * Bit Depth      : %u\n", avif->gainMap->altDepth);
173
0
        }
174
0
        if (avif->gainMap->altPlaneCount) {
175
0
            printf("    * Planes         : %u\n", avif->gainMap->altPlaneCount);
176
0
        }
177
0
        if (gainMapImage->clli.maxCLL > 0 || gainMapImage->clli.maxPALL > 0) {
178
0
            printf("    * CLLI           : %hu, %hu\n", gainMapImage->clli.maxCLL, gainMapImage->clli.maxPALL);
179
0
        }
180
0
        printf("\n");
181
0
    } else if (avif->gainMap != NULL) {
182
0
        printf("Present (but ignored)\n");
183
0
    } else {
184
0
        printf("Absent\n");
185
0
    }
186
0
}
187
188
void avifImageDump(const avifImage * avif, uint32_t gridCols, uint32_t gridRows, avifProgressiveState progressiveState)
189
0
{
190
0
    const avifBool alphaPresent = avif->alphaPlane && (avif->alphaRowBytes > 0);
191
0
    avifImageDumpInternal(avif, gridCols, gridRows, alphaPresent, progressiveState);
192
0
}
193
194
void avifContainerDump(const avifDecoder * decoder)
195
0
{
196
0
    avifImageDumpInternal(decoder->image, 0, 0, decoder->alphaPresent, decoder->progressiveState);
197
0
    if (decoder->imageSequenceTrackPresent) {
198
0
        if (decoder->repetitionCount == AVIF_REPETITION_COUNT_INFINITE) {
199
0
            printf(" * Repeat Count   : Infinite\n");
200
0
        } else if (decoder->repetitionCount == AVIF_REPETITION_COUNT_UNKNOWN) {
201
0
            printf(" * Repeat Count   : Unknown\n");
202
0
        } else {
203
0
            printf(" * Repeat Count   : %d\n", decoder->repetitionCount);
204
0
        }
205
0
    }
206
0
}
207
208
void avifPrintVersions(void)
209
0
{
210
0
    char codecVersions[256];
211
0
    avifCodecVersions(codecVersions);
212
0
    printf("Version: %s (%s)\n", avifVersion(), codecVersions);
213
214
0
    unsigned int libyuvVersion = avifLibYUVVersion();
215
0
    if (libyuvVersion == 0) {
216
0
        printf("libyuv : unavailable\n");
217
0
    } else {
218
0
        printf("libyuv : available (%u)\n", libyuvVersion);
219
0
    }
220
221
0
    printf("\n");
222
0
}
223
224
avifAppFileFormat avifGuessFileFormat(const char * filename)
225
6.17k
{
226
    // Guess from the file header
227
6.17k
    FILE * f = fopen(filename, "rb");
228
6.17k
    if (f) {
229
6.17k
        uint8_t headerBuffer[144];
230
6.17k
        size_t bytesRead = fread(headerBuffer, 1, sizeof(headerBuffer), f);
231
6.17k
        fclose(f);
232
233
6.17k
        if (bytesRead > 0) {
234
            // If the file could be read, use the first bytes to guess the file format.
235
6.17k
            return avifGuessBufferFileFormat(headerBuffer, bytesRead);
236
6.17k
        }
237
6.17k
    }
238
239
    // If we get here, the file header couldn't be read for some reason. Guess from the extension.
240
241
8
    const char * fileExt = strrchr(filename, '.');
242
8
    if (!fileExt) {
243
8
        return AVIF_APP_FILE_FORMAT_UNKNOWN;
244
8
    }
245
0
    ++fileExt; // skip past the dot
246
247
0
    char lowercaseExt[8]; // This only needs to fit up to "jpeg", so this is plenty
248
0
    const size_t fileExtLen = strlen(fileExt);
249
0
    if (fileExtLen >= sizeof(lowercaseExt)) { // >= accounts for NULL terminator
250
0
        return AVIF_APP_FILE_FORMAT_UNKNOWN;
251
0
    }
252
253
0
    for (size_t i = 0; i < fileExtLen; ++i) {
254
0
        lowercaseExt[i] = (char)tolower((unsigned char)fileExt[i]);
255
0
    }
256
0
    lowercaseExt[fileExtLen] = 0;
257
258
0
    if (!strcmp(lowercaseExt, "avif")) {
259
0
        return AVIF_APP_FILE_FORMAT_AVIF;
260
0
    } else if (!strcmp(lowercaseExt, "y4m")) {
261
0
        return AVIF_APP_FILE_FORMAT_Y4M;
262
0
    } else if (!strcmp(lowercaseExt, "jpg") || !strcmp(lowercaseExt, "jpeg")) {
263
0
        return AVIF_APP_FILE_FORMAT_JPEG;
264
0
    } else if (!strcmp(lowercaseExt, "png")) {
265
0
        return AVIF_APP_FILE_FORMAT_PNG;
266
0
    }
267
0
    return AVIF_APP_FILE_FORMAT_UNKNOWN;
268
0
}
269
270
avifAppFileFormat avifGuessBufferFileFormat(const uint8_t * data, size_t size)
271
6.71k
{
272
6.71k
    if (size == 0) {
273
0
        return AVIF_APP_FILE_FORMAT_UNKNOWN;
274
0
    }
275
276
6.71k
    avifROData header;
277
6.71k
    header.data = data;
278
6.71k
    header.size = size;
279
280
6.71k
    if (avifPeekCompatibleFileType(&header)) {
281
372
        return AVIF_APP_FILE_FORMAT_AVIF;
282
372
    }
283
284
6.34k
    static const uint8_t signatureJPEG[2] = { 0xFF, 0xD8 };
285
6.34k
    static const uint8_t signaturePNG[8] = { 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A };
286
6.34k
    static const uint8_t signatureY4M[9] = { 0x59, 0x55, 0x56, 0x34, 0x4D, 0x50, 0x45, 0x47, 0x32 }; // "YUV4MPEG2"
287
6.34k
    struct avifHeaderSignature
288
6.34k
    {
289
6.34k
        avifAppFileFormat format;
290
6.34k
        const uint8_t * magic;
291
6.34k
        size_t magicSize;
292
6.34k
    } signatures[] = { { AVIF_APP_FILE_FORMAT_JPEG, signatureJPEG, sizeof(signatureJPEG) },
293
6.34k
                       { AVIF_APP_FILE_FORMAT_PNG, signaturePNG, sizeof(signaturePNG) },
294
6.34k
                       { AVIF_APP_FILE_FORMAT_Y4M, signatureY4M, sizeof(signatureY4M) } };
295
6.34k
    const size_t signaturesCount = sizeof(signatures) / sizeof(signatures[0]);
296
297
9.11k
    for (size_t signatureIndex = 0; signatureIndex < signaturesCount; ++signatureIndex) {
298
8.93k
        const struct avifHeaderSignature * const signature = &signatures[signatureIndex];
299
8.93k
        if (header.size < signature->magicSize) {
300
91
            continue;
301
91
        }
302
8.84k
        if (!memcmp(header.data, signature->magic, signature->magicSize)) {
303
6.15k
            return signature->format;
304
6.15k
        }
305
8.84k
    }
306
307
185
    return AVIF_APP_FILE_FORMAT_UNKNOWN;
308
6.34k
}
309
310
avifAppFileFormat avifReadImage(const char * filename,
311
                                avifAppFileFormat inputFormat,
312
                                avifPixelFormat requestedFormat,
313
                                int requestedDepth,
314
                                avifChromaDownsampling chromaDownsampling,
315
                                avifBool ignoreColorProfile,
316
                                avifBool ignoreExif,
317
                                avifBool ignoreXMP,
318
                                avifBool allowChangingCicp,
319
                                avifBool ignoreGainMap,
320
                                uint32_t imageSizeLimit,
321
                                avifImage * image,
322
                                uint32_t * outDepth,
323
                                avifAppSourceTiming * sourceTiming,
324
                                struct y4mFrameIterator ** frameIter)
325
6.17k
{
326
6.17k
    if (inputFormat == AVIF_APP_FILE_FORMAT_UNKNOWN) {
327
6.17k
        inputFormat = avifGuessFileFormat(filename);
328
6.17k
    }
329
330
6.17k
    if (inputFormat == AVIF_APP_FILE_FORMAT_Y4M) {
331
22
        if (!y4mRead(filename, imageSizeLimit, image, sourceTiming, frameIter)) {
332
21
            return AVIF_APP_FILE_FORMAT_UNKNOWN;
333
21
        }
334
1
        if (outDepth) {
335
1
            *outDepth = image->depth;
336
1
        }
337
6.15k
    } else if (inputFormat == AVIF_APP_FILE_FORMAT_JPEG) {
338
        // imageSizeLimit is also used to limit Exif and XMP metadata here.
339
3.93k
        if (!avifJPEGRead(filename, image, requestedFormat, requestedDepth, chromaDownsampling, ignoreColorProfile, ignoreExif, ignoreXMP, ignoreGainMap, imageSizeLimit)) {
340
2.45k
            return AVIF_APP_FILE_FORMAT_UNKNOWN;
341
2.45k
        }
342
1.47k
        if (outDepth) {
343
1.47k
            *outDepth = 8;
344
1.47k
        }
345
2.22k
    } else if (inputFormat == AVIF_APP_FILE_FORMAT_PNG) {
346
2.04k
        if (!avifPNGRead(filename,
347
2.04k
                         image,
348
2.04k
                         requestedFormat,
349
2.04k
                         requestedDepth,
350
2.04k
                         chromaDownsampling,
351
2.04k
                         ignoreColorProfile,
352
2.04k
                         ignoreExif,
353
2.04k
                         ignoreXMP,
354
2.04k
                         allowChangingCicp,
355
2.04k
                         imageSizeLimit,
356
2.04k
                         outDepth)) {
357
2.02k
            return AVIF_APP_FILE_FORMAT_UNKNOWN;
358
2.02k
        }
359
2.04k
    } else if (inputFormat == AVIF_APP_FILE_FORMAT_UNKNOWN) {
360
181
        fprintf(stderr, "Unrecognized file format for input file: %s\n", filename);
361
181
        return AVIF_APP_FILE_FORMAT_UNKNOWN;
362
181
    } else {
363
0
        fprintf(stderr, "Unsupported file format %s for input file: %s\n", avifFileFormatToString(inputFormat), filename);
364
0
        return AVIF_APP_FILE_FORMAT_UNKNOWN;
365
0
    }
366
1.49k
    return inputFormat;
367
6.17k
}
368
369
avifBool avifReadEntireFile(const char * filename, avifRWData * raw)
370
0
{
371
0
    FILE * f = fopen(filename, "rb");
372
0
    if (!f) {
373
0
        return AVIF_FALSE;
374
0
    }
375
376
0
    fseek(f, 0, SEEK_END);
377
0
    long pos = ftell(f);
378
0
    if (pos <= 0) {
379
0
        fclose(f);
380
0
        return AVIF_FALSE;
381
0
    }
382
0
    size_t fileSize = (size_t)pos;
383
0
    fseek(f, 0, SEEK_SET);
384
385
0
    if (avifRWDataRealloc(raw, fileSize) != AVIF_RESULT_OK) {
386
0
        fclose(f);
387
0
        return AVIF_FALSE;
388
0
    }
389
0
    size_t bytesRead = fread(raw->data, 1, fileSize, f);
390
0
    fclose(f);
391
392
0
    if (bytesRead != fileSize) {
393
0
        avifRWDataFree(raw);
394
0
        return AVIF_FALSE;
395
0
    }
396
0
    return AVIF_TRUE;
397
0
}
398
399
void avifImageFixXMP(avifImage * image)
400
960
{
401
    // Zero bytes are forbidden in UTF-8 XML: https://en.wikipedia.org/wiki/Valid_characters_in_XML
402
    // Keeping zero bytes in XMP may lead to issues at encoding or decoding.
403
    // For example, the PNG specification forbids null characters in XMP. See avifPNGWrite().
404
    // The XMP Specification Part 3 says "When XMP is encoded as UTF-8,
405
    // there are no zero bytes in the XMP packet" for GIF.
406
407
    // Consider a single trailing null character following a non-null character
408
    // as a programming error. Leave other null characters as is.
409
    // See the discussion at https://github.com/AOMediaCodec/libavif/issues/1333.
410
960
    if (image->xmp.size >= 2 && image->xmp.data[image->xmp.size - 1] == '\0' && image->xmp.data[image->xmp.size - 2] != '\0') {
411
2
        --image->xmp.size;
412
2
    }
413
960
}
414
415
void avifDumpDiagnostics(const avifDiagnostics * diag)
416
0
{
417
0
    if (!*diag->error) {
418
0
        return;
419
0
    }
420
421
0
    printf("Diagnostics:\n");
422
0
    printf(" * %s\n", diag->error);
423
0
}
424
425
// ---------------------------------------------------------------------------
426
// avifQueryCPUCount (separated into OS implementations)
427
428
#if defined(_WIN32)
429
430
// Windows
431
432
#include <windows.h>
433
434
int avifQueryCPUCount(void)
435
{
436
    int numCPU;
437
    SYSTEM_INFO sysinfo;
438
    GetSystemInfo(&sysinfo);
439
    numCPU = sysinfo.dwNumberOfProcessors;
440
    return numCPU;
441
}
442
443
#elif defined(__APPLE__)
444
445
// Apple
446
447
#include <sys/sysctl.h>
448
449
int avifQueryCPUCount(void)
450
{
451
    int mib[4];
452
    int numCPU;
453
    size_t len = sizeof(numCPU);
454
455
    /* set the mib for hw.ncpu */
456
    mib[0] = CTL_HW;
457
    mib[1] = HW_AVAILCPU; // alternatively, try HW_NCPU;
458
459
    /* get the number of CPUs from the system */
460
    sysctl(mib, 2, &numCPU, &len, NULL, 0);
461
462
    if (numCPU < 1) {
463
        mib[1] = HW_NCPU;
464
        sysctl(mib, 2, &numCPU, &len, NULL, 0);
465
        if (numCPU < 1)
466
            numCPU = 1;
467
    }
468
    return numCPU;
469
}
470
471
#elif defined(__EMSCRIPTEN__)
472
473
// Emscripten
474
475
int avifQueryCPUCount(void)
476
{
477
    return 1;
478
}
479
480
#else
481
482
// POSIX
483
484
#include <unistd.h>
485
486
int avifQueryCPUCount(void)
487
0
{
488
0
    int numCPU = (int)sysconf(_SC_NPROCESSORS_ONLN);
489
0
    return (numCPU > 0) ? numCPU : 1;
490
0
}
491
492
#endif