Distribute out and share code populating av01 config box across codecs
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1e29c8b..5488552 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,6 +7,7 @@
 ## [Unreleased]
 ### Changed
 - Fix QP range for rav1e encodes (rav1e uses [0-255], not [0-63])
+- Distribute out and share code populating av01 config box across codecs
 
 ## [0.4.3] - 2019-10-28
 ### Added
diff --git a/include/avif/internal.h b/include/avif/internal.h
index a8de61c..c0bee2d 100644
--- a/include/avif/internal.h
+++ b/include/avif/internal.h
@@ -129,19 +129,18 @@
 typedef avifBool (*avifCodecGetNextImageFunc)(struct avifCodec * codec, avifImage * image);
 // avifCodecEncodeImageFunc: if either OBU* is null, skip its encode. alpha should always be lossless
 typedef avifBool (*avifCodecEncodeImageFunc)(struct avifCodec * codec, avifImage * image, avifEncoder * encoder, avifRWData * obu, avifBool alpha);
-typedef void (*avifCodecGetConfigurationBoxFunc)(struct avifCodec * codec, avifCodecConfigurationBox * outConfig);
 typedef void (*avifCodecDestroyInternalFunc)(struct avifCodec * codec);
 
 typedef struct avifCodec
 {
     avifCodecDecodeInput * decodeInput;
+    avifCodecConfigurationBox configBox; // Pre-populated by avifEncoderWrite(), available and overridable by codec impls
     struct avifCodecInternal * internal; // up to each codec to use how it wants
 
     avifCodecOpenFunc open;
     avifCodecAlphaLimitedRangeFunc alphaLimitedRange;
     avifCodecGetNextImageFunc getNextImage;
     avifCodecEncodeImageFunc encodeImage;
-    avifCodecGetConfigurationBoxFunc getConfigurationBox;
     avifCodecDestroyInternalFunc destroyInternal;
 } avifCodec;
 
diff --git a/src/codec_aom.c b/src/codec_aom.c
index ff9ff52..fee504f 100644
--- a/src/codec_aom.c
+++ b/src/codec_aom.c
@@ -34,8 +34,6 @@
     aom_codec_iter_t iter;
     uint32_t inputSampleIndex;
     aom_image_t * image;
-
-    avifCodecConfigurationBox config;
 };
 
 static void aomCodecDestroyInternal(avifCodec * codec)
@@ -193,12 +191,12 @@
     return AVIF_TRUE;
 }
 
