PDF: Add (low-memory) SkPDFBitmap class

Also: Add SkDeflateWStream and associated unit tests.

SkPDFBitmap is a replacement for SkPDFImage.  As of now, it only
supports 8888 bitmaps (the most common case).

SkPDFBitmap takes very little extra memory (aside from refing the
bitmap's pixels), and its emitObject() does not cache any data.

The SkPDFBitmap::Create function will check the canon for duplicates.
This can reduce the size of the output PDF.

Motivation:  this gives another ~40% decrease in PDF memory overhead

TODO: Support other ColorTypes and scrap SkPDFImage.

BUG=skia:3030

Review URL: https://codereview.chromium.org/918813002
This commit is contained in:
halcanary 2015-02-18 11:29:56 -08:00 committed by Commit bot
parent 0babd3c619
commit 1b5c604d9d
14 changed files with 676 additions and 101 deletions

31
gm/repeated_bitmap.cpp Normal file
View File

@ -0,0 +1,31 @@
/*
* 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 "gm.h"
#include "sk_tool_utils.h"
#include "Resources.h"
DEF_SIMPLE_GM(repeated_bitmap, canvas, 576, 576) {
sk_tool_utils::draw_checkerboard(canvas, 0xFF999999, SK_ColorWHITE, 12);
SkRect rect = SkRect::MakeLTRB(-4.25f, -4.25f, 4.25f, 4.25f);
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);
}
}
}
}

View File

@ -161,6 +161,7 @@
'../gm/quadpaths.cpp',
'../gm/recordopts.cpp',
'../gm/rects.cpp',
'../gm/repeated_bitmap.cpp',
'../gm/resizeimagefilter.cpp',
'../gm/rrect.cpp',
'../gm/rrects.cpp',

View File

@ -12,6 +12,8 @@
'<(skia_src_path)/doc/SkDocument_PDF.cpp',
'<(skia_src_path)/pdf/SkPDFBitmap.cpp',
'<(skia_src_path)/pdf/SkPDFBitmap.h',
'<(skia_src_path)/pdf/SkPDFCanon.cpp',
'<(skia_src_path)/pdf/SkPDFCanon.h',
'<(skia_src_path)/pdf/SkPDFCatalog.cpp',
@ -42,5 +44,7 @@
'<(skia_src_path)/pdf/SkPDFUtils.cpp',
'<(skia_src_path)/pdf/SkPDFUtils.h',
'<(skia_src_path)/pdf/SkTSet.h',
'<(skia_src_path)/pdf/SkDeflateWStream.cpp',
'<(skia_src_path)/pdf/SkDeflateWStream.h',
],
}

View File

@ -78,6 +78,7 @@
'../tests/DashPathEffectTest.cpp',
'../tests/DataRefTest.cpp',
'../tests/DeferredCanvasTest.cpp',
'../tests/DeflateWStream.cpp',
'../tests/DequeTest.cpp',
'../tests/DeviceLooperTest.cpp',
'../tests/DiscardableMemoryPoolTest.cpp',

View File

@ -20,6 +20,7 @@ sufficient then that is preferred to an include.
Forward declarations and file includes should be in alphabetical order (but we
aren't very strict about it).
<span id="no-define-before-sktypes"></span>
Do not use #if/#ifdef before including "SkTypes.h" (directly or indirectly).
We use spaces not tabs (4 of them).

View File

@ -0,0 +1,106 @@
/*
* 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 "SkDeflateWStream.h"
// https://skia.org/dev/contrib/style#no-define-before-sktypes
#ifndef SK_NO_FLATE
#include "zlib.h"
#define SKDEFLATEWSTREAM_INPUT_BUFFER_SIZE 4096
#define SKDEFLATEWSTREAM_OUTPUT_BUFFER_SIZE 4224 // 4096 + 128, usually big
// enough to always do a
// single loop.
// called by both write() and finalize()
static void do_deflate(int flush,
z_stream* zStream,
SkWStream* out,
unsigned char* inBuffer,
size_t inBufferSize) {
zStream->next_in = inBuffer;
zStream->avail_in = inBufferSize;
unsigned char outBuffer[SKDEFLATEWSTREAM_OUTPUT_BUFFER_SIZE];
SkDEBUGCODE(int returnValue;)
do {
zStream->next_out = outBuffer;
zStream->avail_out = sizeof(outBuffer);
SkDEBUGCODE(returnValue =) deflate(zStream, flush);
SkASSERT(!zStream->msg);
out->write(outBuffer, sizeof(outBuffer) - zStream->avail_out);
} while (zStream->avail_in || !zStream->avail_out);
SkASSERT(flush == Z_FINISH
? returnValue == Z_STREAM_END
: returnValue == Z_OK);
}
// Hide all zlib impl details.
struct SkDeflateWStream::Impl {
SkWStream* fOut;
unsigned char fInBuffer[SKDEFLATEWSTREAM_INPUT_BUFFER_SIZE];
size_t fInBufferIndex;
z_stream fZStream;
};
SkDeflateWStream::SkDeflateWStream(SkWStream* out)
: fImpl(SkNEW(SkDeflateWStream::Impl)) {
fImpl->fOut = out;
fImpl->fInBufferIndex = 0;
if (!fImpl->fOut) {
return;
}
fImpl->fZStream.zalloc = Z_NULL;
fImpl->fZStream.zfree = Z_NULL;
fImpl->fZStream.opaque = Z_NULL;
fImpl->fZStream.data_type = Z_BINARY;
SkDEBUGCODE(int r =) deflateInit(&fImpl->fZStream, Z_DEFAULT_COMPRESSION);
SkASSERT(Z_OK == r);
}
SkDeflateWStream::~SkDeflateWStream() { this->finalize(); }
void SkDeflateWStream::finalize() {
if (!fImpl->fOut) {
return;
}
do_deflate(Z_FINISH, &fImpl->fZStream, fImpl->fOut, fImpl->fInBuffer,
fImpl->fInBufferIndex);
(void)deflateEnd(&fImpl->fZStream);
fImpl->fOut = NULL;
}
bool SkDeflateWStream::write(const void* void_buffer, size_t len) {
if (!fImpl->fOut) {
return false;
}
const char* buffer = (const char*)void_buffer;
while (len > 0) {
size_t tocopy =
SkTMin(len, sizeof(fImpl->fInBuffer) - fImpl->fInBufferIndex);
memcpy(fImpl->fInBuffer + fImpl->fInBufferIndex, buffer, tocopy);
len -= tocopy;
buffer += tocopy;
fImpl->fInBufferIndex += tocopy;
SkASSERT(fImpl->fInBufferIndex <= sizeof(fImpl->fInBuffer));
// if the buffer isn't filled, don't call into zlib yet.
if (sizeof(fImpl->fInBuffer) == fImpl->fInBufferIndex) {
do_deflate(Z_NO_FLUSH, &fImpl->fZStream, fImpl->fOut,
fImpl->fInBuffer, fImpl->fInBufferIndex);
fImpl->fInBufferIndex = 0;
}
}
return true;
}
size_t SkDeflateWStream::bytesWritten() const {
return fImpl->fZStream.total_in + fImpl->fInBufferIndex;
}
#endif // SK_NO_ZLIB

View File

@ -0,0 +1,45 @@
/*
* Copyright 2015 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#ifndef SkDeflateWStream_DEFINED
#define SkDeflateWStream_DEFINED
#include "SkStream.h"
// https://skia.org/dev/contrib/style#no-define-before-sktypes
#ifndef SK_NO_FLATE // Clients of this class should be guarded.
/**
* Wrap a stream in this class to compress the information written to
* this stream using the Deflate algorithm. Uses Zlib's
* Z_DEFAULT_COMPRESSION level.
*
* See http://en.wikipedia.org/wiki/DEFLATE
*/
class SkDeflateWStream : public SkWStream {
public:
/** Does not take ownership of the stream. */
SkDeflateWStream(SkWStream*);
/** The destructor calls finalize(). */
~SkDeflateWStream();
/** Write the end of the compressed stream. All subsequent calls to
write() will fail. Subsequent calls to finalize() do nothing. */
void finalize();
// The SkWStream interface:
bool write(const void*, size_t) SK_OVERRIDE;
size_t bytesWritten() const SK_OVERRIDE;
private:
struct Impl;
SkAutoTDelete<Impl> fImpl;
};
#endif // SK_NO_FLATE
#endif // SkDeflateWStream_DEFINED

333
src/pdf/SkPDFBitmap.cpp Normal file
View File

@ -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;
}

