Support update encoder settings during encoding

Detect changes made to avifEncoder between avifEncoderAddImage() calls.
diff --git a/src/avif.c b/src/avif.c
index 41e4748..81f2889 100644
--- a/src/avif.c
+++ b/src/avif.c
@@ -98,6 +98,8 @@
         case AVIF_RESULT_INVALID_ARGUMENT:              return "Invalid argument";
         case AVIF_RESULT_NOT_IMPLEMENTED:               return "Not implemented";
         case AVIF_RESULT_OUT_OF_MEMORY:                 return "Out of memory";
+        case AVIF_RESULT_CANNOT_CHANGE_SETTING:         return "Can not change some settings during encoding";
+        case AVIF_RESULT_INCOMPATIBLE_IMAGE:            return "This image is incompatible with already encoded image";
         case AVIF_RESULT_UNKNOWN_ERROR:
         default:
             break;
diff --git a/src/codec_aom.c b/src/codec_aom.c
index a3fba99..6f64e59 100644
--- a/src/codec_aom.c
+++ b/src/codec_aom.c
@@ -60,6 +60,7 @@
 #if defined(AVIF_CODEC_AOM_ENCODE)
     avifBool encoderInitialized;
     aom_codec_ctx_t encoder;
+    struct aom_codec_enc_cfg cfg;
     avifPixelFormatInfo formatInfo;
     aom_img_fmt_t aomFormat;
     avifBool monochromeEnabled;
@@ -526,10 +527,11 @@
                                       avifEncoder * encoder,
                                       const avifImage * image,
                                       avifBool alpha,
+                                      avifBool updateConfig,
                                       avifAddImageFlags addImageFlags,
                                       avifCodecEncodeOutput * output)
 {
-    if (!codec->internal->encoderInitialized) {
+    if (!codec->internal->encoderInitialized || updateConfig) {
         // Map encoder speed to AOM usage + CpuUsed:
         // Speed  0: GoodQuality CpuUsed 0
         // Speed  1: GoodQuality CpuUsed 1
@@ -587,37 +589,41 @@
             }
         }
 
-        codec->internal->aomFormat = avifImageCalcAOMFmt(image, alpha);
-        if (codec->internal->aomFormat == AOM_IMG_FMT_NONE) {
-            return AVIF_RESULT_UNKNOWN_ERROR;
+        struct aom_codec_enc_cfg * cfg = &codec->internal->cfg;
+
+        aom_codec_iface_t * encoderInterface = NULL;
+        if (!codec->internal->encoderInitialized) {
+            codec->internal->aomFormat = avifImageCalcAOMFmt(image, alpha);
+            if (codec->internal->aomFormat == AOM_IMG_FMT_NONE) {
+                return AVIF_RESULT_UNKNOWN_ERROR;
+            }
+
+            avifGetPixelFormatInfo(image->yuvFormat, &codec->internal->formatInfo);
+
+            encoderInterface = aom_codec_av1_cx();
+            aom_codec_err_t err = aom_codec_enc_config_default(encoderInterface, cfg, aomUsage);
+            if (err != AOM_CODEC_OK) {
+                avifDiagnosticsPrintf(codec->diag, "aom_codec_enc_config_default() failed: %s", aom_codec_err_to_string(err));
+                return AVIF_RESULT_UNKNOWN_ERROR;
+            }
         }
 
-        avifGetPixelFormatInfo(image->yuvFormat, &codec->internal->formatInfo);
-
-        aom_codec_iface_t * encoderInterface = aom_codec_av1_cx();
-        struct aom_codec_enc_cfg cfg;
-        aom_codec_err_t err = aom_codec_enc_config_default(encoderInterface, &cfg, aomUsage);
-        if (err != AOM_CODEC_OK) {
-            avifDiagnosticsPrintf(codec->diag, "aom_codec_enc_config_default() failed: %s", aom_codec_err_to_string(err));
-            return AVIF_RESULT_UNKNOWN_ERROR;
-        }
-
-        // Set our own default cfg.rc_end_usage value, which may differ from libaom's default.
+        // Set our own default cfg->rc_end_usage value, which may differ from libaom's default.
         switch (aomUsage) {
             case AOM_USAGE_GOOD_QUALITY:
                 // libaom's default is AOM_VBR. Change the default to AOM_Q since we don't need to
                 // hit a certain target bit rate. It's easier to control the worst quality in Q
                 // mode.
-                cfg.rc_end_usage = AOM_Q;
+                cfg->rc_end_usage = AOM_Q;
                 break;
             case AOM_USAGE_REALTIME:
                 // For real-time mode we need to use CBR rate control mode. AOM_Q doesn't fit the
                 // rate control requirements for real-time mode. CBR does.
-                cfg.rc_end_usage = AOM_CBR;
+                cfg->rc_end_usage = AOM_CBR;
                 break;
 #if defined(AOM_USAGE_ALL_INTRA)
             case AOM_USAGE_ALL_INTRA:
-                cfg.rc_end_usage = AOM_Q;
+                cfg->rc_end_usage = AOM_Q;
                 break;
 #endif
         }
@@ -657,16 +663,16 @@
             }
         }
 
