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, ©);
- 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());
}
}
}