45
src/pdf/SkPDFBitmap.h Normal file
View File

@ -0,0 +1,45 @@
/*
* Copyright 2015 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#ifndef SkPDFBitmap_DEFINED
#define SkPDFBitmap_DEFINED
#include "SkPDFTypes.h"
#include "SkBitmap.h"
/**
* SkPDFBitmap wraps a SkBitmap 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.
*
* As of now, it only supports 8888 bitmaps (the most common case).
*
* The SkPDFBitmap::Create function will check the canon for duplicates.
*/
class SkPDFBitmap : public SkPDFObject {
public:
// Returns NULL on unsupported bitmap;
// TODO(halcanary): support other bitmap colortypes and replace
// SkPDFImage.
static SkPDFBitmap* Create(const SkBitmap&, const SkIRect& subset);
~SkPDFBitmap();
void emitObject(SkWStream*, SkPDFCatalog*) SK_OVERRIDE;
void addResources(SkTSet<SkPDFObject*>* resourceSet,
SkPDFCatalog* catalog) const SK_OVERRIDE;
bool equals(const SkBitmap& other) const {
return fBitmap.getGenerationID() == other.getGenerationID() &&
fBitmap.pixelRefOrigin() == other.pixelRefOrigin() &&
fBitmap.dimensions() == other.dimensions();
}
private:
const SkBitmap fBitmap;
const SkAutoTUnref<SkPDFObject> fSMask;
SkPDFBitmap(const SkBitmap&, SkPDFObject*);
void emitDict(SkWStream*, SkPDFCatalog*, size_t, bool) const;
};
#endif // SkPDFBitmap_DEFINED