-        cfg.g_profile = seqProfile;
-        cfg.g_bit_depth = image->depth;
-        cfg.g_input_bit_depth = image->depth;
-        cfg.g_w = image->width;
-        cfg.g_h = image->height;
+        cfg->g_profile = seqProfile;
+        cfg->g_bit_depth = image->depth;
+        cfg->g_input_bit_depth = image->depth;
+        cfg->g_w = image->width;
+        cfg->g_h = image->height;
         if (addImageFlags & AVIF_ADD_IMAGE_FLAG_SINGLE) {
             // Set the maximum number of frames to encode to 1. This instructs
             // libaom to set still_picture and reduced_still_picture_header to
             // 1 in AV1 sequence headers.
-            cfg.g_limit = 1;
+            cfg->g_limit = 1;
 
             // Use the default settings of the new AOM_USAGE_ALL_INTRA (added in
             // https://crbug.com/aomedia/2959).
@@ -674,14 +680,14 @@
             // Set g_lag_in_frames to 0 to reduce the number of frame buffers
             // (from 20 to 2) in libaom's lookahead structure. This reduces
             // memory consumption when encoding a single image.
-            cfg.g_lag_in_frames = 0;
+            cfg->g_lag_in_frames = 0;
             // Disable automatic placement of key frames by the encoder.
-            cfg.kf_mode = AOM_KF_DISABLED;
+            cfg->kf_mode = AOM_KF_DISABLED;
             // Tell libaom that all frames will be key frames.
-            cfg.kf_max_dist = 0;
+            cfg->kf_max_dist = 0;
         }
         if (encoder->maxThreads > 1) {
-            cfg.g_threads = encoder->maxThreads;
+            cfg->g_threads = encoder->maxThreads;
         }
 
         int minQuantizer = AVIF_CLAMP(encoder->minQuantizer, 0, 63);
@@ -691,8 +697,8 @@
             maxQuantizer = AVIF_CLAMP(encoder->maxQuantizerAlpha, 0, 63);
         }
         avifBool lossless = ((minQuantizer == AVIF_QUANTIZER_LOSSLESS) && (maxQuantizer == AVIF_QUANTIZER_LOSSLESS));
-        cfg.rc_min_quantizer = minQuantizer;
-        cfg.rc_max_quantizer = maxQuantizer;
+        cfg->rc_min_quantizer = minQuantizer;
+        cfg->rc_max_quantizer = maxQuantizer;
 
         codec->internal->monochromeEnabled = AVIF_FALSE;
         if (aomVersion > aomVersion_2_0_0) {
@@ -700,32 +706,43 @@
             // access nonexistent UV planes when encoding monochrome at faster libavif "speeds". It
             // was fixed shortly after the 2.0.0 libaom release, and the fix exists in both the
             // master and applejack branches. This ensures that the next version *after* 2.0.0 will
-            // have the fix, and we must avoid cfg.monochrome until then.
+            // have the fix, and we must avoid cfg->monochrome until then.
             //
             // Bugfix Change-Id: https://aomedia-review.googlesource.com/q/I26a39791f820b4d4e1d63ff7141f594c3c7181f5
 
             if (alpha || (image->yuvFormat == AVIF_PIXEL_FORMAT_YUV400)) {
                 codec->internal->monochromeEnabled = AVIF_TRUE;
-                cfg.monochrome = 1;
+                cfg->monochrome = 1;
             }
         }
 
