diff options
-rw-r--r-- | dm/DM.cpp | 7 | ||||
-rw-r--r-- | gm/repeated_bitmap.cpp | 62 | ||||
-rw-r--r-- | gyp/pdf.gyp | 1 | ||||
-rw-r--r-- | src/pdf/SkJpegInfo.cpp | 4 | ||||
-rw-r--r-- | src/pdf/SkJpegInfo.h | 5 | ||||
-rw-r--r-- | src/pdf/SkPDFBitmap.cpp | 260 | ||||
-rw-r--r-- | src/pdf/SkPDFBitmap.h | 27 | ||||
-rw-r--r-- | src/pdf/SkPDFCanon.cpp | 33 | ||||
-rw-r--r-- | src/pdf/SkPDFCanon.h | 26 | ||||
-rw-r--r-- | src/pdf/SkPDFDevice.cpp | 330 | ||||
-rw-r--r-- | src/pdf/SkPDFDevice.h | 23 | ||||
-rw-r--r-- | src/pdf/SkPDFTypes.cpp | 14 | ||||
-rw-r--r-- | src/pdf/SkPDFTypes.h | 11 | ||||
-rw-r--r-- | tests/PDFJpegEmbedTest.cpp | 28 |
14 files changed, 477 insertions, 354 deletions
@@ -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 @@ int dm_main() { 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 dc2a2aab07..39eeb4e6e9 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 c294986c79..ee304880f5 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 455b4aa2c0..3c52a102db 100644 --- a/src/pdf/SkJpegInfo.cpp +++ b/src/pdf/SkJpegInfo.cpp @@ -107,8 +107,8 @@ bool SkIsJFIF(const SkData* skdata, SkJFIFInfo* info) { 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 178d648a1a..1b03e45b10 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 de43221630..1978040ec9 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 @@ static void bitmap_alpha_to_a8(const SkBitmap& bitmap, SkWStream* out) { } } -//////////////////////////////////////////////////////////////////////////////// - -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 @@ static SkPDFArray* make_indexed_color_space(const SkColorTable* table) { 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 @@ void PDFDefaultBitmap::emitObject(SkWStream* stream, //////////////////////////////////////////////////////////////////////////////// -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 @@ void PDFJpegBitmap::emitObject(SkWStream* stream, 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 @@ void PDFJpegBitmap::emitObject(SkWStream* stream, //////////////////////////////////////////////////////////////////////////////// -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 58d91bec88..d931331431 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 6cc3995412..4fdaa0db62 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 @@ void SkPDFCanon::reset() { 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 @@ void SkPDFCanon::addGraphicState(const SkPDFGraphicState* state) { //////////////////////////////////////////////////////////////////////////////// -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::addPDFBitmap(uint32_t imageUniqueID, SkPDFObject* pdfBitmap) { + fPDFBitmapMap.set(imageUniqueID, SkRef(pdfBitmap)); } -void SkPDFCanon::addBitmap(SkPDFBitmap* pdfBitmap) { - fBitmapRecords.push(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 2ad5a229aa..a55024d611 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 @@ public: 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 @@ private: }; 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 461ae7f9e2..8d53b9918c 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 @@ static bool not_supported_for_layers(const SkPaint& layerPaint) { // 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 @@ void SkPDFDevice::drawPath(const SkDraw& d, &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 @@ void SkPDFDevice::drawBitmapRect(const SkDraw& draw, const SkBitmap& bitmap, // 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 @@ void SkPDFDevice::drawBitmapRect(const SkDraw& draw, const SkBitmap& bitmap, 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 @@ SkPDFArray* SkPDFDevice::copyMediaBox() const { 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 @@ int SkPDFDevice::getFontResourceIndex(SkTypeface* typeface, uint16_t glyphID) { 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 @@ void SkPDFDevice::internalDrawBitmap(const SkMatrix& origMatrix, // 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 @@ void SkPDFDevice::internalDrawBitmap(const SkMatrix& origMatrix, // 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 @@ void SkPDFDevice::internalDrawBitmap(const SkMatrix& origMatrix, // 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 @@ void SkPDFDevice::internalDrawBitmap(const SkMatrix& origMatrix, 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 @@ void SkPDFDevice::internalDrawBitmap(const SkMatrix& origMatrix, } 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 @@ void SkPDFDevice::internalDrawBitmap(const SkMatrix& origMatrix, 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 45aba29886..75447f9be2 100644 --- a/src/pdf/SkPDFDevice.h +++ b/src/pdf/SkPDFDevice.h @@ -99,6 +99,17 @@ public: 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 @@ private: 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 43361fcf44..4cd48f4757 100644 --- a/src/pdf/SkPDFTypes.cpp +++ b/src/pdf/SkPDFTypes.cpp @@ -506,3 +506,17 @@ int32_t SkPDFObjNumMap::getObjectNumber(SkPDFObject* obj) const { 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 60d511409f..ec527fc932 100644 --- a/src/pdf/SkPDFTypes.h +++ b/src/pdf/SkPDFTypes.h @@ -21,6 +21,10 @@ class SkPDFObjNumMap; 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 @@ private: 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 812fd3b2c3..5185eb7bd4 100644 --- a/tests/PDFJpegEmbedTest.cpp +++ b/tests/PDFJpegEmbedTest.cpp @@ -62,7 +62,7 @@ DEF_TEST(PDFJpegEmbedTest, r) { if (!mandrillData || !cmykData) { return; } - + //////////////////////////////////////////////////////////////////////////// SkDynamicMemoryWStream pdf; SkAutoTUnref<SkDocument> document(SkDocument::CreatePDF(&pdf)); SkCanvas* canvas = document->beginPage(642, 1028); @@ -86,6 +86,30 @@ DEF_TEST(PDFJpegEmbedTest, r) { // 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 @@ DEF_TEST(JpegIdentification, r) { } if (r->verbose()) { SkDebugf("\nJpegIdentification: %s [%d x %d]\n", kTests[i].path, - info.fWidth, info.fHeight); + info.fSize.width(), info.fSize.height()); } } } |