View File

@ -6,6 +6,7 @@
*/
#include "SkLazyPtr.h"
#include "SkPDFBitmap.h"
#include "SkPDFCanon.h"
#include "SkPDFFont.h"
#include "SkPDFGraphicState.h"
@ -16,10 +17,12 @@
SK_DECLARE_STATIC_MUTEX(gSkPDFCanonFontMutex);
SK_DECLARE_STATIC_MUTEX(gSkPDFCanonShaderMutex);
SK_DECLARE_STATIC_MUTEX(gSkPDFCanonPaintMutex);
SK_DECLARE_STATIC_MUTEX(gSkPDFCanonBitmapMutex);
SkBaseMutex& SkPDFCanon::GetFontMutex() { return gSkPDFCanonFontMutex; }
SkBaseMutex& SkPDFCanon::GetShaderMutex() { return gSkPDFCanonShaderMutex; }
SkBaseMutex& SkPDFCanon::GetPaintMutex() { return gSkPDFCanonPaintMutex; }
SkBaseMutex& SkPDFCanon::GetBitmapMutex() { return gSkPDFCanonBitmapMutex; }
SkPDFCanon::SkPDFCanon() {}
@ -168,3 +171,20 @@ void SkPDFCanon::removeGraphicState(SkPDFGraphicState* pdfGraphicState) {
assert_mutex_held(this, gSkPDFCanonPaintMutex);
SkAssertResult(remove_item(&fGraphicStateRecords, pdfGraphicState));
}
////////////////////////////////////////////////////////////////////////////////
SkPDFBitmap* SkPDFCanon::findBitmap(const SkBitmap& bm) const {
assert_mutex_held(this, gSkPDFCanonBitmapMutex);
return find_item(fBitmapRecords, bm);
}
void SkPDFCanon::addBitmap(SkPDFBitmap* pdfBitmap) {
assert_mutex_held(this, gSkPDFCanonBitmapMutex);
fBitmapRecords.push(assert_ptr(pdfBitmap));
}
void SkPDFCanon::removeBitmap(SkPDFBitmap* pdfBitmap) {
assert_mutex_held(this, gSkPDFCanonBitmapMutex);
SkAssertResult(remove_item(&fBitmapRecords, pdfBitmap));
}

View File