-        if (!avifProcessAOMOptionsPreInit(codec, alpha, &cfg)) {
+        if (!avifProcessAOMOptionsPreInit(codec, alpha, cfg)) {
             return AVIF_RESULT_INVALID_CODEC_SPECIFIC_OPTION;
         }
 
-        aom_codec_flags_t encoderFlags = 0;
-        if (image->depth > 8) {
-            encoderFlags |= AOM_CODEC_USE_HIGHBITDEPTH;
+        if (!codec->internal->encoderInitialized) {
+            aom_codec_flags_t encoderFlags = 0;
+            if (image->depth > 8) {
+                encoderFlags |= AOM_CODEC_USE_HIGHBITDEPTH;
+            }
+
+            if (aom_codec_enc_init(&codec->internal->encoder, encoderInterface, cfg, encoderFlags) != AOM_CODEC_OK) {
+                avifDiagnosticsPrintf(codec->diag,
+                                      "aom_codec_enc_init() failed: %s: %s",
+                                      aom_codec_error(&codec->internal->encoder),
+                                      aom_codec_error_detail(&codec->internal->encoder));
+                return AVIF_RESULT_UNKNOWN_ERROR;
+            }
+            codec->internal->encoderInitialized = AVIF_TRUE;
+        } else {
+            if (aom_codec_enc_config_set(&codec->internal->encoder, cfg) != AOM_CODEC_OK) {
+                avifDiagnosticsPrintf(codec->diag,
+                                      "aom_codec_enc_config_set() failed: %s: %s",
+                                      aom_codec_error(&codec->internal->encoder),
+                                      aom_codec_error_detail(&codec->internal->encoder));
+                return AVIF_RESULT_UNKNOWN_ERROR;
+            }
         }
-        if (aom_codec_enc_init(&codec->internal->encoder, encoderInterface, &cfg, encoderFlags) != AOM_CODEC_OK) {
-            avifDiagnosticsPrintf(codec->diag,
-                                  "aom_codec_enc_init() failed: %s: %s",
-                                  aom_codec_error(&codec->internal->encoder),
-                                  aom_codec_error_detail(&codec->internal->encoder));
-            return AVIF_RESULT_UNKNOWN_ERROR;
-        }
-        codec->internal->encoderInitialized = AVIF_TRUE;
 
         if (lossless) {
             aom_codec_control(&codec->internal->encoder, AV1E_SET_LOSSLESS, 1);
@@ -756,8 +773,8 @@
             // set the min and max quantizers in the avifEncoder struct. If this is the case, set
             // cq-level to a reasonable value for the user, otherwise the default cq-level
             // (currently 10) will be unknowingly used.
-            assert(cfg.rc_end_usage == AOM_Q);
-            unsigned int cqLevel = (cfg.rc_min_quantizer + cfg.rc_max_quantizer) / 2;
+            assert(cfg->rc_end_usage == AOM_Q);
+            unsigned int cqLevel = (cfg->rc_min_quantizer + cfg->rc_max_quantizer) / 2;
             aom_codec_control(&codec->internal->encoder, AOME_SET_CQ_LEVEL, cqLevel);
         }
 #endif
diff --git a/src/codec_rav1e.c b/src/codec_rav1e.c
index e9ed443..f8b301a 100644
--- a/src/codec_rav1e.c
+++ b/src/codec_rav1e.c
@@ -51,9 +51,14 @@
                                         avifEncoder * encoder,
                                         const avifImage * image,
                                         avifBool alpha,
+                                        avifBool updateConfig,
                                         uint32_t addImageFlags,
                                         avifCodecEncodeOutput * output)
 {
+    if (updateConfig) {
+        return AVIF_RESULT_NOT_IMPLEMENTED;
+    }
+
     avifResult result = AVIF_RESULT_UNKNOWN_ERROR;
 
     RaConfig * rav1eConfig = NULL;
diff --git a/src/codec_svt.c b/src/codec_svt.c
index f6aefde..f45ccf1 100644
--- a/src/codec_svt.c
+++ b/src/codec_svt.c
@@ -46,9 +46,14 @@
                                       avifEncoder * encoder,
                                       const avifImage * image,
                                       avifBool alpha,
+                                      avifBool updateConfig,
                                       uint32_t addImageFlags,
                                       avifCodecEncodeOutput * output)
 {
+    if (updateConfig) {
+        return AVIF_RESULT_NOT_IMPLEMENTED;
+    }
+
     avifResult result = AVIF_RESULT_UNKNOWN_ERROR;
     EbColorFormat color_format = EB_YUV420;
     EbBufferHeaderType * input_buffer = NULL;
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;
             }