Split SkYUVAInfo::PlanarConfig into PlaneConfig and Subsampling enums

Sometimes it's helpful to think about subsampling separately from
how the channels are spread across planes.

Bug: skia:10632
Change-Id: Ib03f71195f9706ef6def418b1f2125c29e0cf738
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/334102
Commit-Queue: Brian Salomon <bsalomon@google.com>
Reviewed-by: Jim Van Verth <jvanverth@google.com>
Reviewed-by: Robert Phillips <robertphillips@google.com>
diff --git a/RELEASE_NOTES.txt b/RELEASE_NOTES.txt
index 59b06d5..848f86c 100644
--- a/RELEASE_NOTES.txt
+++ b/RELEASE_NOTES.txt
@@ -7,6 +7,10 @@
 Milestone 88
 ------------
 
+  * SkYUVAInfo now has separate enums for division of channels among planes and
+    the subsampling. The previous combined enum, PlanarConfig, is deprecated.
+    https://review.skia.org/334102
+
   * Simplified SkDeferredDisplayListRecorder promise image API. Removed "release"
     callback and renamed "done" callback to "release". The new "release" proc can
     be null. Added a new SkYUVAInfo-based factory for YUVA promise texture images
diff --git a/experimental/ffmpeg/SkVideoDecoder.cpp b/experimental/ffmpeg/SkVideoDecoder.cpp
index 2d46f66..f5b805a 100644
--- a/experimental/ffmpeg/SkVideoDecoder.cpp
+++ b/experimental/ffmpeg/SkVideoDecoder.cpp
@@ -166,7 +166,10 @@
                                    int const strides[],
                                    SkYUVColorSpace yuvSpace,
                                    sk_sp<SkColorSpace> cs) {
-    SkYUVAInfo yuvaInfo({w, h}, SkYUVAInfo::PlanarConfig::kY_U_V_420, yuvSpace);
+    SkYUVAInfo yuvaInfo({w, h},
+                        SkYUVAInfo::PlaneConfig::kY_U_V,
+                        SkYUVAInfo::Subsampling::k420,
+                        yuvSpace);
     SkPixmap pixmaps[3];
     pixmaps[0].reset(SkImageInfo::MakeA8(w, h), data[0], strides[0]);
     w = (w + 1)/2;
diff --git a/gm/asyncrescaleandread.cpp b/gm/asyncrescaleandread.cpp
index b4d55c9..32070e3 100644
--- a/gm/asyncrescaleandread.cpp
+++ b/gm/asyncrescaleandread.cpp
@@ -94,7 +94,10 @@
     if (!asyncContext.fResult) {
         return nullptr;
     }
-    SkYUVAInfo yuvaInfo(size, SkYUVAInfo::PlanarConfig::kY_U_V_420, yuvCS);
+    SkYUVAInfo yuvaInfo(size,
+                        SkYUVAInfo::PlaneConfig::kY_U_V,
+                        SkYUVAInfo::Subsampling::k420,
+                        yuvCS);
     SkPixmap yuvPMs[] = {
             {yII,  asyncContext.fResult->data(0), asyncContext.fResult->rowBytes(0)},
             {uvII, asyncContext.fResult->data(1), asyncContext.fResult->rowBytes(1)},
diff --git a/gm/imagefromyuvtextures.cpp b/gm/imagefromyuvtextures.cpp
index 2a50973..9eef57b 100644
--- a/gm/imagefromyuvtextures.cpp
+++ b/gm/imagefromyuvtextures.cpp
@@ -61,7 +61,8 @@
             bmp = copy;
         }
         SkYUVAPixmapInfo pixmapInfo({bmp.dimensions(),
-                                     SkYUVAInfo::PlanarConfig::kY_U_V_A_4204,
+                                     SkYUVAInfo::PlaneConfig::kY_U_V_A,
+                                     SkYUVAInfo::Subsampling::k420,
                                      kJPEG_Full_SkYUVColorSpace},
                                     SkYUVAPixmapInfo::DataType::kUnorm8,
                                     nullptr);
diff --git a/gm/wacky_yuv_formats.cpp b/gm/wacky_yuv_formats.cpp
index 400378c..5993d5e 100644
--- a/gm/wacky_yuv_formats.cpp
+++ b/gm/wacky_yuv_formats.cpp
@@ -123,51 +123,63 @@
             case kP016F_YUVFormat:
             case kNV12_YUVFormat:
                 if (opaque) {
-                    fPlanarConfig = SkYUVAInfo::PlanarConfig::kY_UV_420;
+                    fPlaneConfig = SkYUVAInfo::PlaneConfig::kY_UV;
+                    fSubsampling = SkYUVAInfo::Subsampling::k420;
                 } else {
-                    fPlanarConfig = SkYUVAInfo::PlanarConfig::kY_UV_A_4204;
+                    fPlaneConfig = SkYUVAInfo::PlaneConfig::kY_UV_A;
+                    fSubsampling = SkYUVAInfo::Subsampling::k420;
                 }
                 break;
             case kY416_YUVFormat:
             case kY410_YUVFormat:
                 if (opaque) {
-                    fPlanarConfig = SkYUVAInfo::PlanarConfig::kUYV_444;
+                    fPlaneConfig = SkYUVAInfo::PlaneConfig::kUYV;
+                    fSubsampling = SkYUVAInfo::Subsampling::k444;
                 } else {
-                    fPlanarConfig = SkYUVAInfo::PlanarConfig::kUYVA_4444;
+                    fPlaneConfig = SkYUVAInfo::PlaneConfig::kUYVA;
+                    fSubsampling = SkYUVAInfo::Subsampling::k444;
                 }
                 break;
             case kAYUV_YUVFormat:
                 if (opaque) {
-                    fPlanarConfig = SkYUVAInfo::PlanarConfig::kYUV_444;
+                    fPlaneConfig = SkYUVAInfo::PlaneConfig::kYUV;
+                    fSubsampling = SkYUVAInfo::Subsampling::k444;
                 } else {
-                    fPlanarConfig = SkYUVAInfo::PlanarConfig::kYUVA_4444;
+                    fPlaneConfig = SkYUVAInfo::PlaneConfig::kYUVA;
+                    fSubsampling = SkYUVAInfo::Subsampling::k444;
                 }
                 break;
             case kNV21_YUVFormat:
                 if (opaque) {
-                    fPlanarConfig = SkYUVAInfo::PlanarConfig::kY_VU_420;
+                    fPlaneConfig = SkYUVAInfo::PlaneConfig::kY_VU;
+                    fSubsampling = SkYUVAInfo::Subsampling::k420;
                 } else {
-                    fPlanarConfig = SkYUVAInfo::PlanarConfig::kY_VU_A_4204;
+                    fPlaneConfig = SkYUVAInfo::PlaneConfig::kY_VU_A;
+                    fSubsampling = SkYUVAInfo::Subsampling::k420;
                 }
                 break;
             case kI420_YUVFormat:
                 if (opaque) {
-                    fPlanarConfig = SkYUVAInfo::PlanarConfig::kY_U_V_420;
+                    fPlaneConfig = SkYUVAInfo::PlaneConfig::kY_U_V;
+                    fSubsampling = SkYUVAInfo::Subsampling::k420;
                 } else {
-                    fPlanarConfig = SkYUVAInfo::PlanarConfig::kY_U_V_A_4204;
+                    fPlaneConfig = SkYUVAInfo::PlaneConfig::kY_U_V_A;
+                    fSubsampling = SkYUVAInfo::Subsampling::k420;
                 }
                 break;
             case kYV12_YUVFormat:
                 if (opaque) {
-                    fPlanarConfig = SkYUVAInfo::PlanarConfig::kY_V_U_420;
+                    fPlaneConfig = SkYUVAInfo::PlaneConfig::kY_V_U;
+                    fSubsampling = SkYUVAInfo::Subsampling::k420;
                 } else {
-                    fPlanarConfig = SkYUVAInfo::PlanarConfig::kY_V_U_A_4204;
+                    fPlaneConfig = SkYUVAInfo::PlaneConfig::kY_V_U_A;
+                    fSubsampling = SkYUVAInfo::Subsampling::k420;
                 }
                 break;
         }
     }
 
