Widen internal API to support more complex YUV formats

Bug: skia:7901
Change-Id: I46fec08711b8b483cf58ccae733e4dc2a9689231
Reviewed-on: https://skia-review.googlesource.com/c/162280
Commit-Queue: Jim Van Verth <jvanverth@google.com>
Reviewed-by: Brian Salomon <bsalomon@google.com>
diff --git a/gm/wacky_yuv_formats.cpp b/gm/wacky_yuv_formats.cpp
index 0cc0a21..0b959cb 100644
--- a/gm/wacky_yuv_formats.cpp
+++ b/gm/wacky_yuv_formats.cpp
@@ -495,29 +495,18 @@
 class YUVGenerator : public SkImageGenerator {
 public:
     YUVGenerator(const SkImageInfo& ii,
-                 YUVFormat yuvFormat,
                  SkYUVColorSpace yuvColorSpace,
-                 SkYUVAIndex yuvaIndices[4],
-                 SkBitmap bitmaps[4])
+                 SkYUVAIndex yuvaIndices[SkYUVAIndex::kIndexCount],
+                 SkBitmap bitmaps[SkYUVSizeInfo::kMaxCount])
             : SkImageGenerator(ii)
-            , fYUVFormat(yuvFormat)
             , fYUVColorSpace(yuvColorSpace) {
         memcpy(fYUVAIndices, yuvaIndices, sizeof(fYUVAIndices));
 
-        bool used[4] = { false, false, false, false };
-        for (int i = 0; i < 4; ++i) {
-            if (yuvaIndices[i].fIndex >= 0) {
-                SkASSERT(yuvaIndices[i].fIndex < 4);
-                used[yuvaIndices[i].fIndex] = true;
-            } else {
-                SkASSERT(3 == i); // only the 'A' channel can be unspecified
-            }
-        }
+        SkAssertResult(SkYUVAIndex::AreValidIndices(fYUVAIndices, &fNumBitmaps));
+        SkASSERT(fNumBitmaps > 0 && fNumBitmaps <= SkYUVSizeInfo::kMaxCount);
 
-        for (int i = 0; i < 4; ++i) {
-            if (used[i]) {
-                fYUVBitmaps[i] = bitmaps[i];
-            }
+        for (int i = 0; i < fNumBitmaps; ++i) {
+            fYUVBitmaps[i] = bitmaps[i];
         }
     }
 
@@ -573,49 +562,53 @@
         return fFlattened.readPixels(info, pixels, rowBytes, 0, 0);
     }
 
