aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorGravatar Hal Canary <halcanary@google.com>2017-05-26 17:01:16 -0400
committerGravatar Skia Commit-Bot <skia-commit-bot@chromium.org>2017-06-05 19:22:46 +0000
commitd12a67626da4f5919b48c513fee80974f603473e (patch)
treee991cd2a37219732937291cf1a76fb2eca34214d
parentf36031b68aa5b92204187c154fc5bc717db20a3a (diff)
SkPDF: Draw paths with mask filters; color filter.
Also: - drawPaint, drawPath w/ perspective shaders - text with mask filters, stroking, path effect. - SkPDFUtils::GetShaderLocalMatrix BUG=skia:237 BUG=skia:238 BUG=skia:5607 Change-Id: Iffeaf2d7abbde13fd2577ce9feaa178657f48364 Reviewed-on: https://skia-review.googlesource.com/18200 Commit-Queue: Hal Canary <halcanary@google.com> Reviewed-by: Ben Wagner <bungeman@google.com>
-rw-r--r--src/pdf/SkPDFDevice.cpp241
-rw-r--r--src/pdf/SkPDFDevice.h6
-rw-r--r--src/pdf/SkPDFShader.cpp2
-rw-r--r--src/pdf/SkPDFUtils.h9
4 files changed, 230 insertions, 28 deletions
diff --git a/src/pdf/SkPDFDevice.cpp b/src/pdf/SkPDFDevice.cpp
index 4014101b25..f4cdd864c8 100644
--- a/src/pdf/SkPDFDevice.cpp
+++ b/src/pdf/SkPDFDevice.cpp
@@ -11,18 +11,19 @@
#include "SkAnnotationKeys.h"
#include "SkBitmapDevice.h"
#include "SkBitmapKey.h"
+#include "SkClipOpPriv.h"
#include "SkColor.h"
#include "SkColorFilter.h"
#include "SkDraw.h"
#include "SkDrawFilter.h"
#include "SkGlyphCache.h"
#include "SkImageFilterCache.h"
+#include "SkJpegEncoder.h"
#include "SkMakeUnique.h"
-#include "SkPath.h"
-#include "SkPathEffect.h"
-#include "SkPathOps.h"
+#include "SkMaskFilter.h"
#include "SkPDFBitmap.h"
#include "SkPDFCanon.h"
+#include "SkPDFCanvas.h"
#include "SkPDFDocument.h"
#include "SkPDFFont.h"
#include "SkPDFFormXObject.h"
@@ -31,9 +32,12 @@
#include "SkPDFShader.h"
#include "SkPDFTypes.h"
#include "SkPDFUtils.h"
+#include "SkPath.h"
+#include "SkPathEffect.h"
+#include "SkPathOps.h"
#include "SkPixelRef.h"
-#include "SkRasterClip.h"
#include "SkRRect.h"
+#include "SkRasterClip.h"
#include "SkScopeExit.h"
#include "SkString.h"
#include "SkSurface.h"
@@ -42,7 +46,17 @@
#include "SkTextFormatParams.h"
#include "SkUtils.h"
#include "SkXfermodeInterpretation.h"
-#include "SkClipOpPriv.h"
+
+#ifndef SK_PDF_MASK_QUALITY
+ // If MASK_QUALITY is in [0,100], will be used for JpegEncoder.
+ // Otherwise, just encode masks losslessly.
+ #define SK_PDF_MASK_QUALITY 50
+ // Since these masks are used for blurry shadows, we shouldn't need
+ // high quality. Raise this value if your shadows have visible JPEG
+ // artifacts.
+ // If SkJpegEncoder::Encode fails, we will fall back to the lossless
+ // encoding.
+#endif
#define DPI_FOR_RASTER_SCALE_ONE 72
@@ -73,6 +87,23 @@ static void replace_srcmode_on_opaque_paint(SkPaint* paint) {
}
}
+// A shader's matrix is: CTMM x LocalMatrix x WrappingLocalMatrix. We want to
+// switch to device space, where CTM = I, while keeping the original behavior.
+//
+// I * LocalMatrix * NewWrappingMatrix = CTM * LocalMatrix
+// LocalMatrix * NewWrappingMatrix = CTM * LocalMatrix
+// InvLocalMatrix * LocalMatrix * NewWrappingMatrix = InvLocalMatrix * CTM * LocalMatrix
+// NewWrappingMatrix = InvLocalMatrix * CTM * LocalMatrix
+//
+static void transform_shader(SkPaint* paint, const SkMatrix& ctm) {
+ SkMatrix lm = SkPDFUtils::GetShaderLocalMatrix(paint->getShader());
+ SkMatrix lmInv;
+ if (lm.invert(&lmInv)) {
+ SkMatrix m = SkMatrix::Concat(SkMatrix::Concat(lmInv, ctm), lm);
+ paint->setShader(paint->getShader()->makeWithLocalMatrix(m));
+ }
+}
+
static void emit_pdf_color(SkColor color, SkWStream* result) {
SkASSERT(SkColorGetA(color) == 0xFF); // We handle alpha elsewhere.
SkPDFUtils::AppendColorComponent(SkColorGetR(color), result);
@@ -122,6 +153,19 @@ static SkImageSubset make_image_subset(const SkBitmap& bitmap) {
return imageSubset;
}
+// If the paint has a color filter, apply the color filter to the shader or the
+// paint color. Remove the color filter.
+void remove_color_filter(SkPaint* paint) {
+ if (SkColorFilter* cf = paint->getColorFilter()) {
+ if (SkShader* shader = paint->getShader()) {
+ paint->setShader(shader->makeWithColorFilter(paint->refColorFilter()));
+ } else {
+ paint->setColor(cf->filterColor(paint->getColor()));
+ }
+ paint->setColorFilter(nullptr);
+ }
+}
+
SkPDFDevice::GraphicStateEntry::GraphicStateEntry()
: fColor(SK_ColorBLACK)
, fTextScaleX(SK_Scalar1)
@@ -544,12 +588,20 @@ void SkPDFDevice::drawAnnotation(const SkRect& rect, const char key[], SkData* v
}
}
-void SkPDFDevice::drawPaint(const SkPaint& paint) {
- SkPaint newPaint = paint;
+void SkPDFDevice::drawPaint(const SkPaint& srcPaint) {
+ SkPaint newPaint = srcPaint;
+ remove_color_filter(&newPaint);
replace_srcmode_on_opaque_paint(&newPaint);
-
newPaint.setStyle(SkPaint::kFill_Style);
- ScopedContentEntry content(this, newPaint);
+
+ SkMatrix ctm = this->ctm();
+ if (ctm.getType() & SkMatrix::kPerspective_Mask) {
+ if (newPaint.getShader()) {
+ transform_shader(&newPaint, ctm);
+ }
+ ctm = SkMatrix::I();
+ }
+ ScopedContentEntry content(this, this->cs(), ctm, newPaint);
this->internalDrawPaint(newPaint, content.entry());
}
@@ -576,6 +628,7 @@ void SkPDFDevice::drawPoints(SkCanvas::PointMode mode,
const SkPoint* points,
const SkPaint& srcPaint) {
SkPaint passedPaint = srcPaint;
+ remove_color_filter(&passedPaint);
replace_srcmode_on_opaque_paint(&passedPaint);
if (count == 0) {
@@ -703,11 +756,12 @@ static sk_sp<SkPDFDict> create_link_named_dest(const SkData* nameData,
void SkPDFDevice::drawRect(const SkRect& rect,
const SkPaint& srcPaint) {
SkPaint paint = srcPaint;
+ remove_color_filter(&paint);
replace_srcmode_on_opaque_paint(&paint);
SkRect r = rect;
r.sort();
- if (paint.getPathEffect()) {
+ if (paint.getPathEffect() || paint.getMaskFilter()) {
if (this->cs().isEmpty(size(*this))) {
return;
}
@@ -729,6 +783,7 @@ void SkPDFDevice::drawRect(const SkRect& rect,
void SkPDFDevice::drawRRect(const SkRRect& rrect,
const SkPaint& srcPaint) {
SkPaint paint = srcPaint;
+ remove_color_filter(&paint);
replace_srcmode_on_opaque_paint(&paint);
SkPath path;
path.addRRect(rrect);
@@ -738,6 +793,7 @@ void SkPDFDevice::drawRRect(const SkRRect& rrect,
void SkPDFDevice::drawOval(const SkRect& oval,
const SkPaint& srcPaint) {
SkPaint paint = srcPaint;
+ remove_color_filter(&paint);
replace_srcmode_on_opaque_paint(&paint);
SkPath path;
path.addOval(oval);
@@ -752,6 +808,91 @@ void SkPDFDevice::drawPath(const SkPath& origPath,
this->cs(), this->ctm(), origPath, srcPaint, prePathMatrix, pathIsMutable);
}
+void SkPDFDevice::internalDrawPathWithFilter(const SkClipStack& clipStack,
+ const SkMatrix& ctm,
+ const SkPath& origPath,
+ const SkPaint& origPaint,
+ const SkMatrix* prePathMatrix) {
+ SkASSERT(origPaint.getMaskFilter());
+ SkPath path(origPath);
+ SkTCopyOnFirstWrite<SkPaint> paint(origPaint);
+ if (prePathMatrix) {
+ path.transform(*prePathMatrix, &path);
+ }
+ SkStrokeRec::InitStyle initStyle = paint->getFillPath(path, &path)
+ ? SkStrokeRec::kFill_InitStyle
+ : SkStrokeRec::kHairline_InitStyle;
+ path.transform(ctm, &path);
+
+ // TODO(halcanary): respect fRasterDpi.
+ // SkScalar rasterScale = (float)fRasterDpi / DPI_FOR_RASTER_SCALE_ONE;
+ // Would it be easier to just change the device size (and pre-scale the canvas)?
+ SkIRect bounds = clipStack.bounds(size(*this)).roundOut();
+ SkMask sourceMask;
+ if (!SkDraw::DrawToMask(path, &bounds, paint->getMaskFilter(), &SkMatrix::I(),
+ &sourceMask, SkMask::kComputeBoundsAndRenderImage_CreateMode,
+ initStyle)) {
+ return;
+ }
+ SkAutoMaskFreeImage srcAutoMaskFreeImage(sourceMask.fImage);
+ SkMask dstMask;
+ SkIPoint margin;
+ if (!paint->getMaskFilter()->filterMask(&dstMask, sourceMask, ctm, &margin)) {
+ return;
+ }
+ SkPixmap pm(SkImageInfo::Make(dstMask.fBounds.width(), dstMask.fBounds.height(),
+ kGray_8_SkColorType, kOpaque_SkAlphaType),
+ dstMask.fImage, dstMask.fRowBytes);
+ sk_sp<SkImage> mask;
+ const int maskQuality = SK_PDF_MASK_QUALITY;
+ if (maskQuality <= 100 && maskQuality >= 0) {
+ SkDynamicMemoryWStream buffer;
+ SkJpegEncoder::Options jpegOptions;
+ jpegOptions.fQuality = maskQuality;
+ if (SkJpegEncoder::Encode(&buffer, pm, jpegOptions)) {
+ mask = SkImage::MakeFromEncoded(buffer.detachAsData());
+ SkASSERT(mask);
+ if (mask) {
+ SkMask::FreeImage(dstMask.fImage);
+ }
+ }
+ }
+ if (!mask) {
+ mask = SkImage::MakeFromRaster(
+ pm, [](const void* p, void*) { SkMask::FreeImage((void*)p); }, nullptr);
+ }
+ // PDF doesn't seem to allow masking vector graphics with an Image XObject.
+ // Must mask with a Form XObject.
+ sk_sp<SkPDFDevice> maskDevice(SkPDFDevice::CreateUnflipped(fPageSize, fRasterDpi, fDocument));
+ {
+ SkPDFCanvas canvas(maskDevice);
+ canvas.drawImage(mask, dstMask.fBounds.x(), dstMask.fBounds.y());
+ }
+ sk_sp<SkPDFDict> sMaskGS = SkPDFGraphicState::GetSMaskGraphicState(
+ maskDevice->makeFormXObjectFromDevice(), false,
+ SkPDFGraphicState::kLuminosity_SMaskMode, fDocument->canon());
+ maskDevice = nullptr;
+ if (!ctm.isIdentity() && paint->getShader()) {
+ transform_shader(paint.writable(), ctm); // Since we are using identity matrix.
+ }
+ ScopedContentEntry content(this, clipStack, SkMatrix::I(), *paint);
+ if (!content.entry()) {
+ return;
+ }
+ SkPDFUtils::ApplyGraphicState(this->addGraphicStateResource(sMaskGS.get()),
+ &content.entry()->fContent);
+
+ SkRect dstBounds = SkRect::Make(dstMask.fBounds);
+ SkPDFUtils::AppendRectangle(dstBounds, &content.entry()->fContent);
+ SkPDFUtils::PaintPath(SkPaint::kFill_Style, path.getFillType(), &content.entry()->fContent);
+
+ // The no-softmask graphic state is used to "turn off" the mask for later draw calls.
+ auto noSMaskGS = SkPDFUtils::GetCachedT(&fDocument->canon()->fNoSmaskGraphicState,
+ &SkPDFGraphicState::MakeNoSmaskGraphicState);
+ SkPDFUtils::ApplyGraphicState(this->addGraphicStateResource(noSMaskGS.get()),
+ &content.entry()->fContent);
+}
+
void SkPDFDevice::internalDrawPath(const SkClipStack& clipStack,
const SkMatrix& ctm,
const SkPath& origPath,
@@ -759,10 +900,16 @@ void SkPDFDevice::internalDrawPath(const SkClipStack& clipStack,
const SkMatrix* prePathMatrix,
bool pathIsMutable) {
SkPaint paint = srcPaint;
+ remove_color_filter(&paint);
replace_srcmode_on_opaque_paint(&paint);
SkPath modifiedPath;
SkPath* pathPtr = const_cast<SkPath*>(&origPath);
+ if (paint.getMaskFilter()) {
+ this->internalDrawPathWithFilter(clipStack, ctm, origPath, paint, prePathMatrix);
+ return;
+ }
+
SkMatrix matrix = ctm;
if (prePathMatrix) {
if (paint.getPathEffect() || paint.getStyle() != SkPaint::kFill_Style) {
@@ -781,26 +928,34 @@ void SkPDFDevice::internalDrawPath(const SkClipStack& clipStack,
return;
}
if (!pathIsMutable) {
+ modifiedPath = origPath;
pathPtr = &modifiedPath;
pathIsMutable = true;
}
- bool fill = paint.getFillPath(origPath, pathPtr);
-
- SkPaint noEffectPaint(paint);
- noEffectPaint.setPathEffect(nullptr);
- if (fill) {
- noEffectPaint.setStyle(SkPaint::kFill_Style);
+ if (paint.getFillPath(*pathPtr, pathPtr)) {
+ paint.setStyle(SkPaint::kFill_Style);
} else {
- noEffectPaint.setStyle(SkPaint::kStroke_Style);
- noEffectPaint.setStrokeWidth(0);
+ paint.setStyle(SkPaint::kStroke_Style);
+ paint.setStrokeWidth(0);
}
- this->internalDrawPath(clipStack, ctm, *pathPtr, noEffectPaint, nullptr, true);
- return;
+ paint.setPathEffect(nullptr);
}
- if (this->handleInversePath(origPath, paint, pathIsMutable, prePathMatrix)) {
+ if (this->handleInversePath(*pathPtr, paint, pathIsMutable, prePathMatrix)) {
return;
}
+ if (matrix.getType() & SkMatrix::kPerspective_Mask) {
+ if (!pathIsMutable) {
+ modifiedPath = origPath;
+ pathPtr = &modifiedPath;
+ pathIsMutable = true;
+ }
+ pathPtr->transform(matrix);
+ if (paint.getShader()) {
+ transform_shader(&paint, matrix);
+ }
+ matrix = SkMatrix::I();
+ }
ScopedContentEntry content(this, clipStack, matrix, paint);
if (!content.entry()) {
@@ -1237,15 +1392,41 @@ static void update_font(SkWStream* wStream, int fontIndex, SkScalar textSize) {
wStream->writeText(" Tf\n");
}
+static SkPath draw_text_as_path(const void* sourceText, size_t sourceByteCount,
+ const SkScalar pos[], SkTextBlob::GlyphPositioning positioning,
+ SkPoint offset, const SkPaint& srcPaint) {
+ SkPath path;
+ int glyphCount;
+ SkAutoTMalloc<SkPoint> tmpPoints;
+ switch (positioning) {
+ case SkTextBlob::kDefault_Positioning:
+ srcPaint.getTextPath(sourceText, sourceByteCount, offset.x(), offset.y(), &path);
+ break;
+ case SkTextBlob::kHorizontal_Positioning:
+ glyphCount = srcPaint.countText(sourceText, sourceByteCount);
+ tmpPoints.realloc(glyphCount);
+ for (int i = 0; i < glyphCount; ++i) {
+ tmpPoints[i] = {pos[i] + offset.x(), offset.y()};
+ }
+ srcPaint.getPosTextPath(sourceText, sourceByteCount, tmpPoints.get(), &path);
+ break;
+ case SkTextBlob::kFull_Positioning:
+ srcPaint.getPosTextPath(sourceText, sourceByteCount, (const SkPoint*)pos, &path);
+ path.offset(offset.x(), offset.y());
+ break;
+ }
+ return path;
+}
+
void SkPDFDevice::internalDrawText(
const void* sourceText, size_t sourceByteCount,
const SkScalar pos[], SkTextBlob::GlyphPositioning positioning,
SkPoint offset, const SkPaint& srcPaint, const uint32_t* clusters,
uint32_t textByteLength, const char* utf8Text) {
- NOT_IMPLEMENTED(srcPaint.getMaskFilter() != nullptr, false);
- if (srcPaint.getMaskFilter() != nullptr) {
- // Don't pretend we support drawing MaskFilters, it makes for artifacts
- // making text unreadable (e.g. same text twice when using CSS shadows).
+ if (0 == sourceByteCount || !sourceText) {
+ return;
+ }
+ if (this->cs().isEmpty(size(*this))) {
return;
}
NOT_IMPLEMENTED(srcPaint.isVerticalText(), false);
@@ -1254,12 +1435,18 @@ void SkPDFDevice::internalDrawText(
// clear to me how to switch to "vertical writing" mode in PDF.
// Currently neither Chromium or Android set this flag.
// https://bug.skia.org/5665
- return;
}
- if (0 == sourceByteCount || !sourceText) {
+ if (srcPaint.getPathEffect()
+ || srcPaint.getMaskFilter()
+ || SkPaint::kFill_Style != srcPaint.getStyle()) {
+ // Stroked Text doesn't work well with Type3 fonts.
+ SkPath path = draw_text_as_path(sourceText, sourceByteCount, pos,
+ positioning, offset, srcPaint);
+ this->drawPath(path, srcPaint, nullptr, true);
return;
}
SkPaint paint = calculate_text_paint(srcPaint);
+ remove_color_filter(&paint);
replace_srcmode_on_opaque_paint(&paint);
if (!paint.getTypeface()) {
paint.setTypeface(SkTypeface::MakeDefault());
diff --git a/src/pdf/SkPDFDevice.h b/src/pdf/SkPDFDevice.h
index 1906bb37a7..61c08c397d 100644
--- a/src/pdf/SkPDFDevice.h
+++ b/src/pdf/SkPDFDevice.h
@@ -274,6 +274,12 @@ private:
const SkMatrix* prePathMatrix,
bool pathIsMutable);
+ void internalDrawPathWithFilter(const SkClipStack& clipStack,
+ const SkMatrix& ctm,
+ const SkPath& origPath,
+ const SkPaint& paint,
+ const SkMatrix* prePathMatrix);
+
bool handleInversePath(const SkPath& origPath,
const SkPaint& paint, bool pathIsMutable,
const SkMatrix* prePathMatrix = nullptr);
diff --git a/src/pdf/SkPDFShader.cpp b/src/pdf/SkPDFShader.cpp
index b6b096d22c..49af685d7a 100644
--- a/src/pdf/SkPDFShader.cpp
+++ b/src/pdf/SkPDFShader.cpp
@@ -1230,7 +1230,7 @@ SkPDFShader::State::State(SkShader* shader, const SkMatrix& canvasTransform,
if (fType != SkShader::kNone_GradientType) {
fBitmapKey = SkBitmapKey{{0, 0, 0, 0}, 0};
- fShaderTransform = shader->getLocalMatrix();
+ fShaderTransform = SkPDFUtils::GetShaderLocalMatrix(shader);
this->allocateGradientInfoStorage();
shader->asAGradient(&fInfo);
return;
diff --git a/src/pdf/SkPDFUtils.h b/src/pdf/SkPDFUtils.h
index 00c4b72b7b..c1d9a72250 100644
--- a/src/pdf/SkPDFUtils.h
+++ b/src/pdf/SkPDFUtils.h
@@ -11,6 +11,7 @@
#include "SkPaint.h"
#include "SkPath.h"
+#include "SkShader.h"
#include "SkStream.h"
#include "SkUtils.h"
@@ -111,6 +112,14 @@ static sk_sp<T> GetCachedT(sk_sp<T>* cachedT, sk_sp<T> (*makeNewT)()) {
*cachedT = (*makeNewT)();
return *cachedT;
}
+
+inline SkMatrix GetShaderLocalMatrix(const SkShader* shader) {
+ SkMatrix localMatrix;
+ if (sk_sp<SkShader> s = shader->makeAsALocalMatrixShader(&localMatrix)) {
+ return SkMatrix::Concat(s->getLocalMatrix(), localMatrix);
+ }
+ return shader->getLocalMatrix();
+}
} // namespace SkPDFUtils
#endif