JPEG(JFIF only) directly embedded into PDF

R=reed@google.com, djsollen@google.com, mtklein@google.com

Author: halcanary@google.com

Review URL: https://codereview.chromium.org/515493003
This commit is contained in:
halcanary 2014-08-27 13:00:54 -07:00 committed by Commit bot
parent 9694d63cf0
commit daefa5b340
5 changed files with 204 additions and 2 deletions

View File

@ -143,6 +143,7 @@
'../tests/OSPathTest.cpp',
'../tests/ObjectPoolTest.cpp',
'../tests/OnceTest.cpp',
'../tests/PDFJpegEmbedTest.cpp',
'../tests/PDFPrimitivesTest.cpp',
'../tests/PackBitsTest.cpp',
'../tests/PaintTest.cpp',

View File

@ -2182,8 +2182,8 @@ void SkPDFDevice::internalDrawBitmap(const SkMatrix& origMatrix,
return;
}
SkAutoTUnref<SkPDFImage> image(
SkPDFImage::CreateImage(*bitmap, subset, fEncoder));
SkAutoTUnref<SkPDFObject> image(
SkPDFCreateImageObject(*bitmap, subset, fEncoder));
if (!image) {
return;
}

View File

@ -13,6 +13,7 @@
#include "SkData.h"
#include "SkFlate.h"
#include "SkPDFCatalog.h"
#include "SkPixelRef.h"
#include "SkRect.h"
#include "SkStream.h"
#include "SkString.h"
@ -628,3 +629,91 @@ bool SkPDFImage::populate(SkPDFCatalog* catalog) {
}
return true;
}
namespace {
/**
* This PDFObject assumes that its constructor was handed
* Jpeg-encoded data that can be directly embedded into a PDF.
*/
class PDFJPEGImage : public SkPDFObject {
SkAutoTUnref<SkData> fData;
int fWidth;
int fHeight;
public:
PDFJPEGImage(SkData* data, int width, int height)
: fData(SkRef(data)), fWidth(width), fHeight(height) {}
virtual void getResources(const SkTSet<SkPDFObject*>&,
SkTSet<SkPDFObject*>*) SK_OVERRIDE {}
virtual void emitObject(
SkWStream* stream,
SkPDFCatalog* catalog, bool indirect) SK_OVERRIDE {
if (indirect) {
this->emitIndirectObject(stream, catalog);
return;
}
SkASSERT(fData.get());
const char kPrefaceFormat[] =
"<<"
"/Type /XObject\n"
"/Subtype /Image\n"
"/Width %d\n"
"/Height %d\n"
"/ColorSpace /DeviceRGB\n"
"/BitsPerComponent 8\n"
"/Filter /DCTDecode\n"
"/ColorTransform 0\n"
"/Length " SK_SIZE_T_SPECIFIER "\n"
">> stream\n";
SkString preface(
SkStringPrintf(kPrefaceFormat, fWidth, fHeight, fData->size()));
const char kPostface[] = "\nendstream";
stream->write(preface.c_str(), preface.size());
stream->write(fData->data(), fData->size());
stream->write(kPostface, sizeof(kPostface));
}
};
/**
* If the bitmap is not subsetted, return its encoded data, if
* availible.
*/
static inline SkData* ref_encoded_data(const SkBitmap& bm) {
if ((NULL == bm.pixelRef())
|| !bm.pixelRefOrigin().isZero()
|| (bm.info().dimensions() != bm.pixelRef()->info().dimensions())) {
return NULL;
}
return bm.pixelRef()->refEncodedData();
}
/*
* This functions may give false negatives but no false positives.
*/
static bool is_jfif_jpeg(SkData* data) {
if (!data || (data->size() < 11)) {
return false;
}
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 ...
return ((0 == memcmp(data->bytes(), bytesZeroToThree,
sizeof(bytesZeroToThree)))
&& (0 == memcmp(data->bytes() + 6, bytesSixToTen,
sizeof(bytesSixToTen))));
}
} // namespace
SkPDFObject* SkPDFCreateImageObject(
const SkBitmap& bitmap,
const SkIRect& subset,
SkPicture::EncodeBitmap encoder) {
if (SkIRect::MakeWH(bitmap.width(), bitmap.height()) == subset) {
SkAutoTUnref<SkData> encodedData(ref_encoded_data(bitmap));
if (is_jfif_jpeg(encodedData)) {
return SkNEW_ARGS(PDFJPEGImage,
(encodedData, bitmap.width(), bitmap.height()));
}
}
return SkPDFImage::CreateImage(bitmap, subset, encoder);
}

