Coverage Report

Created: 2025-11-24 06:52

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libavif/apps/shared/avifjpeg.c
Line
Count
Source
1
// Copyright 2020 Joe Drago. All rights reserved.
2
// SPDX-License-Identifier: BSD-2-Clause
3
4
#include "avifjpeg.h"
5
#include "avifexif.h"
6
#include "avifutil.h"
7
8
#include <assert.h>
9
#include <ctype.h>
10
#include <math.h>
11
#include <setjmp.h>
12
#include <stdint.h>
13
#include <stdio.h>
14
#include <stdlib.h>
15
#include <string.h>
16
17
#include "jpeglib.h"
18
19
#include "iccjpeg.h"
20
21
#if defined(AVIF_ENABLE_JPEG_GAIN_MAP_CONVERSION)
22
#include <libxml/parser.h>
23
#endif
24
25
120k
#define AVIF_MIN(a, b) (((a) < (b)) ? (a) : (b))
26
381
#define AVIF_MAX(a, b) (((a) > (b)) ? (a) : (b))
27
28
struct my_error_mgr
29
{
30
    struct jpeg_error_mgr pub;
31
    jmp_buf setjmp_buffer;
32
};
33
typedef struct my_error_mgr * my_error_ptr;
34
static void my_error_exit(j_common_ptr cinfo)
35
2.70k
{
36
2.70k
    my_error_ptr myerr = (my_error_ptr)cinfo->err;
37
2.70k
    (*cinfo->err->output_message)(cinfo);
38
2.70k
    longjmp(myerr->setjmp_buffer, 1);
39
2.70k
}
40
41
#if JPEG_LIB_VERSION >= 70
42
#define AVIF_LIBJPEG_DCT_v_scaled_size DCT_v_scaled_size
43
#define AVIF_LIBJPEG_DCT_h_scaled_size DCT_h_scaled_size
44
#else
45
381
#define AVIF_LIBJPEG_DCT_h_scaled_size DCT_scaled_size
46
381
#define AVIF_LIBJPEG_DCT_v_scaled_size DCT_scaled_size
47
#endif
48
49
// An internal function used by avifJPEGReadCopy(), this is the shared libjpeg decompression code
50
// for all paths avifJPEGReadCopy() takes.
51
static avifBool avifJPEGCopyPixels(avifImage * avif, uint32_t sizeLimit, struct jpeg_decompress_struct * cinfo)
52
152
{
53
152
    cinfo->raw_data_out = TRUE;
54
152
    jpeg_start_decompress(cinfo);
55
56
152
    avif->width = cinfo->image_width;
57
152
    avif->height = cinfo->image_height;
58
152
    if (avif->width > sizeLimit / avif->height) {
59
0
        return AVIF_FALSE;
60
0
    }
61
62
152
    JSAMPIMAGE buffer = (*cinfo->mem->alloc_small)((j_common_ptr)cinfo, JPOOL_IMAGE, sizeof(JSAMPARRAY) * cinfo->num_components);
63
64
    // lines of output image to be read per jpeg_read_raw_data call
65
152
    int readLines = 0;
66
    // lines of samples to be read per call (for each channel)
67
152
    int linesPerCall[3] = { 0, 0, 0 };
68
    // expected count of sample lines (for each channel)
69
152
    int targetRead[3] = { 0, 0, 0 };
70
533
    for (int i = 0; i < cinfo->num_components; ++i) {
71
381
        jpeg_component_info * comp = &cinfo->comp_info[i];
72
73
381
        linesPerCall[i] = comp->v_samp_factor * comp->AVIF_LIBJPEG_DCT_v_scaled_size;
74
381
        targetRead[i] = comp->downsampled_height;
75
381
        buffer[i] = (*cinfo->mem->alloc_sarray)((j_common_ptr)cinfo,
76
381
                                                JPOOL_IMAGE,
77
381
                                                comp->width_in_blocks * comp->AVIF_LIBJPEG_DCT_h_scaled_size,
78
381
                                                linesPerCall[i]);
79
381
        readLines = AVIF_MAX(readLines, linesPerCall[i]);
80
381
    }
81
82
152
    avifImageFreePlanes(avif, AVIF_PLANES_ALL); // Free planes in case they were already allocated.
83
152
    if (avifImageAllocatePlanes(avif, AVIF_PLANES_YUV) != AVIF_RESULT_OK) {
84
0
        return AVIF_FALSE;
85
0
    }
86
87
    // destination avif channel for each jpeg channel
88
152
    avifChannelIndex targetChannel[3] = { AVIF_CHAN_Y, AVIF_CHAN_Y, AVIF_CHAN_Y };
89
152
    if (cinfo->jpeg_color_space == JCS_YCbCr) {
90
118
        targetChannel[0] = AVIF_CHAN_Y;
91
118
        targetChannel[1] = AVIF_CHAN_U;
92
118
        targetChannel[2] = AVIF_CHAN_V;
93
118
    } else if (cinfo->jpeg_color_space == JCS_GRAYSCALE) {
94
3
        targetChannel[0] = AVIF_CHAN_Y;
95
31
    } else {
96
        // cinfo->jpeg_color_space == JCS_RGB
97
31
        targetChannel[0] = AVIF_CHAN_V;
98
31
        targetChannel[1] = AVIF_CHAN_Y;
99
31
        targetChannel[2] = AVIF_CHAN_U;
100
31
    }
101
102
152
    int workComponents = avif->yuvFormat == AVIF_PIXEL_FORMAT_YUV400 ? 1 : cinfo->num_components;
103
104
    // count of already-read lines (for each channel)
105
152
    int alreadyRead[3] = { 0, 0, 0 };
106
81.3k
    while (cinfo->output_scanline < cinfo->output_height) {
107
81.1k
        jpeg_read_raw_data(cinfo, buffer, readLines);
108
109
202k
        for (int i = 0; i < workComponents; ++i) {
110
120k
            int linesRead = AVIF_MIN(targetRead[i] - alreadyRead[i], linesPerCall[i]);
111
1.49M
            for (int j = 0; j < linesRead; ++j) {
112
1.37M
                memcpy(&avif->yuvPlanes[targetChannel[i]][avif->yuvRowBytes[targetChannel[i]] * (alreadyRead[i] + j)],
113
1.37M
                       buffer[i][j],
114
1.37M
                       avif->yuvRowBytes[targetChannel[i]]);
115
1.37M
            }
116
120k
            alreadyRead[i] += linesPerCall[i];
117
120k
        }
118
81.1k
    }
119
152
    return AVIF_TRUE;
120
152
}
121
122
static avifBool avifJPEGHasCompatibleMatrixCoefficients(avifMatrixCoefficients matrixCoefficients)
123
1.38k
{
124
1.38k
    switch (matrixCoefficients) {
125
302
        case AVIF_MATRIX_COEFFICIENTS_BT470BG:
126
580
        case AVIF_MATRIX_COEFFICIENTS_BT601:
127
            // JPEG always uses [Kr:0.299, Kb:0.114], which matches these MCs.
128
580
            return AVIF_TRUE;
129
1.38k
    }
130
801
    return AVIF_FALSE;
131
1.38k
}
132
133
// This attempts to copy the internal representation of the JPEG directly into avifImage without
134
// YUV->RGB conversion. If it returns AVIF_FALSE, a typical RGB->YUV conversion is required.
135
static avifBool avifJPEGReadCopy(avifImage * avif, uint32_t sizeLimit, struct jpeg_decompress_struct * cinfo)
136
3.91k
{
137
3.91k
    if ((avif->depth != 8) || (avif->yuvRange != AVIF_RANGE_FULL)) {
138
1.65k
        return AVIF_FALSE;
139
1.65k
    }
140
141
2.26k
    if (cinfo->jpeg_color_space == JCS_YCbCr) {
142
        // Import from YUV: must use compatible matrixCoefficients.
143
1.36k
        if (avifJPEGHasCompatibleMatrixCoefficients(avif->matrixCoefficients)) {
144
            // YUV->YUV: require precise match for pixel format.
145
577
            avifPixelFormat jpegFormat = AVIF_PIXEL_FORMAT_NONE;
146
577
            if (cinfo->comp_info[0].h_samp_factor == 1 && cinfo->comp_info[0].v_samp_factor == 1 &&
147
127
                cinfo->comp_info[1].h_samp_factor == 1 && cinfo->comp_info[1].v_samp_factor == 1 &&
148
94
                cinfo->comp_info[2].h_samp_factor == 1 && cinfo->comp_info[2].v_samp_factor == 1) {
149
78
                jpegFormat = AVIF_PIXEL_FORMAT_YUV444;
150
499
            } else if (cinfo->comp_info[0].h_samp_factor == 2 && cinfo->comp_info[0].v_samp_factor == 1 &&
151
103
                       cinfo->comp_info[1].h_samp_factor == 1 && cinfo->comp_info[1].v_samp_factor == 1 &&
152
85
                       cinfo->comp_info[2].h_samp_factor == 1 && cinfo->comp_info[2].v_samp_factor == 1) {
153
14
                jpegFormat = AVIF_PIXEL_FORMAT_YUV422;
154
485
            } else if (cinfo->comp_info[0].h_samp_factor == 2 && cinfo->comp_info[0].v_samp_factor == 2 &&
155
119
                       cinfo->comp_info[1].h_samp_factor == 1 && cinfo->comp_info[1].v_samp_factor == 1 &&
156
81
                       cinfo->comp_info[2].h_samp_factor == 1 && cinfo->comp_info[2].v_samp_factor == 1) {
157
64
                jpegFormat = AVIF_PIXEL_FORMAT_YUV420;
158
64
            }
159
577
            if (jpegFormat != AVIF_PIXEL_FORMAT_NONE) {
160
156
                if (avif->yuvFormat == AVIF_PIXEL_FORMAT_NONE) {
161
                    // The requested format is "auto": Adopt JPEG's internal format.
162
0
                    avif->yuvFormat = jpegFormat;
163
0
                }
164
156
                if (avif->yuvFormat == jpegFormat) {
165
46
                    cinfo->out_color_space = JCS_YCbCr;
166
46
                    return avifJPEGCopyPixels(avif, sizeLimit, cinfo);
167
46
                }
168
156
            }
169
170
            // YUV->Grayscale: subsample Y plane not allowed.
171
531
            if ((avif->yuvFormat == AVIF_PIXEL_FORMAT_YUV400) && (cinfo->comp_info[0].h_samp_factor == cinfo->max_h_samp_factor &&
172
134
                                                                  cinfo->comp_info[0].v_samp_factor == cinfo->max_v_samp_factor)) {
173
89
                cinfo->out_color_space = JCS_YCbCr;
174
89
                return avifJPEGCopyPixels(avif, sizeLimit, cinfo);
175
89
            }
176
531
        }
177
1.36k
    } else if (cinfo->jpeg_color_space == JCS_GRAYSCALE) {
178
        // Import from Grayscale: subsample not allowed.
179
12
        if ((cinfo->comp_info[0].h_samp_factor == cinfo->max_h_samp_factor &&
180
12
             cinfo->comp_info[0].v_samp_factor == cinfo->max_v_samp_factor)) {
181
            // Import to YUV/Grayscale: must use compatible matrixCoefficients.
182
12
            if (avifJPEGHasCompatibleMatrixCoefficients(avif->matrixCoefficients) ||
183
9
                avif->matrixCoefficients == AVIF_MATRIX_COEFFICIENTS_UNSPECIFIED) {
184
                // Grayscale->Grayscale: direct copy.
185
6
                if ((avif->yuvFormat == AVIF_PIXEL_FORMAT_YUV400) || (avif->yuvFormat == AVIF_PIXEL_FORMAT_NONE)) {
186
1
                    avif->yuvFormat = AVIF_PIXEL_FORMAT_YUV400;
187
1
                    cinfo->out_color_space = JCS_GRAYSCALE;
188
1
                    return avifJPEGCopyPixels(avif, sizeLimit, cinfo);
189
1
                }
190
191
                // Grayscale->YUV: copy Y, fill UV with monochrome value.
192
5
                if ((avif->yuvFormat == AVIF_PIXEL_FORMAT_YUV444) || (avif->yuvFormat == AVIF_PIXEL_FORMAT_YUV422) ||
193
5
                    (avif->yuvFormat == AVIF_PIXEL_FORMAT_YUV420)) {
194
5
                    cinfo->out_color_space = JCS_GRAYSCALE;
195
5
                    if (!avifJPEGCopyPixels(avif, sizeLimit, cinfo)) {
196
0
                        return AVIF_FALSE;
197
0
                    }
198
199
5
                    uint32_t uvHeight = avifImagePlaneHeight(avif, AVIF_CHAN_U);
200
5
                    memset(avif->yuvPlanes[AVIF_CHAN_U], 128, (size_t)avif->yuvRowBytes[AVIF_CHAN_U] * uvHeight);
201
5
                    memset(avif->yuvPlanes[AVIF_CHAN_V], 128, (size_t)avif->yuvRowBytes[AVIF_CHAN_V] * uvHeight);
202
203
5
                    return AVIF_TRUE;
204
5
                }
205
5
            }
206
207
            // Grayscale->RGB: copy Y to G, duplicate to B and R.
208
6
            if ((avif->matrixCoefficients == AVIF_MATRIX_COEFFICIENTS_IDENTITY) &&
209
4
                ((avif->yuvFormat == AVIF_PIXEL_FORMAT_YUV444) || (avif->yuvFormat == AVIF_PIXEL_FORMAT_NONE))) {
210
2
                avif->yuvFormat = AVIF_PIXEL_FORMAT_YUV444;
211
2
                cinfo->out_color_space = JCS_GRAYSCALE;
212
2
                if (!avifJPEGCopyPixels(avif, sizeLimit, cinfo)) {
213
0
                    return AVIF_FALSE;
214
0
                }
215
216
2
                memcpy(avif->yuvPlanes[AVIF_CHAN_U], avif->yuvPlanes[AVIF_CHAN_Y], (size_t)avif->yuvRowBytes[AVIF_CHAN_U] * avif->height);
217
2
                memcpy(avif->yuvPlanes[AVIF_CHAN_V], avif->yuvPlanes[AVIF_CHAN_Y], (size_t)avif->yuvRowBytes[AVIF_CHAN_V] * avif->height);
218
219
2
                return AVIF_TRUE;
220
2
            }
221
6
        }
222
881
    } else if (cinfo->jpeg_color_space == JCS_RGB) {
223
        // RGB->RGB: subsample not allowed.
224
874
        if ((avif->matrixCoefficients == AVIF_MATRIX_COEFFICIENTS_IDENTITY) &&
225
166
            ((avif->yuvFormat == AVIF_PIXEL_FORMAT_YUV444) || (avif->yuvFormat == AVIF_PIXEL_FORMAT_NONE)) &&
226
100
            (cinfo->comp_info[0].h_samp_factor == 1 && cinfo->comp_info[0].v_samp_factor == 1 &&
227
32
             cinfo->comp_info[1].h_samp_factor == 1 && cinfo->comp_info[1].v_samp_factor == 1 &&
228
15
             cinfo->comp_info[2].h_samp_factor == 1 && cinfo->comp_info[2].v_samp_factor == 1)) {
229
9
            avif->yuvFormat = AVIF_PIXEL_FORMAT_YUV444;
230
9
            cinfo->out_color_space = JCS_RGB;
231
9
            return avifJPEGCopyPixels(avif, sizeLimit, cinfo);
232
9
        }
233
874
    }
234
235
    // A typical RGB->YUV conversion is required.
236
2.11k
    return AVIF_FALSE;
237
2.26k
}
238
239
// Reads a 4-byte unsigned integer in big-endian format from the raw bitstream src.
240
static uint32_t avifJPEGReadUint32BigEndian(const uint8_t * src)
241
114
{
242
114
    return ((uint32_t)src[0] << 24) | ((uint32_t)src[1] << 16) | ((uint32_t)src[2] << 8) | ((uint32_t)src[3] << 0);
243
114
}
244
245
// Returns the pointer in str to the first occurrence of substr. Returns NULL if substr cannot be found in str.
246
static const uint8_t * avifJPEGFindSubstr(const uint8_t * str, size_t strLength, const uint8_t * substr, size_t substrLength)
247
24
{
248
60.1k
    for (size_t index = 0; index + substrLength <= strLength; ++index) {
249
60.1k
        if (!memcmp(&str[index], substr, substrLength)) {
250
11
            return &str[index];
251
11
        }
252
60.1k
    }
253
13
    return NULL;
254
24
}
255
256
0
#define AVIF_JPEG_MAX_MARKER_DATA_LENGTH 65533
257
258
// Exif tag
259
777
#define AVIF_JPEG_EXIF_HEADER "Exif\0\0"
260
2.08k
#define AVIF_JPEG_EXIF_HEADER_LENGTH 6
261
262
// XMP tags
263
818
#define AVIF_JPEG_STANDARD_XMP_TAG "http://ns.adobe.com/xap/1.0/\0"
264
1.84k
#define AVIF_JPEG_STANDARD_XMP_TAG_LENGTH 29
265
778
#define AVIF_JPEG_EXTENDED_XMP_TAG "http://ns.adobe.com/xmp/extension/\0"
266
2.00k
#define AVIF_JPEG_EXTENDED_XMP_TAG_LENGTH 35
267
268
// MPF tag (Multi-Picture Format)
269
#define AVIF_JPEG_MPF_HEADER "MPF\0"
270
#define AVIF_JPEG_MPF_HEADER_LENGTH 4
271
272
// One way of storing the Extended XMP GUID (generated by a camera for example).
273
16
#define AVIF_JPEG_XMP_NOTE_TAG "xmpNote:HasExtendedXMP=\""
274
32
#define AVIF_JPEG_XMP_NOTE_TAG_LENGTH 24
275
// Another way of storing the Extended XMP GUID (generated by exiftool for example).
276
8
#define AVIF_JPEG_ALTERNATIVE_XMP_NOTE_TAG "<xmpNote:HasExtendedXMP>"
277
16
#define AVIF_JPEG_ALTERNATIVE_XMP_NOTE_TAG_LENGTH 24
278
279
2.79k
#define AVIF_JPEG_EXTENDED_XMP_GUID_LENGTH 32
280
281
// Offset in APP1 segment (skip tag + guid + size + offset).
282
183
#define AVIF_JPEG_OFFSET_TILL_EXTENDED_XMP (AVIF_JPEG_EXTENDED_XMP_TAG_LENGTH + AVIF_JPEG_EXTENDED_XMP_GUID_LENGTH + 4 + 4)
283
284
#define AVIF_CHECK(A)          \
285
    do {                       \
286
        if (!(A))              \
287
            return AVIF_FALSE; \
288
    } while (0)
