Create an SkColorSpaceXform image generator

This should be immediately useful in the Skia-Android
rendering pipeline.

Possible future uses include creating a "renderable"
SkImage from a bitmap with a funny color space.

Inspired by:
https://skia-review.googlesource.com/c/13981/

Bug: b/62347704
Change-Id: I388c7af1fc43834b8ad22022d0caf3ac90b734c8
Reviewed-on: https://skia-review.googlesource.com/18598
Reviewed-by: Mike Reed <reed@google.com>
Commit-Queue: Matt Sarett <msarett@google.com>
diff --git a/gm/xform_image_gen.cpp b/gm/xform_image_gen.cpp
new file mode 100644
index 0000000..69a1ce1
--- /dev/null
+++ b/gm/xform_image_gen.cpp
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2017 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "gm.h"
+#include "SkColorSpaceXformImageGenerator.h"
+
+class ColorXformImageGenGM : public skiagm::GM {
+public:
+    ColorXformImageGenGM() {}
+
+protected:
+
+    SkString onShortName() override {
+        return SkString("color_xform_image_gen");
+    }
+
+    SkISize onISize() override {
+        return SkISize::Make(100, 100);
+    }
+
+    void onDraw(SkCanvas* canvas) override {
+        SkBitmap bitmap;
+        SkImageInfo info =
+                SkImageInfo::MakeN32(100, 100, kOpaque_SkAlphaType, SkColorSpace::MakeSRGB());
+        bitmap.allocPixels(info);
+        bitmap.eraseColor(SK_ColorRED);
+        bitmap.eraseArea(SkIRect::MakeWH(25, 25), SK_ColorBLUE); // We should not see any blue.
+
+        std::unique_ptr<SkImageGenerator> gen = SkColorSpaceXformImageGenerator::Make(
+                bitmap,
+                SkColorSpace::MakeRGB(SkColorSpace::kSRGB_RenderTargetGamma,
+                                      SkColorSpace::kRec2020_Gamut),
+                kNever_SkCopyPixelsMode);
+
+        SkIRect subset = SkIRect::MakeXYWH(25, 25, 50, 50);
+        sk_sp<SkImage> image = SkImage::MakeFromGenerator(std::move(gen), &subset);
+        canvas->drawImage(image, 25, 25);
+    }
+
+private:
+    typedef GM INHERITED;
+};
+
+DEF_GM( return new ColorXformImageGenGM(); )
diff --git a/gn/core.gni b/gn/core.gni
index 21f3c20..0ffff84 100644
--- a/gn/core.gni
+++ b/gn/core.gni
@@ -84,6 +84,8 @@
   "$_src/core/SkColorSpaceXformCanvas.cpp",
   "$_src/core/SkColorSpaceXformer.cpp",
   "$_src/core/SkColorSpaceXformer.h",
+  "$_src/core/SkColorSpaceXformImageGenerator.cpp",
+  "$_src/core/SkColorSpaceXformImageGenerator.h",
   "$_src/core/SkColorSpaceXform_A2B.cpp",
   "$_src/core/SkColorSpaceXform_A2B.h",
   "$_src/core/SkColorTable.cpp",
diff --git a/gn/gm.gni b/gn/gm.gni
index cd7985a..c28504e 100644
--- a/gn/gm.gni
+++ b/gn/gm.gni
@@ -319,5 +319,6 @@
   "$_gm/xfermodes.cpp",
   "$_gm/xfermodes2.cpp",
   "$_gm/xfermodes3.cpp",
+  "$_gm/xform_image_gen.cpp",
   "$_gm/yuvtorgbeffect.cpp",
 ]