View File

@ -17,9 +17,20 @@
#include "SkRefCnt.h"
class SkBitmap;
class SkData;
class SkPDFCatalog;
struct SkIRect;
/**
* Return the mose efficient availible encoding of the given bitmap.
*
* If the bitmap has encoded JPEG data and that data can be embedded
* into the PDF output stream directly, use that. Otherwise, fall
* back on SkPDFImage::CreateImage.
*/
SkPDFObject* SkPDFCreateImageObject(
const SkBitmap&, const SkIRect& subset, SkPicture::EncodeBitmap);
/** \class SkPDFImage
An image XObject.

101
tests/PDFJpegEmbedTest.cpp Normal file
View File

@ -0,0 +1,101 @@
/*
* Copyright 2014 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "SkDocument.h"
#include "SkCanvas.h"
#include "SkImageGenerator.h"
#include "SkData.h"
#include "SkStream.h"
#include "SkDecodingImageGenerator.h"
#include "Resources.h"
#include "Test.h"
// Returned bitmap is lazy. Only lazy bitmaps hold onto the original data.
static SkBitmap bitmap_from_data(SkData* data) {
SkASSERT(data);
SkBitmap bm;
SkInstallDiscardablePixelRef(
SkDecodingImageGenerator::Create(
data, SkDecodingImageGenerator::Options()), &bm);
return bm;
}
static bool is_subset_of(SkData* smaller, SkData* larger) {
SkASSERT(smaller && larger);
if (smaller->size() > larger->size()) {
return false;
}
size_t size = smaller->size();
size_t size_diff = larger->size() - size;
for (size_t i = 0; i <= size_diff; ++i) {
if (0 == memcmp(larger->bytes() + i, smaller->bytes(), size)) {
return true;
}
}
return false;
}
static SkData* load_resource(
skiatest::Reporter* r, const char* test, const char* filename) {
SkString path(GetResourcePath(filename));
SkData* data = SkData::NewFromFileName(path.c_str());
if (!data && r->verbose()) {
SkDebugf("\n%s: Resource '%s' can not be found.\n",
test, filename);
}
return data; // May return NULL.
}
/**
* Test that for Jpeg files that use the JFIF colorspace, they are
* directly embedded into the PDF (without re-encoding) when that
* makes sense.
*/
DEF_TEST(PDFJpegEmbedTest, r) {
const char test[] = "PDFJpegEmbedTest";
SkAutoTUnref<SkData> mandrillData(
load_resource(r, test, "mandrill_512_q075.jpg"));
SkAutoTUnref<SkData> cmykData(load_resource(r, test, "CMYK.jpg"));
if (!mandrillData || !cmykData) {
return;
}
SkDynamicMemoryWStream pdf;
SkAutoTUnref<SkDocument> document(SkDocument::CreatePDF(&pdf));
SkCanvas* canvas = document->beginPage(642, 1028);
canvas->clear(SK_ColorLTGRAY);
SkBitmap bm1(bitmap_from_data(mandrillData));
canvas->drawBitmap(bm1, 65.0, 0.0, NULL);
SkBitmap bm2(bitmap_from_data(cmykData));
canvas->drawBitmap(bm2, 0.0, 512.0, NULL);
canvas->flush();
document->endPage();
document->close();
SkAutoTUnref<SkData> pdfData(pdf.copyToData());
SkASSERT(pdfData);
pdf.reset();
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());
}
}
}