Reland "Add target SkColorSpace to SkImage_GpuYUVA."

This is a reland of 2232b9ed3998110ed613ae01f3637d2ce7317f05

Original change's description:
> Add target SkColorSpace to SkImage_GpuYUVA.
>
> Bug: skia:8868
> Change-Id: I91f58bf88aec14c17ea904adca792e20099c36eb
> Reviewed-on: https://skia-review.googlesource.com/c/182816
> Commit-Queue: Jim Van Verth <jvanverth@google.com>
> Reviewed-by: Brian Osman <brianosman@google.com>

Bug: skia:8668
Change-Id: If41ba39e63b348422afaf6910b03405ef1c2be12
Reviewed-on: https://skia-review.googlesource.com/c/183380
Reviewed-by: Jim Van Verth <jvanverth@google.com>
Commit-Queue: Jim Van Verth <jvanverth@google.com>
diff --git a/gm/wacky_yuv_formats.cpp b/gm/wacky_yuv_formats.cpp
index 0e4abc7..50ae02d 100644
--- a/gm/wacky_yuv_formats.cpp
+++ b/gm/wacky_yuv_formats.cpp
@@ -692,7 +692,8 @@
             false,
             GrMipMapped::kNo,
             2*bm.width());
-    } else {
+    }
+    if (!tex.isValid()) {
         tex = gpu->createTestingOnlyBackendTexture(
             bm.getPixels(),
             bm.width(),
@@ -719,14 +720,18 @@
 // YV12
 class WackyYUVFormatsGM : public GM {
 public:
-    WackyYUVFormatsGM() {
+    WackyYUVFormatsGM(bool useTargetColorSpace) : fUseTargetColorSpace(useTargetColorSpace) {
         this->setBGColor(0xFFCCCCCC);
     }
 
 protected:
 
     SkString onShortName() override {
-        return SkString("wacky_yuv_formats");
+        SkString name("wacky_yuv_formats");
+        if (fUseTargetColorSpace) {
+            name += "_cs";
+        }
+        return name;
     }
 
     SkISize onISize() override {
@@ -754,6 +759,10 @@
             SkPath path = create_splat(origin, innerRadius, outerRadius, 1.0f, 7, &circles);
             fOriginalBMs[1] = make_bitmap(path, circles, true);
         }
+
+        if (fUseTargetColorSpace) {
+            fTargetColorSpace = SkColorSpace::MakeSRGB()->makeColorSpin();
+        }
     }
 
     void createImages(GrContext* context) {
@@ -850,8 +859,13 @@
 
                 for (int format = kAYUV_YUVFormat; format <= kLast_YUVFormat; ++format) {
                     draw_row_label(canvas, y, format);
-                    canvas->drawImage(fImages[opaque][cs][format], x, y);
-
+                    if (fUseTargetColorSpace && fImages[opaque][cs][format]) {
+                        sk_sp<SkImage> csImage =
+                            fImages[opaque][cs][format]->makeColorSpace(fTargetColorSpace);
+                        canvas->drawImage(csImage, x, y);
+                    } else {
+                        canvas->drawImage(fImages[opaque][cs][format], x, y);
+                    }
                     y += kTileWidthHeight + kPad;
                 }
 
@@ -863,11 +877,14 @@
 private:
     SkBitmap       fOriginalBMs[2];
     sk_sp<SkImage> fImages[2][kLastEnum_SkYUVColorSpace+1][kLast_YUVFormat+1];
+    bool           fUseTargetColorSpace;
+    sk_sp<SkColorSpace> fTargetColorSpace;
 
     typedef GM INHERITED;
 };
 
 //////////////////////////////////////////////////////////////////////////////
 
-DEF_GM(return new WackyYUVFormatsGM;)
+DEF_GM(return new WackyYUVFormatsGM(false);)
+DEF_GM(return new WackyYUVFormatsGM(true);)
 }
diff --git a/src/gpu/GrImageTextureMaker.cpp b/src/gpu/GrImageTextureMaker.cpp
index 567e91e..fa87037 100644
--- a/src/gpu/GrImageTextureMaker.cpp
+++ b/src/gpu/GrImageTextureMaker.cpp
@@ -81,6 +81,9 @@
 SkColorSpace* GrYUVAImageTextureMaker::colorSpace() const {
     return fImage->colorSpace();
 }
+SkColorSpace* GrYUVAImageTextureMaker::targetColorSpace() const {
+    return fImage->targetColorSpace();
+}
 
 std::unique_ptr<GrFragmentProcessor> GrYUVAImageTextureMaker::createFragmentProcessor(
     const SkMatrix& textureMatrix,
diff --git a/src/gpu/GrImageTextureMaker.h b/src/gpu/GrImageTextureMaker.h
index 544705f..8024579 100644
--- a/src/gpu/GrImageTextureMaker.h
+++ b/src/gpu/GrImageTextureMaker.h
@@ -65,6 +65,7 @@
 
     SkAlphaType alphaType() const override;
     SkColorSpace* colorSpace() const override;
+    SkColorSpace* targetColorSpace() const override;
 
 private:
     const SkImage_GpuYUVA*  fImage;
diff --git a/src/gpu/GrTextureProducer.h b/src/gpu/GrTextureProducer.h
index 1994f8b..68c1b47 100644
--- a/src/gpu/GrTextureProducer.h
+++ b/src/gpu/GrTextureProducer.h
@@ -109,6 +109,7 @@
     bool isAlphaOnly() const { return fIsAlphaOnly; }
     virtual SkAlphaType alphaType() const = 0;
     virtual SkColorSpace* colorSpace() const = 0;
+    virtual SkColorSpace* targetColorSpace() const { return nullptr; }
 
 protected:
     friend class GrTextureProducer_TestAccess;
diff --git a/src/gpu/SkGpuDevice_drawTexture.cpp b/src/gpu/SkGpuDevice_drawTexture.cpp
index 3867465..a891c03 100644
--- a/src/gpu/SkGpuDevice_drawTexture.cpp
+++ b/src/gpu/SkGpuDevice_drawTexture.cpp
@@ -289,8 +289,11 @@
     }
     auto fp = producer->createFragmentProcessor(*textureMatrix, clippedSrcRect, constraintMode,
                                                 coordsAllInsideSrcRect, filterMode);
+    SkColorSpace* rtColorSpace = fRenderTargetContext->colorSpaceInfo().colorSpace();
+    SkColorSpace* targetColorSpace = producer->targetColorSpace();
+    SkColorSpace* dstColorSpace = SkToBool(rtColorSpace) ? rtColorSpace : targetColorSpace;
     fp = GrColorSpaceXformEffect::Make(std::move(fp), producer->colorSpace(), producer->alphaType(),
-                                       fRenderTargetContext->colorSpaceInfo().colorSpace());
+                                       dstColorSpace);
     if (!fp) {
         return;
     }
diff --git a/src/image/SkImage_Gpu.cpp b/src/image/SkImage_Gpu.cpp
index 932b521..2b28f1a 100644
--- a/src/image/SkImage_Gpu.cpp
+++ b/src/image/SkImage_Gpu.cpp
@@ -60,6 +60,42 @@
     return SkImageInfo::Make(fProxy->width(), fProxy->height(), colorType, fAlphaType, fColorSpace);
 }
 
