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>
diff --git a/gm/wacky_yuv_formats.cpp b/gm/wacky_yuv_formats.cpp
index 0e4abc7..155ec6d 100644
--- a/gm/wacky_yuv_formats.cpp
+++ b/gm/wacky_yuv_formats.cpp
@@ -719,14 +719,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 +758,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 +858,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) {
+                        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 +876,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 5de8aa9..9033780 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 5be97c8..59fce5e 100644
--- a/src/image/SkImage_GpuBase.h
+++ b/src/image/SkImage_GpuBase.h
@@ -49,8 +49,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