Coverage Report

Created: 2026-02-26 06:43

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