289
290
#if defined(AVIF_ENABLE_JPEG_GAIN_MAP_CONVERSION)
291
292
// Reads a 4-byte unsigned integer in little-endian format from the raw bitstream src.
293
static uint32_t avifJPEGReadUint32LittleEndian(const uint8_t * src)
294
{
295
    return ((uint32_t)src[0] << 0) | ((uint32_t)src[1] << 8) | ((uint32_t)src[2] << 16) | ((uint32_t)src[3] << 24);
296
}
297
298
// Reads a 2-byte unsigned integer in big-endian format from the raw bitstream src.
299
static uint16_t avifJPEGReadUint16BigEndian(const uint8_t * src)
300
{
301
    return (uint16_t)((src[0] << 8) | (src[1] << 0));
302
}
303
304
// Reads a 2-byte unsigned integer in little-endian format from the raw bitstream src.
305
static uint16_t avifJPEGReadUint16LittleEndian(const uint8_t * src)
306
{
307
    return (uint16_t)((src[0] << 0) | (src[1] << 8));
308
}
309
310
// Reads 'numBytes' at 'offset', stores them in 'bytes' and increases 'offset'.
311
static avifBool avifJPEGReadBytes(const avifROData * data, uint8_t * bytes, uint32_t * offset, uint32_t numBytes)
312
{
313
    if ((UINT32_MAX - *offset) < numBytes || data->size < (*offset + numBytes)) {
314
        return AVIF_FALSE;
315
    }
316
    memcpy(bytes, &data->data[*offset], numBytes);
317
    *offset += numBytes;
318
    return AVIF_TRUE;
319
}
320
321
static avifBool avifJPEGReadU32(const avifROData * data, uint32_t * v, uint32_t * offset, avifBool isBigEndian)
322
{
323
    uint8_t bytes[4];
324
    AVIF_CHECK(avifJPEGReadBytes(data, bytes, offset, 4));
325
    *v = isBigEndian ? avifJPEGReadUint32BigEndian(bytes) : avifJPEGReadUint32LittleEndian(bytes);
326
    return AVIF_TRUE;
327
}
328
329
static avifBool avifJPEGReadU16(const avifROData * data, uint16_t * v, uint32_t * offset, avifBool isBigEndian)
330
{
331
    uint8_t bytes[2];
332
    AVIF_CHECK(avifJPEGReadBytes(data, bytes, offset, 2));
333
    *v = isBigEndian ? avifJPEGReadUint16BigEndian(bytes) : avifJPEGReadUint16LittleEndian(bytes);
334
    return AVIF_TRUE;
335
}
336
337
static avifBool avifJPEGReadInternal(FILE * f,
338
                                     const char * inputFilename,
339
                                     avifImage * avif,
340
                                     avifPixelFormat requestedFormat,
341
                                     uint32_t requestedDepth,
342
                                     avifChromaDownsampling chromaDownsampling,
343
                                     avifBool ignoreColorProfile,
344
                                     avifBool ignoreExif,
345
                                     avifBool ignoreXMP,
346
                                     avifBool ignoreGainMap,
347
                                     uint32_t sizeLimit);
