Force keyframe for alpha if color is a keyframe

When the encoder supports it (as of now only libaom does),
disable lagged output when encoding animated images with alpha
channel. When the utput of the color planes is a keyframe, force
a keyframe on the alpha channel as well.

While the spec does not enforce this, and the libavif decoder can
handle files without this requirement just fine, this change is
still an enhancement because most decoders look at whether or not
the color frame is a keyframe to determine seek points.

Fixes: Issue #841
diff --git a/include/avif/internal.h b/include/avif/internal.h
index 050c64f..9222948 100644
--- a/include/avif/internal.h
+++ b/include/avif/internal.h
@@ -357,7 +357,9 @@
 // avifCodecEncodeImageFunc is responsible for automatic tiling if encoder->autoTiling is set to
 // AVIF_TRUE. The actual tiling values are passed to avifCodecEncodeImageFunc as parameters.
 // Similarly, avifCodecEncodeImageFunc should use the quantizer parameter instead of
-// encoder->quality and encoder->qualityAlpha.
+// encoder->quality and encoder->qualityAlpha. If disableLaggedOutput is AVIF_TRUE, then the encoder will emit the output frame
+// without any lag (if supported). Note that disableLaggedOutput is only used by the first call to this function (which
+// initializes the encoder) and is ignored by the subsequent calls.
 //
 // Note: The caller of avifCodecEncodeImageFunc always passes encoder->data->tileRowsLog2 and
 // encoder->data->tileColsLog2 as the tileRowsLog2 and tileColsLog2 arguments. Because
@@ -372,6 +374,7 @@
                                                int tileColsLog2,
                                                int quantizer,
                                                avifEncoderChanges encoderChanges,
+                                               avifBool disableLaggedOutput,
                                                avifAddImageFlags addImageFlags,
                                                avifCodecEncodeOutput * output);
 typedef avifBool (*avifCodecEncodeFinishFunc)(struct avifCodec * codec, avifCodecEncodeOutput * output);
diff --git a/src/codec_aom.c b/src/codec_aom.c
index 6c7cbcb..dd20c18 100644
--- a/src/codec_aom.c
+++ b/src/codec_aom.c
@@ -545,6 +545,7 @@
                                       int tileColsLog2,
                                       int quantizer,
                                       avifEncoderChanges encoderChanges,
+                                      avifBool disableLaggedOutput,
                                       avifAddImageFlags addImageFlags,
                                       avifCodecEncodeOutput * output)
 {
@@ -716,6 +717,9 @@
             // frame for each input frame.
             cfg->g_lag_in_frames = 0;
         }
+        if (disableLaggedOutput) {
+            cfg->g_lag_in_frames = 0;
+        }
         if (encoder->maxThreads > 1) {
             cfg->g_threads = encoder->maxThreads;
         }
diff --git a/src/codec_avm.c b/src/codec_avm.c
index a702cf0..a2ac97d 100644
--- a/src/codec_avm.c
+++ b/src/codec_avm.c
@@ -535,6 +535,7 @@
                                       int tileColsLog2,
                                       int quantizer,
                                       avifEncoderChanges encoderChanges,
+                                      avifBool disableLaggedOutput,
                                       avifAddImageFlags addImageFlags,
                                       avifCodecEncodeOutput * output)
 {
@@ -636,6 +637,9 @@
             // frame for each input frame.
             cfg->g_lag_in_frames = 0;
         }
+        if (disableLaggedOutput) {
+            cfg->g_lag_in_frames = 0;
+        }
         if (encoder->maxThreads > 1) {
             cfg->g_threads = encoder->maxThreads;
         }
diff --git a/src/codec_rav1e.c b/src/codec_rav1e.c
index febce2d..28c5999 100644
--- a/src/codec_rav1e.c
+++ b/src/codec_rav1e.c
@@ -57,6 +57,7 @@
                                         int tileColsLog2,
                                         int quantizer,
                                         avifEncoderChanges encoderChanges,
+                                        avifBool disableLaggedOutput,
                                         uint32_t addImageFlags,
                                         avifCodecEncodeOutput * output)
 {
@@ -78,6 +79,9 @@
         return AVIF_RESULT_NOT_IMPLEMENTED;
     }
 
