Large planes refactor to properly support any YUV format (YV12 and studio range still need work)
diff --git a/src/write.c b/src/write.c
index 1b50eee..ce0981c 100644
--- a/src/write.c
+++ b/src/write.c
@@ -13,6 +13,10 @@
 
 avifResult avifImageWrite(avifImage * image, avifRawData * output, int quality)
 {
+    if ((image->depth != 8) && (image->depth != 10) && (image->depth != 12)) {
+        return AVIF_RESULT_UNSUPPORTED_DEPTH;
+    }
+
     avifResult result = AVIF_RESULT_UNKNOWN_ERROR;
     avifRawData colorOBU = AVIF_RAW_DATA_EMPTY;
     avifRawData alphaOBU = AVIF_RAW_DATA_EMPTY;
@@ -23,45 +27,37 @@
     // -----------------------------------------------------------------------
     // Reformat pixels, if need be
 
-    avifPixelFormat dstPixelFormat = image->pixelFormat;
-    if (image->pixelFormat == AVIF_PIXEL_FORMAT_RGBA) {
-        // AV1 doesn't support RGB, reformat
-        dstPixelFormat = AVIF_PIXEL_FORMAT_YUV444;
+    if (!image->width || !image->height || !image->depth) {
+        result = AVIF_RESULT_NO_CONTENT;
+        goto writeCleanup;
     }
 
-#if 0 // TODO: implement choice in depth
-    int dstDepth = AVIF_CLAMP(image->depth, 8, 12);
-    if ((dstDepth == 9) || (dstDepth == 11)) {
-        ++dstDepth;
-    }
-#else
-    int dstDepth = 12;
-#endif
-
-    avifImage * pixelImage = image;
-    avifImage * reformattedImage = NULL;
-    if ((image->pixelFormat != dstPixelFormat) || (image->depth != dstDepth)) {
-        reformattedImage = avifImageCreate();
-        avifResult reformatResult = avifImageReformatPixels(image, reformattedImage, dstPixelFormat, dstDepth);
-        if (reformatResult != AVIF_RESULT_OK) {
-            result = reformatResult;
+    if ((image->yuvFormat == AVIF_PIXEL_FORMAT_NONE) || !image->yuvPlanes[AVIF_CHAN_Y] || !image->yuvPlanes[AVIF_CHAN_U] || !image->yuvPlanes[AVIF_CHAN_V]) {
+        if (!image->rgbPlanes[AVIF_CHAN_R] || !image->rgbPlanes[AVIF_CHAN_G] || !image->rgbPlanes[AVIF_CHAN_B]) {
+            result = AVIF_RESULT_NO_CONTENT;
             goto writeCleanup;
         }
-        pixelImage = reformattedImage;
+
+        avifImageFreePlanes(image, AVIF_PLANES_YUV);
+        if (image->yuvFormat == AVIF_PIXEL_FORMAT_NONE) {
+            result = AVIF_RESULT_NO_YUV_FORMAT_SELECTED;
+            goto writeCleanup;
+        }
+        avifImageRGBToYUV(image);
     }
 
     // -----------------------------------------------------------------------
     // Encode AV1 OBUs
 
-    if (!encodeOBU(pixelImage, AVIF_FALSE, &colorOBU, quality)) {
+    if (!encodeOBU(image, AVIF_FALSE, &colorOBU, quality)) {
         result = AVIF_RESULT_ENCODE_COLOR_FAILED;
         goto writeCleanup;
     }
 
     // Skip alpha creation on opaque images
     avifBool hasAlpha = AVIF_FALSE;
-    if (!avifImageIsOpaque(pixelImage)) {
-        if (!encodeOBU(pixelImage, AVIF_TRUE, &alphaOBU, quality)) {
+    if (!avifImageIsOpaque(image)) {
+        if (!encodeOBU(image, AVIF_TRUE, &alphaOBU, quality)) {
             result = AVIF_RESULT_ENCODE_ALPHA_FAILED;
             goto writeCleanup;
         }
@@ -189,28 +185,88 @@
     // Cleanup
 
 writeCleanup:
-    if (reformattedImage) {
-        avifImageDestroy(reformattedImage);
-    }
     avifRawDataFree(&colorOBU);
     avifRawDataFree(&alphaOBU);
     return result;
 }
 
+static aom_img_fmt_t avifImageCalcAOMFmt(avifImage * image, avifBool alphaOnly, int * yShift)
+{
+    *yShift = 0;
+
+    aom_img_fmt_t fmt;
+    if (alphaOnly) {
+        // We're going monochrome, who cares about chroma quality
+        fmt = AOM_IMG_FMT_I420;
+        *yShift = 1;
+    } else {
+        switch (image->yuvFormat) {
+            case AVIF_PIXEL_FORMAT_YUV444:
+                fmt = AOM_IMG_FMT_I444;
+                break;
+            case AVIF_PIXEL_FORMAT_YUV422:
+                fmt = AOM_IMG_FMT_I422;
+                break;
+            case AVIF_PIXEL_FORMAT_YUV420:
+                fmt = AOM_IMG_FMT_I420;
+                *yShift = 1;
+                break;
+            case AVIF_PIXEL_FORMAT_YV12:
+                fmt = AOM_IMG_FMT_YV12;
+                *yShift = 1;
+                break;
+            default:
+                return AOM_IMG_FMT_NONE;
+        }
+    }
+
+    if (image->depth > 8) {
+        fmt |= AOM_IMG_FMT_HIGHBITDEPTH;
+    }
+
+    return fmt;
+}
+
 static avifBool encodeOBU(avifImage * image, avifBool alphaOnly, avifRawData * outputOBU, int quality)
 {
     avifBool success = AVIF_FALSE;
     aom_codec_iface_t * encoder_interface = aom_codec_av1_cx();
     aom_codec_ctx_t encoder;
 
+    int yShift = 0;
+    aom_img_fmt_t aomFormat = avifImageCalcAOMFmt(image, alphaOnly, &yShift);
+    if (aomFormat == AOM_IMG_FMT_NONE) {
+        return AVIF_FALSE;
+    }
+
     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
-    cfg.g_profile = 2;
-    cfg.g_bit_depth = AOM_BITS_12;
-    cfg.g_input_bit_depth = 12;
+    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;
+            }
+        }
+    }
+
+    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_threads = ...;
@@ -223,38 +279,46 @@
         cfg.rc_max_quantizer = quality;
     }
 
-    aom_codec_enc_init(&encoder, encoder_interface, &cfg, AOM_CODEC_USE_HIGHBITDEPTH);
-    aom_codec_control(&encoder, AV1E_SET_COLOR_RANGE, AOM_CR_FULL_RANGE);
+    uint32_t encoderFlags = 0;
+    if (image->depth > 8) {
+        encoderFlags |= AOM_CODEC_USE_HIGHBITDEPTH;
+    }
+    aom_codec_enc_init(&encoder, encoder_interface, &cfg, encoderFlags);
     if (lossless) {
         aom_codec_control(&encoder, AV1E_SET_LOSSLESS, 1);
     }
 
-    aom_image_t * aomImage = aom_img_alloc(NULL, AOM_IMG_FMT_I44416, image->width, image->height, 16);
-    aomImage->range = AOM_CR_FULL_RANGE; // always use full range
-    if (alphaOnly) {
-    }
+    int uvHeight = image->height >> yShift;
+    aom_image_t * aomImage = aom_img_alloc(NULL, aomFormat, image->width, image->height, 16);
 
     if (alphaOnly) {
+        aomImage->range = AOM_CR_FULL_RANGE; // Alpha is always full range
+        aom_codec_control(&encoder, AV1E_SET_COLOR_RANGE, aomImage->range);
         aomImage->monochrome = 1;
         for (int j = 0; j < image->height; ++j) {
-            for (int i = 0; i < image->width; ++i) {
-                for (int plane = 0; plane < 3; ++plane) {
-                    uint16_t * planeChannel = (uint16_t *)&aomImage->planes[plane][(j * aomImage->stride[plane]) + (2 * i)];
-                    if (plane == 0) {
-                        *planeChannel = image->planes[3][i + (j * image->strides[plane])];
-                    } else {
-                        *planeChannel = 0;
-                    }
-                }
-            }
+            uint8_t * srcAlphaRow = &image->alphaPlane[j * image->alphaRowBytes];
+            uint8_t * dstAlphaRow = &aomImage->planes[0][j * aomImage->stride[0]];
+            memcpy(dstAlphaRow, srcAlphaRow, image->alphaRowBytes);
+        }
+
+        for (int j = 0; j < uvHeight; ++j) {
+            // Zero out U and V
+            memset(&aomImage->planes[1][j * aomImage->stride[1]], 0, aomImage->stride[1]);
+            memset(&aomImage->planes[2][j * aomImage->stride[2]], 0, aomImage->stride[2]);
         }
     } else {
+        aomImage->range = (image->yuvRange == AVIF_RANGE_FULL) ? AOM_CR_FULL_RANGE : AOM_CR_STUDIO_RANGE;
+        aom_codec_control(&encoder, AV1E_SET_COLOR_RANGE, aomImage->range);
         for (int j = 0; j < image->height; ++j) {
-            for (int i = 0; i < image->width; ++i) {
-                for (int plane = 0; plane < 3; ++plane) {
-                    uint16_t * planeChannel = (uint16_t *)&aomImage->planes[plane][(j * aomImage->stride[plane]) + (2 * i)];
-                    *planeChannel = image->planes[plane][i + (j * image->strides[plane])];
+            for (int yuvPlane = 0; yuvPlane < 3; ++yuvPlane) {
+                if ((yuvPlane > 0) && (j >= uvHeight)) {
+                    // Bail out if we're on a half-height UV plane
+                    break;
                 }
+
+                uint8_t * srcRow = &image->yuvPlanes[yuvPlane][j * image->yuvRowBytes[yuvPlane]];
+                uint8_t * dstRow = &aomImage->planes[yuvPlane][j * aomImage->stride[yuvPlane]];
+                memcpy(dstRow, srcRow, image->yuvRowBytes[yuvPlane]);
             }
         }
     }
@@ -281,11 +345,26 @@
 
 static avifBool avifImageIsOpaque(avifImage * image)
 {
+    if (!image->alphaPlane) {
+        return AVIF_TRUE;
+    }
+
     int maxChannel = (1 << image->depth) - 1;
-    for (int j = 0; j < image->height; ++j) {
-        for (int i = 0; i < image->width; ++i) {
-            if (image->planes[3][i + (j * image->strides[3])] != maxChannel) {
-                return AVIF_FALSE;
+    if (avifImageUsesU16(image)) {
+        for (int j = 0; j < image->height; ++j) {
+            for (int i = 0; i < image->width; ++i) {
+                uint16_t * p = (uint16_t *)&image->alphaPlane[(i * 2) + (j * image->alphaRowBytes)];
+                if (*p != maxChannel) {
+                    return AVIF_FALSE;
+                }
+            }
+        }
+    } else {
+        for (int j = 0; j < image->height; ++j) {
+            for (int i = 0; i < image->width; ++i) {
+                if (image->alphaPlane[i + (j * image->alphaRowBytes)] != maxChannel) {
+                    return AVIF_FALSE;
+                }
             }
         }
     }