SkPDF: Implement drawImage*() properly

drawImage calls now properly embeds the original jpeg.

NOTE: drawBitmap*() calls no longer embed JPEG files when
possible (this is in advance of eliminating bitmaps backed
by encoded data).  Chromium has already moved from
drawBitmap to drawImage.

Comparisons:

control:
    total PDF drawImage/drawBitmap calls: 8010
    total PDF jpeg images: 0
    total PDF regular images: 3581

experiament:
    total PDF drawImage/drawBitmap calls: 8014
    total PDF jpeg images: 271
    total PDF regular images: 3311
    total PDF regular images: 3582 (271 + 3311)

When comparing rendered output there were perceptual
differences in the following four GMs: colorcube, emboss,
colormatrix, and tablecolorfilter.  All of these differences
were improvements (that is, closer to the 8888 rendering)
due fixing a bug with colorfilters and forgetting to call
notifyPixelsChanged.

No SKPs had perceptual differences.

Total PDF size dropped from 133964 kB to 126276 kB, a 5.7%
improvement (mostly due to restoring use of JPG images in
SKPs).

BUG=skia:4370

Review URL: https://codereview.chromium.org/1372783003
diff --git a/dm/DM.cpp b/dm/DM.cpp
index 0faca8f..2a96a9e 100644
--- a/dm/DM.cpp
+++ b/dm/DM.cpp
@@ -29,6 +29,10 @@
 #include "Timer.h"
 #include "sk_tool_utils.h"
 
+#ifdef SK_PDF_IMAGE_STATS
+extern void SkPDFImageDumpStats();
+#endif
+
 #ifdef SKIA_PNG_PREFIXED
     // this must proceed png.h
     #include "pngprefix.h"
@@ -1105,6 +1109,9 @@
         SkDebugf("Hrm, we didn't seem to run everything we intended to!  Please file a bug.\n");
         return 1;
     }
+    #ifdef SK_PDF_IMAGE_STATS
+    SkPDFImageDumpStats();
+    #endif  // SK_PDF_IMAGE_STATS
     return 0;
 }
 
diff --git a/gm/repeated_bitmap.cpp b/gm/repeated_bitmap.cpp
index dc2a2aa..39eeb4e 100644
--- a/gm/repeated_bitmap.cpp
+++ b/gm/repeated_bitmap.cpp
@@ -5,49 +5,41 @@
  * found in the LICENSE file.
  */
 
+#include "Resources.h"
+#include "SkImage.h"
 #include "gm.h"
 #include "sk_tool_utils.h"
-#include "Resources.h"
 
