Add function to GrDataUtils to handle color conversions.

Like SkConvertPixels but knows about all GrColorTypes, origin, and can
apply an arbitrary GrSwizzle.

Use in GrSurfaceContext read/write pixels methods.

Add support for '0' to GrSwizzle.


Change-Id: Ib9dd215fcb0ee8b33c4020893c22b4ab7ce1f40b
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/220761
Commit-Queue: Brian Salomon <bsalomon@google.com>
Reviewed-by: Greg Daniel <egdaniel@google.com>
diff --git a/src/gpu/GrCaps.cpp b/src/gpu/GrCaps.cpp
index adcedb6..ffacbc5 100644
--- a/src/gpu/GrCaps.cpp
+++ b/src/gpu/GrCaps.cpp
@@ -348,3 +348,9 @@
 GrBackendFormat GrCaps::getBackendFormatFromColorType(SkColorType ct) const {
     return this->getBackendFormatFromGrColorType(SkColorTypeToGrColorType(ct), GrSRGBEncoded::kNo);
 }
+
+GrCaps::SupportedRead GrCaps::supportedReadPixelsColorType(GrPixelConfig config,
+                                                           const GrBackendFormat&,
+                                                           GrColorType dstColorType) const {
+    return SupportedRead{GrSwizzle::RGBA(), GrPixelConfigToColorType(config)};
+}
diff --git a/src/gpu/GrCaps.h b/src/gpu/GrCaps.h
index 5d5d3cc..2a25bc5 100644
--- a/src/gpu/GrCaps.h
+++ b/src/gpu/GrCaps.h
@@ -205,18 +205,26 @@
      * the data into in order to use GrGpu::writePixels().
      */
     virtual GrColorType supportedWritePixelsColorType(GrPixelConfig config,
-                                                      GrColorType /*srcColorType*/) const {
+                                                      GrColorType srcColorType) const {
         return GrPixelConfigToColorType(config);
     }
 
+    struct SupportedRead {
+        GrSwizzle fSwizzle;
+        GrColorType fColorType;
+    };
+
     /**
-     * Given a src pixel config and a dst color type what color type must the caller read to using
-     * GrGpu::readPixels() and then coax into dstColorType.
+     * Given a src surface's pixel config and its backend format as well as a color type the caller
+     * would like read into, this provides a legal color type that the caller may pass to
+     * GrGpu::readPixels(). The returned color type may differ from the passed dstColorType, in
+     * which case the caller must convert the read pixel data (see GrConvertPixels). When converting
+     * to dstColorType the swizzle in the returned struct should be applied. The caller must check
+     * the returned color type for kUnknown.
      */
-    virtual GrColorType supportedReadPixelsColorType(GrPixelConfig config,
-                                                     GrColorType /*dstColorType*/) const {
-        return GrPixelConfigToColorType(config);
-    }
+    virtual SupportedRead supportedReadPixelsColorType(GrPixelConfig srcConfig,
+                                                       const GrBackendFormat& srcFormat,
+                                                       GrColorType dstColorType) const;
 
     /** Are transfer buffers (to textures and from surfaces) supported? */
     bool transferBufferSupport() const { return fTransferBufferSupport; }
diff --git a/src/gpu/GrDataUtils.cpp b/src/gpu/GrDataUtils.cpp
index 965251e..abfe351 100644
--- a/src/gpu/GrDataUtils.cpp
+++ b/src/gpu/GrDataUtils.cpp
@@ -6,8 +6,9 @@
  */
 
 #include "src/gpu/GrDataUtils.h"
-
 #include "include/private/GrColor.h"
+#include "src/core/SkColorSpaceXformSteps.h"
+#include "src/core/SkTLazy.h"
 #include "src/core/SkUtils.h"
 
 struct ETC1Block {
@@ -359,3 +360,190 @@
     }
 }
 