-    int numPlanes() const { return SkYUVAInfo::NumPlanes(fPlanarConfig); }
+    int numPlanes() const { return SkYUVAInfo::NumPlanes(fPlaneConfig); }
 
     SkYUVAPixmaps makeYUVAPixmaps(SkISize dimensions,
                                   SkYUVColorSpace yuvColorSpace,
@@ -175,14 +187,15 @@
                                   int numBitmaps) const;
 
 private:
-    SkYUVAInfo::PlanarConfig fPlanarConfig;
+    SkYUVAInfo::PlaneConfig fPlaneConfig;
+    SkYUVAInfo::Subsampling fSubsampling;
 };
 
 SkYUVAPixmaps YUVAPlanarConfig::makeYUVAPixmaps(SkISize dimensions,
                                                 SkYUVColorSpace yuvColorSpace,
                                                 const SkBitmap bitmaps[],
                                                 int numBitmaps) const {
-    SkYUVAInfo info(dimensions, fPlanarConfig, yuvColorSpace);
+    SkYUVAInfo info(dimensions, fPlaneConfig, fSubsampling, yuvColorSpace);
     SkPixmap pmaps[SkYUVAInfo::kMaxPlanes];
     int n = info.numPlanes();
     if (numBitmaps < n) {
@@ -1198,7 +1211,10 @@
         for (auto cs : {kRec709_SkYUVColorSpace, kRec601_SkYUVColorSpace, kJPEG_SkYUVColorSpace,
                         kBT2020_SkYUVColorSpace}) {
             split_into_yuv(fOrig.get(), cs, fPM);
-            SkYUVAInfo yuvaInfo(fOrig->dimensions(), SkYUVAInfo::PlanarConfig::kY_U_V_444, cs);
+            SkYUVAInfo yuvaInfo(fOrig->dimensions(),
+                                SkYUVAInfo::PlaneConfig::kY_U_V,
+                                SkYUVAInfo::Subsampling::k444,
+                                cs);
             auto yuvaPixmaps = SkYUVAPixmaps::FromExternalPixmaps(yuvaInfo, fPM);
             auto img = SkImage::MakeFromYUVAPixmaps(canvas->recordingContext(),
                                                     yuvaPixmaps,
diff --git a/include/core/SkYUVAInfo.h b/include/core/SkYUVAInfo.h
index 0c0cb51..a87994b 100644
--- a/include/core/SkYUVAInfo.h
+++ b/include/core/SkYUVAInfo.h
@@ -13,6 +13,8 @@
 #include "include/core/SkSize.h"
 #include "include/core/SkYUVAIndex.h"
 
+#include <tuple>
+
 /**
  * Specifies the structure of planes for a YUV image with optional alpha. The actual planar data
  * is not part of this structure and depending on usage is in external textures or pixmaps.
@@ -30,13 +32,63 @@
      * RG               0:R,    1:G
      * RGB              0:R,    1:G, 2:B
      * RGBA             0:R,    1:G, 2:B, 3:A
+     */
+    enum class PlaneConfig {
+        kUnknown,
+
+        kY_U_V,    ///< Plane 0: Y, Plane 1: U,  Plane 2: V
+        kY_V_U,    ///< Plane 0: Y, Plane 1: V,  Plane 2: U
+        kY_UV,     ///< Plane 0: Y, Plane 1: UV
+        kY_VU,     ///< Plane 0: Y, Plane 1: VU
+        kYUV,      ///< Plane 0: YUV
+        kUYV,      ///< Plane 0: UYV
+
+        kY_U_V_A,  ///< Plane 0: Y, Plane 1: U,  Plane 2: V, Plane 3: A
+        kY_V_U_A,  ///< Plane 0: Y, Plane 1: V,  Plane 2: U, Plane 3: A
+        kY_UV_A,   ///< Plane 0: Y, Plane 1: UV, Plane 2: A
+        kY_VU_A,   ///< Plane 0: Y, Plane 1: VU, Plane 2: A
+        kYUVA,     ///< Plane 0: YUVA
+        kUYVA,     ///< Plane 0: UYVA
+
+        kLast = kUYVA
+    };
+
+    /**
+     * UV subsampling is also specified in the enum value names using J:a:b notation (e.g. 4:2:0 is
+     * 1/2 horizontal and 1/2 vertical resolution for U and V). If alpha is present it is not sub-
+     * sampled. Note that Subsampling values other than k444 are only valid with PlaneConfig values
+     * that have U and V in different planes than Y (and A, if present).
+     */
+    enum class Subsampling {
+        kUnknown,
+
+        k444,    ///< No subsampling. UV values for each Y.
+        k422,    ///< 1 set of UV values for each 2x1 block of Y values.
+        k420,    ///< 1 set of UV values for each 2x2 block of Y values.
+        k440,    ///< 1 set of UV values for each 1x2 block of Y values.
+        k411,    ///< 1 set of UV values for each 4x1 block of Y values.
+        k410,    ///< 1 set of UV values for each 4x2 block of Y values.
+
+        kLast = k410
+    };
+
+    /**
+     * Deprecated in favor of separate PlaneConfig and Subsampling enums.
+     *
+     * Specifies how YUV (and optionally A) are divided among planes. Planes are separated by
+     * underscores in the enum value names. Within each plane the pixmap/texture channels are
+     * mapped to the YUVA channels in the order specified, e.g. for kY_UV Y is in channel 0 of plane
+     * 0, U is in channel 0 of plane 1, and V is in channel 1 of plane 1. Channel ordering
+     * within a pixmap/texture given the channels it contains:
+     * A:               0:A
+     * Luminance/Gray:  0:Gray
+     * RG               0:R,    1:G
+     * RGB              0:R,    1:G, 2:B
+     * RGBA             0:R,    1:G, 2:B, 3:A
      *
      * UV subsampling is also specified in the enum value names using J:a:b notation (e.g. 4:2:0 is
      * 1/2 horizontal and 1/2 vertical resolution for U and V). A fourth number is added if alpha
      * is present (always 4 as only full resolution alpha is supported).
-     *
-     * Currently this only has three-plane formats but more will be added as usage and testing of
-     * this expands.
      */
     enum class PlanarConfig {
         kUnknown,
@@ -65,6 +117,9 @@
         kUYVA_4444,    ///< Plane 0: UYVA
     };
 
+    static constexpr std::tuple<PlaneConfig, Subsampling>
+            PlanarConfigToPlaneConfigAndSubsampling(PlanarConfig);
+
     /**
      * Describes how subsampled chroma values are sited relative to luma values.
      *
@@ -80,37 +135,39 @@
     static constexpr int kMaxPlanes = 4;
 
     /**
-     * Given image dimensions, a planar configuration, and origin, determine the expected size of
-     * each plane. Returns the number of expected planes. planeDimensions[0] through
-     * planeDimensons[<ret>] are written. The input image dimensions are as displayed (after the
-     * planes have been transformed to the intended display orientation). The plane dimensions
-     * are output as stored in memory.
+     * Given image dimensions, a planer configuration, subsampling, and origin, determine the
+     * expected size of each plane. Returns the number of expected planes. planeDimensions[0]
+     * through planeDimensions[<ret>] are written. The input image dimensions are as displayed
+     * (after the planes have been transformed to the intended display orientation). The plane
+     * dimensions are output as the planes are stored in memory (may be rotated from image
+     * dimensions).
      */
     static int PlaneDimensions(SkISize imageDimensions,
-                               PlanarConfig,
+                               PlaneConfig,
+                               Subsampling ,
                                SkEncodedOrigin,
                                SkISize planeDimensions[kMaxPlanes]);
 
-    /** Number of planes for a given PlanarConfig. */
-    static constexpr int NumPlanes(PlanarConfig);
+    /** Number of planes for a given PlaneConfig. */
+    static constexpr int NumPlanes(PlaneConfig);
 
     /**
-     * Number of Y, U, V, A channels in the ith plane for a given PlanarConfig (or 0 if i is
+     * Number of Y, U, V, A channels in the ith plane for a given PlaneConfig (or 0 if i is
      * invalid).
      */
-    static constexpr int NumChannelsInPlane(PlanarConfig, int i);
+    static constexpr int NumChannelsInPlane(PlaneConfig, int i);
 
     /**
-     * Given a PlanarConfig and a set of channel flags for each plane, convert to SkYUVAIndex
-     * representation. Fails if channel flags aren't valid for the PlanarConfig (i.e. don't have
+     * Given a PlaneConfig and a set of channel flags for each plane, convert to SkYUVAIndex
+     * representation. Fails if channel flags aren't valid for the PlaneConfig (i.e. don't have
      * enough channels in a plane).
      */
-    static bool GetYUVAIndices(PlanarConfig,
+    static bool GetYUVAIndices(PlaneConfig,
                                const uint32_t planeChannelFlags[kMaxPlanes],
                                SkYUVAIndex indices[SkYUVAIndex::kIndexCount]);
 
-    /** Does the PlanarConfig have alpha values? */
-    static bool HasAlpha(PlanarConfig);
+    /** Does the PlaneConfig have alpha values? */
+    static bool HasAlpha(PlaneConfig);
 
     SkYUVAInfo() = default;
     SkYUVAInfo(const SkYUVAInfo&) = default;
@@ -120,6 +177,20 @@
      * oriented to how the image is displayed as indicated by 'origin').
      */
     SkYUVAInfo(SkISize dimensions,
+               PlaneConfig,
+               Subsampling,
+               SkYUVColorSpace,
+               SkEncodedOrigin origin = kTopLeft_SkEncodedOrigin,
+               Siting sitingX = Siting::kCentered,
+               Siting sitingY = Siting::kCentered);
+
+    /**
+     * Deprecated in favor of constructor that takes PlaneConfig and Subsampling.
+     *
+     * 'dimensions' should specify the size of the full resolution image (after planes have been
+     * oriented to how the image is displayed as indicated by 'origin').
+     */
+    SkYUVAInfo(SkISize dimensions,
                PlanarConfig,
                SkYUVColorSpace,
                SkEncodedOrigin origin = kTopLeft_SkEncodedOrigin,
@@ -128,7 +199,14 @@
 
     SkYUVAInfo& operator=(const SkYUVAInfo& that) = default;
 
-    PlanarConfig planarConfig() const { return fPlanarConfig; }
+    PlaneConfig planeConfig() const { return fPlaneConfig; }
+    Subsampling subsampling() const { return fSubsampling; }
+
+    /**
+     * Deprecated. May return kUnknown even if this is valid because not all valid PlaneConfig/
+     * Subsampling pairs have am equivalent PlanarConfig value.
+     */
+    PlanarConfig planarConfig() const;
 
     /**
      * Dimensions of the full resolution image (after planes have been oriented to how the image
@@ -144,7 +222,7 @@
 
     SkEncodedOrigin origin() const { return fOrigin; }
 
-    bool hasAlpha() const { return HasAlpha(fPlanarConfig); }
+    bool hasAlpha() const { return HasAlpha(fPlaneConfig); }
 
     /**
      * Returns the number of planes and initializes planeDimensions[0]..planeDimensions[<ret>] to
@@ -152,7 +230,7 @@
      * transformation to image display space as indicated by origin().
      */
     int planeDimensions(SkISize planeDimensions[kMaxPlanes]) const {
-        return PlaneDimensions(fDimensions, fPlanarConfig, fOrigin, planeDimensions);
+        return PlaneDimensions(fDimensions, fPlaneConfig, fSubsampling, fOrigin, planeDimensions);
     }
 
     /**
@@ -163,28 +241,29 @@
     size_t computeTotalBytes(const size_t rowBytes[kMaxPlanes],
                              size_t planeSizes[kMaxPlanes] = nullptr) const;
 
-    int numPlanes() const { return NumPlanes(fPlanarConfig); }
+    int numPlanes() const { return NumPlanes(fPlaneConfig); }
 
-    int numChannelsInPlane(int i) const { return NumChannelsInPlane(fPlanarConfig, i); }
+    int numChannelsInPlane(int i) const { return NumChannelsInPlane(fPlaneConfig, i); }
 
     /**
      * Given a set of channel flags for each plane, converts this->planarConfig() to SkYUVAIndex
-     * representation. Fails if the channel flags aren't valid for the PlanarConfig (i.e. don't have
+     * representation. Fails if the channel flags aren't valid for the PlaneConfig (i.e. don't have
      * enough channels in a plane).
      */
     bool toYUVAIndices(const uint32_t channelFlags[4], SkYUVAIndex indices[4]) const {
-        return GetYUVAIndices(fPlanarConfig, channelFlags, indices);
+        return GetYUVAIndices(fPlaneConfig, channelFlags, indices);
     }
 
     bool operator==(const SkYUVAInfo& that) const;
     bool operator!=(const SkYUVAInfo& that) const { return !(*this == that); }
 
-    bool isValid() const { return fPlanarConfig != PlanarConfig::kUnknown; }
+    bool isValid() const { return fPlaneConfig != PlaneConfig::kUnknown; }
 
 private:
     SkISize fDimensions = {0, 0};
 
-    PlanarConfig fPlanarConfig = PlanarConfig::kUnknown;
+    PlaneConfig fPlaneConfig = PlaneConfig::kUnknown;
+    Subsampling fSubsampling = Subsampling::kUnknown;
 
     SkYUVColorSpace fYUVColorSpace = SkYUVColorSpace::kIdentity_SkYUVColorSpace;
 
@@ -198,74 +277,87 @@
     Siting fSitingY = Siting::kCentered;
 };
 
-constexpr int SkYUVAInfo::NumPlanes(PlanarConfig planarConfig) {
-    switch (planarConfig) {
-        case PlanarConfig::kUnknown:      return 0;
-
-        case PlanarConfig::kY_U_V_444:    return 3;
-        case PlanarConfig::kY_U_V_422:    return 3;
-        case PlanarConfig::kY_U_V_420:    return 3;
-        case PlanarConfig::kY_V_U_420:    return 3;
-        case PlanarConfig::kY_U_V_440:    return 3;
-        case PlanarConfig::kY_U_V_411:    return 3;
-        case PlanarConfig::kY_U_V_410:    return 3;
-
-        case PlanarConfig::kY_U_V_A_4204: return 4;
-        case PlanarConfig::kY_V_U_A_4204: return 4;
-
-        case PlanarConfig::kY_UV_420:     return 2;
-        case PlanarConfig::kY_VU_420:     return 2;
-
-        case PlanarConfig::kY_UV_A_4204:  return 3;
-        case PlanarConfig::kY_VU_A_4204:  return 3;
-
-        case PlanarConfig::kYUV_444:      return 1;
-        case PlanarConfig::kUYV_444:      return 1;
-        case PlanarConfig::kYUVA_4444:    return 1;
-        case PlanarConfig::kUYVA_4444:    return 1;
+constexpr int SkYUVAInfo::NumPlanes(PlaneConfig planeConfig) {
+    switch (planeConfig) {
+        case PlaneConfig::kUnknown: return 0;
+        case PlaneConfig::kY_U_V:   return 3;
+        case PlaneConfig::kY_V_U:   return 3;
+        case PlaneConfig::kY_UV:    return 2;
+        case PlaneConfig::kY_VU:    return 2;
+        case PlaneConfig::kYUV:     return 1;
+        case PlaneConfig::kUYV:     return 1;
+        case PlaneConfig::kY_U_V_A: return 4;
+        case PlaneConfig::kY_V_U_A: return 4;
+        case PlaneConfig::kY_UV_A:  return 3;
+        case PlaneConfig::kY_VU_A:  return 3;
+        case PlaneConfig::kYUVA:    return 1;
+        case PlaneConfig::kUYVA:    return 1;
     }
     SkUNREACHABLE;
 }
 
-constexpr int SkYUVAInfo::NumChannelsInPlane(PlanarConfig config, int i) {
+constexpr int SkYUVAInfo::NumChannelsInPlane(PlaneConfig config, int i) {
     switch (config) {
-        case PlanarConfig::kUnknown:
+        case PlaneConfig::kUnknown:
             return 0;
 
-        case SkYUVAInfo::PlanarConfig::kY_U_V_444:
-        case SkYUVAInfo::PlanarConfig::kY_U_V_422:
-        case SkYUVAInfo::PlanarConfig::kY_U_V_420:
-        case SkYUVAInfo::PlanarConfig::kY_V_U_420:
-        case SkYUVAInfo::PlanarConfig::kY_U_V_440:
-        case SkYUVAInfo::PlanarConfig::kY_U_V_411:
-        case SkYUVAInfo::PlanarConfig::kY_U_V_410:
+        case SkYUVAInfo::PlaneConfig::kY_U_V:
+        case SkYUVAInfo::PlaneConfig::kY_V_U:
             return i >= 0 && i < 3 ? 1 : 0;
-        case SkYUVAInfo::PlanarConfig::kY_U_V_A_4204:
-        case SkYUVAInfo::PlanarConfig::kY_V_U_A_4204:
-            return i >= 0 && i < 4 ? 1 : 0;
-        case SkYUVAInfo::PlanarConfig::kY_UV_420:
-        case SkYUVAInfo::PlanarConfig::kY_VU_420:
+        case SkYUVAInfo::PlaneConfig::kY_UV:
+        case SkYUVAInfo::PlaneConfig::kY_VU:
             switch (i) {
                 case 0:  return 1;
                 case 1:  return 2;
                 default: return 0;
             }
-        case SkYUVAInfo::PlanarConfig::kY_UV_A_4204:
-        case SkYUVAInfo::PlanarConfig::kY_VU_A_4204:
+        case SkYUVAInfo::PlaneConfig::kYUV:
+        case SkYUVAInfo::PlaneConfig::kUYV:
+            return i == 0 ? 3 : 0;
+        case SkYUVAInfo::PlaneConfig::kY_U_V_A:
+        case SkYUVAInfo::PlaneConfig::kY_V_U_A:
+            return i >= 0 && i < 4 ? 1 : 0;
+        case SkYUVAInfo::PlaneConfig::kY_UV_A:
+        case SkYUVAInfo::PlaneConfig::kY_VU_A:
             switch (i) {
                 case 0:  return 1;
                 case 1:  return 2;
                 case 2:  return 1;
                 default: return 0;
             }
-        case SkYUVAInfo::PlanarConfig::kYUV_444:
-        case SkYUVAInfo::PlanarConfig::kUYV_444:
-            return i == 0 ? 3 : 0;
-        case SkYUVAInfo::PlanarConfig::kYUVA_4444:
-        case SkYUVAInfo::PlanarConfig::kUYVA_4444:
+        case SkYUVAInfo::PlaneConfig::kYUVA:
+        case SkYUVAInfo::PlaneConfig::kUYVA:
             return i == 0 ? 4 : 0;
     }
     return 0;
 }
 
+constexpr std::tuple<SkYUVAInfo::PlaneConfig, SkYUVAInfo::Subsampling>
+SkYUVAInfo::PlanarConfigToPlaneConfigAndSubsampling(PlanarConfig planarConfig) {
+    switch (planarConfig) {
+        case PlanarConfig::kUnknown:
+            return {PlaneConfig::kUnknown, Subsampling ::kUnknown};
+
+        case PlanarConfig::kY_U_V_444:    return {PlaneConfig::kY_U_V,   Subsampling::k444};
+        case PlanarConfig::kY_U_V_422:    return {PlaneConfig::kY_U_V,   Subsampling::k422};
+        case PlanarConfig::kY_U_V_420:    return {PlaneConfig::kY_U_V,   Subsampling::k420};
+        case PlanarConfig::kY_V_U_420:    return {PlaneConfig::kY_V_U,   Subsampling::k420};
+        case PlanarConfig::kY_U_V_440:    return {PlaneConfig::kY_U_V,   Subsampling::k440};
+        case PlanarConfig::kY_U_V_411:    return {PlaneConfig::kY_U_V,   Subsampling::k411};
+        case PlanarConfig::kY_U_V_410:    return {PlaneConfig::kY_U_V,   Subsampling::k410};
+        case PlanarConfig::kY_U_V_A_4204: return {PlaneConfig::kY_U_V_A, Subsampling::k420};
+        case PlanarConfig::kY_V_U_A_4204: return {PlaneConfig::kY_V_U_A, Subsampling::k420};
+        case PlanarConfig::kY_UV_420:     return {PlaneConfig::kY_UV,    Subsampling::k420};
+        case PlanarConfig::kY_VU_420:     return {PlaneConfig::kY_VU,    Subsampling::k420};
+        case PlanarConfig::kY_UV_A_4204:  return {PlaneConfig::kY_UV_A,  Subsampling::k420};
+        case PlanarConfig::kY_VU_A_4204:  return {PlaneConfig::kY_VU_A,  Subsampling::k420};
+        case PlanarConfig::kYUV_444:      return {PlaneConfig::kYUV,     Subsampling::k444};
+        case PlanarConfig::kUYV_444:      return {PlaneConfig::kUYV,     Subsampling::k444};
+        case PlanarConfig::kYUVA_4444:    return {PlaneConfig::kYUVA,    Subsampling::k444};
+        case PlanarConfig::kUYVA_4444:    return {PlaneConfig::kUYVA,    Subsampling::k444};
+    }
+    SkUNREACHABLE;
+}
+
+
 #endif
diff --git a/include/core/SkYUVAPixmaps.h b/include/core/SkYUVAPixmaps.h
index bf0950b..b4d04ac 100644
--- a/include/core/SkYUVAPixmaps.h
+++ b/include/core/SkYUVAPixmaps.h
@@ -29,6 +29,8 @@
 public:
     static constexpr auto kMaxPlanes = SkYUVAInfo::kMaxPlanes;
 
+    using PlaneConfig  = SkYUVAInfo::PlaneConfig;
+    using Subsampling  = SkYUVAInfo::Subsampling;
     using PlanarConfig = SkYUVAInfo::PlanarConfig;
 
     /**
@@ -53,13 +55,16 @@
         /** Init based on texture formats supported by the context. */
         SupportedDataTypes(const GrImageContext&);
 
-        /** All legal combinations of PlanarConfig and DataType are supported. */
+        /** All legal combinations of PlaneConfig and DataType are supported. */
         static constexpr SupportedDataTypes All();
 
         /**
          * Checks whether there is a supported combination of color types for planes structured
-         * as indicated by PlanarConfig with channel data types as indicated by DataType.
+         * as indicated by PlaneConfig with channel data types as indicated by DataType.
          */
+        constexpr bool supported(PlaneConfig, DataType) const;
+
+        /** Deprecated. Use PlaneConfig version. */
         constexpr bool supported(PlanarConfig, DataType) const;
 
         /**
@@ -152,7 +157,7 @@
      * Returns true if this has been configured with a non-empty dimensioned SkYUVAInfo with
      * compatible color types and row bytes.
      */
-    bool isValid() const { return fPlaneInfos[0].colorType() != kUnknown_SkColorType; }
+    bool isValid() const { return fYUVAInfo.isValid(); }
 
     /** Is this valid and does it use color types allowed by the passed SupportedDataTypes? */
     bool isSupported(const SupportedDataTypes&) const;
@@ -281,7 +286,7 @@
     return combinations;
 }
 
-constexpr bool SkYUVAPixmapInfo::SupportedDataTypes::supported(PlanarConfig config,
+constexpr bool SkYUVAPixmapInfo::SupportedDataTypes::supported(PlaneConfig config,
                                                                DataType type) const {
     int n = SkYUVAInfo::NumPlanes(config);
     for (int i = 0; i < n; ++i) {
@@ -295,6 +300,12 @@
     return true;
 }
 
+constexpr bool SkYUVAPixmapInfo::SupportedDataTypes::supported(PlanarConfig planarConfig,
+                                                               DataType type) const {
+    auto pc = std::get<0>(SkYUVAInfo::PlanarConfigToPlaneConfigAndSubsampling(planarConfig));
+    return this->supported(pc, type);
+}
+
 constexpr SkColorType SkYUVAPixmapInfo::DefaultColorTypeForDataType(DataType dataType,
                                                                     int numChannels) {
     switch (numChannels) {
diff --git a/include/gpu/GrYUVABackendTextures.h b/include/gpu/GrYUVABackendTextures.h
index da230e7..2acf043 100644
--- a/include/gpu/GrYUVABackendTextures.h
+++ b/include/gpu/GrYUVABackendTextures.h
@@ -23,8 +23,6 @@
 public:
     static constexpr auto kMaxPlanes = SkYUVAInfo::kMaxPlanes;
 
-    using PlanarConfig = SkYUVAInfo::PlanarConfig;
-
     /** Default GrYUVABackendTextureInfo is invalid. */
     GrYUVABackendTextureInfo() = default;
 
diff --git a/src/codec/SkJpegCodec.cpp b/src/codec/SkJpegCodec.cpp
index d7b3974..6c5a28c 100644
--- a/src/codec/SkJpegCodec.cpp
+++ b/src/codec/SkJpegCodec.cpp
@@ -796,24 +796,25 @@
     SkASSERT(hSampY == dinfo->max_h_samp_factor);
     SkASSERT(vSampY == dinfo->max_v_samp_factor);
 
-    SkYUVAInfo::PlanarConfig tempPlanarConfig;
+    SkYUVAInfo::Subsampling tempSubsampling;
     if        (1 == hSampY && 1 == vSampY) {
-        tempPlanarConfig = SkYUVAInfo::PlanarConfig::kY_U_V_444;
+        tempSubsampling = SkYUVAInfo::Subsampling::k444;
     } else if (2 == hSampY && 1 == vSampY) {
-        tempPlanarConfig = SkYUVAInfo::PlanarConfig::kY_U_V_422;
+        tempSubsampling = SkYUVAInfo::Subsampling::k422;
     } else if (2 == hSampY && 2 == vSampY) {
-        tempPlanarConfig = SkYUVAInfo::PlanarConfig::kY_U_V_420;
+        tempSubsampling = SkYUVAInfo::Subsampling::k420;
     } else if (1 == hSampY && 2 == vSampY) {
-        tempPlanarConfig = SkYUVAInfo::PlanarConfig::kY_U_V_440;
+        tempSubsampling = SkYUVAInfo::Subsampling::k440;
     } else if (4 == hSampY && 1 == vSampY) {
-        tempPlanarConfig = SkYUVAInfo::PlanarConfig::kY_U_V_411;
+        tempSubsampling = SkYUVAInfo::Subsampling::k411;
     } else if (4 == hSampY && 2 == vSampY) {
-        tempPlanarConfig = SkYUVAInfo::PlanarConfig::kY_U_V_410;
+        tempSubsampling = SkYUVAInfo::Subsampling::k410;
     } else {
         return false;
     }
     if (supportedDataTypes &&
-        !supportedDataTypes->supported(tempPlanarConfig, SkYUVAPixmapInfo::DataType::kUnorm8)) {
+        !supportedDataTypes->supported(SkYUVAInfo::PlaneConfig::kY_U_V,
+                                       SkYUVAPixmapInfo::DataType::kUnorm8)) {
         return false;
     }
     if (yuvaPixmapInfo) {
@@ -824,7 +825,8 @@
             rowBytes[i] = dinfo->comp_info[i].width_in_blocks * DCTSIZE;
         }
         SkYUVAInfo yuvaInfo(codec.dimensions(),
-                            tempPlanarConfig,
+                            SkYUVAInfo::PlaneConfig::kY_U_V,
+                            tempSubsampling,
                             kJPEG_Full_SkYUVColorSpace,
                             codec.getOrigin(),
                             SkYUVAInfo::Siting::kCentered,
diff --git a/src/core/SkYUVAInfo.cpp b/src/core/SkYUVAInfo.cpp
index c504f69..a7fb245 100644
--- a/src/core/SkYUVAInfo.cpp
+++ b/src/core/SkYUVAInfo.cpp
@@ -8,10 +8,31 @@
 #include "include/core/SkYUVAInfo.h"
 #include "src/core/SkSafeMath.h"
 
+#include <algorithm>
+
+static bool is_plane_config_compatible_with_subsampling(SkYUVAInfo::PlaneConfig config,
+                                                        SkYUVAInfo::Subsampling subsampling) {
+    if (config      == SkYUVAInfo::PlaneConfig::kUnknown ||
+        subsampling == SkYUVAInfo::Subsampling::kUnknown) {
+        return false;
+    }
+    return subsampling == SkYUVAInfo::Subsampling::k444 ||
+           (config != SkYUVAInfo::PlaneConfig::kYUV  &&
+            config != SkYUVAInfo::PlaneConfig::kYUVA &&
+            config != SkYUVAInfo::PlaneConfig::kUYV  &&
+            config != SkYUVAInfo::PlaneConfig::kUYVA);
+}
+
 int SkYUVAInfo::PlaneDimensions(SkISize imageDimensions,
-                                PlanarConfig planarConfig,
+                                PlaneConfig planeConfig,
+                                Subsampling subsampling,
                                 SkEncodedOrigin origin,
                                 SkISize planeDimensions[SkYUVAInfo::kMaxPlanes]) {
+    std::fill_n(planeDimensions, SkYUVAInfo::kMaxPlanes, SkISize{0, 0});
+    if (!is_plane_config_compatible_with_subsampling(planeConfig, subsampling)) {
+        return 0;
+    }
+
     int w = imageDimensions.width();
     int h = imageDimensions.height();
     if (origin >= kLeftTop_SkEncodedOrigin) {
@@ -20,66 +41,50 @@
     }
     auto down2 = [](int x) { return (x + 1)/2; };
     auto down4 = [](int x) { return (x + 3)/4; };
-    switch (planarConfig) {
-        case PlanarConfig::kUnknown:
-            planeDimensions[0] =
-            planeDimensions[1] =
-            planeDimensions[2] =
-            planeDimensions[3] = {0, 0};
-            return 0;
-        case PlanarConfig::kY_U_V_444:
-            planeDimensions[0] = planeDimensions[1] = planeDimensions[2] = {w, h};
-            planeDimensions[3] = {0, 0};
-            return 3;
-        case PlanarConfig::kY_U_V_422:
+    SkISize uvSize;
+    switch (subsampling) {
+        case Subsampling::kUnknown: SkUNREACHABLE;
+
+        case Subsampling::k444: uvSize = {      w ,       h }; break;
+        case Subsampling::k422: uvSize = {down2(w),       h }; break;
+        case Subsampling::k420: uvSize = {down2(w), down2(h)}; break;
+        case Subsampling::k440: uvSize = {      w , down2(h)}; break;
+        case Subsampling::k411: uvSize = {down4(w),       h }; break;
+        case Subsampling::k410: uvSize = {down4(w), down2(h)}; break;
+    }
+    switch (planeConfig) {
+        case PlaneConfig::kUnknown: SkUNREACHABLE;
+
+        case PlaneConfig::kY_U_V:
+        case PlaneConfig::kY_V_U:
             planeDimensions[0] = {w, h};
-            planeDimensions[1] = planeDimensions[2] = {down2(w), h};
-            planeDimensions[3] = {0, 0};
+            planeDimensions[1] = planeDimensions[2] = uvSize;
             return 3;
-        case PlanarConfig::kY_U_V_420:
-        case PlanarConfig::kY_V_U_420:
+
+        case PlaneConfig::kY_UV:
+        case PlaneConfig::kY_VU:
             planeDimensions[0] = {w, h};
-            planeDimensions[1] = planeDimensions[2] = {down2(w), down2(h)};
-            planeDimensions[3] = {0, 0};
-            return 3;
-        case PlanarConfig::kY_UV_A_4204:
-        case PlanarConfig::kY_VU_A_4204:
-            planeDimensions[0] = planeDimensions[2] = {w, h};
-            planeDimensions[1] = {down2(w), down2(h)};
-            planeDimensions[3] = {0, 0};
-            return 3;
-        case PlanarConfig::kY_U_V_440:
-            planeDimensions[0] = {w, h};
-            planeDimensions[1] = planeDimensions[2] = {w, down2(h)};
-            planeDimensions[3] = {0, 0};
-            return 3;
-        case PlanarConfig::kY_U_V_411:
-            planeDimensions[0] = {w, h};
-            planeDimensions[1] = planeDimensions[2] = {down4(w), h};
-            planeDimensions[3] = {0, 0};
-            return 3;
-        case PlanarConfig::kY_U_V_410:
-            planeDimensions[0] = {w, h};
-            planeDimensions[1] = planeDimensions[2] = {down4(w), down2(h)};
-            planeDimensions[3] = {0, 0};
-            return 3;
-        case PlanarConfig::kY_U_V_A_4204:
-        case PlanarConfig::kY_V_U_A_4204:
-            planeDimensions[0] = planeDimensions[3] = {w, h};
-            planeDimensions[1] = planeDimensions[2] = {down2(w), down2(h)};
-            return 4;
-        case PlanarConfig::kY_UV_420:
-        case PlanarConfig::kY_VU_420:
-            planeDimensions[0] = {w, h};
-            planeDimensions[1] = {down2(w), down2(h)};
-            planeDimensions[2] = planeDimensions[3] = {0, 0};
+            planeDimensions[1] = uvSize;
             return 2;
-        case PlanarConfig::kYUV_444:
-        case PlanarConfig::kUYV_444:
-        case PlanarConfig::kYUVA_4444:
-        case PlanarConfig::kUYVA_4444:
+
+        case PlaneConfig::kY_U_V_A:
+        case PlaneConfig::kY_V_U_A:
+            planeDimensions[0] = planeDimensions[3] = {w, h};
+            planeDimensions[1] = planeDimensions[2] = uvSize;
+            return 4;
+
+        case PlaneConfig::kY_UV_A:
+        case PlaneConfig::kY_VU_A:
+            planeDimensions[0] = planeDimensions[2] = {w, h};
+            planeDimensions[1] = uvSize;
+            return 3;
+
+        case PlaneConfig::kYUV:
+        case PlaneConfig::kUYV:
+        case PlaneConfig::kYUVA:
+        case PlaneConfig::kUYVA:
             planeDimensions[0] = {w, h};
-            planeDimensions[1] = planeDimensions[2] = planeDimensions[3] = {0, 0};
+            SkASSERT(planeDimensions[0] == uvSize);
             return 1;
     }
     SkUNREACHABLE;
@@ -125,76 +130,71 @@
     }
 }
 
-bool SkYUVAInfo::GetYUVAIndices(PlanarConfig config,
+bool SkYUVAInfo::GetYUVAIndices(PlaneConfig config,
                                 const uint32_t planeChannelFlags[kMaxPlanes],
                                 SkYUVAIndex indices[SkYUVAIndex::kIndexCount]) {
     struct Location {int plane, chanIdx;};
     const Location* locations = nullptr;
     switch (config) {
-        case PlanarConfig::kUnknown:
+        case PlaneConfig::kUnknown:
             return false;
 
-        case PlanarConfig::kY_U_V_444:
-        case PlanarConfig::kY_U_V_422:
-        case PlanarConfig::kY_U_V_420:
-        case PlanarConfig::kY_U_V_440:
-        case PlanarConfig::kY_U_V_411:
-        case PlanarConfig::kY_U_V_410: {
+        case PlaneConfig::kY_U_V: {
             static constexpr Location kLocations[] = {{0, 0}, {1, 0}, {2, 0}, {-1, -1}};
             locations = kLocations;
             break;
         }
-        case PlanarConfig::kY_V_U_420: {
+        case PlaneConfig::kY_V_U: {
             static constexpr Location kLocations[] = {{0, 0}, {2, 0}, {1, 0}, {-1, -1}};
             locations = kLocations;
             break;
         }
-        case PlanarConfig::kY_U_V_A_4204: {
-            static constexpr Location kLocations[] = {{0, 0}, {1, 0}, {2, 0}, {3, 0}};
-            locations = kLocations;
-            break;
-        }
-        case PlanarConfig::kY_V_U_A_4204: {
-            static constexpr Location kLocations[] = {{0, 0}, {2, 0}, {1, 0}, {3, 0}};
-            locations = kLocations;
-            break;
-        }
-        case PlanarConfig::kY_UV_420: {
+        case PlaneConfig::kY_UV: {
             static constexpr Location kLocations[] = {{0, 0}, {1, 0}, {1, 1}, {-1, -1}};
             locations = kLocations;
             break;
         }
-        case PlanarConfig::kY_VU_420: {
+        case PlaneConfig::kY_VU: {
             static constexpr Location kLocations[] = {{0, 0}, {1, 1}, {1, 0}, {-1, -1}};
             locations = kLocations;
             break;
         }
-        case PlanarConfig::kY_UV_A_4204: {
-            static constexpr Location kLocations[] = {{0, 0}, {1, 0}, {1, 1}, {2, 0}};
-            locations = kLocations;
-            break;
-        }
-        case PlanarConfig::kY_VU_A_4204: {
-            static constexpr Location kLocations[] = {{0, 0}, {1, 1}, {1, 0}, {2, 0}};
-            locations = kLocations;
-            break;
-        }
-        case PlanarConfig::kYUV_444: {
+        case PlaneConfig::kYUV: {
             static constexpr Location kLocations[] = {{0, 0}, {0, 1}, {0, 2}, {-1, -1}};
             locations = kLocations;
             break;
         }
-        case PlanarConfig::kUYV_444: {
+        case PlaneConfig::kUYV: {
             static constexpr Location kLocations[] = {{0, 1}, {0, 0}, {0, 2}, {-1, -1}};
             locations = kLocations;
             break;
         }
-        case PlanarConfig::kYUVA_4444: {
+        case PlaneConfig::kY_U_V_A: {
+            static constexpr Location kLocations[] = {{0, 0}, {1, 0}, {2, 0}, {3, 0}};
+            locations = kLocations;
+            break;
+        }
+        case PlaneConfig::kY_V_U_A: {
+            static constexpr Location kLocations[] = {{0, 0}, {2, 0}, {1, 0}, {3, 0}};
+            locations = kLocations;
+            break;
+        }
+        case PlaneConfig::kY_UV_A: {
+            static constexpr Location kLocations[] = {{0, 0}, {1, 0}, {1, 1}, {2, 0}};
+            locations = kLocations;
+            break;
+        }
+        case PlaneConfig::kY_VU_A: {
+            static constexpr Location kLocations[] = {{0, 0}, {1, 1}, {1, 0}, {2, 0}};
+            locations = kLocations;
+            break;
+        }
+        case PlaneConfig::kYUVA: {
             static constexpr Location kLocations[] = {{0, 0}, {0, 1}, {0, 2}, {0, 3}};
             locations = kLocations;
             break;
         }
-        case PlanarConfig::kUYVA_4444: {
+        case PlaneConfig::kUYVA: {
             static constexpr Location kLocations[] = {{0, 1}, {0, 0}, {0, 2}, {0, 3}};
             locations = kLocations;
             break;
@@ -217,56 +217,58 @@
     return true;
 }
 
-bool SkYUVAInfo::HasAlpha(PlanarConfig planarConfig) {
-    switch (planarConfig) {
-        case PlanarConfig::kUnknown:      return false;
+bool SkYUVAInfo::HasAlpha(PlaneConfig planeConfig) {
+    switch (planeConfig) {
+        case PlaneConfig::kUnknown: return false;
 
-        case PlanarConfig::kY_U_V_444:    return false;
-        case PlanarConfig::kY_U_V_422:    return false;
-        case PlanarConfig::kY_U_V_420:    return false;
-        case PlanarConfig::kY_V_U_420:    return false;
-        case PlanarConfig::kY_U_V_440:    return false;
-        case PlanarConfig::kY_U_V_411:    return false;
-        case PlanarConfig::kY_U_V_410:    return false;
+        case PlaneConfig::kY_U_V:   return false;
+        case PlaneConfig::kY_V_U:   return false;
+        case PlaneConfig::kY_UV:    return false;
+        case PlaneConfig::kY_VU:    return false;
+        case PlaneConfig::kYUV:     return false;
+        case PlaneConfig::kUYV:     return false;
 
-        case PlanarConfig::kY_U_V_A_4204: return true;
-        case PlanarConfig::kY_V_U_A_4204: return true;
-
-        case PlanarConfig::kY_UV_420:     return false;
-        case PlanarConfig::kY_VU_420:     return false;
-
-        case PlanarConfig::kY_UV_A_4204:  return true;
-        case PlanarConfig::kY_VU_A_4204:  return true;
-
-        case PlanarConfig::kYUV_444:      return false;
-        case PlanarConfig::kUYV_444:      return false;
-
-        case PlanarConfig::kYUVA_4444:    return true;
-        case PlanarConfig::kUYVA_4444:    return true;
+        case PlaneConfig::kY_U_V_A: return true;
+        case PlaneConfig::kY_V_U_A: return true;
+        case PlaneConfig::kY_UV_A:  return true;
+        case PlaneConfig::kY_VU_A:  return true;
+        case PlaneConfig::kYUVA:    return true;
+        case PlaneConfig::kUYVA:    return true;
     }
     SkUNREACHABLE;
 }
 
 SkYUVAInfo::SkYUVAInfo(SkISize dimensions,
+                       PlaneConfig planeConfig,
+                       Subsampling subsampling,
+                       SkYUVColorSpace yuvColorSpace,
+                       SkEncodedOrigin origin,
+                       Siting sitingX,
+                       Siting sitingY)
+        : fDimensions(dimensions)
+        , fPlaneConfig(planeConfig)
+        , fSubsampling(subsampling)
+        , fYUVColorSpace(yuvColorSpace)
+        , fOrigin(origin)
+        , fSitingX(sitingX)
+        , fSitingY(sitingY) {
+    if (fDimensions.isEmpty() ||
+        !is_plane_config_compatible_with_subsampling(planeConfig, subsampling)) {
+        *this = {};
+        SkASSERT(!this->isValid());
+        return;
+    }
+    SkASSERT(this->isValid());
+}
+
+SkYUVAInfo::SkYUVAInfo(SkISize dimensions,
                        PlanarConfig planarConfig,
                        SkYUVColorSpace yuvColorSpace,
                        SkEncodedOrigin origin,
                        Siting sitingX,
-                       Siting sitingY)
-    : fDimensions(dimensions)
-    , fPlanarConfig(planarConfig)
-    , fYUVColorSpace(yuvColorSpace)
-    , fOrigin(origin)
-    , fSitingX(sitingX)
-    , fSitingY(sitingY) {
-    if (fDimensions.width() <= 0 ||
-        fDimensions.height() <= 0 ||
-        planarConfig == PlanarConfig::kUnknown) {
-        *this = {};
-        SkASSERT(!this->isValid());
-        return;
-    }
-    SkASSERT(this->isValid());
+                       Siting sitingY) {
+    auto [c, s] = PlanarConfigToPlaneConfigAndSubsampling(planarConfig);
+    *this = SkYUVAInfo(dimensions, c, s, yuvColorSpace, origin, sitingX, sitingY);
 }
 
 size_t SkYUVAInfo::computeTotalBytes(const size_t rowBytes[kMaxPlanes],
@@ -302,8 +304,75 @@
     return safe.ok() ? totalBytes : SIZE_MAX;
 }
 
+SkYUVAInfo::PlanarConfig SkYUVAInfo::planarConfig() const {
+    SkASSERT(!this->isValid() || is_plane_config_compatible_with_subsampling(fPlaneConfig,
+                                                                             fSubsampling));
+    switch (fPlaneConfig) {
+        case PlaneConfig::kUnknown:  return PlanarConfig::kUnknown;
+
+        case PlaneConfig::kY_U_V:
+            switch (fSubsampling) {
+                case Subsampling::kUnknown: SkUNREACHABLE;
+
+                case Subsampling::k444: return PlanarConfig::kY_U_V_444;
+                case Subsampling::k422: return PlanarConfig::kY_U_V_422;
+                case Subsampling::k420: return PlanarConfig::kY_U_V_420;
+                case Subsampling::k440: return PlanarConfig::kY_U_V_440;
+                case Subsampling::k411: return PlanarConfig::kY_U_V_411;
+                case Subsampling::k410: return PlanarConfig::kY_U_V_410;
+            }
+            SkUNREACHABLE;
+
+        case PlaneConfig::kY_V_U:
+            return fSubsampling == Subsampling::k420 ? PlanarConfig::kY_V_U_420
+                                                     : PlanarConfig::kUnknown;
+
+        case PlaneConfig::kY_UV:
+            return fSubsampling == Subsampling::k420 ? PlanarConfig::kY_UV_420
+                                                     : PlanarConfig::kUnknown;
+
+        case PlaneConfig::kY_VU:
+            return fSubsampling == Subsampling::k420 ? PlanarConfig::kY_VU_420
+                                                     : PlanarConfig::kUnknown;
+
+        case PlaneConfig::kYUV:
+            SkASSERT(fSubsampling == Subsampling::k444);
+            return PlanarConfig::kYUV_444;
+
+        case PlaneConfig::kUYV:
+            SkASSERT(fSubsampling == Subsampling::k444);
+            return PlanarConfig::kUYV_444;
+
+        case PlaneConfig::kY_U_V_A:
+            return fSubsampling == Subsampling::k420 ? PlanarConfig::kY_U_V_A_4204
+                                                     : PlanarConfig::kUnknown;
+
+        case PlaneConfig::kY_V_U_A:
+            return fSubsampling == Subsampling::k420 ? PlanarConfig::kY_V_U_A_4204
+                                                     : PlanarConfig::kUnknown;
+
+        case PlaneConfig::kY_UV_A:
+            return fSubsampling == Subsampling::k420 ? PlanarConfig::kY_UV_A_4204
+                                                     : PlanarConfig::kUnknown;
+
+        case PlaneConfig::kY_VU_A:
+            return fSubsampling == Subsampling::k420 ? PlanarConfig::kY_VU_A_4204
+                                                     : PlanarConfig::kUnknown;
+
+        case PlaneConfig::kYUVA:
+            SkASSERT(fSubsampling == Subsampling::k444);
+            return PlanarConfig::kYUVA_4444;
+
+        case PlaneConfig::kUYVA:
+            SkASSERT(fSubsampling == Subsampling::k444);
+            return PlanarConfig::kUYVA_4444;
+    }
+    SkUNREACHABLE;
+}
+
 bool SkYUVAInfo::operator==(const SkYUVAInfo& that) const {
-    return fPlanarConfig  == that.fPlanarConfig  &&
+    return fPlaneConfig   == that.fPlaneConfig   &&
+           fSubsampling   == that.fSubsampling  &&
            fYUVColorSpace == that.fYUVColorSpace &&
            fDimensions    == that.fDimensions    &&
            fSitingX       == that.fSitingX       &&
diff --git a/src/core/SkYUVAPixmaps.cpp b/src/core/SkYUVAPixmaps.cpp
index 36d1562..5ef1268 100644
--- a/src/core/SkYUVAPixmaps.cpp
+++ b/src/core/SkYUVAPixmaps.cpp
@@ -172,7 +172,7 @@
     if (!this->isValid()) {
         return false;
     }
-    return supportedDataTypes.supported(fYUVAInfo.planarConfig(), fDataType);
+    return supportedDataTypes.supported(fYUVAInfo.planeConfig(), fDataType);
 }
 
 //////////////////////////////////////////////////////////////////////////////
diff --git a/tests/ImageGeneratorTest.cpp b/tests/ImageGeneratorTest.cpp
index cd8526d..ddb0be2 100644
--- a/tests/ImageGeneratorTest.cpp
+++ b/tests/ImageGeneratorTest.cpp
@@ -67,7 +67,8 @@
     // Check that the YUV decoding API does not cause any crashes
     ig.queryYUVAInfo(SkYUVAPixmapInfo::SupportedDataTypes::All(), &yuvaPixmapInfo);
     SkYUVAInfo yuvaInfo({250, 250},
-                        SkYUVAInfo::PlanarConfig::kY_UV_420,
+                        SkYUVAInfo::PlaneConfig::kY_UV,
+                        SkYUVAInfo::Subsampling::k420,
                         kJPEG_Full_SkYUVColorSpace);
     yuvaPixmapInfo = SkYUVAPixmapInfo(yuvaInfo,
                                       SkYUVAPixmapInfo::DataType::kUnorm8,
diff --git a/tests/ImageTest.cpp b/tests/ImageTest.cpp
index a77bd47..a42103f 100644
--- a/tests/ImageTest.cpp
+++ b/tests/ImageTest.cpp
@@ -1372,7 +1372,10 @@
 static sk_sp<SkImage> make_yuva_image(GrDirectContext* dContext) {
     SkAutoPixmapStorage pm;
     pm.alloc(SkImageInfo::Make(1, 1, kAlpha_8_SkColorType, kPremul_SkAlphaType));
-    SkYUVAInfo yuvaInfo({1, 1}, SkYUVAInfo::PlanarConfig::kY_U_V_444, kJPEG_Full_SkYUVColorSpace);
+    SkYUVAInfo yuvaInfo({1, 1},
+                        SkYUVAInfo::PlaneConfig::kY_U_V,
+                        SkYUVAInfo::Subsampling::k444,
+                        kJPEG_Full_SkYUVColorSpace);
     const SkPixmap pmaps[] = {pm, pm, pm};
     auto yuvaPixmaps = SkYUVAPixmaps::FromExternalPixmaps(yuvaInfo, pmaps);
 
diff --git a/tests/YUVCacheTest.cpp b/tests/YUVCacheTest.cpp
index 95794f8..c8fb9dc 100644
--- a/tests/YUVCacheTest.cpp
+++ b/tests/YUVCacheTest.cpp
@@ -34,7 +34,8 @@
     SkResourceCache cache(1024);
 
     SkYUVAInfo yuvaInfo({5, 5},
-                        SkYUVAInfo::PlanarConfig::kY_U_V_420,
+                        SkYUVAInfo::PlaneConfig::kY_U_V,
+                        SkYUVAInfo::Subsampling::k420,
                         kRec601_Limited_SkYUVColorSpace);
     SkYUVAPixmapInfo yuvaPixmapInfo(yuvaInfo,
                                     SkYUVAPixmapInfo::DataType::kUnorm8,
diff --git a/tests/YUVTest.cpp b/tests/YUVTest.cpp
index 6c947d9..77203fd 100644
--- a/tests/YUVTest.cpp
+++ b/tests/YUVTest.cpp
@@ -82,39 +82,40 @@
 }
 
 DEF_TEST(Jpeg_YUV_Codec, r) {
-    auto setExpectations = [](SkISize dims, SkYUVAInfo::PlanarConfig planarConfig) {
+    auto setExpectations = [](SkISize dims, SkYUVAInfo::Subsampling subsampling) {
         return SkYUVAInfo(dims,
-                          planarConfig,
+                          SkYUVAInfo::PlaneConfig::kY_U_V,
+                          subsampling,
                           kJPEG_Full_SkYUVColorSpace,
                           kTopLeft_SkEncodedOrigin,
                           SkYUVAInfo::Siting::kCentered,
                           SkYUVAInfo::Siting::kCentered);
     };
 
-    SkYUVAInfo expectations = setExpectations({128, 128}, SkYUVAInfo::PlanarConfig::kY_U_V_420);
+    SkYUVAInfo expectations = setExpectations({128, 128}, SkYUVAInfo::Subsampling::k420);
     codec_yuv(r, "images/color_wheel.jpg", &expectations);
 
     // H2V2
-    expectations = setExpectations({512, 512}, SkYUVAInfo::PlanarConfig::kY_U_V_420);
+    expectations = setExpectations({512, 512}, SkYUVAInfo::Subsampling::k420);
     codec_yuv(r, "images/mandrill_512_q075.jpg", &expectations);
 
     // H1V1
-    expectations = setExpectations({512, 512}, SkYUVAInfo::PlanarConfig::kY_U_V_444);
+    expectations = setExpectations({512, 512}, SkYUVAInfo::Subsampling::k444);
     codec_yuv(r, "images/mandrill_h1v1.jpg", &expectations);
 
     // H2V1
-    expectations = setExpectations({512, 512}, SkYUVAInfo::PlanarConfig::kY_U_V_422);
+    expectations = setExpectations({512, 512}, SkYUVAInfo::Subsampling::k422);
     codec_yuv(r, "images/mandrill_h2v1.jpg", &expectations);
 
     // Non-power of two dimensions
-    expectations = setExpectations({439, 154}, SkYUVAInfo::PlanarConfig::kY_U_V_420);
+    expectations = setExpectations({439, 154}, SkYUVAInfo::Subsampling::k420);
     codec_yuv(r, "images/cropped_mandrill.jpg", &expectations);
 
-    expectations = setExpectations({8, 8}, SkYUVAInfo::PlanarConfig::kY_U_V_420);
+    expectations = setExpectations({8, 8}, SkYUVAInfo::Subsampling::k420);
     codec_yuv(r, "images/randPixels.jpg", &expectations);
 
     // Progressive images
-    expectations = setExpectations({512, 512}, SkYUVAInfo::PlanarConfig::kY_U_V_444);
+    expectations = setExpectations({512, 512}, SkYUVAInfo::Subsampling::k444);
     codec_yuv(r, "images/brickwork-texture.jpg", &expectations);
     codec_yuv(r, "images/brickwork_normal-map.jpg", &expectations);