-DEF_SIMPLE_GM(repeated_bitmap, canvas, 576, 576) {
-    sk_tool_utils::draw_checkerboard(canvas, sk_tool_utils::color_to_565(0xFF999999),
+static void draw_rotated_image(SkCanvas* canvas, const SkImage* image) {
+    sk_tool_utils::draw_checkerboard(canvas, SkColorSetRGB(156, 154, 156),
                                      SK_ColorWHITE, 12);
-    SkRect rect = SkRect::MakeLTRB(-4.25f, -4.25f, 4.25f, 4.25f);
+    if (!image) {
+        return;
+    }
+    SkRect rect = SkRect::MakeLTRB(-68.0f, -68.0f, 68.0f, 68.0f);
     SkPaint paint;
-    paint.setColor(0xFF333333);
-    SkBitmap bm;
-    if (GetResourceAsBitmap("randPixels.png", &bm)) {
-        for (int j = 0; j < 4; ++j) {
-            for (int i = 0; i < 4; ++i) {
-                SkAutoCanvasRestore autoCanvasRestore(canvas, true);
-                canvas->scale(12.0f, 12.0f);
-                canvas->translate(6.0f + 12.0f * SkIntToScalar(i),
-                                  6.0f + 12.0f * SkIntToScalar(j));
-                canvas->rotate(18.0f * (i + 4 * j));
-                canvas->drawRect(rect, paint);
-                canvas->drawBitmap(bm, -4.0f, -4.0f);
-            }
+    paint.setColor(SkColorSetRGB(49, 48, 49));
+    SkScalar scale = SkTMin(128.0f / image->width(),
+                            128.0f / image->height());
+    SkScalar point[2] = {-0.5f * image->width(), -0.5f * image->height()};
+    for (int j = 0; j < 4; ++j) {
+        for (int i = 0; i < 4; ++i) {
+            SkAutoCanvasRestore autoCanvasRestore(canvas, true);
+            canvas->translate(96.0f + 192.0f * i, 96.0f + 192.0f * j);
+            canvas->rotate(18.0f * (i + 4 * j));
+            canvas->drawRect(rect, paint);
+            canvas->scale(scale, scale);
+            canvas->drawImage(image, point[0], point[1]);
         }
     }
 }
 
+DEF_SIMPLE_GM(repeated_bitmap, canvas, 576, 576) {
+    SkAutoTUnref<SkImage> image(GetResourceAsImage("randPixels.png"));
+    draw_rotated_image(canvas, image);
+}
+
 DEF_SIMPLE_GM(repeated_bitmap_jpg, canvas, 576, 576) {
-    sk_tool_utils::draw_checkerboard(canvas, sk_tool_utils::color_to_565(0xFF999999),
-                                     SK_ColorWHITE, 12);
-    SkRect rect = SkRect::MakeLTRB(-68.0f, -68.0f, 68.0f, 68.0f);
-    SkPaint paint;
-    paint.setColor(0xFF333333);
-    SkBitmap bm;
-    if (GetResourceAsBitmap("color_wheel.jpg", &bm)) {
-        for (int j = 0; j < 4; ++j) {
-            for (int i = 0; i < 4; ++i) {
-                SkAutoCanvasRestore autoCanvasRestore(canvas, true);
-                canvas->translate(96.0f + 192.0f * SkIntToScalar(i),
-                                  96.0f + 192.0f * SkIntToScalar(j));
-                canvas->rotate(18.0f * (i + 4 * j));
-                canvas->drawRect(rect, paint);
-                canvas->drawBitmap(bm, -64.0f, -64.0f);
-            }
-        }
-    }
+    SkAutoTUnref<SkImage> image(GetResourceAsImage("color_wheel.jpg"));
+    draw_rotated_image(canvas, image);
 }
diff --git a/gyp/pdf.gyp b/gyp/pdf.gyp
index c294986..ee30488 100644
--- a/gyp/pdf.gyp
+++ b/gyp/pdf.gyp
@@ -29,6 +29,7 @@
         '../include/private',
         '../src/core', # needed to get SkGlyphCache.h and SkTextFormatParams.h
         '../src/pdf',
+        '../src/image',
         '../src/utils', # needed to get SkBitSet.h
       ],
       'sources': [
diff --git a/src/pdf/SkJpegInfo.cpp b/src/pdf/SkJpegInfo.cpp
index 455b4aa..3c52a10 100644
--- a/src/pdf/SkJpegInfo.cpp
+++ b/src/pdf/SkJpegInfo.cpp
@@ -107,8 +107,8 @@
         return false;  // Invalid JFIF
     }
     if (info) {
-        info->fHeight = JpegSegment::GetBigendianUint16(&segment.data()[1]);
-        info->fWidth = JpegSegment::GetBigendianUint16(&segment.data()[3]);
+        info->fSize.set(JpegSegment::GetBigendianUint16(&segment.data()[3]),
+                        JpegSegment::GetBigendianUint16(&segment.data()[1]));
         if (numberOfComponents == 3) {
             info->fType = SkJFIFInfo::kYCbCr;
         } else {
diff --git a/src/pdf/SkJpegInfo.h b/src/pdf/SkJpegInfo.h
index 178d648..1b03e45 100644
--- a/src/pdf/SkJpegInfo.h
+++ b/src/pdf/SkJpegInfo.h
@@ -7,11 +7,12 @@
 #ifndef SkJpegInfo_DEFINED
 #define SkJpegInfo_DEFINED
 
+#include "SkSize.h"
+
 class SkData;
 
 struct SkJFIFInfo {
-    int fWidth;
-    int fHeight;
+    SkISize fSize;
     enum Type {
         kGrayscale,
         kYCbCr,
diff --git a/src/pdf/SkPDFBitmap.cpp b/src/pdf/SkPDFBitmap.cpp
index de43221..1978040 100644
--- a/src/pdf/SkPDFBitmap.cpp
+++ b/src/pdf/SkPDFBitmap.cpp
@@ -8,14 +8,33 @@
 #include "SkColorPriv.h"
 #include "SkData.h"
 #include "SkDeflate.h"
-#include "SkImageGenerator.h"
+#include "SkImage_Base.h"
 #include "SkJpegInfo.h"
 #include "SkPDFBitmap.h"
 #include "SkPDFCanon.h"
-#include "SkPixelRef.h"
 #include "SkStream.h"
 #include "SkUnPreMultiply.h"
 
+void image_get_ro_pixels(const SkImage* image, SkBitmap* dst) {
+    if(as_IB(image)->getROPixels(dst)
+       && dst->dimensions() == image->dimensions()) {
+        return;
+    }
+    // no pixels or wrong size: fill with zeros.
+    SkAlphaType at = image->isOpaque() ? kOpaque_SkAlphaType : kPremul_SkAlphaType;
+    dst->setInfo(SkImageInfo::MakeN32(image->width(), image->height(), at));
+}
+
+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);
+}
+
 ////////////////////////////////////////////////////////////////////////////////
 
 static void pdf_stream_begin(SkWStream* stream) {
@@ -239,80 +258,6 @@
     }
 }
 
-////////////////////////////////////////////////////////////////////////////////
-
-namespace {
-// This SkPDFObject only outputs the alpha layer of the given bitmap.
-class PDFAlphaBitmap : public SkPDFObject {
-public:
-    PDFAlphaBitmap(const SkBitmap& bm) : fBitmap(bm) {}
-    ~PDFAlphaBitmap() {}
-    void emitObject(SkWStream*,
-                    const SkPDFObjNumMap&,
-                    const SkPDFSubstituteMap&) const override;
-
-private:
-    const SkBitmap fBitmap;
-};
-
-void PDFAlphaBitmap::emitObject(SkWStream* stream,
-                                const SkPDFObjNumMap& objNumMap,
-                                const SkPDFSubstituteMap& substitutes) const {
-    SkAutoLockPixels autoLockPixels(fBitmap);
-    SkASSERT(fBitmap.colorType() != kIndex_8_SkColorType ||
-             fBitmap.getColorTable());
-
-    // Write to a temporary buffer to get the compressed length.
-    SkDynamicMemoryWStream buffer;
-    SkDeflateWStream deflateWStream(&buffer);
-    bitmap_alpha_to_a8(fBitmap, &deflateWStream);
-    deflateWStream.finalize();  // call before detachAsStream().
-    SkAutoTDelete<SkStreamAsset> asset(buffer.detachAsStream());
-
-    SkPDFDict pdfDict("XObject");
-    pdfDict.insertName("Subtype", "Image");
-    pdfDict.insertInt("Width", fBitmap.width());
-    pdfDict.insertInt("Height", fBitmap.height());
-    pdfDict.insertName("ColorSpace", "DeviceGray");
-    pdfDict.insertInt("BitsPerComponent", 8);
-    pdfDict.insertName("Filter", "FlateDecode");
-    pdfDict.insertInt("Length", asset->getLength());
-    pdfDict.emitObject(stream, objNumMap, substitutes);
-
-    pdf_stream_begin(stream);
-    stream->writeStream(asset.get(), asset->getLength());
-    pdf_stream_end(stream);
-}
-}  // namespace
-
-////////////////////////////////////////////////////////////////////////////////
-
-namespace {
-class PDFDefaultBitmap : public SkPDFBitmap {
-public:
-    const SkAutoTUnref<SkPDFObject> fSMask;
-    void emitObject(SkWStream*,
-                    const SkPDFObjNumMap&,
-                    const SkPDFSubstituteMap&) const override;
-    void addResources(SkPDFObjNumMap*,
-                      const SkPDFSubstituteMap&) const override;
-    PDFDefaultBitmap(const SkBitmap& bm, SkPDFObject* smask)
-        : SkPDFBitmap(bm), fSMask(smask) {}
-};
-}  // namespace
-
-void PDFDefaultBitmap::addResources(
-        SkPDFObjNumMap* catalog,
-        const SkPDFSubstituteMap& substitutes) const {
-    if (fSMask.get()) {
-        SkPDFObject* obj = substitutes.getSubstitute(fSMask.get());
-        SkASSERT(obj);
-        if (catalog->addObject(obj)) {
-            obj->addResources(catalog, substitutes);
-        }
-    }
-}
-
 static SkPDFArray* make_indexed_color_space(const SkColorTable* table) {
     SkPDFArray* result = new SkPDFArray;
     result->reserve(4);
@@ -343,37 +288,48 @@
     return result;
 }
 
-void PDFDefaultBitmap::emitObject(SkWStream* stream,
-                                  const SkPDFObjNumMap& objNumMap,
-                                  const SkPDFSubstituteMap& substitutes) const {
-    SkAutoLockPixels autoLockPixels(fBitmap);
-    SkASSERT(fBitmap.colorType() != kIndex_8_SkColorType ||
-             fBitmap.getColorTable());
+static void emit_image_xobject(SkWStream* stream,
+                               const SkImage* image,
+                               bool alpha,
+                               SkPDFObject* smask,
+                               const SkPDFObjNumMap& objNumMap,
+                               const SkPDFSubstituteMap& substitutes) {
+    SkBitmap bitmap;
+    image_get_ro_pixels(image, &bitmap);      // TODO(halcanary): test
+    SkAutoLockPixels autoLockPixels(bitmap);  // with malformed images.
+    SkASSERT(bitmap.colorType() != kIndex_8_SkColorType ||
+             bitmap.getColorTable());
 
     // Write to a temporary buffer to get the compressed length.
     SkDynamicMemoryWStream buffer;
     SkDeflateWStream deflateWStream(&buffer);
-    bitmap_to_pdf_pixels(fBitmap, &deflateWStream);
+    if (alpha) {
+        bitmap_alpha_to_a8(bitmap, &deflateWStream);
+    } else {
+        bitmap_to_pdf_pixels(bitmap, &deflateWStream);
+    }
     deflateWStream.finalize();  // call before detachAsStream().
     SkAutoTDelete<SkStreamAsset> asset(buffer.detachAsStream());
 
     SkPDFDict pdfDict("XObject");
     pdfDict.insertName("Subtype", "Image");
-    pdfDict.insertInt("Width", fBitmap.width());
-    pdfDict.insertInt("Height", fBitmap.height());
-    if (fBitmap.colorType() == kIndex_8_SkColorType) {
-        SkASSERT(1 == pdf_color_component_count(fBitmap.colorType()));
+    pdfDict.insertInt("Width", bitmap.width());
+    pdfDict.insertInt("Height", bitmap.height());
+    if (alpha) {
+        pdfDict.insertName("ColorSpace", "DeviceGray");
+    } else if (bitmap.colorType() == kIndex_8_SkColorType) {
+        SkASSERT(1 == pdf_color_component_count(bitmap.colorType()));
         pdfDict.insertObject("ColorSpace",
-                             make_indexed_color_space(fBitmap.getColorTable()));
-    } else if (1 == pdf_color_component_count(fBitmap.colorType())) {
+                             make_indexed_color_space(bitmap.getColorTable()));
+    } else if (1 == pdf_color_component_count(bitmap.colorType())) {
         pdfDict.insertName("ColorSpace", "DeviceGray");
     } else {
         pdfDict.insertName("ColorSpace", "DeviceRGB");
     }
-    pdfDict.insertInt("BitsPerComponent", 8);
-    if (fSMask) {
-        pdfDict.insertObjRef("SMask", SkRef(fSMask.get()));
+    if (smask) {
+        pdfDict.insertObjRef("SMask", SkRef(smask));
     }
+    pdfDict.insertInt("BitsPerComponent", 8);
     pdfDict.insertName("Filter", "FlateDecode");
     pdfDict.insertInt("Length", asset->getLength());
     pdfDict.emitObject(stream, objNumMap, substitutes);
@@ -385,26 +341,68 @@
 
 ////////////////////////////////////////////////////////////////////////////////
 
-static const SkBitmap& immutable_bitmap(const SkBitmap& bm, SkBitmap* copy) {
-    if (bm.isImmutable()) {
-        return bm;
+namespace {
+// This SkPDFObject only outputs the alpha layer of the given bitmap.
+class PDFAlphaBitmap : public SkPDFObject {
+public:
+    PDFAlphaBitmap(const SkImage* image) : fImage(SkRef(image)) {}
+    ~PDFAlphaBitmap() {}
+    void emitObject(SkWStream*  stream,
+                    const SkPDFObjNumMap& objNumMap,
+                    const SkPDFSubstituteMap& subs) const override {
+        emit_image_xobject(stream, fImage, true, nullptr, objNumMap, subs);
     }
-    bm.copyTo(copy);
-    copy->setImmutable();
-    return *copy;
-}
+
+private:
+    SkAutoTUnref<const SkImage> fImage;
+};
+
+}  // namespace
+
+////////////////////////////////////////////////////////////////////////////////
+
+namespace {
+class PDFDefaultBitmap : public SkPDFObject {
+public:
+    void emitObject(SkWStream*  stream,
+                    const SkPDFObjNumMap& objNumMap,
+                    const SkPDFSubstituteMap& substitutes) const override {
+        emit_image_xobject(stream, fImage, false, fSMask, objNumMap, substitutes);
+    }
+    void addResources(SkPDFObjNumMap* catalog,
+                      const SkPDFSubstituteMap& substitutes) const override {
+        if (fSMask.get()) {
+            SkPDFObject* obj = substitutes.getSubstitute(fSMask.get());
+            SkASSERT(obj);
+            if (catalog->addObject(obj)) {
+                obj->addResources(catalog, substitutes);
+            }
+        }
+    }
+    PDFDefaultBitmap(const SkImage* image, SkPDFObject* smask)
+        : fImage(SkRef(image)), fSMask(smask) {}
+
+private:
+    SkAutoTUnref<const SkImage> fImage;
+    const SkAutoTUnref<SkPDFObject> fSMask;
+};
+}  // namespace
+
+////////////////////////////////////////////////////////////////////////////////
 
 namespace {
 /**
- *  This PDFObject assumes that its constructor was handed YUV JFIF
- *  Jpeg-encoded data that can be directly embedded into a PDF.
+ *  This PDFObject assumes that its constructor was handed YUV or
+ *  Grayscale JFIF Jpeg-encoded data that can be directly embedded
+ *  into a PDF.
  */
-class PDFJpegBitmap : public SkPDFBitmap {
+class PDFJpegBitmap : public SkPDFObject {
 public:
+    SkISize fSize;
     SkAutoTUnref<SkData> fData;
     bool fIsYUV;
-    PDFJpegBitmap(const SkBitmap& bm, SkData* data, bool isYUV)
-        : SkPDFBitmap(bm), fData(SkRef(data)), fIsYUV(isYUV) {}
+    PDFJpegBitmap(SkISize size, SkData* data, bool isYUV)
+        : fSize(size), fData(SkRef(data)), fIsYUV(isYUV) {}
     void emitObject(SkWStream*,
                     const SkPDFObjNumMap&,
                     const SkPDFSubstituteMap&) const override;
@@ -415,8 +413,8 @@
                                const SkPDFSubstituteMap& substituteMap) const {
     SkPDFDict pdfDict("XObject");
     pdfDict.insertName("Subtype", "Image");
-    pdfDict.insertInt("Width", fBitmap.width());
-    pdfDict.insertInt("Height", fBitmap.height());
+    pdfDict.insertInt("Width", fSize.width());
+    pdfDict.insertInt("Height", fSize.height());
     if (fIsYUV) {
         pdfDict.insertName("ColorSpace", "DeviceRGB");
     } else {
@@ -435,39 +433,23 @@
 
 ////////////////////////////////////////////////////////////////////////////////
 
-SkPDFBitmap* SkPDFBitmap::Create(SkPDFCanon* canon, const SkBitmap& bitmap) {
-    SkASSERT(canon);
-    if (!SkColorTypeIsValid(bitmap.colorType()) ||
-        kUnknown_SkColorType == bitmap.colorType()) {
-        return nullptr;
-    }
-    SkBitmap copy;
-    const SkBitmap& bm = immutable_bitmap(bitmap, &copy);
-    if (bm.drawsNothing()) {
-        return nullptr;
-    }
-    if (SkPDFBitmap* canonBitmap = canon->findBitmap(bm)) {
-        return SkRef(canonBitmap);
-    }
-
-    if (bm.pixelRef() && bm.pixelRefOrigin().isZero() &&
-        bm.dimensions() == bm.pixelRef()->info().dimensions()) {
-        // Requires the bitmap to be backed by lazy pixels.
-        SkAutoTUnref<SkData> data(bm.pixelRef()->refEncodedData());
-        SkJFIFInfo info;
-        if (data && SkIsJFIF(data, &info)) {
-            bool yuv = info.fType == SkJFIFInfo::kYCbCr;
-            SkPDFBitmap* pdfBitmap = new PDFJpegBitmap(bm, data, yuv);
-            canon->addBitmap(pdfBitmap);
-            return pdfBitmap;
+SkPDFObject* SkPDFCreateBitmapObject(const SkImage* image) {
+    SkAutoTUnref<SkData> data(image->refEncoded());
+    SkJFIFInfo info;
+    if (data && SkIsJFIF(data, &info)) {
+        bool yuv = info.fType == SkJFIFInfo::kYCbCr;
+        if (info.fSize == image->dimensions()) {  // Sanity check.
+            // hold on to data, not image.
+            #ifdef SK_PDF_IMAGE_STATS
+            gJpegImageObjects.fetch_add(1);
+            #endif
+            return new PDFJpegBitmap(info.fSize, data, yuv);
         }
     }
-
-    SkPDFObject* smask = nullptr;
-    if (!bm.isOpaque() && !SkBitmap::ComputeIsOpaque(bm)) {
-        smask = new PDFAlphaBitmap(bm);
-    }
-    SkPDFBitmap* pdfBitmap = new PDFDefaultBitmap(bm, smask);
-    canon->addBitmap(pdfBitmap);
-    return pdfBitmap;
+    SkPDFObject* smask =
+            image_compute_is_opaque(image) ? nullptr : new PDFAlphaBitmap(image);
+    #ifdef SK_PDF_IMAGE_STATS
+    gRegularImageObjects.fetch_add(1);
+    #endif
+    return new PDFDefaultBitmap(image, smask);
 }
diff --git a/src/pdf/SkPDFBitmap.h b/src/pdf/SkPDFBitmap.h
index 58d91be..d931331 100644
--- a/src/pdf/SkPDFBitmap.h
+++ b/src/pdf/SkPDFBitmap.h
@@ -8,33 +8,14 @@
 #define SkPDFBitmap_DEFINED
 
 #include "SkPDFTypes.h"
-#include "SkBitmap.h"
 
-class SkPDFCanon;
+class SkImage;
 
 /**
- * SkPDFBitmap wraps a SkBitmap and serializes it as an image Xobject.
+ * SkPDFBitmap wraps a SkImage and serializes it as an image Xobject.
  * It is designed to use a minimal amout of memory, aside from refing
- * the bitmap's pixels, and its emitObject() does not cache any data.
- *
- * If !bitmap.isImmutable(), then a copy of the bitmap must be made;
- * there is no way around this.
- *
- * The SkPDFBitmap::Create function will check the canon for duplicates.
+ * the image, and its emitObject() does not cache any data.
  */
-class SkPDFBitmap : public SkPDFObject {
-public:
-    // Returns nullptr on unsupported bitmap;
-    static SkPDFBitmap* Create(SkPDFCanon*, const SkBitmap&);
-    bool equals(const SkBitmap& other) const {
-        return fBitmap.getGenerationID() == other.getGenerationID() &&
-               fBitmap.pixelRefOrigin() == other.pixelRefOrigin() &&
-               fBitmap.dimensions() == other.dimensions();
-    }
-
-protected:
-    const SkBitmap fBitmap;
-    SkPDFBitmap(const SkBitmap& bm) : fBitmap(bm) {}
-};
+SkPDFObject* SkPDFCreateBitmapObject(const SkImage*);
 
 #endif  // SkPDFBitmap_DEFINED
diff --git a/src/pdf/SkPDFCanon.cpp b/src/pdf/SkPDFCanon.cpp
index 6cc3995..4fdaa0d 100644
--- a/src/pdf/SkPDFCanon.cpp
+++ b/src/pdf/SkPDFCanon.cpp
@@ -5,6 +5,7 @@
  * found in the LICENSE file.
  */
 
+#include "SkImage.h"
 #include "SkPDFBitmap.h"
 #include "SkPDFCanon.h"
 #include "SkPDFFont.h"
@@ -25,8 +26,13 @@
     fImageShaderRecords.reset();
     fGraphicStateRecords.foreach ([](WrapGS w) { w.fPtr->unref(); });
     fGraphicStateRecords.reset();
-    fBitmapRecords.unrefAll();
-    fBitmapRecords.reset();
+
+    fBitmapToImageMap.foreach(
+            [](SkBitmapKey, const SkImage** p) { SkSafeUnref(*p); });
+    fBitmapToImageMap.reset();
+
+    fPDFBitmapMap.foreach([](uint32_t, SkPDFObject** p) { SkSafeUnref(*p); });
+    fPDFBitmapMap.reset();
 }
 
 ////////////////////////////////////////////////////////////////////////////////
@@ -121,10 +127,25 @@
 
 ////////////////////////////////////////////////////////////////////////////////
 
-SkPDFBitmap* SkPDFCanon::findBitmap(const SkBitmap& bm) const {
-    return find_item(fBitmapRecords, bm);
+SkPDFObject* SkPDFCanon::findPDFBitmap(const SkImage* image) const {
+    SkPDFObject** ptr = fPDFBitmapMap.find(image->uniqueID());
+    return ptr ? *ptr : nullptr;
 }
 
-void SkPDFCanon::addBitmap(SkPDFBitmap* pdfBitmap) {
-    fBitmapRecords.push(SkRef(pdfBitmap));
+void SkPDFCanon::addPDFBitmap(uint32_t imageUniqueID, SkPDFObject* pdfBitmap) {
+    fPDFBitmapMap.set(imageUniqueID, SkRef(pdfBitmap));
+}
+
+const SkImage* SkPDFCanon::bitmapToImage(const SkBitmap& bm) {
+    // reference remains owned by the fBitmapToImageMap!
+    SkBitmapKey key(bm);
+    if (const SkImage** img = fBitmapToImageMap.find(key)) {
+        return *img;
+    }
+    if (SkImage* image = SkImage::NewFromBitmap(bm)) {
+        return *fBitmapToImageMap.set(key, image);
+    }
+    SkBitmap n32bitmap;  // SkImage::NewFromBitmap can be finicky.
+    bm.copyTo(&n32bitmap, kN32_SkColorType);
+    return *fBitmapToImageMap.set(key, SkImage::NewFromBitmap(n32bitmap));
 }
diff --git a/src/pdf/SkPDFCanon.h b/src/pdf/SkPDFCanon.h
index 2ad5a22..a55024d 100644
--- a/src/pdf/SkPDFCanon.h
+++ b/src/pdf/SkPDFCanon.h
@@ -7,15 +7,29 @@
 #ifndef SkPDFCanon_DEFINED
 #define SkPDFCanon_DEFINED
 
+#include "SkBitmap.h"
 #include "SkPDFGraphicState.h"
 #include "SkPDFShader.h"
 #include "SkTDArray.h"
 #include "SkTHash.h"
 
-class SkBitmap;
 class SkPDFFont;
-class SkPDFBitmap;
 class SkPaint;
+class SkImage;
+
+class SkBitmapKey {
+public:
+    SkBitmapKey() : fSubset(SkIRect::MakeEmpty()), fGenID(0) {}
+    explicit SkBitmapKey(const SkBitmap& bm)
+        : fSubset(bm.getSubset()), fGenID(bm.getGenerationID()) {}
+    bool operator==(const SkBitmapKey& rhs) const {
+        return fGenID == rhs.fGenID && fSubset == rhs.fSubset;
+    }
+
+private:
+    SkIRect fSubset;
+    uint32_t fGenID;
+};
 
 /**
  *  The SkPDFCanon canonicalizes objects across PDF pages(SkPDFDevices).
@@ -60,8 +74,9 @@
     const SkPDFGraphicState* findGraphicState(const SkPDFGraphicState&) const;
     void addGraphicState(const SkPDFGraphicState*);
 
-    SkPDFBitmap* findBitmap(const SkBitmap&) const;
-    void addBitmap(SkPDFBitmap*);
+    SkPDFObject* findPDFBitmap(const SkImage* image) const;
+    void addPDFBitmap(uint32_t imageUniqueID, SkPDFObject*);
+    const SkImage* bitmapToImage(const SkBitmap&);
 
 private:
     struct FontRec {
@@ -92,6 +107,7 @@
     };
     SkTHashSet<WrapGS, WrapGS::Hash> fGraphicStateRecords;
 
-    SkTDArray<SkPDFBitmap*> fBitmapRecords;
+    SkTHashMap<SkBitmapKey, const SkImage*> fBitmapToImageMap;
+    SkTHashMap<uint32_t /*ImageUniqueID*/, SkPDFObject*> fPDFBitmapMap;
 };
 #endif  // SkPDFCanon_DEFINED
diff --git a/src/pdf/SkPDFDevice.cpp b/src/pdf/SkPDFDevice.cpp
index 461ae7f..8d53b99 100644
--- a/src/pdf/SkPDFDevice.cpp
+++ b/src/pdf/SkPDFDevice.cpp
@@ -18,6 +18,7 @@
 #include "SkPath.h"
 #include "SkPathOps.h"
 #include "SkPDFBitmap.h"
+#include "SkPDFCanon.h"
 #include "SkPDFFont.h"
 #include "SkPDFFormXObject.h"
 #include "SkPDFGraphicState.h"
@@ -570,7 +571,7 @@
     // PDF does not support image filters, so render them on CPU.
     // Note that this rendering is done at "screen" resolution (100dpi), not
     // printer resolution.
-    // FIXME: It may be possible to express some filters natively using PDF
+    // TODO: It may be possible to express some filters natively using PDF
     // to improve quality and file size (http://skbug.com/3043)
 
     // TODO: should we return true if there is a colorfilter?
@@ -1040,36 +1041,123 @@
                           &content.entry()->fContent);
 }
 
-void SkPDFDevice::drawBitmapRect(const SkDraw& draw, const SkBitmap& bitmap,
-                                 const SkRect* src, const SkRect& dst,
-                                 const SkPaint& srcPaint, SkCanvas::SrcRectConstraint constraint) {
+void SkPDFDevice::drawBitmapRect(const SkDraw& draw,
+                                 const SkBitmap& bitmap,
+                                 const SkRect* src,
+                                 const SkRect& dst,
+                                 const SkPaint& srcPaint,
+                                 SkCanvas::SrcRectConstraint constraint) {
+    const SkImage* image = fCanon->bitmapToImage(bitmap);
+    if (!image) {
+        return;
+    }
+    // ownership of this image is retained by the canon.
+    this->drawImageRect(draw, image, src, dst, srcPaint, constraint);
+}
+
+void SkPDFDevice::drawBitmap(const SkDraw& d,
+                             const SkBitmap& bitmap,
+                             const SkMatrix& matrix,
+                             const SkPaint& srcPaint) {
     SkPaint paint = srcPaint;
     if (bitmap.isOpaque()) {
         replace_srcmode_on_opaque_paint(&paint);
     }
 
-    // TODO: this code path must be updated to respect the flags parameter
-    SkMatrix    matrix;
-    SkRect      bitmapBounds, tmpSrc, tmpDst;
-    SkBitmap    tmpBitmap;
+    if (d.fClip->isEmpty()) {
+        return;
+    }
 
-    bitmapBounds.isetWH(bitmap.width(), bitmap.height());
+    SkMatrix transform = matrix;
+    transform.postConcat(*d.fMatrix);
+    const SkImage* image = fCanon->bitmapToImage(bitmap);
+    if (!image) {
+        return;
+    }
+    this->internalDrawImage(transform, d.fClipStack, *d.fClip, image, nullptr,
+                            paint);
+}
+
+void SkPDFDevice::drawSprite(const SkDraw& d,
+                             const SkBitmap& bitmap,
+                             int x,
+                             int y,
+                             const SkPaint& srcPaint) {
+    SkPaint paint = srcPaint;
+    if (bitmap.isOpaque()) {
+        replace_srcmode_on_opaque_paint(&paint);
+    }
+
+    if (d.fClip->isEmpty()) {
+        return;
+    }
+
+    SkMatrix matrix;
+    matrix.setTranslate(SkIntToScalar(x), SkIntToScalar(y));
+    const SkImage* image = fCanon->bitmapToImage(bitmap);
+    if (!image) {
+        return;
+    }
+    this->internalDrawImage(matrix, d.fClipStack, *d.fClip, image, nullptr,
+                            paint);
+}
+
+void SkPDFDevice::drawImage(const SkDraw& draw,
+                            const SkImage* image,
+                            SkScalar x,
+                            SkScalar y,
+                            const SkPaint& srcPaint) {
+    SkPaint paint = srcPaint;
+    if (!image) {
+        return;
+    }
+    if (image->isOpaque()) {
+        replace_srcmode_on_opaque_paint(&paint);
+    }
+    if (draw.fClip->isEmpty()) {
+        return;
+    }
+    SkMatrix transform = SkMatrix::MakeTrans(x, y);
+    transform.postConcat(*draw.fMatrix);
+    this->internalDrawImage(transform, draw.fClipStack, *draw.fClip, image,
+                            nullptr, paint);
+}
+
+void SkPDFDevice::drawImageRect(const SkDraw& draw,
+                                const SkImage* image,
+                                const SkRect* src,
+                                const SkRect& dst,
+                                const SkPaint& srcPaint,
+                                SkCanvas::SrcRectConstraint constraint) {
+    if (!image) {
+        return;
+    }
+    if (draw.fClip->isEmpty()) {
+        return;
+    }
+    SkPaint paint = srcPaint;
+    if (image->isOpaque()) {
+        replace_srcmode_on_opaque_paint(&paint);
+    }
+    // TODO: this code path must be updated to respect the flags parameter
+    SkMatrix matrix;
+    SkRect tmpSrc, tmpDst;
+    SkRect imageBounds = SkRect::Make(image->bounds());
 
     // Compute matrix from the two rectangles
     if (src) {
         tmpSrc = *src;
     } else {
-        tmpSrc = bitmapBounds;
+        tmpSrc = imageBounds;
     }
     matrix.setRectToRect(tmpSrc, dst, SkMatrix::kFill_ScaleToFit);
 
-    const SkBitmap* bitmapPtr = &bitmap;
-
     // clip the tmpSrc to the bounds of the bitmap, and recompute dstRect if
     // needed (if the src was clipped). No check needed if src==null.
+    SkAutoTUnref<const SkImage> autoImageUnref;
     if (src) {
-        if (!bitmapBounds.contains(*src)) {
-            if (!tmpSrc.intersect(bitmapBounds)) {
+        if (!imageBounds.contains(*src)) {
+            if (!tmpSrc.intersect(imageBounds)) {
                 return; // nothing to draw
             }
             // recompute dst, based on the smaller tmpSrc
@@ -1078,14 +1166,14 @@
 
         // since we may need to clamp to the borders of the src rect within
         // the bitmap, we extract a subset.
-        // TODO: make sure this is handled in drawBitmap and remove from here.
         SkIRect srcIR;
         tmpSrc.roundOut(&srcIR);
-        if (!bitmap.extractSubset(&tmpBitmap, srcIR)) {
+
+        autoImageUnref.reset(image->newSubset(srcIR));
+        if (!autoImageUnref) {
             return;
         }
-        bitmapPtr = &tmpBitmap;
-
+        image = autoImageUnref;
         // Since we did an extract, we need to adjust the matrix accordingly
         SkScalar dx = 0, dy = 0;
         if (srcIR.fLeft > 0) {
@@ -1098,41 +1186,9 @@
             matrix.preTranslate(dx, dy);
         }
     }
-    this->drawBitmap(draw, *bitmapPtr, matrix, paint);
-}
-
-void SkPDFDevice::drawBitmap(const SkDraw& d, const SkBitmap& bitmap,
-                             const SkMatrix& matrix, const SkPaint& srcPaint) {
-    SkPaint paint = srcPaint;
-    if (bitmap.isOpaque()) {
-        replace_srcmode_on_opaque_paint(&paint);
-    }
-
-    if (d.fClip->isEmpty()) {
-        return;
-    }
-
-    SkMatrix transform = matrix;
-    transform.postConcat(*d.fMatrix);
-    this->internalDrawBitmap(transform, d.fClipStack, *d.fClip, bitmap, nullptr,
-                             paint);
-}
-
-void SkPDFDevice::drawSprite(const SkDraw& d, const SkBitmap& bitmap,
-                             int x, int y, const SkPaint& srcPaint) {
-    SkPaint paint = srcPaint;
-    if (bitmap.isOpaque()) {
-        replace_srcmode_on_opaque_paint(&paint);
-    }
-
-    if (d.fClip->isEmpty()) {
-        return;
-    }
-
-    SkMatrix matrix;
-    matrix.setTranslate(SkIntToScalar(x), SkIntToScalar(y));
-    this->internalDrawBitmap(matrix, d.fClipStack, *d.fClip, bitmap, nullptr,
-                             paint);
+    matrix.postConcat(*draw.fMatrix);
+    this->internalDrawImage(matrix, draw.fClipStack, *draw.fClip, image,
+                            nullptr, paint);
 }
 
 //  Create a PDF string. Maximum length (in bytes) is 65,535.
@@ -1435,7 +1491,9 @@
 SkStreamAsset* SkPDFDevice::content() const {
     SkDynamicMemoryWStream buffer;
     this->writeContent(&buffer);
-    return buffer.detachAsStream();
+    return buffer.bytesWritten() > 0
+        ? buffer.detachAsStream()
+        : new SkMemoryStream;
 }
 
 void SkPDFDevice::copyContentEntriesToData(ContentEntry* entry,
@@ -2070,42 +2128,59 @@
     return resourceIndex;
 }
 
-void SkPDFDevice::internalDrawBitmap(const SkMatrix& origMatrix,
-                                     const SkClipStack* clipStack,
-                                     const SkRegion& origClipRegion,
-                                     const SkBitmap& origBitmap,
-                                     const SkIRect* srcRect,
-                                     const SkPaint& paint) {
+static SkSize rect_to_size(const SkRect& r) {
+    return SkSize::Make(r.width(), r.height());
+}
+
+static const SkImage* color_filter(const SkImage* image,
+                                   SkColorFilter* colorFilter) {
+    SkAutoTUnref<SkSurface> surface(SkSurface::NewRaster(
+            SkImageInfo::MakeN32Premul(image->dimensions())));
+    if (!surface) {
+        return image;
+    }
+    SkCanvas* canvas = surface->getCanvas();
+    canvas->clear(SK_ColorTRANSPARENT);
+    SkPaint paint;
+    paint.setColorFilter(colorFilter);
+    canvas->drawImage(image, 0, 0, &paint);
+    canvas->flush();
+    return surface->newImageSnapshot();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+void SkPDFDevice::internalDrawImage(const SkMatrix& origMatrix,
+                                    const SkClipStack* clipStack,
+                                    const SkRegion& origClipRegion,
+                                    const SkImage* image,
+                                    const SkIRect* srcRect,
+                                    const SkPaint& paint) {
+    SkASSERT(image);
+    #ifdef SK_PDF_IMAGE_STATS
+    gDrawImageCalls.fetch_add(1);
+    #endif
     SkMatrix matrix = origMatrix;
     SkRegion perspectiveBounds;
     const SkRegion* clipRegion = &origClipRegion;
-    SkBitmap perspectiveBitmap;
-    const SkBitmap* bitmap = &origBitmap;
-    SkBitmap tmpSubsetBitmap;
+    SkAutoTUnref<const SkImage> autoImageUnref;
 
+    if (srcRect) {
+        autoImageUnref.reset(image->newSubset(*srcRect));
+        if (!autoImageUnref) {
+            return;
+        }
+        image = autoImageUnref;
+    }
     // Rasterize the bitmap using perspective in a new bitmap.
     if (origMatrix.hasPerspective()) {
         if (fRasterDpi == 0) {
             return;
         }
-        SkBitmap* subsetBitmap;
-        if (srcRect) {
-            if (!origBitmap.extractSubset(&tmpSubsetBitmap, *srcRect)) {
-               return;
-            }
-            subsetBitmap = &tmpSubsetBitmap;
-        } else {
-            subsetBitmap = &tmpSubsetBitmap;
-            *subsetBitmap = origBitmap;
-        }
-        srcRect = nullptr;
-
         // Transform the bitmap in the new space, without taking into
         // account the initial transform.
         SkPath perspectiveOutline;
-        perspectiveOutline.addRect(
-                SkRect::MakeWH(SkIntToScalar(subsetBitmap->width()),
-                               SkIntToScalar(subsetBitmap->height())));
+        SkRect imageBounds = SkRect::Make(image->bounds());
+        perspectiveOutline.addRect(imageBounds);
         perspectiveOutline.transform(origMatrix);
 
         // TODO(edisonn): perf - use current clip too.
@@ -2116,20 +2191,18 @@
         // account the initial transform.
         SkMatrix total = origMatrix;
         total.postConcat(fInitialTransform);
-        total.postScale(SkIntToScalar(fRasterDpi) /
-                            SkIntToScalar(DPI_FOR_RASTER_SCALE_ONE),
-                        SkIntToScalar(fRasterDpi) /
-                            SkIntToScalar(DPI_FOR_RASTER_SCALE_ONE));
+        SkScalar dpiScale = SkIntToScalar(fRasterDpi) /
+                            SkIntToScalar(DPI_FOR_RASTER_SCALE_ONE);
+        total.postScale(dpiScale, dpiScale);
+
         SkPath physicalPerspectiveOutline;
-        physicalPerspectiveOutline.addRect(
-                SkRect::MakeWH(SkIntToScalar(subsetBitmap->width()),
-                               SkIntToScalar(subsetBitmap->height())));
+        physicalPerspectiveOutline.addRect(imageBounds);
         physicalPerspectiveOutline.transform(total);
 
-        SkScalar scaleX = physicalPerspectiveOutline.getBounds().width() /
-                              bounds.width();
-        SkScalar scaleY = physicalPerspectiveOutline.getBounds().height() /
-                              bounds.height();
+        SkRect physicalPerspectiveBounds =
+                physicalPerspectiveOutline.getBounds();
+        SkScalar scaleX = physicalPerspectiveBounds.width() / bounds.width();
+        SkScalar scaleY = physicalPerspectiveBounds.height() / bounds.height();
 
         // TODO(edisonn): A better approach would be to use a bitmap shader
         // (in clamp mode) and draw a rect over the entire bounding box. Then
@@ -2138,14 +2211,15 @@
         // the image.  Avoiding alpha will reduce the pdf size and generation
         // CPU time some.
 
-        const int w = SkScalarCeilToInt(physicalPerspectiveOutline.getBounds().width());
-        const int h = SkScalarCeilToInt(physicalPerspectiveOutline.getBounds().height());
-        if (!perspectiveBitmap.tryAllocN32Pixels(w, h)) {
+        SkISize wh = rect_to_size(physicalPerspectiveBounds).toCeil();
+
+        SkAutoTUnref<SkSurface> surface(
+                SkSurface::NewRaster(SkImageInfo::MakeN32Premul(wh)));
+        if (!surface) {
             return;
         }
-        perspectiveBitmap.eraseColor(SK_ColorTRANSPARENT);
-
-        SkCanvas canvas(perspectiveBitmap);
+        SkCanvas* canvas = surface->getCanvas();
+        canvas->clear(SK_ColorTRANSPARENT);
 
         SkScalar deltaX = bounds.left();
         SkScalar deltaY = bounds.top();
@@ -2156,26 +2230,22 @@
 
         // Translate the draw in the new canvas, so we perfectly fit the
         // shape in the bitmap.
-        canvas.setMatrix(offsetMatrix);
-
-        canvas.drawBitmap(*subsetBitmap, SkIntToScalar(0), SkIntToScalar(0));
-
+        canvas->setMatrix(offsetMatrix);
+        canvas->drawImage(image, 0, 0, nullptr);
         // Make sure the final bits are in the bitmap.
-        canvas.flush();
+        canvas->flush();
 
         // In the new space, we use the identity matrix translated
         // and scaled to reflect DPI.
         matrix.setScale(1 / scaleX, 1 / scaleY);
         matrix.postTranslate(deltaX, deltaY);
 
-        perspectiveBounds.setRect(
-                SkIRect::MakeXYWH(SkScalarFloorToInt(bounds.x()),
-                                  SkScalarFloorToInt(bounds.y()),
-                                  SkScalarCeilToInt(bounds.width()),
-                                  SkScalarCeilToInt(bounds.height())));
+        perspectiveBounds.setRect(bounds.roundOut());
         clipRegion = &perspectiveBounds;
         srcRect = nullptr;
-        bitmap = &perspectiveBitmap;
+
+        autoImageUnref.reset(surface->newImageSnapshot());
+        image = autoImageUnref;
     }
 
     SkMatrix scaled;
@@ -2183,9 +2253,9 @@
     scaled.setScale(SK_Scalar1, -SK_Scalar1);
     scaled.postTranslate(0, SK_Scalar1);
     // Scale the image up from 1x1 to WxH.
-    SkIRect subset = bitmap->bounds();
-    scaled.postScale(SkIntToScalar(subset.width()),
-                     SkIntToScalar(subset.height()));
+    SkIRect subset = image->bounds();
+    scaled.postScale(SkIntToScalar(image->width()),
+                     SkIntToScalar(image->height()));
     scaled.postConcat(matrix);
     ScopedContentEntry content(this, clipStack, *clipRegion, scaled, paint);
     if (!content.entry() || (srcRect && !subset.intersect(*srcRect))) {
@@ -2193,8 +2263,7 @@
     }
     if (content.needShape()) {
         SkPath shape;
-        shape.addRect(SkRect::MakeWH(SkIntToScalar(subset.width()),
-                                     SkIntToScalar(subset.height())));
+        shape.addRect(SkRect::Make(subset));
         shape.transform(matrix);
         content.setShape(shape);
     }
@@ -2202,32 +2271,25 @@
         return;
     }
 
-    SkBitmap subsetBitmap;
-    if (!bitmap->extractSubset(&subsetBitmap, subset)) {
-        return;
-    }
     if (SkColorFilter* colorFilter = paint.getColorFilter()) {
         // TODO(http://skbug.com/4378): implement colorfilter on other
-        // draw calls.  This code here works for all drawBitmap*()
-        // calls amd ImageFilters (which rasterize a layer on this
-        // backend).  Fortuanely, this seems to be how Chromium
-        // impements most color-filters.
-        SkBitmap tmp;
-        if (subsetBitmap.copyTo(&tmp, kN32_SkColorType)) {
-            SkAutoLockPixels autoLockPixelsTmp(tmp);
-            for (int y = 0; y < tmp.height();  ++y) {
-                SkPMColor* pixels = tmp.getAddr32(0, y);
-                colorFilter->filterSpan(pixels, tmp.width(), pixels);
-            }
-            tmp.setImmutable();
-            subsetBitmap = tmp;
+        // draw calls.  This code here works for all
+        // drawBitmap*()/drawImage*() calls amd ImageFilters (which
+        // rasterize a layer on this backend).  Fortuanely, this seems
+        // to be how Chromium impements most color-filters.
+        autoImageUnref.reset(color_filter(image, colorFilter));
+        image = autoImageUnref;
+        // TODO(halcanary): de-dupe this by caching filtered images.
+        // (maybe in the resource cache?)
+    }
+    SkAutoTUnref<SkPDFObject> pdfimage(fCanon->findPDFBitmap(image));
+    if (!pdfimage) {
+        pdfimage.reset(SkPDFCreateBitmapObject(image));
+        if (!pdfimage) {
+            return;
         }
+        fCanon->addPDFBitmap(image->uniqueID(), pdfimage);
     }
-    SkAutoTUnref<SkPDFObject> image(SkPDFBitmap::Create(fCanon, subsetBitmap));
-    if (!image) {
-        return;
-    }
-
-    SkPDFUtils::DrawFormXObject(this->addXObjectResource(image.get()),
+    SkPDFUtils::DrawFormXObject(this->addXObjectResource(SkRef(pdfimage.get())),
                                 &content.entry()->fContent);
 }
diff --git a/src/pdf/SkPDFDevice.h b/src/pdf/SkPDFDevice.h
index 45aba29..75447f9 100644
--- a/src/pdf/SkPDFDevice.h
+++ b/src/pdf/SkPDFDevice.h
@@ -99,6 +99,17 @@
                     const SkMatrix& matrix, const SkPaint&) override;
     void drawSprite(const SkDraw&, const SkBitmap& bitmap, int x, int y,
                     const SkPaint& paint) override;
+    void drawImage(const SkDraw&,
+                   const SkImage*,
+                   SkScalar x,
+                   SkScalar y,
+                   const SkPaint&) override;
+    void drawImageRect(const SkDraw&,
+                       const SkImage*,
+                       const SkRect* src,
+                       const SkRect& dst,
+                       const SkPaint&,
+                       SkCanvas::SrcRectConstraint) override;
     void drawText(const SkDraw&, const void* text, size_t len,
                   SkScalar x, SkScalar y, const SkPaint&) override;
     void drawPosText(const SkDraw&, const void* text, size_t len,
@@ -279,12 +290,12 @@
     int getFontResourceIndex(SkTypeface* typeface, uint16_t glyphID);
 
     void internalDrawPaint(const SkPaint& paint, ContentEntry* contentEntry);
-    void internalDrawBitmap(const SkMatrix& matrix,
-                            const SkClipStack* clipStack,
-                            const SkRegion& clipRegion,
-                            const SkBitmap& bitmap,
-                            const SkIRect* srcRect,
-                            const SkPaint& paint);
+    void internalDrawImage(const SkMatrix& matrix,
+                           const SkClipStack* clipStack,
+                           const SkRegion& clipRegion,
+                           const SkImage* image,
+                           const SkIRect* srcRect,
+                           const SkPaint& paint);
 
     /** Helper method for copyContentToData. It is responsible for copying the
      *  list of content entries |entry| to |data|.
diff --git a/src/pdf/SkPDFTypes.cpp b/src/pdf/SkPDFTypes.cpp
index 43361fc..4cd48f4 100644
--- a/src/pdf/SkPDFTypes.cpp
+++ b/src/pdf/SkPDFTypes.cpp
@@ -506,3 +506,17 @@
     return *objectNumberFound;
 }
 
+#ifdef SK_PDF_IMAGE_STATS
+SkAtomic<int> gDrawImageCalls(0);
+SkAtomic<int> gJpegImageObjects(0);
+SkAtomic<int> gRegularImageObjects(0);
+
+void SkPDFImageDumpStats() {
+    SkDebugf("\ntotal PDF drawImage/drawBitmap calls: %d\n"
+             "total PDF jpeg images: %d\n"
+             "total PDF regular images: %d\n",
+             gDrawImageCalls.load(),
+             gJpegImageObjects.load(),
+             gRegularImageObjects.load());
+}
+#endif // SK_PDF_IMAGE_STATS
diff --git a/src/pdf/SkPDFTypes.h b/src/pdf/SkPDFTypes.h
index 60d5114..ec527fc 100644
--- a/src/pdf/SkPDFTypes.h
+++ b/src/pdf/SkPDFTypes.h
@@ -21,6 +21,10 @@
 class SkPDFObject;
 class SkPDFSubstituteMap;
 
+#ifdef SK_PDF_IMAGE_STATS
+#include "SkAtomics.h"
+#endif
+
 /** \class SkPDFObject
 
     A PDF Object is the base class for primitive elements in a PDF file.  A
@@ -382,4 +386,11 @@
     SkTHashMap<SkPDFObject*, SkPDFObject*> fSubstituteMap;
 };
 
+#ifdef SK_PDF_IMAGE_STATS
+extern SkAtomic<int> gDrawImageCalls;
+extern SkAtomic<int> gJpegImageObjects;
+extern SkAtomic<int> gRegularImageObjects;
+extern void SkPDFImageDumpStats();
+#endif // SK_PDF_IMAGE_STATS
+
 #endif
diff --git a/tests/PDFJpegEmbedTest.cpp b/tests/PDFJpegEmbedTest.cpp
index 812fd3b..5185eb7 100644
--- a/tests/PDFJpegEmbedTest.cpp
+++ b/tests/PDFJpegEmbedTest.cpp
@@ -62,7 +62,7 @@
     if (!mandrillData || !cmykData) {
         return;
     }
-
+    ////////////////////////////////////////////////////////////////////////////
     SkDynamicMemoryWStream pdf;
     SkAutoTUnref<SkDocument> document(SkDocument::CreatePDF(&pdf));
     SkCanvas* canvas = document->beginPage(642, 1028);
@@ -86,6 +86,30 @@
     // This JPEG uses a nonstandard colorspace - it can not be
     // embedded into the PDF directly.
     REPORTER_ASSERT(r, !is_subset_of(cmykData, pdfData));
+    ////////////////////////////////////////////////////////////////////////////
+    pdf.reset();
+    document.reset(SkDocument::CreatePDF(&pdf));
+    canvas = document->beginPage(642, 1028);
+
+    canvas->clear(SK_ColorLTGRAY);
+
+    SkAutoTUnref<SkImage> im1(SkImage::NewFromEncoded(mandrillData));
+    canvas->drawImage(im1, 65.0, 0.0, nullptr);
+    SkAutoTUnref<SkImage> im2(SkImage::NewFromEncoded(cmykData));
+    canvas->drawImage(im2, 0.0, 512.0, nullptr);
+
+    canvas->flush();
+    document->endPage();
+    document->close();
+    pdfData.reset(pdf.copyToData());
+    SkASSERT(pdfData);
+    pdf.reset();
+
+    REPORTER_ASSERT(r, is_subset_of(mandrillData, pdfData));
+
+    // This JPEG uses a nonstandard colorspace - it can not be
+    // embedded into the PDF directly.
+    REPORTER_ASSERT(r, !is_subset_of(cmykData, pdfData));
 }
 
 #include "SkJpegInfo.h"
@@ -121,7 +145,7 @@
         }
         if (r->verbose()) {
             SkDebugf("\nJpegIdentification: %s [%d x %d]\n", kTests[i].path,
-                     info.fWidth, info.fHeight);
+                     info.fSize.width(), info.fSize.height());
         }
     }
 }