Support update encoder settings during encoding
Detect changes made to avifEncoder between avifEncoderAddImage() calls.
diff --git a/src/write.c b/src/write.c
index 2585678..0083dcf 100644
--- a/src/write.c
+++ b/src/write.c
@@ -122,11 +122,13 @@
{
avifEncoderItemArray items;
avifEncoderFrameArray frames;
+ avifEncoder lastEncoder;
avifImage * imageMetadata;
uint16_t lastItemID;
uint16_t primaryItemID;
avifBool singleImage; // if true, the AVIF_ADD_IMAGE_FLAG_SINGLE flag was set on the first call to avifEncoderAddImage()
avifBool alphaPresent;
+ avifBool csOptionsUpdated;
} avifEncoderData;
static void avifEncoderDataDestroy(avifEncoderData * data);
@@ -317,6 +319,54 @@
void avifEncoderSetCodecSpecificOption(avifEncoder * encoder, const char * key, const char * value)
{
avifCodecSpecificOptionsSet(encoder->csOptions, key, value);
+ encoder->data->csOptionsUpdated = AVIF_TRUE; // False positive is possible but not important.
+}
+
+static void avifBackupSettings(avifEncoder * encoder)
+{
+ avifEncoder * lastEncoder = &encoder->data->lastEncoder;
+
+ // lastEncoder->data is used to mark that lastEncoder is initialized.
+ lastEncoder->data = encoder->data;
+ lastEncoder->codecChoice = encoder->codecChoice;
+ lastEncoder->keyframeInterval = encoder->keyframeInterval;
+ lastEncoder->timescale = encoder->timescale;
+ lastEncoder->maxThreads = encoder->maxThreads;
+ lastEncoder->minQuantizer = encoder->minQuantizer;
+ lastEncoder->maxQuantizer = encoder->maxQuantizer;
+ lastEncoder->minQuantizerAlpha = encoder->minQuantizerAlpha;
+ lastEncoder->maxQuantizerAlpha = encoder->maxQuantizerAlpha;
+ lastEncoder->tileRowsLog2 = encoder->tileRowsLog2;
+ lastEncoder->tileColsLog2 = encoder->tileColsLog2;
+ lastEncoder->speed = encoder->speed;
+ encoder->data->csOptionsUpdated = AVIF_FALSE;
+}
+
+// This function detect changes made on avifEncoder.
+// It reports if the change is valid, i.e. if any setting that can't change was changed.
+// It also sets needUpdate to true if valid changes are detected.
+static avifBool avifEncoderSettingsChanged(const avifEncoder * encoder, avifBool * needUpdate)
+{
+ const avifEncoder * lastEncoder = &encoder->data->lastEncoder;
+
+ if (!lastEncoder->data) {
+ return AVIF_TRUE;
+ }
+
+ if ((lastEncoder->codecChoice != encoder->codecChoice) || (lastEncoder->keyframeInterval != encoder->keyframeInterval) ||
+ (lastEncoder->timescale != encoder->timescale)) {
+ return AVIF_FALSE;
+ }
+
+ if ((lastEncoder->maxThreads != encoder->maxThreads) || (lastEncoder->minQuantizer != encoder->minQuantizer) ||
+ (lastEncoder->maxQuantizer != encoder->maxQuantizer) || (lastEncoder->minQuantizerAlpha != encoder->minQuantizerAlpha) ||
+ (lastEncoder->maxQuantizerAlpha != encoder->maxQuantizerAlpha) || (lastEncoder->tileRowsLog2 != encoder->tileRowsLog2) ||
+ (lastEncoder->tileColsLog2 != encoder->tileColsLog2) || (lastEncoder->speed != encoder->speed) ||
+ (encoder->data->csOptionsUpdated)) {
+ *needUpdate = AVIF_TRUE;
+ }
+
+ return AVIF_TRUE;
}
// This function is used in two codepaths:
@@ -606,6 +656,12 @@
return AVIF_RESULT_NO_CODEC_AVAILABLE;
}
+ avifBool updateConfig = AVIF_FALSE;
+ if (!avifEncoderSettingsChanged(encoder, &updateConfig)) {
+ return AVIF_RESULT_CANNOT_CHANGE_SETTING;
+ }
+ avifBackupSettings(encoder);
+
// -----------------------------------------------------------------------
// Validate images
@@ -806,10 +862,23 @@
} else {
// Another frame in an image sequence
- if (encoder->data->alphaPresent && !firstCell->alphaPlane) {
- // If the first image in the sequence had an alpha plane (even if fully opaque), all
- // subsequence images must have alpha as well.
- return AVIF_RESULT_ENCODE_ALPHA_FAILED;
+ const avifImage * imageMetadata = encoder->data->imageMetadata;
+ // HEIF (ISO 23008-12:2017), Section 6.6.2.3.1:
+ // All input images shall have exactly the same width and height; call those tile_width and tile_height.
+ // MIAF (ISO 23000-22:2019), Section 7.3.11.4.1:
+ // All input images of a grid image item shall use the same coding format, chroma sampling format, and the
+ // same decoder configuration (see 7.3.6.2).
+ // If the first image in the sequence had an alpha plane (even if fully opaque), all
+ // subsequence images must have alpha as well.
+ if ((imageMetadata->width != firstCell->width) || (imageMetadata->height != firstCell->height) ||
+ (imageMetadata->depth != firstCell->depth) || (imageMetadata->yuvFormat != firstCell->yuvFormat) ||
+ (imageMetadata->yuvRange != firstCell->yuvRange) || (imageMetadata->colorPrimaries != firstCell->colorPrimaries) ||
+ (imageMetadata->transferCharacteristics != firstCell->transferCharacteristics) ||
+ (imageMetadata->matrixCoefficients != firstCell->matrixCoefficients) ||
+ (!!imageMetadata->alphaPlane != !!firstCell->alphaPlane) ||
+ (imageMetadata->alphaPremultiplied != firstCell->alphaPremultiplied) ||
+ (encoder->data->alphaPresent && !firstCell->alphaPlane)) {
+ return AVIF_RESULT_INCOMPATIBLE_IMAGE;
}
}
@@ -825,7 +894,7 @@
if (item->codec) {
const avifImage * cellImage = cellImages[item->cellIndex];
avifResult encodeResult =
- item->codec->encodeImage(item->codec, encoder, cellImage, item->alpha, addImageFlags, item->encodeOutput);
+ item->codec->encodeImage(item->codec, encoder, cellImage, item->alpha, updateConfig, addImageFlags, item->encodeOutput);
if (encodeResult == AVIF_RESULT_UNKNOWN_ERROR) {
encodeResult = item->alpha ? AVIF_RESULT_ENCODE_ALPHA_FAILED : AVIF_RESULT_ENCODE_COLOR_FAILED;
}