PDF: Correctly embed JPEG images directly into PDF output.

We only embed images with YUV planes.  That should only grab the
subset of color JPEGs supported by PDF.

BUG=skia:3180

Review URL: https://codereview.chromium.org/1025773002
This commit is contained in:
halcanary 2015-04-17 13:27:24 -07:00 committed by Commit bot
parent 673e902c9b
commit a8448bc3df
4 changed files with 113 additions and 33 deletions

View File

@ -29,3 +29,23 @@ DEF_SIMPLE_GM(repeated_bitmap, canvas, 576, 576) {
}
}
}
DEF_SIMPLE_GM(repeated_bitmap_jpg, canvas, 576, 576) {
sk_tool_utils::draw_checkerboard(canvas, 0xFF999999, SK_ColorWHITE, 12);
SkRect rect = SkRect::MakeLTRB(-68.0f, -68.0f, 68.0f, 68.0f);
SkPaint paint;
paint.setColor(0xFF333333);
SkBitmap bm;
if (GetResourceAsBitmap("color_wheel.jpg", &bm)) {
for (int j = 0; j < 4; ++j) {
for (int i = 0; i < 4; ++i) {
SkAutoCanvasRestore autoCanvasRestore(canvas, true);
canvas->translate(96.0f + 192.0f * SkIntToScalar(i),
96.0f + 192.0f * SkIntToScalar(j));
canvas->rotate(18.0f * (i + 4 * j));
canvas->drawRect(rect, paint);
canvas->drawBitmap(bm, -64.0f, -64.0f);
}
}
}
}

View File

