Inverse fill support in PDF
authorcommit-bot@chromium.org <commit-bot@chromium.org@2bbb7eff-a529-9590-31e7-b0007b416f81>
Wed, 31 Jul 2013 22:54:31 +0000 (22:54 +0000)
committercommit-bot@chromium.org <commit-bot@chromium.org@2bbb7eff-a529-9590-31e7-b0007b416f81>
Wed, 31 Jul 2013 22:54:31 +0000 (22:54 +0000)
BUG= https://code.google.com/p/skia/issues/detail?id=241 (partial fix)
R=edisonn@google.com, vandebo@chromium.org, reed@google.com

Author: richardlin@chromium.org

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

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

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

diff --git a/gm/inversepaths.cpp b/gm/inversepaths.cpp
new file mode 100644 (file)
index 0000000..dc2b636
--- /dev/null
@@ -0,0 +1,146 @@
+/*
+ * 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 {
+
+static SkPath generate_square(SkScalar cx, SkScalar cy, SkScalar w) {
+    SkRect rect = SkRect::MakeXYWH(cx - w / 2, cy - w / 2, w, w);
+    SkPath path;
+    path.addRect(rect);
+    return path;
+}
+
+static SkPath generate_rect_line(SkScalar cx, SkScalar cy, SkScalar l) {
+    SkRect rect = SkRect::MakeXYWH(cx - l / 2, cy, l, 0);
+    SkPath path;
+    path.addRect(rect);
+    return path;
+}
+
+static SkPath generate_circle(SkScalar cx, SkScalar cy, SkScalar d) {
+    SkPath path;
+    path.addCircle(cx, cy, d/2, SkPath::kCW_Direction);
+    return path;
+}
+
+static SkPath generate_line(SkScalar cx, SkScalar cy, SkScalar l) {
+    SkPath path;
+    path.moveTo(cx - l / 2, cy);
+    path.lineTo(cx + l / 2, cy);
+    return path;
+}
+
+SkPaint::Style styles[] = {
+        SkPaint::kStroke_Style,
+        SkPaint::kStrokeAndFill_Style,
+        SkPaint::kFill_Style
+};
+SkScalar pathSizes[] = {
+        40,
+        10,
+        0
+};
+SkScalar strokeWidths[] = {
+        10,
+        0
+};
+SkPath ((*paths[])(SkScalar, SkScalar, SkScalar)) = {
+        generate_square,
+        generate_rect_line,
+        generate_circle,
+        generate_line
+};
+
+const SkScalar slideWidth = 90, slideHeight = 90;
+const SkScalar slideBoundary = 5;
+
+
+class InversePathsGM : public GM {
+public:
+    InversePathsGM() {
+
+    }
+
+protected:
+    virtual SkString onShortName() {
+        return SkString("inverse_paths");
+    }
+
+    virtual SkISize onISize() {
+        return make_isize(800, 900);
+    }
+
+    virtual void onDraw(SkCanvas* canvas) {
+        SkScalar cx = slideWidth / 2 + slideBoundary;
+        SkScalar cy = slideHeight / 2 + slideBoundary;
+        SkScalar dx = slideWidth + 2 * slideBoundary;
+        SkScalar dy = slideHeight + 2 * slideBoundary;
+
+        SkRect clipRect = SkRect::MakeLTRB(slideBoundary, slideBoundary,
+                                           slideBoundary + slideWidth,
+                                           slideBoundary + slideHeight);
+        SkPaint clipPaint;
+        clipPaint.setStyle(SkPaint::kStroke_Style);
+        clipPaint.setStrokeWidth(SkIntToScalar(2));
+
+        SkPaint outlinePaint;
+        outlinePaint.setColor(0x40000000);
+        outlinePaint.setStyle(SkPaint::kStroke_Style);
+        outlinePaint.setStrokeWidth(SkIntToScalar(0));
+
+        for (size_t styleIndex = 0; styleIndex < SK_ARRAY_COUNT(styles);
+                styleIndex++) {
+            for (size_t sizeIndex = 0; sizeIndex < SK_ARRAY_COUNT(pathSizes);
+                    sizeIndex++) {
+                SkScalar size = pathSizes[sizeIndex];
+
+                canvas->save();
+
+                for (size_t widthIndex = 0;
+                        widthIndex < SK_ARRAY_COUNT(strokeWidths);
+                        widthIndex++) {
+                    SkPaint paint;
+                    paint.setColor(0xff007000);
+                    paint.setStrokeWidth(strokeWidths[widthIndex]);
+                    paint.setStyle(styles[styleIndex]);
+
+                    for (size_t pathIndex = 0;
+                            pathIndex < SK_ARRAY_COUNT(paths);
+                            pathIndex++) {
+                        canvas->drawRect(clipRect, clipPaint);
+
+                        canvas->save();
+                        canvas->clipRect(clipRect);
+
+                        SkPath path = paths[pathIndex](cx, cy, size);
+                        path.setFillType(SkPath::kInverseWinding_FillType);
+                        canvas->drawPath(path, paint);
+
+                        path.setFillType(SkPath::kWinding_FillType);
+                        canvas->drawPath(path, outlinePaint);
+
+                        canvas->restore();
+                        canvas->translate(dx, 0);
+                    }
+                }
+
+                canvas->restore();
+                canvas->translate(0, dy);
+            }
+        }
+    }
+
+private:
+    typedef GM INHERITED;
+};
+
+DEF_GM( return new InversePathsGM; )
+}
index 3af7d73..cb88389 100644 (file)
@@ -61,6 +61,7 @@
     '../gm/hittestpath.cpp',
     '../gm/imageblur.cpp',
     '../gm/imagemagnifier.cpp',
+    '../gm/inversepaths.cpp',
     '../gm/lerpmode.cpp',
     '../gm/lighting.cpp',
     '../gm/image.cpp',
index 60a69e1..236a99a 100644 (file)
  */
 //#define SK_SUPPORT_GPU 1
 
