Merge "Xform bitmaps to sRGB on SW and PDF canvases" into oc-dev am: c29dffed16
am: fd5d5425ee

Change-Id: If960ccd900ebc2b841c2f13ca4f25820a48a950f
diff --git a/core/jni/android/graphics/Picture.cpp b/core/jni/android/graphics/Picture.cpp
index 7b381b4..bfb2511 100644
--- a/core/jni/android/graphics/Picture.cpp
+++ b/core/jni/android/graphics/Picture.cpp
@@ -44,7 +44,7 @@
     mWidth = width;
     mHeight = height;
     SkCanvas* canvas = mRecorder->beginRecording(SkIntToScalar(width), SkIntToScalar(height));
-    return Canvas::create_canvas(canvas);
+    return Canvas::create_canvas(canvas, Canvas::XformToSRGB::kDefer);
 }
 
 void Picture::endRecording() {
diff --git a/core/jni/android/graphics/pdf/PdfDocument.cpp b/core/jni/android/graphics/pdf/PdfDocument.cpp
index d233f7b..abc3599 100644
--- a/core/jni/android/graphics/pdf/PdfDocument.cpp
+++ b/core/jni/android/graphics/pdf/PdfDocument.cpp
@@ -21,6 +21,7 @@
 
 #include "CreateJavaOutputStreamAdaptor.h"
 
+#include "SkColorSpaceXformCanvas.h"
 #include "SkDocument.h"
 #include "SkPicture.h"
 #include "SkPictureRecorder.h"
@@ -94,8 +95,10 @@
 
             SkCanvas* canvas = document->beginPage(page->mWidth, page->mHeight,
                     &(page->mContentRect));
+            std::unique_ptr<SkCanvas> toSRGBCanvas =
+                    SkCreateColorSpaceXformCanvas(canvas, SkColorSpace::MakeSRGB());
 
-            canvas->drawPicture(page->mPicture);
+            toSRGBCanvas->drawPicture(page->mPicture);
 
             document->endPage();
         }
@@ -128,7 +131,7 @@
     PdfDocument* document = reinterpret_cast<PdfDocument*>(documentPtr);
     SkCanvas* canvas = document->startPage(pageWidth, pageHeight,
             contentLeft, contentTop, contentRight, contentBottom);
-    return reinterpret_cast<jlong>(Canvas::create_canvas(canvas));
+    return reinterpret_cast<jlong>(Canvas::create_canvas(canvas, Canvas::XformToSRGB::kDefer));
 }
 
 static void nativeFinishPage(JNIEnv* env, jobject thiz, jlong documentPtr) {
diff --git a/libs/hwui/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp
index 434e01d..6f36689 100644
--- a/libs/hwui/SkiaCanvas.cpp
+++ b/libs/hwui/SkiaCanvas.cpp
@@ -23,6 +23,7 @@
 #include "hwui/MinikinUtils.h"
 #include "pipeline/skia/AnimatedDrawables.h"
 
+#include <SkColorSpaceXformCanvas.h>
 #include <SkDrawable.h>
 #include <SkDeque.h>
 #include <SkDrawFilter.h>
@@ -44,18 +45,22 @@
     return new SkiaCanvas(bitmap);
 }
 
