Coverage Report

Created: 2026-07-25 07:03

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libavif/src/write.c
Line
Count
Source
1
// Copyright 2019 Joe Drago. All rights reserved.
2
// SPDX-License-Identifier: BSD-2-Clause
3
4
#include "avif/internal.h"
5
6
#include <assert.h>
7
#include <stdint.h>
8
#include <string.h>
9
#include <time.h>
10
11
// Section 8.11.14.2 of ISO/IEC 14496-12 (ItemPropertyAssociationBox 'ipma' syntax):
12
//   if (flags & 1)
13
//     unsigned int(15) property_index;
14
//   else
15
//     unsigned int(7) property_index;
16
//
17
// libavif writes 'ipma' with flags set to 0.
18
#define MAX_PROPERTY_INDEX ((1 << 7) - 1)
19
20
// The indices of the properties associated with an item.
21
typedef struct avifItemPropertyAssociation
22
{
23
    uint8_t property_index; // 1-indexed
24
    avifBool essential;
25
} avifItemPropertyAssociation;
26
AVIF_ARRAY_DECLARE(avifItemPropertyAssociationArray, avifItemPropertyAssociation, association);
27
28
// Used to store offsets in meta boxes which need to point at mdat offsets that
29
// aren't known yet. When an item's mdat payload is written, all registered fixups
30
// will have this now-known offset "fixed up".
31
typedef struct avifOffsetFixup
32
{
33
    size_t offset;
34
} avifOffsetFixup;
35
AVIF_ARRAY_DECLARE(avifOffsetFixupArray, avifOffsetFixup, fixup);
36
37
static const char alphaURN[] = AVIF_URN_ALPHA0;
38
static const size_t alphaURNSize = sizeof(alphaURN);
39
40
static const char xmpContentType[] = AVIF_CONTENT_TYPE_XMP;
41
static const size_t xmpContentTypeSize = sizeof(xmpContentType);
42
43
static avifResult writeCodecConfig(avifRWStream * s, const avifCodecConfigurationBox * cfg);
44
static avifResult writeConfigBox(avifRWStream * s, const avifCodecConfigurationBox * cfg, const char * configPropName);
45
46
// ---------------------------------------------------------------------------
47
// avifSetTileConfiguration
48
49
static int floorLog2(uint32_t n)
50
0
{
51
0
    assert(n > 0);
52
0
    int count = 0;
53
0
    while (n != 0) {
54
0
        ++count;
55
0
        n >>= 1;
56
0
    }
57
0
    return count - 1;
58
0
}
59
60
// Splits tilesLog2 into *tileDim1Log2 and *tileDim2Log2, considering the ratio of dim1 to dim2.
61
//
62
// Precondition:
63
//     dim1 >= dim2
64
// Postcondition:
65
//     tilesLog2 == *tileDim1Log2 + *tileDim2Log2
66
//     *tileDim1Log2 >= *tileDim2Log2
67
static void splitTilesLog2(uint32_t dim1, uint32_t dim2, int tilesLog2, int * tileDim1Log2, int * tileDim2Log2)
68
0
{
69
0
    assert(dim1 >= dim2);
70
0
    uint32_t ratio = dim1 / dim2;
71
0
    int diffLog2 = floorLog2(ratio);
72
0
    int subtract = tilesLog2 - diffLog2;
73
0
    if (subtract < 0) {
74
0
        subtract = 0;
75
0
    }
76
0
    *tileDim2Log2 = subtract / 2;
77
0
    *tileDim1Log2 = tilesLog2 - *tileDim2Log2;
78
0
    assert(*tileDim1Log2 >= *tileDim2Log2);
79
0
}
80
81
// Set the tile configuration: the number of tiles and the tile size.
82
//
83
// Tiles improve encoding and decoding speeds when multiple threads are available. However, for
84
// image coding, the total tile boundary length affects the compression efficiency because intra
85
// prediction can't go across tile boundaries. So the more tiles there are in an image, the worse
86
// the compression ratio is. For a given number of tiles, making the tile size close to a square
87
// tends to reduce the total tile boundary length inside the image. Use more tiles along the longer
88
// dimension of the image to make the tile size closer to a square.
89
void avifSetTileConfiguration(int threads, uint32_t width, uint32_t height, int * tileRowsLog2, int * tileColsLog2)
90
0
{
91
0
    *tileRowsLog2 = 0;
92
0
    *tileColsLog2 = 0;
93
0
    if (threads > 1) {
94
        // Avoid small tiles because they are particularly bad for image coding.
95
        //
96
        // Use no more tiles than the number of threads. Aim for one tile per thread. Using more
97
        // than one thread inside one tile could be less efficient. Using more tiles than the
98
        // number of threads would result in a compression penalty without much benefit.
99
0
        const uint32_t kMinTileArea = 512 * 512;
100
0
        const uint32_t kMaxTiles = 32;
101
        // AV1 requires width <= 65536 and height <= 65536, so their product fits
102
        // in uint64_t and the resulting tile count fits in uint32_t.
103
0
        const uint64_t imageArea = (uint64_t)width * height;
104
0
        uint32_t tiles = (uint32_t)((imageArea + kMinTileArea - 1) / kMinTileArea);
105
0
        if (tiles > kMaxTiles) {
106
0
            tiles = kMaxTiles;
107
0
        }
108
0
        if (tiles > (uint32_t)threads) {
109
0
            tiles = threads;
110
0
        }
111
0
        int tilesLog2 = floorLog2(tiles);
112
        // If the image's width is greater than the height, use more tile columns than tile rows.
113
0
        if (width >= height) {
114
0
            splitTilesLog2(width, height, tilesLog2, tileColsLog2, tileRowsLog2);
115
0
        } else {
116
0
            splitTilesLog2(height, width, tilesLog2, tileRowsLog2, tileColsLog2);
117
0
        }
118
0
    }
119
0
}
120
121
// ---------------------------------------------------------------------------
122
// avifCodecEncodeOutput
123
124
avifCodecEncodeOutput * avifCodecEncodeOutputCreate(void)
125
0
{
126
0
    avifCodecEncodeOutput * encodeOutput = (avifCodecEncodeOutput *)avifAlloc(sizeof(avifCodecEncodeOutput));
127
0
    if (encodeOutput == NULL) {
128
0
        return NULL;
129
0
    }
130
0
    memset(encodeOutput, 0, sizeof(avifCodecEncodeOutput));
131
0
    if (!avifArrayCreate(&encodeOutput->samples, sizeof(avifEncodeSample), 1)) {
132
0
        avifCodecEncodeOutputDestroy(encodeOutput);
133
0
        return NULL;
134
0
    }
135
0
    return encodeOutput;
136
0
}
137
138
avifResult avifCodecEncodeOutputAddSample(avifCodecEncodeOutput * encodeOutput, const uint8_t * data, size_t len, avifBool sync)
139
0
{
140
0
    avifEncodeSample * sample = (avifEncodeSample *)avifArrayPush(&encodeOutput->samples);
141
0
    AVIF_CHECKERR(sample, AVIF_RESULT_OUT_OF_MEMORY);
142
0
    const avifResult result = avifRWDataSet(&sample->data, data, len);
143
0
    if (result != AVIF_RESULT_OK) {
144
0
        avifArrayPop(&encodeOutput->samples);
145
0
        return result;
146
0
    }
147
0
    sample->sync = sync;
148
0
    return AVIF_RESULT_OK;
149
0
}
150
151
void avifCodecEncodeOutputDestroy(avifCodecEncodeOutput * encodeOutput)
152
0
{
153
0
    for (uint32_t sampleIndex = 0; sampleIndex < encodeOutput->samples.count; ++sampleIndex) {
154
0
        avifRWDataFree(&encodeOutput->samples.sample[sampleIndex].data);
155
0
    }
156
0
    avifArrayDestroy(&encodeOutput->samples);
157
0
    avifFree(encodeOutput);
158
0
}
159
160
// ---------------------------------------------------------------------------
161
// avifEncoderItem
162
163
// one "item" worth for encoder
164
typedef struct avifEncoderItem
165
{
166
    uint16_t id;
167
    uint8_t type[4];                      // 4-character 'item_type' field in the 'infe' (item info entry) box
168
    avifCodec * codec;                    // only present on image items
169
    avifCodecEncodeOutput * encodeOutput; // AV1 sample data
170
    avifRWData metadataPayload;           // Exif/XMP data
171
    avifCodecConfigurationBox av1C;       // Harvested in avifEncoderFinish(), if encodeOutput has samples
172
                                          // TODO(yguyon): Rename or add av2C
173
    uint32_t cellIndex;                   // Which row-major cell index corresponds to this item. only present on image items
174
    avifItemCategory itemCategory;        // Category of item being encoded
175
    avifBool hiddenImage;                 // A hidden image item has (flags & 1) equal to 1 in its ItemInfoEntry.
176
177
    const char * infeName;
178
    size_t infeNameSize;
179
    const char * infeContentType;
180
    size_t infeContentTypeSize;
181
    avifOffsetFixupArray mdatFixups;
182
183
    uint16_t irefToID; // if non-zero, make an iref from this id -> irefToID
184
    const char * irefType;
185
186
    uint32_t gridCols; // if non-zero (legal range [1-256]), this is a grid item
187
    uint32_t gridRows; // if non-zero (legal range [1-256]), this is a grid item
188
189
    // the reconstructed image of a grid item will be trimmed to these dimensions (only present on grid items)
190
    uint32_t gridWidth;
191
    uint32_t gridHeight;
192
193
    uint32_t extraLayerCount; // if non-zero (legal range [1-(AVIF_MAX_AV1_LAYER_COUNT-1)]), this is a layered AV1 image
194
195
    uint16_t dimgFromID; // if non-zero, make an iref from dimgFromID -> this id
196
197
    avifItemPropertyAssociationArray associations; // 'ipma'
198
} avifEncoderItem;
199
AVIF_ARRAY_DECLARE(avifEncoderItemArray, avifEncoderItem, item);
200
201
// ---------------------------------------------------------------------------
202
// avifEncoderItemReference
203
204
// pointer to one "item" interested in
205
typedef avifEncoderItem * avifEncoderItemReference;
206
AVIF_ARRAY_DECLARE(avifEncoderItemReferenceArray, avifEncoderItemReference, ref);
207
208
// ---------------------------------------------------------------------------
209
// avifEncoderFrame
210
211
typedef struct avifEncoderFrame
212
{
213
    uint64_t durationInTimescales;
214
} avifEncoderFrame;
215
AVIF_ARRAY_DECLARE(avifEncoderFrameArray, avifEncoderFrame, frame);
216
217
// ---------------------------------------------------------------------------
218
// avifEncoderData
219
220
AVIF_ARRAY_DECLARE(avifEncoderItemIdArray, uint16_t, itemID);
221
222
typedef struct avifEncoderData
223
{
224
    avifEncoderItemArray items;
225
    avifEncoderFrameArray frames;
226
    // quality values for the image, alpha, and gain map
227
    // Note: these three fields are the actual quality values after the default values are resolved
228
    int quality;
229
    int qualityAlpha;
230
    int qualityGainMap;
231
    // tileRowsLog2 and tileColsLog2 are the actual tiling values after automatic tiling is handled
232
    int tileRowsLog2;
233
    int tileColsLog2;
234
    avifEncoder lastEncoder; // Shallow state at last avifEncoderAddImageInternal() call.
235
    // lastQuality and lastQualityAlpha are the quality and qualityAlpha values used last time
236
    // Note: Gain maps are supported for still images only, so we don't need a "lastQualityGainMap"
237
    int lastQuality;
238
    int lastQualityAlpha;
239
    // lastTileRowsLog2 and lastTileColsLog2 are the actual tiling values used last time
240
    int lastTileRowsLog2;
241
    int lastTileColsLog2;
242
    // Holds metadata about the base image
243
    avifImage * imageMetadata;
244
    // Holds metadata derived from the avifGainMap struct (when present) about the alternate image
245
    avifImage * altImageMetadata;
246
    uint16_t lastItemID;
247
    uint16_t primaryItemID;
248
    avifEncoderItemIdArray alternativeItemIDs; // list of item ids for an 'altr' box (group of alternatives to each other)
249
    avifBool singleImage; // if true, the AVIF_ADD_IMAGE_FLAG_SINGLE flag was set on the first call to avifEncoderAddImage()
250
    avifBool alphaPresent;
251
    size_t gainMapSizeBytes;
252
    // Fields specific to AV1/AV2
253
    const char * imageItemType;  // "av01" for AV1 ("av02" for AV2 if AVIF_CODEC_AVM)
254
    const char * configPropName; // "av1C" for AV1 ("av2C" for AV2 if AVIF_CODEC_AVM)
255
} avifEncoderData;
256
257
static void avifEncoderDataDestroy(avifEncoderData * data);
258
259
// Returns NULL if a memory allocation failed.
260
static avifEncoderData * avifEncoderDataCreate(void)
261
0
{
262
0
    avifEncoderData * data = (avifEncoderData *)avifAlloc(sizeof(avifEncoderData));
263
0
    if (!data) {
264
0
        return NULL;
265
0
    }
266
0
    memset(data, 0, sizeof(avifEncoderData));
267
0
    data->imageMetadata = avifImageCreateEmpty();
268
0
    if (!data->imageMetadata) {
269
0
        goto error;
270
0
    }
271
0
    data->altImageMetadata = avifImageCreateEmpty();
272
0
    if (!data->altImageMetadata) {
273
0
        goto error;
274
0
    }
275
0
    if (!avifArrayCreate(&data->items, sizeof(avifEncoderItem), 8)) {
276
0
        goto error;
277
0
    }
278
0
    if (!avifArrayCreate(&data->frames, sizeof(avifEncoderFrame), 1)) {
279
0
        goto error;
280
0
    }
281
0
    if (!avifArrayCreate(&data->alternativeItemIDs, sizeof(uint16_t), 1)) {
282
0
        goto error;
283
0
    }
284
0
    return data;
285
286
0
error:
287
0
    avifEncoderDataDestroy(data);
288
0
    return NULL;
289
0
}
290
291
static avifEncoderItem * avifEncoderDataCreateItem(avifEncoderData * data, const char * type, const char * infeName, size_t infeNameSize, uint32_t cellIndex)
292
0
{
293
0
    avifEncoderItem * item = (avifEncoderItem *)avifArrayPush(&data->items);
294
0
    if (item == NULL) {
295
0
        return NULL;
296
0
    }
297
0
    ++data->lastItemID;
298
0
    item->id = data->lastItemID;
299
0
    memcpy(item->type, type, sizeof(item->type));
300
0
    item->infeName = infeName;
301
0
    item->infeNameSize = infeNameSize;
302
0
    item->encodeOutput = avifCodecEncodeOutputCreate();
303
0
    if (item->encodeOutput == NULL) {
304
0
        goto error;
305
0
    }
306
0
    item->cellIndex = cellIndex;
307
0
    if (!avifArrayCreate(&item->mdatFixups, sizeof(avifOffsetFixup), 4)) {
308
0
        goto error;
309
0
    }
310
0
    if (!avifArrayCreate(&item->associations, sizeof(avifItemPropertyAssociation), 4)) {
311
0
        goto error;
312
0
    }
313
0
    return item;
314
315
0
error:
316
0
    if (item->encodeOutput != NULL) {
317
0
        avifCodecEncodeOutputDestroy(item->encodeOutput);
318
0
    }
319
0
    avifArrayDestroy(&item->mdatFixups);
320
0
    --data->lastItemID;
321
0
    avifArrayPop(&data->items);
322
0
    return NULL;
323
0
}
324
325
static avifEncoderItem * avifEncoderDataFindItemByID(avifEncoderData * data, uint16_t id)
326
0
{
327
0
    for (uint32_t itemIndex = 0; itemIndex < data->items.count; ++itemIndex) {
328
0
        avifEncoderItem * item = &data->items.item[itemIndex];
329
0
        if (item->id == id) {
330
0
            return item;
331
0
        }
332
0
    }
333
0
    return NULL;
334
0
}
335
336
static void avifEncoderDataDestroy(avifEncoderData * data)
337
0
{
338
0
    for (uint32_t i = 0; i < data->items.count; ++i) {
339
0
        avifEncoderItem * item = &data->items.item[i];
340
0
        if (item->codec) {
341
0
            avifCodecDestroy(item->codec);
342
0
        }
343
0
        avifCodecEncodeOutputDestroy(item->encodeOutput);
344
0
        avifRWDataFree(&item->metadataPayload);
345
0
        avifArrayDestroy(&item->mdatFixups);
346
0
        avifArrayDestroy(&item->associations);
347
0
    }
348
0
    if (data->imageMetadata) {
349
0
        avifImageDestroy(data->imageMetadata);
350
0
    }
351
0
    if (data->altImageMetadata) {
352
0
        avifImageDestroy(data->altImageMetadata);
353
0
    }
354
0
    avifArrayDestroy(&data->items);
355
0
    avifArrayDestroy(&data->frames);
356
0
    avifArrayDestroy(&data->alternativeItemIDs);
357
0
    avifFree(data);
358
0
}
359
360
static avifResult avifEncoderItemAddMdatFixup(avifEncoderItem * item, const avifRWStream * s)
361
0
{
362
0
    avifOffsetFixup * fixup = (avifOffsetFixup *)avifArrayPush(&item->mdatFixups);
363
0
    AVIF_CHECKERR(fixup != NULL, AVIF_RESULT_OUT_OF_MEMORY);
364
0
    fixup->offset = avifRWStreamOffset(s);
365
0
    return AVIF_RESULT_OK;
366
0
}
367
368
// ---------------------------------------------------------------------------
369
// avifItemPropertyDedup - Provides ipco deduplication
370
371
typedef struct avifItemProperty
372
{
373
    uint8_t index;
374
    size_t offset;
375
    size_t size;
376
} avifItemProperty;
377
AVIF_ARRAY_DECLARE(avifItemPropertyArray, avifItemProperty, property);
378
379
typedef struct avifItemPropertyDedup
380
{
381
    avifItemPropertyArray properties;
382
    avifRWStream s;    // Temporary stream for each new property, checked against already-written boxes for deduplications
383
    avifRWData buffer; // Temporary storage for 's'
384
    uint8_t nextIndex; // 1-indexed, incremented every time another unique property is finished
385
} avifItemPropertyDedup;
386
387
static avifItemPropertyDedup * avifItemPropertyDedupCreate(void)
388
0
{
389
0
    avifItemPropertyDedup * dedup = (avifItemPropertyDedup *)avifAlloc(sizeof(avifItemPropertyDedup));
390
0
    if (dedup == NULL) {
391
0
        return NULL;
392
0
    }
393
0
    memset(dedup, 0, sizeof(avifItemPropertyDedup));
394
0
    if (!avifArrayCreate(&dedup->properties, sizeof(avifItemProperty), 8)) {
395
0
        avifFree(dedup);
396
0
        return NULL;
397
0
    }
398
0
    if (avifRWDataRealloc(&dedup->buffer, 2048) != AVIF_RESULT_OK) {
399
0
        avifArrayDestroy(&dedup->properties);
400
0
        avifFree(dedup);
401
0
        return NULL;
402
0
    }
403
0
    return dedup;
404
0
}
405
406
static void avifItemPropertyDedupDestroy(avifItemPropertyDedup * dedup)
407
0
{
408
0
    avifArrayDestroy(&dedup->properties);
409
0
    avifRWDataFree(&dedup->buffer);
410
0
    avifFree(dedup);
411
0
}
412
413
// Resets the dedup's temporary write stream in preparation for a single item property's worth of writing
414
static void avifItemPropertyDedupStart(avifItemPropertyDedup * dedup)
415
0
{
416
0
    avifRWStreamStart(&dedup->s, &dedup->buffer);
417
0
}
418
419
// This compares the newly written item property (in the dedup's temporary storage buffer) to
420
// already-written properties (whose offsets/sizes in outputStream are recorded in the dedup). If a
421
// match is found, the previous property's index is used. If this new property is unique, it is
422
// assigned the next available property index, written to the output stream, and its offset/size in
423
// the output stream is recorded in the dedup for future comparisons.
424
//
425
// On success, this function adds to the given ipma box a property association linking the reused
426
// or newly created property with the item.
427
static avifResult avifItemPropertyDedupFinish(avifItemPropertyDedup * dedup,
428
                                              avifRWStream * outputStream,
429
                                              avifItemPropertyAssociationArray * associations,
430
                                              avifBool essential)
431
0
{
432
0
    uint8_t propertyIndex = 0;
433
0
    const size_t newPropertySize = avifRWStreamOffset(&dedup->s);
434
435
0
    for (size_t i = 0; i < dedup->properties.count; ++i) {
436
0
        avifItemProperty * property = &dedup->properties.property[i];
437
0
        if ((property->size == newPropertySize) &&
438
0
            !memcmp(&outputStream->raw->data[property->offset], dedup->buffer.data, newPropertySize)) {
439
            // We've already written this exact property, reuse it
440
0
            propertyIndex = property->index;
441
0
            AVIF_ASSERT_OR_RETURN(propertyIndex != 0);
442
0
            break;
443
0
        }
444
0
    }
445
446
0
    if (propertyIndex == 0) {
447
        // Write a new property, and remember its location in the output stream for future deduplication
448
0
        avifItemProperty * property = (avifItemProperty *)avifArrayPush(&dedup->properties);
449
0
        AVIF_CHECKERR(property != NULL, AVIF_RESULT_OUT_OF_MEMORY);
450
0
        AVIF_CHECKERR(dedup->nextIndex < MAX_PROPERTY_INDEX, AVIF_RESULT_INVALID_ARGUMENT);
451
0
        property->index = ++dedup->nextIndex; // preincrement so the first new index is 1 (as ipma is 1-indexed)
452
0
        property->size = newPropertySize;
453
0
        property->offset = avifRWStreamOffset(outputStream);
454
0
        AVIF_CHECKRES(avifRWStreamWrite(outputStream, dedup->buffer.data, newPropertySize));
455
0
        propertyIndex = property->index;
456
0
    }
457
458
0
    avifItemPropertyAssociation * association = (avifItemPropertyAssociation *)avifArrayPush(associations);
459
0
    AVIF_CHECKERR(association != NULL, AVIF_RESULT_OUT_OF_MEMORY);
460
0
    association->property_index = propertyIndex;
461
0
    association->essential = essential;
462
0
    return AVIF_RESULT_OK;
463
0
}
464
465
// ---------------------------------------------------------------------------
466
467
static const avifScalingMode noScaling = { { 1, 1 }, { 1, 1 } };
468
469
avifEncoder * avifEncoderCreate(void)
470
0
{
471
0
    avifEncoder * encoder = (avifEncoder *)avifAlloc(sizeof(avifEncoder));
472
0
    if (!encoder) {
473
0
        return NULL;
474
0
    }
475
0
    memset(encoder, 0, sizeof(avifEncoder));
476
0
    encoder->codecChoice = AVIF_CODEC_CHOICE_AUTO;
477
0
    encoder->maxThreads = 1;
478
0
    encoder->speed = AVIF_SPEED_DEFAULT;
479
0
    encoder->keyframeInterval = 0;
480
0
    encoder->timescale = 1;
481
0
    encoder->repetitionCount = AVIF_REPETITION_COUNT_INFINITE;
482
0
    encoder->quality = AVIF_QUALITY_DEFAULT;
483
0
    encoder->qualityAlpha = AVIF_QUALITY_DEFAULT;
484
0
    encoder->qualityGainMap = AVIF_QUALITY_DEFAULT;
485
0
    encoder->minQuantizer = AVIF_QUANTIZER_BEST_QUALITY;
486
0
    encoder->maxQuantizer = AVIF_QUANTIZER_WORST_QUALITY;
487
0
    encoder->minQuantizerAlpha = AVIF_QUANTIZER_BEST_QUALITY;
488
0
    encoder->maxQuantizerAlpha = AVIF_QUANTIZER_WORST_QUALITY;
489
0
    encoder->tileRowsLog2 = 0;
490
0
    encoder->tileColsLog2 = 0;
491
0
    encoder->autoTiling = AVIF_FALSE;
492
0
    encoder->scalingMode = noScaling;
493
0
    encoder->data = avifEncoderDataCreate();
494
0
    encoder->csOptions = avifCodecSpecificOptionsCreate();
495
0
    if (!encoder->data || !encoder->csOptions) {
496
0
        avifEncoderDestroy(encoder);
497
0
        return NULL;
498
0
    }
499
0
    encoder->headerFormat = AVIF_HEADER_DEFAULT;
500
0
    encoder->creationTime = 0;
501
0
    encoder->modificationTime = 0;
502
0
    encoder->sampleTransformRecipe = AVIF_SAMPLE_TRANSFORM_NONE;
503
0
    return encoder;
504
0
}
505
506
void avifEncoderDestroy(avifEncoder * encoder)
507
0
{
508
0
    if (encoder->csOptions) {
509
0
        avifCodecSpecificOptionsDestroy(encoder->csOptions);
510
0
    }
511
0
    if (encoder->data) {
512
0
        avifEncoderDataDestroy(encoder->data);
513
0
    }
514
0
    avifFree(encoder);
515
0
}
516
517
avifResult avifEncoderSetCodecSpecificOption(avifEncoder * encoder, const char * key, const char * value)
518
0
{
519
0
    return avifCodecSpecificOptionsSet(encoder->csOptions, key, value);
520
0
}
521
522
static void avifEncoderBackupSettings(avifEncoder * encoder)
523
0
{
524
0
    avifEncoder * lastEncoder = &encoder->data->lastEncoder;
525
526
    // lastEncoder->data is only used to mark that lastEncoder is initialized. lastEncoder->data
527
    // must not be dereferenced.
528
0
    lastEncoder->data = encoder->data;
529
0
    lastEncoder->codecChoice = encoder->codecChoice;
530
0
    lastEncoder->maxThreads = encoder->maxThreads;
531
0
    lastEncoder->speed = encoder->speed;
532
0
    lastEncoder->keyframeInterval = encoder->keyframeInterval;
533
0
    lastEncoder->timescale = encoder->timescale;
534
0
    lastEncoder->repetitionCount = encoder->repetitionCount;
535
0
    lastEncoder->extraLayerCount = encoder->extraLayerCount;
536
0
    lastEncoder->minQuantizer = encoder->minQuantizer;
537
0
    lastEncoder->maxQuantizer = encoder->maxQuantizer;
538
0
    lastEncoder->minQuantizerAlpha = encoder->minQuantizerAlpha;
539
0
    lastEncoder->maxQuantizerAlpha = encoder->maxQuantizerAlpha;
540
0
    encoder->data->lastQuality = encoder->data->quality;
541
0
    encoder->data->lastQualityAlpha = encoder->data->qualityAlpha;
542
0
    encoder->data->lastTileRowsLog2 = encoder->data->tileRowsLog2;
543
0
    encoder->data->lastTileColsLog2 = encoder->data->tileColsLog2;
544
0
    lastEncoder->scalingMode = encoder->scalingMode;
545
0
    lastEncoder->sampleTransformRecipe = encoder->sampleTransformRecipe;
546
0
}
547
548
// This function detects changes made on avifEncoder. It returns true on success (i.e., if every
549
// change is valid), or false on failure (i.e., if any setting that can't change was changed). It
550
// reports a bitwise-OR of detected changes in encoderChanges.
551
static avifBool avifEncoderDetectChanges(const avifEncoder * encoder, avifEncoderChanges * encoderChanges)
552
0
{
553
0
    const avifEncoder * lastEncoder = &encoder->data->lastEncoder;
554
0
    *encoderChanges = 0;
555
556
0
    if (!lastEncoder->data) {
557
        // lastEncoder is not initialized.
558
0
        return AVIF_TRUE;
559
0
    }
560
561
0
    if ((lastEncoder->codecChoice != encoder->codecChoice) || (lastEncoder->maxThreads != encoder->maxThreads) ||
562
0
        (lastEncoder->speed != encoder->speed) || (lastEncoder->keyframeInterval != encoder->keyframeInterval) ||
563
0
        (lastEncoder->timescale != encoder->timescale) || (lastEncoder->repetitionCount != encoder->repetitionCount) ||
564
0
        (lastEncoder->extraLayerCount != encoder->extraLayerCount)) {
565
0
        return AVIF_FALSE;
566
0
    }
567
568
0
    if (encoder->data->lastQuality != encoder->data->quality) {
569
0
        *encoderChanges |= AVIF_ENCODER_CHANGE_QUALITY;
570
0
    }
571
0
    if (encoder->data->lastQualityAlpha != encoder->data->qualityAlpha) {
572
0
        *encoderChanges |= AVIF_ENCODER_CHANGE_QUALITY_ALPHA;
573
0
    }
574
0
    if (lastEncoder->minQuantizer != encoder->minQuantizer) {
575
0
        *encoderChanges |= AVIF_ENCODER_CHANGE_MIN_QUANTIZER;
576
0
    }
577
0
    if (lastEncoder->maxQuantizer != encoder->maxQuantizer) {
578
0
        *encoderChanges |= AVIF_ENCODER_CHANGE_MAX_QUANTIZER;
579
0
    }
580
0
    if (lastEncoder->minQuantizerAlpha != encoder->minQuantizerAlpha) {
581
0
        *encoderChanges |= AVIF_ENCODER_CHANGE_MIN_QUANTIZER_ALPHA;
582
0
    }
583
0
    if (lastEncoder->maxQuantizerAlpha != encoder->maxQuantizerAlpha) {
584
0
        *encoderChanges |= AVIF_ENCODER_CHANGE_MAX_QUANTIZER_ALPHA;
585
0
    }
586
0
    if (encoder->data->lastTileRowsLog2 != encoder->data->tileRowsLog2) {
587
0
        *encoderChanges |= AVIF_ENCODER_CHANGE_TILE_ROWS_LOG2;
588
0
    }
589
0
    if (encoder->data->lastTileColsLog2 != encoder->data->tileColsLog2) {
590
0
        *encoderChanges |= AVIF_ENCODER_CHANGE_TILE_COLS_LOG2;
591
0
    }
592
0
    if (memcmp(&lastEncoder->scalingMode, &encoder->scalingMode, sizeof(avifScalingMode)) != 0) {
593
0
        *encoderChanges |= AVIF_ENCODER_CHANGE_SCALING_MODE;
594
0
    }
595
0
    if (encoder->csOptions->count > 0) {
596
0
        *encoderChanges |= AVIF_ENCODER_CHANGE_CODEC_SPECIFIC;
597
0
    }
598
599
0
    if (lastEncoder->sampleTransformRecipe != encoder->sampleTransformRecipe) {
600
0
        return AVIF_FALSE;
601
0
    }
602
603
0
    return AVIF_TRUE;
604
0
}
605
606
// Same as 'avifEncoderWriteColorProperties' but for the colr nclx box only.
607
static avifResult avifEncoderWriteNclxProperty(avifRWStream * dedupStream,
608
                                               avifRWStream * outputStream,
609
                                               const avifImage * imageMetadata,
610
                                               avifItemPropertyAssociationArray * associations,
611
                                               avifItemPropertyDedup * dedup)
612
0
{
613
0
    if (dedup) {
614
0
        avifItemPropertyDedupStart(dedup);
615
0
    }
616
0
    avifBoxMarker colr;
617
0
    AVIF_CHECKRES(avifRWStreamWriteBox(dedupStream, "colr", AVIF_BOX_SIZE_TBD, &colr));
618
0
    AVIF_CHECKRES(avifRWStreamWriteChars(dedupStream, "nclx", 4));                   // unsigned int(32) colour_type;
619
0
    AVIF_CHECKRES(avifRWStreamWriteU16(dedupStream, imageMetadata->colorPrimaries)); // unsigned int(16) colour_primaries;
620
0
    AVIF_CHECKRES(avifRWStreamWriteU16(dedupStream, imageMetadata->transferCharacteristics)); // unsigned int(16) transfer_characteristics;
621
0
    AVIF_CHECKRES(avifRWStreamWriteU16(dedupStream, imageMetadata->matrixCoefficients)); // unsigned int(16) matrix_coefficients;
622
0
    AVIF_CHECKRES(avifRWStreamWriteBits(dedupStream, (imageMetadata->yuvRange == AVIF_RANGE_FULL) ? 1 : 0, /*bitCount=*/1)); // unsigned int(1) full_range_flag;
623
0
    AVIF_CHECKRES(avifRWStreamWriteBits(dedupStream, 0, /*bitCount=*/7)); // unsigned int(7) reserved = 0;
624
0
    AVIF_CHECKRES(avifRWStreamFinishBox(dedupStream, colr));
625
0
    if (dedup) {
626
0
        AVIF_CHECKRES(avifItemPropertyDedupFinish(dedup, outputStream, associations, /*essential=*/AVIF_FALSE));
627
0
    }
628
0
    return AVIF_RESULT_OK;
629
0
}
630
631
static avifResult avifEncoderWritePaspProperty(avifRWStream * dedupStream,
632
                                               avifRWStream * outputStream,
633
                                               const avifImage * imageMetadata,
634
                                               avifItemPropertyAssociationArray * associations,
635
                                               avifItemPropertyDedup * dedup);
636
static avifResult avifEncoderWriteTransformativeProperties(avifRWStream * dedupStream,
637
                                                           avifRWStream * outputStream,
638
                                                           const avifImage * imageMetadata,
639
                                                           avifItemPropertyAssociationArray * associations,
640
                                                           avifItemPropertyDedup * dedup);
