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>
This commit is contained in:
Hal Canary 2017-05-26 17:01:16 -04:00 committed by Skia Commit-Bot
parent f36031b68a
commit d12a67626d
4 changed files with 230 additions and 28 deletions

View File

@ -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());

View File

@ -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);

View File

@ -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;

View File

@ -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