blob: 45f002f78568bd06cec595ff6b7cff55dd4717b0 [file] [log] [blame]
Joe Drago444f0512019-01-23 17:03:24 -08001// Copyright 2019 Joe Drago. All rights reserved.
2// SPDX-License-Identifier: BSD-2-Clause
3
4#include "avif/internal.h"
5
Joe Dragoceb2fa02021-01-29 18:14:55 -08006#include <assert.h>
Joe Drago444f0512019-01-23 17:03:24 -08007#include <string.h>
Joe Drago4a25c192020-06-03 16:29:58 -07008#include <time.h>
Joe Drago444f0512019-01-23 17:03:24 -08009
Joe Drago224b0182019-02-08 10:42:29 -080010#define MAX_ASSOCIATIONS 16
11struct ipmaArray
12{
13 uint8_t associations[MAX_ASSOCIATIONS];
Joe Drago0da29972020-04-23 11:55:10 -070014 avifBool essential[MAX_ASSOCIATIONS];
Joe Drago224b0182019-02-08 10:42:29 -080015 uint8_t count;
16};
Joe Drago0da29972020-04-23 11:55:10 -070017static void ipmaPush(struct ipmaArray * ipma, uint8_t assoc, avifBool essential)
Joe Drago224b0182019-02-08 10:42:29 -080018{
19 ipma->associations[ipma->count] = assoc;
Joe Drago0da29972020-04-23 11:55:10 -070020 ipma->essential[ipma->count] = essential;
Joe Drago224b0182019-02-08 10:42:29 -080021 ++ipma->count;
22}
23
Joe Dragoa72da5b2020-06-15 19:40:17 -070024// Used to store offsets in meta boxes which need to point at mdat offsets that
25// aren't known yet. When an item's mdat payload is written, all registered fixups
26// will have this now-known offset "fixed up".
27typedef struct avifOffsetFixup
28{
29 size_t offset;
30} avifOffsetFixup;
31AVIF_ARRAY_DECLARE(avifOffsetFixupArray, avifOffsetFixup, fixup);
32
Joe Dragocd1e4c32019-02-08 11:26:31 -080033static const char alphaURN[] = URN_ALPHA0;
34static const size_t alphaURNSize = sizeof(alphaURN);
35
Joe Dragof6a42272019-11-21 15:21:41 -080036static const char xmpContentType[] = CONTENT_TYPE_XMP;
37static const size_t xmpContentTypeSize = sizeof(xmpContentType);
38
Wan-Teh Change184dc12020-05-11 12:47:21 -070039static avifBool avifImageIsOpaque(const avifImage * image);
Joe Drago345aaa12019-09-25 13:42:12 -070040static void writeConfigBox(avifRWStream * s, avifCodecConfigurationBox * cfg);
Joe Drago444f0512019-01-23 17:03:24 -080041
Joe Drago800b47f2020-03-18 16:22:37 -070042// ---------------------------------------------------------------------------
Joe Drago5b596c42020-06-02 17:13:38 -070043// avifCodecEncodeOutput
44
45avifCodecEncodeOutput * avifCodecEncodeOutputCreate(void)
46{
47 avifCodecEncodeOutput * encodeOutput = (avifCodecEncodeOutput *)avifAlloc(sizeof(avifCodecEncodeOutput));
48 memset(encodeOutput, 0, sizeof(avifCodecEncodeOutput));
Wan-Teh Changf732a4d2022-01-21 15:56:35 -080049 if (!avifArrayCreate(&encodeOutput->samples, sizeof(avifEncodeSample), 1)) {
50 goto error;
51 }
Joe Drago5b596c42020-06-02 17:13:38 -070052 return encodeOutput;
Wan-Teh Changf732a4d2022-01-21 15:56:35 -080053
54error:
55 avifCodecEncodeOutputDestroy(encodeOutput);
56 return NULL;
Joe Drago5b596c42020-06-02 17:13:38 -070057}
58
59void avifCodecEncodeOutputAddSample(avifCodecEncodeOutput * encodeOutput, const uint8_t * data, size_t len, avifBool sync)
60{
61 avifEncodeSample * sample = (avifEncodeSample *)avifArrayPushPtr(&encodeOutput->samples);
62 avifRWDataSet(&sample->data, data, len);
63 sample->sync = sync;
64}
65
66void avifCodecEncodeOutputDestroy(avifCodecEncodeOutput * encodeOutput)
67{
68 for (uint32_t sampleIndex = 0; sampleIndex < encodeOutput->samples.count; ++sampleIndex) {
69 avifRWDataFree(&encodeOutput->samples.sample[sampleIndex].data);
70 }
71 avifArrayDestroy(&encodeOutput->samples);
72 avifFree(encodeOutput);
73}
74
75// ---------------------------------------------------------------------------
Joe Drago800b47f2020-03-18 16:22:37 -070076// avifEncoderItem
77
78// one "item" worth for encoder
79typedef struct avifEncoderItem
80{
81 uint16_t id;
82 uint8_t type[4];
Joe Drago5b596c42020-06-02 17:13:38 -070083 avifCodec * codec; // only present on type==av01
Joe Drago25fe1272020-07-09 14:20:39 -070084 avifCodecEncodeOutput * encodeOutput; // AV1 sample data
85 avifRWData metadataPayload; // Exif/XMP data
Joe Drago2172ed02020-11-04 18:04:44 -080086 avifCodecConfigurationBox av1C; // Harvested in avifEncoderFinish(), if encodeOutput has samples
Joe Dragoaec9cff2020-12-11 14:23:37 -080087 uint32_t cellIndex; // Which row-major cell index corresponds to this item. ignored on non-av01 types
Joe Drago800b47f2020-03-18 16:22:37 -070088 avifBool alpha;
Wan-Teh Chang0f418912022-02-17 09:16:05 -080089 avifBool hiddenImage; // A hidden image item has (flags & 1) equal to 1 in its ItemInfoEntry.
Joe Drago800b47f2020-03-18 16:22:37 -070090
91 const char * infeName;
92 size_t infeNameSize;
93 const char * infeContentType;
94 size_t infeContentTypeSize;
Joe Dragoa72da5b2020-06-15 19:40:17 -070095 avifOffsetFixupArray mdatFixups;
Joe Drago800b47f2020-03-18 16:22:37 -070096
97 uint16_t irefToID; // if non-zero, make an iref from this id -> irefToID
98 const char * irefType;
99
Wan-Teh Changf9fca862020-12-22 15:08:12 -0800100 uint32_t gridCols; // if non-zero (legal range [1-256]), this is a grid item
101 uint32_t gridRows; // if non-zero (legal range [1-256]), this is a grid item
Joe Dragoaec9cff2020-12-11 14:23:37 -0800102
103 uint16_t dimgFromID; // if non-zero, make an iref from dimgFromID -> this id
104
Joe Drago800b47f2020-03-18 16:22:37 -0700105 struct ipmaArray ipma;
106} avifEncoderItem;
107AVIF_ARRAY_DECLARE(avifEncoderItemArray, avifEncoderItem, item);
108
109// ---------------------------------------------------------------------------
Joe Drago8210c1b2020-06-02 17:40:28 -0700110// avifEncoderFrame
111
112typedef struct avifEncoderFrame
113{
Joe Drago4a25c192020-06-03 16:29:58 -0700114 uint64_t durationInTimescales;
Joe Drago8210c1b2020-06-02 17:40:28 -0700115} avifEncoderFrame;
116AVIF_ARRAY_DECLARE(avifEncoderFrameArray, avifEncoderFrame, frame);
117
118// ---------------------------------------------------------------------------
Joe Drago800b47f2020-03-18 16:22:37 -0700119// avifEncoderData
120
121typedef struct avifEncoderData
122{
123 avifEncoderItemArray items;
Joe Drago8210c1b2020-06-02 17:40:28 -0700124 avifEncoderFrameArray frames;
Yuan Tongf38cd852022-08-19 20:13:09 +0800125 avifEncoder lastEncoder;
Joe Drago250221a2020-06-01 11:11:06 -0700126 avifImage * imageMetadata;
Joe Drago800b47f2020-03-18 16:22:37 -0700127 uint16_t lastItemID;
128 uint16_t primaryItemID;
Joe Dragoaba51d82020-11-19 13:12:29 -0800129 avifBool singleImage; // if true, the AVIF_ADD_IMAGE_FLAG_SINGLE flag was set on the first call to avifEncoderAddImage()
Joe Dragoaec9cff2020-12-11 14:23:37 -0800130 avifBool alphaPresent;
Joe Drago800b47f2020-03-18 16:22:37 -0700131} avifEncoderData;
132
Wan-Teh Changf732a4d2022-01-21 15:56:35 -0800133static void avifEncoderDataDestroy(avifEncoderData * data);
134
Joe Drago800b47f2020-03-18 16:22:37 -0700135static avifEncoderData * avifEncoderDataCreate()
136{
137 avifEncoderData * data = (avifEncoderData *)avifAlloc(sizeof(avifEncoderData));
138 memset(data, 0, sizeof(avifEncoderData));
Joe Drago250221a2020-06-01 11:11:06 -0700139 data->imageMetadata = avifImageCreateEmpty();
Wan-Teh Changf732a4d2022-01-21 15:56:35 -0800140 if (!avifArrayCreate(&data->items, sizeof(avifEncoderItem), 8)) {
141 goto error;
142 }
143 if (!avifArrayCreate(&data->frames, sizeof(avifEncoderFrame), 1)) {
144 goto error;
145 }
Joe Drago800b47f2020-03-18 16:22:37 -0700146 return data;
Wan-Teh Changf732a4d2022-01-21 15:56:35 -0800147
148error:
149 avifEncoderDataDestroy(data);
150 return NULL;
Joe Drago800b47f2020-03-18 16:22:37 -0700151}
152
Wan-Teh Chang8adbb972020-12-18 14:12:51 -0800153static avifEncoderItem * avifEncoderDataCreateItem(avifEncoderData * data, const char * type, const char * infeName, size_t infeNameSize, uint32_t cellIndex)
Joe Drago800b47f2020-03-18 16:22:37 -0700154{
155 avifEncoderItem * item = (avifEncoderItem *)avifArrayPushPtr(&data->items);
Wan-Teh Changd9cffc52022-01-29 22:31:01 -0800156 ++data->lastItemID;
Joe Drago800b47f2020-03-18 16:22:37 -0700157 item->id = data->lastItemID;
158 memcpy(item->type, type, sizeof(item->type));
159 item->infeName = infeName;
160 item->infeNameSize = infeNameSize;
Joe Drago5b596c42020-06-02 17:13:38 -0700161 item->encodeOutput = avifCodecEncodeOutputCreate();
Joe Dragoaec9cff2020-12-11 14:23:37 -0800162 item->cellIndex = cellIndex;
Wan-Teh Changf732a4d2022-01-21 15:56:35 -0800163 if (!avifArrayCreate(&item->mdatFixups, sizeof(avifOffsetFixup), 4)) {
164 goto error;
165 }
Joe Drago800b47f2020-03-18 16:22:37 -0700166 return item;
Wan-Teh Changf732a4d2022-01-21 15:56:35 -0800167
168error:
169 avifCodecEncodeOutputDestroy(item->encodeOutput);
Wan-Teh Changd9cffc52022-01-29 22:31:01 -0800170 --data->lastItemID;
Wan-Teh Changf732a4d2022-01-21 15:56:35 -0800171 avifArrayPop(&data->items);
172 return NULL;
Joe Drago800b47f2020-03-18 16:22:37 -0700173}
174
Joe Dragoceb2fa02021-01-29 18:14:55 -0800175static avifEncoderItem * avifEncoderDataFindItemByID(avifEncoderData * data, uint16_t id)
176{
177 for (uint32_t itemIndex = 0; itemIndex < data->items.count; ++itemIndex) {
178 avifEncoderItem * item = &data->items.item[itemIndex];
179 if (item->id == id) {
180 return item;
181 }
182 }
183 return NULL;
184}
185
Joe Drago800b47f2020-03-18 16:22:37 -0700186static void avifEncoderDataDestroy(avifEncoderData * data)
187{
188 for (uint32_t i = 0; i < data->items.count; ++i) {
189 avifEncoderItem * item = &data->items.item[i];
190 if (item->codec) {
191 avifCodecDestroy(item->codec);
192 }
Joe Drago5b596c42020-06-02 17:13:38 -0700193 avifCodecEncodeOutputDestroy(item->encodeOutput);
Joe Drago25fe1272020-07-09 14:20:39 -0700194 avifRWDataFree(&item->metadataPayload);
Joe Dragoa72da5b2020-06-15 19:40:17 -0700195 avifArrayDestroy(&item->mdatFixups);
Joe Drago800b47f2020-03-18 16:22:37 -0700196 }
Joe Drago250221a2020-06-01 11:11:06 -0700197 avifImageDestroy(data->imageMetadata);
Joe Drago800b47f2020-03-18 16:22:37 -0700198 avifArrayDestroy(&data->items);
Joe Drago8210c1b2020-06-02 17:40:28 -0700199 avifArrayDestroy(&data->frames);
Joe Drago800b47f2020-03-18 16:22:37 -0700200 avifFree(data);
201}
202
Wan-Teh Chang9f89df92020-07-08 18:54:26 -0700203static void avifEncoderItemAddMdatFixup(avifEncoderItem * item, const avifRWStream * s)
Joe Dragoa72da5b2020-06-15 19:40:17 -0700204{
205 avifOffsetFixup * fixup = (avifOffsetFixup *)avifArrayPushPtr(&item->mdatFixups);
206 fixup->offset = avifRWStreamOffset(s);
207}
208
Joe Drago800b47f2020-03-18 16:22:37 -0700209// ---------------------------------------------------------------------------
Joe Drago5779fc42021-09-08 11:15:26 -0700210// avifItemPropertyDedup - Provides ipco deduplication
211
212typedef struct avifItemProperty
213{
214 uint8_t index;
215 size_t offset;
216 size_t size;
217} avifItemProperty;
218AVIF_ARRAY_DECLARE(avifItemPropertyArray, avifItemProperty, property);
219
220typedef struct avifItemPropertyDedup
221{
222 avifItemPropertyArray properties;
223 avifRWStream s; // Temporary stream for each new property, checked against already-written boxes for deduplications
224 avifRWData buffer; // Temporary storage for 's'
225 uint8_t nextIndex; // 1-indexed, incremented every time another unique property is finished
226} avifItemPropertyDedup;
227
Wan-Teh Changf732a4d2022-01-21 15:56:35 -0800228static void avifItemPropertyDedupDestroy(avifItemPropertyDedup * dedup);
229
Joe Drago5779fc42021-09-08 11:15:26 -0700230static avifItemPropertyDedup * avifItemPropertyDedupCreate(void)
231{
232 avifItemPropertyDedup * dedup = (avifItemPropertyDedup *)avifAlloc(sizeof(avifItemPropertyDedup));
233 memset(dedup, 0, sizeof(avifItemPropertyDedup));
Wan-Teh Changf732a4d2022-01-21 15:56:35 -0800234 if (!avifArrayCreate(&dedup->properties, sizeof(avifItemProperty), 8)) {
235 goto error;
236 }
Joe Drago5779fc42021-09-08 11:15:26 -0700237 avifRWDataRealloc(&dedup->buffer, 2048); // This will resize automatically (if necessary)
238 return dedup;
Wan-Teh Changf732a4d2022-01-21 15:56:35 -0800239
240error:
241 avifItemPropertyDedupDestroy(dedup);
242 return NULL;
Joe Drago5779fc42021-09-08 11:15:26 -0700243}
244
245static void avifItemPropertyDedupDestroy(avifItemPropertyDedup * dedup)
246{
247 avifArrayDestroy(&dedup->properties);
248 avifRWDataFree(&dedup->buffer);
249 avifFree(dedup);
250}
251
252// Resets the dedup's temporary write stream in preparation for a single item property's worth of writing
253static void avifItemPropertyDedupStart(avifItemPropertyDedup * dedup)
254{
255 avifRWStreamStart(&dedup->s, &dedup->buffer);
256}
257
258// This compares the newly written item property (in the dedup's temporary storage buffer) to
259// already-written properties (whose offsets/sizes in outputStream are recorded in the dedup). If a
260// match is found, the previous item's index is used. If this new property is unique, it is
261// assigned the next available property index, written to the output stream, and its offset/size in
262// the output stream is recorded in the dedup for future comparisons.
263//
264// This function always returns a valid 1-indexed property index for usage in a property association
265// (ipma) box later. If the most recent property was a duplicate of a previous property, the return
266// value will be the index of the original property, otherwise it will be the index of the newly
267// created property.
268static uint8_t avifItemPropertyDedupFinish(avifItemPropertyDedup * dedup, avifRWStream * outputStream)
269{
270 const size_t newPropertySize = avifRWStreamOffset(&dedup->s);
271
272 for (size_t i = 0; i < dedup->properties.count; ++i) {
273 avifItemProperty * property = &dedup->properties.property[i];
274 if ((property->size == newPropertySize) &&
275 !memcmp(&outputStream->raw->data[property->offset], dedup->buffer.data, newPropertySize)) {
276 // We've already written this exact property, reuse it
277 return property->index;
278 }
279 }
280
281 // Write a new property, and remember its location in the output stream for future deduplication
282 avifItemProperty * property = (avifItemProperty *)avifArrayPushPtr(&dedup->properties);
283 property->index = ++dedup->nextIndex; // preincrement so the first new index is 1 (as ipma is 1-indexed)
284 property->size = newPropertySize;
285 property->offset = avifRWStreamOffset(outputStream);
286 avifRWStreamWrite(outputStream, dedup->buffer.data, newPropertySize);
287 return property->index;
288}
289
290// ---------------------------------------------------------------------------
Joe Drago800b47f2020-03-18 16:22:37 -0700291
Joe Drago0b05eee2019-06-12 13:24:39 -0700292avifEncoder * avifEncoderCreate(void)
293{
294 avifEncoder * encoder = (avifEncoder *)avifAlloc(sizeof(avifEncoder));
295 memset(encoder, 0, sizeof(avifEncoder));
296 encoder->maxThreads = 1;
Wan-Teh Chang1cd92712022-08-19 18:01:16 -0700297 encoder->speed = AVIF_SPEED_DEFAULT;
298 encoder->keyframeInterval = 0;
299 encoder->timescale = 1;
Joe Drago46ea0582019-07-22 15:55:47 -0700300 encoder->minQuantizer = AVIF_QUANTIZER_LOSSLESS;
301 encoder->maxQuantizer = AVIF_QUANTIZER_LOSSLESS;
Joe Drago56fa2bc2020-03-03 13:29:48 -0800302 encoder->minQuantizerAlpha = AVIF_QUANTIZER_LOSSLESS;
303 encoder->maxQuantizerAlpha = AVIF_QUANTIZER_LOSSLESS;
Joe Drago4bf01872019-11-15 18:09:19 -0800304 encoder->tileRowsLog2 = 0;
305 encoder->tileColsLog2 = 0;
Wan-Teh Chang2792c0a2020-08-28 16:10:31 -0700306 encoder->data = avifEncoderDataCreate();
Joe Dragoe1097bd2020-08-27 18:55:03 -0700307 encoder->csOptions = avifCodecSpecificOptionsCreate();
Joe Drago0b05eee2019-06-12 13:24:39 -0700308 return encoder;
309}
310
311void avifEncoderDestroy(avifEncoder * encoder)
312{
Joe Dragoe1097bd2020-08-27 18:55:03 -0700313 avifCodecSpecificOptionsDestroy(encoder->csOptions);
Joe Drago800b47f2020-03-18 16:22:37 -0700314 avifEncoderDataDestroy(encoder->data);
Joe Drago0b05eee2019-06-12 13:24:39 -0700315 avifFree(encoder);
316}
317
Joe Dragoe1097bd2020-08-27 18:55:03 -0700318void avifEncoderSetCodecSpecificOption(avifEncoder * encoder, const char * key, const char * value)
319{
320 avifCodecSpecificOptionsSet(encoder->csOptions, key, value);
Yuan Tongf38cd852022-08-19 20:13:09 +0800321}
322
Wan-Teh Chang1cd92712022-08-19 18:01:16 -0700323static void avifEncoderBackupSettings(avifEncoder * encoder)
Yuan Tongf38cd852022-08-19 20:13:09 +0800324{
325 avifEncoder * lastEncoder = &encoder->data->lastEncoder;
326
Wan-Teh Chang1cd92712022-08-19 18:01:16 -0700327 // lastEncoder->data is only used to mark that lastEncoder is initialized. lastEncoder->data
328 // must not be dereferenced.
Yuan Tongf38cd852022-08-19 20:13:09 +0800329 lastEncoder->data = encoder->data;
330 lastEncoder->codecChoice = encoder->codecChoice;
Wan-Teh Chang1cd92712022-08-19 18:01:16 -0700331 lastEncoder->maxThreads = encoder->maxThreads;
332 lastEncoder->speed = encoder->speed;
Yuan Tongf38cd852022-08-19 20:13:09 +0800333 lastEncoder->keyframeInterval = encoder->keyframeInterval;
334 lastEncoder->timescale = encoder->timescale;
Yuan Tongf38cd852022-08-19 20:13:09 +0800335 lastEncoder->minQuantizer = encoder->minQuantizer;
336 lastEncoder->maxQuantizer = encoder->maxQuantizer;
337 lastEncoder->minQuantizerAlpha = encoder->minQuantizerAlpha;
338 lastEncoder->maxQuantizerAlpha = encoder->maxQuantizerAlpha;
339 lastEncoder->tileRowsLog2 = encoder->tileRowsLog2;
340 lastEncoder->tileColsLog2 = encoder->tileColsLog2;
Yuan Tongf38cd852022-08-19 20:13:09 +0800341}
342
Wan-Teh Chang1cd92712022-08-19 18:01:16 -0700343// This function detects changes made on avifEncoder. It returns true on success (i.e., if every
344// change is valid), or false on failure (i.e., if any setting that can't change was changed). It
345// reports detected changes in encoderChanges.
346static avifBool avifEncoderDetectChanges(const avifEncoder * encoder, avifEncoderChanges * encoderChanges)
Yuan Tongf38cd852022-08-19 20:13:09 +0800347{
348 const avifEncoder * lastEncoder = &encoder->data->lastEncoder;
Wan-Teh Chang1cd92712022-08-19 18:01:16 -0700349 *encoderChanges = 0;
Yuan Tongf38cd852022-08-19 20:13:09 +0800350
351 if (!lastEncoder->data) {
Wan-Teh Chang1cd92712022-08-19 18:01:16 -0700352 // lastEncoder is not initialized.
Yuan Tongf38cd852022-08-19 20:13:09 +0800353 return AVIF_TRUE;
354 }
355
Wan-Teh Chang1cd92712022-08-19 18:01:16 -0700356 if ((lastEncoder->codecChoice != encoder->codecChoice) || (lastEncoder->maxThreads != encoder->maxThreads) ||
357 (lastEncoder->speed != encoder->speed) || (lastEncoder->keyframeInterval != encoder->keyframeInterval) ||
358 (lastEncoder->timescale != encoder->timescale)) {
Yuan Tongf38cd852022-08-19 20:13:09 +0800359 return AVIF_FALSE;
360 }
361
Wan-Teh Chang1cd92712022-08-19 18:01:16 -0700362 if (lastEncoder->minQuantizer != encoder->minQuantizer) {
Yuan Tong47df37c2022-08-26 12:10:37 +0800363 *encoderChanges |= AVIF_ENCODER_CHANGE_MIN_QUANTIZER;
364 }
Wan-Teh Chang1cd92712022-08-19 18:01:16 -0700365 if (lastEncoder->maxQuantizer != encoder->maxQuantizer) {
Yuan Tong47df37c2022-08-26 12:10:37 +0800366 *encoderChanges |= AVIF_ENCODER_CHANGE_MAX_QUANTIZER;
367 }
Wan-Teh Chang1cd92712022-08-19 18:01:16 -0700368 if (lastEncoder->minQuantizerAlpha != encoder->minQuantizerAlpha) {
Yuan Tong47df37c2022-08-26 12:10:37 +0800369 *encoderChanges |= AVIF_ENCODER_CHANGE_MIN_QUANTIZER_ALPHA;
370 }
Wan-Teh Chang1cd92712022-08-19 18:01:16 -0700371 if (lastEncoder->maxQuantizerAlpha != encoder->maxQuantizerAlpha) {
Yuan Tong47df37c2022-08-26 12:10:37 +0800372 *encoderChanges |= AVIF_ENCODER_CHANGE_MAX_QUANTIZER_ALPHA;
373 }
Wan-Teh Chang1cd92712022-08-19 18:01:16 -0700374 if (lastEncoder->tileRowsLog2 != encoder->tileRowsLog2) {
Yuan Tong47df37c2022-08-26 12:10:37 +0800375 *encoderChanges |= AVIF_ENCODER_CHANGE_TILE_ROWS_LOG2;
376 }
Wan-Teh Chang1cd92712022-08-19 18:01:16 -0700377 if (lastEncoder->tileColsLog2 != encoder->tileColsLog2) {
Yuan Tong47df37c2022-08-26 12:10:37 +0800378 *encoderChanges |= AVIF_ENCODER_CHANGE_TILE_COLS_LOG2;
379 }
380 if (encoder->csOptions->count > 0) {
381 *encoderChanges |= AVIF_ENCODER_CHANGE_CODEC_SPECIFIC;
Yuan Tongf38cd852022-08-19 20:13:09 +0800382 }
383
384 return AVIF_TRUE;
Joe Dragoe1097bd2020-08-27 18:55:03 -0700385}
386
Joe Drago5779fc42021-09-08 11:15:26 -0700387// This function is used in two codepaths:
388// * writing color *item* properties
389// * writing color *track* properties
390//
391// Item properties must have property associations with them and can be deduplicated (by reusing
392// these associations), so this function leverages the ipma and dedup arguments to do this.
393//
394// Track properties, however, are implicitly associated by the track in which they are contained, so
395// there is no need to build a property association box (ipma), and no way to deduplicate/reuse a
396// property. In this case, the ipma and dedup properties should/will be set to NULL, and this
397// function will avoid using them.
398static void avifEncoderWriteColorProperties(avifRWStream * outputStream,
399 const avifImage * imageMetadata,
400 struct ipmaArray * ipma,
401 avifItemPropertyDedup * dedup)
Joe Drago332180d2020-06-15 16:55:16 -0700402{
Joe Drago5779fc42021-09-08 11:15:26 -0700403 avifRWStream * s = outputStream;
404 if (dedup) {
405 assert(ipma);
406
407 // Use the dedup's temporary stream for box writes
408 s = &dedup->s;
409 }
410
Wan-Teh Changb139eed2020-07-08 18:04:14 -0700411 if (imageMetadata->icc.size > 0) {
Joe Drago5779fc42021-09-08 11:15:26 -0700412 if (dedup) {
413 avifItemPropertyDedupStart(dedup);
414 }
Joe Drago332180d2020-06-15 16:55:16 -0700415 avifBoxMarker colr = avifRWStreamWriteBox(s, "colr", AVIF_BOX_SIZE_TBD);
416 avifRWStreamWriteChars(s, "prof", 4); // unsigned int(32) colour_type;
417 avifRWStreamWrite(s, imageMetadata->icc.data, imageMetadata->icc.size);
418 avifRWStreamFinishBox(s, colr);
Joe Drago5779fc42021-09-08 11:15:26 -0700419 if (dedup) {
420 ipmaPush(ipma, avifItemPropertyDedupFinish(dedup, outputStream), AVIF_FALSE);
Joe Drago332180d2020-06-15 16:55:16 -0700421 }
Joe Dragobf58fe72020-11-05 13:25:14 -0800422 }
423
424 // HEIF 6.5.5.1, from Amendment 3 allows multiple colr boxes: "at most one for a given value of colour type"
425 // Therefore, *always* writing an nclx box, even if an a prof box was already written above.
Joe Drago5779fc42021-09-08 11:15:26 -0700426 if (dedup) {
427 avifItemPropertyDedupStart(dedup);
428 }
Joe Dragobf58fe72020-11-05 13:25:14 -0800429 avifBoxMarker colr = avifRWStreamWriteBox(s, "colr", AVIF_BOX_SIZE_TBD);
430 avifRWStreamWriteChars(s, "nclx", 4); // unsigned int(32) colour_type;
Joe Dragofb5a5f02021-01-31 20:43:57 -0800431 avifRWStreamWriteU16(s, imageMetadata->colorPrimaries); // unsigned int(16) colour_primaries;
432 avifRWStreamWriteU16(s, imageMetadata->transferCharacteristics); // unsigned int(16) transfer_characteristics;
433 avifRWStreamWriteU16(s, imageMetadata->matrixCoefficients); // unsigned int(16) matrix_coefficients;
Joe Dragobf58fe72020-11-05 13:25:14 -0800434 avifRWStreamWriteU8(s, (imageMetadata->yuvRange == AVIF_RANGE_FULL) ? 0x80 : 0); // unsigned int(1) full_range_flag;
435 // unsigned int(7) reserved = 0;
436 avifRWStreamFinishBox(s, colr);
Joe Drago5779fc42021-09-08 11:15:26 -0700437 if (dedup) {
438 ipmaPush(ipma, avifItemPropertyDedupFinish(dedup, outputStream), AVIF_FALSE);
Joe Drago332180d2020-06-15 16:55:16 -0700439 }
440
441 // Write (Optional) Transformations
442 if (imageMetadata->transformFlags & AVIF_TRANSFORM_PASP) {
Joe Drago5779fc42021-09-08 11:15:26 -0700443 if (dedup) {
444 avifItemPropertyDedupStart(dedup);
445 }
Joe Drago332180d2020-06-15 16:55:16 -0700446 avifBoxMarker pasp = avifRWStreamWriteBox(s, "pasp", AVIF_BOX_SIZE_TBD);
447 avifRWStreamWriteU32(s, imageMetadata->pasp.hSpacing); // unsigned int(32) hSpacing;
448 avifRWStreamWriteU32(s, imageMetadata->pasp.vSpacing); // unsigned int(32) vSpacing;
449 avifRWStreamFinishBox(s, pasp);
Joe Drago5779fc42021-09-08 11:15:26 -0700450 if (dedup) {
451 ipmaPush(ipma, avifItemPropertyDedupFinish(dedup, outputStream), AVIF_FALSE);
Joe Drago332180d2020-06-15 16:55:16 -0700452 }
453 }
454 if (imageMetadata->transformFlags & AVIF_TRANSFORM_CLAP) {
Joe Drago5779fc42021-09-08 11:15:26 -0700455 if (dedup) {
456 avifItemPropertyDedupStart(dedup);
457 }
Joe Drago332180d2020-06-15 16:55:16 -0700458 avifBoxMarker clap = avifRWStreamWriteBox(s, "clap", AVIF_BOX_SIZE_TBD);
459 avifRWStreamWriteU32(s, imageMetadata->clap.widthN); // unsigned int(32) cleanApertureWidthN;
460 avifRWStreamWriteU32(s, imageMetadata->clap.widthD); // unsigned int(32) cleanApertureWidthD;
461 avifRWStreamWriteU32(s, imageMetadata->clap.heightN); // unsigned int(32) cleanApertureHeightN;
462 avifRWStreamWriteU32(s, imageMetadata->clap.heightD); // unsigned int(32) cleanApertureHeightD;
463 avifRWStreamWriteU32(s, imageMetadata->clap.horizOffN); // unsigned int(32) horizOffN;
464 avifRWStreamWriteU32(s, imageMetadata->clap.horizOffD); // unsigned int(32) horizOffD;
465 avifRWStreamWriteU32(s, imageMetadata->clap.vertOffN); // unsigned int(32) vertOffN;
466 avifRWStreamWriteU32(s, imageMetadata->clap.vertOffD); // unsigned int(32) vertOffD;
467 avifRWStreamFinishBox(s, clap);
Joe Drago5779fc42021-09-08 11:15:26 -0700468 if (dedup) {
469 ipmaPush(ipma, avifItemPropertyDedupFinish(dedup, outputStream), AVIF_TRUE);
Joe Drago332180d2020-06-15 16:55:16 -0700470 }
471 }
472 if (imageMetadata->transformFlags & AVIF_TRANSFORM_IROT) {
Joe Drago5779fc42021-09-08 11:15:26 -0700473 if (dedup) {
474 avifItemPropertyDedupStart(dedup);
475 }
Joe Drago332180d2020-06-15 16:55:16 -0700476 avifBoxMarker irot = avifRWStreamWriteBox(s, "irot", AVIF_BOX_SIZE_TBD);
477 uint8_t angle = imageMetadata->irot.angle & 0x3;
478 avifRWStreamWrite(s, &angle, 1); // unsigned int (6) reserved = 0; unsigned int (2) angle;
479 avifRWStreamFinishBox(s, irot);
Joe Drago5779fc42021-09-08 11:15:26 -0700480 if (dedup) {
481 ipmaPush(ipma, avifItemPropertyDedupFinish(dedup, outputStream), AVIF_TRUE);
Joe Drago332180d2020-06-15 16:55:16 -0700482 }
483 }
484 if (imageMetadata->transformFlags & AVIF_TRANSFORM_IMIR) {
Joe Drago5779fc42021-09-08 11:15:26 -0700485 if (dedup) {
486 avifItemPropertyDedupStart(dedup);
487 }
Joe Drago332180d2020-06-15 16:55:16 -0700488 avifBoxMarker imir = avifRWStreamWriteBox(s, "imir", AVIF_BOX_SIZE_TBD);
Joe Dragob551bb32021-06-03 15:22:05 -0700489 uint8_t mode = imageMetadata->imir.mode & 0x1;
490 avifRWStreamWrite(s, &mode, 1); // unsigned int (7) reserved = 0; unsigned int (1) mode;
Joe Drago332180d2020-06-15 16:55:16 -0700491 avifRWStreamFinishBox(s, imir);
Joe Drago5779fc42021-09-08 11:15:26 -0700492 if (dedup) {
493 ipmaPush(ipma, avifItemPropertyDedupFinish(dedup, outputStream), AVIF_TRUE);
Joe Drago332180d2020-06-15 16:55:16 -0700494 }
495 }
496}
497
Joe Dragoa72da5b2020-06-15 19:40:17 -0700498// Write unassociated metadata items (EXIF, XMP) to a small meta box inside of a trak box.
499// These items are implicitly associated with the track they are contained within.
500static void avifEncoderWriteTrackMetaBox(avifEncoder * encoder, avifRWStream * s)
501{
502 // Count how many non-av01 items (such as EXIF/XMP) are being written
503 uint32_t metadataItemCount = 0;
504 for (uint32_t itemIndex = 0; itemIndex < encoder->data->items.count; ++itemIndex) {
505 avifEncoderItem * item = &encoder->data->items.item[itemIndex];
506 if (memcmp(item->type, "av01", 4) != 0) {
507 ++metadataItemCount;
508 }
509 }
510 if (metadataItemCount == 0) {
511 // Don't even bother writing the trak meta box
512 return;
513 }
514
515 avifBoxMarker meta = avifRWStreamWriteFullBox(s, "meta", AVIF_BOX_SIZE_TBD, 0, 0);
516
517 avifBoxMarker hdlr = avifRWStreamWriteFullBox(s, "hdlr", AVIF_BOX_SIZE_TBD, 0, 0);
518 avifRWStreamWriteU32(s, 0); // unsigned int(32) pre_defined = 0;
519 avifRWStreamWriteChars(s, "pict", 4); // unsigned int(32) handler_type;
520 avifRWStreamWriteZeros(s, 12); // const unsigned int(32)[3] reserved = 0;
521 avifRWStreamWriteChars(s, "libavif", 8); // string name; (writing null terminator)
522 avifRWStreamFinishBox(s, hdlr);
523
524 avifBoxMarker iloc = avifRWStreamWriteFullBox(s, "iloc", AVIF_BOX_SIZE_TBD, 0, 0);
525 uint8_t offsetSizeAndLengthSize = (4 << 4) + (4 << 0); // unsigned int(4) offset_size;
526 // unsigned int(4) length_size;
527 avifRWStreamWrite(s, &offsetSizeAndLengthSize, 1); //
528 avifRWStreamWriteZeros(s, 1); // unsigned int(4) base_offset_size;
529 // unsigned int(4) reserved;
530 avifRWStreamWriteU16(s, (uint16_t)metadataItemCount); // unsigned int(16) item_count;
531 for (uint32_t trakItemIndex = 0; trakItemIndex < encoder->data->items.count; ++trakItemIndex) {
532 avifEncoderItem * item = &encoder->data->items.item[trakItemIndex];
533 if (memcmp(item->type, "av01", 4) == 0) {
Joe Drago25fe1272020-07-09 14:20:39 -0700534 // Skip over all non-metadata items
Joe Dragoa72da5b2020-06-15 19:40:17 -0700535 continue;
536 }
537
Joe Drago25fe1272020-07-09 14:20:39 -0700538 avifRWStreamWriteU16(s, item->id); // unsigned int(16) item_ID;
539 avifRWStreamWriteU16(s, 0); // unsigned int(16) data_reference_index;
540 avifRWStreamWriteU16(s, 1); // unsigned int(16) extent_count;
541 avifEncoderItemAddMdatFixup(item, s); //
542 avifRWStreamWriteU32(s, 0 /* set later */); // unsigned int(offset_size*8) extent_offset;
543 avifRWStreamWriteU32(s, (uint32_t)item->metadataPayload.size); // unsigned int(length_size*8) extent_length;
Joe Dragoa72da5b2020-06-15 19:40:17 -0700544 }
545 avifRWStreamFinishBox(s, iloc);
546
547 avifBoxMarker iinf = avifRWStreamWriteFullBox(s, "iinf", AVIF_BOX_SIZE_TBD, 0, 0);
548 avifRWStreamWriteU16(s, (uint16_t)metadataItemCount); // unsigned int(16) entry_count;
549 for (uint32_t trakItemIndex = 0; trakItemIndex < encoder->data->items.count; ++trakItemIndex) {
550 avifEncoderItem * item = &encoder->data->items.item[trakItemIndex];
551 if (memcmp(item->type, "av01", 4) == 0) {
552 continue;
553 }
554
Wan-Teh Chang0f418912022-02-17 09:16:05 -0800555 assert(!item->hiddenImage);
Joe Dragoa72da5b2020-06-15 19:40:17 -0700556 avifBoxMarker infe = avifRWStreamWriteFullBox(s, "infe", AVIF_BOX_SIZE_TBD, 2, 0);
557 avifRWStreamWriteU16(s, item->id); // unsigned int(16) item_ID;
558 avifRWStreamWriteU16(s, 0); // unsigned int(16) item_protection_index;
559 avifRWStreamWrite(s, item->type, 4); // unsigned int(32) item_type;
560 avifRWStreamWriteChars(s, item->infeName, item->infeNameSize); // string item_name; (writing null terminator)
561 if (item->infeContentType && item->infeContentTypeSize) { // string content_type; (writing null terminator)
562 avifRWStreamWriteChars(s, item->infeContentType, item->infeContentTypeSize);
563 }
564 avifRWStreamFinishBox(s, infe);
565 }
566 avifRWStreamFinishBox(s, iinf);
567
568 avifRWStreamFinishBox(s, meta);
569}
570
Wan-Teh Changf9fca862020-12-22 15:08:12 -0800571static void avifWriteGridPayload(avifRWData * data, uint32_t gridCols, uint32_t gridRows, const avifImage * firstCell)
Joe Dragoaec9cff2020-12-11 14:23:37 -0800572{
573 // ISO/IEC 23008-12 6.6.2.3.2
574 // aligned(8) class ImageGrid {
575 // unsigned int(8) version = 0;
576 // unsigned int(8) flags;
577 // FieldLength = ((flags & 1) + 1) * 16;
578 // unsigned int(8) rows_minus_one;
579 // unsigned int(8) columns_minus_one;
580 // unsigned int(FieldLength) output_width;
581 // unsigned int(FieldLength) output_height;
582 // }
583
584 uint32_t gridWidth = firstCell->width * gridCols;
585 uint32_t gridHeight = firstCell->height * gridRows;
586 uint8_t gridFlags = ((gridWidth > 65535) || (gridHeight > 65535)) ? 1 : 0;
587
588 avifRWStream s;
589 avifRWStreamStart(&s, data);
Wan-Teh Chang8adbb972020-12-18 14:12:51 -0800590 avifRWStreamWriteU8(&s, 0); // unsigned int(8) version = 0;
591 avifRWStreamWriteU8(&s, gridFlags); // unsigned int(8) flags;
592 avifRWStreamWriteU8(&s, (uint8_t)(gridRows - 1)); // unsigned int(8) rows_minus_one;
593 avifRWStreamWriteU8(&s, (uint8_t)(gridCols - 1)); // unsigned int(8) columns_minus_one;
Joe Dragoaec9cff2020-12-11 14:23:37 -0800594 if (gridFlags & 1) {
595 avifRWStreamWriteU32(&s, gridWidth); // unsigned int(FieldLength) output_width;
596 avifRWStreamWriteU32(&s, gridHeight); // unsigned int(FieldLength) output_height;
597 } else {
598 uint16_t tmpWidth = (uint16_t)gridWidth;
599 uint16_t tmpHeight = (uint16_t)gridHeight;
600 avifRWStreamWriteU16(&s, tmpWidth); // unsigned int(FieldLength) output_width;
601 avifRWStreamWriteU16(&s, tmpHeight); // unsigned int(FieldLength) output_height;
602 }
603 avifRWStreamFinishWrite(&s);
604}
605
Yannis Guyon4dbafcf2022-05-13 11:23:07 +0200606static avifResult avifEncoderDataCreateExifItem(avifEncoderData * data, const avifRWData * exif)
607{
608 // Validate Exif payload (if any) and find TIFF header offset
609 uint32_t exifTiffHeaderOffset = 0;
610 if (exif->size < 4) {
611 // Can't even fit the TIFF header, something is wrong
612 return AVIF_RESULT_INVALID_EXIF_PAYLOAD;
613 }
614
615 const uint8_t tiffHeaderBE[4] = { 'M', 'M', 0, 42 };
616 const uint8_t tiffHeaderLE[4] = { 'I', 'I', 42, 0 };
617 for (; exifTiffHeaderOffset < (exif->size - 4); ++exifTiffHeaderOffset) {
618 if (!memcmp(&exif->data[exifTiffHeaderOffset], tiffHeaderBE, sizeof(tiffHeaderBE))) {
619 break;
620 }
621 if (!memcmp(&exif->data[exifTiffHeaderOffset], tiffHeaderLE, sizeof(tiffHeaderLE))) {
622 break;
623 }
624 }
625
626 if (exifTiffHeaderOffset >= exif->size - 4) {
627 // Couldn't find the TIFF header
628 return AVIF_RESULT_INVALID_EXIF_PAYLOAD;
629 }
630
631 avifEncoderItem * exifItem = avifEncoderDataCreateItem(data, "Exif", "Exif", 5, 0);
632 if (!exifItem) {
633 return AVIF_RESULT_OUT_OF_MEMORY;
634 }
635 exifItem->irefToID = data->primaryItemID;
636 exifItem->irefType = "cdsc";
637
638 avifRWDataRealloc(&exifItem->metadataPayload, sizeof(uint32_t) + exif->size);
639 exifTiffHeaderOffset = avifHTONL(exifTiffHeaderOffset);
640 memcpy(exifItem->metadataPayload.data, &exifTiffHeaderOffset, sizeof(uint32_t));
641 memcpy(exifItem->metadataPayload.data + sizeof(uint32_t), exif->data, exif->size);
642 return AVIF_RESULT_OK;
643}
644
645static avifResult avifEncoderDataCreateXMPItem(avifEncoderData * data, const avifRWData * xmp)
646{
647 avifEncoderItem * xmpItem = avifEncoderDataCreateItem(data, "mime", "XMP", 4, 0);
648 if (!xmpItem) {
649 return AVIF_RESULT_OUT_OF_MEMORY;
650 }
651 xmpItem->irefToID = data->primaryItemID;
652 xmpItem->irefType = "cdsc";
653
654 xmpItem->infeContentType = xmpContentType;
655 xmpItem->infeContentTypeSize = xmpContentTypeSize;
656 avifRWDataSet(&xmpItem->metadataPayload, xmp->data, xmp->size);
657 return AVIF_RESULT_OK;
658}
659
Joe Dragoaec9cff2020-12-11 14:23:37 -0800660static avifResult avifEncoderAddImageInternal(avifEncoder * encoder,
Wan-Teh Changf9fca862020-12-22 15:08:12 -0800661 uint32_t gridCols,
662 uint32_t gridRows,
Wan-Teh Chang2b7f04e2020-12-14 09:51:01 -0800663 const avifImage * const * cellImages,
Joe Dragoaec9cff2020-12-11 14:23:37 -0800664 uint64_t durationInTimescales,
Joe Dragoe7c95e02021-05-06 12:48:55 -0700665 avifAddImageFlags addImageFlags)
Joe Drago444f0512019-01-23 17:03:24 -0800666{
Joe Drago250221a2020-06-01 11:11:06 -0700667 // -----------------------------------------------------------------------
Joe Dragoc534e702020-10-30 17:56:42 -0700668 // Verify encoding is possible
669
670 if (!avifCodecName(encoder->codecChoice, AVIF_CODEC_FLAG_CAN_ENCODE)) {
671 return AVIF_RESULT_NO_CODEC_AVAILABLE;
672 }
673
Wan-Teh Chang1cd92712022-08-19 18:01:16 -0700674 avifEncoderChanges encoderChanges;
675 if (!avifEncoderDetectChanges(encoder, &encoderChanges)) {
Yuan Tongf38cd852022-08-19 20:13:09 +0800676 return AVIF_RESULT_CANNOT_CHANGE_SETTING;
677 }
Wan-Teh Chang1cd92712022-08-19 18:01:16 -0700678 avifEncoderBackupSettings(encoder);
Yuan Tongf38cd852022-08-19 20:13:09 +0800679
Joe Dragoc534e702020-10-30 17:56:42 -0700680 // -----------------------------------------------------------------------
Joe Dragoaec9cff2020-12-11 14:23:37 -0800681 // Validate images
Joe Drago250221a2020-06-01 11:11:06 -0700682
Joe Dragoaec9cff2020-12-11 14:23:37 -0800683 const uint32_t cellCount = gridCols * gridRows;
684 if (cellCount == 0) {
685 return AVIF_RESULT_INVALID_ARGUMENT;
Joe Drago7ad3ad62019-02-07 11:17:34 -0800686 }
687
Joe Dragoaec9cff2020-12-11 14:23:37 -0800688 const avifImage * firstCell = cellImages[0];
Wan-Teh Chang0b7c20e2020-12-18 16:21:57 -0800689 if ((firstCell->depth != 8) && (firstCell->depth != 10) && (firstCell->depth != 12)) {
690 return AVIF_RESULT_UNSUPPORTED_DEPTH;
691 }
692
693 if (!firstCell->width || !firstCell->height) {
694 return AVIF_RESULT_NO_CONTENT;
695 }
696
Yannis Guyonbf28a922022-02-09 23:19:32 +0100697 if ((cellCount > 1) && !avifAreGridDimensionsValid(firstCell->yuvFormat,
698 gridCols * firstCell->width,
699 gridRows * firstCell->height,
700 firstCell->width,
701 firstCell->height,
702 &encoder->diag)) {
Joe Dragoaec9cff2020-12-11 14:23:37 -0800703 return AVIF_RESULT_INVALID_IMAGE_GRID;
Joe Drago444f0512019-01-23 17:03:24 -0800704 }
705
Joe Dragoaec9cff2020-12-11 14:23:37 -0800706 for (uint32_t cellIndex = 0; cellIndex < cellCount; ++cellIndex) {
707 const avifImage * cellImage = cellImages[cellIndex];
Yannis Guyonbf28a922022-02-09 23:19:32 +0100708 // HEIF (ISO 23008-12:2017), Section 6.6.2.3.1:
709 // All input images shall have exactly the same width and height; call those tile_width and tile_height.
710 // MIAF (ISO 23000-22:2019), Section 7.3.11.4.1:
711 // All input images of a grid image item shall use the same coding format, chroma sampling format, and the
712 // same decoder configuration (see 7.3.6.2).
713 if ((cellImage->width != firstCell->width) || (cellImage->height != firstCell->height) ||
714 (cellImage->depth != firstCell->depth) || (cellImage->yuvFormat != firstCell->yuvFormat) ||
715 (cellImage->yuvRange != firstCell->yuvRange) || (cellImage->colorPrimaries != firstCell->colorPrimaries) ||
716 (cellImage->transferCharacteristics != firstCell->transferCharacteristics) ||
Wan-Teh Chang4e0849d2022-04-29 21:12:59 -0700717 (cellImage->matrixCoefficients != firstCell->matrixCoefficients) || (!!cellImage->alphaPlane != !!firstCell->alphaPlane) ||
718 (cellImage->alphaPremultiplied != firstCell->alphaPremultiplied)) {
Joe Dragoaec9cff2020-12-11 14:23:37 -0800719 return AVIF_RESULT_INVALID_IMAGE_GRID;
720 }
721
Wan-Teh Chang0b7c20e2020-12-18 16:21:57 -0800722 if (!cellImage->yuvPlanes[AVIF_CHAN_Y]) {
Joe Dragoaec9cff2020-12-11 14:23:37 -0800723 return AVIF_RESULT_NO_CONTENT;
724 }
725
726 if (cellImage->yuvFormat == AVIF_PIXEL_FORMAT_NONE) {
727 return AVIF_RESULT_NO_YUV_FORMAT_SELECTED;
728 }
Joe Drago250221a2020-06-01 11:11:06 -0700729 }
730
731 // -----------------------------------------------------------------------
Joe Dragoaba51d82020-11-19 13:12:29 -0800732 // Validate flags
733
734 if (encoder->data->singleImage) {
735 // The previous call to avifEncoderAddImage() set AVIF_ADD_IMAGE_FLAG_SINGLE.
736 // avifEncoderAddImage() cannot be called again for this encode.
737 return AVIF_RESULT_ENCODE_COLOR_FAILED;
738 }
739
740 if (addImageFlags & AVIF_ADD_IMAGE_FLAG_SINGLE) {
741 encoder->data->singleImage = AVIF_TRUE;
742
743 if (encoder->data->items.count > 0) {
744 // AVIF_ADD_IMAGE_FLAG_SINGLE may only be set on the first and only image.
745 return AVIF_RESULT_INVALID_ARGUMENT;
746 }
747 }
748
749 // -----------------------------------------------------------------------
Joe Drago250221a2020-06-01 11:11:06 -0700750
Joe Drago4a25c192020-06-03 16:29:58 -0700751 if (durationInTimescales == 0) {
752 durationInTimescales = 1;
Joe Drago8210c1b2020-06-02 17:40:28 -0700753 }
754
Joe Drago250221a2020-06-01 11:11:06 -0700755 if (encoder->data->items.count == 0) {
756 // Make a copy of the first image's metadata (sans pixels) for future writing/validation
Yannis Guyon1ce971e2022-08-01 20:22:04 +0200757 const avifResult copyResult = avifImageCopy(encoder->data->imageMetadata, firstCell, 0);
758 if (copyResult != AVIF_RESULT_OK) {
759 return copyResult;
760 }
Joe Drago250221a2020-06-01 11:11:06 -0700761
762 // Prepare all AV1 items
763
Joe Dragoaec9cff2020-12-11 14:23:37 -0800764 uint16_t gridColorID = 0;
765 if (cellCount > 1) {
766 avifEncoderItem * gridColorItem = avifEncoderDataCreateItem(encoder->data, "grid", "Color", 6, 0);
767 avifWriteGridPayload(&gridColorItem->metadataPayload, gridCols, gridRows, firstCell);
768 gridColorItem->gridCols = gridCols;
769 gridColorItem->gridRows = gridRows;
Joe Drago250221a2020-06-01 11:11:06 -0700770
Joe Dragoaec9cff2020-12-11 14:23:37 -0800771 gridColorID = gridColorItem->id;
772 encoder->data->primaryItemID = gridColorID;
773 }
774
775 for (uint32_t cellIndex = 0; cellIndex < cellCount; ++cellIndex) {
776 avifEncoderItem * item = avifEncoderDataCreateItem(encoder->data, "av01", "Color", 6, cellIndex);
777 item->codec = avifCodecCreate(encoder->codecChoice, AVIF_CODEC_FLAG_CAN_ENCODE);
778 if (!item->codec) {
779 // Just bail out early, we're not surviving this function without an encoder compiled in
780 return AVIF_RESULT_NO_CODEC_AVAILABLE;
781 }
782 item->codec->csOptions = encoder->csOptions;
Joe Dragobfc5aca2021-05-17 12:00:39 -0700783 item->codec->diag = &encoder->diag;
Joe Dragoaec9cff2020-12-11 14:23:37 -0800784
Wan-Teh Chang0b7c20e2020-12-18 16:21:57 -0800785 if (cellCount > 1) {
Joe Dragoaec9cff2020-12-11 14:23:37 -0800786 item->dimgFromID = gridColorID;
Wan-Teh Chang0f418912022-02-17 09:16:05 -0800787 item->hiddenImage = AVIF_TRUE;
Wan-Teh Chang0b7c20e2020-12-18 16:21:57 -0800788 } else {
Joe Dragoaec9cff2020-12-11 14:23:37 -0800789 encoder->data->primaryItemID = item->id;
790 }
791 }
792
793 encoder->data->alphaPresent = (firstCell->alphaPlane != NULL);
Wan-Teh Chang0b7c20e2020-12-18 16:21:57 -0800794 if (encoder->data->alphaPresent && (addImageFlags & AVIF_ADD_IMAGE_FLAG_SINGLE)) {
Joe Drago96e09182020-07-09 15:32:47 -0700795 // If encoding a single image in which the alpha plane exists but is entirely opaque,
796 // simply skip writing an alpha AV1 payload entirely, as it'll be interpreted as opaque
797 // and is less bytes.
798 //
799 // However, if encoding an image sequence, the first frame's alpha plane being entirely
800 // opaque could be a false positive for removing the alpha AV1 payload, as it might simply
801 // be a fade out later in the sequence. This is why avifImageIsOpaque() is only called
802 // when encoding a single image.
803
Joe Dragoaec9cff2020-12-11 14:23:37 -0800804 encoder->data->alphaPresent = AVIF_FALSE;
805 for (uint32_t cellIndex = 0; cellIndex < cellCount; ++cellIndex) {
806 const avifImage * cellImage = cellImages[cellIndex];
807 if (!avifImageIsOpaque(cellImage)) {
808 encoder->data->alphaPresent = AVIF_TRUE;
809 break;
810 }
Joe Drago250221a2020-06-01 11:11:06 -0700811 }
Joe Dragoaec9cff2020-12-11 14:23:37 -0800812 }
813
814 if (encoder->data->alphaPresent) {
815 uint16_t gridAlphaID = 0;
816 if (cellCount > 1) {
817 avifEncoderItem * gridAlphaItem = avifEncoderDataCreateItem(encoder->data, "grid", "Alpha", 6, 0);
818 avifWriteGridPayload(&gridAlphaItem->metadataPayload, gridCols, gridRows, firstCell);
819 gridAlphaItem->alpha = AVIF_TRUE;
820 gridAlphaItem->irefToID = encoder->data->primaryItemID;
821 gridAlphaItem->irefType = "auxl";
Wan-Teh Chang69c32ef2020-12-18 12:16:42 -0800822 gridAlphaItem->gridCols = gridCols;
823 gridAlphaItem->gridRows = gridRows;
Joe Dragoaec9cff2020-12-11 14:23:37 -0800824 gridAlphaID = gridAlphaItem->id;
Yuan Tonge4850be2021-01-22 14:21:25 +0800825
826 if (encoder->data->imageMetadata->alphaPremultiplied) {
Joe Dragoceb2fa02021-01-29 18:14:55 -0800827 avifEncoderItem * primaryItem = avifEncoderDataFindItemByID(encoder->data, encoder->data->primaryItemID);
828 assert(primaryItem);
829 primaryItem->irefType = "prem";
830 primaryItem->irefToID = gridAlphaID;
Yuan Tonge4850be2021-01-22 14:21:25 +0800831 }
Joe Dragoaec9cff2020-12-11 14:23:37 -0800832 }
833
834 for (uint32_t cellIndex = 0; cellIndex < cellCount; ++cellIndex) {
Wan-Teh Chang69c32ef2020-12-18 12:16:42 -0800835 avifEncoderItem * item = avifEncoderDataCreateItem(encoder->data, "av01", "Alpha", 6, cellIndex);
Joe Dragoaec9cff2020-12-11 14:23:37 -0800836 item->codec = avifCodecCreate(encoder->codecChoice, AVIF_CODEC_FLAG_CAN_ENCODE);
837 if (!item->codec) {
838 return AVIF_RESULT_NO_CODEC_AVAILABLE;
839 }
840 item->codec->csOptions = encoder->csOptions;
Joe Dragobfc5aca2021-05-17 12:00:39 -0700841 item->codec->diag = &encoder->diag;
Joe Dragoaec9cff2020-12-11 14:23:37 -0800842 item->alpha = AVIF_TRUE;
843
Wan-Teh Chang0b7c20e2020-12-18 16:21:57 -0800844 if (cellCount > 1) {
Joe Dragoaec9cff2020-12-11 14:23:37 -0800845 item->dimgFromID = gridAlphaID;
Wan-Teh Chang0f418912022-02-17 09:16:05 -0800846 item->hiddenImage = AVIF_TRUE;
Joe Dragoaec9cff2020-12-11 14:23:37 -0800847 } else {
848 item->irefToID = encoder->data->primaryItemID;
849 item->irefType = "auxl";
Yuan Tonge4850be2021-01-22 14:21:25 +0800850
851 if (encoder->data->imageMetadata->alphaPremultiplied) {
Joe Dragoceb2fa02021-01-29 18:14:55 -0800852 avifEncoderItem * primaryItem = avifEncoderDataFindItemByID(encoder->data, encoder->data->primaryItemID);
853 assert(primaryItem);
854 primaryItem->irefType = "prem";
855 primaryItem->irefToID = item->id;
Yuan Tonge4850be2021-01-22 14:21:25 +0800856 }
Joe Dragoaec9cff2020-12-11 14:23:37 -0800857 }
858 }
Joe Drago250221a2020-06-01 11:11:06 -0700859 }
860
861 // -----------------------------------------------------------------------
862 // Create metadata items (Exif, XMP)
863
Joe Dragoaec9cff2020-12-11 14:23:37 -0800864 if (firstCell->exif.size > 0) {
Yannis Guyon4dbafcf2022-05-13 11:23:07 +0200865 const avifResult result = avifEncoderDataCreateExifItem(encoder->data, &firstCell->exif);
866 if (result != AVIF_RESULT_OK) {
867 return result;
Wan-Teh Changb139eed2020-07-08 18:04:14 -0700868 }
Joe Drago250221a2020-06-01 11:11:06 -0700869 }
870
Joe Dragoaec9cff2020-12-11 14:23:37 -0800871 if (firstCell->xmp.size > 0) {
Yannis Guyon4dbafcf2022-05-13 11:23:07 +0200872 const avifResult result = avifEncoderDataCreateXMPItem(encoder->data, &firstCell->xmp);
873 if (result != AVIF_RESULT_OK) {
874 return result;
875 }
Joe Drago250221a2020-06-01 11:11:06 -0700876 }
Joe Drago250221a2020-06-01 11:11:06 -0700877 } else {
878 // Another frame in an image sequence
Joe Drago96e09182020-07-09 15:32:47 -0700879
Yuan Tongf38cd852022-08-19 20:13:09 +0800880 const avifImage * imageMetadata = encoder->data->imageMetadata;
Wan-Teh Chang1cd92712022-08-19 18:01:16 -0700881 // If the first image in the sequence had an alpha plane (even if fully opaque), all
882 // subsequent images must have alpha as well.
Yuan Tong47df37c2022-08-26 12:10:37 +0800883 if ((imageMetadata->depth != firstCell->depth) || (imageMetadata->yuvFormat != firstCell->yuvFormat) ||
Yuan Tongf38cd852022-08-19 20:13:09 +0800884 (imageMetadata->yuvRange != firstCell->yuvRange) || (imageMetadata->colorPrimaries != firstCell->colorPrimaries) ||
885 (imageMetadata->transferCharacteristics != firstCell->transferCharacteristics) ||
886 (imageMetadata->matrixCoefficients != firstCell->matrixCoefficients) ||
Yuan Tongf38cd852022-08-19 20:13:09 +0800887 (imageMetadata->alphaPremultiplied != firstCell->alphaPremultiplied) ||
888 (encoder->data->alphaPresent && !firstCell->alphaPlane)) {
889 return AVIF_RESULT_INCOMPATIBLE_IMAGE;
Joe Drago96e09182020-07-09 15:32:47 -0700890 }
wantehchangcbfab6b2020-03-11 11:53:53 -0700891 }
892
Joe Drago444f0512019-01-23 17:03:24 -0800893 // -----------------------------------------------------------------------
894 // Encode AV1 OBUs
895
Joe Dragod6850242020-06-16 16:05:37 -0700896 if (encoder->keyframeInterval && ((encoder->data->frames.count % encoder->keyframeInterval) == 0)) {
897 addImageFlags |= AVIF_ADD_IMAGE_FLAG_FORCE_KEYFRAME;
Joe Drago3736df92020-06-16 15:39:55 -0700898 }
899
Joe Drago800b47f2020-03-18 16:22:37 -0700900 for (uint32_t itemIndex = 0; itemIndex < encoder->data->items.count; ++itemIndex) {
901 avifEncoderItem * item = &encoder->data->items.item[itemIndex];
Joe Drago250221a2020-06-01 11:11:06 -0700902 if (item->codec) {
Joe Dragoaec9cff2020-12-11 14:23:37 -0800903 const avifImage * cellImage = cellImages[item->cellIndex];
Joe Dragoe1097bd2020-08-27 18:55:03 -0700904 avifResult encodeResult =
Yuan Tong47df37c2022-08-26 12:10:37 +0800905 item->codec->encodeImage(item->codec, encoder, cellImage, item->alpha, encoderChanges, addImageFlags, item->encodeOutput);
Joe Dragoe1097bd2020-08-27 18:55:03 -0700906 if (encodeResult == AVIF_RESULT_UNKNOWN_ERROR) {
907 encodeResult = item->alpha ? AVIF_RESULT_ENCODE_ALPHA_FAILED : AVIF_RESULT_ENCODE_COLOR_FAILED;
908 }
909 if (encodeResult != AVIF_RESULT_OK) {
910 return encodeResult;
Joe Drago250221a2020-06-01 11:11:06 -0700911 }
912 }
913 }
Joe Dragob3fdc9e2020-06-01 12:33:16 -0700914
Yuan Tong47df37c2022-08-26 12:10:37 +0800915 avifCodecSpecificOptionsClear(encoder->csOptions);
Joe Drago8210c1b2020-06-02 17:40:28 -0700916 avifEncoderFrame * frame = (avifEncoderFrame *)avifArrayPushPtr(&encoder->data->frames);
Joe Drago4a25c192020-06-03 16:29:58 -0700917 frame->durationInTimescales = durationInTimescales;
Joe Drago250221a2020-06-01 11:11:06 -0700918 return AVIF_RESULT_OK;
919}
920
Joe Dragoe7c95e02021-05-06 12:48:55 -0700921avifResult avifEncoderAddImage(avifEncoder * encoder, const avifImage * image, uint64_t durationInTimescales, avifAddImageFlags addImageFlags)
Joe Dragoaec9cff2020-12-11 14:23:37 -0800922{
Joe Dragobfc5aca2021-05-17 12:00:39 -0700923 avifDiagnosticsClearError(&encoder->diag);
Joe Dragoaec9cff2020-12-11 14:23:37 -0800924 return avifEncoderAddImageInternal(encoder, 1, 1, &image, durationInTimescales, addImageFlags);
925}
926
Joe Dragoe7c95e02021-05-06 12:48:55 -0700927avifResult avifEncoderAddImageGrid(avifEncoder * encoder,
928 uint32_t gridCols,
929 uint32_t gridRows,
930 const avifImage * const * cellImages,
931 avifAddImageFlags addImageFlags)
Joe Dragoaec9cff2020-12-11 14:23:37 -0800932{
Joe Dragobfc5aca2021-05-17 12:00:39 -0700933 avifDiagnosticsClearError(&encoder->diag);
Wan-Teh Changf9fca862020-12-22 15:08:12 -0800934 if ((gridCols == 0) || (gridCols > 256) || (gridRows == 0) || (gridRows > 256)) {
935 return AVIF_RESULT_INVALID_IMAGE_GRID;
936 }
Joe Drago11d23592021-01-05 14:18:57 -0800937 return avifEncoderAddImageInternal(encoder, gridCols, gridRows, cellImages, 1, addImageFlags | AVIF_ADD_IMAGE_FLAG_SINGLE); // only single image grids are supported
Joe Dragoaec9cff2020-12-11 14:23:37 -0800938}
939
Wan-Teh Chang2b7f04e2020-12-14 09:51:01 -0800940static size_t avifEncoderFindExistingChunk(avifRWStream * s, size_t mdatStartOffset, const uint8_t * data, size_t size)
Joe Dragoaec9cff2020-12-11 14:23:37 -0800941{
942 const size_t mdatCurrentOffset = avifRWStreamOffset(s);
943 const size_t mdatSearchSize = mdatCurrentOffset - mdatStartOffset;
944 if (mdatSearchSize < size) {
945 return 0;
946 }
Wan-Teh Chang8adbb972020-12-18 14:12:51 -0800947 const size_t mdatEndSearchOffset = mdatCurrentOffset - size;
Joe Dragoaec9cff2020-12-11 14:23:37 -0800948 for (size_t searchOffset = mdatStartOffset; searchOffset <= mdatEndSearchOffset; ++searchOffset) {
949 if (!memcmp(data, &s->raw->data[searchOffset], size)) {
950 return searchOffset;
951 }
952 }
953 return 0;
954}
955
Joe Dragoc7e1d752020-06-15 19:46:50 -0700956avifResult avifEncoderFinish(avifEncoder * encoder, avifRWData * output)
Joe Drago250221a2020-06-01 11:11:06 -0700957{
Joe Dragobfc5aca2021-05-17 12:00:39 -0700958 avifDiagnosticsClearError(&encoder->diag);
Wan-Teh Changb139eed2020-07-08 18:04:14 -0700959 if (encoder->data->items.count == 0) {
Joe Drago250221a2020-06-01 11:11:06 -0700960 return AVIF_RESULT_NO_CONTENT;
961 }
962
963 // -----------------------------------------------------------------------
964 // Finish up AV1 encoding
965
966 for (uint32_t itemIndex = 0; itemIndex < encoder->data->items.count; ++itemIndex) {
967 avifEncoderItem * item = &encoder->data->items.item[itemIndex];
968 if (item->codec) {
Joe Drago5b596c42020-06-02 17:13:38 -0700969 if (!item->codec->encodeFinish(item->codec, item->encodeOutput)) {
Joe Dragob3fdc9e2020-06-01 12:33:16 -0700970 return item->alpha ? AVIF_RESULT_ENCODE_ALPHA_FAILED : AVIF_RESULT_ENCODE_COLOR_FAILED;
971 }
Joe Dragob3fdc9e2020-06-01 12:33:16 -0700972
Joe Drago8210c1b2020-06-02 17:40:28 -0700973 if (item->encodeOutput->samples.count != encoder->data->frames.count) {
Joe Drago250221a2020-06-01 11:11:06 -0700974 return item->alpha ? AVIF_RESULT_ENCODE_ALPHA_FAILED : AVIF_RESULT_ENCODE_COLOR_FAILED;
Joe Drago800b47f2020-03-18 16:22:37 -0700975 }
Joe Drago46ea0582019-07-22 15:55:47 -0700976 }
977 }
978
Joe Drago444f0512019-01-23 17:03:24 -0800979 // -----------------------------------------------------------------------
Joe Drago2172ed02020-11-04 18:04:44 -0800980 // Harvest av1C properties from AV1 sequence headers
981
982 for (uint32_t itemIndex = 0; itemIndex < encoder->data->items.count; ++itemIndex) {
983 avifEncoderItem * item = &encoder->data->items.item[itemIndex];
984 if (item->encodeOutput->samples.count > 0) {
Wan-Teh Chang9fbcb9b2020-11-06 18:48:58 -0800985 const avifEncodeSample * firstSample = &item->encodeOutput->samples.sample[0];
Joe Drago2172ed02020-11-04 18:04:44 -0800986 avifSequenceHeader sequenceHeader;
987 if (avifSequenceHeaderParse(&sequenceHeader, (const avifROData *)&firstSample->data)) {
Wan-Teh Chang21961f42022-03-14 16:25:44 -0700988 item->av1C = sequenceHeader.av1C;
Joe Drago2172ed02020-11-04 18:04:44 -0800989 } else {
990 // This must be an invalid AV1 payload
991 return item->alpha ? AVIF_RESULT_ENCODE_ALPHA_FAILED : AVIF_RESULT_ENCODE_COLOR_FAILED;
992 }
993 }
994 }
995
996 // -----------------------------------------------------------------------
Joe Drago250221a2020-06-01 11:11:06 -0700997 // Begin write stream
998
Wan-Teh Changb11ae432020-07-08 18:25:24 -0700999 const avifImage * imageMetadata = encoder->data->imageMetadata;
1000 // The epoch for creation_time and modification_time is midnight, Jan. 1,
1001 // 1904, in UTC time. Add the number of seconds between that epoch and the
1002 // Unix epoch.
1003 uint64_t now = (uint64_t)time(NULL) + 2082844800;
Joe Drago250221a2020-06-01 11:11:06 -07001004
1005 avifRWStream s;
1006 avifRWStreamStart(&s, output);
1007
1008 // -----------------------------------------------------------------------
Joe Drago444f0512019-01-23 17:03:24 -08001009 // Write ftyp
1010
Joe Drago4a25c192020-06-03 16:29:58 -07001011 const char * majorBrand = "avif";
1012 if (encoder->data->frames.count > 1) {
1013 majorBrand = "avis";
1014 }
1015
1016 avifBoxMarker ftyp = avifRWStreamWriteBox(&s, "ftyp", AVIF_BOX_SIZE_TBD);
1017 avifRWStreamWriteChars(&s, majorBrand, 4); // unsigned int(32) major_brand;
Joe Drago250221a2020-06-01 11:11:06 -07001018 avifRWStreamWriteU32(&s, 0); // unsigned int(32) minor_version;
1019 avifRWStreamWriteChars(&s, "avif", 4); // unsigned int(32) compatible_brands[];
Joe Drago4a25c192020-06-03 16:29:58 -07001020 if (encoder->data->frames.count > 1) { //
1021 avifRWStreamWriteChars(&s, "avis", 4); // ... compatible_brands[]
Joe Drago70f5a2e2020-06-03 17:54:19 -07001022 avifRWStreamWriteChars(&s, "msf1", 4); // ... compatible_brands[]
Vignesh Venkatasubramanian0cf13332022-02-17 14:06:50 -08001023 avifRWStreamWriteChars(&s, "iso8", 4); // ... compatible_brands[]
Joe Drago4a25c192020-06-03 16:29:58 -07001024 } //
Joe Drago250221a2020-06-01 11:11:06 -07001025 avifRWStreamWriteChars(&s, "mif1", 4); // ... compatible_brands[]
1026 avifRWStreamWriteChars(&s, "miaf", 4); // ... compatible_brands[]
1027 if ((imageMetadata->depth == 8) || (imageMetadata->depth == 10)) { //
1028 if (imageMetadata->yuvFormat == AVIF_PIXEL_FORMAT_YUV420) { //
1029 avifRWStreamWriteChars(&s, "MA1B", 4); // ... compatible_brands[]
1030 } else if (imageMetadata->yuvFormat == AVIF_PIXEL_FORMAT_YUV444) { //
1031 avifRWStreamWriteChars(&s, "MA1A", 4); // ... compatible_brands[]
Joe Dragoeb652d82019-04-23 16:29:07 -07001032 }
1033 }
Joe Drago345aaa12019-09-25 13:42:12 -07001034 avifRWStreamFinishBox(&s, ftyp);
Joe Drago444f0512019-01-23 17:03:24 -08001035
1036 // -----------------------------------------------------------------------
Joe Drago444f0512019-01-23 17:03:24 -08001037 // Start meta
1038
Joe Drago4a25c192020-06-03 16:29:58 -07001039 avifBoxMarker meta = avifRWStreamWriteFullBox(&s, "meta", AVIF_BOX_SIZE_TBD, 0, 0);
Joe Drago444f0512019-01-23 17:03:24 -08001040
1041 // -----------------------------------------------------------------------
1042 // Write hdlr
1043
Joe Drago4a25c192020-06-03 16:29:58 -07001044 avifBoxMarker hdlr = avifRWStreamWriteFullBox(&s, "hdlr", AVIF_BOX_SIZE_TBD, 0, 0);
Joe Drago345aaa12019-09-25 13:42:12 -07001045 avifRWStreamWriteU32(&s, 0); // unsigned int(32) pre_defined = 0;
1046 avifRWStreamWriteChars(&s, "pict", 4); // unsigned int(32) handler_type;
1047 avifRWStreamWriteZeros(&s, 12); // const unsigned int(32)[3] reserved = 0;
1048 avifRWStreamWriteChars(&s, "libavif", 8); // string name; (writing null terminator)
1049 avifRWStreamFinishBox(&s, hdlr);
Joe Drago444f0512019-01-23 17:03:24 -08001050
1051 // -----------------------------------------------------------------------
1052 // Write pitm
1053
Joe Drago800b47f2020-03-18 16:22:37 -07001054 if (encoder->data->primaryItemID != 0) {
Joe Drago4a25c192020-06-03 16:29:58 -07001055 avifRWStreamWriteFullBox(&s, "pitm", sizeof(uint16_t), 0, 0);
Joe Drago800b47f2020-03-18 16:22:37 -07001056 avifRWStreamWriteU16(&s, encoder->data->primaryItemID); // unsigned int(16) item_ID;
Joe Dragof6a42272019-11-21 15:21:41 -08001057 }
1058
1059 // -----------------------------------------------------------------------
Joe Drago444f0512019-01-23 17:03:24 -08001060 // Write iloc
1061
Joe Drago4a25c192020-06-03 16:29:58 -07001062 avifBoxMarker iloc = avifRWStreamWriteFullBox(&s, "iloc", AVIF_BOX_SIZE_TBD, 0, 0);
Joe Drago444f0512019-01-23 17:03:24 -08001063
Joe Drago800b47f2020-03-18 16:22:37 -07001064 uint8_t offsetSizeAndLengthSize = (4 << 4) + (4 << 0); // unsigned int(4) offset_size;
1065 // unsigned int(4) length_size;
1066 avifRWStreamWrite(&s, &offsetSizeAndLengthSize, 1); //
1067 avifRWStreamWriteZeros(&s, 1); // unsigned int(4) base_offset_size;
1068 // unsigned int(4) reserved;
1069 avifRWStreamWriteU16(&s, (uint16_t)encoder->data->items.count); // unsigned int(16) item_count;
Joe Drago444f0512019-01-23 17:03:24 -08001070
Joe Drago800b47f2020-03-18 16:22:37 -07001071 for (uint32_t itemIndex = 0; itemIndex < encoder->data->items.count; ++itemIndex) {
1072 avifEncoderItem * item = &encoder->data->items.item[itemIndex];
Joe Drago2c4e7142020-06-03 10:31:46 -07001073
Joe Drago25fe1272020-07-09 14:20:39 -07001074 uint32_t contentSize = (uint32_t)item->metadataPayload.size;
Joe Drago2c4e7142020-06-03 10:31:46 -07001075 if (item->encodeOutput->samples.count > 0) {
Joe Drago25fe1272020-07-09 14:20:39 -07001076 // This is choosing sample 0's size as there are two cases here:
Joe Dragod6aa99c2020-07-09 14:27:46 -07001077 // * This is a single image, in which case this is correct
Joe Drago25fe1272020-07-09 14:20:39 -07001078 // * This is an image sequence, but this file should still be a valid single-image avif,
1079 // so there must still be a primary item pointing at a sync sample. Since the first
1080 // frame of the image sequence is guaranteed to be a sync sample, it is chosen here.
1081 //
Joe Drago746325a2020-07-09 14:22:55 -07001082 // TODO: Offer the ability for a user to specify which frame in the sequence should
Joe Drago25fe1272020-07-09 14:20:39 -07001083 // become the primary item's image, and force that frame to be a keyframe.
Joe Drago2c4e7142020-06-03 10:31:46 -07001084 contentSize = (uint32_t)item->encodeOutput->samples.sample[0].data.size;
1085 }
1086
1087 avifRWStreamWriteU16(&s, item->id); // unsigned int(16) item_ID;
1088 avifRWStreamWriteU16(&s, 0); // unsigned int(16) data_reference_index;
1089 avifRWStreamWriteU16(&s, 1); // unsigned int(16) extent_count;
Joe Dragoa72da5b2020-06-15 19:40:17 -07001090 avifEncoderItemAddMdatFixup(item, &s); //
Joe Drago2c4e7142020-06-03 10:31:46 -07001091 avifRWStreamWriteU32(&s, 0 /* set later */); // unsigned int(offset_size*8) extent_offset;
1092 avifRWStreamWriteU32(&s, (uint32_t)contentSize); // unsigned int(length_size*8) extent_length;
Joe Dragof6a42272019-11-21 15:21:41 -08001093 }
1094
Joe Drago345aaa12019-09-25 13:42:12 -07001095 avifRWStreamFinishBox(&s, iloc);
Joe Drago444f0512019-01-23 17:03:24 -08001096
1097 // -----------------------------------------------------------------------
1098 // Write iinf
1099
Joe Drago4a25c192020-06-03 16:29:58 -07001100 avifBoxMarker iinf = avifRWStreamWriteFullBox(&s, "iinf", AVIF_BOX_SIZE_TBD, 0, 0);
Joe Drago800b47f2020-03-18 16:22:37 -07001101 avifRWStreamWriteU16(&s, (uint16_t)encoder->data->items.count); // unsigned int(16) entry_count;
Joe Drago444f0512019-01-23 17:03:24 -08001102
Joe Drago800b47f2020-03-18 16:22:37 -07001103 for (uint32_t itemIndex = 0; itemIndex < encoder->data->items.count; ++itemIndex) {
1104 avifEncoderItem * item = &encoder->data->items.item[itemIndex];
1105
Wan-Teh Chang0f418912022-02-17 09:16:05 -08001106 uint32_t flags = item->hiddenImage ? 1 : 0;
1107 avifBoxMarker infe = avifRWStreamWriteFullBox(&s, "infe", AVIF_BOX_SIZE_TBD, 2, flags);
Joe Drago800b47f2020-03-18 16:22:37 -07001108 avifRWStreamWriteU16(&s, item->id); // unsigned int(16) item_ID;
Joe Dragof6a42272019-11-21 15:21:41 -08001109 avifRWStreamWriteU16(&s, 0); // unsigned int(16) item_protection_index;
Joe Drago800b47f2020-03-18 16:22:37 -07001110 avifRWStreamWrite(&s, item->type, 4); // unsigned int(32) item_type;
1111 avifRWStreamWriteChars(&s, item->infeName, item->infeNameSize); // string item_name; (writing null terminator)
1112 if (item->infeContentType && item->infeContentTypeSize) { // string content_type; (writing null terminator)
1113 avifRWStreamWriteChars(&s, item->infeContentType, item->infeContentTypeSize);
1114 }
1115 avifRWStreamFinishBox(&s, infe);
Joe Drago444f0512019-01-23 17:03:24 -08001116 }
Joe Drago800b47f2020-03-18 16:22:37 -07001117
Joe Drago345aaa12019-09-25 13:42:12 -07001118 avifRWStreamFinishBox(&s, iinf);
Joe Drago444f0512019-01-23 17:03:24 -08001119
1120 // -----------------------------------------------------------------------
Joe Drago800b47f2020-03-18 16:22:37 -07001121 // Write iref boxes
Joe Drago8f7a3002019-02-07 19:35:37 -08001122
Joe Drago3818cf42020-07-31 13:08:37 -07001123 avifBoxMarker iref = 0;
Joe Drago800b47f2020-03-18 16:22:37 -07001124 for (uint32_t itemIndex = 0; itemIndex < encoder->data->items.count; ++itemIndex) {
1125 avifEncoderItem * item = &encoder->data->items.item[itemIndex];
Joe Dragoaec9cff2020-12-11 14:23:37 -08001126
1127 // Count how many other items refer to this item with dimgFromID
1128 uint16_t dimgCount = 0;
1129 for (uint32_t dimgIndex = 0; dimgIndex < encoder->data->items.count; ++dimgIndex) {
1130 avifEncoderItem * dimgItem = &encoder->data->items.item[dimgIndex];
1131 if (dimgItem->dimgFromID == item->id) {
1132 ++dimgCount;
1133 }
1134 }
1135
1136 if (dimgCount > 0) {
1137 if (!iref) {
1138 iref = avifRWStreamWriteFullBox(&s, "iref", AVIF_BOX_SIZE_TBD, 0, 0);
1139 }
1140 avifBoxMarker refType = avifRWStreamWriteBox(&s, "dimg", AVIF_BOX_SIZE_TBD);
1141 avifRWStreamWriteU16(&s, item->id); // unsigned int(16) from_item_ID;
1142 avifRWStreamWriteU16(&s, dimgCount); // unsigned int(16) reference_count;
1143 for (uint32_t dimgIndex = 0; dimgIndex < encoder->data->items.count; ++dimgIndex) {
1144 avifEncoderItem * dimgItem = &encoder->data->items.item[dimgIndex];
1145 if (dimgItem->dimgFromID == item->id) {
1146 avifRWStreamWriteU16(&s, dimgItem->id); // unsigned int(16) to_item_ID;
1147 }
1148 }
1149 avifRWStreamFinishBox(&s, refType);
1150 }
1151
Joe Drago800b47f2020-03-18 16:22:37 -07001152 if (item->irefToID != 0) {
Joe Drago3818cf42020-07-31 13:08:37 -07001153 if (!iref) {
1154 iref = avifRWStreamWriteFullBox(&s, "iref", AVIF_BOX_SIZE_TBD, 0, 0);
1155 }
Joe Drago4a25c192020-06-03 16:29:58 -07001156 avifBoxMarker refType = avifRWStreamWriteBox(&s, item->irefType, AVIF_BOX_SIZE_TBD);
Joe Drago800b47f2020-03-18 16:22:37 -07001157 avifRWStreamWriteU16(&s, item->id); // unsigned int(16) from_item_ID;
1158 avifRWStreamWriteU16(&s, 1); // unsigned int(16) reference_count;
1159 avifRWStreamWriteU16(&s, item->irefToID); // unsigned int(16) to_item_ID;
1160 avifRWStreamFinishBox(&s, refType);
Joe Drago800b47f2020-03-18 16:22:37 -07001161 }
Joe Drago8f7a3002019-02-07 19:35:37 -08001162 }
Joe Drago3818cf42020-07-31 13:08:37 -07001163 if (iref) {
1164 avifRWStreamFinishBox(&s, iref);
1165 }
Joe Drago8f7a3002019-02-07 19:35:37 -08001166
1167 // -----------------------------------------------------------------------
Joe Drago800b47f2020-03-18 16:22:37 -07001168 // Write iprp -> ipco/ipma
Joe Drago444f0512019-01-23 17:03:24 -08001169
Joe Drago4a25c192020-06-03 16:29:58 -07001170 avifBoxMarker iprp = avifRWStreamWriteBox(&s, "iprp", AVIF_BOX_SIZE_TBD);
Joe Drago224b0182019-02-08 10:42:29 -08001171
Joe Drago5779fc42021-09-08 11:15:26 -07001172 avifItemPropertyDedup * dedup = avifItemPropertyDedupCreate();
Joe Drago4a25c192020-06-03 16:29:58 -07001173 avifBoxMarker ipco = avifRWStreamWriteBox(&s, "ipco", AVIF_BOX_SIZE_TBD);
Joe Drago800b47f2020-03-18 16:22:37 -07001174 for (uint32_t itemIndex = 0; itemIndex < encoder->data->items.count; ++itemIndex) {
1175 avifEncoderItem * item = &encoder->data->items.item[itemIndex];
Joe Dragoaec9cff2020-12-11 14:23:37 -08001176 const avifBool isGrid = (item->gridCols > 0);
Joe Drago800b47f2020-03-18 16:22:37 -07001177 memset(&item->ipma, 0, sizeof(item->ipma));
Joe Dragoaec9cff2020-12-11 14:23:37 -08001178 if (!item->codec && !isGrid) {
Joe Drago800b47f2020-03-18 16:22:37 -07001179 // No ipma to write for this item
1180 continue;
1181 }
Joe Dragoe4dba562019-02-07 21:52:32 -08001182
Joe Dragoaec9cff2020-12-11 14:23:37 -08001183 if (item->dimgFromID) {
1184 // All image cells from a grid should share the exact same properties, so see if we've
1185 // already written properties out for another cell in this grid, and if so, just steal
1186 // their ipma and move on. This is a sneaky way to provide iprp deduplication.
1187
1188 avifBool foundPreviousCell = AVIF_FALSE;
1189 for (uint32_t dedupIndex = 0; dedupIndex < itemIndex; ++dedupIndex) {
1190 avifEncoderItem * dedupItem = &encoder->data->items.item[dedupIndex];
1191 if (item->dimgFromID == dedupItem->dimgFromID) {
1192 // We've already written dedup's items out. Steal their ipma indices and move on!
Wan-Teh Chang21961f42022-03-14 16:25:44 -07001193 item->ipma = dedupItem->ipma;
Joe Dragoaec9cff2020-12-11 14:23:37 -08001194 foundPreviousCell = AVIF_TRUE;
1195 break;
1196 }
1197 }
1198 if (foundPreviousCell) {
1199 continue;
1200 }
1201 }
1202
Wan-Teh Chang8adbb972020-12-18 14:12:51 -08001203 uint32_t imageWidth = imageMetadata->width;
1204 uint32_t imageHeight = imageMetadata->height;
Joe Dragoaec9cff2020-12-11 14:23:37 -08001205 if (isGrid) {
1206 imageWidth = imageMetadata->width * item->gridCols;
1207 imageHeight = imageMetadata->height * item->gridRows;
1208 }
1209
Joe Drago800b47f2020-03-18 16:22:37 -07001210 // Properties all av01 items need
1211
Joe Drago5779fc42021-09-08 11:15:26 -07001212 avifItemPropertyDedupStart(dedup);
1213 avifBoxMarker ispe = avifRWStreamWriteFullBox(&dedup->s, "ispe", AVIF_BOX_SIZE_TBD, 0, 0);
1214 avifRWStreamWriteU32(&dedup->s, imageWidth); // unsigned int(32) image_width;
1215 avifRWStreamWriteU32(&dedup->s, imageHeight); // unsigned int(32) image_height;
1216 avifRWStreamFinishBox(&dedup->s, ispe);
1217 ipmaPush(&item->ipma, avifItemPropertyDedupFinish(dedup, &s), AVIF_FALSE);
Joe Drago800b47f2020-03-18 16:22:37 -07001218
Joe Drago5779fc42021-09-08 11:15:26 -07001219 avifItemPropertyDedupStart(dedup);
Joe Drago332180d2020-06-15 16:55:16 -07001220 uint8_t channelCount = (item->alpha || (imageMetadata->yuvFormat == AVIF_PIXEL_FORMAT_YUV400)) ? 1 : 3;
Joe Drago5779fc42021-09-08 11:15:26 -07001221 avifBoxMarker pixi = avifRWStreamWriteFullBox(&dedup->s, "pixi", AVIF_BOX_SIZE_TBD, 0, 0);
1222 avifRWStreamWriteU8(&dedup->s, channelCount); // unsigned int (8) num_channels;
Joe Drago800b47f2020-03-18 16:22:37 -07001223 for (uint8_t chan = 0; chan < channelCount; ++chan) {
Joe Drago5779fc42021-09-08 11:15:26 -07001224 avifRWStreamWriteU8(&dedup->s, (uint8_t)imageMetadata->depth); // unsigned int (8) bits_per_channel;
Joe Drago800b47f2020-03-18 16:22:37 -07001225 }
Joe Drago5779fc42021-09-08 11:15:26 -07001226 avifRWStreamFinishBox(&dedup->s, pixi);
1227 ipmaPush(&item->ipma, avifItemPropertyDedupFinish(dedup, &s), AVIF_FALSE);
Joe Drago800b47f2020-03-18 16:22:37 -07001228
Joe Dragoaec9cff2020-12-11 14:23:37 -08001229 if (item->codec) {
Joe Drago5779fc42021-09-08 11:15:26 -07001230 avifItemPropertyDedupStart(dedup);
1231 writeConfigBox(&dedup->s, &item->av1C);
1232 ipmaPush(&item->ipma, avifItemPropertyDedupFinish(dedup, &s), AVIF_TRUE);
Joe Dragoaec9cff2020-12-11 14:23:37 -08001233 }
Joe Drago800b47f2020-03-18 16:22:37 -07001234
1235 if (item->alpha) {
1236 // Alpha specific properties
1237
Joe Drago5779fc42021-09-08 11:15:26 -07001238 avifItemPropertyDedupStart(dedup);
1239 avifBoxMarker auxC = avifRWStreamWriteFullBox(&dedup->s, "auxC", AVIF_BOX_SIZE_TBD, 0, 0);
1240 avifRWStreamWriteChars(&dedup->s, alphaURN, alphaURNSize); // string aux_type;
1241 avifRWStreamFinishBox(&dedup->s, auxC);
1242 ipmaPush(&item->ipma, avifItemPropertyDedupFinish(dedup, &s), AVIF_FALSE);
Joe Drago800b47f2020-03-18 16:22:37 -07001243 } else {
1244 // Color specific properties
1245
Joe Drago5779fc42021-09-08 11:15:26 -07001246 avifEncoderWriteColorProperties(&s, imageMetadata, &item->ipma, dedup);
Joe Dragoe4dba562019-02-07 21:52:32 -08001247 }
Joe Dragoe4dba562019-02-07 21:52:32 -08001248 }
Joe Drago800b47f2020-03-18 16:22:37 -07001249 avifRWStreamFinishBox(&s, ipco);
Joe Drago5779fc42021-09-08 11:15:26 -07001250 avifItemPropertyDedupDestroy(dedup);
1251 dedup = NULL;
Joe Drago800b47f2020-03-18 16:22:37 -07001252
Joe Drago4a25c192020-06-03 16:29:58 -07001253 avifBoxMarker ipma = avifRWStreamWriteFullBox(&s, "ipma", AVIF_BOX_SIZE_TBD, 0, 0);
Joe Drago800b47f2020-03-18 16:22:37 -07001254 {
1255 int ipmaCount = 0;
1256 for (uint32_t itemIndex = 0; itemIndex < encoder->data->items.count; ++itemIndex) {
1257 avifEncoderItem * item = &encoder->data->items.item[itemIndex];
1258 if (item->ipma.count > 0) {
1259 ++ipmaCount;
1260 }
1261 }
1262 avifRWStreamWriteU32(&s, ipmaCount); // unsigned int(32) entry_count;
1263
1264 for (uint32_t itemIndex = 0; itemIndex < encoder->data->items.count; ++itemIndex) {
1265 avifEncoderItem * item = &encoder->data->items.item[itemIndex];
1266 if (item->ipma.count == 0) {
1267 continue;
1268 }
1269
Joe Drago0da29972020-04-23 11:55:10 -07001270 avifRWStreamWriteU16(&s, item->id); // unsigned int(16) item_ID;
1271 avifRWStreamWriteU8(&s, item->ipma.count); // unsigned int(8) association_count;
1272 for (int i = 0; i < item->ipma.count; ++i) { //
1273 uint8_t essentialAndIndex = item->ipma.associations[i];
1274 if (item->ipma.essential[i]) {
1275 essentialAndIndex |= 0x80;
1276 }
1277 avifRWStreamWriteU8(&s, essentialAndIndex); // bit(1) essential; unsigned int(7) property_index;
Joe Drago800b47f2020-03-18 16:22:37 -07001278 }
1279 }
1280 }
1281 avifRWStreamFinishBox(&s, ipma);
1282
Joe Drago345aaa12019-09-25 13:42:12 -07001283 avifRWStreamFinishBox(&s, iprp);
Joe Drago444f0512019-01-23 17:03:24 -08001284
1285 // -----------------------------------------------------------------------
Joe Dragoeb652d82019-04-23 16:29:07 -07001286 // Finish meta box
Joe Drago444f0512019-01-23 17:03:24 -08001287
Joe Drago345aaa12019-09-25 13:42:12 -07001288 avifRWStreamFinishBox(&s, meta);
Joe Dragoeb652d82019-04-23 16:29:07 -07001289
1290 // -----------------------------------------------------------------------
Joe Drago4a25c192020-06-03 16:29:58 -07001291 // Write tracks (if an image sequence)
Joe Drago2c4e7142020-06-03 10:31:46 -07001292
1293 if (encoder->data->frames.count > 1) {
Vignesh Venkatasubramaniane234f672022-02-16 11:04:52 -08001294 static const uint8_t unityMatrix[9][4] = {
1295 /* clang-format off */
1296 { 0x00, 0x01, 0x00, 0x00 },
1297 { 0 },
1298 { 0 },
1299 { 0 },
1300 { 0x00, 0x01, 0x00, 0x00 },
1301 { 0 },
1302 { 0 },
1303 { 0 },
1304 { 0x40, 0x00, 0x00, 0x00 }
1305 /* clang-format on */
1306 };
Joe Drago4a25c192020-06-03 16:29:58 -07001307
1308 uint64_t durationInTimescales = 0;
1309 for (uint32_t frameIndex = 0; frameIndex < encoder->data->frames.count; ++frameIndex) {
Wan-Teh Changb139eed2020-07-08 18:04:14 -07001310 const avifEncoderFrame * frame = &encoder->data->frames.frame[frameIndex];
Joe Drago4a25c192020-06-03 16:29:58 -07001311 durationInTimescales += frame->durationInTimescales;
1312 }
1313
1314 // -------------------------------------------------------------------
1315 // Start moov
1316
1317 avifBoxMarker moov = avifRWStreamWriteBox(&s, "moov", AVIF_BOX_SIZE_TBD);
1318
1319 avifBoxMarker mvhd = avifRWStreamWriteFullBox(&s, "mvhd", AVIF_BOX_SIZE_TBD, 1, 0);
1320 avifRWStreamWriteU64(&s, now); // unsigned int(64) creation_time;
1321 avifRWStreamWriteU64(&s, now); // unsigned int(64) modification_time;
1322 avifRWStreamWriteU32(&s, (uint32_t)encoder->timescale); // unsigned int(32) timescale;
1323 avifRWStreamWriteU64(&s, durationInTimescales); // unsigned int(64) duration;
1324 avifRWStreamWriteU32(&s, 0x00010000); // template int(32) rate = 0x00010000; // typically 1.0
1325 avifRWStreamWriteU16(&s, 0x0100); // template int(16) volume = 0x0100; // typically, full volume
1326 avifRWStreamWriteU16(&s, 0); // const bit(16) reserved = 0;
1327 avifRWStreamWriteZeros(&s, 8); // const unsigned int(32)[2] reserved = 0;
Wan-Teh Chang8557cd62020-07-08 18:42:10 -07001328 avifRWStreamWrite(&s, unityMatrix, sizeof(unityMatrix));
Joe Drago4a25c192020-06-03 16:29:58 -07001329 avifRWStreamWriteZeros(&s, 24); // bit(32)[6] pre_defined = 0;
1330 avifRWStreamWriteU32(&s, encoder->data->items.count); // unsigned int(32) next_track_ID;
1331 avifRWStreamFinishBox(&s, mvhd);
1332
1333 // -------------------------------------------------------------------
1334 // Write tracks
1335
1336 for (uint32_t itemIndex = 0; itemIndex < encoder->data->items.count; ++itemIndex) {
1337 avifEncoderItem * item = &encoder->data->items.item[itemIndex];
1338 if (item->encodeOutput->samples.count == 0) {
1339 continue;
1340 }
1341
1342 uint32_t syncSamplesCount = 0;
1343 for (uint32_t sampleIndex = 0; sampleIndex < item->encodeOutput->samples.count; ++sampleIndex) {
1344 avifEncodeSample * sample = &item->encodeOutput->samples.sample[sampleIndex];
1345 if (sample->sync) {
1346 ++syncSamplesCount;
1347 }
1348 }
1349
1350 avifBoxMarker trak = avifRWStreamWriteBox(&s, "trak", AVIF_BOX_SIZE_TBD);
1351
Joe Drago85e387a2020-06-03 17:12:53 -07001352 avifBoxMarker tkhd = avifRWStreamWriteFullBox(&s, "tkhd", AVIF_BOX_SIZE_TBD, 1, 1);
Joe Drago4a25c192020-06-03 16:29:58 -07001353 avifRWStreamWriteU64(&s, now); // unsigned int(64) creation_time;
1354 avifRWStreamWriteU64(&s, now); // unsigned int(64) modification_time;
1355 avifRWStreamWriteU32(&s, itemIndex + 1); // unsigned int(32) track_ID;
1356 avifRWStreamWriteU32(&s, 0); // const unsigned int(32) reserved = 0;
1357 avifRWStreamWriteU64(&s, durationInTimescales); // unsigned int(64) duration;
1358 avifRWStreamWriteZeros(&s, sizeof(uint32_t) * 2); // const unsigned int(32)[2] reserved = 0;
1359 avifRWStreamWriteU16(&s, 0); // template int(16) layer = 0;
1360 avifRWStreamWriteU16(&s, 0); // template int(16) alternate_group = 0;
1361 avifRWStreamWriteU16(&s, 0); // template int(16) volume = {if track_is_audio 0x0100 else 0};
1362 avifRWStreamWriteU16(&s, 0); // const unsigned int(16) reserved = 0;
Wan-Teh Chang8557cd62020-07-08 18:42:10 -07001363 avifRWStreamWrite(&s, unityMatrix, sizeof(unityMatrix)); // template int(32)[9] matrix= // { 0x00010000,0,0,0,0x00010000,0,0,0,0x40000000 };
Joe Drago4a25c192020-06-03 16:29:58 -07001364 avifRWStreamWriteU32(&s, imageMetadata->width << 16); // unsigned int(32) width;
1365 avifRWStreamWriteU32(&s, imageMetadata->height << 16); // unsigned int(32) height;
1366 avifRWStreamFinishBox(&s, tkhd);
1367
1368 if (item->irefToID != 0) {
1369 avifBoxMarker tref = avifRWStreamWriteBox(&s, "tref", AVIF_BOX_SIZE_TBD);
1370 avifBoxMarker refType = avifRWStreamWriteBox(&s, item->irefType, AVIF_BOX_SIZE_TBD);
1371 avifRWStreamWriteU32(&s, (uint32_t)item->irefToID);
1372 avifRWStreamFinishBox(&s, refType);
1373 avifRWStreamFinishBox(&s, tref);
1374 }
1375
Joe Dragoa72da5b2020-06-15 19:40:17 -07001376 if (!item->alpha) {
1377 avifEncoderWriteTrackMetaBox(encoder, &s);
1378 }
1379
Joe Drago4a25c192020-06-03 16:29:58 -07001380 avifBoxMarker mdia = avifRWStreamWriteBox(&s, "mdia", AVIF_BOX_SIZE_TBD);
1381
1382 avifBoxMarker mdhd = avifRWStreamWriteFullBox(&s, "mdhd", AVIF_BOX_SIZE_TBD, 1, 0);
1383 avifRWStreamWriteU64(&s, now); // unsigned int(64) creation_time;
1384 avifRWStreamWriteU64(&s, now); // unsigned int(64) modification_time;
1385 avifRWStreamWriteU32(&s, (uint32_t)encoder->timescale); // unsigned int(32) timescale;
1386 avifRWStreamWriteU64(&s, durationInTimescales); // unsigned int(64) duration;
1387 avifRWStreamWriteU16(&s, 21956); // bit(1) pad = 0; unsigned int(5)[3] language; ("und")
1388 avifRWStreamWriteU16(&s, 0); // unsigned int(16) pre_defined = 0;
1389 avifRWStreamFinishBox(&s, mdhd);
1390
1391 avifBoxMarker hdlrTrak = avifRWStreamWriteFullBox(&s, "hdlr", AVIF_BOX_SIZE_TBD, 0, 0);
1392 avifRWStreamWriteU32(&s, 0); // unsigned int(32) pre_defined = 0;
1393 avifRWStreamWriteChars(&s, "pict", 4); // unsigned int(32) handler_type;
1394 avifRWStreamWriteZeros(&s, 12); // const unsigned int(32)[3] reserved = 0;
1395 avifRWStreamWriteChars(&s, "libavif", 8); // string name; (writing null terminator)
1396 avifRWStreamFinishBox(&s, hdlrTrak);
1397
1398 avifBoxMarker minf = avifRWStreamWriteBox(&s, "minf", AVIF_BOX_SIZE_TBD);
1399
1400 avifBoxMarker vmhd = avifRWStreamWriteFullBox(&s, "vmhd", AVIF_BOX_SIZE_TBD, 0, 1);
1401 avifRWStreamWriteU16(&s, 0); // template unsigned int(16) graphicsmode = 0; (copy over the existing image)
1402 avifRWStreamWriteZeros(&s, 6); // template unsigned int(16)[3] opcolor = {0, 0, 0};
1403 avifRWStreamFinishBox(&s, vmhd);
1404
Joe Drago20ec1762020-06-03 17:34:43 -07001405 avifBoxMarker dinf = avifRWStreamWriteBox(&s, "dinf", AVIF_BOX_SIZE_TBD);
1406 avifBoxMarker dref = avifRWStreamWriteFullBox(&s, "dref", AVIF_BOX_SIZE_TBD, 0, 0);
1407 avifRWStreamWriteU32(&s, 1); // unsigned int(32) entry_count;
1408 avifRWStreamWriteFullBox(&s, "url ", 0, 0, 1); // flags:1 means data is in this file
1409 avifRWStreamFinishBox(&s, dref);
1410 avifRWStreamFinishBox(&s, dinf);
1411
Joe Drago4a25c192020-06-03 16:29:58 -07001412 avifBoxMarker stbl = avifRWStreamWriteBox(&s, "stbl", AVIF_BOX_SIZE_TBD);
1413
1414 avifBoxMarker stco = avifRWStreamWriteFullBox(&s, "stco", AVIF_BOX_SIZE_TBD, 0, 0);
Joe Dragoa72da5b2020-06-15 19:40:17 -07001415 avifRWStreamWriteU32(&s, 1); // unsigned int(32) entry_count;
1416 avifEncoderItemAddMdatFixup(item, &s); //
1417 avifRWStreamWriteU32(&s, 1); // unsigned int(32) chunk_offset; (set later)
Joe Drago4a25c192020-06-03 16:29:58 -07001418 avifRWStreamFinishBox(&s, stco);
1419
1420 avifBoxMarker stsc = avifRWStreamWriteFullBox(&s, "stsc", AVIF_BOX_SIZE_TBD, 0, 0);
1421 avifRWStreamWriteU32(&s, 1); // unsigned int(32) entry_count;
1422 avifRWStreamWriteU32(&s, 1); // unsigned int(32) first_chunk;
1423 avifRWStreamWriteU32(&s, item->encodeOutput->samples.count); // unsigned int(32) samples_per_chunk;
1424 avifRWStreamWriteU32(&s, 1); // unsigned int(32) sample_description_index;
1425 avifRWStreamFinishBox(&s, stsc);
1426
1427 avifBoxMarker stsz = avifRWStreamWriteFullBox(&s, "stsz", AVIF_BOX_SIZE_TBD, 0, 0);
1428 avifRWStreamWriteU32(&s, 0); // unsigned int(32) sample_size;
1429 avifRWStreamWriteU32(&s, item->encodeOutput->samples.count); // unsigned int(32) sample_count;
1430 for (uint32_t sampleIndex = 0; sampleIndex < item->encodeOutput->samples.count; ++sampleIndex) {
1431 avifEncodeSample * sample = &item->encodeOutput->samples.sample[sampleIndex];
1432 avifRWStreamWriteU32(&s, (uint32_t)sample->data.size); // unsigned int(32) entry_size;
1433 }
1434 avifRWStreamFinishBox(&s, stsz);
1435
1436 avifBoxMarker stss = avifRWStreamWriteFullBox(&s, "stss", AVIF_BOX_SIZE_TBD, 0, 0);
1437 avifRWStreamWriteU32(&s, syncSamplesCount); // unsigned int(32) entry_count;
1438 for (uint32_t sampleIndex = 0; sampleIndex < item->encodeOutput->samples.count; ++sampleIndex) {
1439 avifEncodeSample * sample = &item->encodeOutput->samples.sample[sampleIndex];
1440 if (sample->sync) {
1441 avifRWStreamWriteU32(&s, sampleIndex + 1); // unsigned int(32) sample_number;
1442 }
1443 }
1444 avifRWStreamFinishBox(&s, stss);
1445
Joe Drago4a25c192020-06-03 16:29:58 -07001446 avifBoxMarker stts = avifRWStreamWriteFullBox(&s, "stts", AVIF_BOX_SIZE_TBD, 0, 0);
Joe Drago85e387a2020-06-03 17:12:53 -07001447 size_t sttsEntryCountOffset = avifRWStreamOffset(&s);
1448 uint32_t sttsEntryCount = 0;
1449 avifRWStreamWriteU32(&s, 0); // unsigned int(32) entry_count;
1450 for (uint32_t sampleCount = 0, frameIndex = 0; frameIndex < encoder->data->frames.count; ++frameIndex) {
Joe Drago4a25c192020-06-03 16:29:58 -07001451 avifEncoderFrame * frame = &encoder->data->frames.frame[frameIndex];
Joe Drago85e387a2020-06-03 17:12:53 -07001452 ++sampleCount;
1453 if (frameIndex < (encoder->data->frames.count - 1)) {
1454 avifEncoderFrame * nextFrame = &encoder->data->frames.frame[frameIndex + 1];
1455 if (frame->durationInTimescales == nextFrame->durationInTimescales) {
1456 continue;
1457 }
1458 }
1459 avifRWStreamWriteU32(&s, sampleCount); // unsigned int(32) sample_count;
Joe Drago4a25c192020-06-03 16:29:58 -07001460 avifRWStreamWriteU32(&s, (uint32_t)frame->durationInTimescales); // unsigned int(32) sample_delta;
Joe Drago85e387a2020-06-03 17:12:53 -07001461 sampleCount = 0;
1462 ++sttsEntryCount;
Joe Drago4a25c192020-06-03 16:29:58 -07001463 }
Joe Drago85e387a2020-06-03 17:12:53 -07001464 size_t prevOffset = avifRWStreamOffset(&s);
1465 avifRWStreamSetOffset(&s, sttsEntryCountOffset);
1466 avifRWStreamWriteU32(&s, sttsEntryCount);
1467 avifRWStreamSetOffset(&s, prevOffset);
Joe Drago4a25c192020-06-03 16:29:58 -07001468 avifRWStreamFinishBox(&s, stts);
1469
1470 avifBoxMarker stsd = avifRWStreamWriteFullBox(&s, "stsd", AVIF_BOX_SIZE_TBD, 0, 0);
1471 avifRWStreamWriteU32(&s, 1); // unsigned int(32) entry_count;
1472 avifBoxMarker av01 = avifRWStreamWriteBox(&s, "av01", AVIF_BOX_SIZE_TBD);
1473 avifRWStreamWriteZeros(&s, 6); // const unsigned int(8)[6] reserved = 0;
1474 avifRWStreamWriteU16(&s, 1); // unsigned int(16) data_reference_index;
1475 avifRWStreamWriteU16(&s, 0); // unsigned int(16) pre_defined = 0;
1476 avifRWStreamWriteU16(&s, 0); // const unsigned int(16) reserved = 0;
1477 avifRWStreamWriteZeros(&s, sizeof(uint32_t) * 3); // unsigned int(32)[3] pre_defined = 0;
1478 avifRWStreamWriteU16(&s, (uint16_t)imageMetadata->width); // unsigned int(16) width;
1479 avifRWStreamWriteU16(&s, (uint16_t)imageMetadata->height); // unsigned int(16) height;
1480 avifRWStreamWriteU32(&s, 0x00480000); // template unsigned int(32) horizresolution
1481 avifRWStreamWriteU32(&s, 0x00480000); // template unsigned int(32) vertresolution
1482 avifRWStreamWriteU32(&s, 0); // const unsigned int(32) reserved = 0;
1483 avifRWStreamWriteU16(&s, 1); // template unsigned int(16) frame_count = 1;
Wan-Teh Chang1a32f222020-07-09 15:44:25 -07001484 avifRWStreamWriteChars(&s, "\012AOM Coding", 11); // string[32] compressorname;
1485 avifRWStreamWriteZeros(&s, 32 - 11); //
Joe Drago4a25c192020-06-03 16:29:58 -07001486 avifRWStreamWriteU16(&s, 0x0018); // template unsigned int(16) depth = 0x0018;
1487 avifRWStreamWriteU16(&s, (uint16_t)0xffff); // int(16) pre_defined = -1;
Joe Drago2172ed02020-11-04 18:04:44 -08001488 writeConfigBox(&s, &item->av1C);
Joe Drago332180d2020-06-15 16:55:16 -07001489 if (!item->alpha) {
1490 avifEncoderWriteColorProperties(&s, imageMetadata, NULL, NULL);
1491 }
Vignesh Venkatasubramanianf18f54e2022-02-17 14:10:17 -08001492
1493 avifBoxMarker ccst = avifRWStreamWriteFullBox(&s, "ccst", AVIF_BOX_SIZE_TBD, 0, 0);
1494 const uint8_t ccstValue = (0 << 7) | // unsigned int(1) all_ref_pics_intra;
1495 (1 << 6) | // unsigned int(1) intra_pred_used;
1496 (15 << 2); // unsigned int(4) max_ref_per_pic;
1497 avifRWStreamWriteU8(&s, ccstValue);
1498 avifRWStreamWriteZeros(&s, 3); // unsigned int(26) reserved; (two zero bits are written along with ccstValue).
1499 avifRWStreamFinishBox(&s, ccst);
1500
Joe Drago4a25c192020-06-03 16:29:58 -07001501 avifRWStreamFinishBox(&s, av01);
1502 avifRWStreamFinishBox(&s, stsd);
1503
1504 avifRWStreamFinishBox(&s, stbl);
1505
1506 avifRWStreamFinishBox(&s, minf);
1507 avifRWStreamFinishBox(&s, mdia);
1508 avifRWStreamFinishBox(&s, trak);
1509 }
1510
1511 // -------------------------------------------------------------------
1512 // Finish moov box
1513
1514 avifRWStreamFinishBox(&s, moov);
Joe Drago2c4e7142020-06-03 10:31:46 -07001515 }
1516
1517 // -----------------------------------------------------------------------
Joe Dragoeb652d82019-04-23 16:29:07 -07001518 // Write mdat
1519
Joe Dragoaec9cff2020-12-11 14:23:37 -08001520 encoder->ioStats.colorOBUSize = 0;
1521 encoder->ioStats.alphaOBUSize = 0;
1522
Joe Drago4a25c192020-06-03 16:29:58 -07001523 avifBoxMarker mdat = avifRWStreamWriteBox(&s, "mdat", AVIF_BOX_SIZE_TBD);
Joe Dragoaec9cff2020-12-11 14:23:37 -08001524 const size_t mdatStartOffset = avifRWStreamOffset(&s);
Joe Drago76e421e2020-09-21 13:20:24 -07001525 for (uint32_t itemPasses = 0; itemPasses < 3; ++itemPasses) {
1526 // Use multiple passes to pack in the following order:
1527 // * Pass 0: metadata (Exif/XMP)
1528 // * Pass 1: alpha (AV1)
1529 // * Pass 2: all other item data (AV1 color)
Joe Dragoe888d542020-09-08 20:42:09 -07001530 //
Wan-Teh Chang277d0d72020-10-08 10:24:41 -07001531 // See here for the discussion on alpha coming before color:
Joe Dragoe888d542020-09-08 20:42:09 -07001532 // https://github.com/AOMediaCodec/libavif/issues/287
1533 //
Joe Drago76e421e2020-09-21 13:20:24 -07001534 // Exif and XMP are packed first as they're required to be fully available
1535 // by avifDecoderParse() before it returns AVIF_RESULT_OK, unless ignoreXMP
1536 // and ignoreExif are enabled.
1537 //
1538 const avifBool metadataPass = (itemPasses == 0);
1539 const avifBool alphaPass = (itemPasses == 1);
Joe Drago800b47f2020-03-18 16:22:37 -07001540
Joe Dragoe888d542020-09-08 20:42:09 -07001541 for (uint32_t itemIndex = 0; itemIndex < encoder->data->items.count; ++itemIndex) {
1542 avifEncoderItem * item = &encoder->data->items.item[itemIndex];
Joe Drago0e13ce12021-04-01 17:18:53 -07001543 const avifBool isGrid = (item->gridCols > 0); // Grids store their payload in metadataPayload, so use this to distinguish grid payloads from XMP/Exif
Joe Dragoe888d542020-09-08 20:42:09 -07001544 if ((item->metadataPayload.size == 0) && (item->encodeOutput->samples.count == 0)) {
Joe Drago76e421e2020-09-21 13:20:24 -07001545 // this item has nothing for the mdat box
Joe Dragoe888d542020-09-08 20:42:09 -07001546 continue;
Joe Drago2c4e7142020-06-03 10:31:46 -07001547 }
Joe Dragoe568f732021-03-31 00:13:37 -07001548 if (!isGrid && (metadataPass != (item->metadataPayload.size > 0))) {
Joe Drago0e13ce12021-04-01 17:18:53 -07001549 // only process metadata (XMP/Exif) payloads when metadataPass is true
Joe Drago76e421e2020-09-21 13:20:24 -07001550 continue;
1551 }
1552 if (alphaPass != item->alpha) {
1553 // only process alpha payloads when alphaPass is true
Joe Dragoe888d542020-09-08 20:42:09 -07001554 continue;
1555 }
Joe Drago800b47f2020-03-18 16:22:37 -07001556
Joe Dragoaec9cff2020-12-11 14:23:37 -08001557 size_t chunkOffset = 0;
1558
1559 // Deduplication - See if an identical chunk to this has already been written
Qiang Zhou295cab32020-12-31 23:20:37 +08001560 if (item->encodeOutput->samples.count > 0) {
Joe Dragoaec9cff2020-12-11 14:23:37 -08001561 avifEncodeSample * sample = &item->encodeOutput->samples.sample[0];
1562 chunkOffset = avifEncoderFindExistingChunk(&s, mdatStartOffset, sample->data.data, sample->data.size);
Joe Dragoe888d542020-09-08 20:42:09 -07001563 } else {
Joe Dragoaec9cff2020-12-11 14:23:37 -08001564 chunkOffset = avifEncoderFindExistingChunk(&s, mdatStartOffset, item->metadataPayload.data, item->metadataPayload.size);
1565 }
1566
1567 if (!chunkOffset) {
1568 // We've never seen this chunk before; write it out
Wan-Teh Chang8adbb972020-12-18 14:12:51 -08001569 chunkOffset = avifRWStreamOffset(&s);
Joe Dragoaec9cff2020-12-11 14:23:37 -08001570 if (item->encodeOutput->samples.count > 0) {
1571 for (uint32_t sampleIndex = 0; sampleIndex < item->encodeOutput->samples.count; ++sampleIndex) {
1572 avifEncodeSample * sample = &item->encodeOutput->samples.sample[sampleIndex];
1573 avifRWStreamWrite(&s, sample->data.data, sample->data.size);
1574
1575 if (item->alpha) {
1576 encoder->ioStats.alphaOBUSize += sample->data.size;
1577 } else {
1578 encoder->ioStats.colorOBUSize += sample->data.size;
1579 }
1580 }
1581 } else {
1582 avifRWStreamWrite(&s, item->metadataPayload.data, item->metadataPayload.size);
1583 }
Joe Dragoe888d542020-09-08 20:42:09 -07001584 }
1585
1586 for (uint32_t fixupIndex = 0; fixupIndex < item->mdatFixups.count; ++fixupIndex) {
1587 avifOffsetFixup * fixup = &item->mdatFixups.fixup[fixupIndex];
1588 size_t prevOffset = avifRWStreamOffset(&s);
1589 avifRWStreamSetOffset(&s, fixup->offset);
Joe Dragoaec9cff2020-12-11 14:23:37 -08001590 avifRWStreamWriteU32(&s, (uint32_t)chunkOffset);
Joe Dragoe888d542020-09-08 20:42:09 -07001591 avifRWStreamSetOffset(&s, prevOffset);
1592 }
Joe Drago800b47f2020-03-18 16:22:37 -07001593 }
Joe Dragof6a42272019-11-21 15:21:41 -08001594 }
Joe Drago345aaa12019-09-25 13:42:12 -07001595 avifRWStreamFinishBox(&s, mdat);
Joe Dragoeb652d82019-04-23 16:29:07 -07001596
1597 // -----------------------------------------------------------------------
1598 // Finish up stream
1599
Joe Drago345aaa12019-09-25 13:42:12 -07001600 avifRWStreamFinishWrite(&s);
Joe Drago0a5a0e42019-04-11 11:32:56 -07001601
Joe Drago250221a2020-06-01 11:11:06 -07001602 return AVIF_RESULT_OK;
1603}
Joe Drago0b05eee2019-06-12 13:24:39 -07001604
Joe Drago250221a2020-06-01 11:11:06 -07001605avifResult avifEncoderWrite(avifEncoder * encoder, const avifImage * image, avifRWData * output)
1606{
Joe Dragod6850242020-06-16 16:05:37 -07001607 avifResult addImageResult = avifEncoderAddImage(encoder, image, 1, AVIF_ADD_IMAGE_FLAG_SINGLE);
Joe Drago250221a2020-06-01 11:11:06 -07001608 if (addImageResult != AVIF_RESULT_OK) {
1609 return addImageResult;
1610 }
1611 return avifEncoderFinish(encoder, output);
Joe Drago444f0512019-01-23 17:03:24 -08001612}
1613
Wan-Teh Change184dc12020-05-11 12:47:21 -07001614static avifBool avifImageIsOpaque(const avifImage * image)
Joe Drago444f0512019-01-23 17:03:24 -08001615{
Joe Drago7ad3ad62019-02-07 11:17:34 -08001616 if (!image->alphaPlane) {
1617 return AVIF_TRUE;
1618 }
1619
Joe Drago444f0512019-01-23 17:03:24 -08001620 int maxChannel = (1 << image->depth) - 1;
Joe Drago7ad3ad62019-02-07 11:17:34 -08001621 if (avifImageUsesU16(image)) {
Joe Drago341a6a62019-07-23 16:12:59 -07001622 for (uint32_t j = 0; j < image->height; ++j) {
1623 for (uint32_t i = 0; i < image->width; ++i) {
Joe Drago7ad3ad62019-02-07 11:17:34 -08001624 uint16_t * p = (uint16_t *)&image->alphaPlane[(i * 2) + (j * image->alphaRowBytes)];
1625 if (*p != maxChannel) {
1626 return AVIF_FALSE;
1627 }
1628 }
1629 }
1630 } else {
Joe Drago341a6a62019-07-23 16:12:59 -07001631 for (uint32_t j = 0; j < image->height; ++j) {
1632 for (uint32_t i = 0; i < image->width; ++i) {
Joe Drago7ad3ad62019-02-07 11:17:34 -08001633 if (image->alphaPlane[i + (j * image->alphaRowBytes)] != maxChannel) {
1634 return AVIF_FALSE;
1635 }
Joe Drago444f0512019-01-23 17:03:24 -08001636 }
1637 }
1638 }
1639 return AVIF_TRUE;
1640}
Joe Drago15857972019-02-13 17:48:28 -08001641
Joe Drago345aaa12019-09-25 13:42:12 -07001642static void writeConfigBox(avifRWStream * s, avifCodecConfigurationBox * cfg)
Joe Drago15857972019-02-13 17:48:28 -08001643{
Joe Drago4a25c192020-06-03 16:29:58 -07001644 avifBoxMarker av1C = avifRWStreamWriteBox(s, "av1C", AVIF_BOX_SIZE_TBD);
Joe Drago15857972019-02-13 17:48:28 -08001645
1646 // unsigned int (1) marker = 1;
1647 // unsigned int (7) version = 1;
Joe Drago345aaa12019-09-25 13:42:12 -07001648 avifRWStreamWriteU8(s, 0x80 | 0x1);
Joe Drago15857972019-02-13 17:48:28 -08001649
1650 // unsigned int (3) seq_profile;
1651 // unsigned int (5) seq_level_idx_0;
Joe Drago345aaa12019-09-25 13:42:12 -07001652 avifRWStreamWriteU8(s, (uint8_t)((cfg->seqProfile & 0x7) << 5) | (uint8_t)(cfg->seqLevelIdx0 & 0x1f));
Joe Drago15857972019-02-13 17:48:28 -08001653
1654 uint8_t bits = 0;
1655 bits |= (cfg->seqTier0 & 0x1) << 7; // unsigned int (1) seq_tier_0;
1656 bits |= (cfg->highBitdepth & 0x1) << 6; // unsigned int (1) high_bitdepth;
1657 bits |= (cfg->twelveBit & 0x1) << 5; // unsigned int (1) twelve_bit;
1658 bits |= (cfg->monochrome & 0x1) << 4; // unsigned int (1) monochrome;
1659 bits |= (cfg->chromaSubsamplingX & 0x1) << 3; // unsigned int (1) chroma_subsampling_x;
1660 bits |= (cfg->chromaSubsamplingY & 0x1) << 2; // unsigned int (1) chroma_subsampling_y;
1661 bits |= (cfg->chromaSamplePosition & 0x3); // unsigned int (2) chroma_sample_position;
Joe Drago345aaa12019-09-25 13:42:12 -07001662 avifRWStreamWriteU8(s, bits);
Joe Drago15857972019-02-13 17:48:28 -08001663
1664 // unsigned int (3) reserved = 0;
1665 // unsigned int (1) initial_presentation_delay_present;
1666 // if (initial_presentation_delay_present) {
1667 // unsigned int (4) initial_presentation_delay_minus_one;
1668 // } else {
1669 // unsigned int (4) reserved = 0;
1670 // }
Joe Drago345aaa12019-09-25 13:42:12 -07001671 avifRWStreamWriteU8(s, 0);
Joe Drago15857972019-02-13 17:48:28 -08001672
Joe Drago345aaa12019-09-25 13:42:12 -07001673 avifRWStreamFinishBox(s, av1C);
Joe Drago15857972019-02-13 17:48:28 -08001674}