Use Path Ops to generate PDF clips
authorcommit-bot@chromium.org <commit-bot@chromium.org@2bbb7eff-a529-9590-31e7-b0007b416f81>
Thu, 8 Aug 2013 02:52:05 +0000 (02:52 +0000)
committercommit-bot@chromium.org <commit-bot@chromium.org@2bbb7eff-a529-9590-31e7-b0007b416f81>
Thu, 8 Aug 2013 02:52:05 +0000 (02:52 +0000)
R=vandebo@chromium.org, edisonn@google.com, caryclark@google.com

Author: richardlin@chromium.org

Review URL: https://chromiumcodereview.appspot.com/21161003

git-svn-id: http://skia.googlecode.com/svn/trunk@10633 2bbb7eff-a529-9590-31e7-b0007b416f81

gm/circularclips.cpp [new file with mode: 0644]
gyp/gmslides.gypi
include/pdf/SkPDFDevice.h
src/pdf/SkPDFDevice.cpp

diff --git a/gm/circularclips.cpp b/gm/circularclips.cpp
new file mode 100644 (file)
index 0000000..fdf2dfb
--- /dev/null
@@ -0,0 +1,78 @@
+/*
+ * 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 "gm.h"
+#include "SkCanvas.h"
+#include "SkPath.h"
+
+namespace skiagm {
+
+class CircularClipsGM : public GM {
+public:
+    CircularClipsGM() {}
+
+protected:
+    virtual SkString onShortName() {
+        return SkString("circular-clips");
+    }
+
+    virtual SkISize onISize() {
+        return SkISize::Make(800, 600);
+    }
+
+    virtual void onDraw(SkCanvas* canvas) {
+        SkRegion::Op ops[] = {
+            SkRegion::kDifference_Op,
+            SkRegion::kIntersect_Op,
+            SkRegion::kUnion_Op,
+            SkRegion::kXOR_Op,
+            SkRegion::kReverseDifference_Op,
+            SkRegion::kReplace_Op,
+        };
+
+        SkScalar x1 = 80, x2 = 120;
+        SkScalar y = 50;
+        SkScalar r = 40;
+
+        SkPath circle1, circle2;
+        circle1.addCircle(x1, y, r, SkPath::kCW_Direction);
+        circle2.addCircle(x2, y, r, SkPath::kCW_Direction);
+        SkRect rect = SkRect::MakeLTRB(x1 - r, y - r, x2 + r, y + r);
+
+        SkPaint fillPaint;
+
+        for (size_t i = 0; i < 4; i++) {
+            circle1.toggleInverseFillType();
+            if (i % 2 == 0) {
+                circle2.toggleInverseFillType();
+            }
+
+            canvas->save();
+            for (size_t op = 0; op < SK_ARRAY_COUNT(ops); op++) {
+                canvas->save();
+
+                canvas->clipPath(circle1, SkRegion::kReplace_Op);
+                canvas->clipPath(circle2, ops[op]);
+
+                canvas->drawRect(rect, fillPaint);
+
+                canvas->restore();
+                canvas->translate(0, 2 * y);
+            }
+            canvas->restore();
+            canvas->translate(x1 + x2, 0);
+        }
+    }
+
+private:
+    typedef GM INHERITED;
+};
+
+//////////////////////////////////////////////////////////////////////////////
+
+DEF_GM( return new CircularClipsGM; )
+}
index 8d0b2e9..71eb4d2 100644 (file)
@@ -20,6 +20,7 @@
     '../gm/blurquickreject.cpp',
     '../gm/blurrect.cpp',
     '../gm/circles.cpp',
+    '../gm/circularclips.cpp',
     '../gm/colorfilterimagefilter.cpp',
     '../gm/colormatrix.cpp',
     '../gm/colortype.cpp',
index b781978..207b2b2 100644 (file)
@@ -293,8 +293,10 @@ private:
      */
     void copyContentEntriesToData(ContentEntry* entry, SkWStream* data) const;
 