641
642
// This function is used in two codepaths:
643
// * writing color *item* properties
644
// * writing color *track* properties
645
//
646
// Item properties must have property associations with them and can be deduplicated (by reusing
647
// these associations), so this function leverages the ipma and dedup arguments to do this.
648
//
649
// Track properties, however, are implicitly associated by the track in which they are contained, so
650
// there is no need to build a property association box (ipma), and no way to deduplicate/reuse a
651
// property. In this case, the ipma and dedup properties should/will be set to NULL, and this
652
// function will avoid using them.
653
static avifResult avifEncoderWriteColorProperties(avifRWStream * outputStream,
654
                                                  const avifImage * imageMetadata,
655
                                                  avifItemPropertyAssociationArray * associations,
656
                                                  avifItemPropertyDedup * dedup)
657
0
{
658
    // outputStream is the final bitstream that will be output by the libavif encoder API.
659
    // dedupStream is either equal to outputStream or to &dedup->s which is a temporary stream used
660
    // to store parts of the final bitstream; these parts may be discarded if they are a duplicate
661
    // of an already stored property.
662
0
    avifRWStream * dedupStream = outputStream;
663
0
    if (dedup) {
664
0
        AVIF_ASSERT_OR_RETURN(associations);
665
666
        // Use the dedup's temporary stream for box writes.
667
0
        dedupStream = &dedup->s;
668
0
    }
669
670
0
    if (imageMetadata->icc.size > 0) {
671
0
        if (dedup) {
672
0
            avifItemPropertyDedupStart(dedup);
673
0
        }
674
0
        avifBoxMarker colr;
675
0
        AVIF_CHECKRES(avifRWStreamWriteBox(dedupStream, "colr", AVIF_BOX_SIZE_TBD, &colr));
676
0
        AVIF_CHECKRES(avifRWStreamWriteChars(dedupStream, "prof", 4)); // unsigned int(32) colour_type;
677
0
        AVIF_CHECKRES(avifRWStreamWrite(dedupStream, imageMetadata->icc.data, imageMetadata->icc.size));
678
0
        AVIF_CHECKRES(avifRWStreamFinishBox(dedupStream, colr));
679
0
        if (dedup) {
680
0
            AVIF_CHECKRES(avifItemPropertyDedupFinish(dedup, outputStream, associations, /*essential=*/AVIF_FALSE));
681
0
        }
682
0
    }
683
684
    // HEIF 6.5.5.1, from Amendment 3 allows multiple colr boxes: "at most one for a given value of colour type"
685
    // Therefore, *always* writing an nclx box, even if a prof box was already written above.
686
0
    AVIF_CHECKRES(avifEncoderWriteNclxProperty(dedupStream, outputStream, imageMetadata, associations, dedup));
687
688
0
    AVIF_CHECKRES(avifEncoderWritePaspProperty(dedupStream, outputStream, imageMetadata, associations, dedup));
689
690
0
    return AVIF_RESULT_OK;
691
0
}
692
693
static avifResult avifEncoderWriteContentLightLevelInformation(avifRWStream * outputStream,
694
                                                               const avifContentLightLevelInformationBox * clli)
695
0
{
696
0
    AVIF_CHECKRES(avifRWStreamWriteBits(outputStream, clli->maxCLL, 16));  // unsigned int(16) max_content_light_level;
697
0
    AVIF_CHECKRES(avifRWStreamWriteBits(outputStream, clli->maxPALL, 16)); // unsigned int(16) max_pic_average_light_level;
698
0
    return AVIF_RESULT_OK;
699
0
}
700
701
// Same as 'avifEncoderWriteColorProperties' but for properties related to High Dynamic Range only.
702
static avifResult avifEncoderWriteHDRProperties(avifRWStream * dedupStream,
703
                                                avifRWStream * outputStream,
704
                                                const avifImage * imageMetadata,
705
                                                avifItemPropertyAssociationArray * associations,
706
                                                avifItemPropertyDedup * dedup)
707
0
{
708
    // Write Content Light Level Information, if present
709
0
    if (imageMetadata->clli.maxCLL || imageMetadata->clli.maxPALL) {
710
0
        if (dedup) {
711
0
            avifItemPropertyDedupStart(dedup);
712
0
        }
713
0
        avifBoxMarker clli;
714
0
        AVIF_CHECKRES(avifRWStreamWriteBox(dedupStream, "clli", AVIF_BOX_SIZE_TBD, &clli));
715
0
        AVIF_CHECKRES(avifEncoderWriteContentLightLevelInformation(dedupStream, &imageMetadata->clli));
716
0
        AVIF_CHECKRES(avifRWStreamFinishBox(dedupStream, clli));
717
0
        if (dedup) {
718
0
            AVIF_CHECKRES(avifItemPropertyDedupFinish(dedup, outputStream, associations, /*essential=*/AVIF_FALSE));
719
0
        }
720
0
    }
721
722
    // TODO(maryla): add other HDR boxes: mdcv, cclv, etc. (in avifEncoderWriteMiniHDRProperties() too)
723
724
0
    return AVIF_RESULT_OK;
725
0
}
726
727
#if defined(AVIF_ENABLE_EXPERIMENTAL_MINI)
728
static avifResult avifEncoderWriteMiniHDRProperties(avifRWStream * outputStream, const avifImage * imageMetadata)
729
{
730
    const avifBool hasClli = imageMetadata->clli.maxCLL != 0 || imageMetadata->clli.maxPALL != 0;
731
    const avifBool hasMdcv = AVIF_FALSE;
732
    const avifBool hasCclv = AVIF_FALSE;
733
    const avifBool hasAmve = AVIF_FALSE;
734
    const avifBool hasReve = AVIF_FALSE;
735
    const avifBool hasNdwt = AVIF_FALSE;
736
    AVIF_CHECKRES(avifRWStreamWriteBits(outputStream, hasClli, 1)); // bit(1) clli_flag;
737
    AVIF_CHECKRES(avifRWStreamWriteBits(outputStream, hasMdcv, 1)); // bit(1) mdcv_flag;
738
    AVIF_CHECKRES(avifRWStreamWriteBits(outputStream, hasCclv, 1)); // bit(1) cclv_flag;
739
    AVIF_CHECKRES(avifRWStreamWriteBits(outputStream, hasAmve, 1)); // bit(1) amve_flag;
740
    AVIF_CHECKRES(avifRWStreamWriteBits(outputStream, hasReve, 1)); // bit(1) reve_flag;
741
    AVIF_CHECKRES(avifRWStreamWriteBits(outputStream, hasNdwt, 1)); // bit(1) ndwt_flag;
742
743
    if (hasClli) {
744
        // ContentLightLevel clli;
745
        AVIF_CHECKRES(avifEncoderWriteContentLightLevelInformation(outputStream, &imageMetadata->clli));
746
    }
747
    if (hasMdcv) {
748
        // MasteringDisplayColourVolume mdcv;
749
    }
750
    if (hasCclv) {
751
        // ContentColourVolume cclv;
752
    }
753
    if (hasAmve) {
754
        // AmbientViewingEnvironment amve;
755
    }
756
    if (hasReve) {
757
        // ReferenceViewingEnvironment reve;
758
    }
759
    if (hasNdwt) {
760
        // NominalDiffuseWhite ndwt;
761
    }
762
    return AVIF_RESULT_OK;
763
}
764
#endif // AVIF_ENABLE_EXPERIMENTAL_MINI
765
766
static avifResult avifEncoderWritePaspProperty(avifRWStream * dedupStream,
767
                                               avifRWStream * outputStream,
768
                                               const avifImage * imageMetadata,
769
                                               avifItemPropertyAssociationArray * associations,
770
                                               avifItemPropertyDedup * dedup)
771
0
{
772
0
    if (imageMetadata->transformFlags & AVIF_TRANSFORM_PASP) {
773
0
        if (dedup) {
774
0
            avifItemPropertyDedupStart(dedup);
775
0
        }
776
0
        avifBoxMarker pasp;
777
0
        AVIF_CHECKRES(avifRWStreamWriteBox(dedupStream, "pasp", AVIF_BOX_SIZE_TBD, &pasp));
778
0
        AVIF_CHECKRES(avifRWStreamWriteU32(dedupStream, imageMetadata->pasp.hSpacing)); // unsigned int(32) hSpacing;
779
0
        AVIF_CHECKRES(avifRWStreamWriteU32(dedupStream, imageMetadata->pasp.vSpacing)); // unsigned int(32) vSpacing;
780
0
        AVIF_CHECKRES(avifRWStreamFinishBox(dedupStream, pasp));
781
0
        if (dedup) {
782
0
            AVIF_CHECKRES(avifItemPropertyDedupFinish(dedup, outputStream, associations, /*essential=*/AVIF_FALSE));
783
0
        }
784
0
    }
785
0
    return AVIF_RESULT_OK;
786
0
}
787
788
static avifResult avifEncoderWriteTransformativeProperties(avifRWStream * dedupStream,
789
                                                           avifRWStream * outputStream,
790
                                                           const avifImage * imageMetadata,
791
                                                           avifItemPropertyAssociationArray * associations,
792
                                                           avifItemPropertyDedup * dedup)
793
0
{
794
0
    if (imageMetadata->transformFlags & AVIF_TRANSFORM_CLAP) {
795
0
        if (dedup) {
796
0
            avifItemPropertyDedupStart(dedup);
797
0
        }
798
0
        avifBoxMarker clap;
799
0
        AVIF_CHECKRES(avifRWStreamWriteBox(dedupStream, "clap", AVIF_BOX_SIZE_TBD, &clap));
800
0
        AVIF_CHECKRES(avifRWStreamWriteU32(dedupStream, imageMetadata->clap.widthN));    // unsigned int(32) cleanApertureWidthN;
801
0
        AVIF_CHECKRES(avifRWStreamWriteU32(dedupStream, imageMetadata->clap.widthD));    // unsigned int(32) cleanApertureWidthD;
802
0
        AVIF_CHECKRES(avifRWStreamWriteU32(dedupStream, imageMetadata->clap.heightN));   // unsigned int(32) cleanApertureHeightN;
803
0
        AVIF_CHECKRES(avifRWStreamWriteU32(dedupStream, imageMetadata->clap.heightD));   // unsigned int(32) cleanApertureHeightD;
804
0
        AVIF_CHECKRES(avifRWStreamWriteU32(dedupStream, imageMetadata->clap.horizOffN)); // unsigned int(32) horizOffN;
805
0
        AVIF_CHECKRES(avifRWStreamWriteU32(dedupStream, imageMetadata->clap.horizOffD)); // unsigned int(32) horizOffD;
806
0
        AVIF_CHECKRES(avifRWStreamWriteU32(dedupStream, imageMetadata->clap.vertOffN));  // unsigned int(32) vertOffN;
807
0
        AVIF_CHECKRES(avifRWStreamWriteU32(dedupStream, imageMetadata->clap.vertOffD));  // unsigned int(32) vertOffD;
808
0
        AVIF_CHECKRES(avifRWStreamFinishBox(dedupStream, clap));
809
0
        if (dedup) {
810
0
            AVIF_CHECKRES(avifItemPropertyDedupFinish(dedup, outputStream, associations, /*essential=*/AVIF_TRUE));
811
0
        }
812
0
    }
813
0
    if (imageMetadata->transformFlags & AVIF_TRANSFORM_IROT) {
814
0
        if (dedup) {
815
0
            avifItemPropertyDedupStart(dedup);
816
0
        }
817
0
        avifBoxMarker irot;
818
0
        AVIF_CHECKRES(avifRWStreamWriteBox(dedupStream, "irot", AVIF_BOX_SIZE_TBD, &irot));
819
0
        AVIF_CHECKRES(avifRWStreamWriteBits(dedupStream, 0, /*bitCount=*/6)); // unsigned int (6) reserved = 0;
820
0
        AVIF_CHECKRES(avifRWStreamWriteBits(dedupStream, imageMetadata->irot.angle & 0x3, /*bitCount=*/2)); // unsigned int (2) angle;
821
0
        AVIF_CHECKRES(avifRWStreamFinishBox(dedupStream, irot));
822
0
        if (dedup) {
823
0
            AVIF_CHECKRES(avifItemPropertyDedupFinish(dedup, outputStream, associations, /*essential=*/AVIF_TRUE));
824
0
        }
825
0
    }
826
0
    if (imageMetadata->transformFlags & AVIF_TRANSFORM_IMIR) {
827
0
        if (dedup) {
828
0
            avifItemPropertyDedupStart(dedup);
829
0
        }
830
0
        avifBoxMarker imir;
831
0
        AVIF_CHECKRES(avifRWStreamWriteBox(dedupStream, "imir", AVIF_BOX_SIZE_TBD, &imir));
832
0
        AVIF_CHECKRES(avifRWStreamWriteBits(dedupStream, 0, /*bitCount=*/7)); // unsigned int(7) reserved = 0;
833
0
        AVIF_CHECKRES(avifRWStreamWriteBits(dedupStream, imageMetadata->imir.axis ? 1 : 0, /*bitCount=*/1)); // unsigned int(1) axis;
834
0
        AVIF_CHECKRES(avifRWStreamFinishBox(dedupStream, imir));
835
0
        if (dedup) {
836
0
            AVIF_CHECKRES(avifItemPropertyDedupFinish(dedup, outputStream, associations, /*essential=*/AVIF_TRUE));
837
0
        }
838
0
    }
839
0
    return AVIF_RESULT_OK;
840
0
}
841
842
static avifResult avifRWStreamWriteHandlerBox(avifRWStream * s, const char handlerType[4])
843
0
{
844
0
    avifBoxMarker hdlr;
845
0
    AVIF_CHECKRES(avifRWStreamWriteFullBox(s, "hdlr", AVIF_BOX_SIZE_TBD, 0, 0, &hdlr));
846
0
    AVIF_CHECKRES(avifRWStreamWriteU32(s, 0));                // unsigned int(32) pre_defined = 0;
847
0
    AVIF_CHECKRES(avifRWStreamWriteChars(s, handlerType, 4)); // unsigned int(32) handler_type;
848
0
    AVIF_CHECKRES(avifRWStreamWriteZeros(s, 12));             // const unsigned int(32)[3] reserved = 0;
849
0
    AVIF_CHECKRES(avifRWStreamWriteChars(s, "", 1));          // string name; (writing null terminator)
850
0
    AVIF_CHECKRES(avifRWStreamFinishBox(s, hdlr));
851
0
    return AVIF_RESULT_OK;
852
0
}
853
854
// Write unassociated metadata items (EXIF, XMP) to a small meta box inside of a trak box.
855
// These items are implicitly associated with the track they are contained within.
856
static avifResult avifEncoderWriteTrackMetaBox(avifEncoder * encoder, avifRWStream * s)
857
0
{
858
    // Count how many non-image items (such as EXIF/XMP) are being written
859
0
    uint32_t metadataItemCount = 0;
860
0
    for (uint32_t itemIndex = 0; itemIndex < encoder->data->items.count; ++itemIndex) {
861
0
        avifEncoderItem * item = &encoder->data->items.item[itemIndex];
862
0
        if (memcmp(item->type, encoder->data->imageItemType, 4) != 0) {
863
0
            ++metadataItemCount;
864
0
        }
865
0
    }
866
0
    if (metadataItemCount == 0) {
867
        // Don't even bother writing the trak meta box
868
0
        return AVIF_RESULT_OK;
869
0
    }
870
871
0
    avifBoxMarker meta;
872
0
    AVIF_CHECKRES(avifRWStreamWriteFullBox(s, "meta", AVIF_BOX_SIZE_TBD, 0, 0, &meta));
873
874
0
    AVIF_CHECKRES(avifRWStreamWriteHandlerBox(s, "pict"));
875
876
0
    avifBoxMarker iloc;
877
0
    AVIF_CHECKRES(avifRWStreamWriteFullBox(s, "iloc", AVIF_BOX_SIZE_TBD, 0, 0, &iloc));
878
0
    AVIF_CHECKRES(avifRWStreamWriteBits(s, 4, /*bitCount=*/4));          // unsigned int(4) offset_size;
879
0
    AVIF_CHECKRES(avifRWStreamWriteBits(s, 4, /*bitCount=*/4));          // unsigned int(4) length_size;
880
0
    AVIF_CHECKRES(avifRWStreamWriteBits(s, 0, /*bitCount=*/4));          // unsigned int(4) base_offset_size;
881
0
    AVIF_CHECKRES(avifRWStreamWriteBits(s, 0, /*bitCount=*/4));          // unsigned int(4) reserved;
882
0
    AVIF_CHECKRES(avifRWStreamWriteU16(s, (uint16_t)metadataItemCount)); // unsigned int(16) item_count;
883
0
    for (uint32_t trakItemIndex = 0; trakItemIndex < encoder->data->items.count; ++trakItemIndex) {
884
0
        avifEncoderItem * item = &encoder->data->items.item[trakItemIndex];
885
0
        if (memcmp(item->type, encoder->data->imageItemType, 4) == 0) {
886
            // Skip over all non-metadata items
887
0
            continue;
888
0
        }
889
890
0
        AVIF_CHECKRES(avifRWStreamWriteU16(s, item->id));          // unsigned int(16) item_ID;
891
0
        AVIF_CHECKRES(avifRWStreamWriteU16(s, 0));                 // unsigned int(16) data_reference_index;
892
0
        AVIF_CHECKRES(avifRWStreamWriteU16(s, 1));                 // unsigned int(16) extent_count;
893
0
        AVIF_CHECKRES(avifEncoderItemAddMdatFixup(item, s));       //
894
0
        AVIF_CHECKRES(avifRWStreamWriteU32(s, 0 /* set later */)); // unsigned int(offset_size*8) extent_offset;
895
0
        AVIF_CHECKRES(avifRWStreamWriteU32(s, (uint32_t)item->metadataPayload.size)); // unsigned int(length_size*8) extent_length;
896
0
    }
897
0
    AVIF_CHECKRES(avifRWStreamFinishBox(s, iloc));
898
899
0
    avifBoxMarker iinf;
900
0
    AVIF_CHECKRES(avifRWStreamWriteFullBox(s, "iinf", AVIF_BOX_SIZE_TBD, 0, 0, &iinf));
901
0
    AVIF_CHECKRES(avifRWStreamWriteU16(s, (uint16_t)metadataItemCount)); //  unsigned int(16) entry_count;
902
0
    for (uint32_t trakItemIndex = 0; trakItemIndex < encoder->data->items.count; ++trakItemIndex) {
903
0
        avifEncoderItem * item = &encoder->data->items.item[trakItemIndex];
904
0
        if (memcmp(item->type, encoder->data->imageItemType, 4) == 0) {
905
0
            continue;
906
0
        }
907
908
0
        AVIF_ASSERT_OR_RETURN(!item->hiddenImage);
909
0
        avifBoxMarker infe;
910
0
        AVIF_CHECKRES(avifRWStreamWriteFullBox(s, "infe", AVIF_BOX_SIZE_TBD, 2, 0, &infe));
911
0
        AVIF_CHECKRES(avifRWStreamWriteU16(s, item->id));                             // unsigned int(16) item_ID;
912
0
        AVIF_CHECKRES(avifRWStreamWriteU16(s, 0));                                    // unsigned int(16) item_protection_index;
913
0
        AVIF_CHECKRES(avifRWStreamWrite(s, item->type, 4));                           // unsigned int(32) item_type;
914
0
        AVIF_CHECKRES(avifRWStreamWriteChars(s, item->infeName, item->infeNameSize)); // string item_name; (writing null terminator)
915
0
        if (item->infeContentType && item->infeContentTypeSize) { // string content_type; (writing null terminator)
916
0
            AVIF_CHECKRES(avifRWStreamWriteChars(s, item->infeContentType, item->infeContentTypeSize));
917
0
        }
918
0
        AVIF_CHECKRES(avifRWStreamFinishBox(s, infe));
919
0
    }
920
0
    AVIF_CHECKRES(avifRWStreamFinishBox(s, iinf));
921
922
0
    AVIF_CHECKRES(avifRWStreamFinishBox(s, meta));
923
0
    return AVIF_RESULT_OK;
924
0
}
925
926
static avifResult avifWriteGridPayload(avifRWData * data, uint32_t gridCols, uint32_t gridRows, uint32_t gridWidth, uint32_t gridHeight)
927
0
{
928
    // ISO/IEC 23008-12 6.6.2.3.2
929
    // aligned(8) class ImageGrid {
930
    //     unsigned int(8) version = 0;
931
    //     unsigned int(8) flags;
932
    //     FieldLength = ((flags & 1) + 1) * 16;
933
    //     unsigned int(8) rows_minus_one;
934
    //     unsigned int(8) columns_minus_one;
935
    //     unsigned int(FieldLength) output_width;
936
    //     unsigned int(FieldLength) output_height;
937
    // }
938
939
0
    uint8_t gridFlags = ((gridWidth > 65535) || (gridHeight > 65535)) ? 1 : 0;
940
941
0
    avifRWStream s;
942
0
    avifRWStreamStart(&s, data);
943
0
    AVIF_CHECKRES(avifRWStreamWriteU8(&s, 0));                       // unsigned int(8) version = 0;
944
0
    AVIF_CHECKRES(avifRWStreamWriteU8(&s, gridFlags));               // unsigned int(8) flags;
945
0
    AVIF_CHECKRES(avifRWStreamWriteU8(&s, (uint8_t)(gridRows - 1))); // unsigned int(8) rows_minus_one;
946
0
    AVIF_CHECKRES(avifRWStreamWriteU8(&s, (uint8_t)(gridCols - 1))); // unsigned int(8) columns_minus_one;
947
0
    if (gridFlags & 1) {
948
0
        AVIF_CHECKRES(avifRWStreamWriteU32(&s, gridWidth));  // unsigned int(FieldLength) output_width;
949
0
        AVIF_CHECKRES(avifRWStreamWriteU32(&s, gridHeight)); // unsigned int(FieldLength) output_height;
950
0
    } else {
951
0
        uint16_t tmpWidth = (uint16_t)gridWidth;
952
0
        uint16_t tmpHeight = (uint16_t)gridHeight;
953
0
        AVIF_CHECKRES(avifRWStreamWriteU16(&s, tmpWidth));  // unsigned int(FieldLength) output_width;
954
0
        AVIF_CHECKRES(avifRWStreamWriteU16(&s, tmpHeight)); // unsigned int(FieldLength) output_height;
955
0
    }
956
0
    avifRWStreamFinishWrite(&s);
957
0
    return AVIF_RESULT_OK;
958
0
}
959
960
static avifBool avifGainMapIdenticalChannels(const avifGainMap * gainMap)
961
0
{
962
0
    return gainMap->gainMapMin[0].n == gainMap->gainMapMin[1].n && gainMap->gainMapMin[0].n == gainMap->gainMapMin[2].n &&
963
0
           gainMap->gainMapMin[0].d == gainMap->gainMapMin[1].d && gainMap->gainMapMin[0].d == gainMap->gainMapMin[2].d &&
964
0
           gainMap->gainMapMax[0].n == gainMap->gainMapMax[1].n && gainMap->gainMapMax[0].n == gainMap->gainMapMax[2].n &&
965
0
           gainMap->gainMapMax[0].d == gainMap->gainMapMax[1].d && gainMap->gainMapMax[0].d == gainMap->gainMapMax[2].d &&
966
0
           gainMap->gainMapGamma[0].n == gainMap->gainMapGamma[1].n && gainMap->gainMapGamma[0].n == gainMap->gainMapGamma[2].n &&
967
0
           gainMap->gainMapGamma[0].d == gainMap->gainMapGamma[1].d && gainMap->gainMapGamma[0].d == gainMap->gainMapGamma[2].d &&
968
0
           gainMap->baseOffset[0].n == gainMap->baseOffset[1].n && gainMap->baseOffset[0].n == gainMap->baseOffset[2].n &&
969
0
           gainMap->baseOffset[0].d == gainMap->baseOffset[1].d && gainMap->baseOffset[0].d == gainMap->baseOffset[2].d &&
970
0
           gainMap->alternateOffset[0].n == gainMap->alternateOffset[1].n &&
971
0
           gainMap->alternateOffset[0].n == gainMap->alternateOffset[2].n &&
972
0
           gainMap->alternateOffset[0].d == gainMap->alternateOffset[1].d &&
973
0
           gainMap->alternateOffset[0].d == gainMap->alternateOffset[2].d;
974
0
}
975
976
// Returns the number of bytes written by avifWriteGainmapMetadata().
977
static uint32_t avifGainMapMetadataSize(const avifGainMap * gainMap)
978
0
{
979
0
    const uint8_t channelCount = avifGainMapIdenticalChannels(gainMap) ? 1u : 3u;
980
0
    return (uint32_t)(sizeof(uint16_t) * 2 + sizeof(uint8_t) + sizeof(uint32_t) * 4 + channelCount * sizeof(uint32_t) * 10);
981
0
}
982
983
static avifResult avifWriteGainmapMetadata(avifRWStream * s, const avifGainMap * gainMap, avifDiagnostics * diag)
984
0
{
985
0
    AVIF_CHECKRES(avifGainMapValidateMetadata(gainMap, diag));
986
0
    const size_t offset = avifRWStreamOffset(s);
987
988
    // GainMapMetadata syntax as per clause C.2.2 of ISO 21496-1:
989
990
    // GainMapVersion syntax as per clause C.2.2 of ISO 21496-1:
991
0
    const uint16_t minimumVersion = 0;
992
0
    AVIF_CHECKRES(avifRWStreamWriteBits(s, minimumVersion, 16)); // unsigned int(16) minimum_version;
993
0
    const uint16_t writerVersion = 0;
994
0
    AVIF_CHECKRES(avifRWStreamWriteBits(s, writerVersion, 16)); // unsigned int(16) writer_version;
995
996
0
    if (minimumVersion == 0) {
997
0
        const uint8_t channelCount = avifGainMapIdenticalChannels(gainMap) ? 1u : 3u;
998
0
        AVIF_CHECKRES(avifRWStreamWriteBits(s, channelCount == 3, 1));          // unsigned int(1) is_multichannel;
999
0
        AVIF_CHECKRES(avifRWStreamWriteBits(s, gainMap->useBaseColorSpace, 1)); // unsigned int(1) use_base_colour_space;
1000
0
        AVIF_CHECKRES(avifRWStreamWriteBits(s, 0, 6));                          // unsigned int(6) reserved;
1001
1002
0
        AVIF_CHECKRES(avifRWStreamWriteBits(s, gainMap->baseHdrHeadroom.n, 32)); // unsigned int(32) base_hdr_headroom_numerator;
1003
0
        AVIF_CHECKRES(avifRWStreamWriteBits(s, gainMap->baseHdrHeadroom.d, 32)); // unsigned int(32) base_hdr_headroom_denominator;
1004
0
        AVIF_CHECKRES(avifRWStreamWriteBits(s, gainMap->alternateHdrHeadroom.n, 32)); // unsigned int(32) alternate_hdr_headroom_numerator;
1005
0
        AVIF_CHECKRES(avifRWStreamWriteBits(s, gainMap->alternateHdrHeadroom.d, 32)); // unsigned int(32) alternate_hdr_headroom_denominator;
1006
1007
        // GainMapChannel channels[channel_count];
1008
0
        for (int c = 0; c < channelCount; ++c) {
1009
            // GainMapChannel syntax as per clause C.2.2 of ISO 21496-1:
1010
0
            AVIF_CHECKRES(avifRWStreamWriteBits(s, (uint32_t)gainMap->gainMapMin[c].n, 32)); // int(32) gain_map_min_numerator;
1011
0
            AVIF_CHECKRES(avifRWStreamWriteBits(s, gainMap->gainMapMin[c].d, 32)); // unsigned int(32) gain_map_min_denominator;
1012
0
            AVIF_CHECKRES(avifRWStreamWriteBits(s, (uint32_t)gainMap->gainMapMax[c].n, 32)); // int(32) gain_map_max_numerator;
1013
0
            AVIF_CHECKRES(avifRWStreamWriteBits(s, gainMap->gainMapMax[c].d, 32));   // unsigned int(32) gain_map_max_denominator;
1014
0
            AVIF_CHECKRES(avifRWStreamWriteBits(s, gainMap->gainMapGamma[c].n, 32)); // unsigned int(32) gamma_numerator;
1015
0
            AVIF_CHECKRES(avifRWStreamWriteBits(s, gainMap->gainMapGamma[c].d, 32)); // unsigned int(32) gamma_denominator;
1016
0
            AVIF_CHECKRES(avifRWStreamWriteBits(s, (uint32_t)gainMap->baseOffset[c].n, 32)); // int(32) base_offset_numerator;
1017
0
            AVIF_CHECKRES(avifRWStreamWriteBits(s, gainMap->baseOffset[c].d, 32)); // unsigned int(32) base_offset_denominator;
1018
0
            AVIF_CHECKRES(avifRWStreamWriteBits(s, (uint32_t)gainMap->alternateOffset[c].n, 32)); // int(32) alternate_offset_numerator;
1019
0
            AVIF_CHECKRES(avifRWStreamWriteBits(s, gainMap->alternateOffset[c].d, 32)); // unsigned int(32) alternate_offset_denominator;
1020
0
        }
1021
0
    }
1022
1023
0
    AVIF_ASSERT_OR_RETURN(avifRWStreamOffset(s) == offset + avifGainMapMetadataSize(gainMap));
1024
0
    return AVIF_RESULT_OK;
1025
0
}
1026
1027
static avifResult avifWriteToneMappedImagePayload(avifRWData * data, const avifGainMap * gainMap, avifDiagnostics * diag)
1028
0
{
1029
0
    avifRWStream s;
1030
0
    avifRWStreamStart(&s, data);
1031
    // ToneMapImage syntax as per section 6.6.2.4.2 of ISO/IEC 23008-12:2024
1032
    // amendment "Support for tone map derived image items and other improvements":
1033
0
    const uint8_t version = 0;
1034
0
    AVIF_CHECKRES(avifRWStreamWriteU8(&s, version)); // unsigned int(8) version = 0;
1035
0
    if (version == 0) {
1036
0
        AVIF_CHECKRES(avifWriteGainmapMetadata(&s, gainMap, diag)); // GainMapMetadata;
1037
0
    }
1038
0
    avifRWStreamFinishWrite(&s);
1039
0
    return AVIF_RESULT_OK;
1040
0
}
1041
1042
size_t avifEncoderGetGainMapSizeBytes(avifEncoder * encoder)
1043
0
{
1044
0
    return encoder->data->gainMapSizeBytes;
1045
0
}
1046
1047
// Sets altImageMetadata's metadata values to represent the "alternate" image as if applying the gain map to the base image.
1048
// For grid images, imageWithGainMap is the metadata of the first cell. gridWidth and gridHeight are the dimensions of the
1049
// full image.
1050
static avifResult avifImageCopyAltImageMetadata(avifImage * altImageMetadata, const avifImage * imageWithGainMap, uint32_t gridWidth, uint32_t gridHeight)
1051
0
{
1052
0
    altImageMetadata->width = gridWidth;
1053
0
    altImageMetadata->height = gridHeight;
1054
0
    AVIF_CHECKRES(avifRWDataSet(&altImageMetadata->icc, imageWithGainMap->gainMap->altICC.data, imageWithGainMap->gainMap->altICC.size));
1055
0
    altImageMetadata->colorPrimaries = imageWithGainMap->gainMap->altColorPrimaries;
1056
0
    altImageMetadata->transferCharacteristics = imageWithGainMap->gainMap->altTransferCharacteristics;
1057
0
    altImageMetadata->matrixCoefficients = imageWithGainMap->gainMap->altMatrixCoefficients;
1058
0
    altImageMetadata->yuvRange = imageWithGainMap->gainMap->altYUVRange;
1059
0
    altImageMetadata->depth = imageWithGainMap->gainMap->altDepth
1060
0
                                  ? imageWithGainMap->gainMap->altDepth
1061
0
                                  : AVIF_MAX(imageWithGainMap->depth, imageWithGainMap->gainMap->image->depth);
1062
0
    altImageMetadata->yuvFormat = (imageWithGainMap->gainMap->altPlaneCount == 1) ? AVIF_PIXEL_FORMAT_YUV400 : AVIF_PIXEL_FORMAT_YUV444;
1063
0
    altImageMetadata->clli = imageWithGainMap->gainMap->altCLLI;
1064
0
    return AVIF_RESULT_OK;
1065
0
}
1066
1067
static avifResult avifEncoderWriteSampleTransformTokens(avifRWStream * s, const avifSampleTransformExpression * expression)
1068
0
{
1069
0
    AVIF_ASSERT_OR_RETURN(expression->count <= 255);
1070
0
    AVIF_CHECKRES(avifRWStreamWriteU8(s, (uint8_t)expression->count)); // unsigned int(8) token_count;
1071
1072
0
    for (uint32_t t = 0; t < expression->count; ++t) {
1073
0
        const avifSampleTransformToken * token = &expression->tokens[t];
1074
1075
0
        if (token->type == AVIF_SAMPLE_TRANSFORM_CONSTANT) {
1076
0
            AVIF_CHECKRES(avifRWStreamWriteU8(s, token->type)); // unsigned int(8) token;
1077
0
            const uint32_t constant = (uint32_t)token->constant;
1078
0
            AVIF_CHECKRES(avifRWStreamWriteU32(s, constant)); // signed int(1<<(bit_depth+3)) constant;
1079
0
        } else if (token->type == AVIF_SAMPLE_TRANSFORM_INPUT_IMAGE_ITEM_INDEX) {
1080
0
            AVIF_CHECKRES(avifRWStreamWriteU8(s, token->inputImageItemIndex)); // unsigned int(8) token;
1081
0
        } else {
1082
            // Operator.
1083
0
            AVIF_CHECKRES(avifRWStreamWriteU8(s, token->type)); // unsigned int(8) token;
1084
0
        }
1085
0
    }
1086
0
    return AVIF_RESULT_OK;
1087
0
}
1088
1089
static avifResult avifEncoderWriteSampleTransformPayload(avifEncoder * encoder, avifRWData * data)
1090
0
{
1091
0
    avifRWStream s;
1092
0
    avifRWStreamStart(&s, data);
1093
0
    AVIF_CHECKRES(avifRWStreamWriteBits(&s, 0, /*bitCount=*/2)); // unsigned int(2) version = 0;
1094
0
    AVIF_CHECKRES(avifRWStreamWriteBits(&s, 0, /*bitCount=*/4)); // unsigned int(4) reserved;
1095
    // AVIF_SAMPLE_TRANSFORM_BIT_DEPTH_32 is necessary because the two input images
1096
    // once combined use 16-bit unsigned values, but intermediate results are stored in signed integers.
1097
0
    AVIF_CHECKRES(avifRWStreamWriteBits(&s, AVIF_SAMPLE_TRANSFORM_BIT_DEPTH_32, /*bitCount=*/2)); // unsigned int(2) bit_depth;
1098
1099
0
    avifSampleTransformExpression expression = { 0 };
1100
0
    AVIF_CHECKRES(avifSampleTransformRecipeToExpression(encoder->sampleTransformRecipe, &expression));
1101
0
    const avifResult result = avifEncoderWriteSampleTransformTokens(&s, &expression);
1102
0
    avifArrayDestroy(&expression);
1103
0
    if (result != AVIF_RESULT_OK) {
1104
0
        avifDiagnosticsPrintf(&encoder->diag, "Failed to write sample transform metadata for recipe %d", (int)encoder->sampleTransformRecipe);
1105
0
        return result;
1106
0
    }
1107
1108
0
    avifRWStreamFinishWrite(&s);
1109
0
    return AVIF_RESULT_OK;
1110
0
}
1111
1112
static avifResult avifEncoderDataCreateExifItem(avifEncoderData * data, const avifRWData * exif)
1113
0
{
1114
0
    size_t exifTiffHeaderOffset;
1115
0
    const avifResult result = avifGetExifTiffHeaderOffset(exif->data, exif->size, &exifTiffHeaderOffset);
1116
0
    if (result != AVIF_RESULT_OK) {
1117
        // Couldn't find the TIFF header
1118
0
        return result;
1119
0
    }
1120
1121
0
    avifEncoderItem * exifItem = avifEncoderDataCreateItem(data, "Exif", "Exif", 5, 0);
1122
0
    if (!exifItem) {
1123
0
        return AVIF_RESULT_OUT_OF_MEMORY;
1124
0
    }
1125
0
    exifItem->irefToID = data->primaryItemID;
1126
0
    exifItem->irefType = "cdsc";
1127
1128
0
    const uint32_t offset32bit = avifHTONL((uint32_t)exifTiffHeaderOffset);
1129
0
    AVIF_CHECKRES(avifRWDataRealloc(&exifItem->metadataPayload, sizeof(offset32bit) + exif->size));
1130
0
    memcpy(exifItem->metadataPayload.data, &offset32bit, sizeof(offset32bit));
1131
0
    memcpy(exifItem->metadataPayload.data + sizeof(offset32bit), exif->data, exif->size);
1132
0
    return AVIF_RESULT_OK;
1133
0
}
1134
1135
static avifResult avifEncoderDataCreateXMPItem(avifEncoderData * data, const avifRWData * xmp)
1136
0
{
1137
0
    avifEncoderItem * xmpItem = avifEncoderDataCreateItem(data, "mime", "XMP", 4, 0);
1138
0
    if (!xmpItem) {
1139
0
        return AVIF_RESULT_OUT_OF_MEMORY;
1140
0
    }
1141
0
    xmpItem->irefToID = data->primaryItemID;
1142
0
    xmpItem->irefType = "cdsc";
1143
1144
0
    xmpItem->infeContentType = xmpContentType;
1145
0
    xmpItem->infeContentTypeSize = xmpContentTypeSize;
1146
0
    AVIF_CHECKRES(avifRWDataSet(&xmpItem->metadataPayload, xmp->data, xmp->size));
1147
0
    return AVIF_RESULT_OK;
1148
0
}
1149
1150
// Same as avifImageCopy() but pads the dstImage with border pixel values to reach dstWidth and dstHeight.
1151
static avifResult avifImageCopyAndPad(avifImage * const dstImage, const avifImage * srcImage, uint32_t dstWidth, uint32_t dstHeight)
1152
0
{
1153
0
    AVIF_ASSERT_OR_RETURN(dstImage);
1154
0
    AVIF_ASSERT_OR_RETURN(!dstImage->width && !dstImage->height); // dstImage is not set yet.
1155
0
    AVIF_ASSERT_OR_RETURN(dstWidth >= srcImage->width);
1156
0
    AVIF_ASSERT_OR_RETURN(dstHeight >= srcImage->height);
1157
1158
    // Copy all fields but do not allocate the planes.
1159
0
    AVIF_CHECKRES(avifImageCopy(dstImage, srcImage, (avifPlanesFlag)0));
1160
0
    dstImage->width = dstWidth;
1161
0
    dstImage->height = dstHeight;
1162
1163
0
    if (srcImage->yuvPlanes[AVIF_CHAN_Y]) {
1164
0
        AVIF_CHECKRES(avifImageAllocatePlanes(dstImage, AVIF_PLANES_YUV));
1165
0
    }
1166
0
    if (srcImage->alphaPlane) {
1167
0
        AVIF_CHECKRES(avifImageAllocatePlanes(dstImage, AVIF_PLANES_A));
1168
0
    }
1169
0
    const avifBool usesU16 = avifImageUsesU16(srcImage);
1170
0
    for (int plane = AVIF_CHAN_Y; plane <= AVIF_CHAN_A; ++plane) {
1171
0
        const uint8_t * srcRow = avifImagePlane(srcImage, plane);
1172
0
        const uint32_t srcRowBytes = avifImagePlaneRowBytes(srcImage, plane);
1173
0
        const uint32_t srcPlaneWidth = avifImagePlaneWidth(srcImage, plane);
1174
0
        const uint32_t srcPlaneHeight = avifImagePlaneHeight(srcImage, plane); // 0 for A if no alpha and 0 for UV if 4:0:0.
1175
0
        const size_t srcPlaneWidthBytes = (size_t)srcPlaneWidth << usesU16;
1176
1177
0
        uint8_t * dstRow = avifImagePlane(dstImage, plane);
1178
0
        const uint32_t dstRowBytes = avifImagePlaneRowBytes(dstImage, plane);
1179
0
        const uint32_t dstPlaneWidth = avifImagePlaneWidth(dstImage, plane);
1180
0
        const uint32_t dstPlaneHeight = avifImagePlaneHeight(dstImage, plane); // 0 for A if no alpha and 0 for UV if 4:0:0.
1181
0
        const size_t dstPlaneWidthBytes = (size_t)dstPlaneWidth << usesU16;
1182
1183
0
        for (uint32_t j = 0; j < srcPlaneHeight; ++j) {
1184
0
            memcpy(dstRow, srcRow, srcPlaneWidthBytes);
1185
1186
            // Pad columns.
1187
0
            if (dstPlaneWidth > srcPlaneWidth) {
1188
0
                if (usesU16) {
1189
0
                    uint16_t * dstRow16 = (uint16_t *)dstRow;
1190
0
                    for (uint32_t x = srcPlaneWidth; x < dstPlaneWidth; ++x) {
1191
0
                        dstRow16[x] = dstRow16[srcPlaneWidth - 1];
1192
0
                    }
1193
0
                } else {
1194
0
                    memset(&dstRow[srcPlaneWidth], dstRow[srcPlaneWidth - 1], dstPlaneWidth - srcPlaneWidth);
1195
0
                }
1196
0
            }
1197
0
            srcRow += srcRowBytes;
1198
0
            dstRow += dstRowBytes;
1199
0
        }
1200
1201
        // Pad rows.
1202
0
        for (uint32_t j = srcPlaneHeight; j < dstPlaneHeight; ++j) {
1203
0
            memcpy(dstRow, dstRow - dstRowBytes, dstPlaneWidthBytes);
1204
0
            dstRow += dstRowBytes;
1205
0
        }
1206
0
    }
1207
0
    return AVIF_RESULT_OK;
1208
0
}
1209
1210
static int avifGetQuality(int quality, int minQuantizer, int maxQuantizer)
1211
0
{
1212
0
    int quantizer;
1213
1214
0
    if (quality == AVIF_QUALITY_DEFAULT) {
1215
        // In older libavif releases, avifEncoder didn't have the quality and qualityAlpha fields.
1216
        // Supply a default value for quality.
1217
0
        quantizer = (minQuantizer + maxQuantizer) / 2;
1218
0
        quantizer = AVIF_CLAMP(quantizer, 0, 63);
1219
0
        quality = ((63 - quantizer) * 100 + 31) / 63;
1220
0
    } else {
1221
0
        quality = AVIF_CLAMP(quality, 0, 100);
1222
0
    }
1223
1224
0
    return quality;
1225
0
}
1226
1227
static const char infeNameColor[] = "Color";
1228
static const char infeNameAlpha[] = "Alpha";
1229
static const char infeNameGainMap[] = "GMap";
1230
static const char infeNameSampleTransform[] = "SampleTransform";
1231
1232
static const char * getInfeName(avifItemCategory itemCategory)
1233
0
{
1234
0
    if (avifIsAlpha(itemCategory)) {
1235
0
        return infeNameAlpha;
1236
0
    }
1237
0
    if (itemCategory == AVIF_ITEM_GAIN_MAP) {
1238
0
        return infeNameGainMap;
1239
0
    }
1240
0
    if (itemCategory >= AVIF_SAMPLE_TRANSFORM_MIN_CATEGORY && itemCategory <= AVIF_SAMPLE_TRANSFORM_MAX_CATEGORY) {
1241
0
        return infeNameSampleTransform;
1242
0
    }
1243
0
    return infeNameColor;
1244
0
}
1245
1246
// Adds the items for a single cell or a grid of cells. Outputs the topLevelItemID which is
1247
// the only item if there is exactly one cell, or the grid item for multiple cells.
1248
// Note: The topLevelItemID output argument has the type uint16_t* instead of avifEncoderItem** because
1249
//       the avifEncoderItem pointer may be invalidated by a call to avifEncoderDataCreateItem().
1250
static avifResult avifEncoderAddImageItems(avifEncoder * encoder,
1251
                                           uint32_t gridCols,
1252
                                           uint32_t gridRows,
1253
                                           uint32_t gridWidth,
1254
                                           uint32_t gridHeight,
1255
                                           avifItemCategory itemCategory,
1256
                                           uint16_t * topLevelItemID)