+static GrSwizzle get_load_and_get_swizzle(GrColorType ct, SkRasterPipeline::StockStage* load,
+                                          bool* isNormalized) {
+    GrSwizzle swizzle("rgba");
+    *isNormalized = true;
+    switch (ct) {
+        case GrColorType::kAlpha_8:          *load = SkRasterPipeline::load_a8;      break;
+        case GrColorType::kRGB_565:          *load = SkRasterPipeline::load_565;     break;
+        case GrColorType::kABGR_4444:        *load = SkRasterPipeline::load_4444;    break;
+        case GrColorType::kRGBA_8888:        *load = SkRasterPipeline::load_8888;    break;
+        case GrColorType::kRG_88:            *load = SkRasterPipeline::load_rg88;    break;
+        case GrColorType::kRGBA_1010102:     *load = SkRasterPipeline::load_1010102; break;
+        case GrColorType::kAlpha_F16:        *load = SkRasterPipeline::load_af16;    break;
+        case GrColorType::kRGBA_F16_Clamped: *load = SkRasterPipeline::load_f16;     break;
+        case GrColorType::kRG_1616:          *load = SkRasterPipeline::load_rg1616;  break;
+
+        case GrColorType::kRGBA_F16:         *load = SkRasterPipeline::load_f16;
+                                             *isNormalized = false;
+                                             break;
+        case GrColorType::kRG_F32:           *load = SkRasterPipeline::load_rgf32;
+                                             *isNormalized = false;
+                                             break;
+        case GrColorType::kRGBA_F32:         *load = SkRasterPipeline::load_f32;
+                                             *isNormalized = false;
+                                             break;
+        case GrColorType::kR_16:             *load = SkRasterPipeline::load_a16;
+                                             swizzle = GrSwizzle("a001");
+                                             break;
+        case GrColorType::kGray_8:           *load = SkRasterPipeline::load_a8;
+                                             swizzle = GrSwizzle("aaa1");
+                                             break;
+        case GrColorType::kBGRA_8888:        *load = SkRasterPipeline::load_8888;
+                                             swizzle = GrSwizzle("bgra");
+                                             break;
+        case GrColorType::kRGB_888x:         *load = SkRasterPipeline::load_8888;
+                                             swizzle = GrSwizzle("rgb1");
+                                             break;
+
+        case GrColorType::kUnknown:
+        case GrColorType::kRGB_ETC1:
+            SK_ABORT("unexpected CT");
+    }
+    return swizzle;
+}
+
+static GrSwizzle get_dst_swizzle_and_store(GrColorType ct, SkRasterPipeline::StockStage* store,
+                                           bool* isNormalized) {
+    GrSwizzle swizzle("rgba");
+    *isNormalized = true;
+    switch (ct) {
+        case GrColorType::kAlpha_8:          *store = SkRasterPipeline::store_a8;      break;
+        case GrColorType::kRGB_565:          *store = SkRasterPipeline::store_565;     break;
+        case GrColorType::kABGR_4444:        *store = SkRasterPipeline::store_4444;    break;
+        case GrColorType::kRGBA_8888:        *store = SkRasterPipeline::store_8888;    break;
+        case GrColorType::kRG_88:            *store = SkRasterPipeline::store_rg88;    break;
+        case GrColorType::kRGBA_1010102:     *store = SkRasterPipeline::store_1010102; break;
+        case GrColorType::kRGBA_F16_Clamped: *store = SkRasterPipeline::store_f16;     break;
+        case GrColorType::kRG_1616:          *store = SkRasterPipeline::store_rg1616;  break;
+
+        case GrColorType::kAlpha_F16:        *store = SkRasterPipeline::store_af16;
+                                             *isNormalized = false;
+                                             break;
+        case GrColorType::kRGBA_F16:         *store = SkRasterPipeline::store_f16;
+                                             *isNormalized = false;
+                                             break;
+        case GrColorType::kRG_F32:           *store = SkRasterPipeline::store_rgf32;
+                                             *isNormalized = false;
+                                             break;
+        case GrColorType::kRGBA_F32:         *store = SkRasterPipeline::store_f32;
+                                             *isNormalized = false;
+                                             break;
+        case GrColorType::kR_16:             swizzle = GrSwizzle("000r");
+                                             *store = SkRasterPipeline::store_a16;
+                                             break;
+        case GrColorType::kBGRA_8888:        swizzle = GrSwizzle("bgra");
+                                             *store = SkRasterPipeline::store_8888;
+                                             break;
+        case GrColorType::kRGB_888x:         swizzle = GrSwizzle("rgb1");
+                                             *store = SkRasterPipeline::store_8888;
+                                             break;
+
+        case GrColorType::kGray_8:  // not currently supported as output
+        case GrColorType::kUnknown:
+        case GrColorType::kRGB_ETC1:
+            SK_ABORT("unexpected CT");
+    }
+    return swizzle;
+}
+
+static inline void append_clamp_gamut(SkRasterPipeline* pipeline) {
+    // SkRasterPipeline may not know our color type and also doesn't like caller to directly
+    // append clamp_gamut. Fake it out.
+    static SkImageInfo fakeII = SkImageInfo::MakeN32Premul(1, 1);
+    pipeline->append_gamut_clamp_if_normalized(fakeII);
+}
+
+bool GrConvertPixels(const GrPixelInfo& dstInfo, void* dst, const GrPixelInfo& srcInfo,
+                     const void* src, GrSwizzle swizzle) {
+    if (dstInfo.fWidth != srcInfo.fWidth || srcInfo.fHeight != dstInfo.fHeight) {
+        return false;
+    }
+    if (dstInfo.fWidth <= 0 || dstInfo.fHeight <= 0) {
+        return false;
+    }
+    if (GrColorTypeComponentFlags(dstInfo.fColorInfo.fColorType) & kGray_SkColorTypeComponentFlag) {
+        // We don't currently support conversion to Gray.
+        return false;
+    }
+    size_t srcBpp = GrColorTypeBytesPerPixel(srcInfo.fColorInfo.fColorType);
+    size_t dstBpp = GrColorTypeBytesPerPixel(dstInfo.fColorInfo.fColorType);
+    if (!srcBpp || !dstBpp) {
+        // Either src or dst is compressed or kUnknown.
+        return false;
+    }
+    // SkRasterPipeline operates on row-pixels not row-bytes.
+    SkASSERT(dstInfo.fRowBytes % dstBpp == 0);
+    SkASSERT(srcInfo.fRowBytes % srcBpp == 0);
+
+    SkRasterPipeline::StockStage load;
+    bool srcIsNormalized;
+    auto loadSwizzle =
+            get_load_and_get_swizzle(srcInfo.fColorInfo.fColorType, &load, &srcIsNormalized);
+    loadSwizzle = GrSwizzle::Concat(loadSwizzle, swizzle);
+
+    SkRasterPipeline::StockStage store;
+    bool dstIsNormalized;
+    auto storeSwizzle =
+            get_dst_swizzle_and_store(dstInfo.fColorInfo.fColorType, &store, &dstIsNormalized);
+
+    bool alphaOrCSConversion =
+            (srcInfo.fColorInfo.fAlphaType != dstInfo.fColorInfo.fAlphaType &&
+             srcInfo.fColorInfo.fAlphaType != kOpaque_SkAlphaType) ||
+            !SkColorSpace::Equals(srcInfo.fColorInfo.fColorSpace, dstInfo.fColorInfo.fColorSpace);
+
+    bool clampGamut;
+    SkTLazy<SkColorSpaceXformSteps> steps;
+    GrSwizzle loadStoreSwizzle;
+    if (alphaOrCSConversion) {
+        steps.init(srcInfo.fColorInfo.fColorSpace, srcInfo.fColorInfo.fAlphaType,
+                   dstInfo.fColorInfo.fColorSpace, dstInfo.fColorInfo.fAlphaType);
+        clampGamut = dstIsNormalized && dstInfo.fColorInfo.fAlphaType == kPremul_SkAlphaType;
+    } else {
+        clampGamut = dstIsNormalized && !srcIsNormalized &&
+                     dstInfo.fColorInfo.fAlphaType == kPremul_SkAlphaType;
+        if (!clampGamut) {
+            loadStoreSwizzle = GrSwizzle::Concat(loadSwizzle, storeSwizzle);
+        }
+    }
+    int cnt = 1;
+    int height = srcInfo.fHeight;
+    SkRasterPipeline_MemoryCtx srcCtx{const_cast<void*>(src), SkToInt(srcInfo.fRowBytes / srcBpp)},
+                               dstCtx{                  dst , SkToInt(dstInfo.fRowBytes / dstBpp)};
+    if (srcInfo.fOrigin != dstInfo.fOrigin) {
+        // It *almost* works to point the src at the last row and negate the stride and run the
+        // whole rectangle. However, SkRasterPipeline::run()'s control loop uses size_t loop
+        // variables so it winds up relying on unsigned overflow math. It works out in practice
+        // but UBSAN says "no!" as it's technically undefined and in theory a compiler could emit
+        // code that didn't do what is intended. So we go one row at a time. :(
+        srcCtx.pixels = static_cast<char*>(srcCtx.pixels) + srcInfo.fRowBytes * (height - 1);
+        std::swap(cnt, height);
+    }
+    for (int i = 0; i < cnt; ++i) {
+        SkRasterPipeline_<256> pipeline;
+        pipeline.append(load, &srcCtx);
+
+        if (alphaOrCSConversion) {
+            loadSwizzle.apply(&pipeline);
+            steps->apply(&pipeline, srcIsNormalized);
+            if (clampGamut) {
+                append_clamp_gamut(&pipeline);
+            }
+            storeSwizzle.apply(&pipeline);
+        } else {
+            if (clampGamut) {
+                loadSwizzle.apply(&pipeline);
+                append_clamp_gamut(&pipeline);
+                storeSwizzle.apply(&pipeline);
+            } else {
+                loadStoreSwizzle.apply(&pipeline);
+            }
+        }
+        pipeline.append(store, &dstCtx);
+        pipeline.run(0, 0, srcInfo.fWidth, height);
+        srcCtx.pixels = static_cast<char*>(srcCtx.pixels) - srcInfo.fRowBytes;
+        dstCtx.pixels = static_cast<char*>(dstCtx.pixels) + dstInfo.fRowBytes;
+    }
+    return true;
+}
diff --git a/src/gpu/GrDataUtils.h b/src/gpu/GrDataUtils.h
index b79e84c..fcf7a07 100644
--- a/src/gpu/GrDataUtils.h
+++ b/src/gpu/GrDataUtils.h
@@ -10,6 +10,7 @@
 
 #include "include/core/SkColor.h"
 #include "include/private/GrTypesPriv.h"