-static aom_img_fmt_t avifImageCalcAOMFmt(avifImage * image, avifBool alphaOnly, int * yShift)
+static aom_img_fmt_t avifImageCalcAOMFmt(avifImage * image, avifBool alpha, int * yShift)
 {
     *yShift = 0;
 
     aom_img_fmt_t fmt;
-    if (alphaOnly) {
+    if (alpha) {
         // We're going monochrome, who cares about chroma quality
         fmt = AOM_IMG_FMT_I420;
         *yShift = 1;
@@ -230,16 +228,14 @@
     return fmt;
 }
 
-static avifBool encodeOBU(avifImage * image, avifBool alphaOnly, avifEncoder * encoder, avifRWData * outputOBU, avifCodecConfigurationBox * outputConfig)
+static avifBool aomCodecEncodeImage(avifCodec * codec, avifImage * image, avifEncoder * encoder, avifRWData * obu, avifBool alpha)
 {
     avifBool success = AVIF_FALSE;
     aom_codec_iface_t * encoder_interface = aom_codec_av1_cx();
     aom_codec_ctx_t aomEncoder;
 
-    memset(outputConfig, 0, sizeof(avifCodecConfigurationBox));
-
     int yShift = 0;
-    aom_img_fmt_t aomFormat = avifImageCalcAOMFmt(image, alphaOnly, &yShift);
+    aom_img_fmt_t aomFormat = avifImageCalcAOMFmt(image, alpha, &yShift);
     if (aomFormat == AOM_IMG_FMT_NONE) {
         return AVIF_FALSE;
     }
@@ -250,40 +246,7 @@
     struct aom_codec_enc_cfg cfg;
     aom_codec_enc_config_default(encoder_interface, &cfg, 0);
 
-    // Profile 0.  8-bit and 10-bit 4:2:0 and 4:0:0 only.
-    // Profile 1.  8-bit and 10-bit 4:4:4
-    // Profile 2.  8-bit and 10-bit 4:2:2
-    //            12-bit  4:0:0, 4:2:2 and 4:4:4
-    if (image->depth == 12) {
-        // Only profile 2 can handle 12 bit
-        cfg.g_profile = 2;
-    } else {
-        // 8-bit or 10-bit
-
-        if (alphaOnly) {
-            // Assuming aomImage->monochrome makes it 4:0:0
-            cfg.g_profile = 0;
-        } else {
-            switch (image->yuvFormat) {
-                case AVIF_PIXEL_FORMAT_YUV444:
-                    cfg.g_profile = 1;
-                    break;
-                case AVIF_PIXEL_FORMAT_YUV422:
-                    cfg.g_profile = 2;
-                    break;
-                case AVIF_PIXEL_FORMAT_YUV420:
-                    cfg.g_profile = 0;
-                    break;
-                case AVIF_PIXEL_FORMAT_YV12:
-                    cfg.g_profile = 0;
-                    break;
-                case AVIF_PIXEL_FORMAT_NONE:
-                default:
-                    break;
-            }
-        }
-    }
-
+    cfg.g_profile = codec->configBox.seqProfile;
     cfg.g_bit_depth = image->depth;
     cfg.g_input_bit_depth = image->depth;
     cfg.g_w = image->width;
@@ -292,32 +255,9 @@
         cfg.g_threads = encoder->maxThreads;
     }
 
-    // TODO: Choose correct value from Annex A.3 table: https://aomediacodec.github.io/av1-spec/av1-spec.pdf
-    uint8_t seqLevelIdx0 = 31;
-    if ((image->width <= 8192) && (image->height <= 4352) && ((image->width * image->height) <= 8912896)) {
-        // Image is 5.1 compatible
-        seqLevelIdx0 = 13; // 5.1
-    }
-
-    outputConfig->seqProfile = (uint8_t)cfg.g_profile;
-    outputConfig->seqLevelIdx0 = seqLevelIdx0;
-    outputConfig->seqTier0 = 0;
-    outputConfig->highBitdepth = (image->depth > 8) ? 1 : 0;
-    outputConfig->twelveBit = (image->depth == 12) ? 1 : 0;
-    outputConfig->monochrome = alphaOnly ? 1 : 0;
-    outputConfig->chromaSubsamplingX = (uint8_t)formatInfo.chromaShiftX;
-    outputConfig->chromaSubsamplingY = (uint8_t)formatInfo.chromaShiftY;
-
-    // TODO: choose the correct one from below:
-    //   * 0 - CSP_UNKNOWN   Unknown (in this case the source video transfer function must be signaled outside the AV1 bitstream)
-    //   * 1 - CSP_VERTICAL  Horizontally co-located with (0, 0) luma sample, vertical position in the middle between two luma samples
-    //   * 2 - CSP_COLOCATED co-located with (0, 0) luma sample
-    //   * 3 - CSP_RESERVED
-    outputConfig->chromaSamplePosition = 0;
-
     int minQuantizer = AVIF_CLAMP(encoder->minQuantizer, 0, 63);
     int maxQuantizer = AVIF_CLAMP(encoder->maxQuantizer, 0, 63);
-    if (alphaOnly) {
+    if (alpha) {
         minQuantizer = AVIF_QUANTIZER_LOSSLESS;
         maxQuantizer = AVIF_QUANTIZER_LOSSLESS;
     }
@@ -351,7 +291,7 @@
     uint32_t uvHeight = image->height >> yShift;
     aom_image_t * aomImage = aom_img_alloc(NULL, aomFormat, image->width, image->height, 16);
 
-    if (alphaOnly) {
+    if (alpha) {
         aomImage->range = AOM_CR_FULL_RANGE; // Alpha is always full range
         aom_codec_control(&aomEncoder, AV1E_SET_COLOR_RANGE, aomImage->range);
         aomImage->monochrome = 1;
@@ -406,7 +346,7 @@
         if (pkt == NULL)
             break;
         if (pkt->kind == AOM_CODEC_CX_FRAME_PKT) {
-            avifRWDataSet(outputOBU, pkt->data.frame.buf, pkt->data.frame.sz);
+            avifRWDataSet(obu, pkt->data.frame.buf, pkt->data.frame.sz);
             success = AVIF_TRUE;
             break;
         }
@@ -417,19 +357,6 @@
     return success;
 }
 
-static avifBool aomCodecEncodeImage(avifCodec * codec, avifImage * image, avifEncoder * encoder, avifRWData * obu, avifBool alpha)
-{
-    if (!encodeOBU(image, alpha, encoder, obu, &codec->internal->config)) {
-        return AVIF_FALSE;
-    }
-    return AVIF_TRUE;
-}
-
-static void aomCodecGetConfigurationBox(avifCodec * codec, avifCodecConfigurationBox * outConfig)
-{
-    memcpy(outConfig, &codec->internal->config, sizeof(avifCodecConfigurationBox));
-}
-
 const char * avifCodecVersionAOM(void)
 {
     return aom_codec_version_str();
@@ -443,7 +370,6 @@
     codec->alphaLimitedRange = aomCodecAlphaLimitedRange;
     codec->getNextImage = aomCodecGetNextImage;
     codec->encodeImage = aomCodecEncodeImage;
-    codec->getConfigurationBox = aomCodecGetConfigurationBox;
     codec->destroyInternal = aomCodecDestroyInternal;
 
     codec->internal = (struct avifCodecInternal *)avifAlloc(sizeof(struct avifCodecInternal));
diff --git a/src/codec_rav1e.c b/src/codec_rav1e.c
index 0d1c522..a27380d 100644
--- a/src/codec_rav1e.c
+++ b/src/codec_rav1e.c
@@ -9,7 +9,7 @@
 
 struct avifCodecInternal
 {
-    avifCodecConfigurationBox config;
+    uint32_t unused; // rav1e codec has no state
 };
 
 static void rav1eCodecDestroyInternal(avifCodec * codec)
@@ -26,6 +26,8 @@
 
 static avifBool rav1eCodecEncodeImage(avifCodec * codec, avifImage * image, avifEncoder * encoder, avifRWData * obu, avifBool alpha)
 {
+    (void)codec; // unused
+
     avifBool success = AVIF_FALSE;
 
     RaConfig * rav1eConfig = NULL;
@@ -58,9 +60,6 @@
         }
     }
 
-    avifPixelFormatInfo formatInfo;
-    avifGetPixelFormatInfo(image->yuvFormat, &formatInfo);
-
     rav1eConfig = rav1e_config_default();
     if (rav1e_config_set_pixel_format(
             rav1eConfig, (uint8_t)image->depth, chromaSampling, RA_CHROMA_SAMPLE_POSITION_UNKNOWN, rav1eRange) < 0) {
@@ -92,57 +91,6 @@
         goto cleanup;
     }
 
-    // Profile 0.  8-bit and 10-bit 4:2:0 and 4:0:0 only.
-    // Profile 1.  8-bit and 10-bit 4:4:4
-    // Profile 2.  8-bit and 10-bit 4:2:2
-    //            12-bit  4:0:0, 4:2:2 and 4:4:4
-    uint8_t seqProfile = 0;
-    if (image->depth == 12) {
-        // Only seqProfile 2 can handle 12 bit
-        seqProfile = 2;
-    } else {
-        // 8-bit or 10-bit
-
-        if (alpha) {
-            seqProfile = 0;
-        } else {
-            switch (image->yuvFormat) {
-                case AVIF_PIXEL_FORMAT_YUV444:
-                    seqProfile = 1;
-                    break;
-                case AVIF_PIXEL_FORMAT_YUV422:
-                    seqProfile = 2;
-                    break;
-                case AVIF_PIXEL_FORMAT_YUV420:
-                    seqProfile = 0;
-                    break;
-                case AVIF_PIXEL_FORMAT_YV12:
-                    seqProfile = 0;
-                    break;
-                case AVIF_PIXEL_FORMAT_NONE:
-                default:
-                    break;
-            }
-        }
-    }
-
-    // TODO: Choose correct value from Annex A.3 table: https://aomediacodec.github.io/av1-spec/av1-spec.pdf
-    uint8_t seqLevelIdx0 = 31;
-    if ((image->width <= 8192) && (image->height <= 4352) && ((image->width * image->height) <= 8912896)) {
-        // Image is 5.1 compatible
-        seqLevelIdx0 = 13; // 5.1
-    }
-
-    memset(&codec->internal->config, 0, sizeof(avifCodecConfigurationBox));
-    codec->internal->config.seqProfile = seqProfile;
-    codec->internal->config.seqLevelIdx0 = seqLevelIdx0;
-    codec->internal->config.seqTier0 = 0;
-    codec->internal->config.highBitdepth = (image->depth > 8) ? 1 : 0;
-    codec->internal->config.twelveBit = (image->depth == 12) ? 1 : 0;
-    codec->internal->config.monochrome = alpha ? 1 : 0;
-    codec->internal->config.chromaSubsamplingX = (uint8_t)formatInfo.chromaShiftX;
-    codec->internal->config.chromaSubsamplingY = (uint8_t)formatInfo.chromaShiftY;
-
     if (encoder->tileRowsLog2 != 0) {
         int tileRowsLog2 = AVIF_CLAMP(encoder->tileRowsLog2, 0, 6);
         if (rav1e_config_parse_int(rav1eConfig, "tile_rows", 1 << tileRowsLog2) == -1) {
@@ -217,11 +165,6 @@
     return success;
 }
 
-static void rav1eCodecGetConfigurationBox(avifCodec * codec, avifCodecConfigurationBox * outConfig)
-{
-    memcpy(outConfig, &codec->internal->config, sizeof(avifCodecConfigurationBox));
-}
-
 const char * avifCodecVersionRav1e(void)
 {
     return "0"; // https://github.com/xiph/rav1e/issues/1801
@@ -233,7 +176,6 @@
     memset(codec, 0, sizeof(struct avifCodec));
     codec->open = rav1eCodecOpen;
     codec->encodeImage = rav1eCodecEncodeImage;
-    codec->getConfigurationBox = rav1eCodecGetConfigurationBox;
     codec->destroyInternal = rav1eCodecDestroyInternal;
 
     codec->internal = (struct avifCodecInternal *)avifAlloc(sizeof(struct avifCodecInternal));
diff --git a/src/write.c b/src/write.c
index 23774a1..b9c6447 100644
--- a/src/write.c
+++ b/src/write.c
@@ -21,6 +21,7 @@
 static const size_t alphaURNSize = sizeof(alphaURN);
 
 static avifBool avifImageIsOpaque(avifImage * image);
+static void fillConfigBox(avifCodec * codec, avifImage * image, avifBool alpha);
 static void writeConfigBox(avifRWStream * s, avifCodecConfigurationBox * cfg);
 
 avifEncoder * avifEncoderCreate(void)
@@ -65,6 +66,17 @@
         }
     }
 
+    // -----------------------------------------------------------------------
+    // Pre-fill config boxes based on image (codec can query/update later)
+
+    fillConfigBox(codec[AVIF_CODEC_PLANES_COLOR], image, AVIF_FALSE);
+    if (codec[AVIF_CODEC_PLANES_ALPHA]) {
+        fillConfigBox(codec[AVIF_CODEC_PLANES_ALPHA], image, AVIF_TRUE);
+    }
+
+    // -----------------------------------------------------------------------
+    // Begin write stream
+
     avifRWStream s;
     avifRWStreamStart(&s, output);
 
@@ -273,9 +285,7 @@
             ++ipcoIndex;
             ipmaPush(&ipmaColor, ipcoIndex);
 
-            avifCodecConfigurationBox colorConfig;
-            codec[AVIF_CODEC_PLANES_COLOR]->getConfigurationBox(codec[AVIF_CODEC_PLANES_COLOR], &colorConfig);
-            writeConfigBox(&s, &colorConfig);
+            writeConfigBox(&s, &codec[AVIF_CODEC_PLANES_COLOR]->configBox);
             ++ipcoIndex;
             ipmaPush(&ipmaColor, ipcoIndex);
 
@@ -287,9 +297,7 @@
                 ++ipcoIndex;
                 ipmaPush(&ipmaAlpha, ipcoIndex);
 
-                avifCodecConfigurationBox alphaConfig;
-                codec[AVIF_CODEC_PLANES_ALPHA]->getConfigurationBox(codec[AVIF_CODEC_PLANES_ALPHA], &alphaConfig);
-                writeConfigBox(&s, &alphaConfig);
+                writeConfigBox(&s, &codec[AVIF_CODEC_PLANES_ALPHA]->configBox);
                 ++ipcoIndex;
                 ipmaPush(&ipmaAlpha, ipcoIndex);
 
@@ -409,6 +417,70 @@
     return AVIF_TRUE;
 }
 
+static void fillConfigBox(avifCodec * codec, avifImage * image, avifBool alpha)
+{
+    avifPixelFormatInfo formatInfo;
+    avifGetPixelFormatInfo(image->yuvFormat, &formatInfo);
+
+    // Profile 0.  8-bit and 10-bit 4:2:0 and 4:0:0 only.
+    // Profile 1.  8-bit and 10-bit 4:4:4
+    // Profile 2.  8-bit and 10-bit 4:2:2
+    //            12-bit  4:0:0, 4:2:2 and 4:4:4
+    uint8_t seqProfile = 0;
+    if (image->depth == 12) {
+        // Only seqProfile 2 can handle 12 bit
+        seqProfile = 2;
+    } else {
+        // 8-bit or 10-bit
+
+        if (alpha) {
+            seqProfile = 0;
+        } else {
+            switch (image->yuvFormat) {
+                case AVIF_PIXEL_FORMAT_YUV444:
+                    seqProfile = 1;
+                    break;
+                case AVIF_PIXEL_FORMAT_YUV422:
+                    seqProfile = 2;
+                    break;
+                case AVIF_PIXEL_FORMAT_YUV420:
+                    seqProfile = 0;
+                    break;
+                case AVIF_PIXEL_FORMAT_YV12:
+                    seqProfile = 0;
+                    break;
+                case AVIF_PIXEL_FORMAT_NONE:
+                default:
+                    break;
+            }
+        }
+    }
+
+    // TODO: Choose correct value from Annex A.3 table: https://aomediacodec.github.io/av1-spec/av1-spec.pdf
+    uint8_t seqLevelIdx0 = 31;
+    if ((image->width <= 8192) && (image->height <= 4352) && ((image->width * image->height) <= 8912896)) {
+        // Image is 5.1 compatible
+        seqLevelIdx0 = 13; // 5.1
+    }
+
+    memset(&codec->configBox, 0, sizeof(avifCodecConfigurationBox));
+    codec->configBox.seqProfile = seqProfile;
+    codec->configBox.seqLevelIdx0 = seqLevelIdx0;
+    codec->configBox.seqTier0 = 0;
+    codec->configBox.highBitdepth = (image->depth > 8) ? 1 : 0;
+    codec->configBox.twelveBit = (image->depth == 12) ? 1 : 0;
+    codec->configBox.monochrome = alpha ? 1 : 0;
+    codec->configBox.chromaSubsamplingX = (uint8_t)formatInfo.chromaShiftX;
+    codec->configBox.chromaSubsamplingY = (uint8_t)formatInfo.chromaShiftY;
+
+    // TODO: choose the correct one from below:
+    //   * 0 - CSP_UNKNOWN   Unknown (in this case the source video transfer function must be signaled outside the AV1 bitstream)
+    //   * 1 - CSP_VERTICAL  Horizontally co-located with (0, 0) luma sample, vertical position in the middle between two luma samples
+    //   * 2 - CSP_COLOCATED co-located with (0, 0) luma sample
+    //   * 3 - CSP_RESERVED
+    codec->configBox.chromaSamplePosition = 0;
+}
+
 static void writeConfigBox(avifRWStream * s, avifCodecConfigurationBox * cfg)
 {
     avifBoxMarker av1C = avifRWStreamWriteBox(s, "av1C", -1, 0);