1257
0
{
1258
0
    const uint32_t cellCount = gridCols * gridRows;
1259
0
    const char * infeName = getInfeName(itemCategory);
1260
0
    const size_t infeNameSize = strlen(infeName) + 1;
1261
1262
0
    if (cellCount > 1) {
1263
0
        avifEncoderItem * gridItem = avifEncoderDataCreateItem(encoder->data, "grid", infeName, infeNameSize, 0);
1264
0
        AVIF_CHECKRES(avifWriteGridPayload(&gridItem->metadataPayload, gridCols, gridRows, gridWidth, gridHeight));
1265
0
        gridItem->itemCategory = itemCategory;
1266
0
        gridItem->gridCols = gridCols;
1267
0
        gridItem->gridRows = gridRows;
1268
0
        gridItem->gridWidth = gridWidth;
1269
0
        gridItem->gridHeight = gridHeight;
1270
0
        *topLevelItemID = gridItem->id;
1271
0
    }
1272
1273
0
    for (uint32_t cellIndex = 0; cellIndex < cellCount; ++cellIndex) {
1274
0
        avifEncoderItem * item =
1275
0
            avifEncoderDataCreateItem(encoder->data, encoder->data->imageItemType, infeName, infeNameSize, cellIndex);
1276
0
        AVIF_CHECKERR(item, AVIF_RESULT_OUT_OF_MEMORY);
1277
0
        AVIF_CHECKRES(avifCodecCreate(encoder->codecChoice, AVIF_CODEC_FLAG_CAN_ENCODE, &item->codec));
1278
0
        item->codec->csOptions = encoder->csOptions;
1279
0
        item->codec->diag = &encoder->diag;
1280
0
        item->itemCategory = itemCategory;
1281
0
        item->extraLayerCount = encoder->extraLayerCount;
1282
1283
0
        if (cellCount > 1) {
1284
0
            item->dimgFromID = *topLevelItemID;
1285
0
            item->hiddenImage = AVIF_TRUE;
1286
0
        } else {
1287
0
            *topLevelItemID = item->id;
1288
0
        }
1289
0
    }
1290
0
    return AVIF_RESULT_OK;
1291
0
}
1292
1293
static avifResult avifEncoderCreateBitDepthExtensionItems(avifEncoder * encoder,
1294
                                                          uint32_t gridCols,
1295
                                                          uint32_t gridRows,
1296
                                                          uint32_t gridWidth,
1297
                                                          uint32_t gridHeight,
1298
                                                          uint16_t colorItemID)
1299
0
{
1300
0
    AVIF_ASSERT_OR_RETURN(encoder->sampleTransformRecipe == AVIF_SAMPLE_TRANSFORM_BIT_DEPTH_EXTENSION_8B_8B ||
1301
0
                          encoder->sampleTransformRecipe == AVIF_SAMPLE_TRANSFORM_BIT_DEPTH_EXTENSION_12B_4B ||
1302
0
                          encoder->sampleTransformRecipe == AVIF_SAMPLE_TRANSFORM_BIT_DEPTH_EXTENSION_12B_8B_OVERLAP_4B);
1303
1304
    // There are multiple possible ISOBMFF box hierarchies for translucent images,
1305
    // using 'sato' (Sample Transform) derived image items:
1306
    //  - a primary 'sato' item uses a main color coded item and a hidden color coded item; each color coded
1307
    //    item has an auxiliary alpha coded item; the main color coded item and the 'sato' item are in
1308
    //    an 'altr' group (backward-compatible, implemented)
1309
    //  - a primary 'sato' item uses a main color coded item and a hidden color coded item; the primary
1310
    //    'sato' item has an auxiliary alpha 'sato' item using two alpha coded items (backward-incompatible)
1311
    // Likewise, there are multiple possible ISOBMFF box hierarchies for bit-depth-extended grids,
1312
    // using 'sato' (Sample Transform) derived image items:
1313
    //  - a primary color 'grid', an auxiliary alpha 'grid', a hidden color 'grid', a hidden auxiliary alpha 'grid'
1314
    //    and a 'sato' using the two color 'grid's as input items in this order; the primary color item
1315
    //    and the 'sato' item being in an 'altr' group (backward-compatible, implemented)
1316
    //  - a primary 'grid' of 'sato' cells and an auxiliary alpha 'grid' of 'sato' cells (backward-incompatible)
1317
0
    avifEncoderItem * sampleTransformItem = avifEncoderDataCreateItem(encoder->data,
1318
0
                                                                      "sato",
1319
0
                                                                      infeNameSampleTransform,
1320
0
                                                                      /*infeNameSize=*/strlen(infeNameSampleTransform) + 1,
1321
0
                                                                      /*cellIndex=*/0);
1322
0
    AVIF_CHECKRES(avifEncoderWriteSampleTransformPayload(encoder, &sampleTransformItem->metadataPayload));
1323
0
    sampleTransformItem->itemCategory = AVIF_ITEM_SAMPLE_TRANSFORM;
1324
0
    uint16_t sampleTransformItemID = sampleTransformItem->id;
1325
    // 'altr' group
1326
0
    AVIF_ASSERT_OR_RETURN(encoder->data->alternativeItemIDs.count == 0);
1327
0
    uint16_t * alternativeItemID = (uint16_t *)avifArrayPush(&encoder->data->alternativeItemIDs);
1328
0
    AVIF_CHECKERR(alternativeItemID != NULL, AVIF_RESULT_OUT_OF_MEMORY);
1329
0
    *alternativeItemID = sampleTransformItem->id;
1330
0
    alternativeItemID = (uint16_t *)avifArrayPush(&encoder->data->alternativeItemIDs);
1331
0
    AVIF_CHECKERR(alternativeItemID != NULL, AVIF_RESULT_OUT_OF_MEMORY);
1332
0
    *alternativeItemID = colorItemID;
1333
1334
0
    uint16_t bitDepthExtensionColorItemId;
1335
0
    AVIF_CHECKRES(
1336
0
        avifEncoderAddImageItems(encoder, gridCols, gridRows, gridWidth, gridHeight, AVIF_ITEM_SAMPLE_TRANSFORM_INPUT_0_COLOR, &bitDepthExtensionColorItemId));
1337
0
    avifEncoderItem * bitDepthExtensionColorItem = avifEncoderDataFindItemByID(encoder->data, bitDepthExtensionColorItemId);
1338
0
    assert(bitDepthExtensionColorItem);
1339
0
    bitDepthExtensionColorItem->hiddenImage = AVIF_TRUE;
1340
1341
    // Set the color and bit depth extension items' dimgFromID value to point to the sample transform item.
1342
    // The color item shall be first, and the bit depth extension item second. avifEncoderFinish() writes the
1343
    // dimg item references in item id order, so as long as colorItemID < bitDepthExtensionColorItemId, the order
1344
    // will be correct.
1345
0
    AVIF_ASSERT_OR_RETURN(colorItemID < bitDepthExtensionColorItemId);
1346
0
    avifEncoderItem * colorItem = avifEncoderDataFindItemByID(encoder->data, colorItemID);
1347
0
    AVIF_ASSERT_OR_RETURN(colorItem != NULL);
1348
0
    AVIF_ASSERT_OR_RETURN(colorItem->dimgFromID == 0); // The internal API only allows one dimg value per item.
1349
0
    colorItem->dimgFromID = sampleTransformItemID;
1350
0
    bitDepthExtensionColorItem->dimgFromID = sampleTransformItemID;
1351
1352
0
    if (encoder->data->alphaPresent) {
1353
0
        uint16_t bitDepthExtensionAlphaItemId;
1354
0
        AVIF_CHECKRES(
1355
0
            avifEncoderAddImageItems(encoder, gridCols, gridRows, gridWidth, gridHeight, AVIF_ITEM_SAMPLE_TRANSFORM_INPUT_0_ALPHA, &bitDepthExtensionAlphaItemId));
1356
0
        avifEncoderItem * bitDepthExtensionAlphaItem = avifEncoderDataFindItemByID(encoder->data, bitDepthExtensionAlphaItemId);
1357
0
        assert(bitDepthExtensionAlphaItem);
1358
0
        bitDepthExtensionAlphaItem->irefType = "auxl";
1359
0
        bitDepthExtensionAlphaItem->irefToID = bitDepthExtensionColorItemId;
1360
0
        if (encoder->data->imageMetadata->alphaPremultiplied) {
1361
            // The reference may have changed; fetch it again.
1362
0
            bitDepthExtensionColorItem = avifEncoderDataFindItemByID(encoder->data, bitDepthExtensionColorItemId);
1363
0
            assert(bitDepthExtensionColorItem);
1364
0
            bitDepthExtensionColorItem->irefType = "prem";
1365
0
            bitDepthExtensionColorItem->irefToID = bitDepthExtensionAlphaItemId;
1366
0
        }
1367
0
    }
1368
0
    return AVIF_RESULT_OK;
1369
0
}
1370
1371
// Same as avifImageApplyExpression() but for the expression (inputImageItem [op] constant).
1372
// Convenience function.
1373
static avifResult avifImageApplyImgOpConst(avifImage * result,
1374
                                           const avifImage * inputImageItem,
1375
                                           avifSampleTransformTokenType op,
1376
                                           int32_t constant,
1377
                                           avifPlanesFlags planes)
1378
0
{
1379
    // Postfix notation.
1380
0
    const avifSampleTransformToken tokens[] = { { AVIF_SAMPLE_TRANSFORM_INPUT_IMAGE_ITEM_INDEX, 0, /*inputImageItemIndex=*/1 },
1381
0
                                                { AVIF_SAMPLE_TRANSFORM_CONSTANT, constant, 0 },
1382
0
                                                { (uint8_t)op, 0, 0 } };
1383
0
    return avifImageApplyOperations(result, AVIF_SAMPLE_TRANSFORM_BIT_DEPTH_32, /*numTokens=*/3, tokens, /*numInputImageItems=*/1, &inputImageItem, planes);
1384
0
}
1385
1386
static avifResult avifImageCreateAllocate(avifImage ** sampleTransformedImage, const avifImage * reference, uint32_t numBits, avifPlanesFlag planes)
1387
0
{
1388
0
    *sampleTransformedImage = avifImageCreate(reference->width, reference->height, numBits, reference->yuvFormat);
1389
0
    AVIF_CHECKERR(*sampleTransformedImage != NULL, AVIF_RESULT_OUT_OF_MEMORY);
1390
0
    return avifImageAllocatePlanes(*sampleTransformedImage, planes);
1391
0
}
1392
1393
// Finds the encoded base image and decodes it. Callers of this function must free
1394
// *codec and *decodedBaseImage if not null, whether the function succeeds or not.
1395
static avifResult avifEncoderDecodeSatoBaseImage(avifEncoder * encoder,
1396
                                                 uint32_t cellIndex,
1397
                                                 const avifImage * original,
1398
                                                 uint32_t numBits,
1399
                                                 avifPlanesFlag planes,
1400
                                                 avifCodec ** codec,
1401
                                                 avifImage ** decodedBaseImage)
1402
0
{
1403
0
    avifDecodeSample sample;
1404
0
    memset(&sample, 0, sizeof(sample));
1405
0
    sample.spatialID = AVIF_SPATIAL_ID_UNSET;
1406
1407
    // Find the encoded bytes of the base image item.
1408
0
    for (uint32_t itemIndex = 0; itemIndex < encoder->data->items.count; ++itemIndex) {
1409
0
        avifEncoderItem * item = &encoder->data->items.item[itemIndex];
1410
0
        if (item->codec == NULL) {
1411
0
            continue; // Non-image item such as metadata.
1412
0
        }
1413
0
        if ((item->itemCategory != AVIF_ITEM_COLOR || planes != AVIF_PLANES_YUV) &&
1414
0
            (item->itemCategory != AVIF_ITEM_ALPHA || planes != AVIF_PLANES_A)) {
1415
0
            continue;
1416
0
        }
1417
0
        if (item->cellIndex != cellIndex) {
1418
0
            continue;
1419
0
        }
1420
1421
0
        AVIF_ASSERT_OR_RETURN(item->encodeOutput != NULL);
1422
0
        AVIF_ASSERT_OR_RETURN(item->encodeOutput->samples.count == 1);
1423
0
        AVIF_ASSERT_OR_RETURN(item->encodeOutput->samples.sample[0].data.size != 0);
1424
0
        AVIF_ASSERT_OR_RETURN(sample.data.size == 0); // There should be only one base item.
1425
0
        sample.data.data = item->encodeOutput->samples.sample[0].data.data;
1426
0
        sample.data.size = item->encodeOutput->samples.sample[0].data.size;
1427
0
    }
1428
0
    AVIF_ASSERT_OR_RETURN(sample.data.size != 0); // There should be at least one base item.
1429
1430
0
    AVIF_CHECKRES(avifCodecCreate(AVIF_CODEC_CHOICE_AUTO, AVIF_CODEC_FLAG_CAN_DECODE, codec));
1431
0
    (*codec)->diag = &encoder->diag;
1432
0
    (*codec)->maxThreads = encoder->maxThreads;
1433
0
    (*codec)->imageSizeLimit = AVIF_DEFAULT_IMAGE_SIZE_LIMIT;
1434
0
    (*codec)->imageDimensionLimit = AVIF_DEFAULT_IMAGE_DIMENSION_LIMIT;
1435
0
    AVIF_CHECKRES(avifImageCreateAllocate(decodedBaseImage, original, numBits, planes));
1436
0
    avifBool isLimitedRangeAlpha = AVIF_FALSE; // Ignored.
1437
0
    AVIF_CHECKERR((*codec)->getNextImage(*codec, &sample, planes == AVIF_PLANES_A, &isLimitedRangeAlpha, *decodedBaseImage),
1438
0
                  AVIF_RESULT_ENCODE_SAMPLE_TRANSFORM_FAILED);
1439
0
    return AVIF_RESULT_OK;
1440
0
}
1441
1442
static avifResult avifEncoderCreateSatoImage(avifEncoder * encoder,
1443
                                             const avifEncoderItem * item,
1444
                                             avifBool itemWillBeEncodedLosslessly,
1445
                                             const avifImage * image,
1446
                                             avifImage ** sampleTransformedImage)
1447
0
{
1448
0
    const avifPlanesFlag planes = avifIsAlpha(item->itemCategory) ? AVIF_PLANES_A : AVIF_PLANES_YUV;
1449
    // The first image item used as input to the 'sato' Sample Transform derived image item.
1450
0
    avifBool isBase = item->itemCategory == AVIF_ITEM_COLOR || item->itemCategory == AVIF_ITEM_ALPHA;
1451
0
    if (!isBase) {
1452
        // The second image item used as input to the 'sato' Sample Transform derived image item.
1453
0
        AVIF_ASSERT_OR_RETURN(item->itemCategory >= AVIF_SAMPLE_TRANSFORM_MIN_CATEGORY &&
1454
0
                              item->itemCategory <= AVIF_SAMPLE_TRANSFORM_MAX_CATEGORY);
1455
0
    }
1456
1457
0
    if (encoder->sampleTransformRecipe == AVIF_SAMPLE_TRANSFORM_BIT_DEPTH_EXTENSION_8B_8B) {
1458
0
        if (isBase) {
1459
0
            AVIF_CHECKRES(avifImageCreateAllocate(sampleTransformedImage, image, 8, planes));
1460
0
            AVIF_CHECKRES(avifImageApplyImgOpConst(*sampleTransformedImage, image, AVIF_SAMPLE_TRANSFORM_QUOTIENT, 256, planes));
1461
0
        } else {
1462
0
            AVIF_CHECKRES(avifImageCreateAllocate(sampleTransformedImage, image, 8, planes));
1463
0
            AVIF_CHECKRES(avifImageApplyImgOpConst(*sampleTransformedImage, image, AVIF_SAMPLE_TRANSFORM_AND, 255, planes));
1464
0
        }
1465
0
    } else if (encoder->sampleTransformRecipe == AVIF_SAMPLE_TRANSFORM_BIT_DEPTH_EXTENSION_12B_4B) {
1466
0
        if (isBase) {
1467
0
            AVIF_CHECKRES(avifImageCreateAllocate(sampleTransformedImage, image, 12, planes));
1468
0
            AVIF_CHECKRES(avifImageApplyImgOpConst(*sampleTransformedImage, image, AVIF_SAMPLE_TRANSFORM_QUOTIENT, 16, planes));
1469
0
        } else {
1470
0
            AVIF_CHECKRES(avifImageCreateAllocate(sampleTransformedImage, image, 8, planes));
1471
0
            AVIF_CHECKRES(avifImageApplyImgOpConst(*sampleTransformedImage, image, AVIF_SAMPLE_TRANSFORM_AND, 15, planes));
1472
            // AVIF only supports 8, 10 or 12-bit image items. Scale the samples to fit the range.
1473
            // Note: The samples could be encoded as is without being shifted left before encoding,
1474
            //       but they would not be shifted right after decoding either. Right shifting after
1475
            //       decoding provides a guarantee on the range of values and on the lack of integer
1476
            //       overflow, so it is safer to do these extra steps.
1477
            //       It also makes more sense from a compression point-of-view to use the full range.
1478
            // Transform in-place.
1479
0
            AVIF_CHECKRES(
1480
0
                avifImageApplyImgOpConst(*sampleTransformedImage, *sampleTransformedImage, AVIF_SAMPLE_TRANSFORM_PRODUCT, 16, planes));
1481
0
            if (!itemWillBeEncodedLosslessly) {
1482
                // Small loss at encoding could be amplified by the truncation caused by the right
1483
                // shift after decoding. Offset sample values now, before encoding, to round rather
1484
                // than floor the samples shifted after decoding.
1485
                // Note: Samples were just left shifted by numShiftedBits, so adding less than
1486
                //       (1<<numShiftedBits) will not trigger any integer overflow.
1487
                // Transform in-place.
1488
0
                AVIF_CHECKRES(
1489
0
                    avifImageApplyImgOpConst(*sampleTransformedImage, *sampleTransformedImage, AVIF_SAMPLE_TRANSFORM_SUM, 7, planes));
1490
0
            }
1491
0
        }
1492
0
    } else {
1493
0
        AVIF_CHECKERR(encoder->sampleTransformRecipe == AVIF_SAMPLE_TRANSFORM_BIT_DEPTH_EXTENSION_12B_8B_OVERLAP_4B,
1494
0
                      AVIF_RESULT_NOT_IMPLEMENTED);
1495
0
        if (isBase) {
1496
0
            AVIF_CHECKRES(avifImageCreateAllocate(sampleTransformedImage, image, 12, planes));
1497
0
            AVIF_CHECKRES(avifImageApplyImgOpConst(*sampleTransformedImage, image, AVIF_SAMPLE_TRANSFORM_QUOTIENT, 16, planes));
1498
0
        } else {
1499
0
            AVIF_CHECKRES(avifImageCreateAllocate(sampleTransformedImage, image, 8, planes));
1500
0
            avifCodec * codec = NULL;
1501
0
            avifImage * decodedBaseImage = NULL;
1502
0
            avifResult result = avifEncoderDecodeSatoBaseImage(encoder, item->cellIndex, image, 12, planes, &codec, &decodedBaseImage);
1503
0
            if (result == AVIF_RESULT_OK) {
1504
                // decoded = main*16+hidden-128 so hidden = clamp_8b(original-main*16+128). Postfix notation.
1505
0
                const avifSampleTransformToken tokens[] = { { AVIF_SAMPLE_TRANSFORM_INPUT_IMAGE_ITEM_INDEX, 0, /*inputImageItemIndex=*/1 },
1506
0
                                                            { AVIF_SAMPLE_TRANSFORM_INPUT_IMAGE_ITEM_INDEX, 0, /*inputImageItemIndex=*/2 },
1507
0
                                                            { AVIF_SAMPLE_TRANSFORM_CONSTANT, /*constant=*/16, 0 },
1508
0
                                                            { AVIF_SAMPLE_TRANSFORM_PRODUCT, 0, 0 },
1509
0
                                                            { AVIF_SAMPLE_TRANSFORM_DIFFERENCE, 0, 0 },
1510
0
                                                            { AVIF_SAMPLE_TRANSFORM_CONSTANT, /*constant=*/128, 0 },
1511
0
                                                            { AVIF_SAMPLE_TRANSFORM_SUM, 0, 0 } };
1512
                // image is "original" (index 1) and decodedBaseImage is "main" (index 2) in the formula above.
1513
0
                const avifImage * inputImageItems[] = { image, decodedBaseImage };
1514
0
                result = avifImageApplyOperations(*sampleTransformedImage,
1515
0
                                                  AVIF_SAMPLE_TRANSFORM_BIT_DEPTH_32,
1516
0
                                                  /*numTokens=*/7,
1517
0
                                                  tokens,
1518
0
                                                  /*numInputImageItems=*/2,
1519
0
                                                  inputImageItems,
1520
0
                                                  planes);
1521
0
            }
1522
0
            if (decodedBaseImage) {
1523
0
                avifImageDestroy(decodedBaseImage);
1524
0
            }
1525
0
            if (codec) {
1526
0
                avifCodecDestroy(codec);
1527
0
            }
1528
0
            AVIF_CHECKRES(result);
1529
0
        }
1530
0
    }
1531
0
    return AVIF_RESULT_OK;
1532
0
}
1533
1534
static avifResult avifEncoderCreateBitDepthExtensionImage(avifEncoder * encoder,
1535
                                                          const avifEncoderItem * item,
1536
                                                          avifBool itemWillBeEncodedLosslessly,
1537
                                                          const avifImage * image,
1538
                                                          avifImage ** sampleTransformedImage)
1539
0
{
1540
0
    AVIF_ASSERT_OR_RETURN(image->depth == 16); // Other bit depths could be supported but for now it is 16-bit only.
1541
0
    *sampleTransformedImage = NULL;
1542
0
    const avifResult result = avifEncoderCreateSatoImage(encoder, item, itemWillBeEncodedLosslessly, image, sampleTransformedImage);
1543
0
    if (result != AVIF_RESULT_OK && *sampleTransformedImage != NULL) {
1544
0
        avifImageDestroy(*sampleTransformedImage);
1545
0
    }
1546
0
    return result;
1547
0
}
1548
1549
static avifCodecType avifEncoderGetCodecType(const avifEncoder * encoder)
1550
0
{
1551
    // This asserts that images cannot be encoded with AVM unless AVIF_CODEC_CHOICE_AVM is explicitly selected.
1552
0
    assert((encoder->codecChoice != AVIF_CODEC_CHOICE_AUTO) ||
1553
0
           (strcmp(avifCodecName(encoder->codecChoice, AVIF_CODEC_FLAG_CAN_ENCODE), "avm") != 0));
1554
0
    return avifCodecTypeFromChoice(encoder->codecChoice, AVIF_CODEC_FLAG_CAN_ENCODE);
1555
0
}
1556
1557
// This function is called after every color frame is encoded. It returns AVIF_TRUE if a keyframe needs to be forced for the next
1558
// alpha frame to be encoded, AVIF_FALSE otherwise.
1559
static avifBool avifEncoderDataShouldForceKeyframeForAlpha(const avifEncoderData * data,
1560
                                                           const avifEncoderItem * colorItem,
1561
                                                           avifAddImageFlags addImageFlags)
