SkPDF: move image serialization over to new immediate mode.
Change-Id: If2bd4cd61bc3231edd860b0e08f78a6f24bd3094 Reviewed-on: https://skia-review.googlesource.com/c/171532 Reviewed-by: Ben Wagner <bungeman@google.com> Commit-Queue: Hal Canary <halcanary@google.com>
This commit is contained in:
parent
f2a7a20b32
commit
a121183204
@ -119,12 +119,10 @@ protected:
|
||||
return;
|
||||
}
|
||||
while (loops-- > 0) {
|
||||
auto object = SkPDFCreateBitmapObject(fImage);
|
||||
SkASSERT(object);
|
||||
if (!object) {
|
||||
return;
|
||||
}
|
||||
test_pdf_object_serialization(object);
|
||||
SkNullWStream nullStream;
|
||||
SkPDFDocument doc(&nullStream, SkPDF::Metadata());
|
||||
doc.beginPage(256, 256);
|
||||
(void)SkPDFSerializeImage(fImage.get(), &doc);
|
||||
}
|
||||
}
|
||||
|
||||
@ -156,12 +154,10 @@ protected:
|
||||
return;
|
||||
}
|
||||
while (loops-- > 0) {
|
||||
auto object = SkPDFCreateBitmapObject(fImage);
|
||||
SkASSERT(object);
|
||||
if (!object) {
|
||||
return;
|
||||
}
|
||||
test_pdf_object_serialization(object);
|
||||
SkNullWStream nullStream;
|
||||
SkPDFDocument doc(&nullStream, SkPDF::Metadata());
|
||||
doc.beginPage(256, 256);
|
||||
(void)SkPDFSerializeImage(fImage.get(), &doc);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -13,28 +13,12 @@
|
||||
#include "SkImage.h"
|
||||
#include "SkImageInfoPriv.h"
|
||||
#include "SkJpegInfo.h"
|
||||
#include "SkPDFCanon.h"
|
||||
#include "SkPDFDocumentPriv.h"
|
||||
#include "SkPDFTypes.h"
|
||||
#include "SkPDFUtils.h"
|
||||
#include "SkStream.h"
|
||||
#include "SkTo.h"
|
||||
|
||||
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;
|
||||
// if image can not be read, treat as transparent.
|
||||
return SkPDFUtils::ToBitmap(image, &bm) && SkBitmap::ComputeIsOpaque(bm);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
static const char kStreamBegin[] = " stream\n";
|
||||
|
||||
static const char kStreamEnd[] = "\nendstream";
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// write a single byte to a stream n times.
|
||||
@ -56,7 +40,7 @@ static void fill_stream(SkWStream* out, char value, size_t n) {
|
||||
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 */
|
||||
static SkColor get_neighbor_avg_color(const SkBitmap& bm, int xOrig, int yOrig) {
|
||||
static SkColor get_neighbor_avg_color(const SkPixmap& bm, int xOrig, int yOrig) {
|
||||
SkASSERT(kBGRA_8888_SkColorType == bm.colorType());
|
||||
unsigned r = 0, g = 0, b = 0, n = 0;
|
||||
// Clamp the range to the edge of the bitmap.
|
||||
@ -65,7 +49,7 @@ static SkColor get_neighbor_avg_color(const SkBitmap& bm, int xOrig, int yOrig)
|
||||
int xmin = SkTMax(0, xOrig - 1);
|
||||
int xmax = SkTMin(xOrig + 1, bm.width() - 1);
|
||||
for (int y = ymin; y <= ymax; ++y) {
|
||||
const SkColor* scanline = bm.getAddr32(0, y);
|
||||
const SkColor* scanline = bm.addr32(0, y);
|
||||
for (int x = xmin; x <= xmax; ++x) {
|
||||
SkColor color = scanline[x];
|
||||
if (color != SK_ColorTRANSPARENT) {
|
||||
@ -80,173 +64,150 @@ static SkColor get_neighbor_avg_color(const SkBitmap& bm, int xOrig, int yOrig)
|
||||
: SK_ColorTRANSPARENT;
|
||||
}
|
||||
|
||||
static size_t pixel_count(const SkImage* image) {
|
||||
return SkToSizeT(image->width()) * SkToSizeT(image->height());
|
||||
static void emit_stream(SkDynamicMemoryWStream* src, SkWStream* dst) {
|
||||
dst->writeText(" stream\n");
|
||||
src->writeToAndReset(dst);
|
||||
dst->writeText("\nendstream");
|
||||
}
|
||||
|
||||
static size_t pdf_color_component_count(SkColorType ct) {
|
||||
// Single-channel formats remain that way, all others are converted to RGB
|
||||
return SkColorTypeIsAlphaOnly(ct) || SkColorTypeIsGray(ct) ? 1 : 3;
|
||||
static void emit_dict(SkWStream* stream, SkISize size, const char* colorSpace,
|
||||
const SkPDFIndirectReference* smask, int length) {
|
||||
SkPDFDict pdfDict("XObject");
|
||||
pdfDict.insertName("Subtype", "Image");
|
||||
pdfDict.insertInt("Width", size.width());
|
||||
pdfDict.insertInt("Height", size.height());
|
||||
pdfDict.insertName("ColorSpace", colorSpace);
|
||||
if (smask) {
|
||||
pdfDict.insertRef("SMask", *smask);
|
||||
}
|
||||
pdfDict.insertInt("BitsPerComponent", 8);
|
||||
pdfDict.insertName("Filter", "FlateDecode");
|
||||
pdfDict.insertInt("Length", length);
|
||||
pdfDict.emitObject(stream);
|
||||
}
|
||||
|
||||
static void image_to_pdf_pixels(const SkImage* image, SkWStream* out) {
|
||||
if (kAlpha_8_SkColorType == image->colorType()) {
|
||||
fill_stream(out, '\x00', pixel_count(image));
|
||||
} else if (kGray_8_SkColorType == image->colorType()) {
|
||||
SkBitmap gray;
|
||||
gray.allocPixels(SkImageInfo::Make(image->width(), image->height(),
|
||||
kGray_8_SkColorType,
|
||||
kPremul_SkAlphaType));
|
||||
if (image->readPixels(gray.pixmap(), 0, 0)) {
|
||||
out->write(gray.getPixels(), gray.computeByteSize());
|
||||
} else {
|
||||
fill_stream(out, '\x00', pixel_count(image));
|
||||
}
|
||||
static SkPDFIndirectReference do_deflated_alpha(const SkPixmap& pm, SkPDFDocument* doc,
|
||||
SkPDFIndirectReference ref) {
|
||||
SkDynamicMemoryWStream buffer;
|
||||
SkDeflateWStream deflateWStream(&buffer);
|
||||
if (kAlpha_8_SkColorType == pm.colorType()) {
|
||||
SkASSERT(pm.rowBytes() == (size_t)pm.width());
|
||||
buffer.write(pm.addr8(), pm.width() * pm.height());
|
||||
} else {
|
||||
SkBitmap bgra;
|
||||
// TODO: makeColorSpace(sRGB) or actually tag the images
|
||||
bgra.allocPixels(SkImageInfo::Make(image->width(), image->height(),
|
||||
kBGRA_8888_SkColorType,
|
||||
kUnpremul_SkAlphaType));
|
||||
if (image->readPixels(bgra.pixmap(), 0, 0)) {
|
||||
SkAutoTMalloc<uint8_t> scanline(3 * bgra.width());
|
||||
for (int y = 0; y < bgra.height(); ++y) {
|
||||
const SkColor* src = bgra.getAddr32(0, y);
|
||||
uint8_t* dst = scanline.get();
|
||||
for (int x = 0; x < bgra.width(); ++x) {
|
||||
SkASSERT(pm.alphaType() == kUnpremul_SkAlphaType);
|
||||
SkASSERT(pm.colorType() == kBGRA_8888_SkColorType);
|
||||
SkASSERT(pm.rowBytes() == (size_t)pm.width() * 4);
|
||||
const uint32_t* ptr = pm.addr32();
|
||||
const uint32_t* stop = ptr + pm.height() * pm.width();
|
||||
|
||||
uint8_t byteBuffer[4092];
|
||||
uint8_t* bufferStop = byteBuffer + SK_ARRAY_COUNT(byteBuffer);
|
||||
uint8_t* dst = byteBuffer;
|
||||
while (ptr != stop) {
|
||||
*dst++ = 0xFF & ((*ptr++) >> SK_BGRA_A32_SHIFT);
|
||||
if (dst == bufferStop) {
|
||||
deflateWStream.write(byteBuffer, sizeof(byteBuffer));
|
||||
dst = byteBuffer;
|
||||
}
|
||||
}
|
||||
deflateWStream.write(byteBuffer, dst - byteBuffer);
|
||||
}
|
||||
deflateWStream.finalize();
|
||||
SkWStream* stream = doc->beginObject(ref);
|
||||
emit_dict(stream, pm.info().dimensions(), "DeviceGray", nullptr, buffer.bytesWritten());
|
||||
emit_stream(&buffer, stream);
|
||||
doc->endObject();
|
||||
return ref;
|
||||
}
|
||||
|
||||
static SkPDFIndirectReference do_deflated_image(const SkPixmap& pm,
|
||||
SkPDFDocument* doc,
|
||||
bool isOpaque) {
|
||||
SkPDFIndirectReference sMask;
|
||||
if (!isOpaque) {
|
||||
sMask = doc->reserve();
|
||||
}
|
||||
SkDynamicMemoryWStream buffer;
|
||||
SkDeflateWStream deflateWStream(&buffer);
|
||||
const char* colorSpace = "DeviceGray";
|
||||
switch (pm.colorType()) {
|
||||
case kAlpha_8_SkColorType:
|
||||
fill_stream(&deflateWStream, '\x00', pm.width() * pm.height());
|
||||
break;
|
||||
case kGray_8_SkColorType:
|
||||
SkASSERT(sMask.fValue = -1);
|
||||
SkASSERT(pm.rowBytes() == (size_t)pm.width());
|
||||
deflateWStream.write(pm.addr8(), pm.width() * pm.height());
|
||||
break;
|
||||
default:
|
||||
colorSpace = "DeviceRGB";
|
||||
SkASSERT(pm.alphaType() == kUnpremul_SkAlphaType);
|
||||
SkASSERT(pm.colorType() == kBGRA_8888_SkColorType);
|
||||
SkASSERT(pm.rowBytes() == (size_t)pm.width() * 4);
|
||||
uint8_t byteBuffer[3072];
|
||||
static_assert(SK_ARRAY_COUNT(byteBuffer) % 3 == 0, "");
|
||||
uint8_t* bufferStop = byteBuffer + SK_ARRAY_COUNT(byteBuffer);
|
||||
uint8_t* dst = byteBuffer;
|
||||
for (int y = 0; y < pm.height(); ++y) {
|
||||
const SkColor* src = pm.addr32(0, y);
|
||||
for (int x = 0; x < pm.width(); ++x) {
|
||||
SkColor color = *src++;
|
||||
if (SkColorGetA(color) == SK_AlphaTRANSPARENT) {
|
||||
color = get_neighbor_avg_color(bgra, x, y);
|
||||
color = get_neighbor_avg_color(pm, x, y);
|
||||
}
|
||||
*dst++ = SkColorGetR(color);
|
||||
*dst++ = SkColorGetG(color);
|
||||
*dst++ = SkColorGetB(color);
|
||||
if (dst == bufferStop) {
|
||||
deflateWStream.write(byteBuffer, sizeof(byteBuffer));
|
||||
dst = byteBuffer;
|
||||
}
|
||||
}
|
||||
out->write(scanline.get(), 3 * bgra.width());
|
||||
}
|
||||
} else {
|
||||
fill_stream(out, '\x00', 3 * pixel_count(image));
|
||||
}
|
||||
deflateWStream.write(byteBuffer, dst - byteBuffer);
|
||||
}
|
||||
deflateWStream.finalize();
|
||||
SkPDFIndirectReference ref = doc->reserve();
|
||||
SkWStream* stream = doc->beginObject(ref);
|
||||
emit_dict(stream, pm.info().dimensions(), colorSpace,
|
||||
sMask.fValue != -1 ? &sMask : nullptr,
|
||||
buffer.bytesWritten());
|
||||
emit_stream(&buffer, stream);
|
||||
doc->endObject();
|
||||
if (!isOpaque) {
|
||||
do_deflated_alpha(pm, doc, sMask);
|
||||
}
|
||||
return ref;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
static void image_alpha_to_a8(const SkImage* image, SkWStream* out) {
|
||||
SkBitmap alpha;
|
||||
alpha.allocPixels(SkImageInfo::MakeA8(image->width(), image->height()));
|
||||
if (image->readPixels(alpha.pixmap(), 0, 0)) {
|
||||
out->write(alpha.getPixels(), alpha.computeByteSize());
|
||||
} else {
|
||||
fill_stream(out, '\xFF', pixel_count(image));
|
||||
static bool do_jpeg(const SkData& data, SkPDFDocument* doc, SkISize size,
|
||||
SkPDFIndirectReference* result) {
|
||||
SkISize jpegSize;
|
||||
SkEncodedInfo::Color jpegColorType;
|
||||
SkEncodedOrigin exifOrientation;
|
||||
if (!SkGetJpegInfo(data.data(), data.size(), &jpegSize,
|
||||
&jpegColorType, &exifOrientation)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static void emit_image_xobject(SkWStream* stream,
|
||||
const SkImage* image,
|
||||
bool alpha,
|
||||
const sk_sp<SkPDFObject>& smask) {
|
||||
// Write to a temporary buffer to get the compressed length.
|
||||
SkDynamicMemoryWStream buffer;
|
||||
SkDeflateWStream deflateWStream(&buffer);
|
||||
if (alpha) {
|
||||
image_alpha_to_a8(image, &deflateWStream);
|
||||
} else {
|
||||
image_to_pdf_pixels(image, &deflateWStream);
|
||||
bool yuv = jpegColorType == SkEncodedInfo::kYUV_Color;
|
||||
bool goodColorType = yuv || jpegColorType == SkEncodedInfo::kGray_Color;
|
||||
if (jpegSize != size // Sanity check.
|
||||
|| !goodColorType
|
||||
|| kTopLeft_SkEncodedOrigin != exifOrientation) {
|
||||
return false;
|
||||
}
|
||||
deflateWStream.finalize(); // call before buffer.bytesWritten().
|
||||
#ifdef SK_PDF_IMAGE_STATS
|
||||
gJpegImageObjects.fetch_add(1);
|
||||
#endif
|
||||
SkPDFIndirectReference ref = doc->reserve();
|
||||
*result = ref;
|
||||
SkWStream* stream = doc->beginObject(ref);
|
||||
|
||||
SkPDFDict pdfDict("XObject");
|
||||
pdfDict.insertName("Subtype", "Image");
|
||||
pdfDict.insertInt("Width", image->width());
|
||||
pdfDict.insertInt("Height", image->height());
|
||||
if (alpha) {
|
||||
pdfDict.insertName("ColorSpace", "DeviceGray");
|
||||
} else if (1 == pdf_color_component_count(image->colorType())) {
|
||||
pdfDict.insertName("ColorSpace", "DeviceGray");
|
||||
} else {
|
||||
pdfDict.insertName("ColorSpace", "DeviceRGB");
|
||||
}
|
||||
if (smask) {
|
||||
pdfDict.insertObjRef("SMask", smask);
|
||||
}
|
||||
pdfDict.insertInt("BitsPerComponent", 8);
|
||||
pdfDict.insertName("Filter", "FlateDecode");
|
||||
pdfDict.insertInt("Length", buffer.bytesWritten());
|
||||
pdfDict.emitObject(stream);
|
||||
|
||||
stream->writeText(kStreamBegin);
|
||||
buffer.writeToAndReset(stream);
|
||||
stream->writeText(kStreamEnd);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
namespace {
|
||||
// This SkPDFObject only outputs the alpha layer of the given bitmap.
|
||||
class PDFAlphaBitmap final : public SkPDFObject {
|
||||
public:
|
||||
PDFAlphaBitmap(sk_sp<SkImage> image) : fImage(std::move(image)) { SkASSERT(fImage); }
|
||||
void emitObject(SkWStream* stream) const override {
|
||||
SkASSERT(fImage);
|
||||
emit_image_xobject(stream, fImage.get(), true, nullptr);
|
||||
}
|
||||
void drop() override { fImage = nullptr; }
|
||||
|
||||
private:
|
||||
sk_sp<SkImage> fImage;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
namespace {
|
||||
class PDFDefaultBitmap final : public SkPDFObject {
|
||||
public:
|
||||
void emitObject(SkWStream* stream) const override {
|
||||
SkASSERT(fImage);
|
||||
emit_image_xobject(stream, fImage.get(), false, fSMask);
|
||||
}
|
||||
void addResources(SkPDFObjNumMap* catalog) const override {
|
||||
catalog->addObjectRecursively(fSMask.get());
|
||||
}
|
||||
void drop() override { fImage = nullptr; fSMask = nullptr; }
|
||||
PDFDefaultBitmap(sk_sp<SkImage> image, sk_sp<SkPDFObject> smask)
|
||||
: fImage(std::move(image)), fSMask(std::move(smask)) { SkASSERT(fImage); }
|
||||
|
||||
private:
|
||||
sk_sp<SkImage> fImage;
|
||||
sk_sp<SkPDFObject> fSMask;
|
||||
};
|
||||
} // namespace
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
namespace {
|
||||
/**
|
||||
* 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 final : public SkPDFObject {
|
||||
public:
|
||||
SkISize fSize;
|
||||
sk_sp<SkData> fData;
|
||||
bool fIsYUV;
|
||||
PDFJpegBitmap(SkISize size, sk_sp<SkData> data, bool isYUV)
|
||||
: fSize(size), fData(std::move(data)), fIsYUV(isYUV) { SkASSERT(fData); }
|
||||
void emitObject(SkWStream*) const override;
|
||||
void drop() override { fData = nullptr; }
|
||||
};
|
||||
|
||||
void PDFJpegBitmap::emitObject(SkWStream* stream) const {
|
||||
SkASSERT(fData);
|
||||
SkPDFDict pdfDict("XObject");
|
||||
pdfDict.insertName("Subtype", "Image");
|
||||
pdfDict.insertInt("Width", fSize.width());
|
||||
pdfDict.insertInt("Height", fSize.height());
|
||||
if (fIsYUV) {
|
||||
pdfDict.insertInt("Width", jpegSize.width());
|
||||
pdfDict.insertInt("Height", jpegSize.height());
|
||||
if (yuv) {
|
||||
pdfDict.insertName("ColorSpace", "DeviceRGB");
|
||||
} else {
|
||||
pdfDict.insertName("ColorSpace", "DeviceGray");
|
||||
@ -254,60 +215,58 @@ void PDFJpegBitmap::emitObject(SkWStream* stream) const {
|
||||
pdfDict.insertInt("BitsPerComponent", 8);
|
||||
pdfDict.insertName("Filter", "DCTDecode");
|
||||
pdfDict.insertInt("ColorTransform", 0);
|
||||
pdfDict.insertInt("Length", SkToInt(fData->size()));
|
||||
pdfDict.insertInt("Length", SkToInt(data.size()));
|
||||
pdfDict.emitObject(stream);
|
||||
stream->writeText(kStreamBegin);
|
||||
stream->write(fData->data(), fData->size());
|
||||
stream->writeText(kStreamEnd);
|
||||
stream->writeText(" stream\n");
|
||||
stream->write(data.data(), data.size());
|
||||
stream->writeText("\nendstream");
|
||||
doc->endObject();
|
||||
return true;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
sk_sp<PDFJpegBitmap> make_jpeg_bitmap(sk_sp<SkData> data, SkISize size) {
|
||||
SkISize jpegSize;
|
||||
SkEncodedInfo::Color jpegColorType;
|
||||
SkEncodedOrigin exifOrientation;
|
||||
if (data && SkGetJpegInfo(data->data(), data->size(), &jpegSize,
|
||||
&jpegColorType, &exifOrientation)) {
|
||||
bool yuv = jpegColorType == SkEncodedInfo::kYUV_Color;
|
||||
bool goodColorType = yuv || jpegColorType == SkEncodedInfo::kGray_Color;
|
||||
if (jpegSize == size // Sanity check.
|
||||
&& goodColorType
|
||||
&& kTopLeft_SkEncodedOrigin == exifOrientation) {
|
||||
// hold on to data, not image.
|
||||
#ifdef SK_PDF_IMAGE_STATS
|
||||
gJpegImageObjects.fetch_add(1);
|
||||
#endif
|
||||
return sk_make_sp<PDFJpegBitmap>(jpegSize, std::move(data), yuv);
|
||||
static SkBitmap to_pixels(const SkImage* image) {
|
||||
SkBitmap bm;
|
||||
int w = image->width(),
|
||||
h = image->height();
|
||||
switch (image->colorType()) {
|
||||
case kAlpha_8_SkColorType:
|
||||
bm.allocPixels(SkImageInfo::MakeA8(w, h));
|
||||
break;
|
||||
case kGray_8_SkColorType:
|
||||
bm.allocPixels(SkImageInfo::Make(w, h, kGray_8_SkColorType, kOpaque_SkAlphaType));
|
||||
break;
|
||||
default: {
|
||||
// TODO: makeColorSpace(sRGB) or actually tag the images
|
||||
SkAlphaType at = bm.isOpaque() ? kOpaque_SkAlphaType : kUnpremul_SkAlphaType;
|
||||
bm.allocPixels(SkImageInfo::Make(w, h, kBGRA_8888_SkColorType, at));
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
if (!image->readPixels(bm.pixmap(), 0, 0)) {
|
||||
bm.eraseColor(SkColorSetARGB(0xFF, 0, 0, 0));
|
||||
}
|
||||
return bm;
|
||||
}
|
||||
|
||||
sk_sp<SkPDFObject> SkPDFCreateBitmapObject(sk_sp<SkImage> image, int encodingQuality) {
|
||||
SkASSERT(image);
|
||||
SkPDFIndirectReference SkPDFSerializeImage(const SkImage* img,
|
||||
SkPDFDocument* doc,
|
||||
int encodingQuality) {
|
||||
SkPDFIndirectReference result;
|
||||
SkASSERT(img);
|
||||
SkASSERT(doc);
|
||||
SkASSERT(encodingQuality >= 0);
|
||||
SkISize dimensions = image->dimensions();
|
||||
sk_sp<SkData> data = image->refEncodedData();
|
||||
if (auto jpeg = make_jpeg_bitmap(std::move(data), dimensions)) {
|
||||
return std::move(jpeg);
|
||||
SkISize dimensions = img->dimensions();
|
||||
sk_sp<SkData> data = img->refEncodedData();
|
||||
if (data && do_jpeg(*data, doc, dimensions, &result)) {
|
||||
return result;
|
||||
}
|
||||
|
||||
const bool isOpaque = image_compute_is_opaque(image.get());
|
||||
|
||||
SkBitmap bm = to_pixels(img);
|
||||
SkPixmap pm = bm.pixmap();
|
||||
bool isOpaque = pm.isOpaque() || pm.computeIsOpaque();
|
||||
if (encodingQuality <= 100 && isOpaque) {
|
||||
data = image->encodeToData(SkEncodedImageFormat::kJPEG, encodingQuality);
|
||||
if (auto jpeg = make_jpeg_bitmap(std::move(data), dimensions)) {
|
||||
return std::move(jpeg);
|
||||
sk_sp<SkData> data = img->encodeToData(SkEncodedImageFormat::kJPEG, encodingQuality);
|
||||
if (data && do_jpeg(*data, doc, dimensions, &result)) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
sk_sp<SkPDFObject> smask;
|
||||
if (!isOpaque) {
|
||||
smask = sk_make_sp<PDFAlphaBitmap>(image);
|
||||
}
|
||||
#ifdef SK_PDF_IMAGE_STATS
|
||||
gRegularImageObjects.fetch_add(1);
|
||||
#endif
|
||||
return sk_make_sp<PDFDefaultBitmap>(std::move(image), std::move(smask));
|
||||
return do_deflated_image(pm, doc, isOpaque);
|
||||
}
|
||||
|
@ -7,18 +7,16 @@
|
||||
#ifndef SkPDFBitmap_DEFINED
|
||||
#define SkPDFBitmap_DEFINED
|
||||
|
||||
#include "SkRefCnt.h"
|
||||
|
||||
class SkImage;
|
||||
class SkPDFObject;
|
||||
class SkPDFDocument;
|
||||
struct SkPDFIndirectReference;
|
||||
|
||||
/**
|
||||
* 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 image, and its emitObject() does not cache any data.
|
||||
*
|
||||
* Serialize a SkImage as an Image Xobject.
|
||||
* quality > 100 means lossless
|
||||
*/
|
||||
sk_sp<SkPDFObject> SkPDFCreateBitmapObject(sk_sp<SkImage>, int encodingQuality = 101);
|
||||
SkPDFIndirectReference SkPDFSerializeImage(const SkImage* img,
|
||||
SkPDFDocument* doc,
|
||||
int encodingQuality = 101);
|
||||
|
||||
#endif // SkPDFBitmap_DEFINED
|
||||
|
@ -1801,6 +1801,15 @@ static bool is_integral(const SkRect& r) {
|
||||
is_integer(r.bottom());
|
||||
}
|
||||
|
||||
namespace {
|
||||
// This struct will go away when fIndirectReference goes away.
|
||||
struct PDFObj final : public SkPDFObject {
|
||||
PDFObj(SkPDFIndirectReference ref) { fIndirectReference = ref; }
|
||||
// emitObject() is never called since the Object already has a indirect ref.
|
||||
void emitObject(SkWStream*) const override { SK_ABORT("DO NOT REACH HERE"); }
|
||||
};
|
||||
} // namespace
|
||||
|
||||
void SkPDFDevice::internalDrawImageRect(SkKeyedImage imageSubset,
|
||||
const SkRect* src,
|
||||
const SkRect& dst,
|
||||
@ -2028,12 +2037,10 @@ void SkPDFDevice::internalDrawImageRect(SkKeyedImage imageSubset,
|
||||
sk_sp<SkPDFObject> pdfimage = pdfimagePtr ? *pdfimagePtr : nullptr;
|
||||
if (!pdfimage) {
|
||||
SkASSERT(imageSubset);
|
||||
pdfimage = SkPDFCreateBitmapObject(imageSubset.release(),
|
||||
fDocument->metadata().fEncodingQuality);
|
||||
if (!pdfimage) {
|
||||
return;
|
||||
}
|
||||
fDocument->serialize(pdfimage); // serialize images early.
|
||||
auto ref = SkPDFSerializeImage(imageSubset.image().get(), fDocument,
|
||||
fDocument->metadata().fEncodingQuality);
|
||||
SkASSERT(ref.fValue > 0);
|
||||
pdfimage = sk_make_sp<PDFObj>(ref);
|
||||
SkASSERT((key != SkBitmapKey{{0, 0, 0, 0}, 0}));
|
||||
fDocument->canon()->fPDFBitmapMap.set(key, pdfimage);
|
||||
}
|
||||
|
@ -26,19 +26,15 @@ const char* SkPDFGetNodeIdKey() {
|
||||
|
||||
void SkPDFOffsetMap::set(SkPDFIndirectReference iRef, SkPDFFileOffset offset) {
|
||||
SkASSERT(iRef.fValue > 0);
|
||||
SkASSERT(SkToSizeT(iRef.fValue - 1) == fOffsets.size());
|
||||
fOffsets.push_back(offset);
|
||||
// The following logic can be used in a later CL when we serialize objects
|
||||
// out of order:
|
||||
//size_t index = SkToSizeT(iRef.fValue - 1);
|
||||
//if (index == fOffsets.size()) {
|
||||
// fOffsets.push_back(offset);
|
||||
//} else if (index < fOffsets.size()) {
|
||||
// fOffsets[index] = offset;
|
||||
//} else {
|
||||
// fOffsets.resize(index + 1);
|
||||
// fOffsets[index] = offset;
|
||||
//}
|
||||
size_t index = SkToSizeT(iRef.fValue - 1);
|
||||
if (index == fOffsets.size()) {
|
||||
fOffsets.push_back(offset);
|
||||
} else if (index < fOffsets.size()) {
|
||||
fOffsets[index] = offset;
|
||||
} else {
|
||||
fOffsets.resize(index + 1);
|
||||
fOffsets[index] = offset;
|
||||
}
|
||||
}
|
||||
|
||||
SkPDFFileOffset SkPDFOffsetMap::get(SkPDFIndirectReference r) {
|
||||
@ -76,6 +72,18 @@ void SkPDFObjectSerializer::serializeHeader(SkWStream* wStream,
|
||||
}
|
||||
#undef SKPDF_MAGIC
|
||||
|
||||
SkWStream* SkPDFObjectSerializer::beginObject(SkPDFIndirectReference ref, SkWStream* wStream) {
|
||||
SkASSERT(ref.fValue > 0);
|
||||
fOffsets.set(ref, this->offset(wStream));
|
||||
wStream->writeDecAsText(ref.fValue);
|
||||
wStream->writeText(" 0 obj\n"); // Generation number is always 0.
|
||||
return wStream;
|
||||
}
|
||||
|
||||
void SkPDFObjectSerializer::endObject(SkWStream* wStream) {
|
||||
wStream->writeText("\nendobj\n");
|
||||
}
|
||||
|
||||
void SkPDFObjectSerializer::serializeObject(const sk_sp<SkPDFObject>& object,
|
||||
SkWStream* wStream) {
|
||||
SkPDFObjNumMap objNumMap;
|
||||
@ -83,16 +91,15 @@ void SkPDFObjectSerializer::serializeObject(const sk_sp<SkPDFObject>& object,
|
||||
objNumMap.addObjectRecursively(object.get());
|
||||
|
||||
for (const sk_sp<SkPDFObject>& object : objNumMap.fObjects) {
|
||||
fOffsets.set(object->fIndirectReference, this->offset(wStream));
|
||||
wStream->writeDecAsText(object->fIndirectReference.fValue);
|
||||
wStream->writeText(" 0 obj\n"); // Generation number is always 0.
|
||||
this->beginObject(object->fIndirectReference, wStream);
|
||||
object->emitObject(wStream);
|
||||
wStream->writeText("\nendobj\n");
|
||||
this->endObject(wStream);
|
||||
object->drop();
|
||||
}
|
||||
fNextObjectNumber = objNumMap.fNextObjectNumber; // save for later.
|
||||
}
|
||||
|
||||
|
||||
// Xref table and footer
|
||||
void SkPDFObjectSerializer::serializeFooter(SkWStream* wStream,
|
||||
const sk_sp<SkPDFObject> docCatalog,
|
||||
@ -105,6 +112,7 @@ void SkPDFObjectSerializer::serializeFooter(SkWStream* wStream,
|
||||
wStream->writeDecAsText(objCount);
|
||||
wStream->writeText("\n0000000000 65535 f \n");
|
||||
for (int i = 1; i < objCount; ++i) {
|
||||
SkASSERT(fOffsets.get(SkPDFIndirectReference{i}).fValue > 0);
|
||||
wStream->writeBigDecAsText(fOffsets.get(SkPDFIndirectReference{i}).fValue, 10);
|
||||
wStream->writeText(" 00000 n \n");
|
||||
}
|
||||
@ -219,6 +227,19 @@ void SkPDFDocument::serialize(const sk_sp<SkPDFObject>& object) {
|
||||
fObjectSerializer.serializeObject(object, this->getStream());
|
||||
}
|
||||
|
||||
SkPDFIndirectReference SkPDFDocument::reserve() {
|
||||
++fOutstandingRefs;
|
||||
return SkPDFIndirectReference{fObjectSerializer.fNextObjectNumber++};
|
||||
};
|
||||
|
||||
SkWStream* SkPDFDocument::beginObject(SkPDFIndirectReference ref) {
|
||||
--fOutstandingRefs;
|
||||
return fObjectSerializer.beginObject(ref, this->getStream());
|
||||
};
|
||||
void SkPDFDocument::endObject() {
|
||||
fObjectSerializer.endObject(this->getStream());
|
||||
};
|
||||
|
||||
static SkSize operator*(SkISize u, SkScalar s) { return SkSize{u.width() * s, u.height() * s}; }
|
||||
static SkSize operator*(SkSize u, SkScalar s) { return SkSize{u.width() * s, u.height() * s}; }
|
||||
|
||||
@ -554,10 +575,11 @@ void SkPDFDocument::onClose(SkWStream* stream) {
|
||||
}
|
||||
}
|
||||
|
||||
// Build font subsetting info before calling addObjectRecursively().
|
||||
SkPDFCanon* canon = &fCanon;
|
||||
fFonts.foreach([canon](SkPDFFont* p){ p->getFontSubset(canon); });
|
||||
// Build font subsetting info before serializing the catalog and all of
|
||||
// its transitive dependecies.
|
||||
fFonts.foreach([this](SkPDFFont* p){ p->getFontSubset(this); });
|
||||
fObjectSerializer.serializeObject(docCatalog, this->getStream());
|
||||
SkASSERT(fOutstandingRefs == 0);
|
||||
fObjectSerializer.serializeFooter(this->getStream(), docCatalog, fID);
|
||||
this->reset();
|
||||
}
|
||||
|
@ -43,6 +43,8 @@ struct SkPDFObjectSerializer {
|
||||
SkPDFObjectSerializer(const SkPDFObjectSerializer&) = delete;
|
||||
SkPDFObjectSerializer& operator=(const SkPDFObjectSerializer&) = delete;
|
||||
|
||||
SkWStream* beginObject(SkPDFIndirectReference, SkWStream*);
|
||||
void endObject(SkWStream*);
|
||||
void serializeHeader(SkWStream*, const SkPDF::Metadata&);
|
||||
void serializeObject(const sk_sp<SkPDFObject>&, SkWStream*);
|
||||
void serializeFooter(SkWStream*, const sk_sp<SkPDFObject>, sk_sp<SkPDFObject>);
|
||||
@ -79,6 +81,10 @@ public:
|
||||
// Returns -1 if no mark ID.
|
||||
int getMarkIdForNodeId(int nodeId);
|
||||
|
||||
SkPDFIndirectReference reserve();
|
||||
SkWStream* beginObject(SkPDFIndirectReference);
|
||||
void endObject();
|
||||
|
||||
private:
|
||||
sk_sp<SkPDFTag> recursiveBuildTagTree(const SkPDF::StructureElementNode& node,
|
||||
sk_sp<SkPDFTag> parent);
|
||||
@ -105,6 +111,8 @@ private:
|
||||
// A mapping from node ID to tag for fast lookup.
|
||||
SkTHashMap<int, sk_sp<SkPDFTag>> fNodeIdToTag;
|
||||
|
||||
int fOutstandingRefs = 0;
|
||||
|
||||
void reset();
|
||||
};
|
||||
|
||||
|
@ -16,6 +16,7 @@
|
||||
#include "SkPDFCanon.h"
|
||||
#include "SkPDFConvertType1FontStream.h"
|
||||
#include "SkPDFDevice.h"
|
||||
#include "SkPDFDocumentPriv.h"
|
||||
#include "SkPDFMakeCIDGlyphWidthsArray.h"
|
||||
#include "SkPDFMakeToUnicodeCmap.h"
|
||||
#include "SkPDFResourceDict.h"
|
||||
@ -55,7 +56,7 @@ static const int32_t kPdfSymbolic = 4;
|
||||
struct SkPDFType0Font final : public SkPDFFont {
|
||||
SkPDFType0Font(SkPDFFont::Info, const SkAdvancedTypefaceMetrics&);
|
||||
~SkPDFType0Font() override;
|
||||
void getFontSubset(SkPDFCanon*) override;
|
||||
void getFontSubset(SkPDFDocument*) override;
|
||||
#ifdef SK_DEBUG
|
||||
void emitObject(SkWStream*) const override;
|
||||
bool fPopulated;
|
||||
@ -66,13 +67,13 @@ struct SkPDFType0Font final : public SkPDFFont {
|
||||
struct SkPDFType1Font final : public SkPDFFont {
|
||||
SkPDFType1Font(SkPDFFont::Info, const SkAdvancedTypefaceMetrics&, SkPDFCanon*);
|
||||
~SkPDFType1Font() override {}
|
||||
void getFontSubset(SkPDFCanon*) override {} // TODO(halcanary): implement
|
||||
void getFontSubset(SkPDFDocument*) override {} // TODO(halcanary): implement
|
||||
};
|
||||
|
||||
struct SkPDFType3Font final : public SkPDFFont {
|
||||
SkPDFType3Font(SkPDFFont::Info, const SkAdvancedTypefaceMetrics&);
|
||||
~SkPDFType3Font() override {}
|
||||
void getFontSubset(SkPDFCanon*) override;
|
||||
void getFontSubset(SkPDFDocument*) override;
|
||||
};
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
@ -343,7 +344,8 @@ static sk_sp<SkData> stream_to_data(std::unique_ptr<SkStreamAsset> stream) {
|
||||
}
|
||||
#endif // SK_PDF_SUBSET_SUPPORTED
|
||||
|
||||
void SkPDFType0Font::getFontSubset(SkPDFCanon* canon) {
|
||||
void SkPDFType0Font::getFontSubset(SkPDFDocument* doc) {
|
||||
SkPDFCanon* canon = doc->canon();
|
||||
const SkAdvancedTypefaceMetrics* metricsPtr =
|
||||
SkPDFFont::GetMetrics(this->typeface(), canon);
|
||||
SkASSERT(metricsPtr);
|
||||
@ -644,12 +646,13 @@ static ImageAndOffset to_image(SkGlyphID gid, SkGlyphCache* cache) {
|
||||
}
|
||||
}
|
||||
|
||||
static void add_type3_font_info(SkPDFCanon* canon,
|
||||
static void add_type3_font_info(SkPDFDocument* doc,
|
||||
SkPDFDict* font,
|
||||
SkTypeface* typeface,
|
||||
const SkPDFGlyphUse& subset,
|
||||
SkGlyphID firstGlyphID,
|
||||
SkGlyphID lastGlyphID) {
|
||||
SkPDFCanon* canon = doc->canon();
|
||||
const SkAdvancedTypefaceMetrics* metrics = SkPDFFont::GetMetrics(typeface, canon);
|
||||
SkASSERT(lastGlyphID >= firstGlyphID);
|
||||
// Remove unused glyphs at the end of the range.
|
||||
@ -734,7 +737,7 @@ static void add_type3_font_info(SkPDFCanon* canon,
|
||||
content.writeText("/X Do\n");
|
||||
auto proc = sk_make_sp<SkPDFStream>(content.detachAsStream());
|
||||
auto d0 = sk_make_sp<SkPDFDict>();
|
||||
d0->insertObjRef("X", SkPDFCreateBitmapObject(std::move(pimg.fImage)));
|
||||
d0->insertRef("X", SkPDFSerializeImage(pimg.fImage.get(), doc));
|
||||
auto d1 = sk_make_sp<SkPDFDict>();
|
||||
d1->insertObject("XObject", std::move(d0));
|
||||
proc->dict()->insertObject("Resources", std::move(d1));
|
||||
@ -796,8 +799,8 @@ SkPDFType3Font::SkPDFType3Font(SkPDFFont::Info info,
|
||||
const SkAdvancedTypefaceMetrics& metrics)
|
||||
: SkPDFFont(std::move(info)) {}
|
||||
|
||||
void SkPDFType3Font::getFontSubset(SkPDFCanon* canon) {
|
||||
add_type3_font_info(canon, this, this->typeface(), this->glyphUsage(),
|
||||
void SkPDFType3Font::getFontSubset(SkPDFDocument* doc) {
|
||||
add_type3_font_info(doc, this, this->typeface(), this->glyphUsage(),
|
||||
this->firstGlyphID(), this->lastGlyphID());
|
||||
}
|
||||
|
||||
|
@ -99,7 +99,7 @@ public:
|
||||
/** Subset the font based on current usage.
|
||||
* Must be called before emitObject().
|
||||
*/
|
||||
virtual void getFontSubset(SkPDFCanon*) = 0;
|
||||
virtual void getFontSubset(SkPDFDocument*) = 0;
|
||||
|
||||
/**
|
||||
* Return false iff the typeface has its NotEmbeddable flag set.
|
||||
|
@ -202,6 +202,10 @@ void SkPDFUnion::emitObject(SkWStream* stream) const {
|
||||
case Type::kObject:
|
||||
fObject->emitObject(stream);
|
||||
return;
|
||||
case Type::kRef:
|
||||
stream->writeDecAsText(fIntValue);
|
||||
stream->writeText(" 0 R"); // Generation number is always 0.
|
||||
return;
|
||||
default:
|
||||
SkDEBUGFAIL("SkPDFUnion::emitObject with bad type");
|
||||
}
|
||||
@ -218,6 +222,7 @@ void SkPDFUnion::addResources(SkPDFObjNumMap* objNumMap) const {
|
||||
case Type::kString:
|
||||
case Type::kNameSkS:
|
||||
case Type::kStringSkS:
|
||||
case Type::kRef:
|
||||
return; // These have no resources.
|
||||
case Type::kObjRef:
|
||||
objNumMap->addObjectRecursively(fObject);
|
||||
@ -281,6 +286,10 @@ SkPDFUnion SkPDFUnion::Object(sk_sp<SkPDFObject> objSp) {
|
||||
return u;
|
||||
}
|
||||
|
||||
SkPDFUnion SkPDFUnion::Ref(SkPDFIndirectReference ref) {
|
||||
return SkASSERT(ref.fValue > 0), SkPDFUnion(Type::kRef, (int32_t)ref.fValue);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#if 0 // Enable if needed.
|
||||
@ -372,6 +381,10 @@ void SkPDFArray::appendObjRef(sk_sp<SkPDFObject> objSp) {
|
||||
this->append(SkPDFUnion::ObjRef(std::move(objSp)));
|
||||
}
|
||||
|
||||
void SkPDFArray::appendRef(SkPDFIndirectReference ref) {
|
||||
this->append(SkPDFUnion::Ref(ref));
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
SkPDFDict::~SkPDFDict() { this->drop(); }
|
||||
@ -420,6 +433,10 @@ void SkPDFDict::reserve(int n) {
|
||||
fRecords.reserve(n);
|
||||
}
|
||||
|
||||
void SkPDFDict::insertRef(const char key[], SkPDFIndirectReference ref) {
|
||||
fRecords.emplace_back(Record{SkPDFUnion::Name(key), SkPDFUnion::Ref(ref)});
|
||||
}
|
||||
|
||||
void SkPDFDict::insertObjRef(const char key[], sk_sp<SkPDFObject> objSp) {
|
||||
fRecords.emplace_back(Record{SkPDFUnion::Name(key), SkPDFUnion::ObjRef(std::move(objSp))});
|
||||
}
|
||||
@ -538,7 +555,7 @@ void SkPDFSharedStream::addResources(
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
SkPDFStream:: SkPDFStream(sk_sp<SkData> data) {
|
||||
SkPDFStream::SkPDFStream(sk_sp<SkData> data) {
|
||||
this->setData(skstd::make_unique<SkMemoryStream>(std::move(data)));
|
||||
}
|
||||
|
||||
|
@ -145,6 +145,8 @@ public:
|
||||
static SkPDFUnion Object(sk_sp<SkPDFObject>);
|
||||
static SkPDFUnion ObjRef(sk_sp<SkPDFObject>);
|
||||
|
||||
static SkPDFUnion Ref(SkPDFIndirectReference);
|
||||
|
||||
/** These two non-virtual methods mirror SkPDFObject's
|
||||
corresponding virtuals. */
|
||||
void emitObject(SkWStream*) const;
|
||||
@ -176,6 +178,7 @@ private:
|
||||
kStringSkS,
|
||||
kObjRef,
|
||||
kObject,
|
||||
kRef,
|
||||
};
|
||||
Type fType;
|
||||
|
||||
@ -252,6 +255,7 @@ public:
|
||||
void appendString(const SkString&);
|
||||
void appendObject(sk_sp<SkPDFObject>);
|
||||
void appendObjRef(sk_sp<SkPDFObject>);
|
||||
void appendRef(SkPDFIndirectReference);
|
||||
|
||||
private:
|
||||
std::vector<SkPDFUnion> fValues;
|
||||
@ -310,6 +314,7 @@ public:
|
||||
void insertObject(const SkString& key, sk_sp<SkPDFObject>);
|
||||
void insertObjRef(const char key[], sk_sp<SkPDFObject>);
|
||||
void insertObjRef(const SkString& key, sk_sp<SkPDFObject>);
|
||||
void insertRef(const char key[], SkPDFIndirectReference);
|
||||
|
||||
/** Add the value to the dictionary with the given key.
|
||||
* @param key The text of the key for this dictionary entry.
|
||||
|
Loading…
Reference in New Issue
Block a user