-Canvas* Canvas::create_canvas(SkCanvas* skiaCanvas) {
-    return new SkiaCanvas(skiaCanvas);
+Canvas* Canvas::create_canvas(SkCanvas* skiaCanvas, XformToSRGB xformToSRGB) {
+    return new SkiaCanvas(skiaCanvas, xformToSRGB);
 }
 
 SkiaCanvas::SkiaCanvas() {}
 
-SkiaCanvas::SkiaCanvas(SkCanvas* canvas)
-    : mCanvas(canvas) {}
+SkiaCanvas::SkiaCanvas(SkCanvas* canvas, XformToSRGB xformToSRGB)
+    : mCanvas(canvas)
+{
+    LOG_ALWAYS_FATAL_IF(XformToSRGB::kImmediate == xformToSRGB);
+}
 
 SkiaCanvas::SkiaCanvas(const SkBitmap& bitmap) {
     mCanvasOwned = std::unique_ptr<SkCanvas>(new SkCanvas(bitmap));
-    mCanvas = mCanvasOwned.get();
+    mCanvasWrapper = SkCreateColorSpaceXformCanvas(mCanvasOwned.get(), SkColorSpace::MakeSRGB());
+    mCanvas = mCanvasWrapper.get();
 }
 
 SkiaCanvas::~SkiaCanvas() {}
@@ -92,19 +97,22 @@
 };
 
 void SkiaCanvas::setBitmap(const SkBitmap& bitmap) {
-    SkCanvas* newCanvas = new SkCanvas(bitmap);
+    std::unique_ptr<SkCanvas> newCanvas = std::unique_ptr<SkCanvas>(new SkCanvas(bitmap));
+    std::unique_ptr<SkCanvas> newCanvasWrapper =
+            SkCreateColorSpaceXformCanvas(newCanvas.get(), SkColorSpace::MakeSRGB());
 
     if (!bitmap.isNull()) {
         // Copy the canvas matrix & clip state.
-        newCanvas->setMatrix(mCanvas->getTotalMatrix());
+        newCanvasWrapper->setMatrix(mCanvas->getTotalMatrix());
 
-        ClipCopier copier(newCanvas);
+        ClipCopier copier(newCanvasWrapper.get());
         mCanvas->replayClips(&copier);
     }
 
     // deletes the previously owned canvas (if any)
-    mCanvasOwned = std::unique_ptr<SkCanvas>(newCanvas);
-    mCanvas = newCanvas;
+    mCanvasOwned = std::move(newCanvas);
+    mCanvasWrapper = std::move(newCanvasWrapper);
+    mCanvas = mCanvasWrapper.get();
 
     // clean up the old save stack
     mSaveStack.reset(nullptr);
diff --git a/libs/hwui/SkiaCanvas.h b/libs/hwui/SkiaCanvas.h
index be958c4..45924f6 100644
--- a/libs/hwui/SkiaCanvas.h
+++ b/libs/hwui/SkiaCanvas.h
@@ -37,8 +37,12 @@
      *  @param canvas SkCanvas to handle calls made to this SkiaCanvas. Must
      *      not be NULL. This constructor does not take ownership, so the caller
      *      must guarantee that it remains valid while the SkiaCanvas is valid.
+     *  @param xformToSRGB Indicates if bitmaps should be xformed to the sRGB
+     *      color space before drawing.  This makes sense for software rendering.
+     *      For the picture case, it may make more sense to leave bitmaps as is,
+     *      and handle the xform when replaying the picture.
      */
-    explicit SkiaCanvas(SkCanvas* canvas);
+    explicit SkiaCanvas(SkCanvas* canvas, XformToSRGB xformToSRGB);
 
     virtual ~SkiaCanvas();
 
@@ -178,6 +182,7 @@
 
     class Clip;
 
+    std::unique_ptr<SkCanvas> mCanvasWrapper; // might own a wrapper on the canvas
     std::unique_ptr<SkCanvas> mCanvasOwned; // might own a canvas we allocated
     SkCanvas*                 mCanvas;    // we do NOT own this canvas, it must survive us
                                           // unless it is the same as mCanvasOwned.get()
diff --git a/libs/hwui/hwui/Canvas.h b/libs/hwui/hwui/Canvas.h
index ab768cf..2267945 100644
--- a/libs/hwui/hwui/Canvas.h
+++ b/libs/hwui/hwui/Canvas.h
@@ -97,6 +97,15 @@
     static WARN_UNUSED_RESULT Canvas* create_recording_canvas(int width, int height,
             uirenderer::RenderNode* renderNode = nullptr);
 
+    enum class XformToSRGB {
+        // Transform any Bitmaps to the sRGB color space before drawing.
+        kImmediate,
+
+        // Draw the Bitmap as is.  This likely means that we are recording and that the
+        // transform can be handled at playback time.
+        kDefer,
+    };
+
     /**
      *  Create a new Canvas object which delegates to an SkCanvas.
      *
@@ -104,10 +113,12 @@
      *      delegated to this object. This function will call ref() on the
      *      SkCanvas, and the returned Canvas will unref() it upon
      *      destruction.
+     *  @param xformToSRGB Indicates if bitmaps should be xformed to the sRGB
+     *      color space before drawing.
      *  @return new non-null Canvas Object.  The type of DisplayList produced by this canvas is
      *      determined based on  Properties::getRenderPipelineType().
      */
-    static Canvas* create_canvas(SkCanvas* skiaCanvas);
+    static Canvas* create_canvas(SkCanvas* skiaCanvas, XformToSRGB xformToSRGB);
 
     /**
      *  Provides a Skia SkCanvas interface that acts as a proxy to this Canvas.
diff --git a/libs/hwui/tests/unit/SkiaCanvasTests.cpp b/libs/hwui/tests/unit/SkiaCanvasTests.cpp
index 7fb75dc..44476af 100644
--- a/libs/hwui/tests/unit/SkiaCanvasTests.cpp
+++ b/libs/hwui/tests/unit/SkiaCanvasTests.cpp
@@ -44,7 +44,8 @@
         // record the same text draw into a SkPicture and replay it into a Recording canvas
         SkPictureRecorder recorder;
         SkCanvas* skCanvas = recorder.beginRecording(200, 200, NULL, 0);
-        std::unique_ptr<Canvas> pictCanvas(Canvas::create_canvas(skCanvas));
+        std::unique_ptr<Canvas> pictCanvas(Canvas::create_canvas(skCanvas,
+                Canvas::XformToSRGB::kDefer));
         TestUtils::drawUtf8ToCanvas(pictCanvas.get(), text, paint, 25, 25);
         sk_sp<SkPicture> picture = recorder.finishRecordingAsPicture();
 
@@ -63,7 +64,7 @@
 
 TEST(SkiaCanvas, drawShadowLayer) {
     auto surface = SkSurface::MakeRasterN32Premul(10, 10);
-    SkiaCanvas canvas(surface->getCanvas());
+    SkiaCanvas canvas(surface->getCanvas(), Canvas::XformToSRGB::kDefer);
 
     // clear to white
     canvas.drawColor(SK_ColorWHITE, SkBlendMode::kSrc);
@@ -78,3 +79,52 @@
     ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorWHITE);
     ASSERT_NE(TestUtils::getColor(surface, 5, 5), SK_ColorWHITE);
 }
+
+TEST(SkiaCanvas, colorSpaceXform) {
+    sk_sp<SkColorSpace> adobe = SkColorSpace::MakeRGB(SkColorSpace::kSRGB_RenderTargetGamma,
+                                                      SkColorSpace::kAdobeRGB_Gamut);
+
+    SkImageInfo adobeInfo = SkImageInfo::Make(1, 1, kN32_SkColorType, kOpaque_SkAlphaType, adobe);
+    sk_sp<Bitmap> adobeBitmap = Bitmap::allocateHeapBitmap(adobeInfo);
+    SkBitmap adobeSkBitmap;
+    adobeBitmap->getSkBitmap(&adobeSkBitmap);
+    adobeSkBitmap.lockPixels();
+    *adobeSkBitmap.getAddr32(0, 0) = 0xFF0000F0; // Opaque, almost fully-red
+
+    SkImageInfo info = adobeInfo.makeColorSpace(nullptr);
+    sk_sp<Bitmap> bitmap = Bitmap::allocateHeapBitmap(info);
+    SkBitmap skBitmap;
+    bitmap->getSkBitmap(&skBitmap);
+
+    // Create a software canvas.
+    SkiaCanvas canvas(skBitmap);
+    canvas.drawBitmap(*adobeBitmap, 0, 0, nullptr);
+    // The result should be fully red, since we convert to sRGB at draw time.
+    skBitmap.lockPixels();
+    ASSERT_EQ(0xFF0000FF, *skBitmap.getAddr32(0, 0));
+
+    // Now try in kDefer mode.  This is a little strange given that, in practice, all software
+    // canvases are kImmediate.
+    SkCanvas skCanvas(skBitmap);
+    SkiaCanvas deferCanvas(&skCanvas, Canvas::XformToSRGB::kDefer);
+    deferCanvas.drawBitmap(*adobeBitmap, 0, 0, nullptr);
+    // The result should be as initialized, since we deferred the conversion to sRGB.
+    skBitmap.lockPixels();
+    ASSERT_EQ(0xFF0000F0, *skBitmap.getAddr32(0, 0));
+
+    // Test picture recording.  We will kDefer the xform at recording time, but handle it when
+    // we playback to the software canvas.
+    SkPictureRecorder recorder;
+    SkCanvas* skPicCanvas = recorder.beginRecording(1, 1, NULL, 0);
+    SkiaCanvas picCanvas(skPicCanvas, Canvas::XformToSRGB::kDefer);
+    picCanvas.drawBitmap(*adobeBitmap, 0, 0, nullptr);
+    sk_sp<SkPicture> picture = recorder.finishRecordingAsPicture();
+
+    // Playback to a deferred canvas.  The result should be as initialized.
+    deferCanvas.asSkCanvas()->drawPicture(picture);
+    ASSERT_EQ(0xFF0000F0, *skBitmap.getAddr32(0, 0));
+
+    // Playback to an immediate canvas.  The result should be fully red.
+    canvas.asSkCanvas()->drawPicture(picture);
+    ASSERT_EQ(0xFF0000FF, *skBitmap.getAddr32(0, 0));
+}