348
349
// Arbitrary max number of jpeg segments to parse before giving up.
350
#define MAX_JPEG_SEGMENTS 100
351
352
// Finds the offset of the first MPF segment. Returns AVIF_TRUE if it was found.
353
static avifBool avifJPEGFindMpfSegmentOffset(FILE * f, uint32_t * mpfOffset)
354
{
355
    const long oldOffset = ftell(f);
356
    if (oldOffset < 0) {
357
        return AVIF_FALSE;
358
    }
359
360
    uint32_t offset = 2; // Skip the 2 byte SOI (Start Of Image) marker.
361
    if (fseek(f, offset, SEEK_SET) != 0) {
362
        return AVIF_FALSE;
363
    }
364
365
    uint8_t buffer[4];
366
    int numSegments = 0;
367
    while (numSegments < MAX_JPEG_SEGMENTS) {
368
        ++numSegments;
369
        // Read the APP<n> segment marker (2 bytes) and the segment size (2 bytes).
370
        if (fread(buffer, 1, 4, f) != 4) {
371
            fseek(f, oldOffset, SEEK_SET);
372
            return AVIF_FALSE; // End of the file reached.
373
        }
374
        offset += 4;
375
376
        // Total APP<n> segment byte count, including the byte count value (2 bytes), but excluding the 2 byte APP<n> marker itself.
377
        const uint16_t segmentLength = avifJPEGReadUint16BigEndian(&buffer[2]);
378
        if (segmentLength < 2) {
379
            fseek(f, oldOffset, SEEK_SET);
380
            return AVIF_FALSE; // Invalid length.
381
        } else if (segmentLength < 2 + AVIF_JPEG_MPF_HEADER_LENGTH) {
382
            // Cannot be an MPF segment, skip to the next segment.
383
            offset += segmentLength - 2;
384
            if (fseek(f, offset, SEEK_SET) != 0) {
385
                fseek(f, oldOffset, SEEK_SET);
386
                return AVIF_FALSE;
387
            }
388
            continue;
389
        }
390
391
        uint8_t identifier[AVIF_JPEG_MPF_HEADER_LENGTH];
392
        if (fread(identifier, 1, AVIF_JPEG_MPF_HEADER_LENGTH, f) != AVIF_JPEG_MPF_HEADER_LENGTH) {
393
            fseek(f, oldOffset, SEEK_SET);
394
            return AVIF_FALSE; // End of the file reached.
395
        }
396
        offset += AVIF_JPEG_MPF_HEADER_LENGTH;
397
398
        if (buffer[1] == (JPEG_APP0 + 2) && !memcmp(identifier, AVIF_JPEG_MPF_HEADER, AVIF_JPEG_MPF_HEADER_LENGTH)) {
399
            // MPF segment found.
400
            *mpfOffset = offset;
401
            fseek(f, oldOffset, SEEK_SET);
402
            return AVIF_TRUE;
403
        }
404
405
        // Skip to the next segment.
406
        offset += segmentLength - 2 - AVIF_JPEG_MPF_HEADER_LENGTH;
407
        if (fseek(f, offset, SEEK_SET) != 0) {
408
            fseek(f, oldOffset, SEEK_SET);
409
            return AVIF_FALSE;
410
        }
411
    }
412
    return AVIF_FALSE;
413
}
414
415
// Searches for a node called 'nameSpace:nodeName' in the children (or descendants if 'recursive' is set) of 'parentNode'.
416
// Returns the first such node found (in depth first search). Returns NULL if no such node is found.
417
static const xmlNode * avifJPEGFindXMLNodeByName(const xmlNode * parentNode, const char * nameSpace, const char * nodeName, avifBool recursive)
418
{
419
    if (parentNode == NULL) {
420
        return NULL;
421
    }
422
    for (const xmlNode * node = parentNode->children; node != NULL; node = node->next) {
423
        if (node->ns != NULL && !xmlStrcmp(node->ns->href, (const xmlChar *)nameSpace) &&
424
            !xmlStrcmp(node->name, (const xmlChar *)nodeName)) {
425
            return node;
426
        } else if (recursive) {
427
            const xmlNode * descendantNode = avifJPEGFindXMLNodeByName(node, nameSpace, nodeName, recursive);
428
            if (descendantNode != NULL) {
429
                return descendantNode;
430
            }
431
        }
432
    }
433
    return NULL;
434
}
435
436
#define XML_NAME_SPACE_GAIN_MAP "http://ns.adobe.com/hdr-gain-map/1.0/"
437
#define XML_NAME_SPACE_RDF "http://www.w3.org/1999/02/22-rdf-syntax-ns#"
438
#define XML_NAME_SPACE_XMP_NOTE "http://ns.adobe.com/xmp/note/"
439
440
// Finds an 'rdf:Description' node containing a gain map version attribute (hdrgm:Version="1.0").
441
// Returns NULL if not found.
442
static const xmlNode * avifJPEGFindGainMapXMPNode(const xmlNode * rootNode)
443
{
444
    // See XMP specification https://github.com/adobe/XMP-Toolkit-SDK/blob/main/docs/XMPSpecificationPart1.pdf
445
    // ISO 16684-1:2011 7.1 "For this serialization, a single XMP packet shall be serialized using a single rdf:RDF XML element."
446
    // 7.3 "Other XML elements may appear around the rdf:RDF element."
447
    const xmlNode * rdfNode = avifJPEGFindXMLNodeByName(rootNode, XML_NAME_SPACE_RDF, "RDF", /*recursive=*/AVIF_TRUE);
448
    if (rdfNode == NULL) {
449
        return NULL;
450
    }
451
    for (const xmlNode * node = rdfNode->children; node != NULL; node = node->next) {
452
        // Loop through rdf:Description children.
453
        // 7.4 "A single XMP packet shall be serialized using a single rdf:RDF XML element. The rdf:RDF element content
454
        // shall consist of only zero or more rdf:Description elements."
455
        if (node->ns && !xmlStrcmp(node->ns->href, (const xmlChar *)XML_NAME_SPACE_RDF) &&
456
            !xmlStrcmp(node->name, (const xmlChar *)"Description")) {
457
            // Look for the gain map version attribute: hdrgm:Version="1.0"
458
            for (xmlAttr * prop = node->properties; prop != NULL; prop = prop->next) {
459
                if (prop->ns && !xmlStrcmp(prop->ns->href, (const xmlChar *)XML_NAME_SPACE_GAIN_MAP) &&
460
                    !xmlStrcmp(prop->name, (const xmlChar *)"Version") && prop->children != NULL &&
461
                    !xmlStrcmp(prop->children->content, (const xmlChar *)"1.0")) {
462
                    return node;
463
                }
464
            }
465
        }
466
    }
467
    return NULL;
468
}
469
470
// Returns true if there is an 'rdf:Description' node containing a gain map version attribute (hdrgm:Version="1.0").
471
// On the main image, this signals that the file also contains a gain map.
472
// On a subsequent image, this signals that it is a gain map.
473
static avifBool avifJPEGHasGainMapXMPNode(const uint8_t * xmpData, size_t xmpSize)
474
{
475
    xmlDoc * document = xmlReadMemory((const char *)xmpData, (int)xmpSize, NULL, NULL, /*options=*/0);
476
    if (document == NULL) {
477
        return AVIF_FALSE; // Probably and out of memory error.
478
    }
479
    const xmlNode * rootNode = xmlDocGetRootElement(document);
480
    const xmlNode * node = avifJPEGFindGainMapXMPNode(rootNode);
481
    const avifBool found = (node != NULL);
482
    xmlFreeDoc(document);
483
    return found;
484
}
485
486
// Finds the value of a gain map metadata property, that can be either stored as an attribute of 'descriptionNode'
487
// (which should point to a <rdf:Description> node) or as a child node.
488
// 'maxValues' is the maximum number of expected values, and the size of the 'values' array. 'numValues' is set to the number
489
// of values actually found (which may be smaller or larger, but only up to 'maxValues' are stored in 'values').
490
// Returns AVIF_TRUE if the property was found.
491
static avifBool avifJPEGFindGainMapProperty(const xmlNode * descriptionNode,
492
                                            const char * propertyName,
493
                                            uint32_t maxValues,
494
                                            const char * values[],
495
                                            uint32_t * numValues)
496
{
497
    *numValues = 0;
498
499
    // Search attributes.
500
    for (xmlAttr * prop = descriptionNode->properties; prop != NULL; prop = prop->next) {
501
        if (prop->ns && !xmlStrcmp(prop->ns->href, (const xmlChar *)XML_NAME_SPACE_GAIN_MAP) &&
502
            !xmlStrcmp(prop->name, (const xmlChar *)propertyName) && prop->children != NULL && prop->children->content != NULL) {
503
            // Properties should have just one child containing the property's value
504
            // (in fact the 'children' field is documented as "the value of the property").
505
            values[0] = (const char *)prop->children->content;
506
            *numValues = 1;
507
            return AVIF_TRUE;
508
        }
509
    }
510
511
    // Search child nodes.
512
    for (const xmlNode * node = descriptionNode->children; node != NULL; node = node->next) {
513
        if (node->ns && !xmlStrcmp(node->ns->href, (const xmlChar *)XML_NAME_SPACE_GAIN_MAP) &&
514
            !xmlStrcmp(node->name, (const xmlChar *)propertyName) && node->children) {
515
            // Multiple values can be specified with a Seq tag: <rdf:Seq><rdf:li>value1</rdf:li><rdf:li>value2</rdf:li>...</rdf:Seq>
516
            const xmlNode * seq = avifJPEGFindXMLNodeByName(node, XML_NAME_SPACE_RDF, "Seq", /*recursive=*/AVIF_FALSE);
517
            if (seq) {
518
                for (xmlNode * seqChild = seq->children; seqChild; seqChild = seqChild->next) {
519
                    if (!xmlStrcmp(seqChild->name, (const xmlChar *)"li") && seqChild->children != NULL &&
520
                        seqChild->children->content != NULL) {
521
                        if (*numValues < maxValues) {
522
                            values[*numValues] = (const char *)seqChild->children->content;
523
                        }
524
                        ++(*numValues);
525
                    }
526
                }
527
                return *numValues > 0 ? AVIF_TRUE : AVIF_FALSE;
528
            } else if (node->children->next == NULL && node->children->type == XML_TEXT_NODE) { // Only one child and it's text.
529
                values[0] = (const char *)node->children->content;
530
                *numValues = 1;
531
                return AVIF_TRUE;
532
            }
533
            // We found a tag for this property but no valid content.
534
            return AVIF_FALSE;
535
        }
536
    }
537
538
    return AVIF_FALSE; // Property not found.
539
}
540
541
// Up to 3 values per property (one for each RGB channel).
542
#define GAIN_MAP_PROPERTY_MAX_VALUES 3
543
544
// Looks for a given gain map property's double value(s), and if found, stores them in 'values'.
545
// The 'values' array should have size at least 'numDoubles', and should be initialized with default
546
// values for this property, since the array will be left untouched if the property is not found.
547
// Returns AVIF_TRUE if the property was successfully parsed, or if it was not found, since all properties
548
// are optional. Returns AVIF_FALSE in case of error (invalid metadata XMP).
549
static avifBool avifJPEGFindGainMapPropertyDoubles(const xmlNode * descriptionNode, const char * propertyName, double * values, uint32_t numDoubles)
550
{
551
    assert(numDoubles <= GAIN_MAP_PROPERTY_MAX_VALUES);
552
    const char * textValues[GAIN_MAP_PROPERTY_MAX_VALUES];
553
    uint32_t numValues;
554
    if (!avifJPEGFindGainMapProperty(descriptionNode, propertyName, /*maxValues=*/numDoubles, &textValues[0], &numValues)) {
555
        return AVIF_TRUE; // Property was not found, but it's not an error since they're optional.
556
    }
557
    if (numValues != 1 && numValues != numDoubles) {
558
        return AVIF_FALSE; // Invalid, we expect either 1 or exactly numDoubles values.
559
    }
560
    for (uint32_t i = 0; i < numDoubles; ++i) {
561
        if (i >= numValues) {
562
            // If there is only 1 value, it's copied into the rest of the array.
563
            values[i] = values[i - 1];
564
        } else {
565
            int charsRead;
566
            if (sscanf(textValues[i], "%lf%n", &values[i], &charsRead) < 1) {
567
                return AVIF_FALSE; // Was not able to parse the full string value as a double.
568
            }
569
            // Make sure that remaining characters (if any) are only whitespace.
570
            const int len = (int)strlen(textValues[i]);
571
            while (charsRead < len) {
572
                if (!isspace(textValues[i][charsRead])) {
573
                    return AVIF_FALSE; // Invalid character.
574
                }
575
                ++charsRead;
576
            }
577
        }
578
    }
579
580
    return AVIF_TRUE;
581
}
582
583
static inline void SwapDoubles(double * x, double * y)
584
{
585
    double tmp = *x;
586
    *x = *y;
587
    *y = tmp;
588
}
589
590
// Parses gain map metadata from XMP.
591
// See https://developer.android.com/media/platform/hdr-image-format
592
// Returns AVIF_TRUE if the gain map metadata was successfully read.
593
static avifBool avifJPEGParseGainMapXMPProperties(const xmlNode * rootNode, avifGainMap * gainMap)
594
{
595
    const xmlNode * descNode = avifJPEGFindGainMapXMPNode(rootNode);
596
    if (descNode == NULL) {
597
        return AVIF_FALSE;
598
    }
599
600
    double baseHdrHeadroom = 0.0;
601
    double alternateHdrHeadroom = 1.0;
602
    double gainMapMin[3] = { 0.0, 0.0, 0.0 };
603
    double gainMapMax[3] = { 1.0, 1.0, 1.0 };
604
    double gainMapGamma[3] = { 1.0, 1.0, 1.0 };
605
    double baseOffset[3] = { 1.0 / 64.0, 1.0 / 64.0, 1.0 / 64.0 };
606
    double alternateOffset[3] = { 1.0 / 64.0, 1.0 / 64.0, 1.0 / 64.0 };
607
    AVIF_CHECK(avifJPEGFindGainMapPropertyDoubles(descNode, "HDRCapacityMin", &baseHdrHeadroom, /*numDoubles=*/1));
608
    AVIF_CHECK(avifJPEGFindGainMapPropertyDoubles(descNode, "HDRCapacityMax", &alternateHdrHeadroom, /*numDoubles=*/1));
609
    AVIF_CHECK(avifJPEGFindGainMapPropertyDoubles(descNode, "OffsetSDR", baseOffset, /*numDoubles=*/3));
610
    AVIF_CHECK(avifJPEGFindGainMapPropertyDoubles(descNode, "OffsetHDR", alternateOffset, /*numDoubles=*/3));
611
    AVIF_CHECK(avifJPEGFindGainMapPropertyDoubles(descNode, "GainMapMin", gainMapMin, /*numDoubles=*/3));
612
    AVIF_CHECK(avifJPEGFindGainMapPropertyDoubles(descNode, "GainMapMax", gainMapMax, /*numDoubles=*/3));
613
    AVIF_CHECK(avifJPEGFindGainMapPropertyDoubles(descNode, "Gamma", gainMapGamma, /*numDoubles=*/3));
614
615
    AVIF_CHECK(alternateHdrHeadroom > baseHdrHeadroom);
616
    AVIF_CHECK(baseHdrHeadroom >= 0);
617
    for (int i = 0; i < 3; ++i) {
618
        AVIF_CHECK(gainMapMax[i] >= gainMapMin[i]);
619
        AVIF_CHECK(baseOffset[i] >= 0.0);
620
        AVIF_CHECK(alternateOffset[i] >= 0.0);
621
        AVIF_CHECK(gainMapGamma[i] > 0.0);
622
    }
623
624
    uint32_t numValues;
625
    const char * baseRenditionIsHDR;
626
    if (avifJPEGFindGainMapProperty(descNode, "BaseRenditionIsHDR", /*maxValues=*/1, &baseRenditionIsHDR, &numValues)) {
627
        if (!strcmp(baseRenditionIsHDR, "True")) {
628
            SwapDoubles(&baseHdrHeadroom, &alternateHdrHeadroom);
629
            for (int c = 0; c < 3; ++c) {
630
                SwapDoubles(&baseOffset[c], &alternateOffset[c]);
631
            }
632
        } else if (!strcmp(baseRenditionIsHDR, "False")) {
633
        } else {
634
            return AVIF_FALSE; // Unexpected value.
635
        }
636
    }
637
638
    for (int i = 0; i < 3; ++i) {
639
        AVIF_CHECK(avifDoubleToSignedFraction(gainMapMin[i], &gainMap->gainMapMin[i]));
640
        AVIF_CHECK(avifDoubleToSignedFraction(gainMapMax[i], &gainMap->gainMapMax[i]));
641
        AVIF_CHECK(avifDoubleToUnsignedFraction(gainMapGamma[i], &gainMap->gainMapGamma[i]));
642
        AVIF_CHECK(avifDoubleToSignedFraction(baseOffset[i], &gainMap->baseOffset[i]));
643
        AVIF_CHECK(avifDoubleToSignedFraction(alternateOffset[i], &gainMap->alternateOffset[i]));
644
    }
645
    AVIF_CHECK(avifDoubleToUnsignedFraction(baseHdrHeadroom, &gainMap->baseHdrHeadroom));
646
    AVIF_CHECK(avifDoubleToUnsignedFraction(alternateHdrHeadroom, &gainMap->alternateHdrHeadroom));
647
    // Not in the XMP metadata but both color spaces should be the same so this value doesn't matter.
648
    gainMap->useBaseColorSpace = AVIF_TRUE;
649
650
    return AVIF_TRUE;
651
}
652
653
// Parses gain map metadata from an XMP payload.
654
// Returns AVIF_TRUE if the gain map metadata was successfully read.
655
avifBool avifJPEGParseGainMapXMP(const uint8_t * xmpData, size_t xmpSize, avifGainMap * gainMap)
656
{
657
    xmlDoc * document = xmlReadMemory((const char *)xmpData, (int)xmpSize, NULL, NULL, /*options=*/0);
658
    if (document == NULL) {
659
        return AVIF_FALSE; // Probably an out of memory error.
660
    }
661
    xmlNode * rootNode = xmlDocGetRootElement(document);
662
    const avifBool res = avifJPEGParseGainMapXMPProperties(rootNode, gainMap);
663
    xmlFreeDoc(document);
664
    return res;
665
}
666
667
// Parses an MPF (Multi-Picture File) JPEG metadata segment to find the location of other
668
// images, and decodes the gain map image (as determined by having gain map XMP metadata) into 'avif'.
669
// See CIPA DC-007-Translation-2021 Multi-Picture Format at https://www.cipa.jp/e/std/std-sec.html,
670
// (in particular Figures 1 to 6) and https://developer.android.com/media/platform/hdr-image-format.
671
// Returns AVIF_FALSE if no gain map was found.
672
static avifBool avifJPEGExtractGainMapImageFromMpf(FILE * f,
673
                                                   uint32_t sizeLimit,
674
                                                   const avifROData * segmentData,
675
                                                   avifImage * avif,
676
                                                   avifChromaDownsampling chromaDownsampling)
