Merge perspective feature for PDF.
From Skia's repo:
http://code.google.com/p/skia/source/detail?r=11751
Add SkPDFDeviceFlatenner which extends SkPDFDevice to add support
to flatten the path and the text when we have perspective. prepare
to deprecate SkPDFDevice constructor, and route gm and render_pdfs
to use SkDocument::Create pdf interface instead. - controlled by a
flag add comments where we are supposed to flatten other features
(paint, shaders, ... )
https://code.google.com/p/skia/source/detail?r=11822
Implement perspective for bitmaps in pdf.
https://code.google.com/p/skia/source/detail?r=11937
PDF: support perspective in simple shaders. (this version does not
work well with tilling)
BUG:11384071
Change-Id: I48ea88d231231215349f155d972c6b9e1e127c71
diff --git a/Android.mk b/Android.mk
index 3590893..1e90e52 100644
--- a/Android.mk
+++ b/Android.mk
@@ -351,6 +351,7 @@
src/ports/SkTLS_pthread.cpp \
src/pdf/SkPDFCatalog.cpp \
src/pdf/SkPDFDevice.cpp \
+ src/pdf/SkPDFDeviceFlattener.cpp \
src/pdf/SkPDFDocument.cpp \
src/pdf/SkPDFFont.cpp \
src/pdf/SkPDFFormXObject.cpp \
@@ -571,6 +572,7 @@
$(LOCAL_PATH)/src/gpu \
$(LOCAL_PATH)/src/image \
$(LOCAL_PATH)/src/lazy \
+ $(LOCAL_PATH)/src/pdf \
$(LOCAL_PATH)/src/sfnt \
$(LOCAL_PATH)/src/utils \
external/freetype/include \
diff --git a/gm/gm_error.h b/gm/gm_error.h
index e442f5d..e2274c4 100644
--- a/gm/gm_error.h
+++ b/gm/gm_error.h
@@ -28,6 +28,7 @@
kIntentionallySkipped_ErrorType,
kRenderModeMismatch_ErrorType,
+ kGeneratePdfFailed_ErrorType,
kExpectationsMismatch_ErrorType,
kMissingExpectations_ErrorType,
kWritingReferenceImage_ErrorType,
@@ -45,6 +46,8 @@
return "IntentionallySkipped";
case kRenderModeMismatch_ErrorType:
return "RenderModeMismatch";
+ case kGeneratePdfFailed_ErrorType:
+ return "GeneratePdfFailed";
case kExpectationsMismatch_ErrorType:
return "ExpectationsMismatch";
case kMissingExpectations_ErrorType:
diff --git a/gm/gmmain.cpp b/gm/gmmain.cpp
index 67259f1..ed7e7a8 100644
--- a/gm/gmmain.cpp
+++ b/gm/gmmain.cpp
@@ -23,6 +23,7 @@
#include "SkData.h"
#include "SkDeferredCanvas.h"
#include "SkDevice.h"
+#include "SkDocument.h"
#include "SkDrawFilter.h"
#include "SkForceLinking.h"
#include "SkGPipe.h"
@@ -32,6 +33,7 @@
#include "SkOSFile.h"
#include "SkPicture.h"
#include "SkRefCnt.h"
+#include "SkScalar.h"
#include "SkStream.h"
#include "SkTArray.h"
#include "SkTDict.h"
@@ -68,6 +70,7 @@
#define DEBUGFAIL_SEE_STDERR SkDEBUGFAIL("see stderr for message")
extern bool gSkSuppressFontCachePurgeSpew;
+DECLARE_bool(useDocumentInsteadOfDevice);
#ifdef SK_SUPPORT_PDF
#include "SkPDFDevice.h"
@@ -596,34 +599,51 @@
}
}
- static void generate_pdf(GM* gm, SkDynamicMemoryWStream& pdf) {
+ static bool generate_pdf(GM* gm, SkDynamicMemoryWStream& pdf) {
#ifdef SK_SUPPORT_PDF
SkMatrix initialTransform = gm->getInitialTransform();
- SkISize pageSize = gm->getISize();
- SkPDFDevice* dev = NULL;
- if (initialTransform.isIdentity()) {
- dev = new SkPDFDevice(pageSize, pageSize, initialTransform);
+ if (FLAGS_useDocumentInsteadOfDevice) {
+ SkISize pageISize = gm->getISize();
+ SkAutoTUnref<SkDocument> pdfDoc(SkDocument::CreatePDF(&pdf, NULL, encode_to_dct_data));
+
+ if (!pdfDoc.get()) {
+ return false;
+ }
+
+ SkCanvas* canvas = NULL;
+ canvas = pdfDoc->beginPage(SkIntToScalar(pageISize.width()),
+ SkIntToScalar(pageISize.height()));
+ canvas->concat(initialTransform);
+
+ invokeGM(gm, canvas, true, false);
+
+ return pdfDoc->close();
} else {
- SkRect content = SkRect::MakeWH(SkIntToScalar(pageSize.width()),
- SkIntToScalar(pageSize.height()));
- initialTransform.mapRect(&content);
- content.intersect(0, 0, SkIntToScalar(pageSize.width()),
- SkIntToScalar(pageSize.height()));
- SkISize contentSize =
- SkISize::Make(SkScalarRoundToInt(content.width()),
- SkScalarRoundToInt(content.height()));
- dev = new SkPDFDevice(pageSize, contentSize, initialTransform);
+ SkISize pageSize = gm->getISize();
+ SkPDFDevice* dev = NULL;
+ if (initialTransform.isIdentity()) {
+ dev = new SkPDFDevice(pageSize, pageSize, initialTransform);
+ } else {
+ SkRect content = SkRect::MakeWH(SkIntToScalar(pageSize.width()),
+ SkIntToScalar(pageSize.height()));
+ initialTransform.mapRect(&content);
+ content.intersect(0, 0, SkIntToScalar(pageSize.width()),
+ SkIntToScalar(pageSize.height()));
+ SkISize contentSize =
+ SkISize::Make(SkScalarRoundToInt(content.width()),
+ SkScalarRoundToInt(content.height()));
+ dev = new SkPDFDevice(pageSize, contentSize, initialTransform);
+ }
+ dev->setDCTEncoder(encode_to_dct_data);
+ SkAutoUnref aur(dev);
+ SkCanvas c(dev);
+ invokeGM(gm, &c, true, false);
+ SkPDFDocument doc;
+ doc.appendPage(dev);
+ doc.emitPDF(&pdf);
}
- dev->setDCTEncoder(encode_to_dct_stream);
- SkAutoUnref aur(dev);
-
- SkCanvas c(dev);
- invokeGM(gm, &c, true, false);
-
- SkPDFDocument doc;
- doc.appendPage(dev);
- doc.emitPDF(&pdf);
-#endif
+#endif // SK_SUPPORT_PDF
+ return true; // Do not report failure if pdf is not supported.
}
static void generate_xps(GM* gm, SkDynamicMemoryWStream& xps) {
@@ -1042,11 +1062,49 @@
return errors;
}
} else if (gRec.fBackend == kPDF_Backend) {
- generate_pdf(gm, document);
#if CAN_IMAGE_PDF
- SkAutoDataUnref data(document.copyToData());
- SkMemoryStream stream(data->data(), data->size());
- SkPDFDocumentToBitmap(&stream, bitmap);
+ if (!generate_pdf(gm, document)) {
+ errors.add(kGeneratePdfFailed_ErrorType);
+ } else {
+ SkAutoTUnref<SkStreamAsset> documentStream(document.detachAsStream());
+ if (writePath && (gRec.fFlags & kWrite_ConfigFlag)) {
+ path = make_filename(writePath, gm->shortName(), gRec.fName, "", "pdf");
+ errors.add(write_document(path, documentStream));
+ }
+
+ if (!(gm->getFlags() & GM::kSkipPDFRasterization_Flag)) {
+ for (int i = 0; i < pdfRasterizers.count(); i++) {
+ SkBitmap pdfBitmap;
+ SkASSERT(documentStream->rewind());
+ bool success = (*pdfRasterizers[i]->fRasterizerFunction)(
+ documentStream.get(), &pdfBitmap);
+ if (!success) {
+ gm_fprintf(stderr, "FAILED to render PDF for %s using renderer %s\n",
+ gm->shortName(),
+ pdfRasterizers[i]->fName);
+ continue;
+ }
+
+ SkString configName(gRec.fName);
+ configName.append("-");
+ configName.append(pdfRasterizers[i]->fName);
+
+ BitmapAndDigest bitmapAndDigest(pdfBitmap);
+ errors.add(compare_test_results_to_stored_expectations(
+ gm, gRec, configName.c_str(), &bitmapAndDigest));
+
+ if (writePath && (gRec.fFlags & kWrite_ConfigFlag)) {
+ path = make_bitmap_filename(writePath, gm->shortName(),
+ configName.c_str(),
+ "", bitmapAndDigest.fDigest);
+ errors.add(write_bitmap(path, bitmapAndDigest.fBitmap));
+ }
+ }
+ } else {
+ errors.add(kIntentionallySkipped_ErrorType);
+ }
+ }
+
#else
bitmap = NULL; // we don't generate a bitmap rendering of the PDF file
#endif
@@ -1364,6 +1422,7 @@
DEFINE_int32(pdfJpegQuality, -1, "Encodes images in JPEG at quality level N, "
"which can be in range 0-100). N = -1 will disable JPEG compression. "
"Default is N = 100, maximum quality.");
+DEFINE_bool(useDocumentInsteadOfDevice, false, "Use SkDocument::CreateFoo instead of SkFooDevice.");
static bool encode_to_dct_stream(SkWStream* stream, const SkBitmap& bitmap, const SkIRect& rect) {
// Filter output of warnings that JPEG is not available for the image.
diff --git a/gyp/pdf.gyp b/gyp/pdf.gyp
index ce4c59f..695ff47 100644
--- a/gyp/pdf.gyp
+++ b/gyp/pdf.gyp
@@ -15,6 +15,7 @@
'include_dirs': [
'../include/pdf',
'../src/core', # needed to get SkGlyphCache.h and SkTextFormatParams.h
+ '../src/pdf',
'../src/utils', # needed to get SkBitSet.h
],
'sources': [
diff --git a/gyp/pdf.gypi b/gyp/pdf.gypi
index ae4a032..d2650a1 100644
--- a/gyp/pdf.gypi
+++ b/gyp/pdf.gypi
@@ -13,6 +13,8 @@
'<(skia_src_path)/pdf/SkPDFCatalog.cpp',
'<(skia_src_path)/pdf/SkPDFCatalog.h',
'<(skia_src_path)/pdf/SkPDFDevice.cpp',
+ '<(skia_src_path)/pdf/SkPDFDeviceFlattener.cpp',
+ '<(skia_src_path)/pdf/SkPDFDeviceFlattener.h',
'<(skia_src_path)/pdf/SkPDFDocument.cpp',
'<(skia_src_path)/pdf/SkPDFFont.cpp',
'<(skia_src_path)/pdf/SkPDFFont.h',
diff --git a/include/core/SkDraw.h b/include/core/SkDraw.h
index 8642f0a..66c82c9 100644
--- a/include/core/SkDraw.h
+++ b/include/core/SkDraw.h
@@ -97,16 +97,16 @@
static RectType ComputeRectType(const SkPaint&, const SkMatrix&,
SkPoint* strokeSize);
-private:
void drawText_asPaths(const char text[], size_t byteLength,
SkScalar x, SkScalar y, const SkPaint&) const;
- void drawDevMask(const SkMask& mask, const SkPaint&) const;
- void drawBitmapAsMask(const SkBitmap&, const SkPaint&) const;
-
void drawPosText_asPaths(const char text[], size_t byteLength,
const SkScalar pos[], SkScalar constY,
int scalarsPerPosition, const SkPaint&) const;
+private:
+ void drawDevMask(const SkMask& mask, const SkPaint&) const;
+ void drawBitmapAsMask(const SkBitmap&, const SkPaint&) const;
+
/**
* Return the current clip bounds, in local coordinates, with slop to account
* for antialiasing or hairlines (i.e. device-bounds outset by 1, and then
diff --git a/include/pdf/SkPDFDevice.h b/include/pdf/SkPDFDevice.h
index 207b2b2..a8c6186 100644
--- a/include/pdf/SkPDFDevice.h
+++ b/include/pdf/SkPDFDevice.h
@@ -10,6 +10,7 @@
#ifndef SkPDFDevice_DEFINED
#define SkPDFDevice_DEFINED
+#include "SkBitmap.h"
#include "SkCanvas.h"
#include "SkDevice.h"
#include "SkPaint.h"
@@ -64,7 +65,7 @@
* inverse scale+translate to accommodate the one that SkPDFDevice
* always does.
*/
- // TODO(vandebo): The sizes should be SkSize and not SkISize.
+ // Deprecated, please use SkDocument::CreatePdf() instead.
SK_API SkPDFDevice(const SkISize& pageSize, const SkISize& contentSize,
const SkMatrix& initialTransform);
SK_API virtual ~SkPDFDevice();
diff --git a/src/doc/SkDocument_PDF.cpp b/src/doc/SkDocument_PDF.cpp
index 27cf2e8..f484881 100644
--- a/src/doc/SkDocument_PDF.cpp
+++ b/src/doc/SkDocument_PDF.cpp
@@ -7,7 +7,7 @@
#include "SkDocument.h"
#include "SkPDFDocument.h"
-#include "SkPDFDevice.h"
+#include "SkPDFDeviceFlattener.h"
class SkDocument_PDF : public SkDocument {
public:
@@ -25,19 +25,14 @@
protected:
virtual SkCanvas* onBeginPage(SkScalar width, SkScalar height,
- const SkRect& content) SK_OVERRIDE {
+ const SkRect& trimBox) SK_OVERRIDE {
SkASSERT(NULL == fCanvas);
SkASSERT(NULL == fDevice);
- SkISize pageS, contentS;
- SkMatrix matrix;
+ SkSize mediaBoxSize;
+ mediaBoxSize.set(width, height);
- pageS.set(SkScalarRoundToInt(width), SkScalarRoundToInt(height));
- contentS.set(SkScalarRoundToInt(content.width()),
- SkScalarRoundToInt(content.height()));
- matrix.setTranslate(content.fLeft, content.fTop);
-
- fDevice = SkNEW_ARGS(SkPDFDevice, (pageS, contentS, matrix));
+ fDevice = SkNEW_ARGS(SkPDFDeviceFlattener, (mediaBoxSize, &trimBox));
fCanvas = SkNEW_ARGS(SkCanvas, (fDevice));
return fCanvas;
}
@@ -67,7 +62,7 @@
private:
SkPDFDocument* fDoc;
- SkPDFDevice* fDevice;
+ SkPDFDeviceFlattener* fDevice;
SkCanvas* fCanvas;
};
diff --git a/src/pdf/SkPDFDevice.cpp b/src/pdf/SkPDFDevice.cpp
index 6e5e115..2467d9b 100644
--- a/src/pdf/SkPDFDevice.cpp
+++ b/src/pdf/SkPDFDevice.cpp
@@ -648,6 +648,11 @@
void init(const SkClipStack* clipStack, const SkRegion& clipRegion,
const SkMatrix& matrix, const SkPaint& paint, bool hasText) {
fDstFormXObject = NULL;
+ // Shape has to be flatten before we get here.
+ if (matrix.hasPerspective()) {
+ NOT_IMPLEMENTED(!matrix.hasPerspective(), false);
+ return;
+ }
if (paint.getXfermode()) {
paint.getXfermode()->asMode(&fXfermode);
}
@@ -2005,22 +2010,101 @@
return resourceIndex;
}
-void SkPDFDevice::internalDrawBitmap(const SkMatrix& matrix,
+void SkPDFDevice::internalDrawBitmap(const SkMatrix& origMatrix,
const SkClipStack* clipStack,
- const SkRegion& clipRegion,
- const SkBitmap& bitmap,
+ const SkRegion& origClipRegion,
+ const SkBitmap& origBitmap,
const SkIRect* srcRect,
const SkPaint& paint) {
+ SkMatrix matrix = origMatrix;
+ SkRegion perspectiveBounds;
+ const SkRegion* clipRegion = &origClipRegion;
+ SkBitmap perspectiveBitmap;
+ const SkBitmap* bitmap = &origBitmap;
+ SkBitmap tmpSubsetBitmap;
+
+ // Rasterize the bitmap using perspective in a new bitmap.
+ if (origMatrix.hasPerspective()) {
+ SkBitmap* subsetBitmap;
+ if (srcRect) {
+ if (!origBitmap.extractSubset(&tmpSubsetBitmap, *srcRect)) {
+ return;
+ }
+ subsetBitmap = &tmpSubsetBitmap;
+ } else {
+ subsetBitmap = &tmpSubsetBitmap;
+ *subsetBitmap = origBitmap;
+ }
+ srcRect = NULL;
+
+ // Transform the bitmap in the new space.
+ SkPath perspectiveOutline;
+ perspectiveOutline.addRect(
+ SkRect::MakeWH(SkIntToScalar(subsetBitmap->width()),
+ SkIntToScalar(subsetBitmap->height())));
+ perspectiveOutline.transform(origMatrix);
+
+ // TODO(edisonn): perf - use current clip too.
+ // Retrieve the bounds of the new shape.
+ SkRect bounds = perspectiveOutline.getBounds();
+
+ // TODO(edisonn): add DPI settings. Currently 1 pixel/point, which does
+ // not look great, but it is not producing large PDFs.
+
+ // TODO(edisonn): A better approach would be to use a bitmap shader
+ // (in clamp mode) and draw a rect over the entire bounding box. Then
+ // intersect perspectiveOutline to the clip. That will avoid introducing
+ // alpha to the image while still giving good behavior at the edge of
+ // the image. Avoiding alpha will reduce the pdf size and generation
+ // CPU time some.
+
+ perspectiveBitmap.setConfig(SkBitmap::kARGB_8888_Config,
+ SkScalarCeilToInt(bounds.width()),
+ SkScalarCeilToInt(bounds.height()));
+ perspectiveBitmap.allocPixels();
+ perspectiveBitmap.eraseColor(SK_ColorTRANSPARENT);
+
+ // FIXME: Once we merge to latest Skia, this should be an SkBitmapDevice.
+ SkDevice device(perspectiveBitmap);
+ SkCanvas canvas(&device);
+
+ SkScalar deltaX = bounds.left();
+ SkScalar deltaY = bounds.top();
+
+ SkMatrix offsetMatrix = origMatrix;
+ offsetMatrix.postTranslate(-deltaX, -deltaY);
+
+ // Translate the draw in the new canvas, so we perfectly fit the
+ // shape in the bitmap.
+ canvas.setMatrix(offsetMatrix);
+
+ canvas.drawBitmap(*subsetBitmap, SkIntToScalar(0), SkIntToScalar(0));
+
+ // Make sure the final bits are in the bitmap.
+ canvas.flush();
+
+ // In the new space, we use the identity matrix translated.
+ matrix.setTranslate(deltaX, deltaY);
+ perspectiveBounds.setRect(
+ SkIRect::MakeXYWH(SkScalarFloorToInt(bounds.x()),
+ SkScalarFloorToInt(bounds.y()),
+ SkScalarCeilToInt(bounds.width()),
+ SkScalarCeilToInt(bounds.height())));
+ clipRegion = &perspectiveBounds;
+ srcRect = NULL;
+ bitmap = &perspectiveBitmap;
+ }
+
SkMatrix scaled;
// Adjust for origin flip.
scaled.setScale(SK_Scalar1, -SK_Scalar1);
scaled.postTranslate(0, SK_Scalar1);
// Scale the image up from 1x1 to WxH.
- SkIRect subset = SkIRect::MakeWH(bitmap.width(), bitmap.height());
+ SkIRect subset = SkIRect::MakeWH(bitmap->width(), bitmap->height());
scaled.postScale(SkIntToScalar(subset.width()),
SkIntToScalar(subset.height()));
scaled.postConcat(matrix);
- ScopedContentEntry content(this, clipStack, clipRegion, scaled, paint);
+ ScopedContentEntry content(this, clipStack, *clipRegion, scaled, paint);
if (!content.entry()) {
return;
}
@@ -2029,7 +2113,7 @@
return;
}
- SkPDFImage* image = SkPDFImage::CreateImage(bitmap, subset, fEncoder);
+ SkPDFImage* image = SkPDFImage::CreateImage(*bitmap, subset, fEncoder);
if (!image) {
return;
}
diff --git a/src/pdf/SkPDFDeviceFlattener.cpp b/src/pdf/SkPDFDeviceFlattener.cpp
new file mode 100644
index 0000000..67e075c
--- /dev/null
+++ b/src/pdf/SkPDFDeviceFlattener.cpp
@@ -0,0 +1,156 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkPDFDeviceFlattener.h"
+
+#include "SkDraw.h"
+#include "SkShader.h"
+
+static SkISize SkSizeToISize(const SkSize& size) {
+ return SkISize::Make(SkScalarRoundToInt(size.width()), SkScalarRoundToInt(size.height()));
+}
+
+SkPDFDeviceFlattener::SkPDFDeviceFlattener(const SkSize& pageSize, const SkRect* trimBox)
+ : SkPDFDevice(SkSizeToISize(pageSize),
+ SkSizeToISize(pageSize),
+ SkMatrix::I()) {
+ // TODO(edisonn): store the trimbox on emit.
+}
+
+SkPDFDeviceFlattener::~SkPDFDeviceFlattener() {
+}
+
+static void flattenPaint(const SkDraw& d, SkPaint* paint) {
+ if (paint->getShader()) {
+ SkMatrix local = paint->getShader()->getLocalMatrix();
+ local.preConcat(*d.fMatrix);
+ paint->getShader()->setLocalMatrix(local);
+ }
+}
+
+void SkPDFDeviceFlattener::drawPoints(const SkDraw& d, SkCanvas::PointMode mode,
+ size_t count, const SkPoint points[],
+ const SkPaint& paint) {
+ if (!mustFlatten(d)) {
+ INHERITED::drawPoints(d, mode, count, points, paint);
+ return;
+ }
+
+ SkPaint paintFlatten(paint);
+ flattenPaint(d, &paintFlatten);
+
+ SkPoint* flattenedPoints = SkNEW_ARRAY(SkPoint, count);
+ d.fMatrix->mapPoints(flattenedPoints, points, count);
+ SkDraw draw(d);
+ SkMatrix identity = SkMatrix::I();
+ draw.fMatrix = &identity;
+ INHERITED::drawPoints(draw, mode, count, flattenedPoints, paintFlatten);
+ SkDELETE_ARRAY(flattenedPoints);
+}
+
+void SkPDFDeviceFlattener::drawRect(const SkDraw& d, const SkRect& r, const SkPaint& paint) {
+ if (!mustFlatten(d)) {
+ INHERITED::drawRect(d, r, paint);
+ return;
+ }
+
+ SkPath path;
+ path.addRect(r);
+ path.transform(*d.fMatrix);
+ SkDraw draw(d);
+ SkMatrix matrix = SkMatrix::I();
+ draw.fMatrix = &matrix;
+
+ SkPaint paintFlatten(paint);
+ flattenPaint(d, &paintFlatten);
+
+ INHERITED::drawPath(draw, path, paintFlatten, NULL, true);
+}
+
+void SkPDFDeviceFlattener::drawPath(const SkDraw& d, const SkPath& origPath,
+ const SkPaint& paint, const SkMatrix* prePathMatrix,
+ bool pathIsMutable) {
+ if (!mustFlatten(d) && !(prePathMatrix && prePathMatrix->hasPerspective())) {
+ INHERITED::drawPath(d, origPath, paint, prePathMatrix, pathIsMutable);
+ return;
+ }
+
+ SkPath* pathPtr = (SkPath*)&origPath;
+ SkPath tmpPath;
+
+ if (!pathIsMutable) {
+ tmpPath = origPath;
+ pathPtr = &tmpPath;
+ }
+
+ if (prePathMatrix) {
+ pathPtr->transform(*prePathMatrix);
+ }
+
+ SkPaint paintFlatten(paint);
+ flattenPaint(d, &paintFlatten);
+
+ bool fill = paintFlatten.getFillPath(*pathPtr, &tmpPath);
+ SkDEBUGCODE(pathPtr = (SkPath*)0x12345678); // Don't use pathPtr after this point.
+
+ paintFlatten.setPathEffect(NULL);
+ if (fill) {
+ paintFlatten.setStyle(SkPaint::kFill_Style);
+ } else {
+ paintFlatten.setStyle(SkPaint::kStroke_Style);
+ paintFlatten.setStrokeWidth(0);
+ }
+
+ tmpPath.transform(*d.fMatrix);
+
+ SkDraw draw(d);
+ SkMatrix matrix = SkMatrix::I();
+ draw.fMatrix = &matrix;
+
+ INHERITED::drawPath(draw, tmpPath, paintFlatten, NULL, true);
+}
+
+void SkPDFDeviceFlattener::drawText(const SkDraw& d, const void* text, size_t len,
+ SkScalar x, SkScalar y, const SkPaint& paint) {
+ if (mustPathText(d, paint)) {
+ d.drawText_asPaths((const char*)text, len, x, y, paint);
+ return;
+ }
+
+ INHERITED::drawText(d, text, len, x, y, paint);
+}
+
+void SkPDFDeviceFlattener::drawPosText(const SkDraw& d, const void* text, size_t len,
+ const SkScalar pos[], SkScalar constY,
+ int scalarsPerPos, const SkPaint& paint) {
+ if (mustPathText(d, paint)) {
+ d.drawPosText_asPaths((const char*)text, len, pos, constY, scalarsPerPos, paint);
+ return;
+ }
+ INHERITED::drawPosText(d, text, len, pos, constY,scalarsPerPos, paint);
+}
+
+void SkPDFDeviceFlattener::drawTextOnPath(const SkDraw& d, const void* text, size_t len,
+ const SkPath& path, const SkMatrix* matrix,
+ const SkPaint& paint) {
+ if (mustPathText(d, paint) || (matrix && matrix->hasPerspective())) {
+ d.drawTextOnPath((const char*)text, len, path, matrix, paint);
+ return;
+ }
+ INHERITED::drawTextOnPath(d, text, len, path, matrix, paint);
+}
+
+bool SkPDFDeviceFlattener::mustFlatten(const SkDraw& d) const {
+ // TODO(edisonn): testability, add flag to force return true.
+ return d.fMatrix->hasPerspective();
+}
+
+bool SkPDFDeviceFlattener::mustPathText(const SkDraw& d, const SkPaint&) {
+ // TODO(edisonn): testability, add flag to force return true.
+ // TODO(edisonn): TBD: How to flatten MaskFilter.
+ return d.fMatrix->hasPerspective();
+}
diff --git a/src/pdf/SkPDFDeviceFlattener.h b/src/pdf/SkPDFDeviceFlattener.h
new file mode 100644
index 0000000..f1047db
--- /dev/null
+++ b/src/pdf/SkPDFDeviceFlattener.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SkPDFDeviceFlattener_DEFINED
+#define SkPDFDeviceFlattener_DEFINED
+
+#include "SkPDFDevice.h"
+
+
+/** \class SkPDFDeviceFlattener
+
+ The PDF Device Flattener is used to flatten features without native support in PDF.
+ For now, the only one implemented is Perspective.
+
+ TODO(edisonn): Rename the class once we know all the things it will do.
+*/
+class SkPDFDeviceFlattener : public SkPDFDevice {
+private:
+ typedef SkPDFDevice INHERITED;
+
+ SK_API SkPDFDeviceFlattener(const SkSize& pageSize, const SkRect* trimBox = NULL);
+
+public:
+ SK_API virtual ~SkPDFDeviceFlattener();
+
+ virtual void drawPoints(const SkDraw&, SkCanvas::PointMode mode,
+ size_t count, const SkPoint[],
+ const SkPaint& paint) SK_OVERRIDE;
+ virtual void drawRect(const SkDraw&, const SkRect& r, const SkPaint& paint);
+ virtual void drawPath(const SkDraw&, const SkPath& origpath,
+ const SkPaint& paint, const SkMatrix* prePathMatrix,
+ bool pathIsMutable) SK_OVERRIDE;
+ virtual void drawText(const SkDraw&, const void* text, size_t len,
+ SkScalar x, SkScalar y, const SkPaint&) SK_OVERRIDE;
+ virtual void drawPosText(const SkDraw&, const void* text, size_t len,
+ const SkScalar pos[], SkScalar constY,
+ int scalarsPerPos, const SkPaint&) SK_OVERRIDE;
+ virtual void drawTextOnPath(const SkDraw&, const void* text, size_t len,
+ const SkPath& path, const SkMatrix* matrix,
+ const SkPaint& paint) SK_OVERRIDE;
+private:
+
+ bool mustFlatten(const SkDraw& d) const;
+ bool mustPathText(const SkDraw& d, const SkPaint& paint);
+
+ friend class SkDocument_PDF;
+};
+
+#endif // SkPDFDeviceFlattener_DEFINED
diff --git a/src/pdf/SkPDFShader.cpp b/src/pdf/SkPDFShader.cpp
index 9394f1b..c5d4a5d 100644
--- a/src/pdf/SkPDFShader.cpp
+++ b/src/pdf/SkPDFShader.cpp
@@ -9,7 +9,6 @@
#include "SkPDFShader.h"
-#include "SkCanvas.h"
#include "SkData.h"
#include "SkPDFCatalog.h"
#include "SkPDFDevice.h"
@@ -204,16 +203,73 @@
}
}
-static SkString linearCode(const SkShader::GradientInfo& info) {
- SkString function("{pop\n"); // Just ditch the y value.
+/**
+ * Returns PS function code that applies inverse perspective
+ * to a x, y point.
+ * The function assumes that the stack has at least two elements,
+ * and that the top 2 elements are numeric values.
+ * After executing this code on a PS stack, the last 2 elements are updated
+ * while the rest of the stack is preserved intact.
+ * inversePerspectiveMatrix is the inverse perspective matrix.
+ */
+static SkString apply_perspective_to_coordinates(
+ const SkMatrix& inversePerspectiveMatrix) {
+ SkString code;
+ if (!inversePerspectiveMatrix.hasPerspective()) {
+ return code;
+ }
+
+ // Perspective matrix should be:
+ // 1 0 0
+ // 0 1 0
+ // p0 p1 p2
+
+ const SkScalar p0 = inversePerspectiveMatrix[SkMatrix::kMPersp0];
+ const SkScalar p1 = inversePerspectiveMatrix[SkMatrix::kMPersp1];
+ const SkScalar p2 = inversePerspectiveMatrix[SkMatrix::kMPersp2];
+
+ // y = y / (p2 + p0 x + p1 y)
+ // x = x / (p2 + p0 x + p1 y)
+
+ // Input on stack: x y
+ code.append(" dup "); // x y y
+ code.appendScalar(p1); // x y y p1
+ code.append(" mul " // x y y*p1
+ " 2 index "); // x y y*p1 x
+ code.appendScalar(p0); // x y y p1 x p0
+ code.append(" mul "); // x y y*p1 x*p0
+ code.appendScalar(p2); // x y y p1 x*p0 p2
+ code.append(" add " // x y y*p1 x*p0+p2
+ "add " // x y y*p1+x*p0+p2
+ "3 1 roll " // y*p1+x*p0+p2 x y
+ "2 index " // z x y y*p1+x*p0+p2
+ "div " // y*p1+x*p0+p2 x y/(y*p1+x*p0+p2)
+ "3 1 roll " // y/(y*p1+x*p0+p2) y*p1+x*p0+p2 x
+ "exch " // y/(y*p1+x*p0+p2) x y*p1+x*p0+p2
+ "div " // y/(y*p1+x*p0+p2) x/(y*p1+x*p0+p2)
+ "exch\n"); // x/(y*p1+x*p0+p2) y/(y*p1+x*p0+p2)
+ return code;
+}
+
+static SkString linearCode(const SkShader::GradientInfo& info,
+ const SkMatrix& perspectiveRemover) {
+ SkString function("{");
+
+ function.append(apply_perspective_to_coordinates(perspectiveRemover));
+
+ function.append("pop\n"); // Just ditch the y value.
tileModeCode(info.fTileMode, &function);
gradientFunctionCode(info, &function);
function.append("}");
return function;
}
-static SkString radialCode(const SkShader::GradientInfo& info) {
+static SkString radialCode(const SkShader::GradientInfo& info,
+ const SkMatrix& perspectiveRemover) {
SkString function("{");
+
+ function.append(apply_perspective_to_coordinates(perspectiveRemover));
+
// Find the distance from the origin.
function.append("dup " // x y y
"mul " // x y^2
@@ -233,7 +289,8 @@
with one simplification, the coordinate space has been scaled so that
Dr = 1. This means we don't need to scale the entire equation by 1/Dr^2.
*/
-static SkString twoPointRadialCode(const SkShader::GradientInfo& info) {
+static SkString twoPointRadialCode(const SkShader::GradientInfo& info,
+ const SkMatrix& perspectiveRemover) {
SkScalar dx = info.fPoint[0].fX - info.fPoint[1].fX;
SkScalar dy = info.fPoint[0].fY - info.fPoint[1].fY;
SkScalar sr = info.fRadius[0];
@@ -243,6 +300,9 @@
// We start with a stack of (x y), copy it and then consume one copy in
// order to calculate b and the other to calculate c.
SkString function("{");
+
+ function.append(apply_perspective_to_coordinates(perspectiveRemover));
+
function.append("2 copy ");
// Calculate -b and b^2.
@@ -280,7 +340,8 @@
/* Conical gradient shader, based on the Canvas spec for radial gradients
See: http://www.w3.org/TR/2dcontext/#dom-context-2d-createradialgradient
*/
-static SkString twoPointConicalCode(const SkShader::GradientInfo& info) {
+static SkString twoPointConicalCode(const SkShader::GradientInfo& info,
+ const SkMatrix& perspectiveRemover) {
SkScalar dx = info.fPoint[1].fX - info.fPoint[0].fX;
SkScalar dy = info.fPoint[1].fY - info.fPoint[0].fY;
SkScalar r0 = info.fRadius[0];
@@ -294,6 +355,9 @@
// We start with a stack of (x y), copy it and then consume one copy in
// order to calculate b and the other to calculate c.
SkString function("{");
+
+ function.append(apply_perspective_to_coordinates(perspectiveRemover));
+
function.append("2 copy ");
// Calculate b and b^2; b = -2 * (y * dy + x * dx + r0 * dr).
@@ -389,7 +453,8 @@
return function;
}
-static SkString sweepCode(const SkShader::GradientInfo& info) {
+static SkString sweepCode(const SkShader::GradientInfo& info,
+ const SkMatrix& perspectiveRemover) {
SkString function("{exch atan 360 div\n");
tileModeCode(info.fTileMode, &function);
gradientFunctionCode(info, &function);
@@ -719,10 +784,49 @@
SkMatrix::I());
}
+// Finds affine and persp such that in = affine * persp.
+// but it returns the inverse of perspective matrix.
+static bool split_perspective(const SkMatrix in, SkMatrix* affine,
+ SkMatrix* perspectiveInverse) {
+ const SkScalar p2 = in[SkMatrix::kMPersp2];
+
+ if (SkScalarNearlyZero(p2)) {
+ return false;
+ }
+
+ const SkScalar zero = SkIntToScalar(0);
+ const SkScalar one = SkIntToScalar(1);
+
+ const SkScalar sx = in[SkMatrix::kMScaleX];
+ const SkScalar kx = in[SkMatrix::kMSkewX];
+ const SkScalar tx = in[SkMatrix::kMTransX];
+ const SkScalar ky = in[SkMatrix::kMSkewY];
+ const SkScalar sy = in[SkMatrix::kMScaleY];
+ const SkScalar ty = in[SkMatrix::kMTransY];
+ const SkScalar p0 = in[SkMatrix::kMPersp0];
+ const SkScalar p1 = in[SkMatrix::kMPersp1];
+
+ // Perspective matrix would be:
+ // 1 0 0
+ // 0 1 0
+ // p0 p1 p2
+ // But we need the inverse of persp.
+ perspectiveInverse->setAll(one, zero, zero,
+ zero, one, zero,
+ -p0/p2, -p1/p2, 1/p2);
+
+ affine->setAll(sx - p0 * tx / p2, kx - p1 * tx / p2, tx / p2,
+ ky - p0 * ty / p2, sy - p1 * ty / p2, ty / p2,
+ zero, zero, one);
+
+ return true;
+}
+
SkPDFFunctionShader::SkPDFFunctionShader(SkPDFShader::State* state)
: SkPDFDict("Pattern"),
fState(state) {
- SkString (*codeFunction)(const SkShader::GradientInfo& info) = NULL;
+ SkString (*codeFunction)(const SkShader::GradientInfo& info,
+ const SkMatrix& perspectiveRemover) = NULL;
SkPoint transformPoints[2];
// Depending on the type of the gradient, we want to transform the
@@ -774,10 +878,24 @@
// the gradient can be drawn on on the unit segment.
SkMatrix mapperMatrix;
unitToPointsMatrix(transformPoints, &mapperMatrix);
+
SkMatrix finalMatrix = fState.get()->fCanvasTransform;
finalMatrix.preConcat(fState.get()->fShaderTransform);
finalMatrix.preConcat(mapperMatrix);
+ // Preserves as much as posible in the final matrix, and only removes
+ // the perspective. The inverse of the perspective is stored in
+ // perspectiveInverseOnly matrix and has 3 useful numbers
+ // (p0, p1, p2), while everything else is either 0 or 1.
+ // In this way the shader will handle it eficiently, with minimal code.
+ SkMatrix perspectiveInverseOnly = SkMatrix::I();
+ if (finalMatrix.hasPerspective()) {
+ if (!split_perspective(finalMatrix,
+ &finalMatrix, &perspectiveInverseOnly)) {
+ return;
+ }
+ }
+
SkRect bbox;
bbox.set(fState.get()->fBBox);
if (!transformBBox(finalMatrix, &bbox)) {
@@ -806,9 +924,9 @@
inverseMapperMatrix.mapRadius(info->fRadius[0]);
twoPointRadialInfo.fRadius[1] =
inverseMapperMatrix.mapRadius(info->fRadius[1]);
- functionCode = codeFunction(twoPointRadialInfo);
+ functionCode = codeFunction(twoPointRadialInfo, perspectiveInverseOnly);
} else {
- functionCode = codeFunction(*info);
+ functionCode = codeFunction(*info, perspectiveInverseOnly);
}
SkAutoTUnref<SkPDFDict> pdfShader(new SkPDFDict);
diff --git a/tools/PdfRenderer.cpp b/tools/PdfRenderer.cpp
index 704cbea..890abde 100644
--- a/tools/PdfRenderer.cpp
+++ b/tools/PdfRenderer.cpp
@@ -13,7 +13,7 @@
namespace sk_tools {
-void PdfRenderer::init(SkPicture* pict) {
+void PdfRenderer::init(SkPicture* pict, SkWStream* stream) {
SkASSERT(NULL == fPicture);
SkASSERT(NULL == fCanvas.get());
if (fPicture != NULL || NULL != fCanvas.get()) {
@@ -26,44 +26,35 @@
}
fPicture = pict;
- fCanvas.reset(this->setupCanvas());
+ fCanvas.reset(this->setupCanvas(stream, pict->width(), pict->height()));
}
-SkCanvas* PdfRenderer::setupCanvas() {
- return this->setupCanvas(fPicture->width(), fPicture->height());
-}
+SkCanvas* PdfRenderer::setupCanvas(SkWStream* stream, int width, int height) {
+ fPdfDoc.reset(SkDocument::CreatePDF(stream, NULL, fEncoder));
-SkCanvas* PdfRenderer::setupCanvas(int width, int height) {
- SkISize pageSize = SkISize::Make(width, height);
- fPDFDevice = SkNEW_ARGS(SkPDFDevice, (pageSize, pageSize, SkMatrix::I()));
- fPDFDevice->setDCTEncoder(fEncoder);
- return SkNEW_ARGS(SkCanvas, (fPDFDevice));
+ SkCanvas* canvas = fPdfDoc->beginPage(SkIntToScalar(width), SkIntToScalar(height));
+ canvas->ref();
+
+ return canvas;
}
void PdfRenderer::end() {
fPicture = NULL;
fCanvas.reset(NULL);
- if (fPDFDevice) {
- SkDELETE(fPDFDevice);
- fPDFDevice = NULL;
- }
+ fPdfDoc.reset(NULL);
}
-void PdfRenderer::write(SkWStream* stream) const {
- SkPDFDocument doc;
- doc.appendPage(fPDFDevice);
- doc.emitPDF(stream);
-}
-
-void SimplePdfRenderer::render() {
+bool SimplePdfRenderer::render() {
SkASSERT(fCanvas.get() != NULL);
SkASSERT(fPicture != NULL);
if (NULL == fCanvas.get() || NULL == fPicture) {
- return;
+ return false;
}
fCanvas->drawPicture(*fPicture);
fCanvas->flush();
+
+ return fPdfDoc->close();
}
}
diff --git a/tools/PdfRenderer.h b/tools/PdfRenderer.h
index d2d1a5c..2262dbe 100644
--- a/tools/PdfRenderer.h
+++ b/tools/PdfRenderer.h
@@ -13,8 +13,8 @@
// An SkPicture can be built manually, or read from an SKP file.
//
+#include "SkDocument.h"
#include "SkMath.h"
-#include "SkPDFDevice.h"
#include "SkPicture.h"
#include "SkTypes.h"
#include "SkTDArray.h"
@@ -23,32 +23,32 @@
class SkBitmap;
class SkCanvas;
+class SkWStream;
namespace sk_tools {
class PdfRenderer : public SkRefCnt {
public:
- virtual void init(SkPicture* pict);
+ virtual void init(SkPicture* pict, SkWStream* stream);
virtual void setup() {}
- virtual void render() = 0;
+ virtual bool render() = 0;
virtual void end();
PdfRenderer(EncodeToDCTStream encoder)
: fPicture(NULL)
- , fPDFDevice(NULL)
, fEncoder(encoder)
+ , fPDFDoc(NULL)
{}
void write(SkWStream* stream) const;
protected:
- SkCanvas* setupCanvas();
- SkCanvas* setupCanvas(int width, int height);
+ SkCanvas* setupCanvas(SkWStream* stream, int width, int height);
SkAutoTUnref<SkCanvas> fCanvas;
SkPicture* fPicture;
- SkPDFDevice* fPDFDevice;
EncodeToDCTStream fEncoder;
+ SkAutoTUnref<SkDocument> fPdfDoc;
private:
typedef SkRefCnt INHERITED;
@@ -58,7 +58,7 @@
public:
SimplePdfRenderer(EncodeToDCTStream encoder)
: PdfRenderer(encoder) {}
- virtual void render() SK_OVERRIDE;
+ virtual bool render() SK_OVERRIDE;
private:
typedef PdfRenderer INHERITED;
diff --git a/tools/render_pdfs_main.cpp b/tools/render_pdfs_main.cpp
index f443502..785f7b2 100644
--- a/tools/render_pdfs_main.cpp
+++ b/tools/render_pdfs_main.cpp
@@ -127,28 +127,24 @@
* @param inputFilename The skp file that was read.
* @param renderer The object responsible to write the pdf file.
*/
-static bool write_output(const SkString& outputDir,
- const SkString& inputFilename,
- const sk_tools::PdfRenderer& renderer) {
+static SkWStream* open_stream(const SkString& outputDir,
+ const SkString& inputFilename) {
if (outputDir.isEmpty()) {
- SkDynamicMemoryWStream stream;
- renderer.write(&stream);
- return true;
+ return SkNEW(SkDynamicMemoryWStream);
}
SkString outputPath;
if (!make_output_filepath(&outputPath, outputDir, inputFilename)) {
- return false;
+ return NULL;
}
- SkFILEWStream stream(outputPath.c_str());
- if (!stream.isValid()) {
+ SkFILEWStream* stream = SkNEW_ARGS(SkFILEWStream, (outputPath.c_str()));
+ if (!stream->isValid()) {
SkDebugf("Could not write to file %s\n", outputPath.c_str());
- return false;
+ return NULL;
}
- renderer.write(&stream);
- return true;
+ return stream;
}
/** Reads an skp file, renders it to pdf and writes the output to a pdf file
@@ -178,13 +174,19 @@
SkDebugf("exporting... [%i %i] %s\n", picture->width(), picture->height(),
inputPath.c_str());
- renderer.init(picture);
+ SkWStream* stream(open_stream(outputDir, inputFilename));
- renderer.render();
+ if (!stream) {
+ return false;
+ }
- bool success = write_output(outputDir, inputFilename, renderer);
+ renderer.init(picture, stream);
+
+ bool success = renderer.render();
+ SkDELETE(stream);
renderer.end();
+
return success;
}