Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(13)

Unified Diff: src/pdf/SkPDFBitmap.cpp

Issue 918813002: PDF: Add (low-memory) SkPDFBitmap class (Closed) Base URL: https://skia.googlesource.com/skia.git@master
Patch Set: mutable bitmaps get copied Created 5 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « src/pdf/SkPDFBitmap.h ('k') | src/pdf/SkPDFCanon.h » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: src/pdf/SkPDFBitmap.cpp
diff --git a/src/pdf/SkPDFBitmap.cpp b/src/pdf/SkPDFBitmap.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..26410d7fa3f0342bb4494ad4ce8fe2fdaa6ba94c
--- /dev/null
+++ b/src/pdf/SkPDFBitmap.cpp
@@ -0,0 +1,333 @@
+/*
+ * 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) {
+#ifndef SK_NO_FLATE
+ return SkToBool(flag & SkPDFDocument::kFavorSpeedOverSize_Flags);
+#else
+ return true;
+#endif // SK_NO_FLATE
+}
+
+// write a single byte to a stream n times.
+static void fill_stream(SkWStream* out, char value, size_t n) {
+ char buffer[4096];
+ memset(buffer, value, sizeof(buffer));
+ while (n) {
+ size_t k = SkTMin(n, sizeof(buffer));
+ out->write(buffer, k);
+ n -= k;
+ }
+}
+
+static SkPMColor get_pmcolor_neighbor_avg_color(const SkBitmap& bitmap,
+ int xOrig,
+ int yOrig) {
+ SkASSERT(kN32_SkColorType == bitmap.colorType());
+ SkASSERT(bitmap.getPixels());
+ uint8_t count = 0;
+ unsigned r = 0;
+ unsigned g = 0;
+ unsigned b = 0;
+ for (int y = yOrig - 1; y <= yOrig + 1; ++y) {
+ if (y < 0 || y >= bitmap.height()) {
+ continue;
+ }
+ uint32_t* src = bitmap.getAddr32(0, y);
+ for (int x = xOrig - 1; x <= xOrig + 1; ++x) {
+ if (x < 0 || x >= bitmap.width()) {
+ continue;
+ }
+ SkPMColor pmColor = src[x];
+ U8CPU alpha = SkGetPackedA32(pmColor);
+ if (alpha != SK_AlphaTRANSPARENT) {
+ uint32_t s = SkUnPreMultiply::GetScale(alpha);
+ r += SkUnPreMultiply::ApplyScale(s, SkGetPackedR32(pmColor));
+ g += SkUnPreMultiply::ApplyScale(s, SkGetPackedG32(pmColor));
+ b += SkUnPreMultiply::ApplyScale(s, SkGetPackedB32(pmColor));
+ ++count;
+ }
+ }
+ }
+ if (count == 0) {
+ return SkPackARGB32NoCheck(SK_AlphaOPAQUE, 0, 0, 0);
+ } else {
+ return SkPackARGB32NoCheck(
+ SK_AlphaOPAQUE, r / count, g / count, b / count);
+ }
+}
+
+static void pmcolor_to_rgb24(const SkBitmap& bm, SkWStream* out) {
+ SkASSERT(kN32_SkColorType == bm.colorType());
+ if (!bm.getPixels()) {
+ fill_stream(out, '\xFF', 3 * pixel_count(bm));
+ return;
+ }
+ 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 color = *src++;
+ U8CPU alpha = SkGetPackedA32(color);
+ if (alpha != SK_AlphaTRANSPARENT) {
+ uint32_t s = SkUnPreMultiply::GetScale(alpha);
+ *dst++ = SkUnPreMultiply::ApplyScale(s, SkGetPackedR32(color));
+ *dst++ = SkUnPreMultiply::ApplyScale(s, SkGetPackedG32(color));
+ *dst++ = SkUnPreMultiply::ApplyScale(s, SkGetPackedB32(color));
+ } else {
+ /* It is necessary to average the color component of
+ transparent pixels with their surrounding neighbors
+ since the PDF renderer may separately re-sample the
+ alpha and color channels when the image is not
+ displayed at its native resolution. Since an alpha
+ of zero gives no information about the color
+ component, the pathological case is a white image
+ with sharp transparency bounds - the color channel
+ goes to black, and the should-be-transparent pixels
+ are rendered as grey because of the separate soft
+ mask and color resizing. e.g.: gm/bitmappremul.cpp */
+ color = get_pmcolor_neighbor_avg_color(bm, x, y);
+ *dst++ = SkGetPackedR32(color);
+ *dst++ = SkGetPackedG32(color);
+ *dst++ = SkGetPackedB32(color);
+ }
+ }
+ out->write(scanline.get(), scanlineLength);
+ }
+}
+
+static void pmcolor_alpha_to_a8(const SkBitmap& bm, SkWStream* out) {
+ SkASSERT(kN32_SkColorType == bm.colorType());
+ if (!bm.getPixels()) {
+ fill_stream(out, '\xFF', pixel_count(bm));
+ return;
+ }
+ 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 emitDict(SkWStream*, SkPDFCatalog*, size_t, bool) const;
+};
+
+void PDFAlphaBitmap::emitObject(SkWStream* stream, SkPDFCatalog* catalog) {
+ SkAutoLockPixels autoLockPixels(fBitmap);
+
+ if (skip_compression(catalog->getDocumentFlags())) {
+ this->emitDict(stream, catalog, pixel_count(fBitmap),
+ /*deflate=*/false);
+ pdf_stream_begin(stream);
+ pmcolor_alpha_to_a8(fBitmap, stream);
+ pdf_stream_end(stream);
+ return;
+ }
+#ifndef SK_NO_FLATE
+ // 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());
+
+ this->emitDict(stream, catalog, asset->getLength(), /*deflate=*/true);
+ pdf_stream_begin(stream);
+ stream->writeStream(asset.get(), asset->getLength());
+ pdf_stream_end(stream);
+#endif // SK_NO_FLATE
+}
+
+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);
+ if (deflate) {
+ pdfDict.insertName("Filter", "FlateDecode");
+ }
+ pdfDict.insertInt("Length", length);
+ 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) {
+ SkAutoLockPixels autoLockPixels(fBitmap);
+
+ if (skip_compression(catalog->getDocumentFlags())) {
+ this->emitDict(stream, catalog, 3 * pixel_count(fBitmap),
+ /*deflate=*/false);
+ pdf_stream_begin(stream);
+ pmcolor_to_rgb24(fBitmap, stream);
+ pdf_stream_end(stream);
+ return;
+ }
+#ifndef SK_NO_FLATE
+ // 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());
+
+ this->emitDict(stream, catalog, asset->getLength(), /*deflate=*/true);
+ pdf_stream_begin(stream);
+ stream->writeStream(asset.get(), asset->getLength());
+ pdf_stream_end(stream);
+#endif // SK_NO_FLATE
+}
+
+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();
+ }
+ if (deflate) {
+ pdfDict.insertName("Filter", "FlateDecode");
+ }
+ pdfDict.insertInt("Length", length);
+ 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);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+static bool is_transparent(const SkBitmap& bm) {
+ SkAutoLockPixels autoLockPixels(bm);
+ if (NULL == bm.getPixels()) {
+ return true;
+ }
+ SkASSERT(kN32_SkColorType == bm.colorType());
+ for (int y = 0; y < bm.height(); ++y) {
+ U8CPU alpha = 0;
+ const SkPMColor* src = bm.getAddr32(0, y);
+ for (int x = 0; x < bm.width(); ++x) {
+ alpha |= SkGetPackedA32(*src++);
+ }
+ if (alpha) {
+ return false;
+ }
+ }
+ return true;
+}
+
+// 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;
+ }
+ if (!bm.isImmutable()) {
+ SkBitmap copy;
+ if (!bm.copyTo(&copy)) {
+ return NULL;
+ }
+ copy.setImmutable();
+ bm = copy;
+ }
+
+ SkAutoMutexAcquire autoMutexAcquire(SkPDFCanon::GetBitmapMutex());
+ SkPDFCanon& canon = SkPDFCanon::GetCanon();
+ SkPDFBitmap* pdfBitmap = canon.findBitmap(bm);
+ if (pdfBitmap) {
+ return SkRef(pdfBitmap);
+ }
+ SkPDFObject* smask = NULL;
+ if (!bm.isOpaque() && !SkBitmap::ComputeIsOpaque(bm)) {
+ if (is_transparent(bm)) {
+ return NULL;
+ }
+ // PDFAlphaBitmaps 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;
+}
« no previous file with comments | « src/pdf/SkPDFBitmap.h ('k') | src/pdf/SkPDFCanon.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698