SkPDF: Re-use Jpeg Image Shaders

Also, de-dup shader better.

  - SkBitmapKeyFromImage function factors out image de-dup code.
  - SkPDFUtils::ToBitmap funtion factors out to-bitmap code
  - make_image_shader function now takes Image rather than Bitmap.

All tests render the same.  Some are significantly smaller PDFs.

Change-Id: Id8dd36b31c8e573f44a5e9f6058d5a1d622b6385
Reviewed-on: https://skia-review.googlesource.com/24081
Reviewed-by: Florin Malita <fmalita@chromium.org>
Commit-Queue: Hal Canary <halcanary@google.com>
diff --git a/src/pdf/SkKeyedImage.cpp b/src/pdf/SkKeyedImage.cpp
index bf19632..bcf7d70 100644
--- a/src/pdf/SkKeyedImage.cpp
+++ b/src/pdf/SkKeyedImage.cpp
@@ -9,15 +9,19 @@
 
 #include "SkImage_Base.h"
 
-SkKeyedImage::SkKeyedImage(sk_sp<SkImage> i) : fImage(std::move(i)) {
-    if (fImage) {
-        if (const SkBitmap* bm = as_IB(fImage.get())->onPeekBitmap()) {
-            SkIPoint o = bm->pixelRefOrigin();
-            fKey = {fImage->bounds().makeOffset(o.fX, o.fY), bm->getGenerationID()};
-        } else {
-            fKey = {fImage->bounds(), fImage->uniqueID()};
-        }
+SkBitmapKey SkBitmapKeyFromImage(const SkImage* image) {
+    if (!image) {
+        return {{0, 0, 0, 0}, 0};
     }
+    if (const SkBitmap* bm = as_IB(image)->onPeekBitmap()) {
+        SkIPoint o = bm->pixelRefOrigin();
+        return {image->bounds().makeOffset(o.x(), o.y()), bm->getGenerationID()};
+    }
+    return {image->bounds(), image->uniqueID()};
+}
+
+SkKeyedImage::SkKeyedImage(sk_sp<SkImage> i) : fImage(std::move(i)) {
+    fKey = SkBitmapKeyFromImage(fImage.get());
 }
 
 SkKeyedImage::SkKeyedImage(const SkBitmap& bm) : fImage(SkImage::MakeFromBitmap(bm)) {
diff --git a/src/pdf/SkKeyedImage.h b/src/pdf/SkKeyedImage.h
index f74ec5c..f489e57 100644
--- a/src/pdf/SkKeyedImage.h
+++ b/src/pdf/SkKeyedImage.h
@@ -37,4 +37,10 @@
     sk_sp<SkImage> fImage;
     SkBitmapKey fKey = {{0, 0, 0, 0}, 0};
 };
+
+/**
+ *  Given an Image, return the Bitmap Key that corresponds to it.  If the Image
+ *  wraps a Bitmap, use that Bitmap's key.
+ */
+SkBitmapKey SkBitmapKeyFromImage(const SkImage*);
 #endif  // SkKeyedImage_DEFINED
diff --git a/src/pdf/SkPDFBitmap.cpp b/src/pdf/SkPDFBitmap.cpp
index d0dcce6..e28800b 100644
--- a/src/pdf/SkPDFBitmap.cpp
+++ b/src/pdf/SkPDFBitmap.cpp
@@ -5,35 +5,27 @@
  * found in the LICENSE file.
  */
 
+#include "SkPDFBitmap.h"
+
 #include "SkColorPriv.h"
 #include "SkData.h"
 #include "SkDeflate.h"
-#include "SkImage_Base.h"
+#include "SkImage.h"
 #include "SkJpegInfo.h"
-#include "SkPDFBitmap.h"
 #include "SkPDFCanon.h"
 #include "SkPDFTypes.h"
+#include "SkPDFUtils.h"
 #include "SkStream.h"
 #include "SkUnPreMultiply.h"
 
-void image_get_ro_pixels(const SkImage* image, SkBitmap* dst) {
-    SkColorSpace* legacyColorSpace = nullptr;
-    if(as_IB(image)->getROPixels(dst, legacyColorSpace)
-       && dst->dimensions() == image->dimensions()) {
-        return;
-    }
-    // no pixels or wrong size: fill with zeros.
-    dst->setInfo(SkImageInfo::MakeN32(image->width(), image->height(), image->alphaType()));
-}
-
 bool image_compute_is_opaque(const SkImage* image) {
     if (image->isOpaque()) {
         return true;
     }
     // keep output PDF small at cost of possible resource use.
     SkBitmap bm;
-    image_get_ro_pixels(image, &bm);
-    return SkBitmap::ComputeIsOpaque(bm);
+    // if image can not be read, treat as transparent.
+    return SkPDFUtils::ToBitmap(image, &bm) && SkBitmap::ComputeIsOpaque(bm);
 }
 
 ////////////////////////////////////////////////////////////////////////////////
@@ -286,7 +278,10 @@
                                const sk_sp<SkPDFObject>& smask,
                                const SkPDFObjNumMap& objNumMap) {
     SkBitmap bitmap;
-    image_get_ro_pixels(image, &bitmap);      // TODO(halcanary): test
+    if (!SkPDFUtils::ToBitmap(image, &bitmap)) {
+        // no pixels or wrong size: fill with zeros.
+        bitmap.setInfo(SkImageInfo::MakeN32(image->width(), image->height(), image->alphaType()));
+    }
 
     // Write to a temporary buffer to get the compressed length.
     SkDynamicMemoryWStream buffer;
@@ -433,8 +428,7 @@
     if (pixelSerializer) {
         SkBitmap bm;
         SkPixmap pmap;
-        SkColorSpace* legacyColorSpace = nullptr;
-        if (as_IB(image.get())->getROPixels(&bm, legacyColorSpace) && bm.peekPixels(&pmap)) {
+        if (SkPDFUtils::ToBitmap(image.get(), &bm) && bm.peekPixels(&pmap)) {
             data = pixelSerializer->encodeToData(pmap);
             if (data && SkIsJFIF(data.get(), &info)) {
                 bool yuv = info.fType == SkJFIFInfo::kYCbCr;
diff --git a/src/pdf/SkPDFShader.cpp b/src/pdf/SkPDFShader.cpp
index 56d42f6..e52fad9 100644
--- a/src/pdf/SkPDFShader.cpp
+++ b/src/pdf/SkPDFShader.cpp
@@ -22,6 +22,12 @@
 #include "SkTemplates.h"
 
 
+static void draw_image_matrix(SkCanvas* canvas, const SkImage* img, const SkMatrix& matrix) {
+    SkAutoCanvasRestore acr(canvas, true);
+    canvas->concat(matrix);
+    canvas->drawImage(img, 0, 0);
+}
+
 static void draw_bitmap_matrix(SkCanvas* canvas, const SkBitmap& bm, const SkMatrix& matrix) {
     SkAutoCanvasRestore acr(canvas, true);
     canvas->concat(matrix);
@@ -30,9 +36,8 @@
 
 static sk_sp<SkPDFStream> make_image_shader(SkPDFDocument* doc,
                                             const SkPDFImageShaderKey& key,
-                                            SkBitmap image) {
-    SkASSERT(key.fBitmapKey ==
-             (SkBitmapKey{image.getSubset(), image.getGenerationID()}));
+                                            SkImage* image) {
+    SkASSERT(image);
 
     // The image shader pattern cell will be drawn into a separate device
     // in pattern cell space (no scaling on the bitmap, though there may be
@@ -42,14 +47,12 @@
     // to handle fake clamping.
     SkMatrix finalMatrix = key.fCanvasTransform;
     finalMatrix.preConcat(key.fShaderTransform);
-    SkRect deviceBounds;
-    deviceBounds.set(key.fBBox);
+    SkRect deviceBounds = SkRect::Make(key.fBBox);
     if (!SkPDFUtils::InverseTransformBBox(finalMatrix, &deviceBounds)) {
         return nullptr;
     }
 
-    SkRect bitmapBounds;
-    image.getBounds(&bitmapBounds);
+    SkRect bitmapBounds = SkRect::Make(image->bounds());
 
     // For tiling modes, the bounds should be extended to include the bitmap,
     // otherwise the bitmap gets clipped out and the shader is empty and awful.
@@ -63,13 +66,12 @@
         deviceBounds.join(bitmapBounds);
     }
 
-    SkISize size = SkISize::Make(SkScalarRoundToInt(deviceBounds.width()),
-                                 SkScalarRoundToInt(deviceBounds.height()));
-    auto patternDevice = sk_make_sp<SkPDFDevice>(size, doc);
+    SkISize patternDeviceSize = {SkScalarCeilToInt(deviceBounds.width()),
+                                 SkScalarCeilToInt(deviceBounds.height())};
+    auto patternDevice = sk_make_sp<SkPDFDevice>(patternDeviceSize, doc);
     SkCanvas canvas(patternDevice.get());
 
-    SkRect patternBBox;
-    image.getBounds(&patternBBox);
+    SkRect patternBBox = SkRect::Make(image->bounds());
 
     // Translate the canvas so that the bitmap origin is at (0, 0).
     canvas.translate(-deviceBounds.left(), -deviceBounds.top());
@@ -80,24 +82,24 @@
     // If the bitmap is out of bounds (i.e. clamp mode where we only see the
     // stretched sides), canvas will clip this out and the extraneous data
     // won't be saved to the PDF.
-    canvas.drawBitmap(image, 0, 0);
+    canvas.drawImage(image, 0, 0);
 
-    SkScalar width = SkIntToScalar(image.width());
-    SkScalar height = SkIntToScalar(image.height());
+    SkScalar width = SkIntToScalar(image->width());
+    SkScalar height = SkIntToScalar(image->height());
 
     // Tiling is implied.  First we handle mirroring.
     if (tileModes[0] == SkShader::kMirror_TileMode) {
         SkMatrix xMirror;
         xMirror.setScale(-1, 1);
         xMirror.postTranslate(2 * width, 0);
-        draw_bitmap_matrix(&canvas, image, xMirror);
+        draw_image_matrix(&canvas, image, xMirror);
         patternBBox.fRight += width;
     }
     if (tileModes[1] == SkShader::kMirror_TileMode) {
         SkMatrix yMirror;
         yMirror.setScale(SK_Scalar1, -SK_Scalar1);
         yMirror.postTranslate(0, 2 * height);
-        draw_bitmap_matrix(&canvas, image, yMirror);
+        draw_image_matrix(&canvas, image, yMirror);
         patternBBox.fBottom += height;
     }
     if (tileModes[0] == SkShader::kMirror_TileMode &&
@@ -105,53 +107,66 @@
         SkMatrix mirror;
         mirror.setScale(-1, -1);
         mirror.postTranslate(2 * width, 2 * height);
-        draw_bitmap_matrix(&canvas, image, mirror);
+        draw_image_matrix(&canvas, image, mirror);
     }
 
     // Then handle Clamping, which requires expanding the pattern canvas to
     // cover the entire surfaceBBox.
 
+    SkBitmap bitmap;
+    if (tileModes[0] == SkShader::kClamp_TileMode ||
+        tileModes[1] == SkShader::kClamp_TileMode) {
+        // For now, the easiest way to access the colors in the corners and sides is
+        // to just make a bitmap from the image.
+        if (!SkPDFUtils::ToBitmap(image, &bitmap)) {
+            bitmap.allocN32Pixels(image->width(), image->height());
+            bitmap.eraseColor(0x00000000);
+        }
+    }
+
     // If both x and y are in clamp mode, we start by filling in the corners.
     // (Which are just a rectangles of the corner colors.)
     if (tileModes[0] == SkShader::kClamp_TileMode &&
             tileModes[1] == SkShader::kClamp_TileMode) {
+        SkASSERT(!bitmap.drawsNothing());
         SkPaint paint;
         SkRect rect;
         rect = SkRect::MakeLTRB(deviceBounds.left(), deviceBounds.top(), 0, 0);
         if (!rect.isEmpty()) {
-            paint.setColor(image.getColor(0, 0));
+            paint.setColor(bitmap.getColor(0, 0));
             canvas.drawRect(rect, paint);
         }
 
         rect = SkRect::MakeLTRB(width, deviceBounds.top(),
                                 deviceBounds.right(), 0);
         if (!rect.isEmpty()) {
-            paint.setColor(image.getColor(image.width() - 1, 0));
+            paint.setColor(bitmap.getColor(bitmap.width() - 1, 0));
             canvas.drawRect(rect, paint);
         }
 
         rect = SkRect::MakeLTRB(width, height,
                                 deviceBounds.right(), deviceBounds.bottom());
         if (!rect.isEmpty()) {
-            paint.setColor(image.getColor(image.width() - 1,
-                                           image.height() - 1));
+            paint.setColor(bitmap.getColor(bitmap.width() - 1,
+                                           bitmap.height() - 1));
             canvas.drawRect(rect, paint);
         }
 
         rect = SkRect::MakeLTRB(deviceBounds.left(), height,
                                 0, deviceBounds.bottom());
         if (!rect.isEmpty()) {
-            paint.setColor(image.getColor(0, image.height() - 1));
+            paint.setColor(bitmap.getColor(0, bitmap.height() - 1));
             canvas.drawRect(rect, paint);
         }
     }
 
     // Then expand the left, right, top, then bottom.
     if (tileModes[0] == SkShader::kClamp_TileMode) {
-        SkIRect subset = SkIRect::MakeXYWH(0, 0, 1, image.height());
+        SkASSERT(!bitmap.drawsNothing());
+        SkIRect subset = SkIRect::MakeXYWH(0, 0, 1, bitmap.height());
         if (deviceBounds.left() < 0) {
             SkBitmap left;
-            SkAssertResult(image.extractSubset(&left, subset));
+            SkAssertResult(bitmap.extractSubset(&left, subset));
 
             SkMatrix leftMatrix;
             leftMatrix.setScale(-deviceBounds.left(), 1);
@@ -168,8 +183,8 @@
 
         if (deviceBounds.right() > width) {
             SkBitmap right;
-            subset.offset(image.width() - 1, 0);
-            SkAssertResult(image.extractSubset(&right, subset));
+            subset.offset(bitmap.width() - 1, 0);
+            SkAssertResult(bitmap.extractSubset(&right, subset));
 
             SkMatrix rightMatrix;
             rightMatrix.setScale(deviceBounds.right() - width, 1);
@@ -186,10 +201,11 @@
     }
 
     if (tileModes[1] == SkShader::kClamp_TileMode) {
-        SkIRect subset = SkIRect::MakeXYWH(0, 0, image.width(), 1);
+        SkASSERT(!bitmap.drawsNothing());
+        SkIRect subset = SkIRect::MakeXYWH(0, 0, bitmap.width(), 1);
         if (deviceBounds.top() < 0) {
             SkBitmap top;
-            SkAssertResult(image.extractSubset(&top, subset));
+            SkAssertResult(bitmap.extractSubset(&top, subset));
 
             SkMatrix topMatrix;
             topMatrix.setScale(SK_Scalar1, -deviceBounds.top());
@@ -206,8 +222,8 @@
 
         if (deviceBounds.bottom() > height) {
             SkBitmap bottom;
-            subset.offset(0, image.height() - 1);
-            SkAssertResult(image.extractSubset(&bottom, subset));
+            subset.offset(0, bitmap.height() - 1);
+            SkAssertResult(bitmap.extractSubset(&bottom, subset));
 
             SkMatrix bottomMatrix;
             bottomMatrix.setScale(SK_Scalar1, deviceBounds.bottom() - height);
@@ -225,7 +241,7 @@
 
     auto imageShader = sk_make_sp<SkPDFStream>(patternDevice->content());
     SkPDFUtils::PopulateTilingPatternDict(imageShader->dict(), patternBBox,
-                                 patternDevice->makeResourceDict(), finalMatrix);
+                                          patternDevice->makeResourceDict(), finalMatrix);
     return imageShader;
 }
 
@@ -245,7 +261,7 @@
         canvasTransform,
         SkMatrix::I(),
         surfaceBBox,
-        {{0, 0, 0, 0}, 0},
+        {{0, 0, 0, 0}, 0},  // don't need the key; won't de-dup.
         {SkShader::kClamp_TileMode, SkShader::kClamp_TileMode}};
 
     key.fShaderTransform = shader->getLocalMatrix();
@@ -271,23 +287,25 @@
     SkSize scale = {SkIntToScalar(size.width()) / shaderRect.width(),
                     SkIntToScalar(size.height()) / shaderRect.height()};
 
-    SkBitmap image;
-    image.allocN32Pixels(size.width(), size.height());
-    image.eraseColor(SK_ColorTRANSPARENT);
+    SkBitmap bitmap;
+    bitmap.allocN32Pixels(size.width(), size.height());
+    bitmap.eraseColor(SK_ColorTRANSPARENT);
 
     SkPaint p;
     p.setShader(sk_ref_sp(shader));
 
-    SkCanvas canvas(image);
+    SkCanvas canvas(bitmap);
     canvas.scale(scale.width(), scale.height());
     canvas.translate(-shaderRect.x(), -shaderRect.y());
     canvas.drawPaint(p);
 
     key.fShaderTransform.setTranslate(shaderRect.x(), shaderRect.y());
     key.fShaderTransform.preScale(1 / scale.width(), 1 / scale.height());
-    key.fBitmapKey = SkBitmapKey{image.getSubset(), image.getGenerationID()};
-    SkASSERT (!image.isNull());
-    return make_image_shader(doc, key, std::move(image));
+
+    SkASSERT (!bitmap.isNull());
+    bitmap.setImmutable();
+    sk_sp<SkImage> image = SkImage::MakeFromBitmap(bitmap);
+    return make_image_shader(doc, key, image.get());
 }
 
 sk_sp<SkPDFObject> SkPDFMakeShader(SkPDFDocument* doc,
@@ -311,20 +329,14 @@
         {SkShader::kClamp_TileMode, SkShader::kClamp_TileMode}};
 
     SkASSERT(shader->asAGradient(nullptr) == SkShader::kNone_GradientType) ;
-    SkImage* skimg;
-    if ((skimg = shader->isAImage(&key.fShaderTransform, key.fImageTileModes))
-            && skimg->asLegacyBitmap(&image, SkImage::kRO_LegacyBitmapMode)) {
-        // TODO(halcanary): delay converting to bitmap.
-        key.fBitmapKey = SkBitmapKey{image.getSubset(), image.getGenerationID()};
-        if (image.isNull()) {
-            return nullptr;
-        }
+    if (SkImage* skimg = shader->isAImage(&key.fShaderTransform, key.fImageTileModes)) {
+        key.fBitmapKey = SkBitmapKeyFromImage(skimg);
         SkPDFCanon* canon = doc->canon();
         sk_sp<SkPDFObject>* shaderPtr = canon->fImageShaderMap.find(key);
         if (shaderPtr) {
             return *shaderPtr;
         }
-        sk_sp<SkPDFObject> pdfShader = make_image_shader(doc, key, std::move(image));
+        sk_sp<SkPDFObject> pdfShader = make_image_shader(doc, key, skimg);
         canon->fImageShaderMap.set(std::move(key), pdfShader);
         return pdfShader;
     }
diff --git a/src/pdf/SkPDFUtils.cpp b/src/pdf/SkPDFUtils.cpp
index 510be6c..af18432 100644
--- a/src/pdf/SkPDFUtils.cpp
+++ b/src/pdf/SkPDFUtils.cpp
@@ -5,15 +5,16 @@
  * found in the LICENSE file.
  */
 
+#include "SkPDFUtils.h"
 
 #include "SkData.h"
 #include "SkFixed.h"
 #include "SkGeometry.h"
+#include "SkImage_Base.h"
 #include "SkPDFResourceDict.h"
-#include "SkPDFUtils.h"
+#include "SkPDFTypes.h"
 #include "SkStream.h"
 #include "SkString.h"
-#include "SkPDFTypes.h"
 
 #include <cmath>
 
@@ -516,3 +517,16 @@
         pattern->insertObject("Matrix", SkPDFUtils::MatrixToArray(matrix));
     }
 }
+
+bool SkPDFUtils::ToBitmap(const SkImage* img, SkBitmap* dst) {
+    SkASSERT(img);
+    SkASSERT(dst);
+    SkBitmap bitmap;
+    if(as_IB(img)->getROPixels(&bitmap, nullptr)) {
+        SkASSERT(bitmap.dimensions() == img->dimensions());
+        SkASSERT(!bitmap.drawsNothing());
+        *dst = std::move(bitmap);
+        return true;
+    }
+    return false;
+}
diff --git a/src/pdf/SkPDFUtils.h b/src/pdf/SkPDFUtils.h
index 93509fe..27d0a3a 100644
--- a/src/pdf/SkPDFUtils.h
+++ b/src/pdf/SkPDFUtils.h
@@ -4,11 +4,10 @@
  * Use of this source code is governed by a BSD-style license that can be
  * found in the LICENSE file.
  */
-
-
 #ifndef SkPDFUtils_DEFINED
 #define SkPDFUtils_DEFINED
 
+#include "SkPDFTypes.h"
 #include "SkPaint.h"
 #include "SkPath.h"
 #include "SkShader.h"
@@ -124,6 +123,8 @@
                                SkRect& bbox,
                                sk_sp<SkPDFDict> resources,
                                const SkMatrix& matrix);
+
+bool ToBitmap(const SkImage* img, SkBitmap* dst);
 }  // namespace SkPDFUtils
 
 #endif