+#ifdef SK_PDF_USE_PATHOPS
     bool handleInversePath(const SkDraw& d, const SkPath& origPath,
                            const SkPaint& paint, bool pathIsMutable);
+#endif
     bool handleRectAnnotation(const SkRect& r, const SkMatrix& matrix,
                               const SkPaint& paint);
     bool handlePointAnnotation(const SkPoint* points, size_t count,
index eda3616..410b049 100644 (file)
@@ -327,6 +327,96 @@ static void emit_clip(SkPath* clipPath, SkRect* clipRect,
     }
 }
 
+#ifdef SK_PDF_USE_PATHOPS
+/* Calculate an inverted path's equivalent non-inverted path, given the
+ * canvas bounds.
+ * outPath may alias with invPath (since this is supported by PathOps).
+ */
+static bool calculate_inverse_path(const SkRect& bounds, const SkPath& invPath,
+                                   SkPath* outPath) {
+    SkASSERT(invPath.isInverseFillType());
+
+    SkPath clipPath;
+    clipPath.addRect(bounds);
+
+    return Op(clipPath, invPath, kIntersect_PathOp, outPath);
+}
+
+// Sanity check the numerical values of the SkRegion ops and PathOps ops
+// enums so region_op_to_pathops_op can do a straight passthrough cast.
+// If these are failing, it may be necessary to make region_op_to_pathops_op
+// do more.
+SK_COMPILE_ASSERT(SkRegion::kDifference_Op == (int)kDifference_PathOp,
+                  region_pathop_mismatch);
+SK_COMPILE_ASSERT(SkRegion::kIntersect_Op == (int)kIntersect_PathOp,
+                  region_pathop_mismatch);
+SK_COMPILE_ASSERT(SkRegion::kUnion_Op == (int)kUnion_PathOp,
+                  region_pathop_mismatch);
+SK_COMPILE_ASSERT(SkRegion::kXOR_Op == (int)kXOR_PathOp,
+                  region_pathop_mismatch);
+SK_COMPILE_ASSERT(SkRegion::kReverseDifference_Op ==
+                  (int)kReverseDifference_PathOp,
+                  region_pathop_mismatch);
+
+static SkPathOp region_op_to_pathops_op(SkRegion::Op op) {
+    SkASSERT(op >= 0);
+    SkASSERT(op <= SkRegion::kReverseDifference_Op);
+    return (SkPathOp)op;
+}
+
+/* Uses Path Ops to calculate a vector SkPath clip from a clip stack.
+ * Returns true if successful, or false if not successful.
+ * If successful, the resulting clip is stored in outClipPath.
+ * If not successful, outClipPath is undefined, and a fallback method
+ * should be used.
+ */
+static bool get_clip_stack_path(const SkMatrix& transform,
+                                const SkClipStack& clipStack,
+                                const SkRegion& clipRegion,
+                                SkPath* outClipPath) {
+    outClipPath->reset();
+    outClipPath->setFillType(SkPath::kInverseWinding_FillType);
+
+    const SkClipStack::Element* clipEntry;
+    SkClipStack::Iter iter;
+    iter.reset(clipStack, SkClipStack::Iter::kBottom_IterStart);
+    for (clipEntry = iter.next(); clipEntry; clipEntry = iter.next()) {
+        SkPath entryPath;
+        if (SkClipStack::Element::kEmpty_Type == clipEntry->getType()) {
+            outClipPath->reset();
+            outClipPath->setFillType(SkPath::kInverseWinding_FillType);
+            continue;
+        } else if (SkClipStack::Element::kRect_Type == clipEntry->getType()) {
+            entryPath.addRect(clipEntry->getRect());
+        } else if (SkClipStack::Element::kPath_Type == clipEntry->getType()) {
+            entryPath = clipEntry->getPath();
+        }
+        entryPath.transform(transform);
+
+        if (SkRegion::kReplace_Op == clipEntry->getOp()) {
+            *outClipPath = entryPath;
+        } else {
+            SkPathOp op = region_op_to_pathops_op(clipEntry->getOp());
+            if (!Op(*outClipPath, entryPath, op, outClipPath)) {
+                return false;
+            }
+        }
+    }
+
+    if (outClipPath->isInverseFillType()) {
+        // The bounds are slightly outset to ensure this is correct in the
+        // face of floating-point accuracy and possible SkRegion bitmap
+        // approximations.
+        SkRect clipBounds = SkRect::Make(clipRegion.getBounds());
+        clipBounds.outset(SK_Scalar1, SK_Scalar1);
+        if (!calculate_inverse_path(clipBounds, *outClipPath, outClipPath)) {
+            return false;
+        }
+    }
+    return true;
+}
+#endif
+
 // TODO(vandebo): Take advantage of SkClipStack::getSaveCount(), the PDF
 // graphic state stack, and the fact that we can know all the clips used
 // on the page to optimize this.
