2e3f9d8a93
Add SkPDFCanon::reset function to unref all objects. No longer possible to remove object from canon Motivation: this doesn't change these object's lifetime, (they will still be fully unrefed when SkDocument::close() is called, but we no longer have to remove them from the array when their destructor is called. Review URL: https://codereview.chromium.org/966863002
318 lines
11 KiB
C++
318 lines
11 KiB
C++
/*
|
|
* 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 "SkFlate.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());
|
|
}
|
|
|
|
// 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);
|
|
|
|
#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);
|
|
#else
|
|
this->emitDict(stream, catalog, pixel_count(fBitmap), /*deflate=*/false);
|
|
pdf_stream_begin(stream);
|
|
pmcolor_alpha_to_a8(fBitmap, stream);
|
|
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);
|
|
|
|
#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);
|
|
#else
|
|
this->emitDict(stream, catalog, 3 * pixel_count(fBitmap), /*deflate=*/false);
|
|
pdf_stream_begin(stream);
|
|
pmcolor_to_rgb24(fBitmap, stream);
|
|
pdf_stream_end(stream);
|
|
return;
|
|
#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() {}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
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;
|
|
}
|
|
|
|
SkPDFBitmap* SkPDFBitmap::Create(SkPDFCanon* canon,
|
|
const SkBitmap& bitmap,
|
|
const SkIRect& subset) {
|
|
SkASSERT(canon);
|
|
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(©)) {
|
|
return NULL;
|
|
}
|
|
copy.setImmutable();
|
|
bm = copy;
|
|
}
|
|
|
|
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;
|
|
}
|