677
{
678
    uint32_t offset = 0;
679
680
    const uint8_t littleEndian[4] = { 0x49, 0x49, 0x2A, 0x00 }; // "II*\0"
681
    const uint8_t bigEndian[4] = { 0x4D, 0x4D, 0x00, 0x2A };    // "MM\0*"
682
683
    uint8_t endiannessTag[4];
684
    AVIF_CHECK(avifJPEGReadBytes(segmentData, endiannessTag, &offset, 4));
685
686
    avifBool isBigEndian;
687
    if (!memcmp(endiannessTag, bigEndian, 4)) {
688
        isBigEndian = AVIF_TRUE;
689
    } else if (!memcmp(endiannessTag, littleEndian, 4)) {
690
        isBigEndian = AVIF_FALSE;
691
    } else {
692
        return AVIF_FALSE; // Invalid endianness tag.
693
    }
694
695
    uint32_t offsetToFirstIfd;
696
    AVIF_CHECK(avifJPEGReadU32(segmentData, &offsetToFirstIfd, &offset, isBigEndian));
697
    if (offsetToFirstIfd < offset) {
698
        return AVIF_FALSE;
699
    }
700
    offset = offsetToFirstIfd;
701
702
    // Read MP (Multi-Picture) tags.
703
    uint16_t mpTagCount;
704
    AVIF_CHECK(avifJPEGReadU16(segmentData, &mpTagCount, &offset, isBigEndian));
705
706
    // See also https://www.media.mit.edu/pia/Research/deepview/exif.html
707
    uint32_t numImages = 0;
708
    uint32_t mpEntryOffset = 0;
709
    for (int mpTagIdx = 0; mpTagIdx < mpTagCount; ++mpTagIdx) {
710
        uint16_t tagId;
711
        AVIF_CHECK(avifJPEGReadU16(segmentData, &tagId, &offset, isBigEndian));
712
        if (UINT32_MAX - offset < 2 + 4) {
713
            return AVIF_FALSE;
714
        }
715
        offset += 2; // Skip data format.
716
        offset += 4; // Skip num components.
717
        uint8_t valueBytes[4];
718
        AVIF_CHECK(avifJPEGReadBytes(segmentData, valueBytes, &offset, 4));
719
        const uint32_t value = isBigEndian ? avifJPEGReadUint32BigEndian(valueBytes) : avifJPEGReadUint32LittleEndian(valueBytes);
720
721
        switch (tagId) { // MPFVersion
722
            case 45056:  // MPFVersion
723
                if (memcmp(valueBytes, "0100", 4)) {
724
                    // Unexpected version.
725
                    return AVIF_FALSE;
726
                }
727
                break;
728
            case 45057: // NumberOfImages
729
                numImages = value;
730
                break;
731
            case 45058: // MPEntry
732
                mpEntryOffset = value;
733
                break;
734
            case 45059: // ImageUIDList, unused
735
            case 45060: // TotalFrames, unused
736
            default:
737
                break;
738
        }
739
    }
740
    if (numImages < 2 || mpEntryOffset < offset) {
741
        return AVIF_FALSE;
742
    }
743
    offset = mpEntryOffset;
744
745
    uint32_t mpfSegmentOffset;
746
    AVIF_CHECK(avifJPEGFindMpfSegmentOffset(f, &mpfSegmentOffset));
747
748
    for (uint32_t imageIdx = 0; imageIdx < numImages; ++imageIdx) {
749
        if (UINT32_MAX - offset < 4) {
750
            return AVIF_FALSE;
751
        }
752
        offset += 4; // Skip "Individual Image Attribute"
753
        uint32_t imageSize;
754
        AVIF_CHECK(avifJPEGReadU32(segmentData, &imageSize, &offset, isBigEndian));
755
        uint32_t imageDataOffset;
756
        AVIF_CHECK(avifJPEGReadU32(segmentData, &imageDataOffset, &offset, isBigEndian));
757
758
        if (UINT32_MAX - offset < 4) {
759
            return AVIF_FALSE;
760
        }
761
        offset += 4; // Skip "Dependent image Entry Number" (2 + 2 bytes)
762
        if (imageDataOffset == 0) {
763
            // 0 is a special value which indicates the first image.
764
            // Assume the first image cannot be the gain map and skip it.
765
            continue;
766
        }
767
768
        // Offsets are relative to the start of the MPF segment. Make them absolute.
769
        imageDataOffset += mpfSegmentOffset;
770
        if (fseek(f, imageDataOffset, SEEK_SET) != 0) {
771
            return AVIF_FALSE;
772
        }
773
        // Read the image and check its XMP to see if it's a gain map.
774
        // NOTE we decode all additional images until a gain map is found, even if some might not
775
        // be gain maps. This could be fixed by having a helper function to get just the XMP without
776
        // decoding the whole image.
777
        if (!avifJPEGReadInternal(f,
778
                                  "gain map",
779
                                  avif,
780
                                  /*requestedFormat=*/AVIF_PIXEL_FORMAT_NONE, // automatic
781
                                  /*requestedDepth=*/0,                       // automatic
782
                                  chromaDownsampling,
783
                                  /*ignoreColorProfile=*/AVIF_TRUE,
784
                                  /*ignoreExif=*/AVIF_TRUE,
785
                                  /*ignoreXMP=*/AVIF_FALSE,
786
                                  /*ignoreGainMap=*/AVIF_TRUE,
787
                                  sizeLimit)) {
788
            continue;
789
        }
790
        if (avifJPEGHasGainMapXMPNode(avif->xmp.data, avif->xmp.size)) {
791
            return AVIF_TRUE;
792
        }
793
    }
794
795
    return AVIF_FALSE;
796
}
797
798
// Tries to find and decode a gain map image and its metadata.
799
// Looks for an MPF (Multi-Picture Format) segment then loops through the linked images to see
800
// if one of them has gain map XMP metadata.
801
// See CIPA DC-007-Translation-2021 Multi-Picture Format at https://www.cipa.jp/e/std/std-sec.html
802
// and https://developer.android.com/media/platform/hdr-image-format
803
// Returns AVIF_TRUE if a gain map was found.
804
static avifBool avifJPEGExtractGainMapImage(FILE * f,
805
                                            uint32_t sizeLimit,
806
                                            struct jpeg_decompress_struct * cinfo,
807
                                            avifGainMap * gainMap,
808
                                            avifChromaDownsampling chromaDownsampling)