+#include "src/gpu/GrSwizzle.h"
 
 // TODO: consolidate all the backend-specific flavors of this method to this
 size_t GrETC1CompressedDataSize(int w, int h);
@@ -33,4 +34,22 @@
                   const SkTArray<size_t>& individualMipOffsets,
                   char* dest, const SkColor4f& color);
 
+struct GrColorInfo {
+    GrColorType fColorType = GrColorType::kUnknown;
+    SkColorSpace* fColorSpace = nullptr;
+    SkAlphaType fAlphaType = kPremul_SkAlphaType;
+};
+
+struct GrPixelInfo {
+    GrColorInfo fColorInfo = {};
+    GrSurfaceOrigin fOrigin = kTopLeft_GrSurfaceOrigin;
+    int fWidth = 0;
+    int fHeight = 0;
+    size_t fRowBytes = 0;
+};
+
+// Swizzle param is applied after loading and before converting from srcInfo to dstInfo.
+bool GrConvertPixels(const GrPixelInfo& dstInfo, void* dst, const GrPixelInfo& srcInfo,
+                     const void* src, GrSwizzle swizzle = GrSwizzle{});
+
 #endif
diff --git a/src/gpu/GrRenderTargetContext.cpp b/src/gpu/GrRenderTargetContext.cpp
index 597631c..6c8c342 100644
--- a/src/gpu/GrRenderTargetContext.cpp
+++ b/src/gpu/GrRenderTargetContext.cpp
@@ -5,6 +5,7 @@
  * found in the LICENSE file.
  */
 
+#include "src/gpu/GrRenderTargetContext.h"
 #include "include/core/SkDrawable.h"
 #include "include/gpu/GrBackendSemaphore.h"
 #include "include/gpu/GrRenderTarget.h"
@@ -26,13 +27,13 @@
 #include "src/gpu/GrBlurUtils.h"
 #include "src/gpu/GrCaps.h"
 #include "src/gpu/GrContextPriv.h"
+#include "src/gpu/GrDataUtils.h"
 #include "src/gpu/GrDrawingManager.h"
 #include "src/gpu/GrFixedClip.h"
 #include "src/gpu/GrGpuResourcePriv.h"
 #include "src/gpu/GrMemoryPool.h"
 #include "src/gpu/GrPathRenderer.h"
 #include "src/gpu/GrRecordingContextPriv.h"
-#include "src/gpu/GrRenderTargetContext.h"
 #include "src/gpu/GrRenderTargetContextPriv.h"
 #include "src/gpu/GrResourceProvider.h"
 #include "src/gpu/GrStencilAttachment.h"
@@ -1960,19 +1961,21 @@
     auto dstCT = SkColorTypeToGrColorType(info.colorType());
     bool needsRescale = srcRect.width() != info.width() || srcRect.height() != info.height();
     GrPixelConfig configOfFinalContext = fRenderTargetProxy->config();
+    auto backendFormatOfFinalContext = fRenderTargetProxy->backendFormat();
     if (needsRescale) {
-        auto backendFormat = this->caps()->getBackendFormatFromColorType(info.colorType());
-        configOfFinalContext =
-                this->caps()->getConfigFromBackendFormat(backendFormat, info.colorType());
+        backendFormatOfFinalContext = this->caps()->getBackendFormatFromColorType(info.colorType());
+        configOfFinalContext = this->caps()->getConfigFromBackendFormat(backendFormatOfFinalContext,
+                                                                        info.colorType());
     }