@ -12,9 +12,11 @@
#include "SkTDArray.h"
struct SkIRect;
class SkBitmap;
class SkMatrix;
class SkPDFFont;
class SkPDFGraphicState;
class SkPDFBitmap;
class SkPaint;
// This class's fields and methods will eventually become part of
@ -40,6 +42,7 @@ public:
static SkBaseMutex& GetFontMutex();
static SkBaseMutex& GetShaderMutex();
static SkBaseMutex& GetPaintMutex();
static SkBaseMutex& GetBitmapMutex();
// Returns exact match if there is one. If not, it returns NULL.
// If there is no exact match, but there is a related font, we
@ -66,6 +69,10 @@ public:
void addGraphicState(SkPDFGraphicState*);
void removeGraphicState(SkPDFGraphicState*);
SkPDFBitmap* findBitmap(const SkBitmap&) const;
void addBitmap(SkPDFBitmap*);
void removeBitmap(SkPDFBitmap*);
private:
struct FontRec {
SkPDFFont* fFont;
@ -81,5 +88,7 @@ private:
SkTDArray<SkPDFImageShader*> fImageShaderRecords;
SkTDArray<SkPDFGraphicState*> fGraphicStateRecords;
SkTDArray<SkPDFBitmap*> fBitmapRecords;
};
#endif // SkPDFCanon_DEFINED

View File

