Coverage Report

Created: 2026-01-18 06:45

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 <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 (avif->gainMap->altCLLI.maxCLL > 0 || avif->gainMap->altCLLI.maxPALL > 0) {
178
0
            printf("    * CLLI           : %hu, %hu\n", avif->gainMap->altCLLI.maxCLL, avif->gainMap->altCLLI.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
5.77k
{
226
    // Guess from the file header
227
5.77k
    FILE * f = fopen(filename, "rb");
228
5.77k
    if (f) {
229
5.77k
        uint8_t headerBuffer[144];
230
5.77k
        size_t bytesRead = fread(headerBuffer, 1, sizeof(headerBuffer), f);
231
5.77k
        fclose(f);
232
233
5.77k
        if (bytesRead > 0) {
234
            // If the file could be read, use the first bytes to guess the file format.
235
5.74k
            return avifGuessBufferFileFormat(headerBuffer, bytesRead);
236
5.74k
        }
237
5.77k
    }
238
239
    // If we get here, the file header couldn't be read for some reason. Guess from the extension.
240
241
25
    const char * fileExt = strrchr(filename, '.');
242
25
    if (!fileExt) {
243
25
        return AVIF_APP_FILE_FORMAT_UNKNOWN;
244
25
    }
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.31k
{
272
6.31k
    if (size == 0) {
273
0
        return AVIF_APP_FILE_FORMAT_UNKNOWN;
274
0
    }
275
276
6.31k
    avifROData header;
277
6.31k
    header.data = data;
278
6.31k
    header.size = size;
279
280
6.31k
    if (avifPeekCompatibleFileType(&header)) {
281
372
        return AVIF_APP_FILE_FORMAT_AVIF;
282
372
    }
283
284
5.94k
    static const uint8_t signatureJPEG[2] = { 0xFF, 0xD8 };
285
5.94k
    static const uint8_t signaturePNG[8] = { 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A };
286
5.94k
    static const uint8_t signatureY4M[9] = { 0x59, 0x55, 0x56, 0x34, 0x4D, 0x50, 0x45, 0x47, 0x32 }; // "YUV4MPEG2"
287
5.94k
    struct avifHeaderSignature
288
5.94k
    {
289
5.94k
        avifAppFileFormat format;
290
5.94k
        const uint8_t * magic;
291
5.94k
        size_t magicSize;
292
5.94k
    } signatures[] = { { AVIF_APP_FILE_FORMAT_JPEG, signatureJPEG, sizeof(signatureJPEG) },
293
5.94k
                       { AVIF_APP_FILE_FORMAT_PNG, signaturePNG, sizeof(signaturePNG) },
294
5.94k
                       { AVIF_APP_FILE_FORMAT_Y4M, signatureY4M, sizeof(signatureY4M) } };
295
5.94k
    const size_t signaturesCount = sizeof(signatures) / sizeof(signatures[0]);
296
297
9.31k
    for (size_t signatureIndex = 0; signatureIndex < signaturesCount; ++signatureIndex) {
298
9.07k
        const struct avifHeaderSignature * const signature = &signatures[signatureIndex];
299
9.07k
        if (header.size < signature->magicSize) {
300
164
            continue;
301
164
        }
302
8.91k
        if (!memcmp(header.data, signature->magic, signature->magicSize)) {
303
5.70k
            return signature->format;
304
5.70k
        }
305
8.91k
    }
306
307
239
    return AVIF_APP_FILE_FORMAT_UNKNOWN;
308
5.94k
}
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 ignoreGainMap,
319
                                uint32_t imageSizeLimit,
320
                                avifImage * image,
321
                                uint32_t * outDepth,
322
                                avifAppSourceTiming * sourceTiming,
323
                                struct y4mFrameIterator ** frameIter)
324
5.77k
{
325
5.77k
    if (inputFormat == AVIF_APP_FILE_FORMAT_UNKNOWN) {
326
5.77k
        inputFormat = avifGuessFileFormat(filename);
327
5.77k
    }
328
329
5.77k
    if (inputFormat == AVIF_APP_FILE_FORMAT_Y4M) {
330
15
        if (!y4mRead(filename, imageSizeLimit, image, sourceTiming, frameIter)) {
331
12
            return AVIF_APP_FILE_FORMAT_UNKNOWN;
332
12
        }
333
3
        if (outDepth) {
334
3
            *outDepth = image->depth;
335
3
        }
336
5.75k
    } else if (inputFormat == AVIF_APP_FILE_FORMAT_JPEG) {
337
        // imageSizeLimit is also used to limit Exif and XMP metadata here.
338
3.02k
        if (!avifJPEGRead(filename, image, requestedFormat, requestedDepth, chromaDownsampling, ignoreColorProfile, ignoreExif, ignoreXMP, ignoreGainMap, imageSizeLimit)) {
339
2.30k
            return AVIF_APP_FILE_FORMAT_UNKNOWN;
340
2.30k
        }
341
716
        if (outDepth) {
342
716
            *outDepth = 8;
343
716
        }
344
2.73k
    } else if (inputFormat == AVIF_APP_FILE_FORMAT_PNG) {
345
2.48k
        if (!avifPNGRead(filename, image, requestedFormat, requestedDepth, chromaDownsampling, ignoreColorProfile, ignoreExif, ignoreXMP, imageSizeLimit, outDepth)) {
346
2.43k
            return AVIF_APP_FILE_FORMAT_UNKNOWN;
347
2.43k
        }
348
2.48k
    } else if (inputFormat == AVIF_APP_FILE_FORMAT_UNKNOWN) {
349
252
        fprintf(stderr, "Unrecognized file format for input file: %s\n", filename);
350
252
        return AVIF_APP_FILE_FORMAT_UNKNOWN;
351
252
    } else {
352
0
        fprintf(stderr, "Unsupported file format %s for input file: %s\n", avifFileFormatToString(inputFormat), filename);
353
0
        return AVIF_APP_FILE_FORMAT_UNKNOWN;
354
0
    }
355
771
    return inputFormat;
356
5.77k
}
357
358
avifBool avifReadEntireFile(const char * filename, avifRWData * raw)
359
0
{
360
0
    FILE * f = fopen(filename, "rb");
361
0
    if (!f) {
362
0
        return AVIF_FALSE;
363
0
    }
364
365
0
    fseek(f, 0, SEEK_END);
366
0
    long pos = ftell(f);
367
0
    if (pos <= 0) {
368
0
        fclose(f);
369
0
        return AVIF_FALSE;
370
0
    }
371
0
    size_t fileSize = (size_t)pos;
372
0
    fseek(f, 0, SEEK_SET);
373
374
0
    if (avifRWDataRealloc(raw, fileSize) != AVIF_RESULT_OK) {
375
0
        fclose(f);
376
0
        return AVIF_FALSE;
377
0
    }
378
0
    size_t bytesRead = fread(raw->data, 1, fileSize, f);
379
0
    fclose(f);
380
381
0
    if (bytesRead != fileSize) {
382
0
        avifRWDataFree(raw);
383
0
        return AVIF_FALSE;
384
0
    }
385
0
    return AVIF_TRUE;
386
0
}
387
388
void avifImageFixXMP(avifImage * image)
389
1.08k
{
390
    // Zero bytes are forbidden in UTF-8 XML: https://en.wikipedia.org/wiki/Valid_characters_in_XML
391
    // Keeping zero bytes in XMP may lead to issues at encoding or decoding.
392
    // For example, the PNG specification forbids null characters in XMP. See avifPNGWrite().
393
    // The XMP Specification Part 3 says "When XMP is encoded as UTF-8,
394
    // there are no zero bytes in the XMP packet" for GIF.
395
396
    // Consider a single trailing null character following a non-null character
397
    // as a programming error. Leave other null characters as is.
398
    // See the discussion at https://github.com/AOMediaCodec/libavif/issues/1333.
399
1.08k
    if (image->xmp.size >= 2 && image->xmp.data[image->xmp.size - 1] == '\0' && image->xmp.data[image->xmp.size - 2] != '\0') {
400
4
        --image->xmp.size;
401
4
    }
402
1.08k
}
403
404
void avifDumpDiagnostics(const avifDiagnostics * diag)
405
0
{
406
0
    if (!*diag->error) {
407
0
        return;
408
0
    }
409
410
0
    printf("Diagnostics:\n");
411
0
    printf(" * %s\n", diag->error);
412
0
}
413
414
// ---------------------------------------------------------------------------
415
// avifQueryCPUCount (separated into OS implementations)
416
417
#if defined(_WIN32)
418
419
// Windows
420
421
#include <windows.h>
422
423
int avifQueryCPUCount(void)
424
{
425
    int numCPU;
426
    SYSTEM_INFO sysinfo;
427
    GetSystemInfo(&sysinfo);
428
    numCPU = sysinfo.dwNumberOfProcessors;
429
    return numCPU;
430
}
431
432
#elif defined(__APPLE__)
433
434
// Apple
435
436
#include <sys/sysctl.h>
437
438
int avifQueryCPUCount(void)
439
{
440
    int mib[4];
441
    int numCPU;
442
    size_t len = sizeof(numCPU);
443
444
    /* set the mib for hw.ncpu */
445
    mib[0] = CTL_HW;
446
    mib[1] = HW_AVAILCPU; // alternatively, try HW_NCPU;
447
448
    /* get the number of CPUs from the system */
449
    sysctl(mib, 2, &numCPU, &len, NULL, 0);
450
451
    if (numCPU < 1) {
452
        mib[1] = HW_NCPU;
453
        sysctl(mib, 2, &numCPU, &len, NULL, 0);
454
        if (numCPU < 1)
455
            numCPU = 1;
456
    }
457
    return numCPU;
458
}
459
460
#elif defined(__EMSCRIPTEN__)
461
462
// Emscripten
463
464
int avifQueryCPUCount(void)
465
{
466
    return 1;
467
}
468
469
#else
470
471
// POSIX
472
473
#include <unistd.h>
474
475
int avifQueryCPUCount(void)
476
0
{
477
0
    int numCPU = (int)sysconf(_SC_NPROCESSORS_ONLN);
478
0
    return (numCPU > 0) ? numCPU : 1;
479
0
}
480
481
#endif