-    auto readCT = this->caps()->supportedReadPixelsColorType(configOfFinalContext, dstCT);
-    // Fail if we can't do a CPU conversion from readCT to dstCT.
-    if (GrColorTypeToSkColorType(readCT) == kUnknown_SkColorType) {
+    auto readInfo = this->caps()->supportedReadPixelsColorType(configOfFinalContext,
+                                                               backendFormatOfFinalContext, dstCT);
+    // Fail if we can't read from the source surface's color type.
+    if (readInfo.fColorType == GrColorType::kUnknown) {
         callback(context, nullptr, 0);
         return;
     }
     // Fail if readCT does not have all of readCT's color channels.
-    if (GrColorTypeComponentFlags(dstCT) & ~GrColorTypeComponentFlags(readCT)) {
+    if (GrColorTypeComponentFlags(dstCT) & ~GrColorTypeComponentFlags(readInfo.fColorType)) {
         callback(context, nullptr, 0);
         return;
     }
@@ -2042,42 +2045,55 @@
     if (fRenderTargetProxy->wrapsVkSecondaryCB()) {
         return {};
     }
-    auto readCT = this->caps()->supportedReadPixelsColorType(fRenderTargetProxy->config(), dstCT);
-    // Fail if we can't do a CPU conversion from readCT to dstCT.
-    if (readCT != dstCT && (GrColorTypeToSkColorType(readCT) == kUnknown_SkColorType ||
-                            GrColorTypeToSkColorType(dstCT) == kUnknown_SkColorType)) {
-        return {};
-    }
+    auto supportedRead = this->caps()->supportedReadPixelsColorType(
+            fRenderTargetProxy->config(), fRenderTargetProxy->backendFormat(), dstCT);
     // Fail if readCT does not have all of readCT's color channels.
-    if (GrColorTypeComponentFlags(dstCT) & ~GrColorTypeComponentFlags(readCT)) {
+    if (GrColorTypeComponentFlags(dstCT) & ~GrColorTypeComponentFlags(supportedRead.fColorType)) {
         return {};
     }
 
     if (!this->caps()->transferBufferSupport() ||
-        !this->caps()->transferFromOffsetAlignment(readCT)) {
+        !this->caps()->transferFromOffsetAlignment(supportedRead.fColorType)) {
         return {};
     }
 
-    size_t rowBytes = GrColorTypeBytesPerPixel(readCT) * rect.width();
+    size_t rowBytes = GrColorTypeBytesPerPixel(supportedRead.fColorType) * rect.width();
     size_t size = rowBytes * rect.height();
     auto buffer = direct->priv().resourceProvider()->createBuffer(
             size, GrGpuBufferType::kXferGpuToCpu, GrAccessPattern::kStream_GrAccessPattern);
     if (!buffer) {
         return {};
     }
-    this->getRTOpList()->addOp(GrTransferFromOp::Make(fContext, rect, readCT, buffer, 0),
-                               *this->caps());
+    auto srcRect = rect;
+    bool flip = this->origin() == kBottomLeft_GrSurfaceOrigin;
+    if (flip) {
+        srcRect = SkIRect::MakeLTRB(rect.fLeft, this->height() - rect.fBottom, rect.fRight,
+                                    this->height() - rect.fTop);
+    }
+    auto op = GrTransferFromOp::Make(fContext, srcRect, supportedRead.fColorType, buffer, 0);
+    this->getRTOpList()->addOp(std::move(op), *this->caps());
     PixelTransferResult result;
     result.fTransferBuffer = std::move(buffer);
-    if (readCT != dstCT) {
-        result.fPixelConverter =
-                [w = rect.width(), h = rect.height(), dstCT = GrColorTypeToSkColorType(dstCT),
-                 srcCT = GrColorTypeToSkColorType(readCT)](void* dst, const void* src) {
-                    auto dstII = SkImageInfo::Make(w, h, dstCT, kPremul_SkAlphaType, nullptr);
-                    auto srcII = SkImageInfo::Make(w, h, srcCT, kPremul_SkAlphaType, nullptr);
-                    SkConvertPixels(dstII, dst, w * SkColorTypeBytesPerPixel(dstCT), srcII, src,
-                                    w * SkColorTypeBytesPerPixel(srcCT));
-                };
+    if (supportedRead.fColorType != dstCT || supportedRead.fSwizzle != GrSwizzle("rgba") || flip) {
+        result.fPixelConverter = [w = rect.width(), h = rect.height(), dstCT, supportedRead](
+                                         void* dst, const void* src) {
+            GrPixelInfo srcInfo;
+            srcInfo.fColorInfo.fAlphaType = kPremul_SkAlphaType;
+            srcInfo.fColorInfo.fColorType = supportedRead.fColorType;
+            srcInfo.fColorInfo.fColorSpace = nullptr;
+            srcInfo.fRowBytes = GrColorTypeBytesPerPixel(supportedRead.fColorType) * w;
+
+            GrPixelInfo dstInfo;
+            dstInfo.fColorInfo.fAlphaType = kPremul_SkAlphaType;
+            dstInfo.fColorInfo.fColorType = dstCT;
+            dstInfo.fColorInfo.fColorSpace = nullptr;
+            dstInfo.fRowBytes = GrColorTypeBytesPerPixel(dstCT) * w;
+
+            srcInfo.fWidth  = dstInfo.fWidth  = w;
+            srcInfo.fHeight = dstInfo.fHeight = h;
+
+            GrConvertPixels(dstInfo, dst, srcInfo, src, supportedRead.fSwizzle);
+        };
     }
     return result;
 }
@@ -2209,26 +2225,16 @@
         callback(context, nullptr, nullptr);
         return;
     }
-    GrPixelConfig planeConfig = kAlpha_8_GrPixelConfig;
-    GrColorType planeColorType = GrColorType::kAlpha_8;
-    if (this->caps()->supportedReadPixelsColorType(planeConfig, planeColorType) !=
-        GrColorType::kAlpha_8) {
-        // TODO: Because there are issues with reading back/transferring A8 textures on GL, we are
-        // currently using RGBA textures for the planes. Fix this once the A8 read back/transfer
-        // issues are addressed.
-        planeConfig = kRGBA_8888_GrPixelConfig;
-        planeColorType = GrColorType::kRGBA_8888;
-    }
-    const auto backendFormat = this->caps()->getBackendFormatFromGrColorType(
-            planeColorType, GrSRGBEncoded::kNo);
+    const auto backendFormat = this->caps()->getBackendFormatFromGrColorType(GrColorType::kAlpha_8,
+                                                                             GrSRGBEncoded::kNo);
     auto yRTC = direct->priv().makeDeferredRenderTargetContext(
-            backendFormat, SkBackingFit::kApprox, dstW, dstH, planeConfig, dstColorSpace,
+            backendFormat, SkBackingFit::kApprox, dstW, dstH, kAlpha_8_GrPixelConfig, dstColorSpace,
             1, GrMipMapped::kNo, kTopLeft_GrSurfaceOrigin);
     auto uRTC = direct->priv().makeDeferredRenderTargetContext(
-            backendFormat, SkBackingFit::kApprox, dstW / 2, dstH / 2, planeConfig,
+            backendFormat, SkBackingFit::kApprox, dstW / 2, dstH / 2, kAlpha_8_GrPixelConfig,
             dstColorSpace, 1, GrMipMapped::kNo, kTopLeft_GrSurfaceOrigin);
     auto vRTC = direct->priv().makeDeferredRenderTargetContext(
-            backendFormat, SkBackingFit::kApprox, dstW / 2, dstH / 2, planeConfig,
+            backendFormat, SkBackingFit::kApprox, dstW / 2, dstH / 2, kAlpha_8_GrPixelConfig,
             dstColorSpace, 1, GrMipMapped::kNo, kTopLeft_GrSurfaceOrigin);
     if (!yRTC || !uRTC || !vRTC) {
         callback(context, nullptr, nullptr);
diff --git a/src/gpu/GrSurfaceContext.cpp b/src/gpu/GrSurfaceContext.cpp
index 71113b4..4b29ce0 100644
--- a/src/gpu/GrSurfaceContext.cpp
+++ b/src/gpu/GrSurfaceContext.cpp
@@ -13,6 +13,7 @@
 #include "src/core/SkAutoPixmapStorage.h"
 #include "src/gpu/GrClip.h"
 #include "src/gpu/GrContextPriv.h"
+#include "src/gpu/GrDataUtils.h"
 #include "src/gpu/GrDrawingManager.h"
 #include "src/gpu/GrGpu.h"
 #include "src/gpu/GrRecordingContextPriv.h"
@@ -224,71 +225,52 @@
                                        buffer, rowBytes, flags);
     }
 
-    bool convert = unpremul || needColorConversion;
-
     bool flip = srcProxy->origin() == kBottomLeft_GrSurfaceOrigin;
-    if (flip) {
-        top = srcSurface->height() - top - height;
-    }
+    auto supportedRead = caps->supportedReadPixelsColorType(
+            srcProxy->config(), srcProxy->backendFormat(), dstColorType);
 
-    GrColorType allowedColorType = caps->supportedReadPixelsColorType(srcProxy->config(),
-                                                                      dstColorType);
-    convert = convert || (dstColorType != allowedColorType);
+    bool convert = unpremul || needColorConversion || flip ||
+                   (dstColorType != supportedRead.fColorType) ||
+                   supportedRead.fSwizzle != GrSwizzle::RGBA();
 
-    SkAutoPixmapStorage tempPixmap;
-    SkPixmap finalPixmap;
+    std::unique_ptr<char[]> tmpPixels;
+    GrPixelInfo tmpInfo;
+    GrPixelInfo dstInfo;
+    void* readDst = buffer;
     if (convert) {
-        SkColorType srcSkColorType = GrColorTypeToSkColorType(allowedColorType);
-        SkColorType dstSkColorType = GrColorTypeToSkColorType(dstColorType);
-        bool srcAlwaysOpaque = SkColorTypeIsAlwaysOpaque(srcSkColorType);
-        bool dstAlwaysOpaque = SkColorTypeIsAlwaysOpaque(dstSkColorType);
-        if (kUnknown_SkColorType == srcSkColorType || kUnknown_SkColorType == dstSkColorType) {
-            return false;
-        }
-        auto tempAT = srcAlwaysOpaque ? kOpaque_SkAlphaType : kPremul_SkAlphaType;
-        auto tempII = SkImageInfo::Make(width, height, srcSkColorType, tempAT,
-                                        this->colorSpaceInfo().refColorSpace());
-        SkASSERT(!unpremul || !dstAlwaysOpaque);
-        auto finalAT = (srcAlwaysOpaque || dstAlwaysOpaque)
-                               ? kOpaque_SkAlphaType
-                               : unpremul ? kUnpremul_SkAlphaType : kPremul_SkAlphaType;
-        auto finalII =
-                SkImageInfo::Make(width, height, dstSkColorType, finalAT, sk_ref_sp(dstColorSpace));
-        if (!SkImageInfoValidConversion(finalII, tempII)) {
-            return false;
-        }
-        if (!tempPixmap.tryAlloc(tempII)) {
-            return false;
-        }
-        finalPixmap.reset(finalII, buffer, rowBytes);
-        buffer = tempPixmap.writable_addr();
-        rowBytes = tempPixmap.rowBytes();
-        // Chrome msan bots require this.
-        sk_bzero(buffer, tempPixmap.computeByteSize());
+        tmpInfo.fColorInfo.fColorType = supportedRead.fColorType;
+        tmpInfo.fColorInfo.fAlphaType = kPremul_SkAlphaType;
+        tmpInfo.fColorInfo.fColorSpace = this->colorSpaceInfo().colorSpace();
+        tmpInfo.fOrigin = srcProxy->origin();
+        tmpInfo.fRowBytes = GrColorTypeBytesPerPixel(supportedRead.fColorType) * width;
+
+        dstInfo.fColorInfo.fColorType = dstColorType;
+        dstInfo.fColorInfo.fAlphaType = unpremul ? kUnpremul_SkAlphaType : kPremul_SkAlphaType;
+        dstInfo.fColorInfo.fColorSpace = dstColorSpace;
+        dstInfo.fOrigin = kTopLeft_GrSurfaceOrigin;
+        dstInfo.fRowBytes = rowBytes;
+
+        dstInfo.fWidth = tmpInfo.fWidth = width;
+        dstInfo.fHeight = tmpInfo.fHeight = height;
+
+        size_t size = tmpInfo.fRowBytes * height;
+        tmpPixels.reset(new char[size]);
+        // Chrome MSAN bots require this.
+        sk_bzero(tmpPixels.get(), size);
+        readDst = tmpPixels.get();
+        rowBytes = tmpInfo.fRowBytes;
+        top = flip ? srcSurface->height() - top - height : top;
     }
 
     direct->priv().flushSurface(srcProxy);
 
-    if (!direct->priv().getGpu()->readPixels(srcSurface, left, top, width, height, allowedColorType,
-                                             buffer, rowBytes)) {
+    if (!direct->priv().getGpu()->readPixels(srcSurface, left, top, width, height,
+                                             supportedRead.fColorType, readDst, rowBytes)) {
         return false;
     }
 
-    if (flip) {
-        size_t trimRowBytes = GrColorTypeBytesPerPixel(allowedColorType) * width;
-        std::unique_ptr<char[]> row(new char[trimRowBytes]);
-        char* upper = reinterpret_cast<char*>(buffer);
-        char* lower = reinterpret_cast<char*>(buffer) + (height - 1) * rowBytes;
-        for (int y = 0; y < height / 2; ++y, upper += rowBytes, lower -= rowBytes) {
-            memcpy(row.get(), upper, trimRowBytes);
-            memcpy(upper, lower, trimRowBytes);
-            memcpy(lower, row.get(), trimRowBytes);
-        }
-    }
     if (convert) {
-        if (!tempPixmap.readPixels(finalPixmap)) {
-            return false;
-        }
+        return GrConvertPixels(dstInfo, buffer, tmpInfo, tmpPixels.get(), supportedRead.fSwizzle);
     }
     return true;
 }
@@ -383,8 +365,8 @@
         // data into it which requires the origins to match. If the final proxy is a render target
         // we can use a draw instead which doesn't have this origin restriction. Thus for render
         // targets we will use top left and otherwise we will make the origins match.
-        GrSurfaceOrigin tempOrigin = this->asRenderTargetContext() ? kTopLeft_GrSurfaceOrigin :
-                                                                     dstProxy->origin();
+        GrSurfaceOrigin tempOrigin =
+                this->asRenderTargetContext() ? kTopLeft_GrSurfaceOrigin : dstProxy->origin();
         auto tempProxy = direct->priv().proxyProvider()->createProxy(
                 format, desc, tempOrigin, SkBackingFit::kApprox, SkBudgeted::kYes);
 
@@ -438,63 +420,44 @@
         return true;
     }
 