diff --git a/include/core/SkBitmap.h b/include/core/SkBitmap.h
index 764139b..8b8942f 100644
--- a/include/core/SkBitmap.h
+++ b/include/core/SkBitmap.h
@@ -571,7 +571,12 @@
      *  - If the src pixels are not available.
      */
     bool readPixels(const SkImageInfo& dstInfo, void* dstPixels, size_t dstRowBytes,
-                    int srcX, int srcY) const;
+                    int srcX, int srcY, SkTransferFunctionBehavior behavior) const;
+    bool readPixels(const SkImageInfo& dstInfo, void* dstPixels, size_t dstRowBytes,
+                    int srcX, int srcY) const {
+        return this->readPixels(dstInfo, dstPixels, dstRowBytes, srcX, srcY,
+                SkTransferFunctionBehavior::kRespect);
+    }
     bool readPixels(const SkPixmap& dst, int srcX, int srcY) const;
     bool readPixels(const SkPixmap& dst) const {
         return this->readPixels(dst, 0, 0);
@@ -589,6 +594,7 @@
     bool writePixels(const SkPixmap& src) {
         return this->writePixels(src, 0, 0);
     }
+    bool writePixels(const SkPixmap& src, int x, int y, SkTransferFunctionBehavior behavior);
 
 #ifdef SK_BUILD_FOR_ANDROID
     bool hasHardwareMipMap() const {
@@ -684,9 +690,6 @@
     uint32_t            fRowBytes;
     uint8_t             fFlags;
 
-    friend class SkImage_Raster;
-    bool writePixels(const SkPixmap& src, int x, int y, SkTransferFunctionBehavior behavior);
-
     /*  Unreference any pixelrefs or colortables
     */
     void freePixels();
diff --git a/include/core/SkPixmap.h b/include/core/SkPixmap.h
index af8618b..1e79bba 100644
--- a/include/core/SkPixmap.h
+++ b/include/core/SkPixmap.h
@@ -183,10 +183,15 @@
     // copy methods
 
     bool readPixels(const SkImageInfo& dstInfo, void* dstPixels, size_t dstRowBytes,
-                    int srcX, int srcY) const;
+                    int srcX, int srcY, SkTransferFunctionBehavior behavior) const;
     bool readPixels(const SkImageInfo& dstInfo, void* dstPixels, size_t dstRowBytes) const {
         return this->readPixels(dstInfo, dstPixels, dstRowBytes, 0, 0);
     }
+    bool readPixels(const SkImageInfo& dstInfo, void* dstPixels, size_t dstRowBytes, int srcX,
+                    int srcY) const {
+        return this->readPixels(dstInfo, dstPixels, dstRowBytes, srcX, srcY,
+                                SkTransferFunctionBehavior::kRespect);
+    }
     bool readPixels(const SkPixmap& dst, int srcX, int srcY) const {
         return this->readPixels(dst.info(), dst.writable_addr(), dst.rowBytes(), srcX, srcY);
     }
diff --git a/src/core/SkBitmap.cpp b/src/core/SkBitmap.cpp
index 72cac7c..e88fa5e 100644
--- a/src/core/SkBitmap.cpp
+++ b/src/core/SkBitmap.cpp
@@ -512,12 +512,12 @@
 ///////////////////////////////////////////////////////////////////////////////
 
 bool SkBitmap::readPixels(const SkImageInfo& requestedDstInfo, void* dstPixels, size_t dstRB,
-                          int x, int y) const {
+                          int x, int y, SkTransferFunctionBehavior behavior) const {
     SkPixmap src;
     if (!this->peekPixels(&src)) {
         return false;
     }
-    return src.readPixels(requestedDstInfo, dstPixels, dstRB, x, y);
+    return src.readPixels(requestedDstInfo, dstPixels, dstRB, x, y, behavior);
 }
 
 bool SkBitmap::readPixels(const SkPixmap& dst, int srcX, int srcY) const {
diff --git a/src/core/SkColorSpaceXformImageGenerator.cpp b/src/core/SkColorSpaceXformImageGenerator.cpp
new file mode 100644
index 0000000..d9bec78
--- /dev/null
+++ b/src/core/SkColorSpaceXformImageGenerator.cpp
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2017 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkColorSpaceXformImageGenerator.h"
+
+std::unique_ptr<SkImageGenerator> SkColorSpaceXformImageGenerator::Make(
+        const SkBitmap& src, sk_sp<SkColorSpace> dst, SkCopyPixelsMode mode) {
+    if (!dst) {
+        return nullptr;
+    }
+
+    const SkBitmap* srcPtr = &src;
+    SkBitmap copy;
+    if (kAlways_SkCopyPixelsMode == mode ||
+            (kNever_SkCopyPixelsMode != mode && !src.isImmutable())) {
+        if (!copy.tryAllocPixels(src.info())) {
+            return nullptr;
+        }
+
+        SkAssertResult(src.readPixels(copy.info(), copy.getPixels(), copy.rowBytes(), 0, 0));
+        copy.setImmutable();
+        srcPtr = &copy;
+    }
+
+
+    return std::unique_ptr<SkImageGenerator>(
+            new SkColorSpaceXformImageGenerator(*srcPtr, std::move(dst)));
+}
+
+SkColorSpaceXformImageGenerator::SkColorSpaceXformImageGenerator(const SkBitmap& src,
+                                                                 sk_sp<SkColorSpace> dst)
+    : INHERITED(src.info().makeColorSpace(dst), kNeedNewImageUniqueID)
+    , fSrc(src)
+    , fDst(dst)
+{}
+
+bool SkColorSpaceXformImageGenerator::onGetPixels(const SkImageInfo& info, void* pixels,
+                                                  size_t rowBytes, const Options& opts) {
+    SkImageInfo dstInfo = info;
+    if (!info.colorSpace()) {
+        dstInfo = dstInfo.makeColorSpace(fDst);
+    }
+    return fSrc.readPixels(dstInfo, pixels, rowBytes, 0, 0, opts.fBehavior);
+}
+
+#if SK_SUPPORT_GPU
+
+#include "GrClip.h"
+#include "GrContext.h"
+#include "GrPaint.h"
+#include "GrRenderTargetContext.h"
+#include "GrTextureProxy.h"
+#include "SkGr.h"
+#include "effects/GrNonlinearColorSpaceXformEffect.h"
+
+sk_sp<GrTextureProxy> SkColorSpaceXformImageGenerator::onGenerateTexture(GrContext* ctx,
+                                                                         const SkImageInfo& info,
+                                                                         const SkIPoint& origin) {
+    // FIXME:
+    // This always operates as if SkTranferFunctionBehavior is kIgnore.  Should we add
+    // options so that caller can also request kRespect?
+
+    SkASSERT(ctx);
+
+    sk_sp<GrTextureProxy> proxy =
+            GrUploadBitmapToTextureProxy(ctx->resourceProvider(), fSrc, nullptr);
+
+    sk_sp<SkColorSpace> srcSpace =
+            fSrc.colorSpace() ? sk_ref_sp(fSrc.colorSpace()) : SkColorSpace::MakeSRGB();
+    auto xform = GrNonlinearColorSpaceXformEffect::Make(srcSpace.get(), fDst.get());
+    if (!xform) {
+        return nullptr;
+    }
+
+    sk_sp<GrRenderTargetContext> renderTargetContext = ctx->makeDeferredRenderTargetContext(
+            SkBackingFit::kExact, fSrc.width(), fSrc.height(), kRGBA_8888_GrPixelConfig, nullptr);
+    if (!renderTargetContext) {
+        return nullptr;
+    }
+
+    GrPaint paint;
+    paint.setPorterDuffXPFactory(SkBlendMode::kSrc);
+    paint.addColorTextureProcessor(ctx->resourceProvider(), proxy, nullptr,
+            SkMatrix::MakeTrans(origin.fX, origin.fY));
+    paint.addColorFragmentProcessor(std::move(xform));
+
+    const SkRect rect = SkRect::MakeWH(info.width(), info.height());
+    renderTargetContext->drawRect(GrNoClip(), std::move(paint), GrAA::kNo, SkMatrix::I(), rect);
+    return sk_ref_sp(renderTargetContext->asTextureProxy());
+}
+
+#endif
diff --git a/src/core/SkColorSpaceXformImageGenerator.h b/src/core/SkColorSpaceXformImageGenerator.h
new file mode 100644
index 0000000..29bf85b
--- /dev/null
+++ b/src/core/SkColorSpaceXformImageGenerator.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2017 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SkColorSpaceXformImageGenerator_DEFINED
+#define SkColorSpaceXformImageGenerator_DEFINED
+
+#include "SkImageGenerator.h"
+#include "SkImagePriv.h"
+
+class SkColorSpaceXformImageGenerator : public SkImageGenerator {
+public:
+
+    static std::unique_ptr<SkImageGenerator> Make(
+            const SkBitmap& src, sk_sp<SkColorSpace> dst, SkCopyPixelsMode);
+
+protected:
+    bool onGetPixels(const SkImageInfo& info, void* pixels, size_t rowBytes,
+                     const Options& opts) override;
+
+#if SK_SUPPORT_GPU
+    sk_sp<GrTextureProxy> onGenerateTexture(GrContext*, const SkImageInfo&,
+                                            const SkIPoint&) override;
+#endif
+
+private:
+    SkBitmap            fSrc;
+    sk_sp<SkColorSpace> fDst;
+
+    SkColorSpaceXformImageGenerator(const SkBitmap& src, sk_sp<SkColorSpace> dst);
+
+    friend class SkImageGenerator;
+
+    typedef SkImageGenerator INHERITED;
+};
+
+#endif // SkColorSpaceXformImageGenerator_DEFINED
diff --git a/src/core/SkPixmap.cpp b/src/core/SkPixmap.cpp
index 02090b7..6425b29 100644
--- a/src/core/SkPixmap.cpp
+++ b/src/core/SkPixmap.cpp
@@ -74,8 +74,8 @@
     return true;
 }
 
-bool SkPixmap::readPixels(const SkImageInfo& dstInfo, void* dstPixels, size_t dstRB, int x, int y)
-const {
+bool SkPixmap::readPixels(const SkImageInfo& dstInfo, void* dstPixels, size_t dstRB, int x, int y,
+                          SkTransferFunctionBehavior behavior) const {
     if (!SkImageInfoValidConversion(dstInfo, fInfo)) {
         return false;
     }
@@ -88,7 +88,7 @@
     const void* srcPixels = this->addr(rec.fX, rec.fY);
     const SkImageInfo srcInfo = fInfo.makeWH(rec.fInfo.width(), rec.fInfo.height());
     SkConvertPixels(rec.fInfo, rec.fPixels, rec.fRowBytes, srcInfo, srcPixels, this->rowBytes(),
-                    this->ctable(), SkTransferFunctionBehavior::kRespect);
+                    this->ctable(), behavior);
     return true;
 }