809
{
810
    const avifROData tagMpf = { (const uint8_t *)AVIF_JPEG_MPF_HEADER, AVIF_JPEG_MPF_HEADER_LENGTH };
811
    for (jpeg_saved_marker_ptr marker = cinfo->marker_list; marker != NULL; marker = marker->next) {
812
        // Note we assume there is only one MPF segment and only look at the first one.
813
        // Otherwise avifJPEGFindMpfSegmentOffset() would have to be modified to take the index of the
814
        // MPF segment whose offset to return.
815
        if ((marker->marker == (JPEG_APP0 + 2)) && (marker->data_length > tagMpf.size) &&
816
            !memcmp(marker->data, tagMpf.data, tagMpf.size)) {
817
            avifImage * image = avifImageCreateEmpty();
818
            // Set jpeg native matrix coefficients to allow copying YUV values directly.
819
            image->matrixCoefficients = AVIF_MATRIX_COEFFICIENTS_BT601;
820
            assert(avifJPEGHasCompatibleMatrixCoefficients(image->matrixCoefficients));
821
822
            const avifROData mpfData = { (const uint8_t *)marker->data + tagMpf.size, marker->data_length - tagMpf.size };
823
            if (!avifJPEGExtractGainMapImageFromMpf(f, sizeLimit, &mpfData, image, chromaDownsampling)) {
824
                if (f == stdin) {
825
                    // Not supported because fseek doesn't work on stdin.
826
                    fprintf(stderr, "Warning: gain map transcoding is not supported with sdtin\n");
827
                } else {
828
                    fprintf(stderr, "Note: XMP metadata indicated the presence of a gain map, but it could not be found or decoded\n");
829
                }
830
                avifImageDestroy(image);
831
                return AVIF_FALSE;
832
            }
833
            if (!avifJPEGParseGainMapXMP(image->xmp.data, image->xmp.size, gainMap)) {
834
                fprintf(stderr, "Warning: failed to parse gain map metadata\n");
835
                avifImageDestroy(image);
836
                return AVIF_FALSE;
837
            }
838
839
            gainMap->image = image;
840
            return AVIF_TRUE;
841
        }
842
    }
843
    return AVIF_FALSE;
844
}
845
846
// Merges the standard XMP data with the extended XMP data.
847
// Returns AVIF_FALSE if an error occurred.
848
static avifBool avifJPEGMergeXMP(const uint8_t * standardXMPData,
849
                                 uint32_t standardXMPSize,
850
                                 const avifRWData extendedXMP,
851
                                 avifBool foundAlternativeXMPNote,
852
                                 avifRWData * xmp)
853
{
854
    // Initialize the XMP RDF.
855
    avifBool isValid = AVIF_TRUE;
856
    xmlDoc * extendedXMPDoc = NULL;
857
    xmlChar * xmlBuff = NULL;
858
    xmlDoc * xmpDoc = xmlReadMemory((const char *)standardXMPData, (int)standardXMPSize, "standard.xml", NULL, /*options=*/0);
859
    xmlNode * xmpRdf = (xmlNode *)avifJPEGFindXMLNodeByName(xmlDocGetRootElement(xmpDoc),
860
                                                            XML_NAME_SPACE_RDF,
861
                                                            "RDF",
862
                                                            /*recursive=*/AVIF_TRUE);
863
    if (!xmpRdf) {
864
        fprintf(stderr, "XMP extraction failed: cannot find RDF node\n");
865
        isValid = AVIF_FALSE;
866
        goto cleanup_xml;
867
    }
868
    // According to Adobe XMP Specification Part 3 section 1.1.3.1:
869
    //   "A JPEG reader must [...] remove the xmpNote:HasExtendedXMP property."
870
    avifBool foundHasExtendedXMP = AVIF_FALSE;
871
    xmlNode * descNode = xmpRdf->children;
872
    while (!foundHasExtendedXMP && descNode != NULL) {
873
        if (descNode->type == XML_ELEMENT_NODE && descNode->ns != NULL &&
874
            xmlStrcmp(descNode->ns->href, (const xmlChar *)XML_NAME_SPACE_RDF) == 0 &&
875
            xmlStrcmp(descNode->name, (const xmlChar *)"Description") == 0) {
876
            // Remove the HasExtendedXMP property.
877
            if (foundAlternativeXMPNote) {
878
                xmlNodePtr cur = descNode->children;
879
                while (cur != NULL) {
880
                    if (cur->type == XML_ELEMENT_NODE && cur->ns != NULL && xmlStrcmp(cur->name, (const xmlChar *)"HasExtendedXMP") == 0 &&
881
                        xmlStrcmp(cur->ns->href, (const xmlChar *)XML_NAME_SPACE_XMP_NOTE) == 0) {
882
                        // We must Unlink and Free the node.
883
                        xmlUnlinkNode(cur);
884
                        xmlFreeNode(cur);
885
                        foundHasExtendedXMP = AVIF_TRUE;
886
                        break;
887
                    }
888
                    cur = cur->next;
889
                }
890
            } else {
891
                xmlAttrPtr attr = xmlHasNsProp(descNode, (const xmlChar *)"HasExtendedXMP", (const xmlChar *)XML_NAME_SPACE_XMP_NOTE);
892
893
                if (attr) {
894
                    xmlRemoveProp(attr);
895
                    foundHasExtendedXMP = AVIF_TRUE;
896
                    break;
897
                }
898
            }
899
        }
900
        // Check next sibling in case there are multiple Descriptions.
901
        descNode = descNode->next;
902
    }
903
    if (!foundHasExtendedXMP) {
904
        fprintf(stderr, "XMP extraction failed: cannot find HasExtendedXMP property\n");
905
        isValid = AVIF_FALSE;
906
        goto cleanup_xml;
907
    }
908
909
    // Read the extended XMP.
910
    extendedXMPDoc = xmlReadMemory((const char *)extendedXMP.data,
911
                                   (int)extendedXMP.size,
912
                                   "extended.xml",
913
                                   NULL,
914
                                   /*options=*/0);
915
    const xmlNode * extendedXMPRdf = avifJPEGFindXMLNodeByName(xmlDocGetRootElement(extendedXMPDoc),
916
                                                               XML_NAME_SPACE_RDF,
917
                                                               "RDF",
918
                                                               /*recursive=*/AVIF_TRUE);
919
    if (!extendedXMPRdf) {
920
        fprintf(stderr, "XMP extraction failed: invalid standard XMP segment\n");
921
        isValid = AVIF_FALSE;
922
        goto cleanup_xml;
923
    }
924
    // Copy the extended nodes over.
925
    xmlNode * cur = extendedXMPRdf->xmlChildrenNode;
926
    while (cur != NULL) {
927
        // Copy the child.
928
        xmlNode * childCopy = xmlDocCopyNode(cur, xmpDoc, 1);
929
        xmlAddChild(xmpRdf, childCopy);
930
        cur = cur->next;
931
    }
932
933
    // Dump the new XMP to avif->xmp.
934
    int buffer_size;
935
    xmlDocDumpFormatMemory(xmpDoc, &xmlBuff, &buffer_size, 1);
936
    if (xmlBuff == NULL) {
937
        fprintf(stderr, "Error: Could not dump XML to memory.\n");
938
        isValid = AVIF_FALSE;
939
        goto cleanup_xml;
940
    }
941
942
    avifRWDataFree(xmp);
943
    if (avifRWDataRealloc(xmp, (size_t)buffer_size) != AVIF_RESULT_OK) {
944
        fprintf(stderr, "XMP copy failed: out of memory\n");
945
        isValid = AVIF_FALSE;
946
        goto cleanup_xml;
947
    }
948
    memcpy(xmp->data, xmlBuff, buffer_size);
949
cleanup_xml:
950
    xmlFreeDoc(xmpDoc);
951
    xmlFreeDoc(extendedXMPDoc);
952
    xmlFree(xmlBuff);
953
    return isValid;
954
}
955
956
#endif // AVIF_ENABLE_JPEG_GAIN_MAP_CONVERSION
957
958
// Note on setjmp() and volatile variables:
959
//
960
// K & R, The C Programming Language 2nd Ed, p. 254 says:
961
//   ... Accessible objects have the values they had when longjmp was called,
962
//   except that non-volatile automatic variables in the function calling setjmp
963
//   become undefined if they were changed after the setjmp call.
964
//
965
// Therefore, 'iccData' is declared as volatile. 'rgb' should be declared as
966
// volatile, but doing so would be inconvenient (try it) and since it is a
967
// struct, the compiler is unlikely to put it in a register. 'ret' does not need
968
// to be declared as volatile because it is not modified between setjmp and
969
// longjmp. But GCC's -Wclobbered warning may have trouble figuring that out, so
970
// we preemptively declare it as volatile.
971
972
static avifBool avifJPEGReadInternal(FILE * f,
973
                                     const char * inputFilename,
974
                                     avifImage * avif,
975
                                     avifPixelFormat requestedFormat,
976
                                     uint32_t requestedDepth,
977
                                     avifChromaDownsampling chromaDownsampling,
978
                                     avifBool ignoreColorProfile,
979
                                     avifBool ignoreExif,
980
                                     avifBool ignoreXMP,
981
                                     avifBool ignoreGainMap,
982
                                     uint32_t sizeLimit)