@ -12,6 +12,7 @@
#include "SkColorPriv.h"
#include "SkData.h"
#include "SkFlate.h"
#include "SkPDFBitmap.h"
#include "SkPDFCatalog.h"
#include "SkPixelRef.h"
#include "SkRect.h"
@ -728,6 +729,9 @@ SkPDFObject* SkPDFCreateImageObject(
const SkBitmap& bitmap,
const SkIRect& subset,
SkPicture::EncodeBitmap encoder) {
if (SkPDFObject* pdfBitmap = SkPDFBitmap::Create(bitmap, subset)) {
return pdfBitmap;
}
#if 0 // reenable when we can figure out the JPEG colorspace
if (SkIRect::MakeWH(bitmap.width(), bitmap.height()) == subset) {
SkAutoTUnref<SkData> encodedData(ref_encoded_data(bitmap));

76
tests/DeflateWStream.cpp Normal file
View File

@ -0,0 +1,76 @@
/*
* 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 "SkDeflateWStream.h"
#include "SkFlate.h"
#include "SkRandom.h"
#include "Test.h"
#ifndef SK_NO_FLATE
DEF_TEST(SkDeflateWStream, r) {
SkRandom random(123456);
for (int i = 0; i < 50; ++i) {
uint32_t size = random.nextULessThan(10000);
SkAutoTMalloc<uint8_t> buffer(size);
for (uint32_t i = 0; i < size; ++i) {
buffer[i] = random.nextU() & 0xff;
}
SkDynamicMemoryWStream dynamicMemoryWStream;
{
SkDeflateWStream deflateWStream(&dynamicMemoryWStream);
uint32_t j = 0;
while (j < size) {
uint32_t writeSize =
SkTMin(size - j, random.nextRangeU(1, 400));
if (!deflateWStream.write(&buffer[j], writeSize)) {
ERRORF(r, "something went wrong.");
return;
}
j += writeSize;
}
}
SkAutoTDelete<SkStreamAsset> compressed(
dynamicMemoryWStream.detachAsStream());
SkDynamicMemoryWStream decompressedDynamicMemoryWStream;
SkAssertResult(SkFlate::Inflate(compressed,
&decompressedDynamicMemoryWStream));
SkAutoTDelete<SkStreamAsset> decompressed(
decompressedDynamicMemoryWStream.detachAsStream());
if (decompressed->getLength() != size) {
ERRORF(r, "Decompression failed to get right size [%d]."
" %u != %u", i, (unsigned)(decompressed->getLength()),
(unsigned)size);
SkString s = SkStringPrintf("/tmp/deftst_compressed_%d", i);
SkFILEWStream o(s.c_str());
o.writeStream(compressed.get(), compressed->getLength());
compressed->rewind();
s = SkStringPrintf("/tmp/deftst_input_%d", i);
SkFILEWStream o2(s.c_str());
o2.write(&buffer[0], size);
continue;
}
uint32_t minLength = SkTMin(size,
(uint32_t)(decompressed->getLength()));
for (uint32_t i = 0; i < minLength; ++i) {
uint8_t c;
SkDEBUGCODE(size_t rb =)decompressed->read(&c, sizeof(uint8_t));
SkASSERT(sizeof(uint8_t) == rb);
if (buffer[i] != c) {
ERRORF(r, "Decompression failed at byte %u.", (unsigned)i);
break;
}
}
}
}
#endif // SK_NO_FLATE

View File

@ -41,11 +41,6 @@ private:
#define DUMMY_TEXT "DCT compessed stream."
static SkData* encode_to_dct_data(size_t* pixelRefOffset, const SkBitmap& bitmap) {
*pixelRefOffset = 0;
return SkData::NewWithProc(DUMMY_TEXT, sizeof(DUMMY_TEXT) - 1, NULL, NULL);
}
static bool stream_equals(const SkDynamicMemoryWStream& stream, size_t offset,
const void* buffer, size_t len) {
SkAutoDataUnref data(stream.copyToData());
@ -55,20 +50,6 @@ static bool stream_equals(const SkDynamicMemoryWStream& stream, size_t offset,
return memcmp(data->bytes() + offset, buffer, len) == 0;
}
static bool stream_contains(const SkDynamicMemoryWStream& stream,
const char* buffer) {
SkAutoDataUnref data(stream.copyToData());
size_t len = strlen(buffer); // our buffer does not have EOSs.
for (size_t offset = 0 ; offset < data->size() - len; offset++) {
if (memcmp(data->bytes() + offset, buffer, len) == 0) {
return true;
}
}
return false;
}
static void emit_object(SkPDFObject* object,
SkWStream* stream,
SkPDFCatalog* catalog,
@ -243,86 +224,6 @@ static void TestSubstitute(skiatest::Reporter* reporter) {
REPORTER_ASSERT(reporter, proxy.get() != catalog.getSubstituteObject(stub));
}
// Create a bitmap that would be very eficiently compressed in a ZIP.
static void setup_bitmap(SkBitmap* bitmap, int width, int height) {
bitmap->allocN32Pixels(width, height);
bitmap->eraseColor(SK_ColorWHITE);
}
static void TestImage(skiatest::Reporter* reporter, const SkBitmap& bitmap,
const char* expected, bool useDCTEncoder) {
SkISize pageSize = SkISize::Make(bitmap.width(), bitmap.height());
SkAutoTUnref<SkPDFDevice> dev(new SkPDFDevice(pageSize, pageSize, SkMatrix::I()));
if (useDCTEncoder) {
dev->setDCTEncoder(encode_to_dct_data);
}
SkCanvas c(dev);
c.drawBitmap(bitmap, 0, 0, NULL);
SkPDFDocument doc;
doc.appendPage(dev);
SkDynamicMemoryWStream stream;
doc.emitPDF(&stream);
REPORTER_ASSERT(reporter, stream_contains(stream, expected));
}
static void TestUncompressed(skiatest::Reporter* reporter) {
SkBitmap bitmap;
setup_bitmap(&bitmap, 1, 1);
TestImage(reporter, bitmap,
"/Subtype /Image\n"
"/Width 1\n"
"/Height 1\n"
"/ColorSpace /DeviceRGB\n"
"/BitsPerComponent 8\n"
"/Length 3\n"
">> stream",
true);
}
static void TestFlateDecode(skiatest::Reporter* reporter) {
#ifndef SK_NO_FLATE
SkBitmap bitmap;
setup_bitmap(&bitmap, 10, 10);
TestImage(reporter, bitmap,
"/Subtype /Image\n"
"/Width 10\n"
"/Height 10\n"
"/ColorSpace /DeviceRGB\n"
"/BitsPerComponent 8\n"
"/Filter /FlateDecode\n"
"/Length 13\n"
">> stream",
false);
#endif // SK_NO_FLATE
}
static void TestDCTDecode(skiatest::Reporter* reporter) {
SkBitmap bitmap;
setup_bitmap(&bitmap, 32, 32);
TestImage(reporter, bitmap,
"/Subtype /Image\n"
"/Width 32\n"
"/Height 32\n"
"/ColorSpace /DeviceRGB\n"
"/BitsPerComponent 8\n"
"/Filter /DCTDecode\n"
"/ColorTransform 0\n"
"/Length 21\n"
">> stream",
true);
}
static void TestImages(skiatest::Reporter* reporter) {
TestUncompressed(reporter);
TestFlateDecode(reporter);
TestDCTDecode(reporter);
}
// This test used to assert without the fix submitted for
// http://code.google.com/p/skia/issues/detail?id=1083.
// SKP files might have invalid glyph ids. This test ensures they are ignored,
@ -426,8 +327,6 @@ DEF_TEST(PDFPrimitives, reporter) {
TestSubstitute(reporter);
test_issue1083();
TestImages(reporter);
}
namespace {