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:
parent
673e902c9b
commit
a8448bc3df
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -6,9 +6,12 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "SkColorPriv.h"
|
#include "SkColorPriv.h"
|
||||||
|
#include "SkData.h"
|
||||||
#include "SkFlate.h"
|
#include "SkFlate.h"
|
||||||
|
#include "SkImageGenerator.h"
|
||||||
#include "SkPDFBitmap.h"
|
#include "SkPDFBitmap.h"
|
||||||
#include "SkPDFCanon.h"
|
#include "SkPDFCanon.h"
|
||||||
|
#include "SkPixelRef.h"
|
||||||
#include "SkStream.h"
|
#include "SkStream.h"
|
||||||
#include "SkUnPreMultiply.h"
|
#include "SkUnPreMultiply.h"
|
||||||
|
|
||||||
@ -283,8 +286,23 @@ void PDFAlphaBitmap::emitObject(SkWStream* stream,
|
|||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
void SkPDFBitmap::addResources(SkPDFObjNumMap* catalog,
|
namespace {
|
||||||
const SkPDFSubstituteMap& substitutes) const {
|
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()) {
|
if (fSMask.get()) {
|
||||||
SkPDFObject* obj = substitutes.getSubstitute(fSMask.get());
|
SkPDFObject* obj = substitutes.getSubstitute(fSMask.get());
|
||||||
SkASSERT(obj);
|
SkASSERT(obj);
|
||||||
@ -324,9 +342,9 @@ static SkPDFArray* make_indexed_color_space(const SkColorTable* table) {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
void SkPDFBitmap::emitObject(SkWStream* stream,
|
void PDFDefaultBitmap::emitObject(SkWStream* stream,
|
||||||
const SkPDFObjNumMap& objNumMap,
|
const SkPDFObjNumMap& objNumMap,
|
||||||
const SkPDFSubstituteMap& substitutes) {
|
const SkPDFSubstituteMap& substitutes) {
|
||||||
SkAutoLockPixels autoLockPixels(fBitmap);
|
SkAutoLockPixels autoLockPixels(fBitmap);
|
||||||
SkASSERT(fBitmap.colorType() != kIndex_8_SkColorType ||
|
SkASSERT(fBitmap.colorType() != kIndex_8_SkColorType ||
|
||||||
fBitmap.getColorTable());
|
fBitmap.getColorTable());
|
||||||
@ -357,19 +375,13 @@ void SkPDFBitmap::emitObject(SkWStream* stream,
|
|||||||
}
|
}
|
||||||
pdfDict.insertName("Filter", "FlateDecode");
|
pdfDict.insertName("Filter", "FlateDecode");
|
||||||
pdfDict.insertInt("Length", asset->getLength());
|
pdfDict.insertInt("Length", asset->getLength());
|
||||||
pdfDict.emitObject(stream, objNumMap,substitutes);
|
pdfDict.emitObject(stream, objNumMap, substitutes);
|
||||||
|
|
||||||
pdf_stream_begin(stream);
|
pdf_stream_begin(stream);
|
||||||
stream->writeStream(asset.get(), asset->getLength());
|
stream->writeStream(asset.get(), asset->getLength());
|
||||||
pdf_stream_end(stream);
|
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) {
|
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;
|
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) {
|
SkPDFBitmap* SkPDFBitmap::Create(SkPDFCanon* canon, const SkBitmap& bitmap) {
|
||||||
SkASSERT(canon);
|
SkASSERT(canon);
|
||||||
if (!SkColorTypeIsValid(bitmap.colorType()) ||
|
if (!SkColorTypeIsValid(bitmap.colorType()) ||
|
||||||
@ -395,11 +460,23 @@ SkPDFBitmap* SkPDFBitmap::Create(SkPDFCanon* canon, const SkBitmap& bitmap) {
|
|||||||
if (SkPDFBitmap* canonBitmap = canon->findBitmap(bm)) {
|
if (SkPDFBitmap* canonBitmap = canon->findBitmap(bm)) {
|
||||||
return SkRef(canonBitmap);
|
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;
|
SkPDFObject* smask = NULL;
|
||||||
if (!bm.isOpaque() && !SkBitmap::ComputeIsOpaque(bm)) {
|
if (!bm.isOpaque() && !SkBitmap::ComputeIsOpaque(bm)) {
|
||||||
smask = SkNEW_ARGS(PDFAlphaBitmap, (bm));
|
smask = SkNEW_ARGS(PDFAlphaBitmap, (bm));
|
||||||
}
|
}
|
||||||
SkPDFBitmap* pdfBitmap = SkNEW_ARGS(SkPDFBitmap, (bm, smask));
|
SkPDFBitmap* pdfBitmap = SkNEW_ARGS(PDFDefaultBitmap, (bm, smask));
|
||||||
canon->addBitmap(pdfBitmap);
|
canon->addBitmap(pdfBitmap);
|
||||||
return pdfBitmap;
|
return pdfBitmap;
|
||||||
}
|
}
|
||||||
|
@ -26,22 +26,15 @@ class SkPDFBitmap : public SkPDFObject {
|
|||||||
public:
|
public:
|
||||||
// Returns NULL on unsupported bitmap;
|
// Returns NULL on unsupported bitmap;
|
||||||
static SkPDFBitmap* Create(SkPDFCanon*, const SkBitmap&);
|
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 {
|
bool equals(const SkBitmap& other) const {
|
||||||
return fBitmap.getGenerationID() == other.getGenerationID() &&
|
return fBitmap.getGenerationID() == other.getGenerationID() &&
|
||||||
fBitmap.pixelRefOrigin() == other.pixelRefOrigin() &&
|
fBitmap.pixelRefOrigin() == other.pixelRefOrigin() &&
|
||||||
fBitmap.dimensions() == other.dimensions();
|
fBitmap.dimensions() == other.dimensions();
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
protected:
|
||||||
const SkBitmap fBitmap;
|
const SkBitmap fBitmap;
|
||||||
const SkAutoTUnref<SkPDFObject> fSMask;
|
SkPDFBitmap(const SkBitmap& bm) : fBitmap(bm) {}
|
||||||
SkPDFBitmap(const SkBitmap&, SkPDFObject*);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // SkPDFBitmap_DEFINED
|
#endif // SkPDFBitmap_DEFINED
|
||||||
|
@ -81,19 +81,9 @@ DEF_TEST(PDFJpegEmbedTest, r) {
|
|||||||
SkASSERT(pdfData);
|
SkASSERT(pdfData);
|
||||||
pdf.reset();
|
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
|
// This JPEG uses a nonstandard colorspace - it can not be
|
||||||
// embedded into the PDF directly.
|
// embedded into the PDF directly.
|
||||||
REPORTER_ASSERT(r, !is_subset_of(cmykData, pdfData));
|
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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user