@@ -345,6 +435,19 @@ void GraphicStackState::updateClip(const SkClipStack& clipStack,
     }
     push();
 
+    currentEntry()->fClipStack = clipStack;
+    currentEntry()->fClipRegion = clipRegion;
+
+    SkMatrix transform;
+    transform.setTranslate(translation.fX, translation.fY);
+
+#ifdef SK_PDF_USE_PATHOPS
+    SkPath clipPath;
+    if (get_clip_stack_path(transform, clipStack, clipRegion, &clipPath)) {
+        emit_clip(&clipPath, NULL, fContentStream);
+        return;
+    }
+#endif
     // gsState->initialEntry()->fClipStack/Region specifies the clip that has
     // already been applied.  (If this is a top level device, then it specifies
     // a clip to the content area.  If this is a layer, then it specifies
@@ -373,8 +476,6 @@ void GraphicStackState::updateClip(const SkClipStack& clipStack,
         emit_clip(&clipPath, NULL, fContentStream);
     } else {
         skip_clip_stack_prefix(fEntries[0].fClipStack, clipStack, &iter);
-        SkMatrix transform;
-        transform.setTranslate(translation.fX, translation.fY);
         const SkClipStack::Element* clipEntry;
         for (clipEntry = iter.next(); clipEntry; clipEntry = iter.next()) {
             SkASSERT(clipEntry->getOp() == SkRegion::kIntersect_Op);
@@ -396,8 +497,6 @@ void GraphicStackState::updateClip(const SkClipStack& clipStack,
             }
         }
     }
-    currentEntry()->fClipStack = clipStack;
-    currentEntry()->fClipRegion = clipRegion;
 }
 
 void GraphicStackState::updateMatrix(const SkMatrix& matrix) {
@@ -1239,19 +1338,7 @@ SkData* SkPDFDevice::copyContentToData() const {
     return data.copyToData();
 }
 
-/* Calculate an inverted path's equivalent non-inverted path, given the
- * canvas bounds.
- */
-static bool calculate_inverse_path(const SkRect& bounds, const SkPath& invPath,
-                                   SkPath* outPath) {
-    SkASSERT(invPath.isInverseFillType());
-
-    SkPath clipPath;
-    clipPath.addRect(bounds);
-
-    return Op(clipPath, invPath, kIntersect_PathOp, outPath);
-}
-
+#ifdef SK_PDF_USE_PATHOPS
 /* Draws an inverse filled path by using Path Ops to compute the positive
  * inverse using the current clip as the inverse bounds.
  * Return true if this was an inverse path and was properly handled,
@@ -1312,6 +1399,7 @@ bool SkPDFDevice::handleInversePath(const SkDraw& d, const SkPath& origPath,
     drawPath(d, modifiedPath, noInversePaint, NULL, true);
     return true;
 }
+#endif
 
 bool SkPDFDevice::handleRectAnnotation(const SkRect& r, const SkMatrix& matrix,
                                        const SkPaint& p) {