[SkSVGDevice] Initial clipping support
Implement SVG clips based on clip stack flattening -
which is now exposed in SkClipStack::asPath() and shared
with SkCanvas's simplify-clip code.
R=reed@google.com,mtklein@google.com
Review URL: https://codereview.chromium.org/876923003
diff --git a/experimental/svg/SkSVGDevice.cpp b/experimental/svg/SkSVGDevice.cpp
index bd503b7..9f4a54a8a 100644
--- a/experimental/svg/SkSVGDevice.cpp
+++ b/experimental/svg/SkSVGDevice.cpp
@@ -11,6 +11,7 @@
#include "SkDraw.h"
#include "SkPaint.h"
#include "SkParsePath.h"
+#include "SkPathOps.h"
#include "SkShader.h"
#include "SkStream.h"
#include "SkTypeface.h"
@@ -20,12 +21,10 @@
namespace {
static SkString svg_color(SkColor color) {
- SkString colorStr;
- colorStr.printf("rgb(%u,%u,%u)",
- SkColorGetR(color),
- SkColorGetG(color),
- SkColorGetB(color));
- return colorStr;
+ return SkStringPrintf("rgb(%u,%u,%u)",
+ SkColorGetR(color),
+ SkColorGetG(color),
+ SkColorGetB(color));
}
static SkScalar svg_opacity(SkColor color) {
@@ -102,6 +101,7 @@
: fPaintServer(svg_color(paint.getColor())) {}
SkString fPaintServer;
+ SkString fClip;
};
}
@@ -110,16 +110,19 @@
// and deduplicate resources.
class SkSVGDevice::ResourceBucket : ::SkNoncopyable {
public:
- ResourceBucket() : fGradientCount(0) {}
+ ResourceBucket() : fGradientCount(0), fClipCount(0) {}
SkString addLinearGradient() {
- SkString id;
- id.printf("gradient_%d", fGradientCount++);
- return id;
+ return SkStringPrintf("gradient_%d", fGradientCount++);
+ }
+
+ SkString addClip() {
+ return SkStringPrintf("clip_%d", fClipCount++);
}
private:
uint32_t fGradientCount;
+ uint32_t fClipCount;
};
class SkSVGDevice::AutoElement : ::SkNoncopyable {
@@ -135,7 +138,7 @@
: fWriter(writer)
, fResourceBucket(bucket) {
- Resources res = this->addResources(paint);
+ Resources res = this->addResources(draw, paint);
fWriter->startElement(name);
@@ -167,11 +170,13 @@
fWriter->addText(text.c_str());
}
+ void addRectAttributes(const SkRect&);
void addFontAttributes(const SkPaint&);
private:
- Resources addResources(const SkPaint& paint);
- void addResourceDefs(const SkPaint& paint, Resources* resources);
+ Resources addResources(const SkDraw& draw, const SkPaint& paint);
+ void addClipResources(const SkDraw& draw, Resources* resources);
+ void addShaderResources(const SkPaint& paint, Resources* resources);
void addPaint(const SkPaint& paint, const Resources& resources);
void addTransform(const SkMatrix& transform, const char name[] = "transform");
@@ -200,6 +205,10 @@
if (SK_AlphaOPAQUE != SkColorGetA(paint.getColor())) {
this->addAttribute("opacity", svg_opacity(paint.getColor()));
}
+
+ if (!resources.fClip.isEmpty()) {
+ this->addAttribute("clip-path", resources.fClip);
+ }
}
void SkSVGDevice::AutoElement::addTransform(const SkMatrix& t, const char name[]) {
@@ -233,18 +242,31 @@
fWriter->addAttribute(name, tstr.c_str());
}
-Resources SkSVGDevice::AutoElement::addResources(const SkPaint& paint) {
+Resources SkSVGDevice::AutoElement::addResources(const SkDraw& draw, const SkPaint& paint) {
Resources resources(paint);
- this->addResourceDefs(paint, &resources);
+
+ // FIXME: this is a weak heuristic and we end up with LOTS of redundant clips.
+ bool hasClip = !draw.fClipStack->isWideOpen();
+ bool hasShader = SkToBool(paint.getShader());
+
+ if (hasClip || hasShader) {
+ AutoElement defs("defs", fWriter);
+
+ if (hasClip) {
+ this->addClipResources(draw, &resources);
+ }
+
+ if (hasShader) {
+ this->addShaderResources(paint, &resources);
+ }
+ }
+
return resources;
}
-void SkSVGDevice::AutoElement::addResourceDefs(const SkPaint& paint, Resources* resources) {
+void SkSVGDevice::AutoElement::addShaderResources(const SkPaint& paint, Resources* resources) {
const SkShader* shader = paint.getShader();
- if (!SkToBool(shader)) {
- // TODO: clip support
- return;
- }
+ SkASSERT(SkToBool(shader));
SkShader::GradientInfo grInfo;
grInfo.fColorCount = 0;
@@ -254,21 +276,49 @@
return;
}
+ SkAutoSTArray<16, SkColor> grColors(grInfo.fColorCount);
+ SkAutoSTArray<16, SkScalar> grOffsets(grInfo.fColorCount);
+ grInfo.fColors = grColors.get();
+ grInfo.fColorOffsets = grOffsets.get();
+
+ // One more call to get the actual colors/offsets.
+ shader->asAGradient(&grInfo);
+ SkASSERT(grInfo.fColorCount <= grColors.count());
+ SkASSERT(grInfo.fColorCount <= grOffsets.count());
+
+ resources->fPaintServer.printf("url(#%s)", addLinearGradientDef(grInfo, shader).c_str());
+}
+
+void SkSVGDevice::AutoElement::addClipResources(const SkDraw& draw, Resources* resources) {
+ SkASSERT(!draw.fClipStack->isWideOpen());
+
+ SkPath clipPath;
+ (void) draw.fClipStack->asPath(&clipPath);
+
+ SkString clipID = fResourceBucket->addClip();
+ const char* clipRule = clipPath.getFillType() == SkPath::kEvenOdd_FillType ?
+ "evenodd" : "nonzero";
{
- AutoElement defs("defs", fWriter);
+ // clipPath is in device space, but since we're only pushing transform attributes
+ // to the leaf nodes, so are all our elements => SVG userSpaceOnUse == device space.
+ AutoElement clipPathElement("clipPath", fWriter);
+ clipPathElement.addAttribute("id", clipID);
- SkAutoSTArray<16, SkColor> grColors(grInfo.fColorCount);
- SkAutoSTArray<16, SkScalar> grOffsets(grInfo.fColorCount);
- grInfo.fColors = grColors.get();
- grInfo.fColorOffsets = grOffsets.get();
-
- // One more call to get the actual colors/offsets.
- shader->asAGradient(&grInfo);
- SkASSERT(grInfo.fColorCount <= grColors.count());
- SkASSERT(grInfo.fColorCount <= grOffsets.count());
-
- resources->fPaintServer.printf("url(#%s)", addLinearGradientDef(grInfo, shader).c_str());
+ SkRect clipRect = SkRect::MakeEmpty();
+ if (clipPath.isEmpty() || clipPath.isRect(&clipRect)) {
+ AutoElement rectElement("rect", fWriter);
+ rectElement.addRectAttributes(clipRect);
+ rectElement.addAttribute("clip-rule", clipRule);
+ } else {
+ AutoElement pathElement("path", fWriter);
+ SkString pathStr;
+ SkParsePath::ToSVGString(clipPath, &pathStr);
+ pathElement.addAttribute("d", pathStr.c_str());
+ pathElement.addAttribute("clip-rule", clipRule);
+ }
}
+
+ resources->fClip.printf("url(#%s)", clipID.c_str());
}
SkString SkSVGDevice::AutoElement::addLinearGradientDef(const SkShader::GradientInfo& info,
@@ -307,6 +357,19 @@
return id;
}
+void SkSVGDevice::AutoElement::addRectAttributes(const SkRect& rect) {
+ // x, y default to 0
+ if (rect.x() != 0) {
+ this->addAttribute("x", rect.x());
+ }
+ if (rect.y() != 0) {
+ this->addAttribute("y", rect.y());
+ }
+
+ this->addAttribute("width", rect.width());
+ this->addAttribute("height", rect.height());
+}
+
void SkSVGDevice::AutoElement::addFontAttributes(const SkPaint& paint) {
this->addAttribute("font-size", paint.getTextSize());
@@ -365,10 +428,8 @@
void SkSVGDevice::drawPaint(const SkDraw& draw, const SkPaint& paint) {
AutoElement rect("rect", fWriter, fResourceBucket, draw, paint);
- rect.addAttribute("x", 0);
- rect.addAttribute("y", 0);
- rect.addAttribute("width", this->width());
- rect.addAttribute("height", this->height());
+ rect.addRectAttributes(SkRect::MakeWH(SkIntToScalar(this->width()),
+ SkIntToScalar(this->height())));
}
void SkSVGDevice::drawPoints(const SkDraw&, SkCanvas::PointMode mode, size_t count,
@@ -379,10 +440,7 @@
void SkSVGDevice::drawRect(const SkDraw& draw, const SkRect& r, const SkPaint& paint) {
AutoElement rect("rect", fWriter, fResourceBucket, draw, paint);
- rect.addAttribute("x", r.fLeft);
- rect.addAttribute("y", r.fTop);
- rect.addAttribute("width", r.width());
- rect.addAttribute("height", r.height());
+ rect.addRectAttributes(r);
}
void SkSVGDevice::drawOval(const SkDraw& draw, const SkRect& oval, const SkPaint& paint) {
diff --git a/include/core/SkClipStack.h b/include/core/SkClipStack.h
index 95e41e6..79789dc 100644
--- a/include/core/SkClipStack.h
+++ b/include/core/SkClipStack.h
@@ -323,6 +323,12 @@
*/
bool quickContains(const SkRect& devRect) const;
+ /**
+ * Flattens the clip stack into a single SkPath. Returns true if any of
+ * the clip stack components requires anti-aliasing.
+ */
+ bool asPath(SkPath* path) const;
+
void clipDevRect(const SkIRect& ir, SkRegion::Op op) {
SkRect r;
r.set(ir);
diff --git a/src/core/SkCanvas.cpp b/src/core/SkCanvas.cpp
index 5b37e08..56cfb8d 100644
--- a/src/core/SkCanvas.cpp
+++ b/src/core/SkCanvas.cpp
@@ -1446,30 +1446,11 @@
fClipStack.clipDevPath(devPath, op, kSoft_ClipEdgeStyle == edgeStyle);
if (fAllowSimplifyClip) {
- devPath.reset();
- devPath.setFillType(SkPath::kInverseEvenOdd_FillType);
- const SkClipStack* clipStack = getClipStack();
- SkClipStack::Iter iter(*clipStack, SkClipStack::Iter::kBottom_IterStart);
- const SkClipStack::Element* element;
- while ((element = iter.next())) {
- SkClipStack::Element::Type type = element->getType();
- SkPath operand;
- if (type != SkClipStack::Element::kEmpty_Type) {
- element->asPath(&operand);
- }
- SkRegion::Op elementOp = element->getOp();
- if (elementOp == SkRegion::kReplace_Op) {
- devPath = operand;
- } else {
- Op(devPath, operand, (SkPathOp) elementOp, &devPath);
- }
- // if the prev and curr clips disagree about aa -vs- not, favor the aa request.
- // perhaps we need an API change to avoid this sort of mixed-signals about
- // clipping.
- if (element->isAA()) {
- edgeStyle = kSoft_ClipEdgeStyle;
- }
+ bool clipIsAA = getClipStack()->asPath(&devPath);
+ if (clipIsAA) {
+ edgeStyle = kSoft_ClipEdgeStyle;
}
+
op = SkRegion::kReplace_Op;
}
diff --git a/src/core/SkClipStack.cpp b/src/core/SkClipStack.cpp
index 515596a..2d8c94f 100644
--- a/src/core/SkClipStack.cpp
+++ b/src/core/SkClipStack.cpp
@@ -8,6 +8,7 @@
#include "SkCanvas.h"
#include "SkClipStack.h"
#include "SkPath.h"
+#include "SkPathOps.h"
#include "SkThread.h"
#include <new>
@@ -665,6 +666,35 @@
return true;
}
+bool SkClipStack::asPath(SkPath *path) const {
+ bool isAA = false;
+
+ path->reset();
+ path->setFillType(SkPath::kInverseEvenOdd_FillType);
+
+ SkClipStack::Iter iter(*this, SkClipStack::Iter::kBottom_IterStart);
+ while (const SkClipStack::Element* element = iter.next()) {
+ SkPath operand;
+ if (element->getType() != SkClipStack::Element::kEmpty_Type) {
+ element->asPath(&operand);
+ }
+
+ SkRegion::Op elementOp = element->getOp();
+ if (elementOp == SkRegion::kReplace_Op) {
+ *path = operand;
+ } else {
+ Op(*path, operand, (SkPathOp)elementOp, path);
+ }
+
+ // if the prev and curr clips disagree about aa -vs- not, favor the aa request.
+ // perhaps we need an API change to avoid this sort of mixed-signals about
+ // clipping.
+ isAA = (isAA || element->isAA());
+ }
+
+ return isAA;
+}
+
void SkClipStack::pushElement(const Element& element) {
// Use reverse iterator instead of back because Rect path may need previous
SkDeque::Iter iter(fDeque, SkDeque::Iter::kBack_IterStart);