1562
0
{
1563
0
    if (!data->alphaPresent) {
1564
        // There is no alpha plane.
1565
0
        return AVIF_FALSE;
1566
0
    }
1567
0
    if (addImageFlags & AVIF_ADD_IMAGE_FLAG_SINGLE) {
1568
        // Not an animated image.
1569
0
        return AVIF_FALSE;
1570
0
    }
1571
0
    if (data->frames.count == 0) {
1572
        // data->frames.count is the number of frames that have been encoded so far by previous calls to avifEncoderAddImage. If
1573
        // this is the first frame, there is no need to force keyframe.
1574
0
        return AVIF_FALSE;
1575
0
    }
1576
0
    const uint32_t colorFramesOutputSoFar = colorItem->encodeOutput->samples.count;
1577
0
    const avifBool isLaggedOutput = (data->frames.count + 1) != colorFramesOutputSoFar;
1578
0
    if (isLaggedOutput) {
1579
        // If the encoder is operating with lag, then there is no way to determine if the last encoded frame was a keyframe until
1580
        // the encoder outputs it (after the lag). So do not force keyframe for alpha channel in this case.
1581
0
        return AVIF_FALSE;
1582
0
    }
1583
0
    return colorItem->encodeOutput->samples.sample[colorFramesOutputSoFar - 1].sync;
1584
0
}
1585
1586
static avifResult avifGetErrorForItemCategory(avifItemCategory itemCategory)
1587
0
{
1588
0
    if (itemCategory == AVIF_ITEM_GAIN_MAP) {
1589
0
        return AVIF_RESULT_ENCODE_GAIN_MAP_FAILED;
1590
0
    }
1591
0
    if (itemCategory == AVIF_ITEM_SAMPLE_TRANSFORM ||
1592
0
        (itemCategory >= AVIF_SAMPLE_TRANSFORM_MIN_CATEGORY && itemCategory <= AVIF_SAMPLE_TRANSFORM_MAX_CATEGORY)) {
1593
0
        return AVIF_RESULT_ENCODE_SAMPLE_TRANSFORM_FAILED;
1594
0
    }
1595
0
    return avifIsAlpha(itemCategory) ? AVIF_RESULT_ENCODE_ALPHA_FAILED : AVIF_RESULT_ENCODE_COLOR_FAILED;
1596
0
}
1597
1598
static uint32_t avifGridWidth(uint32_t gridCols, const avifImage * firstCell, const avifImage * bottomRightCell)
1599
0
{
1600
0
    return (gridCols - 1) * firstCell->width + bottomRightCell->width;
1601
0
}
1602
1603
static uint32_t avifGridHeight(uint32_t gridRows, const avifImage * firstCell, const avifImage * bottomRightCell)
1604
0
{
1605
0
    return (gridRows - 1) * firstCell->height + bottomRightCell->height;
1606
0
}
1607
1608
static avifResult avifValidateGrid(uint32_t gridCols,
1609
                                   uint32_t gridRows,
1610
                                   const avifImage * const * cellImages,
1611
                                   avifBool validateGainMap,
1612
                                   avifDiagnostics * diag)
1613
0
{
1614
0
    const uint32_t cellCount = gridCols * gridRows;
1615
0
    const avifImage * firstCell = cellImages[0];
1616
0
    const avifImage * bottomRightCell = cellImages[cellCount - 1];
1617
0
    if (validateGainMap) {
1618
0
        AVIF_ASSERT_OR_RETURN(firstCell->gainMap && firstCell->gainMap->image);
1619
0
        firstCell = firstCell->gainMap->image;
1620
0
        AVIF_ASSERT_OR_RETURN(bottomRightCell->gainMap && bottomRightCell->gainMap->image);
1621
0
        bottomRightCell = bottomRightCell->gainMap->image;
1622
0
    }
1623
0
    const uint32_t tileWidth = firstCell->width;
1624
0
    const uint32_t tileHeight = firstCell->height;
1625
0
    if ((tileWidth > 65536) || (tileHeight > 65536)) {
1626
0
        avifDiagnosticsPrintf(diag,
1627
0
                              "the first %s cell has invalid dimensions for AV1: %ux%u",
1628
0
                              validateGainMap ? "gain map" : "image",
1629
0
                              tileWidth,
1630
0
                              tileHeight);
1631
0
        return AVIF_RESULT_INVALID_ARGUMENT;
1632
0
    }
1633
0
    for (uint32_t cellIndex = 0; cellIndex < cellCount; ++cellIndex) {
1634
0
        const avifImage * cellImage = cellImages[cellIndex];
1635
0
        if (validateGainMap) {
1636
0
            AVIF_ASSERT_OR_RETURN(cellImage->gainMap && cellImage->gainMap->image);
1637
0
            cellImage = cellImage->gainMap->image;
1638
0
        }
1639
0
        const uint32_t expectedCellWidth = ((cellIndex + 1) % gridCols) ? tileWidth : bottomRightCell->width;
1640
0
        const uint32_t expectedCellHeight = (cellIndex < (cellCount - gridCols)) ? tileHeight : bottomRightCell->height;
1641
0
        if ((cellImage->width != expectedCellWidth) || (cellImage->height != expectedCellHeight)) {
1642
0
            avifDiagnosticsPrintf(diag,
1643
0
                                  "%s cell %u has invalid dimensions: expected %ux%u found %ux%u",
1644
0
                                  validateGainMap ? "gain map" : "image",
1645
0
                                  cellIndex,
1646
0
                                  expectedCellWidth,
1647
0
                                  expectedCellHeight,
1648
0
                                  cellImage->width,
1649
0
                                  cellImage->height);
1650
0
            return AVIF_RESULT_INVALID_IMAGE_GRID;
1651
0
        }
1652
1653
        // MIAF (ISO 23000-22:2019), Section 7.3.11.4.1:
1654
        //   All input images of a grid image item shall use the same coding format, chroma sampling format, and the
1655
        //   same decoder configuration (see 7.3.6.2).
1656
0
        if ((cellImage->depth != firstCell->depth) || (cellImage->yuvFormat != firstCell->yuvFormat) ||
1657
0
            (cellImage->yuvRange != firstCell->yuvRange) || (cellImage->colorPrimaries != firstCell->colorPrimaries) ||
1658
0
            (cellImage->transferCharacteristics != firstCell->transferCharacteristics) ||
1659
0
            (cellImage->matrixCoefficients != firstCell->matrixCoefficients) || (!!cellImage->alphaPlane != !!firstCell->alphaPlane) ||
1660
0
            (cellImage->alphaPremultiplied != firstCell->alphaPremultiplied)) {
1661
0
            avifDiagnosticsPrintf(diag,
1662
0
                                  "all grid cells should have the same value for: depth, yuvFormat, yuvRange, colorPrimaries, "
1663
0
                                  "transferCharacteristics, matrixCoefficients, alphaPlane presence, alphaPremultiplied");
1664
0
            return AVIF_RESULT_INVALID_IMAGE_GRID;
1665
0
        }
1666
1667
        // AV1 (Version 1.0.0 with Errata 1), Section 6.4.2. Color config semantics
1668
        //   If matrix_coefficients is equal to MC_IDENTITY, it is a requirement of bitstream conformance that
1669
        //   subsampling_x is equal to 0 and subsampling_y is equal to 0.
1670
        // Although matrix_coefficients in the Sequence Header OBU is set to Undefined (2), the requirement
1671
        // is still enforced here between what is written in the ColourInformationProperty of colour_type 'nclx'
1672
        // and the subsampling information in the Sequence Header OBU.
1673
0
        if (cellImage->matrixCoefficients == AVIF_MATRIX_COEFFICIENTS_IDENTITY && cellImage->yuvFormat != AVIF_PIXEL_FORMAT_YUV444) {
1674
0
            avifDiagnosticsPrintf(diag, "subsampling must be 0 (4:4:4) with identity matrix coefficients");
1675
0
            return AVIF_RESULT_INVALID_ARGUMENT;
1676
0
        }
1677
1678
0
        if (!cellImage->yuvPlanes[AVIF_CHAN_Y]) {
1679
0
            return AVIF_RESULT_NO_CONTENT;
1680
0
        }
1681
0
    }
1682
1683
0
    if ((bottomRightCell->width > tileWidth) || (bottomRightCell->height > tileHeight)) {
1684
0
        avifDiagnosticsPrintf(diag,
1685
0
                              "the last %s cell can be smaller but not larger than the other cells which are %ux%u, found %ux%u",
1686
0
                              validateGainMap ? "gain map" : "image",
1687
0
                              tileWidth,
1688
0
                              tileHeight,
1689
0
                              bottomRightCell->width,
1690
0
                              bottomRightCell->height);
1691
0
        return AVIF_RESULT_INVALID_IMAGE_GRID;
1692
0
    }
1693
0
    const uint32_t gridWidth = avifGridWidth(gridCols, firstCell, bottomRightCell);
1694
0
    const uint32_t gridHeight = avifGridHeight(gridRows, firstCell, bottomRightCell);
1695
0
    if ((cellCount > 1) && !avifAreGridDimensionsValid(firstCell->yuvFormat, gridWidth, gridHeight, tileWidth, tileHeight, diag)) {
1696
0
        return AVIF_RESULT_INVALID_IMAGE_GRID;
1697
0
    }
1698
1699
0
    return AVIF_RESULT_OK;
1700
0
}
1701
1702
static avifResult avifEncoderAddImageInternal(avifEncoder * encoder,
1703
                                              uint32_t gridCols,
1704
                                              uint32_t gridRows,
1705
                                              const avifImage * const * cellImages,
1706
                                              uint64_t durationInTimescales,
1707
                                              avifAddImageFlags addImageFlags)
1708
0
{
1709
    // -----------------------------------------------------------------------
1710
    // Verify encoding is possible
1711
1712
0
    if (!avifCodecName(encoder->codecChoice, AVIF_CODEC_FLAG_CAN_ENCODE)) {
1713
0
        return AVIF_RESULT_NO_CODEC_AVAILABLE;
1714
0
    }
1715
1716
0
    if (encoder->extraLayerCount >= AVIF_MAX_AV1_LAYER_COUNT) {
1717
0
        avifDiagnosticsPrintf(&encoder->diag, "extraLayerCount [%u] must be less than %d", encoder->extraLayerCount, AVIF_MAX_AV1_LAYER_COUNT);
1718
0
        return AVIF_RESULT_INVALID_ARGUMENT;
1719
0
    }
1720
1721
    // -----------------------------------------------------------------------
1722
    // Validate images
1723
1724
0
    const uint32_t cellCount = gridCols * gridRows;
1725
0
    if (cellCount == 0) {
1726
0
        return AVIF_RESULT_INVALID_ARGUMENT;
1727
0
    }
1728
1729
0
    const avifImage * firstCell = cellImages[0];
1730
0
    const avifImage * bottomRightCell = cellImages[cellCount - 1];
1731
0
    AVIF_CHECKERR(firstCell->depth == 8 || firstCell->depth == 10 || firstCell->depth == 12 ||
1732
0
                      (firstCell->depth == 16 && encoder->sampleTransformRecipe != AVIF_SAMPLE_TRANSFORM_NONE),
1733
0
                  AVIF_RESULT_UNSUPPORTED_DEPTH);
1734
0
    AVIF_CHECKERR(firstCell->yuvFormat != AVIF_PIXEL_FORMAT_NONE, AVIF_RESULT_NO_YUV_FORMAT_SELECTED);
1735
0
    if (!firstCell->width || !firstCell->height || !bottomRightCell->width || !bottomRightCell->height) {
1736
0
        return AVIF_RESULT_NO_CONTENT;
1737
0
    }
1738
1739
0
    AVIF_CHECKRES(avifValidateGrid(gridCols, gridRows, cellImages, /*validateGainMap=*/AVIF_FALSE, &encoder->diag));
1740
1741
0
    const avifBool hasGainMap = (firstCell->gainMap && firstCell->gainMap->image != NULL);
1742
1743
    // Check that either all cells have a gain map, or none of them do.
1744
    // If a gain map is present, check that they all have the same gain map metadata.
1745
0
    for (uint32_t cellIndex = 0; cellIndex < cellCount; ++cellIndex) {
1746
0
        const avifImage * cellImage = cellImages[cellIndex];
1747
0
        const avifBool cellHasGainMap = (cellImage->gainMap && cellImage->gainMap->image);
1748
0
        if (cellHasGainMap != hasGainMap) {
1749
0
            avifDiagnosticsPrintf(&encoder->diag, "cells should either all have a gain map image, or none of them should, found a mix");
1750
0
            return AVIF_RESULT_INVALID_IMAGE_GRID;
1751
0
        }
1752
0
        if (hasGainMap) {
1753
0
            if (!avifSameGainMapAltMetadata(firstCell->gainMap, cellImage->gainMap)) {
1754
0
                avifDiagnosticsPrintf(&encoder->diag, "all cells should have the same alternate image metadata in the gain map");
1755
0
                return AVIF_RESULT_INVALID_IMAGE_GRID;
1756
0
            }
1757
0
            if (!avifSameGainMapMetadata(firstCell->gainMap, cellImage->gainMap)) {
1758
0
                avifDiagnosticsPrintf(&encoder->diag, "all cells should have the same gain map metadata");
1759
0
                return AVIF_RESULT_INVALID_IMAGE_GRID;
1760
0
            }
1761
0
        }
1762
0
    }
1763
1764
0
    if (hasGainMap) {
1765
        // AVIF supports 16-bit images through sample transforms used as bit depth extensions,
1766
        // but this is not implemented for gain maps for now. Stick to at most 12 bits.
1767
0
        AVIF_CHECKERR(firstCell->gainMap->image->depth == 8 || firstCell->gainMap->image->depth == 10 ||
1768
0
                          firstCell->gainMap->image->depth == 12,
1769
0
                      AVIF_RESULT_UNSUPPORTED_DEPTH);
1770
0
        AVIF_CHECKERR(firstCell->gainMap->image->yuvFormat != AVIF_PIXEL_FORMAT_NONE, AVIF_RESULT_NO_YUV_FORMAT_SELECTED);
1771
0
        AVIF_CHECKRES(avifValidateGrid(gridCols, gridRows, cellImages, /*validateGainMap=*/AVIF_TRUE, &encoder->diag));
1772
0
        if (firstCell->gainMap->image->colorPrimaries != AVIF_COLOR_PRIMARIES_UNSPECIFIED ||
1773
0
            firstCell->gainMap->image->transferCharacteristics != AVIF_TRANSFER_CHARACTERISTICS_UNSPECIFIED) {
1774
0
            avifDiagnosticsPrintf(&encoder->diag, "the gain map image must have colorPrimaries = 2 and transferCharacteristics = 2");
1775
0
            return AVIF_RESULT_INVALID_ARGUMENT;
1776
0
        }
1777
0
    }
1778
1779
    // -----------------------------------------------------------------------
1780
    // Validate flags
1781
1782
0
    if (encoder->data->singleImage) {
1783
        // The previous call to avifEncoderAddImage() set AVIF_ADD_IMAGE_FLAG_SINGLE.
1784
        // avifEncoderAddImage() cannot be called again for this encode.
1785
0
        return AVIF_RESULT_ENCODE_COLOR_FAILED;
1786
0
    }
1787
1788
0
    if (addImageFlags & AVIF_ADD_IMAGE_FLAG_SINGLE) {
1789
0
        encoder->data->singleImage = AVIF_TRUE;
1790
1791
0
        if (encoder->extraLayerCount > 0) {
1792
            // AVIF_ADD_IMAGE_FLAG_SINGLE may not be set for layered image.
1793
0
            return AVIF_RESULT_INVALID_ARGUMENT;
1794
0
        }
1795
1796
0
        if (encoder->data->items.count > 0) {
1797
            // AVIF_ADD_IMAGE_FLAG_SINGLE may only be set on the first and only image.
1798
0
            return AVIF_RESULT_INVALID_ARGUMENT;
1799
0
        }
1800
0
    }
1801
1802
    // -----------------------------------------------------------------------
1803
    // Choose AV1 or AV2
1804
1805
0
    const avifCodecType codecType = avifEncoderGetCodecType(encoder);
1806
0
    switch (codecType) {
1807
0
        case AVIF_CODEC_TYPE_AV1:
1808
0
            encoder->data->imageItemType = "av01";
1809
0
            encoder->data->configPropName = "av1C";
1810
0
            break;
1811
#if defined(AVIF_CODEC_AVM)
1812
        case AVIF_CODEC_TYPE_AV2:
1813
            encoder->data->imageItemType = "av02";
1814
            encoder->data->configPropName = "av2C";
1815
            break;
1816
#endif
1817
0
        default:
1818
0
            return AVIF_RESULT_NO_CODEC_AVAILABLE;
1819
0
    }
1820
1821
    // -----------------------------------------------------------------------
1822
    // Populate encoder data (color) quality, qualityAlpha and qualityGainMap.
1823
0
    encoder->data->quality = avifGetQuality(encoder->quality, encoder->minQuantizer, encoder->maxQuantizer);
1824
1825
    // If alpha quality, and min and max alpha quantizer have their default values, default to the same quality as color.
1826
0
    if (encoder->qualityAlpha == AVIF_QUALITY_DEFAULT && encoder->minQuantizerAlpha == AVIF_QUANTIZER_BEST_QUALITY &&
1827
0
        encoder->maxQuantizerAlpha == AVIF_QUANTIZER_WORST_QUALITY) {
1828
0
        encoder->data->qualityAlpha = encoder->data->quality;
1829
0
    } else {
1830
0
        encoder->data->qualityAlpha = avifGetQuality(encoder->qualityAlpha, encoder->minQuantizerAlpha, encoder->maxQuantizerAlpha);
1831
0
    }
1832
0
    if (encoder->qualityGainMap == AVIF_QUALITY_DEFAULT) {
1833
0
        encoder->data->qualityGainMap = encoder->data->quality; // Default to the same quality as color.
1834
0
    } else {
1835
0
        encoder->data->qualityGainMap =
1836
0
            avifGetQuality(encoder->qualityGainMap, AVIF_QUANTIZER_BEST_QUALITY, AVIF_QUANTIZER_WORST_QUALITY);
1837
0
    }
1838
1839
    // -----------------------------------------------------------------------
1840
    // Handle automatic tiling
1841
1842
0
    encoder->data->tileRowsLog2 = AVIF_CLAMP(encoder->tileRowsLog2, 0, 6);
1843
0
    encoder->data->tileColsLog2 = AVIF_CLAMP(encoder->tileColsLog2, 0, 6);
1844
0
    if (encoder->autoTiling) {
1845
        // Use as many tiles as allowed by the minimum tile area requirement and impose a maximum
1846
        // of 8 tiles.
1847
0
        const int threads = 8;
1848
0
        avifSetTileConfiguration(threads, firstCell->width, firstCell->height, &encoder->data->tileRowsLog2, &encoder->data->tileColsLog2);
1849
0
    }
1850
1851
    // -----------------------------------------------------------------------
1852
    // All encoder settings are known now. Detect changes.
1853
1854
0
    avifEncoderChanges encoderChanges;
1855
0
    if (!avifEncoderDetectChanges(encoder, &encoderChanges)) {
1856
0
        return AVIF_RESULT_CANNOT_CHANGE_SETTING;
1857
0
    }
1858
0
    avifEncoderBackupSettings(encoder);
1859
1860
    // -----------------------------------------------------------------------
1861
1862
0
    if (durationInTimescales == 0) {
1863
0
        durationInTimescales = 1;
1864
0
    }
1865
1866
0
    if (encoder->data->items.count == 0) {
1867
        // Make a copy of the first image's metadata (sans pixels) for future writing/validation
1868
0
        AVIF_CHECKRES(avifImageCopy(encoder->data->imageMetadata, firstCell, 0));
1869
1870
0
        const uint32_t gridWidth = avifGridWidth(gridCols, firstCell, bottomRightCell);
1871
0
        const uint32_t gridHeight = avifGridHeight(gridRows, firstCell, bottomRightCell);
1872
1873
0
        if (hasGainMap) {
1874
0
            AVIF_CHECKRES(
1875
0
                avifImageCopyAltImageMetadata(encoder->data->altImageMetadata, encoder->data->imageMetadata, gridWidth, gridHeight));
1876
0
        }
1877
1878
        // Prepare all AV1 items
1879
0
        uint16_t colorItemID;
1880
0
        AVIF_CHECKRES(avifEncoderAddImageItems(encoder, gridCols, gridRows, gridWidth, gridHeight, AVIF_ITEM_COLOR, &colorItemID));
1881
0
        encoder->data->primaryItemID = colorItemID;
1882
1883
0
        encoder->data->alphaPresent = (firstCell->alphaPlane != NULL);
1884
0
        if (encoder->data->alphaPresent && (addImageFlags & AVIF_ADD_IMAGE_FLAG_SINGLE)) {
1885
            // If encoding a single image in which the alpha plane exists but is entirely opaque,
1886
            // simply skip writing an alpha AV1 payload entirely, as it'll be interpreted as opaque
1887
            // and is less bytes.
1888
            //
1889
            // However, if encoding an image sequence, the first frame's alpha plane being entirely
1890
            // opaque could be a false positive for removing the alpha AV1 payload, as it might simply
1891
            // be a fade out later in the sequence. This is why avifImageIsOpaque() is only called
1892
            // when encoding a single image.
1893
1894
0
            encoder->data->alphaPresent = AVIF_FALSE;
1895
0
            for (uint32_t cellIndex = 0; cellIndex < cellCount; ++cellIndex) {
1896
0
                const avifImage * cellImage = cellImages[cellIndex];
1897
0
                if (!avifImageIsOpaque(cellImage)) {
1898
0
                    encoder->data->alphaPresent = AVIF_TRUE;
1899
0
                    break;
1900
0
                }
1901
0
            }
1902
0
        }
1903
1904
0
        if (encoder->data->alphaPresent) {
1905
0
            uint16_t alphaItemID;
1906
0
            AVIF_CHECKRES(avifEncoderAddImageItems(encoder, gridCols, gridRows, gridWidth, gridHeight, AVIF_ITEM_ALPHA, &alphaItemID));
1907
0
            avifEncoderItem * alphaItem = avifEncoderDataFindItemByID(encoder->data, alphaItemID);
1908
0
            AVIF_ASSERT_OR_RETURN(alphaItem);
1909
0
            alphaItem->irefType = "auxl";
1910
0
            alphaItem->irefToID = colorItemID;
1911
0
            if (encoder->data->imageMetadata->alphaPremultiplied) {
1912
0
                avifEncoderItem * colorItem = avifEncoderDataFindItemByID(encoder->data, colorItemID);
1913
0
                AVIF_ASSERT_OR_RETURN(colorItem);
1914
0
                colorItem->irefType = "prem";
1915
0
                colorItem->irefToID = alphaItemID;
1916
0
            }
1917
0
        }
1918
1919
0
        if (firstCell->gainMap && firstCell->gainMap->image) {
1920
0
            avifEncoderItem * toneMappedItem = avifEncoderDataCreateItem(encoder->data,
1921
0
                                                                         "tmap",
1922
0
                                                                         infeNameGainMap,
1923
0
                                                                         /*infeNameSize=*/strlen(infeNameGainMap) + 1,
1924
0
                                                                         /*cellIndex=*/0);
1925
0
            AVIF_CHECKRES(avifWriteToneMappedImagePayload(&toneMappedItem->metadataPayload, firstCell->gainMap, &encoder->diag));
1926
            // Even though the 'tmap' item is related to the gain map, it represents a color image and its metadata is more similar to the color item.
1927
0
            toneMappedItem->itemCategory = AVIF_ITEM_COLOR;
1928
0
            uint16_t toneMappedItemID = toneMappedItem->id;
1929
1930
0
            AVIF_ASSERT_OR_RETURN(encoder->data->alternativeItemIDs.count == 0);
1931
0
            uint16_t * alternativeItemID = (uint16_t *)avifArrayPush(&encoder->data->alternativeItemIDs);
1932
0
            AVIF_CHECKERR(alternativeItemID != NULL, AVIF_RESULT_OUT_OF_MEMORY);
1933
0
            *alternativeItemID = toneMappedItemID;
1934
1935
0
            alternativeItemID = (uint16_t *)avifArrayPush(&encoder->data->alternativeItemIDs);
1936
0
            AVIF_CHECKERR(alternativeItemID != NULL, AVIF_RESULT_OUT_OF_MEMORY);
1937
0
            *alternativeItemID = colorItemID;
1938
1939
0
            const uint32_t gainMapGridWidth =
1940
0
                avifGridWidth(gridCols, cellImages[0]->gainMap->image, cellImages[gridCols * gridRows - 1]->gainMap->image);
1941
0
            const uint32_t gainMapGridHeight =
1942
0
                avifGridHeight(gridRows, cellImages[0]->gainMap->image, cellImages[gridCols * gridRows - 1]->gainMap->image);
1943
1944
0
            uint16_t gainMapItemID;
1945
0
            AVIF_CHECKRES(
1946
0
                avifEncoderAddImageItems(encoder, gridCols, gridRows, gainMapGridWidth, gainMapGridHeight, AVIF_ITEM_GAIN_MAP, &gainMapItemID));
1947
0
            avifEncoderItem * gainMapItem = avifEncoderDataFindItemByID(encoder->data, gainMapItemID);
1948
0
            AVIF_ASSERT_OR_RETURN(gainMapItem);
1949
0
            gainMapItem->hiddenImage = AVIF_TRUE;
1950
1951
            // Set the color item and gain map item's dimgFromID value to point to the tone mapped item.
1952
            // The color item shall be first, and the gain map second. avifEncoderFinish() writes the
1953
            // dimg item references in item id order, so as long as colorItemID < gainMapItemID, the order
1954
            // will be correct.
1955
0
            AVIF_ASSERT_OR_RETURN(colorItemID < gainMapItemID);
1956
0
            avifEncoderItem * colorItem = avifEncoderDataFindItemByID(encoder->data, colorItemID);
1957
0
            AVIF_ASSERT_OR_RETURN(colorItem);
1958
0
            AVIF_ASSERT_OR_RETURN(colorItem->dimgFromID == 0); // Our internal API only allows one dimg value per item.
1959
0
            colorItem->dimgFromID = toneMappedItemID;
1960
0
            gainMapItem->dimgFromID = toneMappedItemID;
1961
0
        }
1962
1963
0
        if (encoder->sampleTransformRecipe == AVIF_SAMPLE_TRANSFORM_BIT_DEPTH_EXTENSION_8B_8B ||
1964
0
            encoder->sampleTransformRecipe == AVIF_SAMPLE_TRANSFORM_BIT_DEPTH_EXTENSION_12B_4B ||
1965
0
            encoder->sampleTransformRecipe == AVIF_SAMPLE_TRANSFORM_BIT_DEPTH_EXTENSION_12B_8B_OVERLAP_4B) {
1966
            // For now, only 16-bit depth is supported.
1967
0
            AVIF_CHECKERR(firstCell->depth == 16, AVIF_RESULT_NOT_IMPLEMENTED);
1968
            // For now, gain maps are not supported in the same file as Sample Transforms ('altr' group conflict).
1969
0
            AVIF_CHECKERR(!firstCell->gainMap, AVIF_RESULT_NOT_IMPLEMENTED);
1970
0
            AVIF_CHECKRES(avifEncoderCreateBitDepthExtensionItems(encoder, gridCols, gridRows, gridWidth, gridHeight, colorItemID));
1971
0
        } else {
1972
0
            AVIF_CHECKERR(encoder->sampleTransformRecipe == AVIF_SAMPLE_TRANSFORM_NONE, AVIF_RESULT_NOT_IMPLEMENTED);
1973
0
        }
1974
1975
        // -----------------------------------------------------------------------
1976
        // Create metadata items (Exif, XMP)
1977
1978
0
        if (firstCell->exif.size > 0) {
1979
0
            const avifResult result = avifEncoderDataCreateExifItem(encoder->data, &firstCell->exif);
1980
0
            if (result != AVIF_RESULT_OK) {
1981
0
                return result;
1982
0
            }
1983
0
        }
1984
1985
0
        if (firstCell->xmp.size > 0) {
1986
0
            const avifResult result = avifEncoderDataCreateXMPItem(encoder->data, &firstCell->xmp);
1987
0
            if (result != AVIF_RESULT_OK) {
1988
0
                return result;
1989
0
            }
1990
0
        }
1991
0
    } else {
1992
        // Another frame in an image sequence, or layer in a layered image
1993
1994
0
        if (hasGainMap) {
1995
0
            avifDiagnosticsPrintf(&encoder->diag, "gain maps are not supported for image sequences or layered images");
1996
0
            return AVIF_RESULT_NOT_IMPLEMENTED;
1997
0
        }
1998
1999
0
        const avifImage * imageMetadata = encoder->data->imageMetadata;
2000
        // Image metadata that are copied to the configuration property and nclx boxes are not allowed to change.
2001
        // If the first image in the sequence had an alpha plane (even if fully opaque), all
2002
        // subsequent images must have alpha as well.
2003
0
        if ((imageMetadata->depth != firstCell->depth) || (imageMetadata->yuvFormat != firstCell->yuvFormat) ||
2004
0
            (imageMetadata->yuvRange != firstCell->yuvRange) ||
2005
0
            (imageMetadata->yuvChromaSamplePosition != firstCell->yuvChromaSamplePosition) ||
2006
0
            (imageMetadata->colorPrimaries != firstCell->colorPrimaries) ||
2007
0
            (imageMetadata->transferCharacteristics != firstCell->transferCharacteristics) ||
2008
0
            (imageMetadata->matrixCoefficients != firstCell->matrixCoefficients) ||
2009
0
            (imageMetadata->alphaPremultiplied != firstCell->alphaPremultiplied) ||
2010
0
            (encoder->data->alphaPresent && !firstCell->alphaPlane)) {
2011
0
            return AVIF_RESULT_INCOMPATIBLE_IMAGE;
2012
0
        }
2013
0
    }
2014
2015
0
    if (encoder->data->frames.count == 1) {
2016
        // We will be writing an image sequence. When writing the AV1SampleEntry (derived from
2017
        // VisualSampleEntry) in the stsd box, we need to cast imageMetadata->width and
2018
        // imageMetadata->height to uint16_t:
2019
        //     class VisualSampleEntry(codingname) extends SampleEntry (codingname){
2020
        //        ...
2021
        //        unsigned int(16) width;
2022
        //        unsigned int(16) height;
2023
        //        ...
2024
        //     }
2025
        // Check whether it is safe to cast width and height to uint16_t. The maximum width and
2026
        // height of an AV1 frame are 65536, which just exceeds uint16_t.
2027
0
        AVIF_ASSERT_OR_RETURN(encoder->data->items.count > 0);
2028
0
        const avifImage * imageMetadata = encoder->data->imageMetadata;
2029
0
        AVIF_CHECKERR(imageMetadata->width <= 65535 && imageMetadata->height <= 65535, AVIF_RESULT_INVALID_ARGUMENT);
2030
0
    }
2031
2032
    // -----------------------------------------------------------------------
2033
    // Encode AV1 OBUs
2034
2035
0
    for (uint32_t itemIndex = 0; itemIndex < encoder->data->items.count; ++itemIndex) {
2036
0
        avifEncoderItem * item = &encoder->data->items.item[itemIndex];
2037
0
        if (item->codec) {
2038
0
            const avifImage * cellImage = cellImages[item->cellIndex];
2039
0
            avifImage * cellImagePlaceholder = NULL; // May be used as a temporary, modified cellImage. Left as NULL otherwise.
2040
0
            const avifImage * firstCellImage = firstCell;
2041
2042
0
            if (item->itemCategory == AVIF_ITEM_GAIN_MAP) {
2043
0
                AVIF_ASSERT_OR_RETURN(cellImage->gainMap && cellImage->gainMap->image);
2044
0
                cellImage = cellImage->gainMap->image;
2045
0
                AVIF_ASSERT_OR_RETURN(firstCell->gainMap && firstCell->gainMap->image);
2046
0
                firstCellImage = firstCell->gainMap->image;
2047
0
            }
2048
2049
0
            if ((cellImage->width != firstCellImage->width) || (cellImage->height != firstCellImage->height)) {
2050
                // Pad the right-most and/or bottom-most tiles so that all tiles share the same dimensions.
2051
0
                cellImagePlaceholder = avifImageCreateEmpty();
2052
0
                AVIF_CHECKERR(cellImagePlaceholder, AVIF_RESULT_OUT_OF_MEMORY);
2053
0
                const avifResult result =
2054
0
                    avifImageCopyAndPad(cellImagePlaceholder, cellImage, firstCellImage->width, firstCellImage->height);
2055
0
                if (result != AVIF_RESULT_OK) {
2056
0
                    avifImageDestroy(cellImagePlaceholder);
2057
0
                    return result;
2058
0
                }
2059
0
                cellImage = cellImagePlaceholder;
2060
0
            }
2061
2062
0
            const avifBool isAlpha = avifIsAlpha(item->itemCategory);
2063
0
            int quality = isAlpha                                      ? encoder->data->qualityAlpha
2064
0
                          : (item->itemCategory == AVIF_ITEM_GAIN_MAP) ? encoder->data->qualityGainMap
2065
0
                                                                       : encoder->data->quality;
2066
2067
            // Remember original quantizer values in case they change, to reset them afterwards.
2068
0
            int * encoderMinQuantizer = isAlpha ? &encoder->minQuantizerAlpha : &encoder->minQuantizer;
2069
0
            int * encoderMaxQuantizer = isAlpha ? &encoder->maxQuantizerAlpha : &encoder->maxQuantizer;
2070
0
            const int originalMinQuantizer = *encoderMinQuantizer;
2071
0
            const int originalMaxQuantizer = *encoderMaxQuantizer;
2072
2073
0
            if (encoder->sampleTransformRecipe != AVIF_SAMPLE_TRANSFORM_NONE) {
2074
0
                if ((encoder->sampleTransformRecipe == AVIF_SAMPLE_TRANSFORM_BIT_DEPTH_EXTENSION_8B_8B ||
2075
0
                     encoder->sampleTransformRecipe == AVIF_SAMPLE_TRANSFORM_BIT_DEPTH_EXTENSION_12B_4B) &&
2076
0
                    (item->itemCategory == AVIF_ITEM_COLOR || item->itemCategory == AVIF_ITEM_ALPHA)) {
2077
                    // Encoding the least significant bits of a sample does not make any sense if the
2078
                    // other bits are lossily compressed. Encode the most significant bits losslessly.
2079
0
                    quality = AVIF_QUALITY_LOSSLESS;
2080
0
                    *encoderMinQuantizer = AVIF_QUANTIZER_LOSSLESS;
2081
0
                    *encoderMaxQuantizer = AVIF_QUANTIZER_LOSSLESS;
2082
0
                    if (!avifEncoderDetectChanges(encoder, &encoderChanges)) {
2083
0
                        assert(AVIF_FALSE);
2084
0
                    }
2085
0
                }
2086
2087
                // Replace cellImage by the first or second input to the AVIF_ITEM_SAMPLE_TRANSFORM derived image item.
2088
0
                const avifBool itemWillBeEncodedLosslessly = (quality == AVIF_QUALITY_LOSSLESS);
2089
0
                avifImage * sampleTransformedImage = NULL;
2090
0
                if (cellImagePlaceholder) {
2091
0
                    avifImageDestroy(cellImagePlaceholder); // Replaced by sampleTransformedImage.
2092
0
                    cellImagePlaceholder = NULL;
2093
0
                }
2094
0
                AVIF_CHECKRES(
2095
0
                    avifEncoderCreateBitDepthExtensionImage(encoder, item, itemWillBeEncodedLosslessly, cellImage, &sampleTransformedImage));
2096
0
                assert(cellImagePlaceholder == NULL);
2097
0
                cellImagePlaceholder = sampleTransformedImage; // Transfer ownership.
2098
0
                cellImage = cellImagePlaceholder;
2099
0
            }
2100
2101
            // If alpha channel is present, set disableLaggedOutput to AVIF_TRUE. If the encoder supports it, this enables
2102
            // avifEncoderDataShouldForceKeyframeForAlpha to force a keyframe in the alpha channel whenever a keyframe has been
2103
            // encoded in the color channel for animated images.
2104
0
            avifResult encodeResult = item->codec->encodeImage(item->codec,
2105
0
                                                               encoder,
2106
0
                                                               cellImage,
2107
0
                                                               isAlpha,
2108
0
                                                               encoder->data->tileRowsLog2,
2109
0
                                                               encoder->data->tileColsLog2,
2110
0
                                                               quality,
2111
0
                                                               encoderChanges,
2112
0
                                                               /*disableLaggedOutput=*/encoder->data->alphaPresent,
2113
0
                                                               addImageFlags,
2114
0
                                                               item->encodeOutput);
2115
            // Revert quality settings if they changed.
2116
0
            if (*encoderMinQuantizer != originalMinQuantizer || *encoderMaxQuantizer != originalMaxQuantizer) {
2117
0
                avifEncoderBackupSettings(encoder); // Remember last encoding settings for next avifEncoderDetectChanges().
2118
0
                *encoderMinQuantizer = originalMinQuantizer;
2119
0
                *encoderMaxQuantizer = originalMaxQuantizer;
2120
0
            }
2121
0
            if (cellImagePlaceholder) {
2122
0
                avifImageDestroy(cellImagePlaceholder);
2123
0
            }
2124
0
            if (encodeResult == AVIF_RESULT_UNKNOWN_ERROR) {
2125
0
                encodeResult = avifGetErrorForItemCategory(item->itemCategory);
2126
0
            }
2127
0
            AVIF_CHECKRES(encodeResult);
2128
0
            if (itemIndex == 0 && avifEncoderDataShouldForceKeyframeForAlpha(encoder->data, item, addImageFlags)) {
2129
0
                addImageFlags |= AVIF_ADD_IMAGE_FLAG_FORCE_KEYFRAME;
2130
0
            }
2131
0
        }
2132
0
    }
2133
2134
0
    avifEncoderFrame * frame = (avifEncoderFrame *)avifArrayPush(&encoder->data->frames);
2135
0
    AVIF_CHECKERR(frame != NULL, AVIF_RESULT_OUT_OF_MEMORY);
2136
0
    frame->durationInTimescales = durationInTimescales;
2137
0
    avifCodecSpecificOptionsClear(encoder->csOptions);
2138
0
    return AVIF_RESULT_OK;
2139
0
}
2140
2141
avifResult avifEncoderAddImage(avifEncoder * encoder, const avifImage * image, uint64_t durationInTimescales, avifAddImageFlags addImageFlags)
2142
0
{
2143
0
    avifDiagnosticsClearError(&encoder->diag);
2144
0
    return avifEncoderAddImageInternal(encoder, 1, 1, &image, durationInTimescales, addImageFlags);
2145
0
}
2146
2147
avifResult avifEncoderAddImageGrid(avifEncoder * encoder,
2148
                                   uint32_t gridCols,
2149
                                   uint32_t gridRows,
2150
                                   const avifImage * const * cellImages,
2151
                                   avifAddImageFlags addImageFlags)