-    bool onQueryYUV8(SkYUVSizeInfo* size, SkYUVColorSpace* yuvColorSpace) const override {
-        if (kI420_YUVFormat != fYUVFormat && kYV12_YUVFormat != fYUVFormat) {
-            return false; // currently this API only supports planar formats
+    bool onQueryYUVA8(SkYUVSizeInfo* size,
+                      SkYUVAIndex yuvaIndices[SkYUVAIndex::kIndexCount],
+                      SkYUVColorSpace* yuvColorSpace) const override {
+
+        memcpy(yuvaIndices, fYUVAIndices, sizeof(fYUVAIndices));
+        *yuvColorSpace = fYUVColorSpace;
+
+        int i = 0;
+        for ( ; i < fNumBitmaps; ++i) {
+            size->fColorTypes[i] = fYUVBitmaps[i].colorType();
+            size->fSizes[i].fWidth = fYUVBitmaps[i].width();
+            size->fSizes[i].fHeight = fYUVBitmaps[i].height();
+            size->fWidthBytes[i] = fYUVBitmaps[i].rowBytes();
+        }
+        for ( ; i < SkYUVSizeInfo::kMaxCount; ++i) {
+            size->fColorTypes[i] = kUnknown_SkColorType;
+            size->fSizes[i].fWidth = 0;
+            size->fSizes[i].fHeight = 0;
+            size->fWidthBytes[i] = 0;
         }
 
-        *yuvColorSpace = fYUVColorSpace;
-        size->fSizes[0].fWidth = fYUVBitmaps[fYUVAIndices[0].fIndex].width();
-        size->fSizes[0].fHeight = fYUVBitmaps[fYUVAIndices[0].fIndex].height();
-        size->fWidthBytes[0] = fYUVBitmaps[fYUVAIndices[0].fIndex].rowBytes();
-
-        size->fSizes[1].fWidth = fYUVBitmaps[fYUVAIndices[1].fIndex].width();
-        size->fSizes[1].fHeight = fYUVBitmaps[fYUVAIndices[1].fIndex].height();
-        size->fWidthBytes[1] = fYUVBitmaps[fYUVAIndices[1].fIndex].rowBytes();
-
-        size->fSizes[2].fWidth = fYUVBitmaps[fYUVAIndices[2].fIndex].width();
-        size->fSizes[2].fHeight = fYUVBitmaps[fYUVAIndices[2].fIndex].height();
-        size->fWidthBytes[2] = fYUVBitmaps[fYUVAIndices[2].fIndex].rowBytes();
         return true;
     }
 
-    bool onGetYUV8Planes(const SkYUVSizeInfo&, void* planes[3]) override {
-        planes[0] = fYUVBitmaps[fYUVAIndices[0].fIndex].getAddr(0, 0);
-        planes[1] = fYUVBitmaps[fYUVAIndices[1].fIndex].getAddr(0, 0);
-        planes[2] = fYUVBitmaps[fYUVAIndices[2].fIndex].getAddr(0, 0);
+    bool onGetYUVA8Planes(const SkYUVSizeInfo&, const SkYUVAIndex[SkYUVAIndex::kIndexCount],
+                          void* planes[SkYUVSizeInfo::kMaxCount]) override {
+        for (int i = 0; i < fNumBitmaps; ++i) {
+            planes[i] = fYUVBitmaps[i].getPixels();
+        }
         return true;
     }
 
 private:
-    YUVFormat       fYUVFormat;
     SkYUVColorSpace fYUVColorSpace;
-    SkYUVAIndex     fYUVAIndices[4];
-    SkBitmap        fYUVBitmaps[4];
+    SkYUVAIndex     fYUVAIndices[SkYUVAIndex::kIndexCount];
+    int             fNumBitmaps;
+    SkBitmap        fYUVBitmaps[SkYUVSizeInfo::kMaxCount];
     SkBitmap        fFlattened;
 
 };
 
 static sk_sp<SkImage> make_yuv_gen_image(const SkImageInfo& ii,
-                                         YUVFormat yuvFormat,
                                          SkYUVColorSpace yuvColorSpace,
-                                         SkYUVAIndex yuvaIndices[4],
+                                         SkYUVAIndex yuvaIndices[SkYUVAIndex::kIndexCount],
                                          SkBitmap bitmaps[]) {
-     std::unique_ptr<SkImageGenerator> gen(new YUVGenerator(ii, yuvFormat, yuvColorSpace,
-                                                            yuvaIndices, bitmaps));
+    std::unique_ptr<SkImageGenerator> gen(new YUVGenerator(ii, yuvColorSpace,
+                                                           yuvaIndices, bitmaps));
 
     return SkImage::MakeFromGenerator(std::move(gen));
 }
@@ -774,7 +767,6 @@
                     {
                         fImages[opaque][cs][format] = make_yuv_gen_image(
                                                                 fOriginalBMs[opaque].info(),
-                                                                (YUVFormat) format,
                                                                 (SkYUVColorSpace) cs,
                                                                 yuvaIndices,
                                                                 resultBMs);
diff --git a/gn/core.gni b/gn/core.gni
index f7341a3..4e51c27 100644
--- a/gn/core.gni
+++ b/gn/core.gni
@@ -327,6 +327,7 @@
   "$_src/core/SkXfermodeInterpretation.h",
   "$_src/core/SkYUVPlanesCache.cpp",
   "$_src/core/SkYUVPlanesCache.h",
+  "$_src/core/SkYUVSizeInfo.cpp",
 
   "$_src/image/SkImage.cpp",
 
@@ -419,6 +420,7 @@
   "$_include/core/SkUnPreMultiply.h",
   "$_include/core/SkVertices.h",
   "$_include/core/SkYUVAIndex.h",
+  "$_include/core/SkYUVSizeInfo.h",
 
   "$_src/jumper/SkJumper.cpp",
 
diff --git a/include/codec/SkCodec.h b/include/codec/SkCodec.h
index b0287a8..7f81399 100644
--- a/include/codec/SkCodec.h
+++ b/include/codec/SkCodec.h
@@ -351,7 +351,9 @@
      *  returns false and does not modify any of the parameters.
      *
      *  @param sizeInfo   Output parameter indicating the sizes and required
-     *                    allocation widths of the Y, U, and V planes.
+     *                    allocation widths of the Y, U, V, and A planes. Given current codec
+     *                    limitations the size of the A plane will always be 0 and the Y, U, V
+     *                    channels will always be planar.
      *  @param colorSpace Output parameter.  If non-NULL this is set to kJPEG,
      *                    otherwise this is ignored.
      */
@@ -360,7 +362,19 @@
             return false;
         }
 
-        return this->onQueryYUV8(sizeInfo, colorSpace);
+        bool result = this->onQueryYUV8(sizeInfo, colorSpace);
+        if (result) {
+            for (int i = 0; i <= 2; ++i) {
+                SkASSERT(kAlpha_8_SkColorType == sizeInfo->fColorTypes[i]);
+                SkASSERT(sizeInfo->fSizes[i].fWidth > 0 && sizeInfo->fSizes[i].fHeight > 0 &&
+                         sizeInfo->fWidthBytes[i] > 0);
+            }
+            SkASSERT(kUnknown_SkColorType == sizeInfo->fColorTypes[3]);
+            SkASSERT(!sizeInfo->fSizes[3].fWidth &&
+                     !sizeInfo->fSizes[3].fHeight &&
+                     !sizeInfo->fWidthBytes[3]);
+        }
+        return result;
     }
 
     /**
@@ -373,11 +387,12 @@
      *                    recommendation (but not smaller).
      *  @param planes     Memory for each of the Y, U, and V planes.
      */
-    Result getYUV8Planes(const SkYUVSizeInfo& sizeInfo, void* planes[3]) {
-        if (nullptr == planes || nullptr == planes[0] || nullptr == planes[1] ||
-                nullptr == planes[2]) {
+    Result getYUV8Planes(const SkYUVSizeInfo& sizeInfo, void* planes[SkYUVSizeInfo::kMaxCount]) {
+        if (!planes || !planes[0] || !planes[1] || !planes[2]) {
             return kInvalidInput;
         }
+        SkASSERT(kUnknown_SkColorType == sizeInfo.fColorTypes[3]);
+        SkASSERT(!planes[3]); // TODO: is this a fair assumption?
 
         if (!this->rewindIfNeeded()) {
             return kCouldNotRewind;
@@ -712,7 +727,8 @@
         return false;
     }
 
-    virtual Result onGetYUV8Planes(const SkYUVSizeInfo&, void*[3] /*planes*/) {
+    virtual Result onGetYUV8Planes(const SkYUVSizeInfo&,
+                                   void*[SkYUVSizeInfo::kMaxCount] /*planes*/) {
         return kUnimplemented;
     }
 
diff --git a/include/core/SkImageGenerator.h b/include/core/SkImageGenerator.h
index 9c7c9a2..646d6d5 100644
--- a/include/core/SkImageGenerator.h
+++ b/include/core/SkImageGenerator.h
@@ -86,23 +86,30 @@
      *  If decoding to YUV is supported, this returns true.  Otherwise, this
      *  returns false and does not modify any of the parameters.
      *
-     *  @param sizeInfo   Output parameter indicating the sizes and required
-     *                    allocation widths of the Y, U, and V planes.
-     *  @param colorSpace Output parameter.
+     *  @param sizeInfo    Output parameter indicating the sizes and required
+     *                     allocation widths of the Y, U, V, and A planes.
+     *  @param yuvaIndices How the YUVA planes are organized/used
+     *  @param colorSpace  Output parameter.
      */
-    bool queryYUV8(SkYUVSizeInfo* sizeInfo, SkYUVColorSpace* colorSpace) const;
+    bool queryYUVA8(SkYUVSizeInfo* sizeInfo,
+                    SkYUVAIndex yuvaIndices[SkYUVAIndex::kIndexCount],
+                    SkYUVColorSpace* colorSpace) const;
 
     /**
      *  Returns true on success and false on failure.
      *  This always attempts to perform a full decode.  If the client only
-     *  wants size, it should call queryYUV8().
+     *  wants size, it should call queryYUVA8().
      *
-     *  @param sizeInfo   Needs to exactly match the values returned by the
-     *                    query, except the WidthBytes may be larger than the
-     *                    recommendation (but not smaller).
-     *  @param planes     Memory for each of the Y, U, and V planes.
+     *  @param sizeInfo    Needs to exactly match the values returned by the
+     *                     query, except the WidthBytes may be larger than the
+     *                     recommendation (but not smaller).
+     *  @param yuvaIndices Needs to exactly match the values returned by the query.
+     *  @param planes      Memory for the Y, U, V, and A planes. Note that, depending on the
+     *                     settings in yuvaIndices, anywhere from 1..4 planes could be returned.
      */
-    bool getYUV8Planes(const SkYUVSizeInfo& sizeInfo, void* planes[3]);
+    bool getYUVA8Planes(const SkYUVSizeInfo& sizeInfo,
+                        const SkYUVAIndex yuvaIndices[SkYUVAIndex::kIndexCount],
+                        void* planes[]);
 
 #if SK_SUPPORT_GPU
     /**
@@ -163,9 +170,13 @@
     struct Options {};
     virtual bool onGetPixels(const SkImageInfo&, void*, size_t, const Options&) { return false; }
     virtual bool onIsValid(GrContext*) const { return true; }
+    virtual bool onQueryYUVA8(SkYUVSizeInfo*, SkYUVAIndex[SkYUVAIndex::kIndexCount],
+                              SkYUVColorSpace*) const { return false; }
+    virtual bool onGetYUVA8Planes(const SkYUVSizeInfo&, const SkYUVAIndex[SkYUVAIndex::kIndexCount],
+                                  void*[4] /*planes*/) { return false; }
+    // Deprecated methods
     virtual bool onQueryYUV8(SkYUVSizeInfo*, SkYUVColorSpace*) const { return false; }
     virtual bool onGetYUV8Planes(const SkYUVSizeInfo&, void*[3] /*planes*/) { return false; }
-
 #if SK_SUPPORT_GPU
     enum class TexGenType {
         kNone,           //image generator does not implement onGenerateTexture
diff --git a/include/core/SkYUVAIndex.h b/include/core/SkYUVAIndex.h
index 7cb8cf1..f52786c 100644
--- a/include/core/SkYUVAIndex.h
+++ b/include/core/SkYUVAIndex.h
@@ -38,12 +38,16 @@
     }
 
     // Index in the array of SkYUVAIndex
+    // TODO: rename as Component
     enum Index {
         kY_Index = 0,
         kU_Index = 1,
         kV_Index = 2,
-        kA_Index = 3
+        kA_Index = 3,
+
+        kLast_Index = kA_Index
     };
+    static constexpr int kIndexCount = kLast_Index + 1;
 
     /** The index is a number between -1..3 which definies which image source to read from, where -1
      * means the image source doesn't exist. The assumption is we will always have image sources for
diff --git a/include/core/SkYUVASizeInfo.h b/include/core/SkYUVASizeInfo.h
new file mode 100644
index 0000000..c6a413f
--- /dev/null
+++ b/include/core/SkYUVASizeInfo.h
@@ -0,0 +1,15 @@
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SkYUVASizeInfo_DEFINED
+#define SkYUVASizeInfo_DEFINED
+
+#include "SkYUVSizeInfo.h"
+
+using SkYUVASizeInfo = SkYUVSizeInfo;
+
+#endif // SkYUVASizeInfo_DEFINED
diff --git a/include/core/SkYUVSizeInfo.h b/include/core/SkYUVSizeInfo.h
index fc2ceba..a882e2d 100644
--- a/include/core/SkYUVSizeInfo.h
+++ b/include/core/SkYUVSizeInfo.h
@@ -8,18 +8,18 @@
 #ifndef SkYUVSizeInfo_DEFINED
 #define SkYUVSizeInfo_DEFINED
 
+#include "SkImageInfo.h"
 #include "SkSize.h"
+#include "SkYUVAIndex.h"
 
 struct SkYUVSizeInfo {
-    enum YUVIndex {
-        kY          = 0,
-        kU          = 1,
-        kV          = 2,
-    };
-    SkISize fSizes[3];
+    static constexpr auto kMaxCount = 4;
+
+    SkColorType fColorTypes[kMaxCount];
+    SkISize     fSizes[kMaxCount];
 
     /**
-     * While the widths of the Y, U, and V planes are not restricted, the
+     * While the widths of the Y, U, V and A planes are not restricted, the
      * implementation often requires that the width of the memory allocated
      * for each plane be a multiple of 8.
      *
@@ -30,7 +30,44 @@
      * include any extra padding, while, in this case, every single row of
      * the allocation must be at least "widthBytes".
      */
-    size_t fWidthBytes[3];
+    size_t      fWidthBytes[kMaxCount];
+
+    bool operator==(const SkYUVSizeInfo& that) const {
+        for (int i = 0; i < kMaxCount; ++i) {
+            if (fColorTypes[i] != that.fColorTypes[i]) {
+                return false;
+            }
+
+            if (kUnknown_SkColorType == fColorTypes[i]) {
+                SkASSERT(!fSizes[i].fWidth && !fSizes[i].fHeight && !fWidthBytes[i]);
+                SkASSERT(!that.fSizes[i].fWidth && !that.fSizes[i].fHeight && !that.fWidthBytes[i]);
+                continue;
+            }
+
+            SkASSERT(fSizes[i].fWidth && fSizes[i].fHeight && fWidthBytes[i]);
+            if (fSizes[i] != that.fSizes[i] || fWidthBytes[i] != that.fWidthBytes[i]) {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    size_t computeTotalBytes() const {
+        size_t totalBytes = 0;
+
+        for (int i = 0; i < kMaxCount; ++i) {
+            SkASSERT(kUnknown_SkColorType != fColorTypes[i] ||
+                        (!fSizes[i].fWidth && !fSizes[i].fHeight && !fWidthBytes[i]));
+
+            totalBytes += fWidthBytes[i] * fSizes[i].height();
+        }
+
+        return totalBytes;
+    }
+
+    void computePlanes(void* base, void* planes[kMaxCount]) const;
+
 };
 
 #endif // SkYUVSizeInfo_DEFINED
diff --git a/src/codec/SkCodecImageGenerator.cpp b/src/codec/SkCodecImageGenerator.cpp
index 55a6575..3005ee3 100644
--- a/src/codec/SkCodecImageGenerator.cpp
+++ b/src/codec/SkCodecImageGenerator.cpp
@@ -8,6 +8,7 @@
 #include "SkCodecImageGenerator.h"
 #include "SkMakeUnique.h"
 #include "SkPixmapPriv.h"
+#include "SkYUVAIndex.h"
 
 std::unique_ptr<SkImageGenerator> SkCodecImageGenerator::MakeFromEncodedCodec(sk_sp<SkData> data) {
     auto codec = SkCodec::MakeFromData(data);
@@ -58,13 +59,27 @@
     return SkPixmapPriv::Orient(dst, fCodec->getOrigin(), decode);
 }
 
-bool SkCodecImageGenerator::onQueryYUV8(SkYUVSizeInfo* sizeInfo, SkYUVColorSpace* colorSpace) const
-{
+bool SkCodecImageGenerator::onQueryYUVA8(SkYUVSizeInfo* sizeInfo,
+                                         SkYUVAIndex yuvaIndices[SkYUVAIndex::kIndexCount],
+                                         SkYUVColorSpace* colorSpace) const {
+    // This image generator always returns 3 separate non-interleaved planes
+    yuvaIndices[SkYUVAIndex::kY_Index].fIndex = 0;
+    yuvaIndices[SkYUVAIndex::kY_Index].fChannel = SkColorChannel::kR;
+    yuvaIndices[SkYUVAIndex::kU_Index].fIndex = 1;
+    yuvaIndices[SkYUVAIndex::kU_Index].fChannel = SkColorChannel::kR;
+    yuvaIndices[SkYUVAIndex::kV_Index].fIndex = 2;
+    yuvaIndices[SkYUVAIndex::kV_Index].fChannel = SkColorChannel::kR;
+    yuvaIndices[SkYUVAIndex::kA_Index].fIndex = -1;
+    yuvaIndices[SkYUVAIndex::kA_Index].fChannel = SkColorChannel::kR;
+
     return fCodec->queryYUV8(sizeInfo, colorSpace);
 }
 
-bool SkCodecImageGenerator::onGetYUV8Planes(const SkYUVSizeInfo& sizeInfo, void* planes[3]) {
+bool SkCodecImageGenerator::onGetYUVA8Planes(const SkYUVSizeInfo& sizeInfo,
+                                             const SkYUVAIndex indices[SkYUVAIndex::kIndexCount],
+                                             void* planes[]) {
     SkCodec::Result result = fCodec->getYUV8Planes(sizeInfo, planes);
+    // TODO: check indices
 
     switch (result) {
         case SkCodec::kSuccess:
diff --git a/src/codec/SkCodecImageGenerator.h b/src/codec/SkCodecImageGenerator.h
index b9c9d3c..9a64b7c 100644
--- a/src/codec/SkCodecImageGenerator.h
+++ b/src/codec/SkCodecImageGenerator.h
@@ -22,12 +22,14 @@
 protected:
     sk_sp<SkData> onRefEncodedData() override;
 
-    bool onGetPixels(const SkImageInfo& info, void* pixels, size_t rowBytes, const Options& opts)
-                     override;
+    bool onGetPixels(
+        const SkImageInfo& info, void* pixels, size_t rowBytes, const Options& opts) override;
 
-    bool onQueryYUV8(SkYUVSizeInfo*, SkYUVColorSpace*) const override;
+    bool onQueryYUVA8(
+        SkYUVSizeInfo*, SkYUVAIndex[SkYUVAIndex::kIndexCount], SkYUVColorSpace*) const override;
 
-    bool onGetYUV8Planes(const SkYUVSizeInfo&, void* planes[3]) override;
+    bool onGetYUVA8Planes(const SkYUVSizeInfo&, const SkYUVAIndex[SkYUVAIndex::kIndexCount],
+                          void* planes[]) override;
 
 private:
     /*
diff --git a/src/codec/SkJpegCodec.cpp b/src/codec/SkJpegCodec.cpp
index 257b764..f5c23a2 100644
--- a/src/codec/SkJpegCodec.cpp
+++ b/src/codec/SkJpegCodec.cpp
@@ -831,11 +831,18 @@
     }
 
     jpeg_component_info * comp_info = dinfo->comp_info;
-    for (auto i : { SkYUVSizeInfo::kY, SkYUVSizeInfo::kU, SkYUVSizeInfo::kV }) {
+    for (auto i : { SkYUVAIndex::kY_Index, SkYUVAIndex::kU_Index, SkYUVAIndex::kV_Index }) {
+        sizeInfo->fColorTypes[i] = kAlpha_8_SkColorType;
         sizeInfo->fSizes[i].set(comp_info[i].downsampled_width, comp_info[i].downsampled_height);
         sizeInfo->fWidthBytes[i] = comp_info[i].width_in_blocks * DCTSIZE;
     }
 
+    // JPEG never has an alpha channel
+    sizeInfo->fColorTypes[SkYUVAIndex::kA_Index] = kUnknown_SkColorType;
+    sizeInfo->fSizes[SkYUVAIndex::kA_Index].fHeight =
+        sizeInfo->fSizes[SkYUVAIndex::kA_Index].fWidth =
+        sizeInfo->fWidthBytes[SkYUVAIndex::kA_Index] = 0;
+
     if (colorSpace) {
         *colorSpace = kJPEG_SkYUVColorSpace;
     }
@@ -843,18 +850,22 @@
     return true;
 }
 
-SkCodec::Result SkJpegCodec::onGetYUV8Planes(const SkYUVSizeInfo& sizeInfo, void* planes[3]) {
+SkCodec::Result SkJpegCodec::onGetYUV8Planes(const SkYUVSizeInfo& sizeInfo,
+                                             void* planes[SkYUVSizeInfo::kMaxCount]) {
     SkYUVSizeInfo defaultInfo;
 
     // This will check is_yuv_supported(), so we don't need to here.
     bool supportsYUV = this->onQueryYUV8(&defaultInfo, nullptr);
     if (!supportsYUV ||
-            sizeInfo.fSizes[SkYUVSizeInfo::kY] != defaultInfo.fSizes[SkYUVSizeInfo::kY] ||
-            sizeInfo.fSizes[SkYUVSizeInfo::kU] != defaultInfo.fSizes[SkYUVSizeInfo::kU] ||
-            sizeInfo.fSizes[SkYUVSizeInfo::kV] != defaultInfo.fSizes[SkYUVSizeInfo::kV] ||
-            sizeInfo.fWidthBytes[SkYUVSizeInfo::kY] < defaultInfo.fWidthBytes[SkYUVSizeInfo::kY] ||
-            sizeInfo.fWidthBytes[SkYUVSizeInfo::kU] < defaultInfo.fWidthBytes[SkYUVSizeInfo::kU] ||
-            sizeInfo.fWidthBytes[SkYUVSizeInfo::kV] < defaultInfo.fWidthBytes[SkYUVSizeInfo::kV]) {
+            kAlpha_8_SkColorType != sizeInfo.fColorTypes[0] ||
+            kAlpha_8_SkColorType != sizeInfo.fColorTypes[1] ||
+            kAlpha_8_SkColorType != sizeInfo.fColorTypes[2] ||
+            sizeInfo.fSizes[0] != defaultInfo.fSizes[0] ||
+            sizeInfo.fSizes[1] != defaultInfo.fSizes[1] ||
+            sizeInfo.fSizes[2] != defaultInfo.fSizes[2] ||
+            sizeInfo.fWidthBytes[0] < defaultInfo.fWidthBytes[0] ||
+            sizeInfo.fWidthBytes[1] < defaultInfo.fWidthBytes[1] ||
+            sizeInfo.fWidthBytes[2] < defaultInfo.fWidthBytes[2]) {
         return fDecoderMgr->returnFailure("onGetYUV8Planes", kInvalidInput);
     }
 
@@ -879,9 +890,9 @@
 
     // Currently, we require that the Y plane dimensions match the image dimensions
     // and that the U and V planes are the same dimensions.
-    SkASSERT(sizeInfo.fSizes[SkYUVSizeInfo::kU] == sizeInfo.fSizes[SkYUVSizeInfo::kV]);
-    SkASSERT((uint32_t) sizeInfo.fSizes[SkYUVSizeInfo::kY].width() == dinfo->output_width &&
-            (uint32_t) sizeInfo.fSizes[SkYUVSizeInfo::kY].height() == dinfo->output_height);
+    SkASSERT(sizeInfo.fSizes[1] == sizeInfo.fSizes[2]);
+    SkASSERT((uint32_t) sizeInfo.fSizes[0].width() == dinfo->output_width &&
+             (uint32_t) sizeInfo.fSizes[0].height() == dinfo->output_height);
 
     // Build a JSAMPIMAGE to handle output from libjpeg-turbo.  A JSAMPIMAGE has
     // a 2-D array of pixels for each of the components (Y, U, V) in the image.
@@ -898,20 +909,19 @@
     // Initialize rowptrs.
     int numYRowsPerBlock = DCTSIZE * dinfo->comp_info[0].v_samp_factor;
     for (int i = 0; i < numYRowsPerBlock; i++) {
-        rowptrs[i] = SkTAddOffset<JSAMPLE>(planes[SkYUVSizeInfo::kY],
-                i * sizeInfo.fWidthBytes[SkYUVSizeInfo::kY]);
+        rowptrs[i] = SkTAddOffset<JSAMPLE>(planes[0], i * sizeInfo.fWidthBytes[0]);
     }
     for (int i = 0; i < DCTSIZE; i++) {
-        rowptrs[i + 2 * DCTSIZE] = SkTAddOffset<JSAMPLE>(planes[SkYUVSizeInfo::kU],
-                i * sizeInfo.fWidthBytes[SkYUVSizeInfo::kU]);
-        rowptrs[i + 3 * DCTSIZE] = SkTAddOffset<JSAMPLE>(planes[SkYUVSizeInfo::kV],
-                i * sizeInfo.fWidthBytes[SkYUVSizeInfo::kV]);
+        rowptrs[i + 2 * DCTSIZE] =
+            SkTAddOffset<JSAMPLE>(planes[1], i * sizeInfo.fWidthBytes[1]);
+        rowptrs[i + 3 * DCTSIZE] =
+            SkTAddOffset<JSAMPLE>(planes[2], i * sizeInfo.fWidthBytes[2]);
     }
 
     // After each loop iteration, we will increment pointers to Y, U, and V.
-    size_t blockIncrementY = numYRowsPerBlock * sizeInfo.fWidthBytes[SkYUVSizeInfo::kY];
-    size_t blockIncrementU = DCTSIZE * sizeInfo.fWidthBytes[SkYUVSizeInfo::kU];
-    size_t blockIncrementV = DCTSIZE * sizeInfo.fWidthBytes[SkYUVSizeInfo::kV];
+    size_t blockIncrementY = numYRowsPerBlock * sizeInfo.fWidthBytes[0];
+    size_t blockIncrementU = DCTSIZE * sizeInfo.fWidthBytes[1];
+    size_t blockIncrementV = DCTSIZE * sizeInfo.fWidthBytes[2];
 
     uint32_t numRowsPerBlock = numYRowsPerBlock;
 
@@ -944,7 +954,7 @@
         // this requirement using a dummy row buffer.
         // FIXME: Should SkCodec have an extra memory buffer that can be shared among
         //        all of the implementations that use temporary/garbage memory?
-        SkAutoTMalloc<JSAMPLE> dummyRow(sizeInfo.fWidthBytes[SkYUVSizeInfo::kY]);
+        SkAutoTMalloc<JSAMPLE> dummyRow(sizeInfo.fWidthBytes[SkYUVAIndex::kY_Index]);
         for (int i = remainingRows; i < numYRowsPerBlock; i++) {
             rowptrs[i] = dummyRow.get();
         }
diff --git a/src/codec/SkJpegCodec.h b/src/codec/SkJpegCodec.h
index 5eff1d5..c102627 100644
--- a/src/codec/SkJpegCodec.h
+++ b/src/codec/SkJpegCodec.h
@@ -46,7 +46,8 @@
 
     bool onQueryYUV8(SkYUVSizeInfo* sizeInfo, SkYUVColorSpace* colorSpace) const override;
 
-    Result onGetYUV8Planes(const SkYUVSizeInfo& sizeInfo, void* planes[3]) override;
+    Result onGetYUV8Planes(const SkYUVSizeInfo& sizeInfo,
+                           void* planes[SkYUVSizeInfo::kMaxCount]) override;
 
     SkEncodedImageFormat onGetEncodedFormat() const override {
         return SkEncodedImageFormat::kJPEG;
diff --git a/src/core/SkImageGenerator.cpp b/src/core/SkImageGenerator.cpp
index e0275a1..42d5214 100644
--- a/src/core/SkImageGenerator.cpp
+++ b/src/core/SkImageGenerator.cpp
@@ -8,6 +8,7 @@
 #include "SkImage.h"
 #include "SkImageGenerator.h"
 #include "SkNextID.h"
+#include "SkYUVAIndex.h"
 
 SkImageGenerator::SkImageGenerator(const SkImageInfo& info, uint32_t uniqueID)
     : fInfo(info)
@@ -29,28 +30,119 @@
     return this->onGetPixels(info, pixels, rowBytes, defaultOpts);
 }
 
-bool SkImageGenerator::queryYUV8(SkYUVSizeInfo* sizeInfo, SkYUVColorSpace* colorSpace) const {
+bool SkImageGenerator::queryYUVA8(SkYUVSizeInfo* sizeInfo,
+                                  SkYUVAIndex yuvaIndices[SkYUVAIndex::kIndexCount],
+                                  SkYUVColorSpace* colorSpace) const {
     SkASSERT(sizeInfo);
 
-    return this->onQueryYUV8(sizeInfo, colorSpace);
+    if (!this->onQueryYUVA8(sizeInfo, yuvaIndices, colorSpace)) {
+        // try the deprecated method and make a guess at the other data
+        if (this->onQueryYUV8(sizeInfo, colorSpace)) {
+            // take a guess at the number of planes
+            int numPlanes = SkYUVSizeInfo::kMaxCount;
+            for (int i = 0; i < SkYUVSizeInfo::kMaxCount; ++i) {
+                if (sizeInfo->fSizes[i].isEmpty()) {
+                    numPlanes = i;
+                    break;
+                }
+            }
+            if (!numPlanes) {
+                return false;
+            }
+            switch (numPlanes) {
+                case 1:
+                    // Assume 3 interleaved planes
+                    sizeInfo->fColorTypes[0] = kRGBA_8888_SkColorType;
+                    sizeInfo->fColorTypes[1] = kUnknown_SkColorType;
+                    sizeInfo->fColorTypes[2] = kUnknown_SkColorType;
+                    sizeInfo->fColorTypes[3] = kUnknown_SkColorType;
+                    yuvaIndices[SkYUVAIndex::kY_Index].fIndex = 0;
+                    yuvaIndices[SkYUVAIndex::kY_Index].fChannel = SkColorChannel::kR;
+                    yuvaIndices[SkYUVAIndex::kU_Index].fIndex = 0;
+                    yuvaIndices[SkYUVAIndex::kU_Index].fChannel = SkColorChannel::kG;
+                    yuvaIndices[SkYUVAIndex::kV_Index].fIndex = 0;
+                    yuvaIndices[SkYUVAIndex::kV_Index].fChannel = SkColorChannel::kB;
+                    yuvaIndices[SkYUVAIndex::kA_Index].fIndex = -1;
+                    yuvaIndices[SkYUVAIndex::kA_Index].fChannel = SkColorChannel::kR;
+                    break;
+                case 2:
+                    // Assume 1 Y plane and interleaved UV planes
+                    sizeInfo->fColorTypes[0] = kAlpha_8_SkColorType;
+                    sizeInfo->fColorTypes[1] = kRGBA_8888_SkColorType;
+                    sizeInfo->fColorTypes[2] = kUnknown_SkColorType;
+                    sizeInfo->fColorTypes[3] = kUnknown_SkColorType;
+                    yuvaIndices[SkYUVAIndex::kY_Index].fIndex = 0;
+                    yuvaIndices[SkYUVAIndex::kY_Index].fChannel = SkColorChannel::kR;
+                    yuvaIndices[SkYUVAIndex::kU_Index].fIndex = 1;
+                    yuvaIndices[SkYUVAIndex::kU_Index].fChannel = SkColorChannel::kR;
+                    yuvaIndices[SkYUVAIndex::kV_Index].fIndex = 1;
+                    yuvaIndices[SkYUVAIndex::kV_Index].fChannel = SkColorChannel::kG;
+                    yuvaIndices[SkYUVAIndex::kA_Index].fIndex = -1;
+                    yuvaIndices[SkYUVAIndex::kA_Index].fChannel = SkColorChannel::kR;
+                    break;
+                case 3:
+                    // Assume 3 separate non-interleaved planes
+                    sizeInfo->fColorTypes[0] = kAlpha_8_SkColorType;
+                    sizeInfo->fColorTypes[1] = kAlpha_8_SkColorType;
+                    sizeInfo->fColorTypes[2] = kAlpha_8_SkColorType;
+                    sizeInfo->fColorTypes[3] = kUnknown_SkColorType;
+                    yuvaIndices[SkYUVAIndex::kY_Index].fIndex = 0;
+                    yuvaIndices[SkYUVAIndex::kY_Index].fChannel = SkColorChannel::kR;
+                    yuvaIndices[SkYUVAIndex::kU_Index].fIndex = 1;
+                    yuvaIndices[SkYUVAIndex::kU_Index].fChannel = SkColorChannel::kR;
+                    yuvaIndices[SkYUVAIndex::kV_Index].fIndex = 2;
+                    yuvaIndices[SkYUVAIndex::kV_Index].fChannel = SkColorChannel::kR;
+                    yuvaIndices[SkYUVAIndex::kA_Index].fIndex = -1;
+                    yuvaIndices[SkYUVAIndex::kA_Index].fChannel = SkColorChannel::kR;
+                    break;
+                case 4:
+                default:
+                    // Assume 4 separate non-interleaved planes
+                    sizeInfo->fColorTypes[0] = kAlpha_8_SkColorType;
+                    sizeInfo->fColorTypes[1] = kAlpha_8_SkColorType;
+                    sizeInfo->fColorTypes[2] = kAlpha_8_SkColorType;
+                    sizeInfo->fColorTypes[3] = kAlpha_8_SkColorType;
+                    yuvaIndices[SkYUVAIndex::kY_Index].fIndex = 0;
+                    yuvaIndices[SkYUVAIndex::kY_Index].fChannel = SkColorChannel::kR;
+                    yuvaIndices[SkYUVAIndex::kU_Index].fIndex = 1;
+                    yuvaIndices[SkYUVAIndex::kU_Index].fChannel = SkColorChannel::kR;
+                    yuvaIndices[SkYUVAIndex::kV_Index].fIndex = 2;
+                    yuvaIndices[SkYUVAIndex::kV_Index].fChannel = SkColorChannel::kR;
+                    yuvaIndices[SkYUVAIndex::kA_Index].fIndex = 3;
+                    yuvaIndices[SkYUVAIndex::kA_Index].fChannel = SkColorChannel::kR;
+                    break;
+            }
+
+            return true;
+        }
+
+        return false;
+    }
+
+    return true;
 }
 
-bool SkImageGenerator::getYUV8Planes(const SkYUVSizeInfo& sizeInfo, void* planes[3]) {
-    SkASSERT(sizeInfo.fSizes[SkYUVSizeInfo::kY].fWidth >= 0);
-    SkASSERT(sizeInfo.fSizes[SkYUVSizeInfo::kY].fHeight >= 0);
-    SkASSERT(sizeInfo.fSizes[SkYUVSizeInfo::kU].fWidth >= 0);
-    SkASSERT(sizeInfo.fSizes[SkYUVSizeInfo::kU].fHeight >= 0);
-    SkASSERT(sizeInfo.fSizes[SkYUVSizeInfo::kV].fWidth >= 0);
-    SkASSERT(sizeInfo.fSizes[SkYUVSizeInfo::kV].fHeight >= 0);
-    SkASSERT(sizeInfo.fWidthBytes[SkYUVSizeInfo::kY] >=
-            (size_t) sizeInfo.fSizes[SkYUVSizeInfo::kY].fWidth);
-    SkASSERT(sizeInfo.fWidthBytes[SkYUVSizeInfo::kU] >=
-            (size_t) sizeInfo.fSizes[SkYUVSizeInfo::kU].fWidth);
-    SkASSERT(sizeInfo.fWidthBytes[SkYUVSizeInfo::kV] >=
-            (size_t) sizeInfo.fSizes[SkYUVSizeInfo::kV].fWidth);
-    SkASSERT(planes && planes[0] && planes[1] && planes[2]);
+bool SkImageGenerator::getYUVA8Planes(const SkYUVSizeInfo& sizeInfo,
+                                      const SkYUVAIndex yuvaIndices[SkYUVAIndex::kIndexCount],
+                                      void* planes[SkYUVSizeInfo::kMaxCount]) {
 
-    return this->onGetYUV8Planes(sizeInfo, planes);
+    for (int i = 0; i < SkYUVSizeInfo::kMaxCount; ++i) {
+        SkASSERT(sizeInfo.fSizes[i].fWidth >= 0);
+        SkASSERT(sizeInfo.fSizes[i].fHeight >= 0);
+        SkASSERT(sizeInfo.fWidthBytes[i] >= (size_t) sizeInfo.fSizes[i].fWidth);
+    }
+
+    int numPlanes = 0;
+    SkASSERT(SkYUVAIndex::AreValidIndices(yuvaIndices, &numPlanes));
+    SkASSERT(planes);
+    for (int i = 0; i < numPlanes; ++i) {
+        SkASSERT(planes[i]);
+    }
+
+    if (!this->onGetYUVA8Planes(sizeInfo, yuvaIndices, planes)) {
+        return this->onGetYUV8Planes(sizeInfo, planes);
+    }
+    return true;
 }
 
 #if SK_SUPPORT_GPU
diff --git a/src/core/SkYUVPlanesCache.h b/src/core/SkYUVPlanesCache.h
index 1c866a2..648f194 100644
--- a/src/core/SkYUVPlanesCache.h
+++ b/src/core/SkYUVPlanesCache.h
@@ -10,6 +10,7 @@
 
 #include "SkCachedData.h"
 #include "SkImageInfo.h"
+#include "SkYUVAIndex.h"
 #include "SkYUVSizeInfo.h"
 
 class SkResourceCache;
@@ -17,14 +18,15 @@
 class SkYUVPlanesCache {
 public:
     /**
-     * The Info struct contains data about the 3 Y, U and V planes of memory stored
+     * The Info struct contains data about the 4 Y, U, V, and A planes of memory stored
      * contiguously, in that order, as a single block of memory within SkYUVPlanesCache.
      *
-     * fSizeInfo: fWidth, fHeight, and fWidthBytes of each of the Y, U, and V planes.
+     * fSizeInfo: fWidth, fHeight, and fWidthBytes of each of the Y, U, V, and A planes.
      * fColorSpace: color space that will be used for the YUV -> RGB conversion.
      */
     struct Info {
         SkYUVSizeInfo   fSizeInfo;
+        SkYUVAIndex     fYUVAIndices[SkYUVAIndex::kIndexCount];
         SkYUVColorSpace fColorSpace;
     };
     /**
diff --git a/src/core/SkYUVSizeInfo.cpp b/src/core/SkYUVSizeInfo.cpp
new file mode 100644
index 0000000..7ebdbb9
--- /dev/null
+++ b/src/core/SkYUVSizeInfo.cpp
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2018 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkYUVSizeInfo.h"
+#include "SkTemplates.h"
+
+void SkYUVSizeInfo::computePlanes(void* base, void* planes[SkYUVSizeInfo::kMaxCount]) const {
+    planes[0] = base;
+    int i = 1;
+    for (; i < SkYUVSizeInfo::kMaxCount; ++i) {
+        if (fSizes[i].isEmpty()) {
+            break;
+        }
+        planes[i] = SkTAddOffset<void>(planes[i - 1], fWidthBytes[i - 1] * fSizes[i - 1].height());
+    }
+    for (; i < SkYUVSizeInfo::kMaxCount; ++i) {
+        planes[i] = nullptr;
+    }
+}
diff --git a/src/gpu/GrYUVProvider.cpp b/src/gpu/GrYUVProvider.cpp
index da86eac..beccd7d 100644
--- a/src/gpu/GrYUVProvider.cpp
+++ b/src/gpu/GrYUVProvider.cpp
@@ -22,40 +22,64 @@
 #include "effects/GrYUVtoRGBEffect.h"
 
 sk_sp<SkCachedData> GrYUVProvider::getPlanes(SkYUVSizeInfo* size,
+                                             SkYUVAIndex yuvaIndices[SkYUVAIndex::kIndexCount],
                                              SkYUVColorSpace* colorSpace,
-                                             const void* constPlanes[3]) {
+                                             const void* constPlanes[SkYUVSizeInfo::kMaxCount]) {
     sk_sp<SkCachedData> data;
     SkYUVPlanesCache::Info yuvInfo;
     data.reset(SkYUVPlanesCache::FindAndRef(this->onGetID(), &yuvInfo));
 
-    void* planes[3];
+    void* planes[SkYUVSizeInfo::kMaxCount];
 
     if (data.get()) {
-        planes[0] = (void*)data->data();
-        planes[1] = (uint8_t*)planes[0] + (yuvInfo.fSizeInfo.fWidthBytes[SkYUVSizeInfo::kY] *
-                                           yuvInfo.fSizeInfo.fSizes[SkYUVSizeInfo::kY].fHeight);
-        planes[2] = (uint8_t*)planes[1] + (yuvInfo.fSizeInfo.fWidthBytes[SkYUVSizeInfo::kU] *
-                                           yuvInfo.fSizeInfo.fSizes[SkYUVSizeInfo::kU].fHeight);
+        planes[0] = (void*)data->data(); // we should always have at least one plane
+
+        for (int i = 1; i < SkYUVSizeInfo::kMaxCount; ++i) {
+            if (!yuvInfo.fSizeInfo.fWidthBytes[i]) {
+                SkASSERT(kUnknown_SkColorType == yuvInfo.fSizeInfo.fColorTypes[i] &&
+                         !yuvInfo.fSizeInfo.fWidthBytes[i] &&
+                         !yuvInfo.fSizeInfo.fSizes[i].fHeight);
+                planes[i] = nullptr;
+                continue;
+            }
+
+            planes[i] = (uint8_t*)planes[i-1] + (yuvInfo.fSizeInfo.fWidthBytes[i-1] *
+                                                 yuvInfo.fSizeInfo.fSizes[i-1].fHeight);
+        }
     } else {
         // Fetch yuv plane sizes for memory allocation.
-        if (!this->onQueryYUV8(&yuvInfo.fSizeInfo, &yuvInfo.fColorSpace)) {
+        if (!this->onQueryYUVA8(&yuvInfo.fSizeInfo, yuvInfo.fYUVAIndices, &yuvInfo.fColorSpace)) {
             return nullptr;
         }
 
-        // Allocate the memory for YUV
+        // Allocate the memory for YUVA
         size_t totalSize(0);
-        for (int i = 0; i < 3; i++) {
+        for (int i = 0; i < SkYUVSizeInfo::kMaxCount; i++) {
+            SkASSERT(kUnknown_SkColorType != yuvInfo.fSizeInfo.fColorTypes[i] ||
+                     (!yuvInfo.fSizeInfo.fWidthBytes[i] && !yuvInfo.fSizeInfo.fSizes[i].fHeight));
+
             totalSize += yuvInfo.fSizeInfo.fWidthBytes[i] * yuvInfo.fSizeInfo.fSizes[i].fHeight;
         }
+
         data.reset(SkResourceCache::NewCachedData(totalSize));
+
         planes[0] = data->writable_data();
-        planes[1] = (uint8_t*)planes[0] + (yuvInfo.fSizeInfo.fWidthBytes[SkYUVSizeInfo::kY] *
-                                           yuvInfo.fSizeInfo.fSizes[SkYUVSizeInfo::kY].fHeight);
-        planes[2] = (uint8_t*)planes[1] + (yuvInfo.fSizeInfo.fWidthBytes[SkYUVSizeInfo::kU] *
-                                           yuvInfo.fSizeInfo.fSizes[SkYUVSizeInfo::kU].fHeight);
+
+        for (int i = 1; i < SkYUVSizeInfo::kMaxCount; ++i) {
+            if (!yuvInfo.fSizeInfo.fWidthBytes[i]) {
+                SkASSERT(kUnknown_SkColorType == yuvInfo.fSizeInfo.fColorTypes[i] &&
+                         !yuvInfo.fSizeInfo.fWidthBytes[i] &&
+                         !yuvInfo.fSizeInfo.fSizes[i].fHeight);
+                planes[i] = nullptr;
+                continue;
+            }
+
+            planes[i] = (uint8_t*)planes[i-1] + (yuvInfo.fSizeInfo.fWidthBytes[i-1] *
+                                                 yuvInfo.fSizeInfo.fSizes[i-1].fHeight);
+        }
 
         // Get the YUV planes.
-        if (!this->onGetYUV8Planes(yuvInfo.fSizeInfo, planes)) {
+        if (!this->onGetYUVA8Planes(yuvInfo.fSizeInfo, yuvInfo.fYUVAIndices, planes)) {
             return nullptr;
         }
 
@@ -64,10 +88,12 @@
     }
 
     *size = yuvInfo.fSizeInfo;
+    memcpy(yuvaIndices, yuvInfo.fYUVAIndices, sizeof(yuvInfo.fYUVAIndices));
     *colorSpace = yuvInfo.fColorSpace;
     constPlanes[0] = planes[0];
     constPlanes[1] = planes[1];
     constPlanes[2] = planes[2];
+    constPlanes[3] = planes[3];
     return data;
 }
 
@@ -81,24 +107,33 @@
                                                        SkColorSpace* srcColorSpace,
                                                        SkColorSpace* dstColorSpace) {
     SkYUVSizeInfo yuvSizeInfo;
+    SkYUVAIndex yuvaIndices[SkYUVAIndex::kIndexCount];
     SkYUVColorSpace yuvColorSpace;
-    const void* planes[3];
+    const void* planes[SkYUVSizeInfo::kMaxCount];
 
-    sk_sp<SkCachedData> dataStorage = this->getPlanes(&yuvSizeInfo, &yuvColorSpace, planes);
+    sk_sp<SkCachedData> dataStorage = this->getPlanes(&yuvSizeInfo, yuvaIndices,
+                                                      &yuvColorSpace, planes);
     if (!dataStorage) {
         return nullptr;
     }
 
-    sk_sp<GrTextureProxy> yuvTextureProxies[3];
-    for (int i = 0; i < 3; i++) {
+    sk_sp<GrTextureProxy> yuvTextureProxies[SkYUVSizeInfo::kMaxCount];
+    for (int i = 0; i < SkYUVSizeInfo::kMaxCount; ++i) {
+        if (kUnknown_SkColorType == yuvSizeInfo.fColorTypes[i]) {
+            SkASSERT(!yuvSizeInfo.fSizes[i].fWidth ||
+                     !yuvSizeInfo.fSizes[i].fHeight ||
+                     !yuvSizeInfo.fWidthBytes[i]);
+            continue;
+        }
+
         int componentWidth  = yuvSizeInfo.fSizes[i].fWidth;
         int componentHeight = yuvSizeInfo.fSizes[i].fHeight;
         // If the sizes of the components are not all the same we choose to create exact-match
         // textures for the smaller ones rather than add a texture domain to the draw.
         // TODO: revisit this decision to improve texture reuse?
         SkBackingFit fit =
-                (componentWidth  != yuvSizeInfo.fSizes[SkYUVSizeInfo::kY].fWidth) ||
-                (componentHeight != yuvSizeInfo.fSizes[SkYUVSizeInfo::kY].fHeight)
+                (componentWidth  != yuvSizeInfo.fSizes[0].fWidth) ||
+                (componentHeight != yuvSizeInfo.fSizes[0].fHeight)
                     ? SkBackingFit::kExact : SkBackingFit::kApprox;
 
         SkImageInfo imageInfo = SkImageInfo::MakeA8(componentWidth, componentHeight);
@@ -131,17 +166,8 @@
         return nullptr;
     }
 
-    // This code path only generates I420 (i.e., 3 separate plane) YUVs
-    SkYUVAIndex yuvaIndices[4] = {
-        {  0, SkColorChannel::kA },
-        {  1, SkColorChannel::kA },
-        {  2, SkColorChannel::kA },
-        { -1, SkColorChannel::kA }, // no alpha
-    };
-
     GrPaint paint;
-    auto yuvToRgbProcessor =
-            GrYUVtoRGBEffect::Make(yuvTextureProxies, yuvaIndices, yuvColorSpace);
+    auto yuvToRgbProcessor = GrYUVtoRGBEffect::Make(yuvTextureProxies, yuvaIndices, yuvColorSpace);
     paint.addColorFragmentProcessor(std::move(yuvToRgbProcessor));
 
     // If the caller expects the pixels in a different color space than the one from the image,
@@ -154,8 +180,8 @@
     }
 
     paint.setPorterDuffXPFactory(SkBlendMode::kSrc);
-    const SkRect r = SkRect::MakeIWH(yuvSizeInfo.fSizes[SkYUVSizeInfo::kY].fWidth,
-                                     yuvSizeInfo.fSizes[SkYUVSizeInfo::kY].fHeight);
+    const SkRect r = SkRect::MakeIWH(yuvSizeInfo.fSizes[0].fWidth,
+                                     yuvSizeInfo.fSizes[0].fHeight);
 
     renderTargetContext->drawRect(GrNoClip(), std::move(paint), GrAA::kNo, SkMatrix::I(), r);
 
diff --git a/src/gpu/GrYUVProvider.h b/src/gpu/GrYUVProvider.h
index de6ce33..505274e 100644
--- a/src/gpu/GrYUVProvider.h
+++ b/src/gpu/GrYUVProvider.h
@@ -17,6 +17,7 @@
 class GrTexture;
 class GrTextureProxy;
 class SkCachedData;
+struct SkYUVAIndex;
 
 /**
  *  There are at least 2 different ways to extract/retrieve YUV planar data...
@@ -43,7 +44,8 @@
                                             SkColorSpace* srcColorSpace,
                                             SkColorSpace* dstColorSpace);
 
-    sk_sp<SkCachedData> getPlanes(SkYUVSizeInfo*, SkYUVColorSpace*, const void* planes[3]);
+    sk_sp<SkCachedData> getPlanes(SkYUVSizeInfo*, SkYUVAIndex[SkYUVAIndex::kIndexCount],
+                                  SkYUVColorSpace*, const void* planes[SkYUVSizeInfo::kMaxCount]);
 
 private:
     virtual uint32_t onGetID() const = 0;
@@ -54,23 +56,29 @@
      *  If decoding to YUV is supported, this returns true.  Otherwise, this
      *  returns false and does not modify any of the parameters.
      *
-     *  @param sizeInfo   Output parameter indicating the sizes and required
-     *                    allocation widths of the Y, U, and V planes.
-     *  @param colorSpace Output parameter.
+     *  @param sizeInfo    Output parameter indicating the sizes and required
+     *                     allocation widths of the Y, U, V, and A planes.
+     *  @param yuvaIndices How the YUVA planes are used/organized
+     *  @param colorSpace  Output parameter.
      */
-    virtual bool onQueryYUV8(SkYUVSizeInfo* sizeInfo, SkYUVColorSpace* colorSpace) const = 0;
+    virtual bool onQueryYUVA8(SkYUVSizeInfo* sizeInfo,
+                              SkYUVAIndex yuvaIndices[SkYUVAIndex::kIndexCount],
+                              SkYUVColorSpace* colorSpace) const = 0;
 
     /**
      *  Returns true on success and false on failure.
      *  This always attempts to perform a full decode.  If the client only
-     *  wants size, it should call onQueryYUV8().
+     *  wants size, it should call onQueryYUVA8().
      *
-     *  @param sizeInfo   Needs to exactly match the values returned by the
-     *                    query, except the WidthBytes may be larger than the
-     *                    recommendation (but not smaller).
-     *  @param planes     Memory for each of the Y, U, and V planes.
+     *  @param sizeInfo    Needs to exactly match the values returned by the
+     *                     query, except the WidthBytes may be larger than the
+     *                     recommendation (but not smaller).
+     *  @param yuvaIndices How the YUVA planes are used/organized
+     *  @param planes      Memory for each of the Y, U, V, and A planes.
      */
-    virtual bool onGetYUV8Planes(const SkYUVSizeInfo& sizeInfo, void* planes[3]) = 0;
+    virtual bool onGetYUVA8Planes(const SkYUVSizeInfo& sizeInfo,
+                                  const SkYUVAIndex yuvaIndices[SkYUVAIndex::kIndexCount],
+                                  void* planes[]) = 0;
 
     // This is used as release callback for the YUV data that we capture in an SkImage when
     // uploading to a gpu. When the upload is complete and we release the SkImage this callback will
diff --git a/src/image/SkImage.cpp b/src/image/SkImage.cpp
index 75cf127..dcdd103 100644
--- a/src/image/SkImage.cpp
+++ b/src/image/SkImage.cpp
@@ -227,7 +227,8 @@
     return as_IB(this)->onAsLegacyBitmap(bitmap);
 }
 
-sk_sp<SkCachedData> SkImage_Base::getPlanes(SkYUVSizeInfo*, SkYUVColorSpace*,const void*[3]) {
+sk_sp<SkCachedData> SkImage_Base::getPlanes(SkYUVSizeInfo*, SkYUVAIndex[4],
+                                            SkYUVColorSpace*, const void*[4]) {
     return nullptr;
 }
 
diff --git a/src/image/SkImage_Base.h b/src/image/SkImage_Base.h
index 8765f89..7bbd81d 100644
--- a/src/image/SkImage_Base.h
+++ b/src/image/SkImage_Base.h
@@ -72,7 +72,8 @@
 
     virtual sk_sp<SkImage> onMakeSubset(const SkIRect&) const = 0;
 
-    virtual sk_sp<SkCachedData> getPlanes(SkYUVSizeInfo*, SkYUVColorSpace*, const void* planes[3]);
+    virtual sk_sp<SkCachedData> getPlanes(SkYUVSizeInfo*, SkYUVAIndex[4],
+                                          SkYUVColorSpace*, const void* planes[4]);
     virtual sk_sp<SkData> onRefEncoded() const { return nullptr; }
 
     virtual bool onAsLegacyBitmap(SkBitmap*) const;
diff --git a/src/image/SkImage_GpuYUVA.cpp b/src/image/SkImage_GpuYUVA.cpp
index 25b9349..2fd4406 100644
--- a/src/image/SkImage_GpuYUVA.cpp
+++ b/src/image/SkImage_GpuYUVA.cpp
@@ -233,6 +233,7 @@
     }
 
     // Set up color types
+    // TODO: pass in the correct color type rather than deducing it?
     SkColorType texColorTypes[4] = { kUnknown_SkColorType, kUnknown_SkColorType,
                                      kUnknown_SkColorType, kUnknown_SkColorType };
     for (int yuvIndex = 0; yuvIndex < 4; ++yuvIndex) {
@@ -247,10 +248,6 @@
             texColorTypes[texIdx] = kRGBA_8888_SkColorType;
         }
     }
-    // If UV is interleaved, then Y will have RGBA color type
-    if (kRGBA_8888_SkColorType == texColorTypes[yuvaIndices[SkYUVAIndex::kU_Index].fIndex]) {
-        texColorTypes[yuvaIndices[SkYUVAIndex::kY_Index].fIndex] = kRGBA_8888_SkColorType;
-    }
 
     // Get lazy proxies
     GrProxyProvider* proxyProvider = context->contextPriv().proxyProvider();
@@ -261,9 +258,17 @@
             GrPixelConfig fConfig;
             SkPromiseImageHelper fPromiseHelper;
         } params;
-        if (!context->contextPriv().caps()->getConfigFromBackendFormat(yuvaFormats[texIdx],
-                                                                       texColorTypes[texIdx],
-                                                                       &params.fConfig)) {
+        bool res = context->contextPriv().caps()->getConfigFromBackendFormat(yuvaFormats[texIdx],
+                                                                             texColorTypes[texIdx],
+                                                                             &params.fConfig);
+        // Even though the data is single channel, we might be handed a multi-channel texture.
+        // To cover this case, we'll re-try with the RGBA colortype on failure.
+        if (!res && kAlpha_8_SkColorType == texColorTypes[texIdx]) {
+            res = context->contextPriv().caps()->getConfigFromBackendFormat(yuvaFormats[texIdx],
+                                                                            kRGBA_8888_SkColorType,
+                                                                            &params.fConfig);
+        }
+        if (!res) {
             return nullptr;
         }
         params.fPromiseHelper = promiseHelpers[texIdx];
diff --git a/src/image/SkImage_Lazy.cpp b/src/image/SkImage_Lazy.cpp
index 7071b4b..1688a81 100644
--- a/src/image/SkImage_Lazy.cpp
+++ b/src/image/SkImage_Lazy.cpp
@@ -355,11 +355,15 @@
 
 private:
     uint32_t onGetID() const override { return fGen->uniqueID(); }
-    bool onQueryYUV8(SkYUVSizeInfo* sizeInfo, SkYUVColorSpace* colorSpace) const override {
-        return fGen->queryYUV8(sizeInfo, colorSpace);
+    bool onQueryYUVA8(SkYUVSizeInfo* sizeInfo,
+                      SkYUVAIndex yuvaIndices[SkYUVAIndex::kIndexCount],
+                      SkYUVColorSpace* colorSpace) const override {
+        return fGen->queryYUVA8(sizeInfo, yuvaIndices, colorSpace);
     }
-    bool onGetYUV8Planes(const SkYUVSizeInfo& sizeInfo, void* planes[3]) override {
-        return fGen->getYUV8Planes(sizeInfo, planes);
+    bool onGetYUVA8Planes(const SkYUVSizeInfo& sizeInfo,
+                          const SkYUVAIndex yuvaIndices[SkYUVAIndex::kIndexCount],
+                          void* planes[]) override {
+        return fGen->getYUVA8Planes(sizeInfo, yuvaIndices, planes);
     }
 
     SkImageGenerator* fGen;
@@ -384,13 +388,14 @@
     }
 }
 
-sk_sp<SkCachedData> SkImage_Lazy::getPlanes(SkYUVSizeInfo* yuvSizeInfo,
+sk_sp<SkCachedData> SkImage_Lazy::getPlanes(SkYUVSizeInfo* yuvaSizeInfo,
+                                            SkYUVAIndex yuvaIndices[SkYUVAIndex::kIndexCount],
                                             SkYUVColorSpace* yuvColorSpace,
-                                            const void* planes[3]) {
+                                            const void* planes[SkYUVSizeInfo::kMaxCount]) {
     ScopedGenerator generator(fSharedGenerator);
     Generator_GrYUVProvider provider(generator);
 
-    sk_sp<SkCachedData> data = provider.getPlanes(yuvSizeInfo, yuvColorSpace, planes);
+    sk_sp<SkCachedData> data = provider.getPlanes(yuvaSizeInfo, yuvaIndices, yuvColorSpace, planes);
     if (!data) {
         return nullptr;
     }
diff --git a/src/image/SkImage_Lazy.h b/src/image/SkImage_Lazy.h
index a2b434a..5c30ee3 100644
--- a/src/image/SkImage_Lazy.h
+++ b/src/image/SkImage_Lazy.h
@@ -49,7 +49,8 @@
                                             const GrSamplerState&, SkColorSpace*,
                                             sk_sp<SkColorSpace>*,
                                             SkScalar scaleAdjust[2]) const override;
-    sk_sp<SkCachedData> getPlanes(SkYUVSizeInfo*, SkYUVColorSpace*, const void* planes[3]) override;
+    sk_sp<SkCachedData> getPlanes(SkYUVSizeInfo*, SkYUVAIndex[4],
+                                  SkYUVColorSpace*, const void* planes[4]) override;
 #endif
     sk_sp<SkData> onRefEncoded() const override;
     sk_sp<SkImage> onMakeSubset(const SkIRect&) const override;
diff --git a/tests/ImageGeneratorTest.cpp b/tests/ImageGeneratorTest.cpp
index 5c8864e..907efc0 100644
--- a/tests/ImageGeneratorTest.cpp
+++ b/tests/ImageGeneratorTest.cpp
@@ -7,9 +7,11 @@
 
 #include "SkData.h"
 #include "SkCanvas.h"
+
 #include "SkGraphics.h"
 #include "SkImageGenerator.h"
 #include "SkImageInfoPriv.h"
+#include "SkYUVAIndex.h"
 #include "Test.h"
 
 #if defined(SK_BUILD_FOR_MAC) || defined(SK_BUILD_FOR_IOS)
@@ -61,24 +63,32 @@
 DEF_TEST(ImageGenerator, reporter) {
     MyImageGenerator ig;
     SkYUVSizeInfo sizeInfo;
-    sizeInfo.fSizes[SkYUVSizeInfo::kY] = SkISize::Make(200, 200);
-    sizeInfo.fSizes[SkYUVSizeInfo::kU] = SkISize::Make(100, 100);
-    sizeInfo.fSizes[SkYUVSizeInfo::kV] = SkISize::Make( 50,  50);
-    sizeInfo.fWidthBytes[SkYUVSizeInfo::kY] = 0;
-    sizeInfo.fWidthBytes[SkYUVSizeInfo::kU] = 0;
-    sizeInfo.fWidthBytes[SkYUVSizeInfo::kV] = 0;
-    void* planes[3] = { nullptr };
+    sizeInfo.fSizes[0] = SkISize::Make(200, 200);
+    sizeInfo.fSizes[1] = SkISize::Make(100, 100);
+    sizeInfo.fSizes[2] = SkISize::Make( 50,  50);
+    sizeInfo.fSizes[3] = SkISize::Make( 25,  25);
+    sizeInfo.fWidthBytes[0] = 0;
+    sizeInfo.fWidthBytes[1] = 0;
+    sizeInfo.fWidthBytes[2] = 0;
+    sizeInfo.fWidthBytes[3] = 0;
+    void* planes[4] = { nullptr };
+    SkYUVAIndex yuvaIndices[4];
     SkYUVColorSpace colorSpace;
 
     // Check that the YUV decoding API does not cause any crashes
-    ig.queryYUV8(&sizeInfo, nullptr);
-    ig.queryYUV8(&sizeInfo, &colorSpace);
-    sizeInfo.fWidthBytes[SkYUVSizeInfo::kY] = 250;
-    sizeInfo.fWidthBytes[SkYUVSizeInfo::kU] = 250;
-    sizeInfo.fWidthBytes[SkYUVSizeInfo::kV] = 250;
+    ig.queryYUVA8(&sizeInfo, yuvaIndices, nullptr);
+    ig.queryYUVA8(&sizeInfo, yuvaIndices, &colorSpace);
+    sizeInfo.fWidthBytes[0] = 250;
+    sizeInfo.fWidthBytes[1] = 250;
+    sizeInfo.fWidthBytes[2] = 250;
+    sizeInfo.fWidthBytes[3] = 250;
+    yuvaIndices[0] = { 0, SkColorChannel::kR };
+    yuvaIndices[1] = { 1, SkColorChannel::kR };
+    yuvaIndices[2] = { 2, SkColorChannel::kR };
+    yuvaIndices[3] = { 3, SkColorChannel::kR };
     int dummy;
-    planes[SkYUVSizeInfo::kY] = planes[SkYUVSizeInfo::kU] = planes[SkYUVSizeInfo::kV] = &dummy;
-    ig.getYUV8Planes(sizeInfo, planes);
+    planes[0] = planes[1] = planes[2] = planes[3] = &dummy;
+    ig.getYUVA8Planes(sizeInfo, yuvaIndices, planes);
 
     // Suppressed due to https://code.google.com/p/skia/issues/detail?id=4339
     if (false) {
diff --git a/tests/YUVCacheTest.cpp b/tests/YUVCacheTest.cpp
index b34cf06..0410fdb 100644
--- a/tests/YUVCacheTest.cpp
+++ b/tests/YUVCacheTest.cpp
@@ -32,10 +32,16 @@
     SkResourceCache cache(1024);
 
     SkYUVPlanesCache::Info yuvInfo;
-    for (int i = 0; i < 3; i++) {
-        yuvInfo.fSizeInfo.fSizes[i].fWidth = 20 * i;
-        yuvInfo.fSizeInfo.fSizes[i].fHeight = 10 * i;
-        yuvInfo.fSizeInfo.fWidthBytes[i] = 80 * i;
+    for (int i = 0; i < SkYUVSizeInfo::kMaxCount; i++) {
+        yuvInfo.fSizeInfo.fColorTypes[i] = kAlpha_8_SkColorType;
+        yuvInfo.fSizeInfo.fSizes[i].fWidth = 20 * (i + 1);
+        yuvInfo.fSizeInfo.fSizes[i].fHeight = 10 * (i + 1);
+        yuvInfo.fSizeInfo.fWidthBytes[i] = 80 * (i + 1);
+    }
+
+    for (int i = 0; i < SkYUVAIndex::kIndexCount; ++i) {
+        yuvInfo.fYUVAIndices[i].fIndex = -1;
+        yuvInfo.fYUVAIndices[i].fChannel = SkColorChannel::kR;
     }
     yuvInfo.fColorSpace = kRec601_SkYUVColorSpace;
 
@@ -59,14 +65,12 @@
 
     REPORTER_ASSERT(reporter, data);
     REPORTER_ASSERT(reporter, data->size() == size);
-    for (int i = 0; i < 3; ++i) {
-        REPORTER_ASSERT(reporter, yuvInfo.fSizeInfo.fSizes[i].fWidth ==
-                yuvInfoRead.fSizeInfo.fSizes[i].fWidth);
-        REPORTER_ASSERT(reporter, yuvInfo.fSizeInfo.fSizes[i].fHeight ==
-                yuvInfoRead.fSizeInfo.fSizes[i].fHeight);
-        REPORTER_ASSERT(reporter, yuvInfo.fSizeInfo.fWidthBytes[i] ==
-                yuvInfoRead.fSizeInfo.fWidthBytes[i]);
+    REPORTER_ASSERT(reporter, yuvInfo.fSizeInfo == yuvInfoRead.fSizeInfo);
+
+    for (int i = 0; i < SkYUVAIndex::kIndexCount; ++i) {
+        REPORTER_ASSERT(reporter, yuvInfo.fYUVAIndices[i] == yuvInfoRead.fYUVAIndices[i]);
     }
+
     REPORTER_ASSERT(reporter, yuvInfo.fColorSpace == yuvInfoRead.fColorSpace);
 
     check_data(reporter, data, 2, kInCache, kLocked);
diff --git a/tests/YUVTest.cpp b/tests/YUVTest.cpp
index 3862952..50e36fd 100644
--- a/tests/YUVTest.cpp
+++ b/tests/YUVTest.cpp
@@ -14,8 +14,8 @@
 #include "Test.h"
 
 static void codec_yuv(skiatest::Reporter* reporter,
-                  const char path[],
-                  SkISize expectedSizes[3]) {
+                      const char path[],
+                      SkISize expectedSizes[4]) {
     std::unique_ptr<SkStream> stream(GetResourceAsStream(path));
     if (!stream) {
         return;
@@ -28,59 +28,59 @@
 
     // Test queryYUV8()
     SkYUVSizeInfo info;
-    bool success = codec->queryYUV8(nullptr, nullptr);
-    REPORTER_ASSERT(reporter, !success);
-    success = codec->queryYUV8(&info, nullptr);
-    REPORTER_ASSERT(reporter, (expectedSizes == nullptr) == !success);
-    if (!success) {
-        return;
+
+    {
+        bool success = codec->queryYUV8(nullptr, nullptr);
+        REPORTER_ASSERT(reporter, !success);
+        success = codec->queryYUV8(&info, nullptr);
+        REPORTER_ASSERT(reporter, (expectedSizes == nullptr) == !success);
+        if (!success) {
+            return;
+        }
+
+        for (int i = 0; i < SkYUVSizeInfo::kMaxCount; ++i) {
+            REPORTER_ASSERT(reporter, info.fSizes[i] == expectedSizes[i]);
+            REPORTER_ASSERT(reporter,
+                            info.fWidthBytes[i] == (uint32_t) SkAlign8(info.fSizes[i].width()));
+        }
     }
-    REPORTER_ASSERT(reporter,
-            0 == memcmp((const void*) &info, (const void*) expectedSizes, 3 * sizeof(SkISize)));
-    REPORTER_ASSERT(reporter, info.fWidthBytes[SkYUVSizeInfo::kY] ==
-            (uint32_t) SkAlign8(info.fSizes[SkYUVSizeInfo::kY].width()));
-    REPORTER_ASSERT(reporter, info.fWidthBytes[SkYUVSizeInfo::kU] ==
-            (uint32_t) SkAlign8(info.fSizes[SkYUVSizeInfo::kU].width()));
-    REPORTER_ASSERT(reporter, info.fWidthBytes[SkYUVSizeInfo::kV] ==
-            (uint32_t) SkAlign8(info.fSizes[SkYUVSizeInfo::kV].width()));
-    SkYUVColorSpace colorSpace;
-    success = codec->queryYUV8(&info, &colorSpace);
-    REPORTER_ASSERT(reporter,
-            0 == memcmp((const void*) &info, (const void*) expectedSizes, 3 * sizeof(SkISize)));
-    REPORTER_ASSERT(reporter, info.fWidthBytes[SkYUVSizeInfo::kY] ==
-            (uint32_t) SkAlign8(info.fSizes[SkYUVSizeInfo::kY].width()));
-    REPORTER_ASSERT(reporter, info.fWidthBytes[SkYUVSizeInfo::kU] ==
-            (uint32_t) SkAlign8(info.fSizes[SkYUVSizeInfo::kU].width()));
-    REPORTER_ASSERT(reporter, info.fWidthBytes[SkYUVSizeInfo::kV] ==
-            (uint32_t) SkAlign8(info.fSizes[SkYUVSizeInfo::kV].width()));
-    REPORTER_ASSERT(reporter, kJPEG_SkYUVColorSpace == colorSpace);
+
+    {
+        SkYUVColorSpace colorSpace;
+        bool success = codec->queryYUV8(&info, &colorSpace);
+        REPORTER_ASSERT(reporter, (expectedSizes == nullptr) == !success);
+        if (!success) {
+            return;
+        }
+
+        for (int i = 0; i < SkYUVSizeInfo::kMaxCount; ++i) {
+            REPORTER_ASSERT(reporter, info.fSizes[i] == expectedSizes[i]);
+            REPORTER_ASSERT(reporter,
+                            info.fWidthBytes[i] == (uint32_t) SkAlign8(info.fSizes[i].width()));
+        }
+        REPORTER_ASSERT(reporter, kJPEG_SkYUVColorSpace == colorSpace);
+    }
 
     // Allocate the memory for the YUV decode
-    size_t totalBytes =
-            info.fWidthBytes[SkYUVSizeInfo::kY] * info.fSizes[SkYUVSizeInfo::kY].height() +
-            info.fWidthBytes[SkYUVSizeInfo::kU] * info.fSizes[SkYUVSizeInfo::kU].height() +
-            info.fWidthBytes[SkYUVSizeInfo::kV] * info.fSizes[SkYUVSizeInfo::kV].height();
+    size_t totalBytes = info.computeTotalBytes();
+
     SkAutoMalloc storage(totalBytes);
-    void* planes[3];
-    planes[0] = storage.get();
-    planes[1] = SkTAddOffset<void>(planes[0],
-            info.fWidthBytes[SkYUVSizeInfo::kY] * info.fSizes[SkYUVSizeInfo::kY].height());
-    planes[2] = SkTAddOffset<void>(planes[1],
-            info.fWidthBytes[SkYUVSizeInfo::kU] * info.fSizes[SkYUVSizeInfo::kU].height());
+    void* planes[SkYUVSizeInfo::kMaxCount];
+
+    info.computePlanes(storage.get(), planes);
 
     // Test getYUV8Planes()
-    REPORTER_ASSERT(reporter, SkCodec::kInvalidInput ==
-            codec->getYUV8Planes(info, nullptr));
-    REPORTER_ASSERT(reporter, SkCodec::kSuccess ==
-            codec->getYUV8Planes(info, planes));
+    REPORTER_ASSERT(reporter, SkCodec::kInvalidInput == codec->getYUV8Planes(info, nullptr));
+    REPORTER_ASSERT(reporter, SkCodec::kSuccess == codec->getYUV8Planes(info, planes));
 }
 
 DEF_TEST(Jpeg_YUV_Codec, r) {
-    SkISize sizes[3];
+    SkISize sizes[4];
 
     sizes[0].set(128, 128);
     sizes[1].set(64, 64);
     sizes[2].set(64, 64);
+    sizes[3].set(0, 0);
     codec_yuv(r, "images/color_wheel.jpg", sizes);
 
     // H2V2
diff --git a/tools/DDLPromiseImageHelper.cpp b/tools/DDLPromiseImageHelper.cpp
index 63369cc..079348a 100644
--- a/tools/DDLPromiseImageHelper.cpp
+++ b/tools/DDLPromiseImageHelper.cpp
@@ -60,7 +60,9 @@
 
         // DDL TODO: how can we tell if we need mipmapping!
         if (info.isYUV()) {
-            for (int j = 0; j < 3; ++j) {
+            int numPixmaps;
+            SkAssertResult(SkYUVAIndex::AreValidIndices(info.yuvaIndices(), &numPixmaps));
+            for (int j = 0; j < numPixmaps; ++j) {
                 const SkPixmap& yuvPixmap = info.yuvPixmap(j);
 
                 sk_sp<PromiseImageCallbackContext> callbackContext(
@@ -140,11 +142,13 @@
 
     sk_sp<SkImage> image;
     if (curImage.isYUV()) {
-        GrBackendFormat backendFormats[4];
-        void* contexts[4] = { nullptr, nullptr, nullptr, nullptr };
-        SkISize sizes[4];
-
-        for (int i = 0; i < 3; ++i) {
+        GrBackendFormat backendFormats[SkYUVSizeInfo::kMaxCount];
+        void* contexts[SkYUVSizeInfo::kMaxCount] = { nullptr, nullptr, nullptr, nullptr };
+        SkISize sizes[SkYUVSizeInfo::kMaxCount];
+        // TODO: store this value somewhere?
+        int textureCount;
+        SkAssertResult(SkYUVAIndex::AreValidIndices(curImage.yuvaIndices(), &textureCount));
+        for (int i = 0; i < textureCount; ++i) {
             const GrBackendTexture& backendTex = curImage.backendTexture(i);
             backendFormats[i] = caps->createFormatFromBackendTexture(backendTex);
 
@@ -152,17 +156,10 @@
             sizes[i].set(curImage.yuvPixmap(i).width(), curImage.yuvPixmap(i).height());
         }
 
-        SkYUVAIndex yuvaIndices[4] = {
-                SkYUVAIndex{0, SkColorChannel::kA},
-                SkYUVAIndex{1, SkColorChannel::kA},
-                SkYUVAIndex{2, SkColorChannel::kA},
-                SkYUVAIndex{-1, SkColorChannel::kA}  // TODO: enable this
-        };
-
         image = recorder->makeYUVAPromiseTexture(curImage.yuvColorSpace(),
                                                  backendFormats,
                                                  sizes,
-                                                 yuvaIndices,
+                                                 curImage.yuvaIndices(),
                                                  curImage.overallWidth(),
                                                  curImage.overallHeight(),
                                                  GrSurfaceOrigin::kTopLeft_GrSurfaceOrigin,
@@ -219,17 +216,27 @@
                                                              image->uniqueID(),
                                                              overallII);
 
-    SkYUVSizeInfo yuvSizeInfo;
+    SkYUVSizeInfo yuvaSizeInfo;
+    SkYUVAIndex yuvaIndices[SkYUVAIndex::kIndexCount];
     SkYUVColorSpace yuvColorSpace;
-    const void* planes[3];
-    sk_sp<SkCachedData> yuvData = ib->getPlanes(&yuvSizeInfo, &yuvColorSpace, planes);
+    const void* planes[SkYUVSizeInfo::kMaxCount];
+    sk_sp<SkCachedData> yuvData = ib->getPlanes(&yuvaSizeInfo, yuvaIndices, &yuvColorSpace, planes);
     if (yuvData) {
-        newImageInfo.setYUVData(std::move(yuvData), yuvColorSpace);
+        newImageInfo.setYUVData(std::move(yuvData), yuvaIndices, yuvColorSpace);
 
-        for (int i = 0; i < 3; ++i) {
-            SkImageInfo planeII = SkImageInfo::MakeA8(yuvSizeInfo.fSizes[i].fWidth,
-                                                      yuvSizeInfo.fSizes[i].fHeight);
-            newImageInfo.addYUVPlane(i, planeII, planes[i], yuvSizeInfo.fWidthBytes[i]);
+        for (int i = 0; i < SkYUVSizeInfo::kMaxCount; ++i) {
+            if (kUnknown_SkColorType == yuvaSizeInfo.fColorTypes[i]) {
+                SkASSERT(!yuvaSizeInfo.fSizes[i].fWidth &&
+                         !yuvaSizeInfo.fSizes[i].fHeight &&
+                         !yuvaSizeInfo.fWidthBytes[i]);
+                continue;
+            }
+
+            SkImageInfo planeII = SkImageInfo::Make(yuvaSizeInfo.fSizes[i].fWidth,
+                                                    yuvaSizeInfo.fSizes[i].fHeight,
+                                                    yuvaSizeInfo.fColorTypes[i],
+                                                    kUnpremul_SkAlphaType);
+            newImageInfo.addYUVPlane(i, planeII, planes[i], yuvaSizeInfo.fWidthBytes[i]);
         }
     } else {
         sk_sp<SkImage> rasterImage = image->makeRasterImage(); // force decoding of lazy images
diff --git a/tools/DDLPromiseImageHelper.h b/tools/DDLPromiseImageHelper.h
index 4c6c163..763fe05 100644
--- a/tools/DDLPromiseImageHelper.h
+++ b/tools/DDLPromiseImageHelper.h
@@ -13,12 +13,14 @@
 
 #include "GrBackendSurface.h"
 #include "SkCachedData.h"
+#include "SkYUVAIndex.h"
 #include "SkYUVSizeInfo.h"
 
 class GrContext;
 class SkDeferredDisplayListRecorder;
 class SkImage;
 class SkPicture;
+struct SkYUVAIndex;
 
 // This class consolidates tracking & extraction of the original image data from an skp,
 // the upload of said data to the GPU and the fulfillment of promise images.
@@ -116,9 +118,13 @@
             SkASSERT(this->isYUV());
             return fYUVColorSpace;
         }
+        const SkYUVAIndex* yuvaIndices() const {
+            SkASSERT(this->isYUV());
+            return fYUVAIndices;
+        }
         const SkPixmap& yuvPixmap(int index) const {
             SkASSERT(this->isYUV());
-            SkASSERT(index >= 0 && index < 3);
+            SkASSERT(index >= 0 && index < SkYUVSizeInfo::kMaxCount);
             return fYUVPlanes[index];
         }
         const SkBitmap& normalBitmap() const {
@@ -127,34 +133,37 @@
         }
 
         void setCallbackContext(int index, sk_sp<PromiseImageCallbackContext> callbackContext) {
-            SkASSERT(index >= 0 && index < (this->isYUV() ? 3 : 1));
+            SkASSERT(index >= 0 && index < (this->isYUV() ? SkYUVSizeInfo::kMaxCount : 1));
             fCallbackContexts[index] = callbackContext;
         }
         PromiseImageCallbackContext* callbackContext(int index) {
-            SkASSERT(index >= 0 && index < (this->isYUV() ? 3 : 1));
+            SkASSERT(index >= 0 && index < (this->isYUV() ? SkYUVSizeInfo::kMaxCount : 1));
             return fCallbackContexts[index].get();
         }
         sk_sp<PromiseImageCallbackContext> refCallbackContext(int index) const {
-            SkASSERT(index >= 0 && index < (this->isYUV() ? 3 : 1));
+            SkASSERT(index >= 0 && index < (this->isYUV() ? SkYUVSizeInfo::kMaxCount : 1));
             return fCallbackContexts[index];
         }
 
         const GrCaps* caps() const { return fCallbackContexts[0]->caps(); }
 
         const GrBackendTexture& backendTexture(int index) const {
-            SkASSERT(index >= 0 && index < (this->isYUV() ? 3 : 1));
+            SkASSERT(index >= 0 && index < (this->isYUV() ? SkYUVSizeInfo::kMaxCount : 1));
             return fCallbackContexts[index]->backendTexture();
         }
 
         void setNormalBitmap(const SkBitmap& bm) { fBitmap = bm; }
 
-        void setYUVData(sk_sp<SkCachedData> yuvData, SkYUVColorSpace cs) {
+        void setYUVData(sk_sp<SkCachedData> yuvData,
+                        SkYUVAIndex yuvaIndices[SkYUVAIndex::kIndexCount],
+                        SkYUVColorSpace cs) {
             fYUVData = yuvData;
+            memcpy(fYUVAIndices, yuvaIndices, sizeof(fYUVAIndices));
             fYUVColorSpace = cs;
         }
         void addYUVPlane(int index, const SkImageInfo& ii, const void* plane, size_t widthBytes) {
             SkASSERT(this->isYUV());
-            SkASSERT(index >= 0 && index < 3);
+            SkASSERT(index >= 0 && index < SkYUVSizeInfo::kMaxCount);
             fYUVPlanes[index].reset(ii, plane, widthBytes);
         }
 
@@ -170,10 +179,11 @@
         // CPU-side cache of a YUV SkImage's contents
         sk_sp<SkCachedData>                fYUVData;       // when !null, this is a YUV image
         SkYUVColorSpace                    fYUVColorSpace = kJPEG_SkYUVColorSpace;
-        SkPixmap                           fYUVPlanes[3];
+        SkYUVAIndex                        fYUVAIndices[SkYUVAIndex::kIndexCount];
+        SkPixmap                           fYUVPlanes[SkYUVSizeInfo::kMaxCount];
 
-        // Up to 3 for a YUV image. Only one for a normal image.
-        sk_sp<PromiseImageCallbackContext> fCallbackContexts[3];
+        // Up to SkYUVSizeInfo::kMaxCount for a YUVA image. Only one for a normal image.
+        sk_sp<PromiseImageCallbackContext> fCallbackContexts[SkYUVSizeInfo::kMaxCount];
     };
 
     // This stack-based context allows each thread to re-inflate the image indices into