+sk_sp<SkImage> SkImage_Gpu::onMakeColorSpace(sk_sp<SkColorSpace> target) const {
+    auto xform = GrColorSpaceXformEffect::Make(fColorSpace.get(), fAlphaType,
+                                               target.get(), fAlphaType);
+    SkASSERT(xform);
+
+    sk_sp<GrTextureProxy> proxy = this->asTextureProxyRef();
+
+    GrBackendFormat format = proxy->backendFormat().makeTexture2D();
+    if (!format.isValid()) {
+        return nullptr;
+    }
+
+    sk_sp<GrRenderTargetContext> renderTargetContext(
+        fContext->contextPriv().makeDeferredRenderTargetContextWithFallback(
+            format, SkBackingFit::kExact, this->width(), this->height(),
+            proxy->config(), nullptr));
+    if (!renderTargetContext) {
+        return nullptr;
+    }
+
+    GrPaint paint;
+    paint.setPorterDuffXPFactory(SkBlendMode::kSrc);
+    paint.addColorTextureProcessor(std::move(proxy), SkMatrix::I());
+    paint.addColorFragmentProcessor(std::move(xform));
+
+    renderTargetContext->drawRect(GrNoClip(), std::move(paint), GrAA::kNo, SkMatrix::I(),
+                                  SkRect::MakeIWH(this->width(), this->height()));
+    if (!renderTargetContext->asTextureProxy()) {
+        return nullptr;
+    }
+
+    // MDB: this call is okay bc we know 'renderTargetContext' was exact
+    return sk_make_sp<SkImage_Gpu>(fContext, kNeedNewImageUniqueID, fAlphaType,
+                                   renderTargetContext->asTextureProxyRef(), std::move(target));
+}
+
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
 static sk_sp<SkImage> new_wrapped_texture_common(GrContext* ctx,
diff --git a/src/image/SkImage_Gpu.h b/src/image/SkImage_Gpu.h
index 1f82539..42ba0a5 100644
--- a/src/image/SkImage_Gpu.h
+++ b/src/image/SkImage_Gpu.h
@@ -37,6 +37,8 @@
 
     virtual bool onIsTextureBacked() const override { return SkToBool(fProxy.get()); }
 
+    sk_sp<SkImage> onMakeColorSpace(sk_sp<SkColorSpace>) const final;
+
     /**
         Create a new SkImage that is very similar to an SkImage created by MakeFromTexture. The main
         difference is that the client doesn't have the backend texture on the gpu yet but they know
diff --git a/src/image/SkImage_GpuBase.cpp b/src/image/SkImage_GpuBase.cpp
index 73aecf4..047b1a2 100644
--- a/src/image/SkImage_GpuBase.cpp
+++ b/src/image/SkImage_GpuBase.cpp
@@ -256,42 +256,6 @@
     return proxy->peekTexture();
 }
 
-sk_sp<SkImage> SkImage_GpuBase::onMakeColorSpace(sk_sp<SkColorSpace> target) const {
-    auto xform = GrColorSpaceXformEffect::Make(fColorSpace.get(), fAlphaType,
-                                               target.get(),      fAlphaType);
-    SkASSERT(xform);
-
-    sk_sp<GrTextureProxy> proxy = this->asTextureProxyRef();
-
-    GrBackendFormat format = proxy->backendFormat().makeTexture2D();
-    if (!format.isValid()) {
-        return nullptr;
-    }
-
-    sk_sp<GrRenderTargetContext> renderTargetContext(
-        fContext->contextPriv().makeDeferredRenderTargetContextWithFallback(
-            format, SkBackingFit::kExact, this->width(), this->height(),
-            proxy->config(), nullptr));
-    if (!renderTargetContext) {
-        return nullptr;
-    }
-
-    GrPaint paint;
-    paint.setPorterDuffXPFactory(SkBlendMode::kSrc);
-    paint.addColorTextureProcessor(std::move(proxy), SkMatrix::I());
-    paint.addColorFragmentProcessor(std::move(xform));
-
-    renderTargetContext->drawRect(GrNoClip(), std::move(paint), GrAA::kNo, SkMatrix::I(),
-                                  SkRect::MakeIWH(this->width(), this->height()));
-    if (!renderTargetContext->asTextureProxy()) {
-        return nullptr;
-    }
-
-    // MDB: this call is okay bc we know 'renderTargetContext' was exact
-    return sk_make_sp<SkImage_Gpu>(fContext, kNeedNewImageUniqueID, fAlphaType,
-                                   renderTargetContext->asTextureProxyRef(), std::move(target));
-}
-
 bool SkImage_GpuBase::onIsValid(GrContext* context) const {
     // The base class has already checked that context isn't abandoned (if it's not nullptr)
     if (fContext->abandoned()) {
diff --git a/src/image/SkImage_GpuBase.h b/src/image/SkImage_GpuBase.h
index 58b09c6..37d3b22 100644
--- a/src/image/SkImage_GpuBase.h
+++ b/src/image/SkImage_GpuBase.h
@@ -50,8 +50,6 @@
 
     GrTexture* onGetTexture() const final;
 
-    sk_sp<SkImage> onMakeColorSpace(sk_sp<SkColorSpace>) const final;
-
     bool onIsValid(GrContext*) const final;
 
 #if GR_TEST_UTILS
diff --git a/src/image/SkImage_GpuYUVA.cpp b/src/image/SkImage_GpuYUVA.cpp
index 340f1ef..55411cc 100644
--- a/src/image/SkImage_GpuYUVA.cpp
+++ b/src/image/SkImage_GpuYUVA.cpp
@@ -48,6 +48,28 @@
     memcpy(fYUVAIndices, yuvaIndices, 4*sizeof(SkYUVAIndex));
 }
 
+// For onMakeColorSpace()
+SkImage_GpuYUVA::SkImage_GpuYUVA(const SkImage_GpuYUVA* image, sk_sp<SkColorSpace> targetCS)
+    : INHERITED(image->fContext, image->width(), image->height(), kNeedNewImageUniqueID,
+                // If an alpha channel is present we always switch to kPremul. This is because,
+                // although the planar data is always un-premul, the final interleaved RGB image
+                // is/would-be premul.
+                GetAlphaTypeFromYUVAIndices(image->fYUVAIndices), image->fColorSpace)
+    , fNumProxies(image->fNumProxies)
+    , fYUVColorSpace(image->fYUVColorSpace)
+    , fOrigin(image->fOrigin)
+    , fTargetColorSpace(targetCS) {
+        // The caller should have done this work, just verifying
+    SkDEBUGCODE(int textureCount;)
+        SkASSERT(SkYUVAIndex::AreValidIndices(image->fYUVAIndices, &textureCount));
+    SkASSERT(textureCount == fNumProxies);
+
+    for (int i = 0; i < fNumProxies; ++i) {
+        fProxies[i] = image->fProxies[i];  // we ref in this case, not move
+    }
+    memcpy(fYUVAIndices, image->fYUVAIndices, 4 * sizeof(SkYUVAIndex));
+}
+
 SkImage_GpuYUVA::~SkImage_GpuYUVA() {}
 
 SkImageInfo SkImage_GpuYUVA::onImageInfo() const {
@@ -121,6 +143,22 @@
 
 //////////////////////////////////////////////////////////////////////////////////////////////////
 
+sk_sp<SkImage> SkImage_GpuYUVA::onMakeColorSpace(sk_sp<SkColorSpace> target) const {
+    // we may need a mutex here but for now we expect usage to be in a single thread
+    if (fOnMakeColorSpaceTarget &&
+        SkColorSpace::Equals(target.get(), fOnMakeColorSpaceTarget.get())) {
+        return fOnMakeColorSpaceResult;
+    }
+    sk_sp<SkImage> result = sk_sp<SkImage>(new SkImage_GpuYUVA(this, target));
+    if (result) {
+        fOnMakeColorSpaceTarget = target;
+        fOnMakeColorSpaceResult = result;
+    }
+    return result;
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////////////
+
 sk_sp<SkImage> SkImage::MakeFromYUVATextures(GrContext* ctx,
                                              SkYUVColorSpace colorSpace,
                                              const GrBackendTexture yuvaTextures[],
diff --git a/src/image/SkImage_GpuYUVA.h b/src/image/SkImage_GpuYUVA.h
index 1a6ab93..c8b9616 100644
--- a/src/image/SkImage_GpuYUVA.h
+++ b/src/image/SkImage_GpuYUVA.h
@@ -36,6 +36,8 @@
 
     virtual bool onIsTextureBacked() const override { return SkToBool(fProxies[0].get()); }
 
+    sk_sp<SkImage> onMakeColorSpace(sk_sp<SkColorSpace>) const final;
+
     virtual bool isYUVA() const override { return true; }
     virtual bool asYUVATextureProxiesRef(sk_sp<GrTextureProxy> proxies[4],
                                          SkYUVAIndex yuvaIndices[4],
@@ -53,6 +55,8 @@
     // Returns a ref-ed texture proxy with miplevels
     sk_sp<GrTextureProxy> asMippedTextureProxyRef() const;
 
+    SkColorSpace* targetColorSpace() const { return fTargetColorSpace.get(); }
+
     /**
         Create a new SkImage_GpuYUVA that's very similar to SkImage created by MakeFromYUVATextures.
         The main difference is that the client doesn't have the backend textures on the gpu yet but
@@ -109,6 +113,8 @@
                                                  PromiseImageTextureContext textureContexts[]);
 
 private:
+    SkImage_GpuYUVA(const SkImage_GpuYUVA* image, sk_sp<SkColorSpace>);
+
     // This array will usually only be sparsely populated.
     // The actual non-null fields are dictated by the 'fYUVAIndices' indices
     mutable sk_sp<GrTextureProxy>    fProxies[4];
@@ -116,6 +122,12 @@
     SkYUVAIndex                      fYUVAIndices[4];
     const SkYUVColorSpace            fYUVColorSpace;
     GrSurfaceOrigin                  fOrigin;
+    const sk_sp<SkColorSpace>        fTargetColorSpace;
+
+    // Repeated calls to onMakeColorSpace will result in a proliferation of unique IDs and
+    // SkImage_GpuYUVA instances. Cache the result of the last successful onMakeColorSpace call.
+    mutable sk_sp<SkColorSpace>      fOnMakeColorSpaceTarget;
+    mutable sk_sp<SkImage>           fOnMakeColorSpaceResult;
 
     // This is only allocated when the image needs to be flattened rather than
     // using the separate YUVA planes. From thence forth we will only use the