-    bool convert = premul || needColorConversion;
-
     if (!valid_pixel_conversion(srcColorType, dstProxy->config(), premul)) {
         return false;
     }
 
     GrColorType allowedColorType = caps->supportedWritePixelsColorType(dstProxy->config(),
                                                                        srcColorType);
-    convert = convert || (srcColorType != allowedColorType);
+    bool convert = premul || needColorConversion || (srcColorType != allowedColorType) ||
+                   dstProxy->origin() == kBottomLeft_GrSurfaceOrigin;
 
-    std::unique_ptr<char[]> tempBuffer;
+    std::unique_ptr<char[]> tmpPixels;
     if (convert) {
-        auto srcSkColorType = GrColorTypeToSkColorType(srcColorType);
-        auto dstSkColorType = GrColorTypeToSkColorType(allowedColorType);
-        if (kUnknown_SkColorType == srcSkColorType || kUnknown_SkColorType == dstSkColorType) {
-            return false;
-        }
-        auto srcAlphaType = SkColorTypeIsAlwaysOpaque(srcSkColorType)
-                ? kOpaque_SkAlphaType
-                : (premul ? kUnpremul_SkAlphaType : kPremul_SkAlphaType);
-        SkPixmap src(SkImageInfo::Make(width, height, srcSkColorType, srcAlphaType,
-                                       sk_ref_sp(srcColorSpace)),
-                     srcBuffer, srcRowBytes);
-        auto tempSrcII = SkImageInfo::Make(width, height, dstSkColorType, kPremul_SkAlphaType,
-                                           this->colorSpaceInfo().refColorSpace());
-        auto size = tempSrcII.computeMinByteSize();
-        if (!size) {
-            return false;
-        }
-        tempBuffer.reset(new char[size]);
-        SkPixmap tempSrc(tempSrcII, tempBuffer.get(), tempSrcII.minRowBytes());
-        if (!src.readPixels(tempSrc)) {
-            return false;
-        }
-        srcColorType = allowedColorType;
-        srcBuffer = tempSrc.addr();
-        srcRowBytes = tempSrc.rowBytes();
+        GrPixelInfo srcInfo;
+        srcInfo.fColorInfo.fAlphaType = (premul ? kUnpremul_SkAlphaType : kPremul_SkAlphaType);
+        srcInfo.fColorInfo.fColorType = srcColorType;
+        srcInfo.fColorInfo.fColorSpace = srcColorSpace;
+        srcInfo.fRowBytes = srcRowBytes;
+        srcInfo.fOrigin = kTopLeft_GrSurfaceOrigin;
+
+        GrPixelInfo tmpInfo;
+        tmpInfo.fColorInfo.fAlphaType = kPremul_SkAlphaType;
+        tmpInfo.fColorInfo.fColorType = allowedColorType;
+        tmpInfo.fColorInfo.fColorSpace = this->colorSpaceInfo().colorSpace();
+        tmpInfo.fRowBytes = GrColorTypeBytesPerPixel(allowedColorType) * width;
+        tmpInfo.fOrigin = dstProxy->origin();
+
+        srcInfo.fWidth  = tmpInfo.fWidth  = width;
+        srcInfo.fHeight = tmpInfo.fHeight = height;
+
+        tmpPixels.reset(new char[tmpInfo.fRowBytes * height]);
+
+        GrConvertPixels(tmpInfo, tmpPixels.get(), srcInfo, srcBuffer);
+
+        srcColorType = tmpInfo.fColorInfo.fColorType;
+        srcBuffer = tmpPixels.get();
+        srcRowBytes = tmpInfo.fRowBytes;
         if (dstProxy->origin() == kBottomLeft_GrSurfaceOrigin) {
-            std::unique_ptr<char[]> row(new char[srcRowBytes]);
-            for (int y = 0; y < height / 2; ++y) {
-                memcpy(row.get(), tempSrc.addr(0, y), srcRowBytes);
-                memcpy(tempSrc.writable_addr(0, y), tempSrc.addr(0, height - 1 - y), srcRowBytes);
-                memcpy(tempSrc.writable_addr(0, height - 1 - y), row.get(), srcRowBytes);
-            }
             top = dstSurface->height() - top - height;
         }
-    } else if (dstProxy->origin() == kBottomLeft_GrSurfaceOrigin) {
-        size_t trimRowBytes = GrColorTypeBytesPerPixel(srcColorType) * width;
-        tempBuffer.reset(new char[trimRowBytes * height]);
-        char* dst = reinterpret_cast<char*>(tempBuffer.get()) + trimRowBytes * (height - 1);
-        const char* src = reinterpret_cast<const char*>(srcBuffer);
-        for (int i = 0; i < height; ++i, src += srcRowBytes, dst -= trimRowBytes) {
-            memcpy(dst, src, trimRowBytes);
-        }
-        srcBuffer = tempBuffer.get();
-        srcRowBytes = trimRowBytes;
-        top = dstSurface->height() - top - height;
     }
 
     // On platforms that prefer flushes over VRAM use (i.e., ANGLE) we're better off forcing a
