Add avifImagePlaneWidth/Height() in avif.h

Add avifImagePlane(), avifImagePlaneRowBytes().
Add AVIF_CHAN_A to avifChannelIndex.
diff --git a/include/avif/avif.h b/include/avif/avif.h
index 297a729..3ff06ad 100644
--- a/include/avif/avif.h
+++ b/include/avif/avif.h
@@ -102,7 +102,10 @@
     // These can be used as the index for the yuvPlanes and yuvRowBytes arrays in avifImage.
     AVIF_CHAN_Y = 0,
     AVIF_CHAN_U = 1,
-    AVIF_CHAN_V = 2
+    AVIF_CHAN_V = 2,
+
+    // This may not be used in yuvPlanes and yuvRowBytes, but is available for use with avifImagePlane().
+    AVIF_CHAN_A = 3
 } avifChannelIndex;
 
 // ---------------------------------------------------------------------------
@@ -1103,6 +1106,11 @@
 
 // Helpers
 AVIF_API avifBool avifImageUsesU16(const avifImage * image);
+// channel can be an avifChannelIndex.
+AVIF_API uint8_t * avifImagePlane(const avifImage * image, int channel);
+AVIF_API uint32_t avifImagePlaneRowBytes(const avifImage * image, int channel);
+AVIF_API uint32_t avifImagePlaneWidth(const avifImage * image, int channel);
+AVIF_API uint32_t avifImagePlaneHeight(const avifImage * image, int channel);
 
 // Returns AVIF_TRUE if input begins with a valid FileTypeBox (ftyp) that supports
 // either the brand 'avif' or 'avis' (or both), without performing any allocations.
diff --git a/src/avif.c b/src/avif.c
index ac9d3bb..eb5bbf4 100644
--- a/src/avif.c
+++ b/src/avif.c
@@ -404,6 +404,66 @@
     return (image->depth > 8);
 }
 