2152
0
{
2153
0
    avifDiagnosticsClearError(&encoder->diag);
2154
0
    if ((gridCols == 0) || (gridCols > 256) || (gridRows == 0) || (gridRows > 256)) {
2155
0
        return AVIF_RESULT_INVALID_IMAGE_GRID;
2156
0
    }
2157
0
    if (encoder->extraLayerCount == 0) {
2158
0
        addImageFlags |= AVIF_ADD_IMAGE_FLAG_SINGLE; // image grids cannot be image sequences
2159
0
    }
2160
0
    return avifEncoderAddImageInternal(encoder, gridCols, gridRows, cellImages, 1, addImageFlags);
2161
0
}
2162
2163
static size_t avifEncoderFindExistingChunk(avifRWStream * s, size_t mdatStartOffset, const uint8_t * data, size_t size)
2164
0
{
2165
0
    const size_t mdatCurrentOffset = avifRWStreamOffset(s);
2166
0
    const size_t mdatSearchSize = mdatCurrentOffset - mdatStartOffset;
2167
0
    if (mdatSearchSize < size) {
2168
0
        return 0;
2169
0
    }
2170
0
    const size_t mdatEndSearchOffset = mdatCurrentOffset - size;
2171
0
    for (size_t searchOffset = mdatStartOffset; searchOffset <= mdatEndSearchOffset; ++searchOffset) {
2172
0
        if (!memcmp(data, &s->raw->data[searchOffset], size)) {
2173
0
            return searchOffset;
2174
0
        }
2175
0
    }
2176
0
    return 0;
2177
0
}
2178
2179
static avifResult avifEncoderWriteMediaDataBox(avifEncoder * encoder,
2180
                                               avifRWStream * s,
2181
                                               avifEncoderItemReferenceArray * layeredColorItems,
2182
                                               avifEncoderItemReferenceArray * layeredAlphaItems)
2183
0
{
2184
0
    encoder->ioStats.colorOBUSize = 0;
2185
0
    encoder->ioStats.alphaOBUSize = 0;
2186
0
    encoder->data->gainMapSizeBytes = 0;
2187
2188
0
    avifBoxMarker mdat;
2189
0
    AVIF_CHECKRES(avifRWStreamWriteBox(s, "mdat", AVIF_BOX_SIZE_TBD, &mdat));
2190
0
    const size_t mdatStartOffset = avifRWStreamOffset(s);
2191
0
    for (uint32_t itemPasses = 0; itemPasses < 3; ++itemPasses) {
2192
        // Use multiple passes to pack in the following order:
2193
        //   * Pass 0: metadata (Exif/XMP/gain map metadata)
2194
        //   * Pass 1: alpha, gain map image (AV1)
2195
        //   * Pass 2: all other item data (AV1 color)
2196
        //
2197
        // See here for the discussion on alpha coming before color:
2198
        // https://github.com/AOMediaCodec/libavif/issues/287
2199
        //
2200
        // Exif and XMP are packed first as they're required to be fully available
2201
        // by avifDecoderParse() before it returns AVIF_RESULT_OK, unless ignoreXMP
2202
        // and ignoreExif are enabled.
2203
        //
2204
0
        const avifBool metadataPass = (itemPasses == 0);
2205
0
        const avifBool alphaAndGainMapPass = (itemPasses == 1);
2206
2207
0
        for (uint32_t itemIndex = 0; itemIndex < encoder->data->items.count; ++itemIndex) {
2208
0
            avifEncoderItem * item = &encoder->data->items.item[itemIndex];
2209
0
            if ((item->metadataPayload.size == 0) && (item->encodeOutput->samples.count == 0)) {
2210
                // this item has nothing for the mdat box
2211
0
                continue;
2212
0
            }
2213
0
            const avifBool isMetadata = !memcmp(item->type, "mime", 4) || !memcmp(item->type, "Exif", 4) ||
2214
0
                                        !memcmp(item->type, "tmap", 4);
2215
0
            if (metadataPass != isMetadata) {
2216
                // only process metadata (XMP/Exif) payloads when metadataPass is true
2217
0
                continue;
2218
0
            }
2219
0
            const avifBool isAlpha = avifIsAlpha(item->itemCategory);
2220
0
            const avifBool isAlphaOrGainMap = isAlpha || item->itemCategory == AVIF_ITEM_GAIN_MAP;
2221
0
            if (alphaAndGainMapPass != isAlphaOrGainMap) {
2222
                // only process alpha payloads when alphaPass is true
2223
0
                continue;
2224
0
            }
2225
2226
0
            if ((encoder->extraLayerCount > 0) && (item->encodeOutput->samples.count > 0)) {
2227
                // Interleave - Pick out AV1 items and interleave them later.
2228
                // We always interleave all AV1 items for layered images.
2229
0
                AVIF_ASSERT_OR_RETURN(item->encodeOutput->samples.count == item->mdatFixups.count);
2230
2231
0
                avifEncoderItemReference * ref =
2232
0
                    (avifEncoderItemReference *)avifArrayPush(isAlpha ? layeredAlphaItems : layeredColorItems);
2233
0
                AVIF_CHECKERR(ref != NULL, AVIF_RESULT_OUT_OF_MEMORY);
2234
0
                *ref = item;
2235
0
                continue;
2236
0
            }
2237
2238
0
            size_t chunkOffset = 0;
2239
2240
            // Deduplication - See if an identical chunk to this has already been written.
2241
            // Doing it when item->encodeOutput->samples.count > 1 would require contiguous memory.
2242
0
            if (item->encodeOutput->samples.count == 1) {
2243
0
                avifEncodeSample * sample = &item->encodeOutput->samples.sample[0];
2244
0
                chunkOffset = avifEncoderFindExistingChunk(s, mdatStartOffset, sample->data.data, sample->data.size);
2245
0
            } else if (item->encodeOutput->samples.count == 0) {
2246
0
                chunkOffset = avifEncoderFindExistingChunk(s, mdatStartOffset, item->metadataPayload.data, item->metadataPayload.size);
2247
0
            }
2248
2249
0
            if (!chunkOffset) {
2250
                // We've never seen this chunk before; write it out
2251
0
                chunkOffset = avifRWStreamOffset(s);
2252
0
                if (item->encodeOutput->samples.count > 0) {
2253
0
                    for (uint32_t sampleIndex = 0; sampleIndex < item->encodeOutput->samples.count; ++sampleIndex) {
2254
0
                        avifEncodeSample * sample = &item->encodeOutput->samples.sample[sampleIndex];
2255
0
                        AVIF_CHECKRES(avifRWStreamWrite(s, sample->data.data, sample->data.size));
2256
2257
0
                        if (isAlpha) {
2258
0
                            encoder->ioStats.alphaOBUSize += sample->data.size;
2259
0
                        } else if (item->itemCategory == AVIF_ITEM_COLOR) {
2260
0
                            encoder->ioStats.colorOBUSize += sample->data.size;
2261
0
                        } else if (item->itemCategory == AVIF_ITEM_GAIN_MAP) {
2262
0
                            encoder->data->gainMapSizeBytes += sample->data.size;
2263
0
                        }
2264
0
                    }
2265
0
                } else {
2266
0
                    AVIF_CHECKRES(avifRWStreamWrite(s, item->metadataPayload.data, item->metadataPayload.size));
2267
0
                }
2268
0
            }
2269
2270
0
            for (uint32_t fixupIndex = 0; fixupIndex < item->mdatFixups.count; ++fixupIndex) {
2271
0
                avifOffsetFixup * fixup = &item->mdatFixups.fixup[fixupIndex];
2272
0
                size_t prevOffset = avifRWStreamOffset(s);
2273
0
                avifRWStreamSetOffset(s, fixup->offset);
2274
0
                AVIF_CHECKRES(avifRWStreamWriteU32(s, (uint32_t)chunkOffset));
2275
0
                avifRWStreamSetOffset(s, prevOffset);
2276
0
            }
2277
0
        }
2278
0
    }
2279
2280
0
    uint32_t layeredItemCount = AVIF_MAX(layeredColorItems->count, layeredAlphaItems->count);
2281
0
    if (layeredItemCount > 0) {
2282
        // Interleave samples of all AV1 items.
2283
        // We first write the first layer of all items,
2284
        // in which we write first layer of each cell,
2285
        // in which we write alpha first and then color.
2286
0
        avifBool hasMoreSample;
2287
0
        uint32_t layerIndex = 0;
2288
0
        do {
2289
0
            hasMoreSample = AVIF_FALSE;
2290
0
            for (uint32_t itemIndex = 0; itemIndex < layeredItemCount; ++itemIndex) {
2291
0
                for (int samplePass = 0; samplePass < 2; ++samplePass) {
2292
                    // Alpha coming before color
2293
0
                    avifEncoderItemReferenceArray * currentItems = (samplePass == 0) ? layeredAlphaItems : layeredColorItems;
2294
0
                    if (itemIndex >= currentItems->count) {
2295
0
                        continue;
2296
0
                    }
2297
2298
                    // There is no way to select which grid cell should be written first for now.
2299
0
                    avifEncoderItem * item = currentItems->ref[itemIndex];
2300
0
                    if (item->encodeOutput->samples.count <= layerIndex) {
2301
                        // We've already written all samples of this item
2302
0
                        continue;
2303
0
                    } else if (item->encodeOutput->samples.count > layerIndex + 1) {
2304
0
                        hasMoreSample = AVIF_TRUE;
2305
0
                    }
2306
0
                    avifRWData * data = &item->encodeOutput->samples.sample[layerIndex].data;
2307
0
                    size_t chunkOffset = avifEncoderFindExistingChunk(s, mdatStartOffset, data->data, data->size);
2308
0
                    if (!chunkOffset) {
2309
                        // We've never seen this chunk before; write it out
2310
0
                        chunkOffset = avifRWStreamOffset(s);
2311
0
                        AVIF_CHECKRES(avifRWStreamWrite(s, data->data, data->size));
2312
0
                        if (samplePass == 0) {
2313
0
                            encoder->ioStats.alphaOBUSize += data->size;
2314
0
                        } else {
2315
0
                            encoder->ioStats.colorOBUSize += data->size;
2316
0
                        }
2317
0
                    }
2318
2319
0
                    size_t prevOffset = avifRWStreamOffset(s);
2320
0
                    avifRWStreamSetOffset(s, item->mdatFixups.fixup[layerIndex].offset);
2321
0
                    AVIF_CHECKRES(avifRWStreamWriteU32(s, (uint32_t)chunkOffset));
2322
0
                    avifRWStreamSetOffset(s, prevOffset);
2323
0
                }
2324
0
            }
2325
0
            ++layerIndex;
2326
0
        } while (hasMoreSample);
2327
2328
0
        AVIF_ASSERT_OR_RETURN(layerIndex <= AVIF_MAX_AV1_LAYER_COUNT);
2329
0
    }
2330
0
    AVIF_CHECKRES(avifRWStreamFinishBox(s, mdat));
2331
0
    return AVIF_RESULT_OK;
2332
0
}
2333
2334
static avifResult avifWriteAltrGroup(avifRWStream * s, uint32_t groupID, const avifEncoderItemIdArray * itemIDs)
2335
0
{
2336
0
    avifBoxMarker grpl;
2337
0
    AVIF_CHECKRES(avifRWStreamWriteBox(s, "grpl", AVIF_BOX_SIZE_TBD, &grpl));
2338
2339
0
    avifBoxMarker altr;
2340
0
    AVIF_CHECKRES(avifRWStreamWriteFullBox(s, "altr", AVIF_BOX_SIZE_TBD, 0, 0, &altr));
2341
2342
0
    AVIF_CHECKRES(avifRWStreamWriteU32(s, groupID));                  // unsigned int(32) group_id;
2343
0
    AVIF_CHECKRES(avifRWStreamWriteU32(s, (uint32_t)itemIDs->count)); // unsigned int(32) num_entities_in_group;
2344
0
    for (uint32_t i = 0; i < itemIDs->count; ++i) {
2345
0
        AVIF_CHECKRES(avifRWStreamWriteU32(s, (uint32_t)itemIDs->itemID[i])); // unsigned int(32) entity_id;
2346
0
    }
2347
2348
0
    AVIF_CHECKRES(avifRWStreamFinishBox(s, altr));
2349
2350
0
    AVIF_CHECKRES(avifRWStreamFinishBox(s, grpl));
2351
2352
0
    return AVIF_RESULT_OK;
2353
0
}
2354
2355
#if defined(AVIF_ENABLE_EXPERIMENTAL_MINI)
2356
// Returns true if the image can be encoded with a MinimizedImageBox instead of a full regular MetaBox.
2357
static avifBool avifEncoderIsMiniCompatible(const avifEncoder * encoder)
2358
{
2359
    // The MinimizedImageBox ("mif3" brand) only supports non-layered, still images.
2360
    if (encoder->extraLayerCount || (encoder->data->frames.count != 1)) {
2361
        return AVIF_FALSE;
2362
    }
2363
2364
    if (encoder->sampleTransformRecipe != AVIF_SAMPLE_TRANSFORM_NONE) {
2365
        return AVIF_FALSE;
2366
    }
2367
2368
    // Check for maximum field values and maximum chunk sizes.
2369
2370
    // width_minus1 and height_minus1
2371
    if (encoder->data->imageMetadata->width > (1 << 15) || encoder->data->imageMetadata->height > (1 << 15)) {
2372
        return AVIF_FALSE;
2373
    }
2374
    // icc_data_size_minus1, exif_data_size_minus1 and xmp_data_size_minus1
2375
    if (encoder->data->imageMetadata->icc.size > (1 << 20) || encoder->data->imageMetadata->exif.size > (1 << 20) ||
2376
        encoder->data->imageMetadata->xmp.size > (1 << 20)) {
2377
        return AVIF_FALSE;
2378
    }
2379
    // gainmap_width_minus1 and gainmap_height_minus1
2380
    if (encoder->data->imageMetadata->gainMap != NULL && encoder->data->imageMetadata->gainMap->image != NULL &&
2381
        (encoder->data->imageMetadata->gainMap->image->width > (1 << 15) ||
2382
         encoder->data->imageMetadata->gainMap->image->height > (1 << 15))) {
2383
        return AVIF_FALSE;
2384
    }
2385
    // tmap_icc_data_size_minus1
2386
    if (encoder->data->altImageMetadata->icc.size > (1 << 20)) {
2387
        return AVIF_FALSE;
2388
    }
2389
    // gainmap_metadata_size
2390
    if (encoder->data->imageMetadata->gainMap != NULL && avifGainMapMetadataSize(encoder->data->imageMetadata->gainMap) >= (1 << 20)) {
2391
        return AVIF_FALSE;
2392
    }
2393
2394
    // 4:4:4, 4:2:2, 4:2:0 and 4:0:0 are supported by a MinimizedImageBox.
2395
    // chroma_subsampling
2396
    if (encoder->data->imageMetadata->yuvFormat != AVIF_PIXEL_FORMAT_YUV444 &&
2397
        encoder->data->imageMetadata->yuvFormat != AVIF_PIXEL_FORMAT_YUV422 &&
2398
        encoder->data->imageMetadata->yuvFormat != AVIF_PIXEL_FORMAT_YUV420 &&
2399
        encoder->data->imageMetadata->yuvFormat != AVIF_PIXEL_FORMAT_YUV400) {
2400
        return AVIF_FALSE;
2401
    }
2402
    // gainmap_chroma_subsampling
2403
    if (encoder->data->imageMetadata->gainMap != NULL && encoder->data->imageMetadata->gainMap->image != NULL &&
2404
        (encoder->data->imageMetadata->gainMap->image->yuvFormat != AVIF_PIXEL_FORMAT_YUV444 &&
2405
         encoder->data->imageMetadata->gainMap->image->yuvFormat != AVIF_PIXEL_FORMAT_YUV422 &&
2406
         encoder->data->imageMetadata->gainMap->image->yuvFormat != AVIF_PIXEL_FORMAT_YUV420 &&
2407
         encoder->data->imageMetadata->gainMap->image->yuvFormat != AVIF_PIXEL_FORMAT_YUV400)) {
2408
        return AVIF_FALSE;
2409
    }
2410
2411
    // colour_primaries, transfer_characteristics and matrix_coefficients
2412
    if (encoder->data->imageMetadata->colorPrimaries > 255 || encoder->data->imageMetadata->transferCharacteristics > 255 ||
2413
        encoder->data->imageMetadata->matrixCoefficients > 255) {
2414
        return AVIF_FALSE;
2415
    }
2416
    // gainmap_colour_primaries, gainmap_transfer_characteristics and gainmap_matrix_coefficients
2417
    if (encoder->data->imageMetadata->gainMap != NULL && encoder->data->imageMetadata->gainMap->image != NULL &&
2418
        (encoder->data->imageMetadata->gainMap->image->colorPrimaries > 255 ||
2419
         encoder->data->imageMetadata->gainMap->image->transferCharacteristics > 255 ||
2420
         encoder->data->imageMetadata->gainMap->image->matrixCoefficients > 255)) {
2421
        return AVIF_FALSE;
2422
    }
2423
    // tmap_colour_primaries, tmap_transfer_characteristics and tmap_matrix_coefficients
2424
    if (encoder->data->altImageMetadata->colorPrimaries > 255 || encoder->data->altImageMetadata->transferCharacteristics > 255 ||
2425
        encoder->data->altImageMetadata->matrixCoefficients > 255) {
2426
        return AVIF_FALSE;
2427
    }
2428
2429
    const avifEncoderItem * colorItem = NULL;
2430
    for (uint32_t itemIndex = 0; itemIndex < encoder->data->items.count; ++itemIndex) {
2431
        avifEncoderItem * item = &encoder->data->items.item[itemIndex];
2432
2433
        // Grids are not supported by a MinimizedImageBox.
2434
        if (item->gridCols || item->gridRows) {
2435
            return AVIF_FALSE;
2436
        }
2437
2438
        if (item->id == encoder->data->primaryItemID) {
2439
            assert(!colorItem);
2440
            colorItem = item;
2441
            // main_item_data_size_minus1
2442
            if (item->encodeOutput->samples.count != 1 || item->encodeOutput->samples.sample[0].data.size > (1 << 28)) {
2443
                return AVIF_FALSE;
2444
            }
2445
            continue; // The primary item can be stored in the MinimizedImageBox.
2446
        }
2447
        if (item->itemCategory == AVIF_ITEM_ALPHA && item->irefToID == encoder->data->primaryItemID) {
2448
            // alpha_item_data_size
2449
            if (item->encodeOutput->samples.count != 1 || item->encodeOutput->samples.sample[0].data.size >= (1 << 28)) {
2450
                return AVIF_FALSE;
2451
            }
2452
            continue; // The alpha auxiliary item can be stored in the MinimizedImageBox.
2453
        }
2454
        if (item->itemCategory == AVIF_ITEM_GAIN_MAP) {
2455
            // gainmap_item_data_size
2456
            if (item->encodeOutput->samples.count != 1 || item->encodeOutput->samples.sample[0].data.size >= (1 << 28)) {
2457
                return AVIF_FALSE;
2458
            }
2459
            continue; // The gainmap input image item can be stored in the MinimizedImageBox.
2460
        }
2461
        if (!memcmp(item->type, "tmap", 4)) {
2462
            assert(item->itemCategory == AVIF_ITEM_COLOR); // Cannot be differentiated from the primary item by its itemCategory.
2463
            continue; // The tone mapping derived image item can be represented in the MinimizedImageBox.
2464
        }
2465
        if (!memcmp(item->type, "mime", 4) && !memcmp(item->infeName, "XMP", item->infeNameSize)) {
2466
            assert(item->metadataPayload.size == encoder->data->imageMetadata->xmp.size);
2467
            continue; // XMP metadata can be stored in the MinimizedImageBox.
2468
        }
2469
        if (!memcmp(item->type, "Exif", 4) && !memcmp(item->infeName, "Exif", item->infeNameSize)) {
2470
            assert(item->metadataPayload.size == encoder->data->imageMetadata->exif.size + 4);
2471
            const uint32_t exif_tiff_header_offset = *(uint32_t *)item->metadataPayload.data;
2472
            if (exif_tiff_header_offset != 0) {
2473
                return AVIF_FALSE;
2474
            }
2475
            continue; // Exif metadata can be stored in the MinimizedImageBox if exif_tiff_header_offset is 0.
2476
        }
2477
2478
        // Items besides the colorItem, the alphaItem, the gainmap item and Exif/XMP/ICC/HDR
2479
        // metadata are not directly supported by the MinimizedImageBox.
2480
        return AVIF_FALSE;
2481
    }
2482
    // A primary item is necessary.
2483
    if (!colorItem) {
2484
        return AVIF_FALSE;
2485
    }
2486
    return AVIF_TRUE;
2487
}
2488
2489
static avifResult avifEncoderWriteMiniBox(avifEncoder * encoder, avifRWStream * s);
2490
2491
static avifResult avifEncoderWriteFileTypeBoxAndMiniBox(avifEncoder * encoder, avifRWData * output)
2492
{
2493
    avifRWStream s;
2494
    avifRWStreamStart(&s, output);
2495
2496
    avifBoxMarker ftyp;
2497
    AVIF_CHECKRES(avifRWStreamWriteBox(&s, "ftyp", AVIF_BOX_SIZE_TBD, &ftyp));
2498
    AVIF_CHECKRES(avifRWStreamWriteChars(&s, "mif3", 4)); // unsigned int(32) major_brand;
2499
    AVIF_CHECKRES(avifRWStreamWriteChars(&s, "avif", 4)); // unsigned int(32) minor_version;
2500
                                                          // unsigned int(32) compatible_brands[];
2501
    AVIF_CHECKRES(avifRWStreamFinishBox(&s, ftyp));
2502
2503
    AVIF_CHECKRES(avifEncoderWriteMiniBox(encoder, &s));
2504
2505
    avifRWStreamFinishWrite(&s);
2506
    return AVIF_RESULT_OK;
2507
}
2508
2509
static avifResult avifEncoderWriteMiniBox(avifEncoder * encoder, avifRWStream * s)
2510
{
2511
    const avifEncoderItem * colorItem = NULL;
2512
    const avifEncoderItem * alphaItem = NULL;
2513
    const avifEncoderItem * gainmapItem = NULL;
2514
    for (uint32_t itemIndex = 0; itemIndex < encoder->data->items.count; ++itemIndex) {
2515
        avifEncoderItem * item = &encoder->data->items.item[itemIndex];
2516
        if (item->id == encoder->data->primaryItemID) {
2517
            AVIF_ASSERT_OR_RETURN(!colorItem);
2518
            colorItem = item;
2519
        } else if (item->itemCategory == AVIF_ITEM_ALPHA && item->irefToID == encoder->data->primaryItemID) {
2520
            AVIF_ASSERT_OR_RETURN(!alphaItem);
2521
            alphaItem = item;
2522
        }
2523
        if (item->itemCategory == AVIF_ITEM_GAIN_MAP) {
2524
            AVIF_ASSERT_OR_RETURN(!gainmapItem);
2525
            gainmapItem = item;
2526
        }
2527
    }
2528
2529
    AVIF_ASSERT_OR_RETURN(colorItem);
2530
    const avifRWData * colorData = &colorItem->encodeOutput->samples.sample[0].data;
2531
    const avifRWData * alphaData = alphaItem ? &alphaItem->encodeOutput->samples.sample[0].data : NULL;
2532
    const avifRWData * gainmapData = gainmapItem ? &gainmapItem->encodeOutput->samples.sample[0].data : NULL;
2533
2534
    const avifImage * const image = encoder->data->imageMetadata;
2535
2536
    const avifBool hasAlpha = alphaItem != NULL;
2537
    const avifBool alphaIsPremultiplied = encoder->data->imageMetadata->alphaPremultiplied;
2538
    const avifBool hasGainmap = gainmapItem != NULL;
2539
    const avifBool hasHdr = hasGainmap; // libavif only supports gainmap-based HDR encoding for now.
2540
    const avifBool hasIcc = image->icc.size != 0;
2541
    const uint32_t chromaSubsampling = image->yuvFormat == AVIF_PIXEL_FORMAT_YUV400   ? 0
2542
                                       : image->yuvFormat == AVIF_PIXEL_FORMAT_YUV420 ? 1
2543
                                       : image->yuvFormat == AVIF_PIXEL_FORMAT_YUV422 ? 2
2544
                                                                                      : 3;
2545
2546
    const avifColorPrimaries defaultColorPrimaries = hasIcc ? AVIF_COLOR_PRIMARIES_UNSPECIFIED : AVIF_COLOR_PRIMARIES_BT709;
2547
    const avifTransferCharacteristics defaultTransferCharacteristics = hasIcc ? AVIF_TRANSFER_CHARACTERISTICS_UNSPECIFIED
2548
                                                                              : AVIF_TRANSFER_CHARACTERISTICS_SRGB;
2549
    const avifMatrixCoefficients defaultMatrixCoefficients = chromaSubsampling == 0 ? AVIF_MATRIX_COEFFICIENTS_UNSPECIFIED
2550
                                                                                    : AVIF_MATRIX_COEFFICIENTS_BT601;
2551
    const avifBool hasExplicitCicp = image->colorPrimaries != defaultColorPrimaries ||
2552
                                     image->transferCharacteristics != defaultTransferCharacteristics ||
2553
                                     image->matrixCoefficients != defaultMatrixCoefficients;
2554
2555
    const avifBool floatFlag = AVIF_FALSE;
2556
    const avifBool fullRange = image->yuvRange == AVIF_RANGE_FULL;
2557
2558
    // In AV1, the chroma_sample_position syntax element is not present for the YUV 4:2:2 format.
2559
    // Assume that AV1 uses the same 4:2:2 chroma sample location as HEVC and VVC (colocated).
2560
    if (image->yuvFormat != AVIF_PIXEL_FORMAT_YUV420 && image->yuvChromaSamplePosition != AVIF_CHROMA_SAMPLE_POSITION_UNKNOWN) {
2561
        avifDiagnosticsPrintf(&encoder->diag,
2562
                              "YUV chroma sample position %d is only supported with 4:2:0 YUV format in AV1",
2563
                              image->yuvChromaSamplePosition);
2564
        return AVIF_RESULT_INVALID_ARGUMENT;
2565
    }
2566
    // For the YUV 4:2:0 format, assume centered sample position unless specified otherwise.
2567
    // This is consistent with the behavior in read.c.
2568
    const avifBool chromaIsHorizontallyCentered = image->yuvFormat == AVIF_PIXEL_FORMAT_YUV420 &&
2569
                                                  image->yuvChromaSamplePosition != AVIF_CHROMA_SAMPLE_POSITION_VERTICAL &&
2570
                                                  image->yuvChromaSamplePosition != AVIF_CHROMA_SAMPLE_POSITION_COLOCATED;
2571
    const avifBool chromaIsVerticallyCentered = image->yuvFormat == AVIF_PIXEL_FORMAT_YUV420 &&
2572
                                                image->yuvChromaSamplePosition != AVIF_CHROMA_SAMPLE_POSITION_COLOCATED;
2573
2574
    const uint32_t orientationMinus1 = avifImageIrotImirToExifOrientation(image) - 1;
2575
2576
    uint8_t infeType[4];
2577
    uint8_t codecConfigType[4];
2578
    avifBool hasExplicitCodecTypes;
2579
    if (encoder->codecChoice == AVIF_CODEC_CHOICE_AVM) {
2580
        memcpy(infeType, "av02", 4);
2581
        memcpy(codecConfigType, "av2C", 4); // Same syntax as 'av1C'.
2582
        hasExplicitCodecTypes = AVIF_TRUE;
2583
    } else {
2584
        memcpy(infeType, "av01", 4);
2585
        memcpy(codecConfigType, "av1C", 4);
2586
        // 'av01' and 'av1C' are implied by 'avif' minor_version field of FileTypeBox. No need to write them.
2587
        hasExplicitCodecTypes = AVIF_FALSE;
2588
    }
2589
2590
    // _minus1 is encoded for these fields.
2591
    AVIF_ASSERT_OR_RETURN(image->width != 0 && image->height != 0);
2592
    AVIF_ASSERT_OR_RETURN(colorData->size != 0);
2593
2594
    uint32_t largeDimensionsFlag = image->width - 1 >= (1 << 7) || image->height - 1 >= (1 << 7);
2595
    const uint32_t codecConfigSize = 4;  // 'av1C' always uses 4 bytes.
2596
    uint32_t alphaCodecConfigSize = 0;   // 0 if same codec config as main. Equal to codecConfigSize otherwise.
2597
    uint32_t gainmapCodecConfigSize = 0; // 0 if same codec config as main. Equal to codecConfigSize otherwise.
2598
    uint32_t gainmapMetadataSize = 0;
2599
    const uint32_t largeCodecConfigFlag = codecConfigSize >= (1 << 3);
2600
    uint32_t largeItemDataFlag = colorData->size - 1 >= (1 << 15) || (alphaData && alphaData->size >= (1 << 15));
2601
    uint32_t largeMetadataFlag = (hasIcc && image->icc.size - 1 >= (1 << 10)) ||
2602
                                 (image->exif.size != 0 && image->exif.size - 1 >= (1 << 10)) ||
2603
                                 (image->xmp.size != 0 && image->xmp.size - 1 >= (1 << 10));
2604
2605
    if (hasGainmap) {
2606
        AVIF_ASSERT_OR_RETURN(image->gainMap != NULL && image->gainMap->image != NULL);
2607
        gainmapMetadataSize = avifGainMapMetadataSize(image->gainMap);
2608
        AVIF_ASSERT_OR_RETURN(gainmapData != NULL);
2609
2610
        // _minus1 is encoded for these fields.
2611
        AVIF_ASSERT_OR_RETURN(image->gainMap->image->width != 0 && image->gainMap->image->height != 0);
2612
2613
        largeDimensionsFlag |= image->gainMap->image->width - 1 >= (1 << 7) || image->gainMap->image->height - 1 >= (1 << 7);
2614
        largeItemDataFlag |= gainmapData->size >= (1 << 15);
2615
        largeMetadataFlag |=
2616
            (encoder->data->altImageMetadata->icc.size != 0 && encoder->data->altImageMetadata->icc.size - 1 >= (1 << 10)) ||
2617
            gainmapMetadataSize >= (1 << 10);
2618
        // image->gainMap->image->icc is ignored.
2619
    }
2620
2621
    avifBoxMarker mini;
2622
    AVIF_CHECKRES(avifRWStreamWriteBox(s, "mini", AVIF_BOX_SIZE_TBD, &mini));
2623
    AVIF_CHECKRES(avifRWStreamWriteBits(s, 0, 2)); // bit(2) version = 0;
2624
2625
    // flags
2626
    AVIF_CHECKRES(avifRWStreamWriteBits(s, hasExplicitCodecTypes, 1)); // bit(1) explicit_codec_types_flag;
2627
    AVIF_CHECKRES(avifRWStreamWriteBits(s, floatFlag, 1));             // bit(1) float_flag;
2628
    AVIF_CHECKRES(avifRWStreamWriteBits(s, fullRange, 1));             // bit(1) full_range_flag;
2629
    AVIF_CHECKRES(avifRWStreamWriteBits(s, alphaItem != 0, 1));        // bit(1) alpha_flag;
2630
    AVIF_CHECKRES(avifRWStreamWriteBits(s, hasExplicitCicp, 1));       // bit(1) explicit_cicp_flag;
2631
    AVIF_CHECKRES(avifRWStreamWriteBits(s, hasHdr, 1));                // bit(1) hdr_flag;
2632
    AVIF_CHECKRES(avifRWStreamWriteBits(s, hasIcc, 1));                // bit(1) icc_flag;
2633
    AVIF_CHECKRES(avifRWStreamWriteBits(s, image->exif.size != 0, 1)); // bit(1) exif_flag;
2634
    AVIF_CHECKRES(avifRWStreamWriteBits(s, image->xmp.size != 0, 1));  // bit(1) xmp_flag;
2635
2636
    AVIF_CHECKRES(avifRWStreamWriteBits(s, chromaSubsampling, 2)); // bit(2) chroma_subsampling;
2637
    AVIF_CHECKRES(avifRWStreamWriteBits(s, orientationMinus1, 3)); // bit(3) orientation_minus1;
2638
2639
    // Spatial extents
2640
    AVIF_CHECKRES(avifRWStreamWriteBits(s, largeDimensionsFlag, 1));                         // bit(1) large_dimensions_flag;
2641
    AVIF_CHECKRES(avifRWStreamWriteBits(s, image->width - 1, largeDimensionsFlag ? 15 : 7)); // unsigned int(large_dimensions_flag ? 15 : 7) width_minus1;
2642
    AVIF_CHECKRES(avifRWStreamWriteBits(s, image->height - 1, largeDimensionsFlag ? 15 : 7)); // unsigned int(large_dimensions_flag ? 15 : 7) height_minus1;
2643
2644
    // Pixel information
2645
    if (chromaSubsampling == 1 || chromaSubsampling == 2) {
2646
        AVIF_CHECKRES(avifRWStreamWriteBits(s, chromaIsHorizontallyCentered, 1)); // bit(1) chroma_is_horizontally_centered;
2647
    }
2648
    if (chromaSubsampling == 1) {
2649
        AVIF_CHECKRES(avifRWStreamWriteBits(s, chromaIsVerticallyCentered, 1)); // bit(1) chroma_is_vertically_centered;
2650
    }
2651
2652
    if (floatFlag) {
2653
        // bit(2) bit_depth_log2_minus4;
2654
        AVIF_ASSERT_NOT_REACHED_OR_RETURN;
2655
    } else {
2656
        AVIF_CHECKRES(avifRWStreamWriteBits(s, image->depth > 8, 1)); // bit(1) high_bit_depth_flag;
2657
        if (image->depth > 8) {
2658
            AVIF_CHECKRES(avifRWStreamWriteBits(s, image->depth - 9, 3)); // bit(3) bit_depth_minus9;
2659
        }
2660
    }
2661
2662
    if (alphaItem) {
2663
        AVIF_CHECKRES(avifRWStreamWriteBits(s, alphaIsPremultiplied, 1)); // bit(1) alpha_is_premultiplied;
2664
    }
2665
2666
    // Colour properties
2667
    if (hasExplicitCicp) {
2668
        AVIF_CHECKRES(avifRWStreamWriteBits(s, image->colorPrimaries, 8));          // bit(8) colour_primaries;
2669
        AVIF_CHECKRES(avifRWStreamWriteBits(s, image->transferCharacteristics, 8)); // bit(8) transfer_characteristics;
2670
        if (chromaSubsampling != 0) {
2671
            AVIF_CHECKRES(avifRWStreamWriteBits(s, image->matrixCoefficients, 8)); // bit(8) matrix_coefficients;
2672
        } else {
2673
            AVIF_CHECKERR(image->matrixCoefficients == AVIF_MATRIX_COEFFICIENTS_UNSPECIFIED, AVIF_RESULT_ENCODE_COLOR_FAILED);
2674
        }
2675
    }
2676
2677
    if (hasExplicitCodecTypes) {
2678
        // bit(32) infe_type;
2679
        for (int i = 0; i < 4; ++i) {
2680
            AVIF_CHECKRES(avifRWStreamWriteBits(s, infeType[i], 8));
2681
        }
2682
        // bit(32) codec_config_type;
2683
        for (int i = 0; i < 4; ++i) {
2684
            AVIF_CHECKRES(avifRWStreamWriteBits(s, codecConfigType[i], 8));
2685
        }
2686
    }
2687
2688
    // High Dynamic Range properties
2689
    size_t tmapIccSize = 0;
2690
    if (hasHdr) {
2691
        AVIF_CHECKRES(avifRWStreamWriteBits(s, hasGainmap, 1)); // bit(1) gainmap_flag;
2692
        if (hasGainmap) {
2693
            const avifImage * tmap = encoder->data->altImageMetadata;
2694
            const avifImage * gainmap = image->gainMap->image;
2695
            AVIF_CHECKRES(avifRWStreamWriteBits(s, gainmap->width - 1, largeDimensionsFlag ? 15 : 7)); // unsigned int(large_dimensions_flag ? 15 : 7) gainmap_width_minus1;
2696
            AVIF_CHECKRES(avifRWStreamWriteBits(s, gainmap->height - 1, largeDimensionsFlag ? 15 : 7)); // unsigned int(large_dimensions_flag ? 15 : 7) gainmap_height_minus1;
2697
            AVIF_CHECKRES(avifRWStreamWriteBits(s, gainmap->matrixCoefficients, 8)); // bit(8) gainmap_matrix_coefficients;
2698
            AVIF_CHECKRES(avifRWStreamWriteBits(s, gainmap->yuvRange == AVIF_RANGE_FULL, 1)); // bit(1) gainmap_full_range_flag;
2699
            const uint32_t gainmapChromaSubsampling = gainmap->yuvFormat == AVIF_PIXEL_FORMAT_YUV400   ? 0
2700
                                                      : gainmap->yuvFormat == AVIF_PIXEL_FORMAT_YUV420 ? 1
2701
                                                      : gainmap->yuvFormat == AVIF_PIXEL_FORMAT_YUV422 ? 2
2702
                                                                                                       : 3;
2703
            AVIF_CHECKRES(avifRWStreamWriteBits(s, gainmapChromaSubsampling, 2)); // bit(1) gainmap_chroma_subsampling;
2704
            if (gainmapChromaSubsampling == 1 || gainmapChromaSubsampling == 2) {
2705
                AVIF_CHECKRES(avifRWStreamWriteBits(s,
2706
                                                    gainmap->yuvFormat == AVIF_PIXEL_FORMAT_YUV420 &&
2707
                                                        gainmap->yuvChromaSamplePosition != AVIF_CHROMA_SAMPLE_POSITION_VERTICAL &&
2708
                                                        gainmap->yuvChromaSamplePosition != AVIF_CHROMA_SAMPLE_POSITION_COLOCATED,
2709
                                                    1)); // bit(1) gainmap_chroma_is_horizontally_centered;
2710
            }
2711
            if (gainmapChromaSubsampling == 1) {
2712
                AVIF_CHECKRES(avifRWStreamWriteBits(s,
2713
                                                    gainmap->yuvFormat == AVIF_PIXEL_FORMAT_YUV420 &&
2714
                                                        gainmap->yuvChromaSamplePosition != AVIF_CHROMA_SAMPLE_POSITION_COLOCATED,
2715
                                                    1)); // bit(1) gainmap_chroma_is_vertically_centered;
2716
            }
2717
2718
            const avifBool gainmapFloatFlag = AVIF_FALSE;
2719
            AVIF_CHECKRES(avifRWStreamWriteBits(s, gainmapFloatFlag, 1)); // bit(1) gainmap_float_flag;
2720
            if (gainmapFloatFlag) {
2721
                // bit(2) gainmap_bit_depth_log2_minus4;
2722
                AVIF_ASSERT_NOT_REACHED_OR_RETURN;
2723
            } else {
2724
                AVIF_CHECKRES(avifRWStreamWriteBits(s, gainmap->depth > 8, 1)); // bit(1) gainmap_high_bit_depth_flag;
2725
                if (gainmap->depth > 8) {
2726
                    AVIF_CHECKRES(avifRWStreamWriteBits(s, gainmap->depth - 9, 3)); // bit(3) gainmap_bit_depth_minus9;
2727
                }
2728
            }
2729
2730
            tmapIccSize = encoder->data->altImageMetadata->icc.size;
2731
            AVIF_CHECKRES(avifRWStreamWriteBits(s, tmapIccSize != 0, 1)); // bit(1) tmap_icc_flag;
2732
            const avifBool tmapHasExplicitCicp = tmap->colorPrimaries != AVIF_COLOR_PRIMARIES_BT709 ||
2733
                                                 tmap->transferCharacteristics != AVIF_TRANSFER_CHARACTERISTICS_SRGB ||
2734
                                                 tmap->matrixCoefficients != AVIF_MATRIX_COEFFICIENTS_BT601 ||
2735
                                                 tmap->yuvRange != AVIF_RANGE_FULL;
2736
            AVIF_CHECKRES(avifRWStreamWriteBits(s, tmapHasExplicitCicp, 1)); // bit(1) tmap_explicit_cicp_flag;
2737
            if (tmapHasExplicitCicp) {
2738
                AVIF_CHECKRES(avifRWStreamWriteBits(s, tmap->colorPrimaries, 8));          // bit(8) tmap_colour_primaries;
2739
                AVIF_CHECKRES(avifRWStreamWriteBits(s, tmap->transferCharacteristics, 8)); // bit(8) tmap_transfer_characteristics;
2740
                AVIF_CHECKRES(avifRWStreamWriteBits(s, tmap->matrixCoefficients, 8));      // bit(8) tmap_matrix_coefficients;
2741
                AVIF_CHECKRES(avifRWStreamWriteBits(s, tmap->yuvRange == AVIF_RANGE_FULL, 1)); // bit(8) tmap_full_range_flag;
2742
            }
2743
            // gainmap->icc is ignored.
2744
        }
2745
2746
        AVIF_CHECKRES(avifEncoderWriteMiniHDRProperties(s, image));
2747
        if (hasGainmap) {
2748
            AVIF_CHECKRES(avifEncoderWriteMiniHDRProperties(s, encoder->data->altImageMetadata));
2749
        }
2750
    }
2751
2752
    // Chunk sizes
2753
    if (hasIcc || image->exif.size || image->xmp.size || (hasHdr && hasGainmap)) {
2754
        AVIF_CHECKRES(avifRWStreamWriteBits(s, largeMetadataFlag, 1)); // bit(1) large_metadata_flag;
2755
    }
2756
    AVIF_CHECKRES(avifRWStreamWriteBits(s, largeCodecConfigFlag, 1)); // bit(1) large_codec_config_flag;
2757
    AVIF_CHECKRES(avifRWStreamWriteBits(s, largeItemDataFlag, 1));    // bit(1) large_item_data_flag;
2758
2759
    if (hasIcc) {
2760
        AVIF_CHECKRES(avifRWStreamWriteBits(s, (uint32_t)image->icc.size - 1, largeMetadataFlag ? 20 : 10)); // unsigned int(large_metadata_flag ? 20 : 10) icc_data_size_minus1;
2761
    }
2762
    if (hasHdr && hasGainmap && tmapIccSize != 0) {
2763
        AVIF_CHECKRES(avifRWStreamWriteBits(s, (uint32_t)tmapIccSize - 1, largeMetadataFlag ? 20 : 10)); // unsigned int(large_metadata_flag ? 20 : 10) tmap_icc_data_size_minus1;
2764
    }
2765
2766
    if (hasHdr && hasGainmap) {
2767
        AVIF_CHECKRES(avifRWStreamWriteBits(s, gainmapMetadataSize, largeMetadataFlag ? 20 : 10)); // unsigned int(large_metadata_flag ? 20 : 10) gainmap_metadata_size;
2768
    }
2769
    if (hasHdr && hasGainmap) {
2770
        AVIF_CHECKRES(avifRWStreamWriteBits(s, (uint32_t)gainmapData->size, largeItemDataFlag ? 28 : 15)); // unsigned int(large_item_data_flag ? 28 : 15) gainmap_item_data_size;
2771
    }
2772
    if (hasHdr && hasGainmap && gainmapData->size != 0) {
2773
        if (!memcmp(&colorItem->av1C, &gainmapItem->av1C, sizeof(colorItem->av1C))) {
2774
            // The gainmap codec config is copied from the main codec config.
2775
            // This is signaled by a size of 0.
2776
            gainmapCodecConfigSize = 0;
2777
        } else {
2778
            gainmapCodecConfigSize = codecConfigSize;
2779
        }
2780
        AVIF_CHECKRES(avifRWStreamWriteBits(s, gainmapCodecConfigSize, largeCodecConfigFlag ? 12 : 3)); // unsigned int(large_codec_config_flag ? 12 : 3) gainmap_item_codec_config_size;
2781
    }
2782
2783
    AVIF_CHECKRES(avifRWStreamWriteBits(s, codecConfigSize, largeCodecConfigFlag ? 12 : 3)); // unsigned int(large_codec_config_flag ? 12 : 3) main_item_codec_config_size;
2784
    AVIF_CHECKRES(avifRWStreamWriteBits(s, (uint32_t)colorData->size - 1, largeItemDataFlag ? 28 : 15)); // unsigned int(large_item_data_flag ? 28 : 15) main_item_data_size_minus1;
2785
2786
    if (hasAlpha) {
2787
        AVIF_CHECKRES(avifRWStreamWriteBits(s, (uint32_t)alphaData->size, largeItemDataFlag ? 28 : 15)); // unsigned int(large_item_data_flag ? 28 : 15) alpha_item_data_size;
2788
    }
2789
    if (hasAlpha && alphaData->size != 0) {
2790
        if (!memcmp(&colorItem->av1C, &alphaItem->av1C, sizeof(colorItem->av1C))) {
2791
            // The alpha codec config is copied from the main codec config.
2792
            // This is signaled by a size of 0.
2793
            alphaCodecConfigSize = 0;
2794
        } else {
2795
            alphaCodecConfigSize = codecConfigSize;
2796
        }
2797
        AVIF_CHECKRES(avifRWStreamWriteBits(s, alphaCodecConfigSize, largeCodecConfigFlag ? 12 : 3)); // unsigned int(large_codec_config_flag ? 12 : 3) alpha_item_codec_config_size;
2798
    }
2799
2800
    if (image->exif.size || image->xmp.size) {
2801
        AVIF_CHECKRES(avifRWStreamWriteBits(s, 0, 1)); // unsigned int(1) exif_xmp_compressed_flag
2802
    }
2803
    if (image->exif.size) {
2804
        AVIF_CHECKRES(avifRWStreamWriteBits(s, (uint32_t)image->exif.size - 1, largeMetadataFlag ? 20 : 10)); // unsigned int(large_metadata_flag ? 20 : 10) exif_data_size_minus_one;
2805
    }
2806
    if (image->xmp.size) {
2807
        AVIF_CHECKRES(avifRWStreamWriteBits(s, (uint32_t)image->xmp.size - 1, largeMetadataFlag ? 20 : 10)); // unsigned int(large_metadata_flag ? 20 : 10) xmp_data_size_minus_one;
2808
    }
2809
2810
    // trailing_bits(); // bit padding till byte alignment
2811
    if (s->numUsedBitsInPartialByte != 0) {
2812
        AVIF_CHECKRES(avifRWStreamWriteBits(s, 0, 8 - s->numUsedBitsInPartialByte));
2813
    }
2814
    const size_t headerBytes = avifRWStreamOffset(s);
2815
2816
    // Chunks
2817
    if (codecConfigSize > 0) {
2818
        AVIF_CHECKRES(writeCodecConfig(s, &colorItem->av1C)); // unsigned int(8) main_item_codec_config[main_item_codec_config_size];
2819
    }
2820
    if (hasAlpha && alphaData->size != 0 && alphaCodecConfigSize != 0) {
2821
        AVIF_CHECKRES(writeCodecConfig(s, &alphaItem->av1C)); // unsigned int(8) alpha_item_codec_config[alpha_item_codec_config_size];
2822
    }
2823
    if (hasHdr && hasGainmap && gainmapCodecConfigSize != 0) {
2824
        AVIF_CHECKRES(writeCodecConfig(s, &gainmapItem->av1C)); // unsigned int(8) gainmap_item_codec_config[gainmap_item_codec_config_size];
2825
    }
2826
2827
    if (hasIcc) {
2828
        AVIF_CHECKRES(avifRWStreamWrite(s, image->icc.data, image->icc.size)); // unsigned int(8) icc_data[icc_data_size_minus1 + 1];
2829
    }
2830
    if (hasHdr && hasGainmap && tmapIccSize != 0) {
2831
        AVIF_CHECKRES(avifRWStreamWrite(s, encoder->data->altImageMetadata->icc.data, tmapIccSize)); // unsigned int(8) tmap_icc_data[tmap_icc_data_size_minus1 + 1];
2832
    }
2833
    if (hasHdr && hasGainmap && gainmapMetadataSize != 0) {
2834
        AVIF_CHECKRES(avifWriteGainmapMetadata(s, image->gainMap, &encoder->diag)); // unsigned int(8) gainmap_metadata[gainmap_metadata_size];
2835
    }
2836
2837
    if (hasAlpha && alphaData->size != 0) {
2838
        AVIF_CHECKRES(avifRWStreamWrite(s, alphaData->data, alphaData->size)); // unsigned int(8) alpha_item_data[alpha_item_data_size];
2839
    }
2840
    if (hasHdr && hasGainmap && gainmapData->size != 0) {
2841
        AVIF_CHECKRES(avifRWStreamWrite(s, gainmapData->data, gainmapData->size)); // unsigned int(8) gainmap_item_data[gainmap_item_data_size];
2842
    }
2843
2844
    AVIF_CHECKRES(avifRWStreamWrite(s, colorData->data, colorData->size)); // unsigned int(8) main_item_data[main_item_data_size_minus1 + 1];
2845
2846
    if (image->exif.size) {
2847
        AVIF_CHECKRES(avifRWStreamWrite(s, image->exif.data, image->exif.size)); // unsigned int(8) exif_data[exif_data_size_minus1 + 1];
2848
    }
2849
    if (image->xmp.size) {
2850
        AVIF_CHECKRES(avifRWStreamWrite(s, image->xmp.data, image->xmp.size)); // unsigned int(8) xmp_data[xmp_data_size_minus1 + 1];
2851
    }
2852
2853
    const size_t expectedChunkBytes = codecConfigSize + alphaCodecConfigSize + gainmapCodecConfigSize + image->icc.size +
2854
                                      tmapIccSize + gainmapMetadataSize + (hasAlpha ? alphaData->size : 0) +
2855
                                      (hasGainmap ? gainmapData->size : 0) + colorData->size + image->exif.size + image->xmp.size;
2856
    AVIF_ASSERT_OR_RETURN(avifRWStreamOffset(s) == headerBytes + expectedChunkBytes);
2857
    AVIF_CHECKRES(avifRWStreamFinishBox(s, mini));
2858
    return AVIF_RESULT_OK;
2859
}
2860
#endif // AVIF_ENABLE_EXPERIMENTAL_MINI
2861
2862
static avifResult avifRWStreamWriteProperties(avifItemPropertyDedup * const dedup,
2863
                                              avifRWStream * const s,
2864
                                              const avifEncoder * const encoder,
2865
                                              const avifImage * const imageMetadata,
2866
                                              const avifImage * const altImageMetadata)