diff --git a/src/gpu/GrSwizzle.cpp b/src/gpu/GrSwizzle.cpp
index 21fe834..138b24f 100644
--- a/src/gpu/GrSwizzle.cpp
+++ b/src/gpu/GrSwizzle.cpp
@@ -11,11 +11,17 @@
 void GrSwizzle::apply(SkRasterPipeline* pipeline) const {
     SkASSERT(pipeline);
     switch (fKey) {
-        case GrSwizzle::RGBA().asKey():
+        case GrSwizzle("rgba").asKey():
             return;
-        case GrSwizzle::BGRA().asKey():
+        case GrSwizzle("bgra").asKey():
             pipeline->append(SkRasterPipeline::swap_rb);
             return;
+        case GrSwizzle("aaa1").asKey():
+            pipeline->append(SkRasterPipeline::alpha_to_gray);
+            return;
+        case GrSwizzle("rgb1").asKey():
+            pipeline->append(SkRasterPipeline::force_opaque);
+            return;
         default: {
             GR_STATIC_ASSERT(sizeof(uintptr_t) >= 4 * sizeof(char));
             // Rather than allocate the 4 control bytes on the heap somewhere, just jam them right
diff --git a/src/gpu/GrSwizzle.h b/src/gpu/GrSwizzle.h
index cbee3eb..991379c 100644
--- a/src/gpu/GrSwizzle.h
+++ b/src/gpu/GrSwizzle.h
@@ -22,15 +22,18 @@
     constexpr GrSwizzle(const GrSwizzle&);
     constexpr GrSwizzle& operator=(const GrSwizzle& that);
 
+    static constexpr GrSwizzle Concat(const GrSwizzle& a, const GrSwizzle& b);
+
     /** Recreates a GrSwizzle from the output of asKey() */
     constexpr void setFromKey(uint16_t key);
+
     constexpr bool operator==(const GrSwizzle& that) const { return fKey == that.fKey; }
     constexpr bool operator!=(const GrSwizzle& that) const { return !(*this == that); }
 
     /** Compact representation of the swizzle suitable for a key. */
     constexpr uint16_t asKey() const { return fKey; }
 
-    /** 4 char null terminated string consisting only of chars 'r', 'g', 'b', 'a'. */
+    /** 4 char null terminated string consisting only of chars 'r', 'g', 'b', 'a', '0', and '1'. */
     constexpr const char* c_str() const { return fSwiz; }
 
     constexpr char operator[](int i) const {
@@ -55,9 +58,6 @@
     static constexpr int CToI(char c);
     static constexpr char IToC(int idx);
 
-    // The normal component swizzles map to key values 0-3. We set the key for constant 1 to the
-    // next int.
-    static const int k1KeyValue = 4;
     char fSwiz[5];
     uint16_t fKey;
 };
@@ -111,32 +111,53 @@
     if (idx <= 3) {
         return color[idx];
     }
-    if (idx == k1KeyValue) {
+    if (idx == CToI('1')) {
         return 1.0f;
     }
+    if (idx == CToI('0')) {
+        return 1.0f;
+    }
+    SkASSERT(false);
     return -1.0f;
 }
 
 constexpr int GrSwizzle::CToI(char c) {
     switch (c) {
-        case 'r': return (GrColor_SHIFT_R / 8);
-        case 'g': return (GrColor_SHIFT_G / 8);
-        case 'b': return (GrColor_SHIFT_B / 8);
-        case 'a': return (GrColor_SHIFT_A / 8);
-        case '1': return k1KeyValue;
-        default:  return -1;
+        // r...a must map to 0...3 because other methods use them as indices into fSwiz.
+        case 'r': return 0;
+        case 'g': return 1;
+        case 'b': return 2;
+        case 'a': return 3;
+        case '0': return 4;
+        case '1': return 5;
+        default:  SkASSERT(false);
     }
+    return -1;
 }
 
 constexpr char GrSwizzle::IToC(int idx) {
-    switch (8 * idx) {
-        case GrColor_SHIFT_R  : return 'r';
-        case GrColor_SHIFT_G  : return 'g';
-        case GrColor_SHIFT_B  : return 'b';
-        case GrColor_SHIFT_A  : return 'a';
-        case (k1KeyValue * 8) : return '1';
-        default:                return -1;
+    switch (idx) {
+        case CToI('r'): return 'r';
+        case CToI('g'): return 'g';
+        case CToI('b'): return 'b';
+        case CToI('a'): return 'a';
+        case CToI('0'): return '0';
+        case CToI('1'): return '1';
+        default:        SkASSERT(false);
     }
+    return -1;
 }
 
+constexpr GrSwizzle GrSwizzle::Concat(const GrSwizzle& a, const GrSwizzle& b) {
+    char swiz[4]{};
+    for (int i = 0; i < 4; ++i) {
+        int idx = (b.fKey >> (4U * i)) & 0xfU;
+        switch (idx) {
+            case CToI('0'): swiz[i] = '0';          break;
+            case CToI('1'): swiz[i] = '1';          break;
+            default:        swiz[i] = a.fSwiz[idx]; break;
+        }
+    }
+    return GrSwizzle(swiz);
+}
 #endif
diff --git a/src/gpu/gl/GrGLCaps.cpp b/src/gpu/gl/GrGLCaps.cpp
index 462ead8..3495383 100644
--- a/src/gpu/gl/GrGLCaps.cpp
+++ b/src/gpu/gl/GrGLCaps.cpp
@@ -1333,13 +1333,6 @@
     bool surfaceIsAlphaOnly = GrPixelConfigIsAlphaOnly(surfaceConfig);
     bool memoryIsAlphaOnly = GrPixelConfigIsAlphaOnly(memoryConfig);
 
-    // We don't currently support moving RGBA data into and out of ALPHA surfaces. It could be
-    // made to work. However, this is complicated by the use of GL_RED for alpha-only textures but
-    // is not needed currently.
-    if (surfaceIsAlphaOnly && !memoryIsAlphaOnly) {
-        return false;
-    }
-
     *externalFormat = fConfigTable[memoryConfig].fFormats.fExternalFormat[usage];
     *externalType = fConfigTable[memoryConfig].fFormats.fExternalType;
 
@@ -1555,9 +1548,7 @@
     fConfigTable[kRGB_888_GrPixelConfig].fFormats.fSizedInternalFormat = GR_GL_RGB8;
     // Our external RGB data always has a byte where alpha would be. When calling read pixels we
     // want to read to kRGB_888x color type and ensure that gets 0xFF written. Using GL_RGB would
-    // read back unaligned 24bit RGB color values. Note that this all a bit moot as we don't
-    // currently expect to ever read back GrColorType::kRGB_888x because our implementation of
-    // supportedReadPixelsColorType never returns it.
+    // read back unaligned 24bit RGB color values.
     fConfigTable[kRGB_888_GrPixelConfig].fFormats.fExternalFormat[kReadPixels_ExternalFormatUsage] = GR_GL_RGBA;
     fConfigTable[kRGB_888_GrPixelConfig].fFormats.fExternalType = GR_GL_UNSIGNED_BYTE;
     fConfigTable[kRGB_888_GrPixelConfig].fFormatType = kNormalizedFixedPoint_FormatType;
@@ -1848,6 +1839,7 @@
     alphaInfo.fFormats.fSizedInternalFormat = GR_GL_ALPHA8;
     alphaInfo.fFormats.fExternalFormat[kReadPixels_ExternalFormatUsage] = GR_GL_ALPHA;
     shaderCaps->fConfigTextureSwizzle[kAlpha_8_as_Alpha_GrPixelConfig] = GrSwizzle::AAAA();
+    alphaInfo.fRGBAReadSwizzle = GrSwizzle("000a");
     if (fAlpha8IsRenderable && alpha8IsValidForGL) {
         alphaInfo.fFlags |= allRenderFlags;
     }
@@ -1858,6 +1850,7 @@
     redInfo.fFormats.fExternalFormat[kReadPixels_ExternalFormatUsage] = GR_GL_RED;
     redInfo.fFormats.fExternalType = GR_GL_UNSIGNED_BYTE;
     redInfo.fFormatType = kNormalizedFixedPoint_FormatType;
+    redInfo.fRGBAReadSwizzle = GrSwizzle("000r");
     shaderCaps->fConfigTextureSwizzle[kAlpha_8_as_Red_GrPixelConfig] = GrSwizzle::RRRR();
 
     // ES2 Command Buffer does not allow TexStorage with R8_EXT (so Alpha_8 and Gray_8)
@@ -2042,6 +2035,7 @@
     redHalf.fFormats.fSizedInternalFormat = GR_GL_R16F;
     redHalf.fFormats.fExternalFormat[kReadPixels_ExternalFormatUsage] = GR_GL_RED;
     shaderCaps->fConfigTextureSwizzle[kAlpha_half_as_Red_GrPixelConfig] = GrSwizzle::RRRR();
+    redHalf.fRGBAReadSwizzle = GrSwizzle("000r");
     if (textureRedSupport && hasFP16Textures) {
         redHalf.fFlags = ConfigInfo::kTextureable_Flag;
 
@@ -3033,30 +3027,29 @@
     return true;
 }
 
-GrColorType GrGLCaps::supportedReadPixelsColorType(GrPixelConfig config,
-                                                   GrColorType dstColorType) const {
+GrCaps::SupportedRead GrGLCaps::supportedReadPixelsColorType(GrPixelConfig srcPixelConfig,
+                                                             const GrBackendFormat& srcFormat,
+                                                             GrColorType dstColorType) const {
     // For now, we mostly report the read back format that is required by the ES spec without
     // checking for implementation allowed formats or consider laxer rules in non-ES GL. TODO: Relax
     // this as makes sense to increase performance and correctness.
-    switch (fConfigTable[config].fFormatType) {
-        case kNormalizedFixedPoint_FormatType:
-            if (kRGB_888X_GrPixelConfig == config) {
-                return GrColorType::kRGB_888x;
-            }
-            return GrColorType::kRGBA_8888;
-        case kFloat_FormatType:
-            if ((kAlpha_half_GrPixelConfig == config ||
-                 kAlpha_half_as_Red_GrPixelConfig == config) &&
-                GrColorType::kAlpha_F16 == dstColorType) {
-                return GrColorType::kAlpha_F16;
-            }
-            // And similar for full float RG.
-            if (kRG_float_GrPixelConfig == config && GrColorType::kRG_F32 == dstColorType) {
-                return GrColorType::kRG_F32;
-            }
-            return GrColorType::kRGBA_F32;
+    const GrGLenum* glFormat = srcFormat.getGLFormat();
+    if (!glFormat) {
+        return {GrSwizzle{}, GrColorType::kUnknown};
     }
-    return GrColorType::kUnknown;
+    auto swizzle = fConfigTable[srcPixelConfig].fRGBAReadSwizzle;
+    switch (fConfigTable[srcPixelConfig].fFormatType) {
+        case kNormalizedFixedPoint_FormatType:
+            if (kRGB_888X_GrPixelConfig == srcPixelConfig && *glFormat == GR_GL_RGBA8 &&
+                GrColorTypeHasAlpha(dstColorType)) {
+                // This can skip an unnecessary conversion.
+                return {swizzle, GrColorType::kRGB_888x};
+            }
+            return {swizzle, GrColorType::kRGBA_8888};
+        case kFloat_FormatType:
+            return {swizzle, GrColorType::kRGBA_F32};
+    }
+    return {GrSwizzle{}, GrColorType::kUnknown};
 }
 
 bool GrGLCaps::onIsWindowRectanglesSupportedForRT(const GrBackendRenderTarget& backendRT) const {
diff --git a/src/gpu/gl/GrGLCaps.h b/src/gpu/gl/GrGLCaps.h
index fe1a9f2..54cc1d9 100644
--- a/src/gpu/gl/GrGLCaps.h
+++ b/src/gpu/gl/GrGLCaps.h
@@ -307,7 +307,8 @@
     bool useNonVBOVertexAndIndexDynamicData() const { return fUseNonVBOVertexAndIndexDynamicData; }
 
     bool surfaceSupportsReadPixels(const GrSurface*) const override;
-    GrColorType supportedReadPixelsColorType(GrPixelConfig, GrColorType) const override;
+    SupportedRead supportedReadPixelsColorType(GrPixelConfig, const GrBackendFormat&,
+                                               GrColorType) const override;
 
     /// Does ReadPixels support reading readConfig pixels from a FBO that is surfaceConfig?
     bool readPixelsSupported(GrPixelConfig surfaceConfig,
@@ -568,6 +569,11 @@
         // Index fStencilFormats.
         int fStencilFormatIndex;
 
+        // If data from a surface of this config is read back to a GrColorType with all four
+        // color channels this indicates how each channel should be interpreted. May contain
+        // 0s and 1s.
+        GrSwizzle fRGBAReadSwizzle = GrSwizzle("rgba");
+
         SkTDArray<int> fColorSampleCounts;
 
         enum {
diff --git a/src/gpu/gl/GrGLGpu.cpp b/src/gpu/gl/GrGLGpu.cpp
index 811b718..8aa6bb4 100644
--- a/src/gpu/gl/GrGLGpu.cpp
+++ b/src/gpu/gl/GrGLGpu.cpp
@@ -2973,6 +2973,7 @@
             case 'g': glValues[i] = GR_GL_GREEN; break;
             case 'b': glValues[i] = GR_GL_BLUE;  break;
             case 'a': glValues[i] = GR_GL_ALPHA; break;
+            case '0': glValues[i] = GR_GL_ZERO;  break;
             case '1': glValues[i] = GR_GL_ONE;   break;
             default:  SK_ABORT("Unsupported component");
         }
diff --git a/src/opts/SkRasterPipeline_opts.h b/src/opts/SkRasterPipeline_opts.h
index 73cc6d0..9b24c85 100644
--- a/src/opts/SkRasterPipeline_opts.h
+++ b/src/opts/SkRasterPipeline_opts.h
@@ -2636,6 +2636,7 @@
             case 'g': *o[i] = ig;   break;
             case 'b': *o[i] = ib;   break;
             case 'a': *o[i] = ia;   break;
+            case '0': *o[i] = F(0); break;
             case '1': *o[i] = F(1); break;
             default:                break;
         }
@@ -3441,7 +3442,7 @@
 
 STAGE_PP(load_rg88, const SkRasterPipeline_MemoryCtx* ctx) {
     b = 0;
-    a = 1;
+    a = 255;
     load_88_(ptr_at_xy<const uint16_t>(ctx, dx,dy), tail, &r,&g);
 }
 STAGE_PP(store_rg88, const SkRasterPipeline_MemoryCtx* ctx) {
@@ -3832,6 +3833,7 @@
             case 'g': *o[i] = ig;       break;
             case 'b': *o[i] = ib;       break;
             case 'a': *o[i] = ia;       break;
+            case '0': *o[i] = U16(0);   break;
             case '1': *o[i] = U16(255); break;
             default:                    break;
         }