+uint8_t * avifImagePlane(const avifImage * image, int channel)
+{
+    if ((channel == AVIF_CHAN_Y) || (channel == AVIF_CHAN_U) || (channel == AVIF_CHAN_V)) {
+        return image->yuvPlanes[channel];
+    }
+    if (channel == AVIF_CHAN_A) {
+        return image->alphaPlane;
+    }
+    return NULL;
+}
+
+uint32_t avifImagePlaneRowBytes(const avifImage * image, int channel)
+{
+    if ((channel == AVIF_CHAN_Y) || (channel == AVIF_CHAN_U) || (channel == AVIF_CHAN_V)) {
+        return image->yuvRowBytes[channel];
+    }
+    if (channel == AVIF_CHAN_A) {
+        return image->alphaRowBytes;
+    }
+    return 0;
+}
+
+uint32_t avifImagePlaneWidth(const avifImage * image, int channel)
+{
+    if (channel == AVIF_CHAN_Y) {
+        return image->width;
+    }
+    if ((channel == AVIF_CHAN_U) || (channel == AVIF_CHAN_V)) {
+        avifPixelFormatInfo formatInfo;
+        avifGetPixelFormatInfo(image->yuvFormat, &formatInfo);
+        if (formatInfo.monochrome) {
+            return 0;
+        }
+        return (image->width + formatInfo.chromaShiftX) >> formatInfo.chromaShiftX;
+    }
+    if ((channel == AVIF_CHAN_A) && (image->alphaPlane)) {
+        return image->width;
+    }
+    return 0;
+}
+
+uint32_t avifImagePlaneHeight(const avifImage * image, int channel)
+{
+    if (channel == AVIF_CHAN_Y) {
+        return image->height;
+    }
+    if ((channel == AVIF_CHAN_U) || (channel == AVIF_CHAN_V)) {
+        avifPixelFormatInfo formatInfo;
+        avifGetPixelFormatInfo(image->yuvFormat, &formatInfo);
+        if (formatInfo.monochrome) {
+            return 0;
+        }
+        return (image->height + formatInfo.chromaShiftY) >> formatInfo.chromaShiftY;
+    }
+    if ((channel == AVIF_CHAN_A) && (image->alphaPlane)) {
+        return image->height;
+    }
+    return 0;
+}
+
 avifBool avifDimensionsTooLarge(uint32_t width, uint32_t height, uint32_t imageSizeLimit, uint32_t imageDimensionLimit)
 {
     if (width > (imageSizeLimit / height)) {
diff --git a/tests/gtest/aviftest_helpers.cc b/tests/gtest/aviftest_helpers.cc
index c7dd3ae..0cc53ea 100644
--- a/tests/gtest/aviftest_helpers.cc
+++ b/tests/gtest/aviftest_helpers.cc
@@ -13,11 +13,6 @@
 
 namespace libavif {
 namespace testutil {
-namespace {
-
-constexpr int AVIF_CHAN_A = AVIF_CHAN_V + 1;
-
-}  // namespace
 
 //------------------------------------------------------------------------------
 
@@ -75,24 +70,12 @@
 }
 
 void FillImagePlain(avifImage* image, const uint32_t yuva[4]) {
-  avifPixelFormatInfo info;
-  avifGetPixelFormatInfo(image->yuvFormat, &info);
-
-  for (int c = 0; c < 4; c++) {
-    uint8_t* row = (c == AVIF_CHAN_A) ? image->alphaPlane : image->yuvPlanes[c];
-    if (!row) {
-      continue;
-    }
-    const uint32_t row_bytes =
-        (c == AVIF_CHAN_A) ? image->alphaRowBytes : image->yuvRowBytes[c];
-    const uint32_t plane_width =
-        (c == AVIF_CHAN_Y || c == AVIF_CHAN_A)
-            ? image->width
-            : ((image->width + info.chromaShiftX) >> info.chromaShiftX);
-    const uint32_t plane_height =
-        (c == AVIF_CHAN_Y || c == AVIF_CHAN_A)
-            ? image->height
-            : ((image->height + info.chromaShiftY) >> info.chromaShiftY);
+  for (avifChannelIndex c :
+       {AVIF_CHAN_Y, AVIF_CHAN_U, AVIF_CHAN_V, AVIF_CHAN_A}) {
+    const uint32_t plane_width = avifImagePlaneWidth(image, c);
+    const uint32_t plane_height = avifImagePlaneHeight(image, c);
+    uint8_t* row = avifImagePlane(image, c);
+    const uint32_t row_bytes = avifImagePlaneRowBytes(image, c);
     for (uint32_t y = 0; y < plane_height; ++y) {
       if (avifImageUsesU16(image)) {
         std::fill(reinterpret_cast<uint16_t*>(row),
@@ -107,24 +90,12 @@
 }
 
 void FillImageGradient(avifImage* image) {
-  avifPixelFormatInfo info;
-  avifGetPixelFormatInfo(image->yuvFormat, &info);
-
-  for (int c = 0; c < 4; c++) {
-    uint8_t* row = (c == AVIF_CHAN_A) ? image->alphaPlane : image->yuvPlanes[c];
-    if (!row) {
-      continue;
-    }
-    const uint32_t row_bytes =
-        (c == AVIF_CHAN_A) ? image->alphaRowBytes : image->yuvRowBytes[c];
-    const uint32_t plane_width =
-        (c == AVIF_CHAN_Y || c == AVIF_CHAN_A)
-            ? image->width
-            : ((image->width + info.chromaShiftX) >> info.chromaShiftX);
-    const uint32_t plane_height =
-        (c == AVIF_CHAN_Y || c == AVIF_CHAN_A)
-            ? image->height
-            : ((image->height + info.chromaShiftY) >> info.chromaShiftY);
+  for (avifChannelIndex c :
+       {AVIF_CHAN_Y, AVIF_CHAN_U, AVIF_CHAN_V, AVIF_CHAN_A}) {
+    const uint32_t plane_width = avifImagePlaneWidth(image, c);
+    const uint32_t plane_height = avifImagePlaneHeight(image, c);
+    uint8_t* row = avifImagePlane(image, c);
+    const uint32_t row_bytes = avifImagePlaneRowBytes(image, c);
     for (uint32_t y = 0; y < plane_height; ++y) {
       for (uint32_t x = 0; x < plane_width; ++x) {
         const uint32_t value = (x + y) * ((1u << image->depth) - 1u) /
@@ -186,38 +157,23 @@
   }
   assert(image1.width * image1.height > 0);
 
-  avifPixelFormatInfo info;
-  avifGetPixelFormatInfo(image1.yuvFormat, &info);
-
-  for (int c = 0; c < 4; c++) {
+  for (avifChannelIndex c :
+       {AVIF_CHAN_Y, AVIF_CHAN_U, AVIF_CHAN_V, AVIF_CHAN_A}) {
     if (ignore_alpha && c == AVIF_CHAN_A) continue;
-    uint8_t* row1 =
-        (c == AVIF_CHAN_A) ? image1.alphaPlane : image1.yuvPlanes[c];
-    uint8_t* row2 =
-        (c == AVIF_CHAN_A) ? image2.alphaPlane : image2.yuvPlanes[c];
+    const uint8_t* row1 = avifImagePlane(&image1, c);
+    const uint8_t* row2 = avifImagePlane(&image2, c);
     if (!row1 != !row2) {
       return false;
     }
-    if (!row1) {
-      continue;
-    }
-    const uint32_t row_bytes1 =
-        (c == AVIF_CHAN_A) ? image1.alphaRowBytes : image1.yuvRowBytes[c];
-    const uint32_t row_bytes2 =
-        (c == AVIF_CHAN_A) ? image2.alphaRowBytes : image2.yuvRowBytes[c];
-    const uint32_t plane_width =
-        (c == AVIF_CHAN_Y || c == AVIF_CHAN_A)
-            ? image1.width
-            : ((image1.width + info.chromaShiftX) >> info.chromaShiftX);
-    const uint32_t plane_height =
-        (c == AVIF_CHAN_Y || c == AVIF_CHAN_A)
-            ? image1.height
-            : ((image1.height + info.chromaShiftY) >> info.chromaShiftY);
+    const uint32_t row_bytes1 = avifImagePlaneRowBytes(&image1, c);
+    const uint32_t row_bytes2 = avifImagePlaneRowBytes(&image2, c);
+    const uint32_t plane_width = avifImagePlaneWidth(&image1, c);
+    const uint32_t plane_height = avifImagePlaneHeight(&image1, c);
     for (uint32_t y = 0; y < plane_height; ++y) {
       if (avifImageUsesU16(&image1)) {
-        if (!std::equal(reinterpret_cast<uint16_t*>(row1),
-                        reinterpret_cast<uint16_t*>(row1) + plane_width,
-                        reinterpret_cast<uint16_t*>(row2))) {
+        if (!std::equal(reinterpret_cast<const uint16_t*>(row1),
+                        reinterpret_cast<const uint16_t*>(row1) + plane_width,
+                        reinterpret_cast<const uint16_t*>(row2))) {
           return false;
         }
       } else {
@@ -241,29 +197,15 @@
   assert(from.yuvFormat == to->yuvFormat);
   assert(from.yuvRange == to->yuvRange);
 
-  avifPixelFormatInfo info;
-  avifGetPixelFormatInfo(from.yuvFormat, &info);
-
-  for (int c = 0; c < 4; c++) {
-    const uint8_t* from_row =
-        (c == AVIF_CHAN_A) ? from.alphaPlane : from.yuvPlanes[c];
-    uint8_t* to_row = (c == AVIF_CHAN_A) ? to->alphaPlane : to->yuvPlanes[c];
+  for (avifChannelIndex c :
+       {AVIF_CHAN_Y, AVIF_CHAN_U, AVIF_CHAN_V, AVIF_CHAN_A}) {
+    const uint8_t* from_row = avifImagePlane(&from, c);
+    uint8_t* to_row = avifImagePlane(to, c);
     assert(!from_row == !to_row);
-    if (!from_row) {
-      continue;
-    }
-    const uint32_t from_row_bytes =
-        (c == AVIF_CHAN_A) ? from.alphaRowBytes : from.yuvRowBytes[c];
-    const uint32_t to_row_bytes =
-        (c == AVIF_CHAN_A) ? to->alphaRowBytes : to->yuvRowBytes[c];
-    const uint32_t plane_width =
-        (c == AVIF_CHAN_Y || c == AVIF_CHAN_A)
-            ? from.width
-            : ((from.width + info.chromaShiftX) >> info.chromaShiftX);
-    const uint32_t plane_height =
-        (c == AVIF_CHAN_Y || c == AVIF_CHAN_A)
-            ? from.height
-            : ((from.height + info.chromaShiftY) >> info.chromaShiftY);
+    const uint32_t from_row_bytes = avifImagePlaneRowBytes(&from, c);
+    const uint32_t to_row_bytes = avifImagePlaneRowBytes(to, c);
+    const uint32_t plane_width = avifImagePlaneWidth(&from, c);
+    const uint32_t plane_height = avifImagePlaneHeight(&from, c);
     for (uint32_t y = 0; y < plane_height; ++y) {
       if (avifImageUsesU16(&from)) {
         std::copy(reinterpret_cast<const uint16_t*>(from_row),