@ -6,9 +6,12 @@
*/
#include "SkColorPriv.h"
#include "SkData.h"
#include "SkFlate.h"
#include "SkImageGenerator.h"
#include "SkPDFBitmap.h"
#include "SkPDFCanon.h"
#include "SkPixelRef.h"
#include "SkStream.h"
#include "SkUnPreMultiply.h"
@ -283,8 +286,23 @@ void PDFAlphaBitmap::emitObject(SkWStream* stream,
////////////////////////////////////////////////////////////////////////////////
void SkPDFBitmap::addResources(SkPDFObjNumMap* catalog,
const SkPDFSubstituteMap& substitutes) const {
namespace {
class PDFDefaultBitmap : public SkPDFBitmap {
public:
const SkAutoTUnref<SkPDFObject> fSMask;
void emitObject(SkWStream*,
const SkPDFObjNumMap&,
const SkPDFSubstituteMap&) override;
void addResources(SkPDFObjNumMap*,
const SkPDFSubstituteMap&) const override;
PDFDefaultBitmap(const SkBitmap& bm, SkPDFObject* smask)
: SkPDFBitmap(bm), fSMask(smask) {}
};
} // namespace
void PDFDefaultBitmap::addResources(
SkPDFObjNumMap* catalog,
const SkPDFSubstituteMap& substitutes) const {
if (fSMask.get()) {
SkPDFObject* obj = substitutes.getSubstitute(fSMask.get());
SkASSERT(obj);
@ -324,9 +342,9 @@ static SkPDFArray* make_indexed_color_space(const SkColorTable* table) {
return result;
}
void SkPDFBitmap::emitObject(SkWStream* stream,
const SkPDFObjNumMap& objNumMap,
const SkPDFSubstituteMap& substitutes) {
void PDFDefaultBitmap::emitObject(SkWStream* stream,
const SkPDFObjNumMap& objNumMap,
const SkPDFSubstituteMap& substitutes) {
SkAutoLockPixels autoLockPixels(fBitmap);
SkASSERT(fBitmap.colorType() != kIndex_8_SkColorType ||
fBitmap.getColorTable());
@ -357,19 +375,13 @@ void SkPDFBitmap::emitObject(SkWStream* stream,
}
pdfDict.insertName("Filter", "FlateDecode");
pdfDict.insertInt("Length", asset->getLength());
pdfDict.emitObject(stream, objNumMap,substitutes);
pdfDict.emitObject(stream, objNumMap, substitutes);
pdf_stream_begin(stream);
stream->writeStream(asset.get(), asset->getLength());
pdf_stream_end(stream);
}
SkPDFBitmap::SkPDFBitmap(const SkBitmap& bm,
SkPDFObject* smask)
: fBitmap(bm), fSMask(smask) {}
SkPDFBitmap::~SkPDFBitmap() {}
////////////////////////////////////////////////////////////////////////////////
static const SkBitmap& immutable_bitmap(const SkBitmap& bm, SkBitmap* copy) {
@ -381,6 +393,59 @@ static const SkBitmap& immutable_bitmap(const SkBitmap& bm, SkBitmap* copy) {
return *copy;
}
namespace {
/**
* This PDFObject assumes that its constructor was handed YUV JFIF
* Jpeg-encoded data that can be directly embedded into a PDF.
*/
class PDFJpegBitmap : public SkPDFBitmap {
public:
SkAutoTUnref<SkData> fData;
PDFJpegBitmap(const SkBitmap& bm, SkData* data)
: SkPDFBitmap(bm), fData(SkRef(data)) {}
void emitObject(SkWStream*,
const SkPDFObjNumMap&,
const SkPDFSubstituteMap&) override;
};
void PDFJpegBitmap::emitObject(SkWStream* stream,
const SkPDFObjNumMap& objNumMap,
const SkPDFSubstituteMap& substituteMap) {
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);
pdfDict.insertName("Filter", "DCTDecode");
pdfDict.insertInt("ColorTransform", 0);
pdfDict.insertInt("Length", SkToInt(fData->size()));
pdfDict.emitObject(stream, objNumMap, substituteMap);
pdf_stream_begin(stream);
stream->write(fData->data(), fData->size());
pdf_stream_end(stream);
}
} // namespace
////////////////////////////////////////////////////////////////////////////////
static bool is_jfif_yuv_jpeg(SkData* data) {
const uint8_t bytesZeroToThree[] = {0xFF, 0xD8, 0xFF, 0xE0};
const uint8_t bytesSixToTen[] = {'J', 'F', 'I', 'F', 0};
// 0 1 2 3 4 5 6 7 8 9 10
// FF D8 FF E0 ?? ?? 'J' 'F' 'I' 'F' 00 ...
if (data->size() < 11 ||
0 != memcmp(data->bytes(), bytesZeroToThree,
sizeof(bytesZeroToThree)) ||
0 != memcmp(data->bytes() + 6, bytesSixToTen, sizeof(bytesSixToTen))) {
return false;
}
SkAutoTDelete<SkImageGenerator> gen(SkImageGenerator::NewFromData(data));
SkISize sizes[3];
// Only YUV JPEG allows access to YUV planes.
return gen && gen->getYUV8Planes(sizes, NULL, NULL, NULL);
}
SkPDFBitmap* SkPDFBitmap::Create(SkPDFCanon* canon, const SkBitmap& bitmap) {
SkASSERT(canon);
if (!SkColorTypeIsValid(bitmap.colorType()) ||
@ -395,11 +460,23 @@ SkPDFBitmap* SkPDFBitmap::Create(SkPDFCanon* canon, const SkBitmap& bitmap) {
if (SkPDFBitmap* canonBitmap = canon->findBitmap(bm)) {
return SkRef(canonBitmap);
}
if (bm.pixelRef() && bm.pixelRefOrigin().isZero() &&
bm.dimensions() == bm.pixelRef()->info().dimensions()) {
// Requires the bitmap to be backed by lazy pixels.
SkAutoTUnref<SkData> data(bm.pixelRef()->refEncodedData());
if (data && is_jfif_yuv_jpeg(data)) {
SkPDFBitmap* pdfBitmap = SkNEW_ARGS(PDFJpegBitmap, (bm, data));
canon->addBitmap(pdfBitmap);
return pdfBitmap;
}
}
SkPDFObject* smask = NULL;
if (!bm.isOpaque() && !SkBitmap::ComputeIsOpaque(bm)) {
smask = SkNEW_ARGS(PDFAlphaBitmap, (bm));
}
SkPDFBitmap* pdfBitmap = SkNEW_ARGS(SkPDFBitmap, (bm, smask));
SkPDFBitmap* pdfBitmap = SkNEW_ARGS(PDFDefaultBitmap, (bm, smask));
canon->addBitmap(pdfBitmap);
return pdfBitmap;
}

View File

@ -26,22 +26,15 @@ class SkPDFBitmap : public SkPDFObject {
public:
// Returns NULL on unsupported bitmap;
static SkPDFBitmap* Create(SkPDFCanon*, const SkBitmap&);
~SkPDFBitmap();
void emitObject(SkWStream*,
const SkPDFObjNumMap& objNumMap,
const SkPDFSubstituteMap& substitutes) override;
void addResources(SkPDFObjNumMap*,
const SkPDFSubstituteMap&) const override;
bool equals(const SkBitmap& other) const {
return fBitmap.getGenerationID() == other.getGenerationID() &&
fBitmap.pixelRefOrigin() == other.pixelRefOrigin() &&
fBitmap.dimensions() == other.dimensions();
}
private:
protected:
const SkBitmap fBitmap;
const SkAutoTUnref<SkPDFObject> fSMask;
SkPDFBitmap(const SkBitmap&, SkPDFObject*);
SkPDFBitmap(const SkBitmap& bm) : fBitmap(bm) {}
};
#endif // SkPDFBitmap_DEFINED

View File

@ -81,19 +81,9 @@ DEF_TEST(PDFJpegEmbedTest, r) {
SkASSERT(pdfData);
pdf.reset();
// Test disabled, waiting on resolution to http://skbug.com/3180
// REPORTER_ASSERT(r, is_subset_of(mandrillData, pdfData));
REPORTER_ASSERT(r, is_subset_of(mandrillData, pdfData));
// This JPEG uses a nonstandard colorspace - it can not be
// embedded into the PDF directly.
REPORTER_ASSERT(r, !is_subset_of(cmykData, pdfData));
// The following is for debugging purposes only.
const char* outputPath = getenv("SKIA_TESTS_PDF_JPEG_EMBED_OUTPUT_PATH");
if (outputPath) {
SkFILEWStream output(outputPath);
if (output.isValid()) {
output.write(pdfData->data(), pdfData->size());
}
}
}