983
4.99k
{
984
4.99k
    volatile avifBool ret = AVIF_FALSE;
985
4.99k
    uint8_t * volatile iccData = NULL;
986
987
4.99k
    avifRGBImage rgb;
988
4.99k
    memset(&rgb, 0, sizeof(avifRGBImage));
989
990
    // Extended XMP after concatenation of all extended XMP segments.
991
4.99k
    avifRWData extendedXMP = { NULL, 0 };
992
    // Each byte set to 0 is a missing byte. Each byte set to 1 was read and copied to totalXMP.
993
4.99k
    avifRWData extendedXMPReadBytes = { NULL, 0 };
994
995
4.99k
    struct my_error_mgr jerr;
996
4.99k
    struct jpeg_decompress_struct cinfo;
997
4.99k
    cinfo.err = jpeg_std_error(&jerr.pub);
998
4.99k
    jerr.pub.error_exit = my_error_exit;
999
4.99k
    if (setjmp(jerr.setjmp_buffer)) {
1000
2.70k
        goto cleanup;
1001
2.70k
    }
1002
1003
4.99k
    jpeg_create_decompress(&cinfo);
1004
1005
    // See also https://exiftool.org/TagNames/JPEG.html for the meaning of various APP<n> segments.
1006
4.37k
    if (!ignoreExif || !ignoreXMP || !ignoreGainMap) {
1007
        // Keep APP1 blocks, for Exif and XMP.
1008
4.37k
        jpeg_save_markers(&cinfo, JPEG_APP0 + 1, /*length_limit=*/0xFFFF);
1009
4.37k
    }
1010
2.45k
    if (!ignoreGainMap) {
1011
        // Keep APP2 blocks, for obtaining ICC and MPF data.
1012
2.45k
        jpeg_save_markers(&cinfo, JPEG_APP0 + 2, /*length_limit=*/0xFFFF);
1013
2.45k
    }
1014
1015
2.57k
    if (!ignoreColorProfile) {
1016
2.57k
        setup_read_icc_profile(&cinfo);
1017
2.57k
    }
1018
2.29k
    jpeg_stdio_src(&cinfo, f);
1019
2.29k
    jpeg_read_header(&cinfo, TRUE);
1020
1021
2.29k
    jpeg_calc_output_dimensions(&cinfo);
1022
2.29k
    if (cinfo.output_width > sizeLimit / cinfo.output_height) {
1023
18
        fprintf(stderr, "Too big JPEG dimensions (%u x %u > %u px): %s\n", cinfo.output_width, cinfo.output_height, sizeLimit, inputFilename);
1024
18
        goto cleanup;
1025
18
    }
1026
1027
2.27k
    if (!ignoreColorProfile) {
1028
2.03k
        uint8_t * iccDataTmp;
1029
2.03k
        unsigned int iccDataLen;
1030
2.03k
        if (read_icc_profile(&cinfo, &iccDataTmp, &iccDataLen)) {
1031
87
            iccData = iccDataTmp;
1032
87
            const avifBool isGray = (cinfo.jpeg_color_space == JCS_GRAYSCALE);
1033
87
            if (!isGray && (requestedFormat == AVIF_PIXEL_FORMAT_YUV400)) {
1034
17
                fprintf(stderr,
1035
17
                        "The image contains a color ICC profile which is incompatible with the requested output "
1036
17
                        "format YUV400 (grayscale). Pass --ignore-icc to discard the ICC profile.\n");
1037
17
                goto cleanup;
1038
17
            }
1039
70
            if (isGray && requestedFormat != AVIF_PIXEL_FORMAT_YUV400) {
1040
1
                fprintf(stderr,
1041
1
                        "The image contains a gray ICC profile which is incompatible with the requested output "
1042
1
                        "format YUV (color). Pass --ignore-icc to discard the ICC profile.\n");
1043
1
                goto cleanup;
1044
1
            }
1045
69
            if (avifImageSetProfileICC(avif, iccDataTmp, (size_t)iccDataLen) != AVIF_RESULT_OK) {
1046
0
                fprintf(stderr, "Setting ICC profile failed: %s (out of memory)\n", inputFilename);
1047
0
                goto cleanup;
1048
0
            }
1049
69
        }
1050
2.03k
    }
1051
1052
2.25k
    avif->yuvFormat = requestedFormat; // This may be AVIF_PIXEL_FORMAT_NONE, which is "auto" to avifJPEGReadCopy()
1053
18.4E
    avif->depth = requestedDepth ? requestedDepth : 8;
1054
    // JPEG doesn't have alpha. Prevent confusion.
1055
2.25k
    avif->alphaPremultiplied = AVIF_FALSE;
1056
1057
2.25k
    if (avifJPEGReadCopy(avif, sizeLimit, &cinfo)) {
1058
        // JPEG pixels were successfully copied without conversion. Notify the enduser.
1059
1060
106
        assert(inputFilename); // JPEG read doesn't support stdin
1061
106
        printf("Directly copied JPEG pixel data (no YUV conversion): %s\n", inputFilename);
1062
2.15k
    } else {
1063
        // JPEG pixels could not be copied without conversion. Request (converted) RGB pixels from
1064
        // libjpeg and convert to YUV with libavif instead.
1065
1066
2.15k
        cinfo.out_color_space = JCS_RGB;
1067
2.15k
        jpeg_start_decompress(&cinfo);
1068
1069
2.15k
        int row_stride = cinfo.output_width * cinfo.output_components;
1070
2.15k
        JSAMPARRAY buffer = (*cinfo.mem->alloc_sarray)((j_common_ptr)&cinfo, JPOOL_IMAGE, row_stride, 1);
1071
1072
2.15k
        avif->width = cinfo.output_width;
1073
2.15k
        avif->height = cinfo.output_height;
1074
2.15k
        if (avif->matrixCoefficients == AVIF_MATRIX_COEFFICIENTS_YCGCO_RO) {
1075
0
            fprintf(stderr, "AVIF_MATRIX_COEFFICIENTS_YCGCO_RO cannot be used with JPEG because it has an even bit depth.\n");
1076
0
            goto cleanup;
1077
0
        }
1078
2.15k
        if (avif->yuvFormat == AVIF_PIXEL_FORMAT_NONE) {
1079
            // Identity and YCgCo-R are only valid with YUV444.
1080
0
            avif->yuvFormat = (avif->matrixCoefficients == AVIF_MATRIX_COEFFICIENTS_IDENTITY ||
1081
0
                               avif->matrixCoefficients == AVIF_MATRIX_COEFFICIENTS_YCGCO_RE)
1082
0
                                  ? AVIF_PIXEL_FORMAT_YUV444
1083
0
                                  : AVIF_APP_DEFAULT_PIXEL_FORMAT;
1084
0
        }
1085
18.4E
        avif->depth = requestedDepth ? requestedDepth : 8;
1086
2.15k
        if (avif->matrixCoefficients == AVIF_MATRIX_COEFFICIENTS_YCGCO_RE) {
1087
0
            if (requestedDepth && requestedDepth != 10) {
1088
0
                fprintf(stderr, "Cannot request %u bits for YCgCo-Re as it uses 2 extra bits.\n", requestedDepth);
1089
0
                goto cleanup;
1090
0
            }
1091
0
            avif->depth = 10;
1092
0
        }
1093
2.15k
        avifRGBImageSetDefaults(&rgb, avif);
1094
2.15k
        rgb.format = AVIF_RGB_FORMAT_RGB;
1095
2.15k
        rgb.chromaDownsampling = chromaDownsampling;
1096
2.15k
        rgb.depth = 8;
1097
2.15k
        if (avifRGBImageAllocatePixels(&rgb) != AVIF_RESULT_OK) {
1098
0
            fprintf(stderr, "Conversion to YUV failed: %s (out of memory)\n", inputFilename);
1099
0
            goto cleanup;
1100
0
        }
1101
1102
2.15k
        int row = 0;
1103
27.1M
        while (cinfo.output_scanline < cinfo.output_height) {
1104
27.1M
            jpeg_read_scanlines(&cinfo, buffer, 1);
1105
27.1M
            uint8_t * pixelRow = &rgb.pixels[row * rgb.rowBytes];
1106
27.1M
            memcpy(pixelRow, buffer[0], rgb.rowBytes);
1107
27.1M
            ++row;
1108
27.1M
        }
1109
2.15k
        if (avifImageRGBToYUV(avif, &rgb) != AVIF_RESULT_OK) {
1110
208
            fprintf(stderr, "Conversion to YUV failed: %s\n", inputFilename);
1111
208
            goto cleanup;
1112
208
        }
1113
2.15k
    }
1114
1115
2.05k
    if (!ignoreExif) {
1116
1.12k
        avifBool found = AVIF_FALSE;
1117
2.86k
        for (jpeg_saved_marker_ptr marker = cinfo.marker_list; marker != NULL; marker = marker->next) {
1118
1.74k
            if ((marker->marker == (JPEG_APP0 + 1)) && (marker->data_length > AVIF_JPEG_EXIF_HEADER_LENGTH) &&
1119
777
                !memcmp(marker->data, AVIF_JPEG_EXIF_HEADER, AVIF_JPEG_EXIF_HEADER_LENGTH)) {
1120
174
                if (found) {
1121
                    // TODO(yguyon): Implement instead of outputting an error.
1122
0
                    fprintf(stderr, "Exif extraction failed: unsupported Exif split into multiple segments or invalid multiple Exif segments\n");
1123
0
                    goto cleanup;
1124
0
                }
1125
1126
174
                if (marker->data_length - AVIF_JPEG_EXIF_HEADER_LENGTH > sizeLimit) {
1127
0
                    fprintf(stderr,
1128
0
                            "Setting Exif metadata failed: Exif size is too large (%u > %u bytes): %s\n",
1129
0
                            marker->data_length - AVIF_JPEG_EXIF_HEADER_LENGTH,
1130
0
                            sizeLimit,
1131
0
                            inputFilename);
1132
0
                    goto cleanup;
1133
0
                }
1134
1135
                // Exif orientation, if any, is imported to avif->irot/imir and kept in avif->exif.
1136
                // libheif has the same behavior, see
1137
                // https://github.com/strukturag/libheif/blob/ea78603d8e47096606813d221725621306789ff2/examples/heif_enc.cc#L403
1138
174
                if (avifImageSetMetadataExif(avif,
1139
174
                                             marker->data + AVIF_JPEG_EXIF_HEADER_LENGTH,
1140
174
                                             marker->data_length - AVIF_JPEG_EXIF_HEADER_LENGTH) != AVIF_RESULT_OK) {
1141
0
                    fprintf(stderr, "Setting Exif metadata failed: %s (out of memory)\n", inputFilename);
1142
0
                    goto cleanup;
1143
0
                }
1144
174
                found = AVIF_TRUE;
1145
174
            }
1146
1.74k
        }
1147
1.12k
    }
1148
1149
2.05k
    avifBool readXMP = !ignoreXMP;
1150
#if defined(AVIF_ENABLE_JPEG_GAIN_MAP_CONVERSION)
1151
    readXMP = readXMP || !ignoreGainMap; // Gain map metadata is in XMP.
1152
#endif
1153
2.05k
    if (readXMP) {
1154
1.13k
        const uint8_t * standardXMPData = NULL;
1155
1.13k
        uint32_t standardXMPSize = 0; // At most 64kB as defined by Adobe XMP Specification Part 3.
1156
2.70k
        for (jpeg_saved_marker_ptr marker = cinfo.marker_list; marker != NULL; marker = marker->next) {
1157
1.56k
            if ((marker->marker == (JPEG_APP0 + 1)) && (marker->data_length > AVIF_JPEG_STANDARD_XMP_TAG_LENGTH) &&
1158
818
                !memcmp(marker->data, AVIF_JPEG_STANDARD_XMP_TAG, AVIF_JPEG_STANDARD_XMP_TAG_LENGTH)) {
1159
95
                if (standardXMPData) {
1160
1
                    fprintf(stderr, "XMP extraction failed: invalid multiple standard XMP segments\n");
1161
1
                    goto cleanup;
1162
1
                }
1163
94
                standardXMPData = marker->data + AVIF_JPEG_STANDARD_XMP_TAG_LENGTH;
1164
94
                standardXMPSize = (uint32_t)(marker->data_length - AVIF_JPEG_STANDARD_XMP_TAG_LENGTH);
1165
94
            }
1166
1.56k
        }
1167
1168
1.13k
        avifBool foundExtendedXMP = AVIF_FALSE;
1169
1.13k
        uint8_t extendedXMPGUID[AVIF_JPEG_EXTENDED_XMP_GUID_LENGTH]; // The value is common to all extended XMP segments.
1170
2.62k
        for (jpeg_saved_marker_ptr marker = cinfo.marker_list; marker != NULL; marker = marker->next) {
1171
1.55k
            if ((marker->marker == (JPEG_APP0 + 1)) && (marker->data_length > AVIF_JPEG_EXTENDED_XMP_TAG_LENGTH) &&
1172
778
                !memcmp(marker->data, AVIF_JPEG_EXTENDED_XMP_TAG, AVIF_JPEG_EXTENDED_XMP_TAG_LENGTH)) {
1173
98
                if (!standardXMPData) {
1174
8
                    fprintf(stderr, "XMP extraction failed: extended XMP segment found, missing standard XMP segment\n");
1175
8
                    goto cleanup;
1176
8
                }
1177
1178
90
                if (marker->data_length < AVIF_JPEG_OFFSET_TILL_EXTENDED_XMP) {
1179
0
                    fprintf(stderr, "XMP extraction failed: truncated extended XMP segment\n");
1180
0
                    goto cleanup;
1181
0
                }
1182
90
                const uint8_t * guid = &marker->data[AVIF_JPEG_EXTENDED_XMP_TAG_LENGTH];
1183
2.43k
                for (size_t c = 0; c < AVIF_JPEG_EXTENDED_XMP_GUID_LENGTH; ++c) {
1184
                    // According to Adobe XMP Specification Part 3 section 1.1.3.1:
1185
                    //   "128-bit GUID stored as a 32-byte ASCII hex string, capital A-F, no null termination"
1186
                    // Also allow lowercase since some cameras use lowercase. https://github.com/AOMediaCodec/libavif/issues/2755
1187
2.37k
                    if (!isxdigit(guid[c])) {
1188
33
                        fprintf(stderr, "XMP extraction failed: invalid XMP segment GUID\n");
1189
33
                        goto cleanup;
1190
33
                    }
1191
2.37k
                }
1192
                // Size of the current extended segment.
1193
57
                const size_t extendedXMPSize = marker->data_length - AVIF_JPEG_OFFSET_TILL_EXTENDED_XMP;
1194
                // Expected size of the sum of all extended segments.
1195
                // According to Adobe XMP Specification Part 3 section 1.1.3.1:
1196
                //   "full length of the ExtendedXMP serialization as a 32-bit unsigned integer"
1197
57
                const uint32_t totalExtendedXMPSize =
1198
57
                    avifJPEGReadUint32BigEndian(&marker->data[AVIF_JPEG_EXTENDED_XMP_TAG_LENGTH + AVIF_JPEG_EXTENDED_XMP_GUID_LENGTH]);
1199
                // Offset in totalXMP after standardXMP.
1200
                // According to Adobe XMP Specification Part 3 section 1.1.3.1:
1201
                //   "offset of this portion as a 32-bit unsigned integer"
1202
57
                const uint32_t extendedXMPOffset = avifJPEGReadUint32BigEndian(
1203
57
                    &marker->data[AVIF_JPEG_EXTENDED_XMP_TAG_LENGTH + AVIF_JPEG_EXTENDED_XMP_GUID_LENGTH + 4]);
1204
57
                if (((uint64_t)standardXMPSize + totalExtendedXMPSize) > SIZE_MAX ||
1205
57
                    ((uint64_t)standardXMPSize + totalExtendedXMPSize) > sizeLimit) {
1206
12
                    fprintf(stderr,
1207
12
                            "XMP extraction failed: total XMP size is too large (%u + %u > %u bytes): %s\n",
1208
12
                            standardXMPSize,
1209
12
                            totalExtendedXMPSize,
1210
12
                            sizeLimit,
1211
12
                            inputFilename);
1212
12
                    goto cleanup;
1213
12
                }
1214
45
                if ((extendedXMPSize == 0) || (((uint64_t)extendedXMPOffset + extendedXMPSize) > totalExtendedXMPSize)) {
1215
6
                    fprintf(stderr, "XMP extraction failed: invalid extended XMP segment size or offset\n");
1216
6
                    goto cleanup;
1217
6
                }
1218
39
                if (foundExtendedXMP) {
1219
14
                    if (memcmp(guid, extendedXMPGUID, AVIF_JPEG_EXTENDED_XMP_GUID_LENGTH)) {
1220
2
                        fprintf(stderr, "XMP extraction failed: extended XMP segment GUID mismatch\n");
1221
2
                        goto cleanup;
1222
2
                    }
1223
12
                    if (totalExtendedXMPSize != extendedXMP.size) {
1224
1
                        fprintf(stderr, "XMP extraction failed: extended XMP total size mismatch\n");
1225
1
                        goto cleanup;
1226
1
                    }
1227
25
                } else {
1228
25
                    memcpy(extendedXMPGUID, guid, AVIF_JPEG_EXTENDED_XMP_GUID_LENGTH);
1229
1230
                    // Allocate the extended XMP and keep track of the bytes that were set.
1231
25
                    if (avifRWDataRealloc(&extendedXMP, (size_t)totalExtendedXMPSize) != AVIF_RESULT_OK ||
1232
25
                        avifRWDataRealloc(&extendedXMPReadBytes, totalExtendedXMPSize) != AVIF_RESULT_OK) {
1233
0
                        fprintf(stderr, "XMP extraction failed: out of memory\n");
1234
0
                        goto cleanup;
1235
0
                    }
1236
25
                    memset(extendedXMPReadBytes.data, 0, extendedXMPReadBytes.size);
1237
1238
25
                    foundExtendedXMP = AVIF_TRUE;
1239
25
                }
1240
                // According to Adobe XMP Specification Part 3 section 1.1.3.1:
1241
                //   "A robust JPEG reader should tolerate the marker segments in any order."
1242
36
                memcpy(&extendedXMP.data[extendedXMPOffset], &marker->data[AVIF_JPEG_OFFSET_TILL_EXTENDED_XMP], extendedXMPSize);
1243
1244
                // Make sure no previously read data was overwritten by the current segment.
1245
36
                if (memchr(&extendedXMPReadBytes.data[extendedXMPOffset], 1, extendedXMPSize)) {
1246
1
                    fprintf(stderr, "XMP extraction failed: overlapping extended XMP segments\n");
1247
1
                    goto cleanup;
1248
1
                }
1249
                // Keep track of the bytes that were set.
1250
35
                memset(&extendedXMPReadBytes.data[extendedXMPOffset], 1, extendedXMPSize);
1251
35
            }
1252
1.55k
        }
1253
1254
1.07k
        if (foundExtendedXMP) {
1255
            // Make sure there is no missing byte.
1256
21
            if (memchr(extendedXMPReadBytes.data, 0, extendedXMPReadBytes.size)) {
1257
5
                fprintf(stderr, "XMP extraction failed: missing extended XMP segments\n");
1258
5
                goto cleanup;
1259
5
            }
1260
1261
            // According to Adobe XMP Specification Part 3 section 1.1.3.1:
1262
            //   "A reader must incorporate only ExtendedXMP blocks whose GUID matches the value of xmpNote:HasExtendedXMP."
1263
16
            uint8_t xmpNote[AVIF_JPEG_XMP_NOTE_TAG_LENGTH + AVIF_JPEG_EXTENDED_XMP_GUID_LENGTH];
1264
16
            memcpy(xmpNote, AVIF_JPEG_XMP_NOTE_TAG, AVIF_JPEG_XMP_NOTE_TAG_LENGTH);
1265
16
            memcpy(xmpNote + AVIF_JPEG_XMP_NOTE_TAG_LENGTH, extendedXMPGUID, AVIF_JPEG_EXTENDED_XMP_GUID_LENGTH);
1266
16
            avifBool foundAlternativeXMPNote;
1267
16
            if (avifJPEGFindSubstr(standardXMPData, standardXMPSize, xmpNote, sizeof(xmpNote))) {
1268
8
                foundAlternativeXMPNote = AVIF_FALSE;
1269
8
            } else {
1270
                // Try the alternative before returning an error.
1271
8
                uint8_t alternativeXmpNote[AVIF_JPEG_ALTERNATIVE_XMP_NOTE_TAG_LENGTH + AVIF_JPEG_EXTENDED_XMP_GUID_LENGTH];
1272
8
                memcpy(alternativeXmpNote, AVIF_JPEG_ALTERNATIVE_XMP_NOTE_TAG, AVIF_JPEG_ALTERNATIVE_XMP_NOTE_TAG_LENGTH);
1273
8
                memcpy(alternativeXmpNote + AVIF_JPEG_ALTERNATIVE_XMP_NOTE_TAG_LENGTH, extendedXMPGUID, AVIF_JPEG_EXTENDED_XMP_GUID_LENGTH);
1274
8
                if (!avifJPEGFindSubstr(standardXMPData, standardXMPSize, alternativeXmpNote, sizeof(alternativeXmpNote))) {
1275
5
                    fprintf(stderr, "XMP extraction failed: standard and extended XMP GUID mismatch\n");
1276
5
                    goto cleanup;
1277
5
                }
1278
3
                foundAlternativeXMPNote = AVIF_TRUE;
1279
3
            }
1280
11
            (void)foundAlternativeXMPNote;
1281
1282
#if defined(AVIF_ENABLE_JPEG_GAIN_MAP_CONVERSION)
1283
            if (!avifJPEGMergeXMP(standardXMPData, standardXMPSize, extendedXMP, foundAlternativeXMPNote, &avif->xmp)) {
1284
                goto cleanup;
1285
            }
1286
#else
1287
11
            fprintf(stderr, "WARNING: must be compiled with libxml2 to copy extended XMP properly\n");
1288
11
            avifRWDataFree(&avif->xmp);
1289
11
            if (avifRWDataRealloc(&avif->xmp, (size_t)standardXMPSize + extendedXMP.size) != AVIF_RESULT_OK) {
1290
0
                fprintf(stderr, "XMP copy failed: out of memory\n");
1291
0
                goto cleanup;
1292
0
            }
1293
11
            memcpy(avif->xmp.data, standardXMPData, standardXMPSize);
1294
11
            memcpy(avif->xmp.data + standardXMPSize, extendedXMP.data, extendedXMP.size);
1295
11
#endif // AVIF_ENABLE_JPEG_GAIN_MAP_CONVERSION
1296
1.05k
        } else if (standardXMPData) {
1297
17
            if (avifImageSetMetadataXMP(avif, standardXMPData, standardXMPSize) != AVIF_RESULT_OK) {
1298
0
                fprintf(stderr, "XMP extraction failed: out of memory\n");
1299
0
                goto cleanup;
1300
0
            }
1301
17
        }
1302
1.06k
        avifImageFixXMP(avif); // Remove one trailing null character if any.
1303
1.06k
    }
1304
1305
#if defined(AVIF_ENABLE_JPEG_GAIN_MAP_CONVERSION)
1306
    // The primary XMP block (for the main image) must contain a node with an hdrgm:Version field if and only if a gain map is present.
1307
    if (!ignoreGainMap && avifJPEGHasGainMapXMPNode(avif->xmp.data, avif->xmp.size)) {
1308
        avifGainMap * gainMap = avifGainMapCreate();
1309
        if (gainMap == NULL) {
1310
            fprintf(stderr, "Creating gain map failed: out of memory\n");
1311
            goto cleanup;
1312
        }
1313
        // Ignore the return value: continue even if we fail to find/parse/decode the gain map.
1314
        if (avifJPEGExtractGainMapImage(f, sizeLimit, &cinfo, gainMap, chromaDownsampling)) {
1315
            // Since jpeg doesn't provide this metadata, assume the values are the same as the base image
1316
            // with a PQ transfer curve.
1317
            gainMap->altColorPrimaries = avif->colorPrimaries;
1318
            gainMap->altTransferCharacteristics = AVIF_TRANSFER_CHARACTERISTICS_PQ;
1319
            gainMap->altMatrixCoefficients = avif->matrixCoefficients;
1320
            gainMap->altDepth = 8;
1321
            gainMap->altPlaneCount =
1322
                (avif->yuvFormat == AVIF_PIXEL_FORMAT_YUV400 && gainMap->image->yuvFormat == AVIF_PIXEL_FORMAT_YUV400) ? 1 : 3;
1323
            if (avif->icc.size > 0) {
1324
                // The base image's ICC should also apply to the alternage image.
1325
                if (avifRWDataSet(&gainMap->altICC, avif->icc.data, avif->icc.size) != AVIF_RESULT_OK) {
1326
                    fprintf(stderr, "Setting gain map ICC profile failed: out of memory\n");
1327
                    goto cleanup;
1328
                }
1329
            }
1330
            avif->gainMap = gainMap;
1331
        } else {
1332
            avifGainMapDestroy(gainMap);
1333
        }
1334
    }
1335
1336
    if (avif->xmp.size > 0 && ignoreXMP) {
1337
        // Clear XMP in case we read it for something else (like gain map).
1338
        if (avifImageSetMetadataXMP(avif, NULL, 0) != AVIF_RESULT_OK) {
1339
            assert(AVIF_FALSE);
1340
        }
1341
    }
1342
#endif // AVIF_ENABLE_JPEG_GAIN_MAP_CONVERSION
1343
1.97k
    jpeg_finish_decompress(&cinfo);
1344
1.97k
    ret = AVIF_TRUE;
1345
4.99k
cleanup:
1346
4.99k
    jpeg_destroy_decompress(&cinfo);
1347
4.99k
    free(iccData);
1348
4.99k
    avifRGBImageFreePixels(&rgb);
1349
4.99k
    avifRWDataFree(&extendedXMP);
1350
4.99k
    avifRWDataFree(&extendedXMPReadBytes);
1351
4.99k
    return ret;
1352
1.97k
}
1353
1354
avifBool avifJPEGRead(const char * inputFilename,
1355
                      avifImage * avif,
1356
                      avifPixelFormat requestedFormat,
1357
                      uint32_t requestedDepth,
1358
                      avifChromaDownsampling chromaDownsampling,
1359
                      avifBool ignoreColorProfile,
1360
                      avifBool ignoreExif,
1361
                      avifBool ignoreXMP,
1362
                      avifBool ignoreGainMap,
1363
                      uint32_t sizeLimit)