+
+/* The PDF generation code uses Path Ops to generate inverse fills and complex
+ * clipping paths, but at this time, Path Ops is not release ready yet. So,
+ * the code is hidden behind this #define guard. If you are feeling adventurous
+ * and want the latest and greatest PDF generation code, uncomment the #define.
+ * When Path Ops is release ready, the define guards and this user config
+ * define should be removed entirely.
+ */
+//#define SK_PDF_USE_PATHOPS
+
 #endif
index cb17134..b781978 100644 (file)
@@ -293,6 +293,8 @@ private:
      */
     void copyContentEntriesToData(ContentEntry* entry, SkWStream* data) const;
 
+    bool handleInversePath(const SkDraw& d, const SkPath& origPath,
+                           const SkPaint& paint, bool pathIsMutable);
     bool handleRectAnnotation(const SkRect& r, const SkMatrix& matrix,
                               const SkPaint& paint);
     bool handlePointAnnotation(const SkPoint* points, size_t count,
index 387bfe8..eda3616 100644 (file)
@@ -16,6 +16,7 @@
 #include "SkGlyphCache.h"
 #include "SkPaint.h"
 #include "SkPath.h"
+#include "SkPathOps.h"
 #include "SkPDFFont.h"
 #include "SkPDFFormXObject.h"
 #include "SkPDFGraphicState.h"
@@ -842,6 +843,12 @@ void SkPDFDevice::drawPath(const SkDraw& d, const SkPath& origPath,
         return;
     }
 
+#ifdef SK_PDF_USE_PATHOPS
+    if (handleInversePath(d, origPath, paint, pathIsMutable)) {
+        return;
+    }
+#endif
+
     if (handleRectAnnotation(pathPtr->getBounds(), *d.fMatrix, paint)) {
         return;
     }
@@ -1232,6 +1239,80 @@ 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);
+}
+
+/* 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,
+ * otherwise returns false and the normal drawing routine should continue,
+ * either as a (incorrect) fallback or because the path was not inverse
+ * in the first place.
+ */
+bool SkPDFDevice::handleInversePath(const SkDraw& d, const SkPath& origPath,
+                                    const SkPaint& paint, bool pathIsMutable) {
+    if (!origPath.isInverseFillType()) {
+        return false;
+    }
+
+    if (d.fClip->isEmpty()) {
+        return false;
+    }
+
+    SkPath modifiedPath;
+    SkPath* pathPtr = const_cast<SkPath*>(&origPath);
+    SkPaint noInversePaint(paint);
+
+    // Merge stroking operations into final path.
+    if (SkPaint::kStroke_Style == paint.getStyle() ||
+        SkPaint::kStrokeAndFill_Style == paint.getStyle()) {
+        bool doFillPath = paint.getFillPath(origPath, &modifiedPath);
+        if (doFillPath) {
+            noInversePaint.setStyle(SkPaint::kFill_Style);
+            noInversePaint.setStrokeWidth(0);
+            pathPtr = &modifiedPath;
+        } else {
+            // To be consistent with the raster output, hairline strokes
+            // are rendered as non-inverted.
+            modifiedPath.toggleInverseFillType();
+            drawPath(d, modifiedPath, paint, NULL, true);
+            return true;
+        }
+    }
+
+    // Get bounds of clip in current transform space
+    // (clip bounds are given in device space).
+    SkRect bounds;
+    SkMatrix transformInverse;
+    if (!d.fMatrix->invert(&transformInverse)) {
+        return false;
+    }
+    bounds.set(d.fClip->getBounds());
+    transformInverse.mapRect(&bounds);
+
+    // Extend the bounds by the line width (plus some padding)
+    // so the edge doesn't cause a visible stroke.
+    bounds.outset(paint.getStrokeWidth() + SK_Scalar1,
+                  paint.getStrokeWidth() + SK_Scalar1);
+
+    if (!calculate_inverse_path(bounds, *pathPtr, &modifiedPath)) {
+        return false;
+    }
+
+    drawPath(d, modifiedPath, noInversePaint, NULL, true);
+    return true;
+}
+
 bool SkPDFDevice::handleRectAnnotation(const SkRect& r, const SkMatrix& matrix,
                                        const SkPaint& p) {
     SkAnnotation* annotationInfo = p.getAnnotation();