Index: src/pdf/SkPDFBitmap.cpp |
diff --git a/src/pdf/SkPDFBitmap.cpp b/src/pdf/SkPDFBitmap.cpp |
new file mode 100644 |
index 0000000000000000000000000000000000000000..c2d3cfd2d56124adbcd2888bfa1e6b3baabf25e7 |
--- /dev/null |
+++ b/src/pdf/SkPDFBitmap.cpp |
@@ -0,0 +1,258 @@ |
+/* |
+ * Copyright 2015 Google Inc. |
+ * |
+ * Use of this source code is governed by a BSD-style license that can be |
+ * found in the LICENSE file. |
+ */ |
+ |
+#include "SkColorPriv.h" |
+#include "SkDeflateWStream.h" |
+#include "SkPDFBitmap.h" |
+#include "SkPDFCanon.h" |
+#include "SkPDFCatalog.h" |
+#include "SkPDFDocument.h" |
+#include "SkStream.h" |
+#include "SkUnPreMultiply.h" |
+ |
+//////////////////////////////////////////////////////////////////////////////// |
+ |
+static void pdf_stream_begin(SkWStream* stream) { |
+ static const char streamBegin[] = " stream\n"; |
+ stream->write(streamBegin, strlen(streamBegin)); |
+} |
+ |
+static void pdf_stream_end(SkWStream* stream) { |
+ static const char streamEnd[] = "\nendstream"; |
+ stream->write(streamEnd, strlen(streamEnd)); |
+} |
+ |
+static size_t pixel_count(const SkBitmap& bm) { |
+ return SkToSizeT(bm.width()) * SkToSizeT(bm.height()); |
+} |
+ |
+static bool skip_compression(SkPDFDocument::Flags flag) { |
+ return SkToBool(flag & SkPDFDocument::kFavorSpeedOverSize_Flags); |
+} |
+ |
+static bool is_better_compressed(const SkStreamAsset* asset, |
+ const SkBitmap& bitmap) { |
+ return asset->getLength() + strlen("/Filter /FlateDecode\n") |
+ <= pixel_count(bitmap); |
+} |
+ |
+ |
+ |
+static void pmcolor_to_rgb24(const SkBitmap& bm, SkWStream* out) { |
+ SkASSERT(kN32_SkColorType == bm.colorType()); |
+ size_t scanlineLength = 3 * bm.width(); |
+ SkAutoTMalloc<uint8_t> scanline(scanlineLength); |
+ for (int y = 0; y < bm.height(); ++y) { |
+ uint8_t* dst = scanline.get(); |
+ const SkPMColor* src = bm.getAddr32(0, y); |
+ for (int x = 0; x < bm.width(); ++x) { |
+ SkPMColor pmColor = *src++; |
+ uint32_t s = SkUnPreMultiply::GetScale(SkGetPackedA32(pmColor)); |
+ *dst++ = SkUnPreMultiply::ApplyScale(s, SkGetPackedR32(pmColor)); |
+ *dst++ = SkUnPreMultiply::ApplyScale(s, SkGetPackedG32(pmColor)); |
+ *dst++ = SkUnPreMultiply::ApplyScale(s, SkGetPackedB32(pmColor)); |
+ } |
+ out->write(scanline.get(), scanlineLength); |
+ } |
+} |
+ |
+static void pmcolor_alpha_to_a8(const SkBitmap& bm, SkWStream* out) { |
+ SkASSERT(kN32_SkColorType == bm.colorType()); |
+ size_t scanlineLength = bm.width(); |
+ SkAutoTMalloc<uint8_t> scanline(scanlineLength); |
+ for (int y = 0; y < bm.height(); ++y) { |
+ uint8_t* dst = scanline.get(); |
+ const SkPMColor* src = bm.getAddr32(0, y); |
+ for (int x = 0; x < bm.width(); ++x) { |
+ *dst++ = SkGetPackedA32(*src++); |
+ } |
+ out->write(scanline.get(), scanlineLength); |
+ } |
+} |
+ |
+//////////////////////////////////////////////////////////////////////////////// |
+ |
+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*, SkPDFCatalog*) SK_OVERRIDE; |
+ void addResources(SkTSet<SkPDFObject*>*, SkPDFCatalog*) const SK_OVERRIDE {} |
+ |
+private: |
+ const SkBitmap fBitmap; |
+ void emitFast(SkWStream*, SkPDFCatalog*) const; |
+ void emitDict(SkWStream*, SkPDFCatalog*, size_t, bool) const; |
+}; |
+ |
+void PDFAlphaBitmap::emitObject(SkWStream* stream, SkPDFCatalog* catalog) { |
+ SkASSERT(kN32_SkColorType == fBitmap.colorType()); |
+ SkAutoLockPixels autoLockPixels(fBitmap); |
+ SkASSERT(fBitmap.getPixels()); |
+ |
+#ifdef SK_HAS_ZLIB |
+ if (skip_compression(catalog->getDocumentFlags())) { |
+ this->emitFast(stream, catalog); |
+ return; |
+ } |
+ |
+ // Write to a temporary buffer to get the compressed length. |
+ SkDynamicMemoryWStream buffer; |
+ SkDeflateWStream deflateWStream(&buffer); |
+ pmcolor_alpha_to_a8(fBitmap, &deflateWStream); |
+ deflateWStream.finalize(); // call before detachAsStream(). |
+ SkAutoTDelete<SkStreamAsset> asset(buffer.detachAsStream()); |
+ |
+ if (is_better_compressed(asset, fBitmap)) { |
+ this->emitDict(stream, catalog, asset->getLength(), /*deflate=*/true); |
+ pdf_stream_begin(stream); |
+ stream->writeStream(asset.get(), asset->getLength()); |
+ pdf_stream_end(stream); |
+ } else { |
+ this->emitFast(stream, catalog); |
+ } |
+#else |
+ this->emitFast(stream, catalog); |
+#endif |
+} |
+void PDFAlphaBitmap::emitFast(SkWStream* stream, |
+ SkPDFCatalog* catalog) const { |
+ this->emitDict(stream, catalog, pixel_count(fBitmap), /*deflate=*/false); |
+ pdf_stream_begin(stream); |
+ pmcolor_alpha_to_a8(fBitmap, stream); |
+ pdf_stream_end(stream); |
+} |
+ |
+void PDFAlphaBitmap::emitDict(SkWStream* stream, |
+ SkPDFCatalog* catalog, |
+ size_t length, |
+ bool deflate) const { |
+ 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.insertInt("Length", length); |
+ if (deflate) { |
+ pdfDict.insertName("Filter", "FlateDecode"); |
+ } |
+ pdfDict.emitObject(stream, catalog); |
+} |
+} // namespace |
+ |
+//////////////////////////////////////////////////////////////////////////////// |
+ |
+void SkPDFBitmap::addResources(SkTSet<SkPDFObject*>* resourceSet, |
+ SkPDFCatalog* catalog) const { |
+ if (fSMask.get()) { |
+ resourceSet->add(fSMask.get()); |
+ } |
+} |
+ |
+void SkPDFBitmap::emitObject(SkWStream* stream, SkPDFCatalog* catalog) { |
+ SkASSERT(kN32_SkColorType == fBitmap.colorType()); |
+ SkAutoLockPixels autoLockPixels(fBitmap); |
+ SkASSERT(fBitmap.getPixels()); |
+ |
+#ifdef SK_HAS_ZLIB |
+ if (skip_compression(catalog->getDocumentFlags())) { |
+ this->emitFast(stream, catalog); |
+ return; |
+ } |
+ // Write to a temporary buffer to get the compressed length. |
+ SkDynamicMemoryWStream buffer; |
+ SkDeflateWStream deflateWStream(&buffer); |
+ pmcolor_to_rgb24(fBitmap, &deflateWStream); |
+ deflateWStream.finalize(); // call before detachAsStream(). |
+ SkAutoTDelete<SkStreamAsset> asset(buffer.detachAsStream()); |
+ |
+ if (is_better_compressed(asset, fBitmap)) { |
+ this->emitDict(stream, catalog, asset->getLength(), /*deflate=*/true); |
+ pdf_stream_begin(stream); |
+ stream->writeStream(asset.get(), asset->getLength()); |
+ pdf_stream_end(stream); |
+ } else { |
+ // probably a very small bitmap: output_pmcolor() is cheap. |
+ this->emitFast(stream, catalog); |
+ } |
+#else |
+ this->emitFast(stream, catalog); |
+#endif |
+} |
+ |
+void SkPDFBitmap::emitFast(SkWStream* stream, SkPDFCatalog* catalog) const { |
+ this->emitDict(stream, catalog, 3 * pixel_count(fBitmap), |
+ /*deflate=*/false); |
+ pdf_stream_begin(stream); |
+ pmcolor_to_rgb24(fBitmap, stream); |
+ pdf_stream_end(stream); |
+} |
+ |
+void SkPDFBitmap::emitDict(SkWStream* stream, |
+ SkPDFCatalog* catalog, |
+ size_t length, |
+ bool deflate) const { |
+ SkPDFDict pdfDict("Xobject"); |
+ pdfDict.insertName("Subtype", "Image"); |
+ pdfDict.insertInt("Width", fBitmap.width()); |
+ pdfDict.insertInt("Height", fBitmap.height()); |
+ pdfDict.insertName("ColorSpace", "DeviceRGB"); |
+ pdfDict.insertInt("BitsPerComponent", 8); |
+ if (fSMask) { |
+ pdfDict.insert("SMask", new SkPDFObjRef(fSMask))->unref(); |
+ } |
+ pdfDict.insertInt("Length", length); |
+ if (deflate) { |
+ pdfDict.insertName("Filter", "FlateDecode"); |
+ } |
+ pdfDict.emitObject(stream, catalog); |
+} |
+ |
+SkPDFBitmap::SkPDFBitmap(const SkBitmap& bm, SkPDFObject* smask) |
+ : fBitmap(bm), fSMask(smask) {} |
+ |
+SkPDFBitmap::~SkPDFBitmap() { |
+ SkAutoMutexAcquire autoMutexAcquire(SkPDFCanon::GetBitmapMutex()); |
+ SkPDFCanon::GetCanon().removeBitmap(this); |
+} |
+ |
+//////////////////////////////////////////////////////////////////////////////// |
+ |
+// TODO(halcanary): SkPDFBitmap::Create should take a SkPDFCanon* parameter. |
+SkPDFBitmap* SkPDFBitmap::Create(const SkBitmap& bitmap, |
+ const SkIRect& subset) { |
+ if (kN32_SkColorType != bitmap.colorType()) { |
+ // TODO(halcanary): support other colortypes. |
+ return NULL; |
+ } |
+ SkBitmap bm; |
+ // Should extractSubset be done by the SkPDFDevice? |
+ if (!bitmap.extractSubset(&bm, subset)) { |
+ return NULL; |
+ } |
+ if (bm.drawsNothing()) { |
+ return NULL; |
+ } |
+ SkAutoMutexAcquire autoMutexAcquire(SkPDFCanon::GetBitmapMutex()); |
+ SkPDFCanon& canon = SkPDFCanon::GetCanon(); |
+ SkPDFBitmap* pdfBitmap = canon.findBitmap(bm); |
+ if (pdfBitmap) { |
+ return SkRef(pdfBitmap); |
+ } |
+ SkPDFObject* smask = NULL; |
+ if (!bitmap.isOpaque() && !SkBitmap::ComputeIsOpaque(bitmap)) { |
+ // PDFAlphaBitmap do not get directly canonicalized (they |
+ // are refed by the SkPDFBitmap). |
+ smask = SkNEW_ARGS(PDFAlphaBitmap, (bm)); |
+ } |
+ pdfBitmap = SkNEW_ARGS(SkPDFBitmap, (bm, smask)); |
+ canon.addBitmap(pdfBitmap); |
+ return pdfBitmap; |
+} |