1364
4.99k
{
1365
4.99k
    FILE * f;
1366
4.99k
    if (inputFilename) {
1367
4.99k
        f = fopen(inputFilename, "rb");
1368
4.99k
        if (!f) {
1369
0
            fprintf(stderr, "Can't open JPEG file for read: %s\n", inputFilename);
1370
0
            return AVIF_FALSE;
1371
0
        }
1372
4.99k
    } else {
1373
0
        f = stdin;
1374
0
        inputFilename = "(stdin)";
1375
0
    }
1376
4.99k
    const avifBool res = avifJPEGReadInternal(f,
1377
4.99k
                                              inputFilename,
1378
4.99k
                                              avif,
1379
4.99k
                                              requestedFormat,
1380
4.99k
                                              requestedDepth,
1381
4.99k
                                              chromaDownsampling,
1382
4.99k
                                              ignoreColorProfile,
1383
4.99k
                                              ignoreExif,
1384
4.99k
                                              ignoreXMP,
1385
4.99k
                                              ignoreGainMap,
1386
4.99k
                                              sizeLimit);
1387
4.99k
    if (f && f != stdin) {
1388
4.99k
        fclose(f);
1389
4.99k
    }
1390
4.99k
    return res;
1391
4.99k
}
1392
1393
avifBool avifJPEGWrite(const char * outputFilename, const avifImage * avif, int jpegQuality, avifChromaUpsampling chromaUpsampling)
1394
0
{
1395
0
    avifBool ret = AVIF_FALSE;
1396
0
    FILE * f = NULL;
1397
1398
0
    struct jpeg_compress_struct cinfo;
1399
0
    struct jpeg_error_mgr jerr;
1400
0
    JSAMPROW row_pointer[1];
1401
0
    cinfo.err = jpeg_std_error(&jerr);
1402
0
    jpeg_create_compress(&cinfo);
1403
1404
0
    avifRGBImage rgb;
1405
0
    avifRGBImageSetDefaults(&rgb, avif);
1406
0
    rgb.format = avif->yuvFormat == AVIF_PIXEL_FORMAT_YUV400 ? AVIF_RGB_FORMAT_GRAY : AVIF_RGB_FORMAT_RGB;
1407
0
    rgb.chromaUpsampling = chromaUpsampling;
1408
0
    rgb.depth = 8;
1409
0
    if (avifRGBImageAllocatePixels(&rgb) != AVIF_RESULT_OK) {
1410
0
        fprintf(stderr, "Conversion to RGB failed: %s (out of memory)\n", outputFilename);
1411
0
        goto cleanup;
1412
0
    }
1413
0
    if (avifImageYUVToRGB(avif, &rgb) != AVIF_RESULT_OK) {
1414
0
        fprintf(stderr, "Conversion to RGB failed: %s\n", outputFilename);
1415
0
        goto cleanup;
1416
0
    }
1417
1418
0
    f = fopen(outputFilename, "wb");
1419
0
    if (!f) {
1420
0
        fprintf(stderr, "Can't open JPEG file for write: %s\n", outputFilename);
1421
0
        goto cleanup;
1422
0
    }
1423
1424
0
    jpeg_stdio_dest(&cinfo, f);
1425
0
    cinfo.image_width = avif->width;
1426
0
    cinfo.image_height = avif->height;
1427
0
    const avifBool isGray = avif->yuvFormat == AVIF_PIXEL_FORMAT_YUV400;
1428
0
    cinfo.input_components = isGray ? 1 : 3;
1429
0
    cinfo.in_color_space = isGray ? JCS_GRAYSCALE : JCS_RGB;
1430
0
    jpeg_set_defaults(&cinfo);
1431
0
    jpeg_set_quality(&cinfo, jpegQuality, TRUE);
1432
0
    jpeg_start_compress(&cinfo, TRUE);
1433
1434
0
    if (avif->icc.data && (avif->icc.size > 0)) {
1435
        // TODO(yguyon): Use jpeg_write_icc_profile() instead?
1436
0
        write_icc_profile(&cinfo, avif->icc.data, (unsigned int)avif->icc.size);
1437
0
    }
1438
1439
0
    if (avif->transformFlags & AVIF_TRANSFORM_CLAP) {
1440
0
        avifCropRect cropRect;
1441
0
        avifDiagnostics diag;
1442
0
        if (avifCropRectFromCleanApertureBox(&cropRect, &avif->clap, avif->width, avif->height, &diag) &&
1443
0
            (cropRect.x != 0 || cropRect.y != 0 || cropRect.width != avif->width || cropRect.height != avif->height)) {
1444
            // TODO: https://github.com/AOMediaCodec/libavif/issues/2427 - Implement.
1445
0
            fprintf(stderr,
1446
0
                    "Warning: Clean Aperture values were ignored, the output image was NOT cropped to rectangle {%u,%u,%u,%u}\n",
1447
0
                    cropRect.x,
1448
0
                    cropRect.y,
1449
0
                    cropRect.width,
1450
0
                    cropRect.height);
1451
0
        }
1452
0
    }
1453
1454
0
    if (avif->exif.data && (avif->exif.size > 0)) {
1455
0
        size_t exifTiffHeaderOffset;
1456
0
        avifResult result = avifGetExifTiffHeaderOffset(avif->exif.data, avif->exif.size, &exifTiffHeaderOffset);
1457
0
        if (result != AVIF_RESULT_OK) {
1458
0
            fprintf(stderr, "Error writing JPEG metadata: %s\n", avifResultToString(result));
1459
0
            goto cleanup;
1460
0
        }
1461
1462
0
        avifRWData exif = { NULL, 0 };
1463
0
        if (avifRWDataRealloc(&exif, AVIF_JPEG_EXIF_HEADER_LENGTH + avif->exif.size - exifTiffHeaderOffset) != AVIF_RESULT_OK) {
1464
0
            fprintf(stderr, "Error writing JPEG metadata: out of memory\n");
1465
0
            goto cleanup;
1466
0
        }
1467
0
        memcpy(exif.data, AVIF_JPEG_EXIF_HEADER, AVIF_JPEG_EXIF_HEADER_LENGTH);
1468
0
        memcpy(exif.data + AVIF_JPEG_EXIF_HEADER_LENGTH, avif->exif.data + exifTiffHeaderOffset, avif->exif.size - exifTiffHeaderOffset);
1469
        // Make sure the Exif orientation matches the irot/imir values.
1470
        // libheif does not have the same behavior. The orientation is applied to samples and orientation data is discarded there,
1471
        // see https://github.com/strukturag/libheif/blob/ea78603d8e47096606813d221725621306789ff2/examples/encoder_jpeg.cc#L187
1472
0
        const uint8_t orientation = avifImageGetExifOrientationFromIrotImir(avif);
1473
0
        result = avifSetExifOrientation(&exif, orientation);
1474
0
        if (result != AVIF_RESULT_OK) {
1475
            // Ignore errors if the orientation is the default one because not being able to set Exif orientation now
1476
            // means a reader will not be able to parse it later either.
1477
0
            if (orientation != 1) {
1478
0
                fprintf(stderr, "Error writing JPEG metadata: %s\n", avifResultToString(result));
1479
0
                avifRWDataFree(&exif);
1480
0
                goto cleanup;
1481
0
            }
1482
0
        }
1483
1484
0
        avifROData remainingExif = { exif.data, exif.size };
1485
0
        while (remainingExif.size > AVIF_JPEG_MAX_MARKER_DATA_LENGTH) {
1486
0
            jpeg_write_marker(&cinfo, JPEG_APP0 + 1, remainingExif.data, AVIF_JPEG_MAX_MARKER_DATA_LENGTH);
1487
0
            remainingExif.data += AVIF_JPEG_MAX_MARKER_DATA_LENGTH;
1488
0
            remainingExif.size -= AVIF_JPEG_MAX_MARKER_DATA_LENGTH;
1489
0
        }
1490
0
        jpeg_write_marker(&cinfo, JPEG_APP0 + 1, remainingExif.data, (unsigned int)remainingExif.size);
1491
0
        avifRWDataFree(&exif);
1492
0
    } else if (avifImageGetExifOrientationFromIrotImir(avif) != 1) {
1493
        // There is no Exif yet, but we need to store the orientation.
1494
        // TODO: https://github.com/AOMediaCodec/libavif/issues/2427 - Add a valid Exif payload or rotate the samples.
1495
0
        fprintf(stderr,
1496
0
                "Warning: Orientation %u was ignored, the output image was NOT rotated or mirrored\n",
1497
0
                avifImageGetExifOrientationFromIrotImir(avif));
1498
0
    }
1499
1500
0
    if (avif->xmp.data && (avif->xmp.size > 0)) {
1501
        // See XMP specification part 3.
1502
0
        if (avif->xmp.size > 65502) {
1503
            // libheif just refuses to export JPEG with long XMP, see
1504
            // https://github.com/strukturag/libheif/blob/18291ddebc23c924440a8a3c9a7267fe3beb5901/examples/encoder_jpeg.cc#L227
1505
            // But libheif also ignores extended XMP at reading, so converting a JPEG with extended XMP to HEIC and back to JPEG
1506
            // works, with the extended XMP part dropped, even if it had fit into a single JPEG marker.
1507
1508
            // In libavif the whole XMP payload is dropped if it exceeds a single JPEG marker size limit, with a warning.
1509
            // The advantage is that it keeps the whole XMP payload, including the extended part, if it fits into a single JPEG
1510
            // marker. This is acceptable because section 1.1.3.1 of XMP specification part 3 says
1511
            //   "It is unusual for XMP to exceed 65502 bytes; typically, it is around 2 KB."
1512
0
            fprintf(stderr, "Warning writing JPEG metadata: XMP payload is too big and was dropped\n");
1513
0
        } else {
1514
0
            avifRWData xmp = { NULL, 0 };
1515
0
            if (avifRWDataRealloc(&xmp, AVIF_JPEG_STANDARD_XMP_TAG_LENGTH + avif->xmp.size) != AVIF_RESULT_OK) {
1516
0
                fprintf(stderr, "Error writing JPEG metadata: out of memory\n");
1517
0
                goto cleanup;
1518
0
            }
1519
0
            memcpy(xmp.data, AVIF_JPEG_STANDARD_XMP_TAG, AVIF_JPEG_STANDARD_XMP_TAG_LENGTH);
1520
0
            memcpy(xmp.data + AVIF_JPEG_STANDARD_XMP_TAG_LENGTH, avif->xmp.data, avif->xmp.size);
1521
0
            jpeg_write_marker(&cinfo, JPEG_APP0 + 1, xmp.data, (unsigned int)xmp.size);
1522
0
            avifRWDataFree(&xmp);
1523
0
        }
1524
0
    }
1525
1526
0
    while (cinfo.next_scanline < cinfo.image_height) {
1527
0
        row_pointer[0] = &rgb.pixels[cinfo.next_scanline * rgb.rowBytes];
1528
0
        (void)jpeg_write_scanlines(&cinfo, row_pointer, 1);
1529
0
    }
1530
1531
0
    jpeg_finish_compress(&cinfo);
1532
0
    ret = AVIF_TRUE;
1533
0
    printf("Wrote JPEG: %s\n", outputFilename);
1534
0
cleanup:
1535
0
    if (f) {
1536
0
        fclose(f);
1537
0
    }
1538
0
    jpeg_destroy_compress(&cinfo);
1539
0
    avifRGBImageFreePixels(&rgb);
1540
0
    return ret;
1541
0
}