+    // rav1e does not support disabling lagged output. See https://github.com/xiph/rav1e/issues/2267. Ignore this setting.
+    (void)disableLaggedOutput;
+
     avifResult result = AVIF_RESULT_UNKNOWN_ERROR;
 
     RaConfig * rav1eConfig = NULL;
diff --git a/src/codec_svt.c b/src/codec_svt.c
index 442a103..609f35b 100644
--- a/src/codec_svt.c
+++ b/src/codec_svt.c
@@ -50,6 +50,7 @@
                                       int tileColsLog2,
                                       int quantizer,
                                       avifEncoderChanges encoderChanges,
+                                      avifBool disableLaggedOutput,
                                       uint32_t addImageFlags,
                                       avifCodecEncodeOutput * output)
 {
@@ -70,6 +71,9 @@
         return AVIF_RESULT_NOT_IMPLEMENTED;
     }
 
+    // SVT-AV1 does not support disabling lagged output. Ignore this setting.
+    (void)disableLaggedOutput;
+
     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 2ba6251..b2b546a 100644
--- a/src/write.c
+++ b/src/write.c
@@ -941,6 +941,35 @@
     return avifCodecTypeFromChoice(encoder->codecChoice, AVIF_CODEC_FLAG_CAN_ENCODE);
 }
 
+// This function is called after every color frame is encoded. It returns AVIF_TRUE if a keyframe needs to be forced for the next
+// alpha frame to be encoded, AVIF_FALSE otherwise.
+static avifBool avifEncoderDataShouldForceKeyframeForAlpha(const avifEncoderData * data,
+                                                           const avifEncoderItem * colorItem,
+                                                           avifAddImageFlags addImageFlags)
+{
+    if (!data->alphaPresent) {
+        // There is no alpha plane.
+        return AVIF_FALSE;
+    }
+    if (addImageFlags & AVIF_ADD_IMAGE_FLAG_SINGLE) {
+        // Not an animated image.
+        return AVIF_FALSE;
+    }
+    if (data->frames.count == 0) {
+        // data->frames.count is the number of frames that have been encoded so far by previous calls to avifEncoderAddImage. If
+        // this is the first frame, there is no need to force keyframe.
+        return AVIF_FALSE;
+    }
+    const uint32_t colorFramesOutputSoFar = colorItem->encodeOutput->samples.count;
+    const avifBool isLaggedOutput = (data->frames.count + 1) != colorFramesOutputSoFar;
+    if (isLaggedOutput) {
+        // If the encoder is operating with lag, then there is no way to determine if the last encoded frame was a keyframe until
+        // the encoder outputs it (after the lag). So do not force keyframe for alpha channel in this case.
+        return AVIF_FALSE;
+    }
+    return colorItem->encodeOutput->samples.sample[colorFramesOutputSoFar - 1].sync;
+}
+
 static avifResult avifEncoderAddImageInternal(avifEncoder * encoder,
                                               uint32_t gridCols,
                                               uint32_t gridRows,
@@ -1222,6 +1251,9 @@
                 cellImage = paddedCellImage;
             }
             const int quantizer = item->alpha ? encoder->data->quantizerAlpha : encoder->data->quantizer;
+            // If alpha channel is present, set disableLaggedOutput to AVIF_TRUE. If the encoder supports it, this enables
+            // avifEncoderDataShouldForceKeyframeForAlpha to force a keyframe in the alpha channel whenever a keyframe has been
+            // encoded in the color channel for animated images.
             avifResult encodeResult = item->codec->encodeImage(item->codec,
                                                                encoder,
                                                                cellImage,
@@ -1230,6 +1262,7 @@
                                                                encoder->data->tileColsLog2,
                                                                quantizer,
                                                                encoderChanges,
+                                                               /*disableLaggedOutput=*/encoder->data->alphaPresent,
                                                                addImageFlags,
                                                                item->encodeOutput);
             if (paddedCellImage) {
@@ -1241,6 +1274,9 @@
             if (encodeResult != AVIF_RESULT_OK) {
                 return encodeResult;
             }
+            if (itemIndex == 0 && avifEncoderDataShouldForceKeyframeForAlpha(encoder->data, item, addImageFlags)) {
+                addImageFlags |= AVIF_ADD_IMAGE_FLAG_FORCE_KEYFRAME;
+            }
         }
     }