2867
0
{
2868
0
    for (uint32_t itemIndex = 0; itemIndex < encoder->data->items.count; ++itemIndex) {
2869
0
        avifEncoderItem * item = &encoder->data->items.item[itemIndex];
2870
0
        const avifBool isGrid = (item->gridCols > 0);
2871
        // Whether there is ipma to write for this item.
2872
0
        avifBool hasIpmaToWrite = item->codec || isGrid;
2873
0
        const avifBool isToneMappedImage = !memcmp(item->type, "tmap", 4);
2874
0
        if (isToneMappedImage) {
2875
0
            hasIpmaToWrite = AVIF_TRUE;
2876
0
        }
2877
0
        const avifBool isSampleTransformImage = !memcmp(item->type, "sato", 4);
2878
0
        if (isSampleTransformImage) {
2879
0
            hasIpmaToWrite = AVIF_TRUE;
2880
0
        }
2881
0
        item->associations.count = 0;
2882
0
        if (!hasIpmaToWrite) {
2883
0
            continue;
2884
0
        }
2885
2886
0
        if (item->dimgFromID && (item->extraLayerCount == 0)) {
2887
0
            avifEncoderItem * parentItem = avifEncoderDataFindItemByID(encoder->data, item->dimgFromID);
2888
0
            if (parentItem && !memcmp(parentItem->type, "grid", 4)) {
2889
                // All image cells from a grid should share the exact same properties unless they are
2890
                // layered image which have different al1x, so see if we've already written properties
2891
                // out for another cell in this grid, and if so, just steal their ipma and move on.
2892
                // This is a sneaky way to provide iprp deduplication.
2893
2894
0
                avifBool foundPreviousCell = AVIF_FALSE;
2895
0
                for (uint32_t dedupIndex = 0; dedupIndex < itemIndex; ++dedupIndex) {
2896
0
                    avifEncoderItem * dedupItem = &encoder->data->items.item[dedupIndex];
2897
0
                    if ((item->dimgFromID == dedupItem->dimgFromID) && (dedupItem->extraLayerCount == 0)) {
2898
                        // We've already written dedup's items out. Steal their ipma indices and move on!
2899
0
                        item->associations.count = 0;
2900
0
                        for (uint32_t associationIndex = 0; associationIndex < dedupItem->associations.count; ++associationIndex) {
2901
0
                            avifItemPropertyAssociation * association = (avifItemPropertyAssociation *)avifArrayPush(&item->associations);
2902
0
                            AVIF_CHECKERR(association != NULL, AVIF_RESULT_OUT_OF_MEMORY);
2903
0
                            *association = dedupItem->associations.association[associationIndex];
2904
0
                        }
2905
0
                        foundPreviousCell = AVIF_TRUE;
2906
0
                        break;
2907
0
                    }
2908
0
                }
2909
0
                if (foundPreviousCell) {
2910
0
                    continue;
2911
0
                }
2912
0
            }
2913
0
        }
2914
2915
0
        const avifImage * itemMetadata = imageMetadata;
2916
0
        if (isToneMappedImage) {
2917
0
            itemMetadata = altImageMetadata;
2918
0
        } else if (item->itemCategory == AVIF_ITEM_GAIN_MAP) {
2919
0
            AVIF_ASSERT_OR_RETURN(itemMetadata->gainMap && itemMetadata->gainMap->image);
2920
0
            itemMetadata = itemMetadata->gainMap->image;
2921
0
        }
2922
0
        uint32_t imageWidth = itemMetadata->width;
2923
0
        uint32_t imageHeight = itemMetadata->height;
2924
0
        if (isGrid) {
2925
0
            imageWidth = item->gridWidth;
2926
0
            imageHeight = item->gridHeight;
2927
0
        }
2928
2929
        // ISO/IEC 23008-12:2024 section 6.5.1:
2930
        //   Writers should arrange the descriptive properties specified in 6.5 prior to any other properties
2931
        //   in the sequence associating properties with an item.
2932
        // For each item, write the descriptive properties first (and thus associate them before the
2933
        // transformative properties).
2934
2935
        // Properties all image items need (coded and derived)
2936
        // ispe = image spatial extent (width, height)
2937
0
        avifItemPropertyDedupStart(dedup);
2938
0
        avifBoxMarker ispe;
2939
0
        AVIF_CHECKRES(avifRWStreamWriteFullBox(&dedup->s, "ispe", AVIF_BOX_SIZE_TBD, 0, 0, &ispe));
2940
0
        AVIF_CHECKRES(avifRWStreamWriteU32(&dedup->s, imageWidth));  // unsigned int(32) image_width;
2941
0
        AVIF_CHECKRES(avifRWStreamWriteU32(&dedup->s, imageHeight)); // unsigned int(32) image_height;
2942
0
        AVIF_CHECKRES(avifRWStreamFinishBox(&dedup->s, ispe));
2943
0
        AVIF_CHECKRES(avifItemPropertyDedupFinish(dedup, s, &item->associations, /*essential=*/AVIF_FALSE));
2944
2945
        // pixi = pixel information (depth, channel count)
2946
0
        avifBool hasPixi = AVIF_TRUE;
2947
        // Pixi is optional for the 'tmap' item.
2948
0
        if (isToneMappedImage && imageMetadata->gainMap->altDepth == 0 && imageMetadata->gainMap->altPlaneCount == 0) {
2949
0
            hasPixi = AVIF_FALSE;
2950
0
        }
2951
0
        const avifBool isAlpha = avifIsAlpha(item->itemCategory);
2952
0
        uint8_t depth = (uint8_t)itemMetadata->depth;
2953
0
        if (encoder->sampleTransformRecipe == AVIF_SAMPLE_TRANSFORM_BIT_DEPTH_EXTENSION_8B_8B ||
2954
0
            encoder->sampleTransformRecipe == AVIF_SAMPLE_TRANSFORM_BIT_DEPTH_EXTENSION_12B_4B ||
2955
0
            encoder->sampleTransformRecipe == AVIF_SAMPLE_TRANSFORM_BIT_DEPTH_EXTENSION_12B_8B_OVERLAP_4B) {
2956
0
            if (item->itemCategory == AVIF_ITEM_SAMPLE_TRANSFORM) {
2957
0
                AVIF_ASSERT_OR_RETURN(depth == 16); // Only 16-bit depth is supported for now.
2958
0
            } else if (encoder->sampleTransformRecipe == AVIF_SAMPLE_TRANSFORM_BIT_DEPTH_EXTENSION_8B_8B) {
2959
0
                depth = 8;
2960
0
            } else {
2961
0
                if (item->itemCategory == AVIF_ITEM_COLOR || item->itemCategory == AVIF_ITEM_ALPHA) {
2962
0
                    depth = 12;
2963
0
                } else {
2964
0
                    AVIF_ASSERT_OR_RETURN(item->itemCategory == AVIF_ITEM_SAMPLE_TRANSFORM_INPUT_0_COLOR ||
2965
0
                                          item->itemCategory == AVIF_ITEM_SAMPLE_TRANSFORM_INPUT_0_ALPHA);
2966
                    // Will be shifted to 4-bit samples at decoding for AVIF_SAMPLE_TRANSFORM_BIT_DEPTH_EXTENSION_12B_4B.
2967
0
                    depth = 8;
2968
0
                }
2969
0
            }
2970
0
        } else {
2971
0
            AVIF_CHECKERR(encoder->sampleTransformRecipe == AVIF_SAMPLE_TRANSFORM_NONE, AVIF_RESULT_NOT_IMPLEMENTED);
2972
0
        }
2973
0
        assert(isSampleTransformImage == (item->itemCategory == AVIF_ITEM_SAMPLE_TRANSFORM));
2974
2975
0
        if (hasPixi) {
2976
0
            avifItemPropertyDedupStart(dedup);
2977
0
            uint8_t channelCount = (isAlpha || (itemMetadata->yuvFormat == AVIF_PIXEL_FORMAT_YUV400)) ? 1 : 3;
2978
            // See ISO/IEC 23008-12:2024/CDAM 2:2025 section 6.5.6.3.
2979
0
            avifBoxMarker pixi;
2980
0
            uint32_t flags = 0;
2981
#if defined(AVIF_ENABLE_EXPERIMENTAL_EXTENDED_PIXI)
2982
            if (encoder->headerFormat & AVIF_HEADER_EXTENDED_PIXI) {
2983
                flags |= 1;
2984
            }
2985
#endif // AVIF_ENABLE_EXPERIMENTAL_EXTENDED_PIXI
2986
0
            AVIF_CHECKRES(avifRWStreamWriteFullBox(&dedup->s, "pixi", AVIF_BOX_SIZE_TBD, 0, flags, &pixi));
2987
0
            AVIF_CHECKRES(avifRWStreamWriteU8(&dedup->s, channelCount)); // unsigned int (8) num_channels;
2988
0
            for (uint8_t chan = 0; chan < channelCount; ++chan) {
2989
0
                AVIF_CHECKRES(avifRWStreamWriteU8(&dedup->s, depth)); // unsigned int (8) bits_per_channel;
2990
0
            }
2991
#if defined(AVIF_ENABLE_EXPERIMENTAL_EXTENDED_PIXI)
2992
            if (flags & 1) {
2993
                AVIF_ASSERT_OR_RETURN(item->av1C.chromaSamplePosition != AVIF_CHROMA_SAMPLE_POSITION_RESERVED);
2994
                // Do not signal any subsampling information if the sample position is unknown because the 'pixi' box
2995
                // does not have an enum entry for "unknown subsampling location".
2996
                const uint8_t subsampling_flag = item->av1C.chromaSamplePosition == AVIF_CHROMA_SAMPLE_POSITION_VERTICAL ||
2997
                                                 item->av1C.chromaSamplePosition == AVIF_CHROMA_SAMPLE_POSITION_COLOCATED;
2998
                for (uint8_t chan = 0; chan < channelCount; ++chan) {
2999
                    AVIF_CHECKRES(avifRWStreamWriteBits(&dedup->s, 0, /*bitCount=*/3)); // unsigned int(3) channel_idc;
3000
                    AVIF_CHECKRES(avifRWStreamWriteBits(&dedup->s, 0, /*bitCount=*/1)); // unsigned int(1) reserved;
3001
                    AVIF_CHECKRES(avifRWStreamWriteBits(&dedup->s, 0, /*bitCount=*/2)); // unsigned int(2) component_format;
3002
                    AVIF_CHECKRES(avifRWStreamWriteBits(&dedup->s, subsampling_flag, /*bitCount=*/1)); // unsigned int(1) subsampling_flag;
3003
                    AVIF_CHECKRES(avifRWStreamWriteBits(&dedup->s, 0, /*bitCount=*/1)); // unsigned int(1) channel_label_flag;
3004
                    if (subsampling_flag) {
3005
                        const uint8_t subsamplingType = avifCodecConfigurationBoxGetSubsamplingType(&item->av1C, chan);
3006
                        const uint8_t subsamplingLocation = subsamplingType == AVIF_PIXI_444 ? 0
3007
                                                            : item->av1C.chromaSamplePosition == AVIF_CHROMA_SAMPLE_POSITION_VERTICAL
3008
                                                                ? 0
3009
                                                                : 2;
3010
                        AVIF_CHECKRES(avifRWStreamWriteBits(&dedup->s, subsamplingType, /*bitCount=*/4)); // unsigned int(4) subsampling_type;
3011
                        AVIF_CHECKRES(avifRWStreamWriteBits(&dedup->s, subsamplingLocation, /*bitCount=*/4)); // unsigned int(4) subsampling_location;
3012
                    }
3013
                }
3014
            }
3015
#endif // AVIF_ENABLE_EXPERIMENTAL_EXTENDED_PIXI
3016
0
            AVIF_CHECKRES(avifRWStreamFinishBox(&dedup->s, pixi));
3017
0
            AVIF_CHECKRES(avifItemPropertyDedupFinish(dedup, s, &item->associations, /*essential=*/AVIF_FALSE));
3018
0
        }
3019
3020
        // Codec configuration box ('av1C' or 'av2C')
3021
0
        if (item->codec) {
3022
0
            avifItemPropertyDedupStart(dedup);
3023
0
            AVIF_CHECKRES(writeConfigBox(&dedup->s, &item->av1C, encoder->data->configPropName));
3024
0
            AVIF_CHECKRES(avifItemPropertyDedupFinish(dedup, s, &item->associations, /*essential=*/AVIF_TRUE));
3025
0
        }
3026
3027
0
        if (isAlpha) {
3028
            // Alpha specific properties
3029
3030
0
            avifItemPropertyDedupStart(dedup);
3031
0
            avifBoxMarker auxC;
3032
0
            AVIF_CHECKRES(avifRWStreamWriteFullBox(&dedup->s, "auxC", AVIF_BOX_SIZE_TBD, 0, 0, &auxC));
3033
0
            AVIF_CHECKRES(avifRWStreamWriteChars(&dedup->s, alphaURN, alphaURNSize)); //  string aux_type;
3034
0
            AVIF_CHECKRES(avifRWStreamFinishBox(&dedup->s, auxC));
3035
0
            AVIF_CHECKRES(avifItemPropertyDedupFinish(dedup, s, &item->associations, /*essential=*/AVIF_FALSE));
3036
0
        } else if (item->itemCategory == AVIF_ITEM_COLOR) {
3037
            // Color specific properties
3038
            // Note the 'tmap' (tone mapped image) item when a gain map is present also has itemCategory AVIF_ITEM_COLOR.
3039
3040
0
            AVIF_CHECKRES(avifEncoderWriteColorProperties(s, itemMetadata, &item->associations, dedup));
3041
0
            AVIF_CHECKRES(avifEncoderWriteHDRProperties(&dedup->s, s, itemMetadata, &item->associations, dedup));
3042
0
        } else if (item->itemCategory == AVIF_ITEM_GAIN_MAP) {
3043
            // Gain map specific properties
3044
3045
            // Write the colr nclx box.
3046
0
            AVIF_CHECKRES(avifEncoderWriteNclxProperty(&dedup->s, s, itemMetadata, &item->associations, dedup));
3047
3048
0
            AVIF_CHECKRES(avifEncoderWritePaspProperty(&dedup->s, s, imageMetadata, &item->associations, dedup));
3049
0
        }
3050
3051
0
        if (item->extraLayerCount > 0) {
3052
            // Layered Image Indexing Property
3053
3054
0
            avifItemPropertyDedupStart(dedup);
3055
0
            avifBoxMarker a1lx;
3056
0
            AVIF_CHECKRES(avifRWStreamWriteBox(&dedup->s, "a1lx", AVIF_BOX_SIZE_TBD, &a1lx));
3057
0
            uint32_t layerSize[AVIF_MAX_AV1_LAYER_COUNT - 1] = { 0 };
3058
0
            avifBool largeSize = AVIF_FALSE;
3059
3060
0
            for (uint32_t validLayer = 0; validLayer < item->extraLayerCount; ++validLayer) {
3061
0
                uint32_t size = (uint32_t)item->encodeOutput->samples.sample[validLayer].data.size;
3062
0
                layerSize[validLayer] = size;
3063
0
                if (size > 0xffff) {
3064
0
                    largeSize = AVIF_TRUE;
3065
0
                }
3066
0
            }
3067
3068
0
            AVIF_CHECKRES(avifRWStreamWriteBits(&dedup->s, 0, /*bitCount=*/7));                 // unsigned int(7) reserved = 0;
3069
0
            AVIF_CHECKRES(avifRWStreamWriteBits(&dedup->s, largeSize ? 1 : 0, /*bitCount=*/1)); // unsigned int(1) large_size;
3070
3071
            // FieldLength = (large_size + 1) * 16;
3072
            // unsigned int(FieldLength) layer_size[3];
3073
0
            for (uint32_t layer = 0; layer < AVIF_MAX_AV1_LAYER_COUNT - 1; ++layer) {
3074
0
                if (largeSize) {
3075
0
                    AVIF_CHECKRES(avifRWStreamWriteU32(&dedup->s, layerSize[layer]));
3076
0
                } else {
3077
0
                    AVIF_CHECKRES(avifRWStreamWriteU16(&dedup->s, (uint16_t)layerSize[layer]));
3078
0
                }
3079
0
            }
3080
0
            AVIF_CHECKRES(avifRWStreamFinishBox(&dedup->s, a1lx));
3081
0
            AVIF_CHECKRES(avifItemPropertyDedupFinish(dedup, s, &item->associations, /*essential=*/AVIF_FALSE));
3082
3083
            // We don't add an 'lsel' property since many decoders do not support it and will reject the image,
3084
            // see https://github.com/AOMediaCodec/libavif/pull/2429
3085
0
        }
3086
3087
        // Write out any opaque properties from avifImageAddOpaqueProperty() or avifImageAddUUIDProperty().
3088
0
        for (size_t i = 0; i < itemMetadata->numProperties; i++) {
3089
0
            avifItemPropertyDedupStart(dedup);
3090
0
            const avifImageItemProperty * prop = &itemMetadata->properties[i];
3091
0
            avifBoxMarker propMarker;
3092
0
            AVIF_CHECKRES(avifRWStreamWriteBox(&dedup->s, (const char *)prop->boxtype, AVIF_BOX_SIZE_TBD, &propMarker));
3093
0
            if (memcmp(prop->boxtype, "uuid", 4) == 0) {
3094
0
                AVIF_CHECKRES(avifRWStreamWrite(&dedup->s, prop->usertype, 16));
3095
0
            }
3096
0
            AVIF_CHECKRES(avifRWStreamWrite(&dedup->s, prop->boxPayload.data, prop->boxPayload.size));
3097
0
            AVIF_CHECKRES(avifRWStreamFinishBox(&dedup->s, propMarker));
3098
0
            AVIF_CHECKRES(avifItemPropertyDedupFinish(dedup, s, &item->associations, /*essential=*/AVIF_FALSE));
3099
0
        }
3100
3101
        // Also write the transformative properties.
3102
3103
0
        if (item->itemCategory == AVIF_ITEM_COLOR) {
3104
            // Color specific properties
3105
            // Note the 'tmap' (tone mapped image) item when a gain map is present also has itemCategory AVIF_ITEM_COLOR.
3106
0
            AVIF_CHECKRES(avifEncoderWriteTransformativeProperties(&dedup->s, s, itemMetadata, &item->associations, dedup));
3107
0
        } else if (item->itemCategory == AVIF_ITEM_ALPHA) {
3108
            // Cropping, rotation and mirroring must also be applied to alpha auxiliary items.
3109
0
            AVIF_CHECKRES(avifEncoderWriteTransformativeProperties(&dedup->s, s, itemMetadata, &item->associations, dedup));
3110
0
        } else if (item->itemCategory == AVIF_ITEM_GAIN_MAP) {
3111
            // Gain map specific properties
3112
3113
            // For the orientation, it could be done in multiple ways:
3114
            // - Bake the orientation in the base and gain map images.
3115
            //   This does not allow for orientation changes without recompression.
3116
            // - Associate 'irot'/'imir' with the 'tmap' derived image item only.
3117
            //   If so, decoding only the base image would give a different orientation than
3118
            //   decoding the tone-mapped image.
3119
            // - Wrap the base image in an 'iden' derived image item and associate 'irot'/'imir'
3120
            //   with the 'tmap' and 'iden' derived image items. 'iden' is not currently supported
3121
            //   by libavif, reducing the backward compatibility of this solution.
3122
            // - Associate 'irot'/'imir' with the base and gain map image items.
3123
            //   Do not associate 'irot'/'imir' with the 'tmap' derived image item.
3124
            //   These transformative properties are supposed to be applied at decoding on
3125
            //   image items before these are used as input to a derived image item.
3126
            //   libavif uses this pattern at encoding and requires it at decoding.
3127
            //   As of today, this is forbidden by the AVIF specification:
3128
            //     https://aomediacodec.github.io/av1-avif/v1.1.0.html#file-constraints
3129
            //   That rule was written before 'tmap' was proposed and may be relaxed for 'tmap'.
3130
3131
            // 'clap' is treated as 'irot'/'imir', although it could differ between the base and
3132
            // gain map image items if these have different dimensions.
3133
0
            if (imageMetadata->transformFlags & AVIF_TRANSFORM_CLAP) {
3134
0
                AVIF_CHECKERR(imageMetadata->width != itemMetadata->width || imageMetadata->height != itemMetadata->height,
3135
0
                              AVIF_RESULT_NOT_IMPLEMENTED);
3136
0
            }
3137
3138
            // 'pasp' is not a transformative property (despite AVIF_TRANSFORM_PASP being part of
3139
            // avifTransformFlag) but it is assumed to apply to the gain map in the same way as
3140
            // the transformative properties above.
3141
3142
            // Based on the explanation above, 'clap', 'irot', 'imir' and 'pasp' have to match between the base and
3143
            // gain map image items in the container part of the encoded file.
3144
            // To enforce that, the transformative and 'pasp' properties of the gain map cannot be set explicitly in the API.
3145
0
            AVIF_CHECKERR(itemMetadata->transformFlags == AVIF_TRANSFORM_NONE, AVIF_RESULT_ENCODE_GAIN_MAP_FAILED);
3146
0
            AVIF_CHECKRES(avifEncoderWriteTransformativeProperties(&dedup->s, s, imageMetadata, &item->associations, dedup));
3147
0
        }
3148
0
    }
3149
0
    return AVIF_RESULT_OK;
3150
0
}
3151
3152
avifResult avifEncoderFinish(avifEncoder * encoder, avifRWData * output)
3153
0
{
3154
0
    avifDiagnosticsClearError(&encoder->diag);
3155
0
    if (encoder->data->items.count == 0) {
3156
0
        return AVIF_RESULT_NO_CONTENT;
3157
0
    }
3158
3159
0
    const avifCodecType codecType = avifEncoderGetCodecType(encoder);
3160
0
    if (codecType == AVIF_CODEC_TYPE_UNKNOWN) {
3161
0
        return AVIF_RESULT_NO_CODEC_AVAILABLE;
3162
0
    }
3163
3164
    // -----------------------------------------------------------------------
3165
    // Finish up encoding
3166
3167
0
    for (uint32_t itemIndex = 0; itemIndex < encoder->data->items.count; ++itemIndex) {
3168
0
        avifEncoderItem * item = &encoder->data->items.item[itemIndex];
3169
0
        if (item->codec) {
3170
0
            if (!item->codec->encodeFinish(item->codec, item->encodeOutput)) {
3171
0
                return avifGetErrorForItemCategory(item->itemCategory);
3172
0
            }
3173
3174
0
            if (item->encodeOutput->samples.count != encoder->data->frames.count) {
3175
0
                return avifGetErrorForItemCategory(item->itemCategory);
3176
0
            }
3177
3178
0
            if ((item->extraLayerCount > 0) && (item->encodeOutput->samples.count != item->extraLayerCount + 1)) {
3179
                // Check whether user has sent enough frames to encoder.
3180
0
                avifDiagnosticsPrintf(&encoder->diag,
3181
0
                                      "Expected %u frames given to avifEncoderAddImage() to encode this layered image according to extraLayerCount, but got %u frames.",
3182
0
                                      item->extraLayerCount + 1,
3183
0
                                      item->encodeOutput->samples.count);
3184
0
                return AVIF_RESULT_INVALID_ARGUMENT;
3185
0
            }
3186
0
        }
3187
0
    }
3188
3189
    // -----------------------------------------------------------------------
3190
    // Harvest configuration properties from sequence headers
3191
3192
0
    for (uint32_t itemIndex = 0; itemIndex < encoder->data->items.count; ++itemIndex) {
3193
0
        avifEncoderItem * item = &encoder->data->items.item[itemIndex];
3194
0
        if (item->encodeOutput->samples.count > 0) {
3195
0
            const avifEncodeSample * firstSample = &item->encodeOutput->samples.sample[0];
3196
0
            avifSequenceHeader sequenceHeader;
3197
0
            AVIF_CHECKERR(avifSequenceHeaderParse(&sequenceHeader, (const avifROData *)&firstSample->data, codecType),
3198
0
                          avifGetErrorForItemCategory(item->itemCategory));
3199
0
            item->av1C = sequenceHeader.av1C;
3200
0
        }
3201
0
    }
3202
3203
    // -----------------------------------------------------------------------
3204
    // Begin write stream
3205
3206
#if defined(AVIF_ENABLE_EXPERIMENTAL_MINI)
3207
    // Decide whether to go for a reduced MinimizedImageBox or a full regular MetaBox.
3208
    if ((encoder->headerFormat & AVIF_HEADER_MINI) && avifEncoderIsMiniCompatible(encoder)) {
3209
        AVIF_CHECKRES(avifEncoderWriteFileTypeBoxAndMiniBox(encoder, output));
3210
        return AVIF_RESULT_OK;
3211
    }
3212
#endif // AVIF_ENABLE_EXPERIMENTAL_MINI
3213
3214
0
    const avifImage * imageMetadata = encoder->data->imageMetadata;
3215
0
    uint64_t now = (uint64_t)time(NULL);
3216
0
    uint64_t modificationTime = (encoder->modificationTime != 0) ? encoder->modificationTime : now;
3217
0
    uint64_t creationTime = (encoder->creationTime != 0) ? encoder->creationTime : modificationTime;
3218
    // The epoch for creation_time and modification_time is midnight, Jan. 1,
3219
    // 1904, in UTC time. Add the number of seconds between that epoch and the
3220
    // Unix epoch.
3221
0
    creationTime += 2082844800;
3222
0
    modificationTime += 2082844800;
3223
3224
0
    avifRWStream s;
3225
0
    avifRWStreamStart(&s, output);
3226
3227
    // -----------------------------------------------------------------------
3228
    // Write ftyp
3229
3230
    // Layered sequence is not supported for now.
3231
0
    const avifBool isSequence = (encoder->extraLayerCount == 0) && (encoder->data->frames.count > 1);
3232
3233
0
    const char * majorBrand = "avif";
3234
0
    if (isSequence) {
3235
0
        majorBrand = "avis";
3236
0
    }
3237
3238
0
    uint32_t minorVersion = 0;
3239
#if defined(AVIF_CODEC_AVM)
3240
    if (codecType == AVIF_CODEC_TYPE_AV2) {
3241
        // TODO(yguyon): Experimental AV2-AVIF is AVIF version 2 for now (change once it is ratified).
3242
        minorVersion = 2;
3243
    }
3244
#endif
3245
3246
    // According to section 5.2 of AV1 Image File Format specification v1.1.0:
3247
    //   If the primary item or all the items referenced by the primary item are AV1 image items made only
3248
    //   of Intra Frames, the brand "avio" should be used in the compatible_brands field of the FileTypeBox.
3249
    // See https://aomediacodec.github.io/av1-avif/v1.1.0.html#image-and-image-collection-brand.
3250
    // This rule corresponds to using the "avio" brand in all cases except for layered images, because:
3251
    //  - Non-layered still images are always Intra Frames, even with grids;
3252
    //  - Sequences cannot be combined with layers or grids, and the first frame of the sequence
3253
    //    (referred to by the primary image item) is always an Intra Frame.
3254
0
    avifBool useAvioBrand;
3255
0
    if (isSequence) {
3256
        // According to section 5.3 of AV1 Image File Format specification v1.1.0:
3257
        //   Additionally, if a file contains AV1 image sequences and the brand avio is used in the
3258
        //   compatible_brands field of the FileTypeBox, the item constraints for this brand shall be met
3259
        //   and at least one of the AV1 image sequences shall be made only of AV1 Samples marked as sync.
3260
        // See https://aomediacodec.github.io/av1-avif/v1.1.0.html#image-sequence-brand.
3261
0
        useAvioBrand = AVIF_FALSE;
3262
0
        for (uint32_t itemIndex = 0; itemIndex < encoder->data->items.count; ++itemIndex) {
3263
0
            avifEncoderItem * item = &encoder->data->items.item[itemIndex];
3264
0
            if (item->encodeOutput->samples.count == 0) {
3265
0
                continue; // Not a track.
3266
0
            }
3267
0
            avifBool onlySyncSamples = AVIF_TRUE;
3268
0
            for (uint32_t sampleIndex = 0; sampleIndex < item->encodeOutput->samples.count; ++sampleIndex) {
3269
0
                if (!item->encodeOutput->samples.sample[sampleIndex].sync) {
3270
0
                    onlySyncSamples = AVIF_FALSE;
3271
0
                    break;
3272
0
                }
3273
0
            }
3274
0
            if (onlySyncSamples) {
3275
0
                useAvioBrand = AVIF_TRUE; // at least one of the AV1 image sequences is made only of sync samples
3276
0
                break;
3277
0
            }
3278
0
        }
3279
0
    } else {
3280
        // The gpac/ComplianceWarden tool only warns about the lack of the "avio" brand for sequences,
3281
        // and the specification says the brand "should" be used, not "shall". Leverage that opportunity
3282
        // to save four bytes for still images.
3283
0
        useAvioBrand = AVIF_FALSE; // Should be (encoder->extraLayerCount == 0) to be fully compliant.
3284
0
    }
3285
3286
0
    avifBoxMarker ftyp;
3287
0
    AVIF_CHECKRES(avifRWStreamWriteBox(&s, "ftyp", AVIF_BOX_SIZE_TBD, &ftyp));
3288
0
    AVIF_CHECKRES(avifRWStreamWriteChars(&s, majorBrand, 4)); // unsigned int(32) major_brand;
3289
0
    AVIF_CHECKRES(avifRWStreamWriteU32(&s, minorVersion));    // unsigned int(32) minor_version;
3290
0
    AVIF_CHECKRES(avifRWStreamWriteChars(&s, "avif", 4));     // unsigned int(32) compatible_brands[];
3291
0
    if (useAvioBrand) {
3292
0
        AVIF_CHECKRES(avifRWStreamWriteChars(&s, "avio", 4)); // ... compatible_brands[]
3293
0
    }
3294
0
    if (isSequence) {
3295
0
        AVIF_CHECKRES(avifRWStreamWriteChars(&s, "avis", 4)); // ... compatible_brands[]
3296
0
        AVIF_CHECKRES(avifRWStreamWriteChars(&s, "msf1", 4)); // ... compatible_brands[]
3297
0
        AVIF_CHECKRES(avifRWStreamWriteChars(&s, "iso8", 4)); // ... compatible_brands[]
3298
0
    }
3299
0
    AVIF_CHECKRES(avifRWStreamWriteChars(&s, "mif1", 4));                  // ... compatible_brands[]
3300
0
    AVIF_CHECKRES(avifRWStreamWriteChars(&s, "miaf", 4));                  // ... compatible_brands[]
3301
0
    if ((imageMetadata->depth == 8) || (imageMetadata->depth == 10)) {     //
3302
0
        if (imageMetadata->yuvFormat == AVIF_PIXEL_FORMAT_YUV420) {        //
3303
0
            AVIF_CHECKRES(avifRWStreamWriteChars(&s, "MA1B", 4));          // ... compatible_brands[]
3304
0
        } else if (imageMetadata->yuvFormat == AVIF_PIXEL_FORMAT_YUV444) { //
3305
0
            AVIF_CHECKRES(avifRWStreamWriteChars(&s, "MA1A", 4));          // ... compatible_brands[]
3306
0
        }
3307
0
    }
3308
0
    for (uint32_t itemIndex = 0; itemIndex < encoder->data->items.count; ++itemIndex) {
3309
0
        if (!memcmp(encoder->data->items.item[itemIndex].type, "tmap", 4)) {
3310
            // ISO/IEC 23008-12:2024/AMD 1:2024(E)
3311
            // This brand enables file players to identify and decode HEIF files containing tone-map derived image
3312
            // items. When present, this brand shall be among the brands included in the compatible_brands
3313
            // array of the FileTypeBox.
3314
0
            AVIF_CHECKRES(avifRWStreamWriteChars(&s, "tmap", 4)); // ... compatible_brands[]
3315
0
            break;
3316
0
        }
3317
0
    }
3318
0
    AVIF_CHECKRES(avifRWStreamFinishBox(&s, ftyp));
3319
3320
    // -----------------------------------------------------------------------
3321
    // Start meta
3322
3323
0
    avifBoxMarker meta;
3324
0
    AVIF_CHECKRES(avifRWStreamWriteFullBox(&s, "meta", AVIF_BOX_SIZE_TBD, 0, 0, &meta));
3325
3326
    // -----------------------------------------------------------------------
3327
    // Write hdlr
3328
3329
0
    AVIF_CHECKRES(avifRWStreamWriteHandlerBox(&s, "pict"));
3330
3331
    // -----------------------------------------------------------------------
3332
    // Write pitm
3333
3334
0
    if (encoder->data->primaryItemID != 0) {
3335
0
        AVIF_CHECKRES(avifRWStreamWriteFullBox(&s, "pitm", sizeof(uint16_t), 0, 0, /*marker=*/NULL));
3336
0
        AVIF_CHECKRES(avifRWStreamWriteU16(&s, encoder->data->primaryItemID)); //  unsigned int(16) item_ID;
3337
0
    }
3338
3339
    // -----------------------------------------------------------------------
3340
    // Write iloc
3341
3342
0
    avifBoxMarker iloc;
3343
0
    AVIF_CHECKRES(avifRWStreamWriteFullBox(&s, "iloc", AVIF_BOX_SIZE_TBD, 0, 0, &iloc));
3344
0
    AVIF_CHECKRES(avifRWStreamWriteBits(&s, 4, /*bitCount=*/4));                   // unsigned int(4) offset_size;
3345
0
    AVIF_CHECKRES(avifRWStreamWriteBits(&s, 4, /*bitCount=*/4));                   // unsigned int(4) length_size;
3346
0
    AVIF_CHECKRES(avifRWStreamWriteBits(&s, 0, /*bitCount=*/4));                   // unsigned int(4) base_offset_size;
3347
0
    AVIF_CHECKRES(avifRWStreamWriteBits(&s, 0, /*bitCount=*/4));                   // unsigned int(4) reserved;
3348
0
    AVIF_CHECKRES(avifRWStreamWriteU16(&s, (uint16_t)encoder->data->items.count)); // unsigned int(16) item_count;
3349
3350
0
    for (uint32_t itemIndex = 0; itemIndex < encoder->data->items.count; ++itemIndex) {
3351
0
        avifEncoderItem * item = &encoder->data->items.item[itemIndex];
3352
0
        AVIF_CHECKRES(avifRWStreamWriteU16(&s, item->id)); // unsigned int(16) item_ID;
3353
0
        AVIF_CHECKRES(avifRWStreamWriteU16(&s, 0));        // unsigned int(16) data_reference_index;
3354
3355
        // Layered Image, write location for all samples
3356
0
        if (item->extraLayerCount > 0) {
3357
0
            uint32_t layerCount = item->extraLayerCount + 1;
3358
0
            AVIF_CHECKRES(avifRWStreamWriteU16(&s, (uint16_t)layerCount)); // unsigned int(16) extent_count;
3359
0
            for (uint32_t i = 0; i < layerCount; ++i) {
3360
0
                AVIF_CHECKRES(avifEncoderItemAddMdatFixup(item, &s));
3361
0
                AVIF_CHECKRES(avifRWStreamWriteU32(&s, 0 /* set later */)); // unsigned int(offset_size*8) extent_offset;
3362
0
                AVIF_CHECKRES(avifRWStreamWriteU32(&s, (uint32_t)item->encodeOutput->samples.sample[i].data.size)); // unsigned int(length_size*8) extent_length;
3363
0
            }
3364
0
            continue;
3365
0
        }
3366
3367
0
        uint32_t contentSize = (uint32_t)item->metadataPayload.size;
3368
0
        if (item->encodeOutput->samples.count > 0) {
3369
            // This is choosing sample 0's size as there are two cases here:
3370
            // * This is a single image, in which case this is correct
3371
            // * This is an image sequence, but this file should still be a valid single-image avif,
3372
            //   so there must still be a primary item pointing at a sync sample. Since the first
3373
            //   frame of the image sequence is guaranteed to be a sync sample, it is chosen here.
3374
0
            contentSize = (uint32_t)item->encodeOutput->samples.sample[0].data.size;
3375
0
        }
3376
3377
0
        AVIF_CHECKRES(avifRWStreamWriteU16(&s, 1));                     // unsigned int(16) extent_count;
3378
0
        AVIF_CHECKRES(avifEncoderItemAddMdatFixup(item, &s));           //
3379
0
        AVIF_CHECKRES(avifRWStreamWriteU32(&s, 0 /* set later */));     // unsigned int(offset_size*8) extent_offset;
3380
0
        AVIF_CHECKRES(avifRWStreamWriteU32(&s, (uint32_t)contentSize)); // unsigned int(length_size*8) extent_length;
3381
0
    }
3382
3383
0
    AVIF_CHECKRES(avifRWStreamFinishBox(&s, iloc));
3384
3385
    // -----------------------------------------------------------------------
3386
    // Write iinf
3387
3388
    // Section 8.11.6.2 of ISO/IEC 14496-12.
3389
0
    avifBoxMarker iinf;
3390
0
    AVIF_CHECKRES(avifRWStreamWriteFullBox(&s, "iinf", AVIF_BOX_SIZE_TBD, 0, 0, &iinf));
3391
0
    AVIF_CHECKRES(avifRWStreamWriteU16(&s, (uint16_t)encoder->data->items.count)); //  unsigned int(16) entry_count;
3392
3393
0
    for (uint32_t itemIndex = 0; itemIndex < encoder->data->items.count; ++itemIndex) {
3394
0
        avifEncoderItem * item = &encoder->data->items.item[itemIndex];
3395
3396
0
        uint32_t flags = item->hiddenImage ? 1 : 0;
3397
0
        avifBoxMarker infe;
3398
0
        AVIF_CHECKRES(avifRWStreamWriteFullBox(&s, "infe", AVIF_BOX_SIZE_TBD, 2, flags, &infe));
3399
0
        AVIF_CHECKRES(avifRWStreamWriteU16(&s, item->id));                             // unsigned int(16) item_ID;
3400
0
        AVIF_CHECKRES(avifRWStreamWriteU16(&s, 0));                                    // unsigned int(16) item_protection_index;
3401
0
        AVIF_CHECKRES(avifRWStreamWrite(&s, item->type, 4));                           // unsigned int(32) item_type;
3402
0
        AVIF_CHECKRES(avifRWStreamWriteChars(&s, item->infeName, item->infeNameSize)); // utf8string item_name; (writing null terminator)
3403
0
        if (!memcmp(item->type, "mime", 4)) {
3404
0
            AVIF_CHECKRES(avifRWStreamWriteChars(&s, item->infeContentType, item->infeContentTypeSize)); // utf8string content_type; (writing null terminator)
3405
            // utf8string content_encoding; //optional
3406
0
        } else if (!memcmp(item->type, "uri ", 4)) {
3407
            // utf8string item_uri_type;
3408
0
            return AVIF_RESULT_NOT_IMPLEMENTED;
3409
0
        }
3410
0
        AVIF_CHECKRES(avifRWStreamFinishBox(&s, infe));
3411
0
    }
3412
3413
0
    AVIF_CHECKRES(avifRWStreamFinishBox(&s, iinf));
3414
3415
    // -----------------------------------------------------------------------
3416
    // Write iref boxes
3417
3418
0
    avifBoxMarker iref = 0;
3419
0
    for (uint32_t itemIndex = 0; itemIndex < encoder->data->items.count; ++itemIndex) {
3420
0
        avifEncoderItem * item = &encoder->data->items.item[itemIndex];
3421
3422
        // Count how many other items refer to this item with dimgFromID
3423
0
        uint16_t dimgCount = 0;
3424
0
        for (uint32_t dimgIndex = 0; dimgIndex < encoder->data->items.count; ++dimgIndex) {
3425
0
            avifEncoderItem * dimgItem = &encoder->data->items.item[dimgIndex];
3426
0
            if (dimgItem->dimgFromID == item->id) {
3427
0
                ++dimgCount;
3428
0
            }
3429
0
        }
3430
3431
0
        if (dimgCount > 0) {
3432
0
            if (!iref) {
3433
0
                AVIF_CHECKRES(avifRWStreamWriteFullBox(&s, "iref", AVIF_BOX_SIZE_TBD, 0, 0, &iref));
3434
0
            }
3435
0
            avifBoxMarker refType;
3436
0
            AVIF_CHECKRES(avifRWStreamWriteBox(&s, "dimg", AVIF_BOX_SIZE_TBD, &refType));
3437
0
            AVIF_CHECKRES(avifRWStreamWriteU16(&s, item->id));  // unsigned int(16) from_item_ID;
3438
0
            AVIF_CHECKRES(avifRWStreamWriteU16(&s, dimgCount)); // unsigned int(16) reference_count;
3439
0
            for (uint32_t dimgIndex = 0; dimgIndex < encoder->data->items.count; ++dimgIndex) {
3440
0
                avifEncoderItem * dimgItem = &encoder->data->items.item[dimgIndex];
3441
0
                if (dimgItem->dimgFromID == item->id) {
3442
0
                    AVIF_CHECKRES(avifRWStreamWriteU16(&s, dimgItem->id)); // unsigned int(16) to_item_ID;
3443
0
                }
3444
0
            }
3445
0
            AVIF_CHECKRES(avifRWStreamFinishBox(&s, refType));
3446
0
        }
3447
3448
0
        if (item->irefToID != 0) {
3449
0
            if (!iref) {
3450
0
                AVIF_CHECKRES(avifRWStreamWriteFullBox(&s, "iref", AVIF_BOX_SIZE_TBD, 0, 0, &iref));
3451
0
            }
3452
0
            avifBoxMarker refType;
3453
0
            AVIF_CHECKRES(avifRWStreamWriteBox(&s, item->irefType, AVIF_BOX_SIZE_TBD, &refType));
3454
0
            AVIF_CHECKRES(avifRWStreamWriteU16(&s, item->id));       // unsigned int(16) from_item_ID;
3455
0
            AVIF_CHECKRES(avifRWStreamWriteU16(&s, 1));              // unsigned int(16) reference_count;
3456
0
            AVIF_CHECKRES(avifRWStreamWriteU16(&s, item->irefToID)); // unsigned int(16) to_item_ID;
3457
0
            AVIF_CHECKRES(avifRWStreamFinishBox(&s, refType));
3458
0
        }
3459
0
    }
3460
0
    if (iref) {
3461
0
        AVIF_CHECKRES(avifRWStreamFinishBox(&s, iref));
3462
0
    }
3463
3464
    // -----------------------------------------------------------------------
3465
    // Write iprp -> ipco/ipma
3466
3467
0
    avifBoxMarker iprp;
3468
0
    AVIF_CHECKRES(avifRWStreamWriteBox(&s, "iprp", AVIF_BOX_SIZE_TBD, &iprp));
3469
3470
0
    avifItemPropertyDedup * dedup = avifItemPropertyDedupCreate();
3471
0
    AVIF_CHECKERR(dedup != NULL, AVIF_RESULT_OUT_OF_MEMORY);
3472
0
    avifBoxMarker ipco;
3473
0
    AVIF_CHECKRES(avifRWStreamWriteBox(&s, "ipco", AVIF_BOX_SIZE_TBD, &ipco));
3474
0
    avifImage * altImageMetadata = NULL;
3475
0
    altImageMetadata = encoder->data->altImageMetadata;
3476
0
    avifResult result = avifRWStreamWriteProperties(dedup, &s, encoder, imageMetadata, altImageMetadata);
3477
0
    avifItemPropertyDedupDestroy(dedup);
3478
0
    dedup = NULL;
3479
0
    AVIF_CHECKRES(result);
3480
0
    AVIF_CHECKRES(avifRWStreamFinishBox(&s, ipco));
3481
3482
0
    avifBoxMarker ipma;
3483
0
    AVIF_CHECKRES(avifRWStreamWriteFullBox(&s, "ipma", AVIF_BOX_SIZE_TBD, 0, 0, &ipma));
3484
0
    {
3485
0
        uint32_t ipmaCount = 0;
3486
0
        for (uint32_t itemIndex = 0; itemIndex < encoder->data->items.count; ++itemIndex) {
3487
0
            avifEncoderItem * item = &encoder->data->items.item[itemIndex];
3488
0
            if (item->associations.count > 0) {
3489
0
                AVIF_CHECKERR(ipmaCount < UINT32_MAX, AVIF_RESULT_INVALID_ARGUMENT);
3490
0
                ++ipmaCount;
3491
0
            }
3492
0
        }
3493
0
        AVIF_CHECKRES(avifRWStreamWriteU32(&s, ipmaCount)); // unsigned int(32) entry_count;
3494
3495
0
        for (uint32_t itemIndex = 0; itemIndex < encoder->data->items.count; ++itemIndex) {
3496
0
            avifEncoderItem * item = &encoder->data->items.item[itemIndex];
3497
0
            if (item->associations.count == 0) {
3498
0
                continue;
3499
0
            }
3500
3501
0
            AVIF_CHECKRES(avifRWStreamWriteU16(&s, item->id)); // unsigned int(16) item_ID;
3502
0
            AVIF_ASSERT_OR_RETURN(item->associations.count < (1 << 8));
3503
0
            AVIF_CHECKRES(avifRWStreamWriteU8(&s, (uint8_t)item->associations.count)); // unsigned int(8) association_count;
3504
0
            for (uint32_t i = 0; i < item->associations.count; ++i) {
3505
0
                const avifItemPropertyAssociation * association = &item->associations.association[i];
3506
0
                AVIF_CHECKRES(avifRWStreamWriteBits(&s, association->essential ? 1 : 0, /*bitCount=*/1)); // bit(1) essential;
3507
0
                AVIF_ASSERT_OR_RETURN(association->property_index <= MAX_PROPERTY_INDEX);
3508
0
                AVIF_CHECKRES(avifRWStreamWriteBits(&s, association->property_index, /*bitCount=*/7)); // unsigned int(7) property_index;
3509
0
            }
3510
0
        }
3511
0
    }
3512
0
    AVIF_CHECKRES(avifRWStreamFinishBox(&s, ipma));
3513
3514
0
    AVIF_CHECKRES(avifRWStreamFinishBox(&s, iprp));
3515
3516
    // -----------------------------------------------------------------------
3517
    // Write grpl/altr box
3518
3519
0
    if (encoder->data->alternativeItemIDs.count) {
3520
        // Section 8.18.3.3 of ISO 14496-12 (ISOBMFF) says:
3521
        //   group_id is a non-negative integer assigned to the particular grouping that shall not be equal to any
3522
        //   group_id value of any other EntityToGroupBox, any item_ID value of the hierarchy level
3523
        //   (file, movie. or track) that contains the GroupsListBox, or any track_ID value (when the
3524
        //   GroupsListBox is contained in the file level).
3525
0
        AVIF_ASSERT_OR_RETURN(encoder->data->lastItemID < UINT16_MAX);
3526
0
        ++encoder->data->lastItemID;
3527
0
        const uint32_t groupID = encoder->data->lastItemID;
3528
0
        AVIF_CHECKRES(avifWriteAltrGroup(&s, groupID, &encoder->data->alternativeItemIDs));
3529
0
    }
3530
3531
    // -----------------------------------------------------------------------
3532
    // Finish meta box
3533
3534
0
    AVIF_CHECKRES(avifRWStreamFinishBox(&s, meta));
3535
3536
    // -----------------------------------------------------------------------
3537
    // Write tracks (if an image sequence)
3538
3539
0
    if (isSequence) {
3540
0
        static const uint8_t unityMatrix[9][4] = {
3541
            /* clang-format off */
3542
0
            { 0x00, 0x01, 0x00, 0x00 },
3543
0
            { 0 },
3544
0
            { 0 },
3545
0
            { 0 },
3546
0
            { 0x00, 0x01, 0x00, 0x00 },
3547
0
            { 0 },
3548
0
            { 0 },
3549
0
            { 0 },
3550
0
            { 0x40, 0x00, 0x00, 0x00 } /* clang-format on */
3551
0
        };
3552
3553
0
        if (encoder->repetitionCount < 0 && encoder->repetitionCount != AVIF_REPETITION_COUNT_INFINITE) {
3554
0
            return AVIF_RESULT_INVALID_ARGUMENT;
3555
0
        }
3556
3557
0
        uint64_t framesDurationInTimescales = 0;
3558
0
        for (uint32_t frameIndex = 0; frameIndex < encoder->data->frames.count; ++frameIndex) {
3559
0
            const avifEncoderFrame * frame = &encoder->data->frames.frame[frameIndex];
3560
0
            framesDurationInTimescales += frame->durationInTimescales;
3561
0
        }
3562
0
        uint64_t durationInTimescales;
3563
0
        if (encoder->repetitionCount == AVIF_REPETITION_COUNT_INFINITE) {
3564
0
            durationInTimescales = AVIF_INDEFINITE_DURATION64;
3565
0
        } else {
3566
0
            uint64_t loopCount = encoder->repetitionCount + 1;
3567
0
            AVIF_ASSERT_OR_RETURN(framesDurationInTimescales != 0);
3568
0
            if (loopCount > UINT64_MAX / framesDurationInTimescales) {
3569
                // The multiplication will overflow uint64_t.
3570
0
                return AVIF_RESULT_INVALID_ARGUMENT;
3571
0
            }
3572
0
            durationInTimescales = framesDurationInTimescales * loopCount;
3573
0
        }
3574
3575
        // -------------------------------------------------------------------
3576
        // Start moov
3577
3578
0
        avifBoxMarker moov;
3579
0
        AVIF_CHECKRES(avifRWStreamWriteBox(&s, "moov", AVIF_BOX_SIZE_TBD, &moov));
3580
3581
0
        avifBoxMarker mvhd;
3582
0
        AVIF_CHECKRES(avifRWStreamWriteFullBox(&s, "mvhd", AVIF_BOX_SIZE_TBD, 1, 0, &mvhd));
3583
0
        AVIF_CHECKRES(avifRWStreamWriteU64(&s, creationTime));                 // unsigned int(64) creation_time;
3584
0
        AVIF_CHECKRES(avifRWStreamWriteU64(&s, modificationTime));             // unsigned int(64) modification_time;
3585
0
        AVIF_CHECKRES(avifRWStreamWriteU32(&s, (uint32_t)encoder->timescale)); // unsigned int(32) timescale;
3586
0
        AVIF_CHECKRES(avifRWStreamWriteU64(&s, durationInTimescales));         // unsigned int(64) duration;
3587
0
        AVIF_CHECKRES(avifRWStreamWriteU32(&s, 0x00010000)); // template int(32) rate = 0x00010000; // typically 1.0
3588
0
        AVIF_CHECKRES(avifRWStreamWriteU16(&s, 0x0100));     // template int(16) volume = 0x0100; // typically, full volume
3589
0
        AVIF_CHECKRES(avifRWStreamWriteU16(&s, 0));          // const bit(16) reserved = 0;
3590
0
        AVIF_CHECKRES(avifRWStreamWriteZeros(&s, 8));        // const unsigned int(32)[2] reserved = 0;
3591
0
        AVIF_CHECKRES(avifRWStreamWrite(&s, unityMatrix, sizeof(unityMatrix)));
3592
0
        AVIF_CHECKRES(avifRWStreamWriteZeros(&s, 24));                       // bit(32)[6] pre_defined = 0;
3593
0
        AVIF_CHECKRES(avifRWStreamWriteU32(&s, encoder->data->items.count)); // unsigned int(32) next_track_ID;
3594
0
        AVIF_CHECKRES(avifRWStreamFinishBox(&s, mvhd));
3595
3596
        // -------------------------------------------------------------------
3597
        // Write tracks
3598
3599
0
        for (uint32_t itemIndex = 0; itemIndex < encoder->data->items.count; ++itemIndex) {
3600
0
            avifEncoderItem * item = &encoder->data->items.item[itemIndex];
3601
0
            if (item->encodeOutput->samples.count == 0) {
3602
0
                continue;
3603
0
            }
3604
3605
0
            uint32_t syncSamplesCount = 0;
3606
0
            for (uint32_t sampleIndex = 0; sampleIndex < item->encodeOutput->samples.count; ++sampleIndex) {
3607
0
                avifEncodeSample * sample = &item->encodeOutput->samples.sample[sampleIndex];
3608
0
                if (sample->sync) {
3609
0
                    ++syncSamplesCount;
3610
0
                }
3611
0
            }
3612
3613
0
            avifBoxMarker trak;
3614
0
            AVIF_CHECKRES(avifRWStreamWriteBox(&s, "trak", AVIF_BOX_SIZE_TBD, &trak));
3615
3616
0
            avifBoxMarker tkhd;
3617
0
            AVIF_CHECKRES(avifRWStreamWriteFullBox(&s, "tkhd", AVIF_BOX_SIZE_TBD, 1, 1, &tkhd));
3618
0
            AVIF_CHECKRES(avifRWStreamWriteU64(&s, creationTime));           // unsigned int(64) creation_time;
3619
0
            AVIF_CHECKRES(avifRWStreamWriteU64(&s, modificationTime));       // unsigned int(64) modification_time;
3620
0
            AVIF_CHECKRES(avifRWStreamWriteU32(&s, itemIndex + 1));          // unsigned int(32) track_ID;
3621
0
            AVIF_CHECKRES(avifRWStreamWriteU32(&s, 0));                      // const unsigned int(32) reserved = 0;
3622
0
            AVIF_CHECKRES(avifRWStreamWriteU64(&s, durationInTimescales));   // unsigned int(64) duration;
3623
0
            AVIF_CHECKRES(avifRWStreamWriteZeros(&s, sizeof(uint32_t) * 2)); // const unsigned int(32)[2] reserved = 0;
3624
0
            AVIF_CHECKRES(avifRWStreamWriteU16(&s, 0));                      // template int(16) layer = 0;
3625
0
            AVIF_CHECKRES(avifRWStreamWriteU16(&s, 0));                      // template int(16) alternate_group = 0;
3626
0
            AVIF_CHECKRES(avifRWStreamWriteU16(&s, 0)); // template int(16) volume = {if track_is_audio 0x0100 else 0};
3627
0
            AVIF_CHECKRES(avifRWStreamWriteU16(&s, 0)); // const unsigned int(16) reserved = 0;
3628
0
            AVIF_CHECKRES(avifRWStreamWrite(&s, unityMatrix, sizeof(unityMatrix))); // template int(32)[9] matrix= // { 0x00010000,0,0,0,0x00010000,0,0,0,0x40000000 };
3629
0
            AVIF_CHECKRES(avifRWStreamWriteU32(&s, imageMetadata->width << 16));  // unsigned int(32) width;
3630
0
            AVIF_CHECKRES(avifRWStreamWriteU32(&s, imageMetadata->height << 16)); // unsigned int(32) height;
3631
0
            AVIF_CHECKRES(avifRWStreamFinishBox(&s, tkhd));
3632
3633
0
            if (item->irefToID != 0) {
3634
0
                avifBoxMarker tref;
3635
0
                AVIF_CHECKRES(avifRWStreamWriteBox(&s, "tref", AVIF_BOX_SIZE_TBD, &tref));
3636
0
                avifBoxMarker refType;
3637
0
                AVIF_CHECKRES(avifRWStreamWriteBox(&s, item->irefType, AVIF_BOX_SIZE_TBD, &refType));
3638
0
                AVIF_CHECKRES(avifRWStreamWriteU32(&s, (uint32_t)item->irefToID));
3639
0
                AVIF_CHECKRES(avifRWStreamFinishBox(&s, refType));
3640
0
                AVIF_CHECKRES(avifRWStreamFinishBox(&s, tref));
3641
0
            }
3642
3643
0
            avifBoxMarker edts;
3644
0
            AVIF_CHECKRES(avifRWStreamWriteBox(&s, "edts", AVIF_BOX_SIZE_TBD, &edts));
3645
0
            uint32_t elstFlags = (encoder->repetitionCount != 0);
3646
0
            avifBoxMarker elst;
3647
0
            AVIF_CHECKRES(avifRWStreamWriteFullBox(&s, "elst", AVIF_BOX_SIZE_TBD, 1, elstFlags, &elst));
3648
0
            AVIF_CHECKRES(avifRWStreamWriteU32(&s, 1));                          // unsigned int(32) entry_count;
3649
0
            AVIF_CHECKRES(avifRWStreamWriteU64(&s, framesDurationInTimescales)); // unsigned int(64) segment_duration;
3650
0
            AVIF_CHECKRES(avifRWStreamWriteU64(&s, 0));                          // int(64) media_time;
3651
0
            AVIF_CHECKRES(avifRWStreamWriteU16(&s, 1));                          // int(16) media_rate_integer;
3652
0
            AVIF_CHECKRES(avifRWStreamWriteU16(&s, 0));                          // int(16) media_rate_fraction = 0;
3653
0
            AVIF_CHECKRES(avifRWStreamFinishBox(&s, elst));
3654
0
            AVIF_CHECKRES(avifRWStreamFinishBox(&s, edts));
3655
3656
0
            if (item->itemCategory != AVIF_ITEM_ALPHA) {
3657
0
                AVIF_CHECKRES(avifEncoderWriteTrackMetaBox(encoder, &s));
3658
0
            }
3659
3660
0
            avifBoxMarker mdia;
3661
0
            AVIF_CHECKRES(avifRWStreamWriteBox(&s, "mdia", AVIF_BOX_SIZE_TBD, &mdia));
3662
3663
0
            avifBoxMarker mdhd;
3664
0
            AVIF_CHECKRES(avifRWStreamWriteFullBox(&s, "mdhd", AVIF_BOX_SIZE_TBD, 1, 0, &mdhd));
3665
0
            AVIF_CHECKRES(avifRWStreamWriteU64(&s, creationTime));                 // unsigned int(64) creation_time;
3666
0
            AVIF_CHECKRES(avifRWStreamWriteU64(&s, modificationTime));             // unsigned int(64) modification_time;
3667
0
            AVIF_CHECKRES(avifRWStreamWriteU32(&s, (uint32_t)encoder->timescale)); // unsigned int(32) timescale;
3668
0
            AVIF_CHECKRES(avifRWStreamWriteU64(&s, framesDurationInTimescales));   // unsigned int(64) duration;
3669
0
            AVIF_CHECKRES(avifRWStreamWriteU16(&s, 21956)); // bit(1) pad = 0; unsigned int(5)[3] language; ("und")
3670
0
            AVIF_CHECKRES(avifRWStreamWriteU16(&s, 0));     // unsigned int(16) pre_defined = 0;
3671
0
            AVIF_CHECKRES(avifRWStreamFinishBox(&s, mdhd));
3672
3673
0
            AVIF_CHECKRES(avifRWStreamWriteHandlerBox(&s, (item->itemCategory == AVIF_ITEM_ALPHA) ? "auxv" : "pict"));
3674
3675
0
            avifBoxMarker minf;
3676
0
            AVIF_CHECKRES(avifRWStreamWriteBox(&s, "minf", AVIF_BOX_SIZE_TBD, &minf));
3677
3678
0
            avifBoxMarker vmhd;
3679
0
            AVIF_CHECKRES(avifRWStreamWriteFullBox(&s, "vmhd", AVIF_BOX_SIZE_TBD, 0, 1, &vmhd));
3680
0
            AVIF_CHECKRES(avifRWStreamWriteU16(&s, 0)); // template unsigned int(16) graphicsmode = 0; (copy over the existing image)
3681
0
            AVIF_CHECKRES(avifRWStreamWriteZeros(&s, 6)); // template unsigned int(16)[3] opcolor = {0, 0, 0};
3682
0
            AVIF_CHECKRES(avifRWStreamFinishBox(&s, vmhd));
3683
3684
0
            avifBoxMarker dinf;
3685
0
            AVIF_CHECKRES(avifRWStreamWriteBox(&s, "dinf", AVIF_BOX_SIZE_TBD, &dinf));
3686
0
            avifBoxMarker dref;
3687
0
            AVIF_CHECKRES(avifRWStreamWriteFullBox(&s, "dref", AVIF_BOX_SIZE_TBD, 0, 0, &dref));
3688
0
            AVIF_CHECKRES(avifRWStreamWriteU32(&s, 1)); // unsigned int(32) entry_count;
3689
0
            AVIF_CHECKRES(avifRWStreamWriteFullBox(&s, "url ", /*contentSize=*/0, 0, 1, /*marker=*/NULL)); // flags:1 means data is in this file
3690
0
            AVIF_CHECKRES(avifRWStreamFinishBox(&s, dref));
3691
0
            AVIF_CHECKRES(avifRWStreamFinishBox(&s, dinf));
3692
3693
            // The boxes within the "stbl" box are ordered using the following recommendation in ISO/IEC 14496-12, Section 6.2.3:
3694
            // 4) It is recommended that the boxes within the Sample Table Box be in the following order: Sample Description
3695
            // (stsd), Time to Sample (stts), Sample to Chunk (stsc), Sample Size (stsz), Chunk Offset (stco).
3696
            //
3697
            // Any boxes not listed in the above line are placed in the end (after the "stco" box).
3698
0
            avifBoxMarker stbl;
3699
0
            AVIF_CHECKRES(avifRWStreamWriteBox(&s, "stbl", AVIF_BOX_SIZE_TBD, &stbl));
3700
3701
0
            avifBoxMarker stsd;
3702
0
            AVIF_CHECKRES(avifRWStreamWriteFullBox(&s, "stsd", AVIF_BOX_SIZE_TBD, 0, 0, &stsd));
3703
0
            AVIF_CHECKRES(avifRWStreamWriteU32(&s, 1)); // unsigned int(32) entry_count;
3704
0
            avifBoxMarker imageItem;
3705
0
            AVIF_CHECKRES(avifRWStreamWriteBox(&s, encoder->data->imageItemType, AVIF_BOX_SIZE_TBD, &imageItem));
3706
0
            AVIF_CHECKRES(avifRWStreamWriteZeros(&s, 6));                             // const unsigned int(8)[6] reserved = 0;
3707
0
            AVIF_CHECKRES(avifRWStreamWriteU16(&s, 1));                               // unsigned int(16) data_reference_index;
3708
0
            AVIF_CHECKRES(avifRWStreamWriteU16(&s, 0));                               // unsigned int(16) pre_defined = 0;
3709
0
            AVIF_CHECKRES(avifRWStreamWriteU16(&s, 0));                               // const unsigned int(16) reserved = 0;
3710
0
            AVIF_CHECKRES(avifRWStreamWriteZeros(&s, sizeof(uint32_t) * 3));          // unsigned int(32)[3] pre_defined = 0;
3711
0
            AVIF_CHECKRES(avifRWStreamWriteU16(&s, (uint16_t)imageMetadata->width));  // unsigned int(16) width;
3712
0
            AVIF_CHECKRES(avifRWStreamWriteU16(&s, (uint16_t)imageMetadata->height)); // unsigned int(16) height;
3713
0
            AVIF_CHECKRES(avifRWStreamWriteU32(&s, 0x00480000));                      // template unsigned int(32) horizresolution
3714
0
            AVIF_CHECKRES(avifRWStreamWriteU32(&s, 0x00480000));                      // template unsigned int(32) vertresolution
3715
0
            AVIF_CHECKRES(avifRWStreamWriteU32(&s, 0));                               // const unsigned int(32) reserved = 0;
3716
0
            AVIF_CHECKRES(avifRWStreamWriteU16(&s, 1));                      // template unsigned int(16) frame_count = 1;
3717
0
            AVIF_CHECKRES(avifRWStreamWriteChars(&s, "\012AOM Coding", 11)); // string[32] compressorname;
3718
0
            AVIF_CHECKRES(avifRWStreamWriteZeros(&s, 32 - 11));              //
3719
0
            AVIF_CHECKRES(avifRWStreamWriteU16(&s, 0x0018));                 // template unsigned int(16) depth = 0x0018;
3720
0
            AVIF_CHECKRES(avifRWStreamWriteU16(&s, (uint16_t)0xffff));       // int(16) pre_defined = -1;
3721
0
            AVIF_CHECKRES(writeConfigBox(&s, &item->av1C, encoder->data->configPropName));
3722
0
            if (item->itemCategory == AVIF_ITEM_COLOR) {
3723
0
                AVIF_CHECKRES(avifEncoderWriteColorProperties(&s, imageMetadata, NULL, NULL));
3724
0
                AVIF_CHECKRES(avifEncoderWriteHDRProperties(NULL, &s, imageMetadata, NULL, NULL));
3725
0
                AVIF_CHECKRES(avifEncoderWriteTransformativeProperties(NULL, &s, imageMetadata, NULL, NULL));
3726
0
            }
3727
3728
0
            avifBoxMarker ccst;
3729
0
            AVIF_CHECKRES(avifRWStreamWriteFullBox(&s, "ccst", AVIF_BOX_SIZE_TBD, 0, 0, &ccst));
3730
0
            AVIF_CHECKRES(avifRWStreamWriteBits(&s, 0, /*bitCount=*/1));  // unsigned int(1) all_ref_pics_intra;
3731
0
            AVIF_CHECKRES(avifRWStreamWriteBits(&s, 1, /*bitCount=*/1));  // unsigned int(1) intra_pred_used;
3732
0
            AVIF_CHECKRES(avifRWStreamWriteBits(&s, 15, /*bitCount=*/4)); // unsigned int(4) max_ref_per_pic;
3733
0
            AVIF_CHECKRES(avifRWStreamWriteBits(&s, 0, /*bitCount=*/26)); // unsigned int(26) reserved;
3734
0
            AVIF_CHECKRES(avifRWStreamFinishBox(&s, ccst));
3735
3736
0
            if (item->itemCategory == AVIF_ITEM_ALPHA) {
3737
0
                avifBoxMarker auxi;
3738
0
                AVIF_CHECKRES(avifRWStreamWriteFullBox(&s, "auxi", AVIF_BOX_SIZE_TBD, 0, 0, &auxi));
3739
0
                AVIF_CHECKRES(avifRWStreamWriteChars(&s, alphaURN, alphaURNSize)); //  string aux_track_type;
3740
0
                AVIF_CHECKRES(avifRWStreamFinishBox(&s, auxi));
3741
0
            }
3742
3743
0
            AVIF_CHECKRES(avifRWStreamFinishBox(&s, imageItem));
3744
0
            AVIF_CHECKRES(avifRWStreamFinishBox(&s, stsd));
3745
3746
0
            avifBoxMarker stts;
3747
0
            AVIF_CHECKRES(avifRWStreamWriteFullBox(&s, "stts", AVIF_BOX_SIZE_TBD, 0, 0, &stts));
3748
0
            size_t sttsEntryCountOffset = avifRWStreamOffset(&s);
3749
0
            uint32_t sttsEntryCount = 0;
3750
0
            AVIF_CHECKRES(avifRWStreamWriteU32(&s, 0)); // unsigned int(32) entry_count;
3751
0
            for (uint32_t sampleCount = 0, frameIndex = 0; frameIndex < encoder->data->frames.count; ++frameIndex) {
3752
0
                avifEncoderFrame * frame = &encoder->data->frames.frame[frameIndex];
3753
0
                ++sampleCount;
3754
0
                if (frameIndex < (encoder->data->frames.count - 1)) {
3755
0
                    avifEncoderFrame * nextFrame = &encoder->data->frames.frame[frameIndex + 1];
3756
0
                    if (frame->durationInTimescales == nextFrame->durationInTimescales) {
3757
0
                        continue;
3758
0
                    }
3759
0
                }
3760
0
                AVIF_CHECKRES(avifRWStreamWriteU32(&s, sampleCount));                           // unsigned int(32) sample_count;
3761
0
                AVIF_CHECKRES(avifRWStreamWriteU32(&s, (uint32_t)frame->durationInTimescales)); // unsigned int(32) sample_delta;
3762
0
                sampleCount = 0;
3763
0
                ++sttsEntryCount;
3764
0
            }
3765
0
            size_t prevOffset = avifRWStreamOffset(&s);
3766
0
            avifRWStreamSetOffset(&s, sttsEntryCountOffset);
3767
0
            AVIF_CHECKRES(avifRWStreamWriteU32(&s, sttsEntryCount));
3768
0
            avifRWStreamSetOffset(&s, prevOffset);
3769
0
            AVIF_CHECKRES(avifRWStreamFinishBox(&s, stts));
3770
3771
0
            avifBoxMarker stsc;
3772
0
            AVIF_CHECKRES(avifRWStreamWriteFullBox(&s, "stsc", AVIF_BOX_SIZE_TBD, 0, 0, &stsc));
3773
0
            AVIF_CHECKRES(avifRWStreamWriteU32(&s, 1));                                 // unsigned int(32) entry_count;
3774
0
            AVIF_CHECKRES(avifRWStreamWriteU32(&s, 1));                                 // unsigned int(32) first_chunk;
3775
0
            AVIF_CHECKRES(avifRWStreamWriteU32(&s, item->encodeOutput->samples.count)); // unsigned int(32) samples_per_chunk;
3776
0
            AVIF_CHECKRES(avifRWStreamWriteU32(&s, 1)); // unsigned int(32) sample_description_index;
3777
0
            AVIF_CHECKRES(avifRWStreamFinishBox(&s, stsc));
3778
3779
0
            avifBoxMarker stsz;
3780
0
            AVIF_CHECKRES(avifRWStreamWriteFullBox(&s, "stsz", AVIF_BOX_SIZE_TBD, 0, 0, &stsz));
3781
0
            AVIF_CHECKRES(avifRWStreamWriteU32(&s, 0));                                 // unsigned int(32) sample_size;
3782
0
            AVIF_CHECKRES(avifRWStreamWriteU32(&s, item->encodeOutput->samples.count)); // unsigned int(32) sample_count;
3783
0
            for (uint32_t sampleIndex = 0; sampleIndex < item->encodeOutput->samples.count; ++sampleIndex) {
3784
0
                avifEncodeSample * sample = &item->encodeOutput->samples.sample[sampleIndex];
3785
0
                AVIF_CHECKRES(avifRWStreamWriteU32(&s, (uint32_t)sample->data.size)); // unsigned int(32) entry_size;
3786
0
            }
3787
0
            AVIF_CHECKRES(avifRWStreamFinishBox(&s, stsz));
3788
3789
0
            avifBoxMarker stco;
3790
0
            AVIF_CHECKRES(avifRWStreamWriteFullBox(&s, "stco", AVIF_BOX_SIZE_TBD, 0, 0, &stco));
3791
0
            AVIF_CHECKRES(avifRWStreamWriteU32(&s, 1));           // unsigned int(32) entry_count;
3792
0
            AVIF_CHECKRES(avifEncoderItemAddMdatFixup(item, &s)); //
3793
0
            AVIF_CHECKRES(avifRWStreamWriteU32(&s, 1));           // unsigned int(32) chunk_offset; (set later)
3794
0
            AVIF_CHECKRES(avifRWStreamFinishBox(&s, stco));
3795
3796
0
            avifBool hasNonSyncSample = AVIF_FALSE;
3797
0
            for (uint32_t sampleIndex = 0; sampleIndex < item->encodeOutput->samples.count; ++sampleIndex) {
3798
0
                if (!item->encodeOutput->samples.sample[sampleIndex].sync) {
3799
0
                    hasNonSyncSample = AVIF_TRUE;
3800
0
                    break;
3801
0
                }
3802
0
            }
3803
            // ISO/IEC 14496-12, Section 8.6.2.1:
3804
            //   If the SyncSampleBox is not present, every sample is a sync sample.
3805
0
            if (hasNonSyncSample) {
3806
0
                avifBoxMarker stss;
3807
0
                AVIF_CHECKRES(avifRWStreamWriteFullBox(&s, "stss", AVIF_BOX_SIZE_TBD, 0, 0, &stss));
3808
0
                AVIF_CHECKRES(avifRWStreamWriteU32(&s, syncSamplesCount)); // unsigned int(32) entry_count;
3809
0
                for (uint32_t sampleIndex = 0; sampleIndex < item->encodeOutput->samples.count; ++sampleIndex) {
3810
0
                    avifEncodeSample * sample = &item->encodeOutput->samples.sample[sampleIndex];
3811
0
                    if (sample->sync) {
3812
0
                        AVIF_CHECKRES(avifRWStreamWriteU32(&s, sampleIndex + 1)); // unsigned int(32) sample_number;
3813
0
                    }
3814
0
                }
3815
0
                AVIF_CHECKRES(avifRWStreamFinishBox(&s, stss));
3816
0
            }
3817
3818
0
            AVIF_CHECKRES(avifRWStreamFinishBox(&s, stbl));
3819
3820
0
            AVIF_CHECKRES(avifRWStreamFinishBox(&s, minf));
3821
0
            AVIF_CHECKRES(avifRWStreamFinishBox(&s, mdia));
3822
0
            AVIF_CHECKRES(avifRWStreamFinishBox(&s, trak));
3823
0
        }
3824
3825
        // -------------------------------------------------------------------
3826
        // Finish moov box
3827
3828
0
        AVIF_CHECKRES(avifRWStreamFinishBox(&s, moov));
3829
0
    }
3830
3831
    // -----------------------------------------------------------------------
3832
    // Write mdat
3833
3834
0
    avifEncoderItemReferenceArray layeredColorItems;
3835
0
    avifEncoderItemReferenceArray layeredAlphaItems;
3836
0
    if (!avifArrayCreate(&layeredColorItems, sizeof(avifEncoderItemReference), 1)) {
3837
0
        result = AVIF_RESULT_OUT_OF_MEMORY;
3838
0
    }
3839
0
    if (!avifArrayCreate(&layeredAlphaItems, sizeof(avifEncoderItemReference), 1)) {
3840
0
        result = AVIF_RESULT_OUT_OF_MEMORY;
3841
0
    }
3842
0
    if (result == AVIF_RESULT_OK) {
3843
0
        result = avifEncoderWriteMediaDataBox(encoder, &s, &layeredColorItems, &layeredAlphaItems);
3844
0
    }
3845
0
    avifArrayDestroy(&layeredColorItems);
3846
0
    avifArrayDestroy(&layeredAlphaItems);
3847
0
    AVIF_CHECKRES(result);
3848
3849
    // -----------------------------------------------------------------------
3850
    // Finish up stream
3851
3852
0
    avifRWStreamFinishWrite(&s);
3853
3854
#if defined(AVIF_ENABLE_COMPLIANCE_WARDEN)
3855
    AVIF_CHECKRES(avifIsCompliant(output->data, output->size));
3856
#endif
3857
3858
0
    return AVIF_RESULT_OK;
3859
0
}
3860
3861
avifResult avifEncoderWrite(avifEncoder * encoder, const avifImage * image, avifRWData * output)
3862
0
{
3863
0
    avifResult addImageResult = avifEncoderAddImage(encoder, image, 1, AVIF_ADD_IMAGE_FLAG_SINGLE);
3864
0
    if (addImageResult != AVIF_RESULT_OK) {
3865
0
        return addImageResult;
3866
0
    }
3867
0
    return avifEncoderFinish(encoder, output);
3868
0
}
3869
3870
// Implementation of section 2.3.3 of AV1 Codec ISO Media File Format Binding specification v1.2.0.
3871
// See https://aomediacodec.github.io/av1-isobmff/v1.2.0.html#av1codecconfigurationbox-syntax.
3872
static avifResult writeCodecConfig(avifRWStream * s, const avifCodecConfigurationBox * cfg)
3873
0
{
3874
0
    const size_t av1COffset = s->offset;
3875
3876
0
    AVIF_CHECKRES(avifRWStreamWriteBits(s, 1, /*bitCount=*/1)); // unsigned int (1) marker = 1;
3877
0
    AVIF_CHECKRES(avifRWStreamWriteBits(s, 1, /*bitCount=*/7)); // unsigned int (7) version = 1;
3878
3879
0
    AVIF_CHECKRES(avifRWStreamWriteBits(s, cfg->seqProfile, /*bitCount=*/3));   // unsigned int (3) seq_profile;
3880
0
    AVIF_CHECKRES(avifRWStreamWriteBits(s, cfg->seqLevelIdx0, /*bitCount=*/5)); // unsigned int (5) seq_level_idx_0;
3881
3882
0
    AVIF_CHECKRES(avifRWStreamWriteBits(s, cfg->seqTier0, /*bitCount=*/1));             // unsigned int (1) seq_tier_0;
3883
0
    AVIF_CHECKRES(avifRWStreamWriteBits(s, cfg->highBitdepth, /*bitCount=*/1));         // unsigned int (1) high_bitdepth;
3884
0
    AVIF_CHECKRES(avifRWStreamWriteBits(s, cfg->twelveBit, /*bitCount=*/1));            // unsigned int (1) twelve_bit;
3885
0
    AVIF_CHECKRES(avifRWStreamWriteBits(s, cfg->monochrome, /*bitCount=*/1));           // unsigned int (1) monochrome;
3886
0
    AVIF_CHECKRES(avifRWStreamWriteBits(s, cfg->chromaSubsamplingX, /*bitCount=*/1));   // unsigned int (1) chroma_subsampling_x;
3887
0
    AVIF_CHECKRES(avifRWStreamWriteBits(s, cfg->chromaSubsamplingY, /*bitCount=*/1));   // unsigned int (1) chroma_subsampling_y;
3888
0
    AVIF_CHECKRES(avifRWStreamWriteBits(s, cfg->chromaSamplePosition, /*bitCount=*/2)); // unsigned int (2) chroma_sample_position;
3889
3890
0
    AVIF_CHECKRES(avifRWStreamWriteBits(s, 0, /*bitCount=*/3)); // unsigned int (3) reserved = 0;
3891
0
    AVIF_CHECKRES(avifRWStreamWriteBits(s, 0, /*bitCount=*/1)); // unsigned int (1) initial_presentation_delay_present;
3892
0
    AVIF_CHECKRES(avifRWStreamWriteBits(s, 0, /*bitCount=*/4)); // unsigned int (4) reserved = 0;
3893
3894
    // According to section 2.2.1 of AV1 Image File Format specification v1.1.0,
3895
    // there is no need to write any OBU here.
3896
    // See https://aomediacodec.github.io/av1-avif/v1.1.0.html#av1-configuration-item-property.
3897
    // unsigned int (8) configOBUs[];
3898
3899
0
    AVIF_ASSERT_OR_RETURN(s->offset - av1COffset == 4); // Make sure writeCodecConfig() writes exactly 4 bytes.
3900
0
    return AVIF_RESULT_OK;
3901
0
}
3902
3903
static avifResult writeConfigBox(avifRWStream * s, const avifCodecConfigurationBox * cfg, const char * configPropName)
3904
0
{
3905
0
    avifBoxMarker configBox;
3906
0
    AVIF_CHECKRES(avifRWStreamWriteBox(s, configPropName, AVIF_BOX_SIZE_TBD, &configBox));
3907
0
    AVIF_CHECKRES(writeCodecConfig(s, cfg));
3908
0
    AVIF_CHECKRES(avifRWStreamFinishBox(s, configBox));
3909
0
    return AVIF_RESULT_OK;
3910
0
}