Bug 827053 - Add support for winding in fill + clip + isPointInPath + tests the feature. r=bas
authorRik Cabanier <cabanier@adobe.com>
Wed, 16 Jan 2013 21:55:43 -0500
changeset 119115 64450d6fee962d0210848a586033a1ac90ecd6cd
parent 119114 3b3c304723cc9d66b5e54f2423e677476dcf5f75
child 119116 81f41dd974c45b43e37ee425d23c793b299669c3
push id24189
push useremorley@mozilla.com
push dateThu, 17 Jan 2013 10:42:06 +0000
treeherdermozilla-central@712eca11a04e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbas
bugs827053
milestone21.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 827053 - Add support for winding in fill + clip + isPointInPath + tests the feature. r=bas
content/canvas/src/CanvasRenderingContext2D.cpp
content/canvas/src/CanvasRenderingContext2D.h
content/canvas/test/Makefile.in
content/canvas/test/test_2d.clip.winding.html
content/canvas/test/test_2d.fill.winding.html
content/canvas/test/test_2d.isPointInPath.winding.html
dom/webidl/CanvasRenderingContext2D.webidl
--- a/content/canvas/src/CanvasRenderingContext2D.cpp
+++ b/content/canvas/src/CanvasRenderingContext2D.cpp
@@ -1631,19 +1631,19 @@ CanvasRenderingContext2D::BeginPath()
 {
   mPath = nullptr;
   mPathBuilder = nullptr;
   mDSPathBuilder = nullptr;
   mPathTransformWillUpdate = false;
 }
 
 void
-CanvasRenderingContext2D::Fill()
+CanvasRenderingContext2D::Fill(const CanvasWindingRule& winding)
 {
-  EnsureUserSpacePath();
+  EnsureUserSpacePath(winding);
 
   if (!mPath) {
     return;
   }
 
   mgfx::Rect bounds;
 
   if (NeedToDrawShadow()) {
@@ -1682,19 +1682,19 @@ CanvasRenderingContext2D::Stroke()
   AdjustedTarget(this, bounds.IsEmpty() ? nullptr : &bounds)->
     Stroke(mPath, CanvasGeneralPattern().ForStyle(this, STYLE_STROKE, mTarget),
            strokeOptions, DrawOptions(state.globalAlpha, UsedOperation()));
 
   Redraw();
 }
 
 void
-CanvasRenderingContext2D::Clip()
+CanvasRenderingContext2D::Clip(const CanvasWindingRule& winding)
 {
-  EnsureUserSpacePath();
+  EnsureUserSpacePath(winding);
 
   if (!mPath) {
     return;
   }
 
   mTarget->PushClip(mPath);
   CurrentState().clipsPushed.push_back(mPath);
 }
@@ -1843,19 +1843,21 @@ CanvasRenderingContext2D::EnsureWritable
     mDSPathBuilder =
       mPath->TransformedCopyToBuilder(mPathToDS, fillRule);
     mPathTransformWillUpdate = false;
     mPath = nullptr;
   }
 }
 
 void
-CanvasRenderingContext2D::EnsureUserSpacePath()
+CanvasRenderingContext2D::EnsureUserSpacePath(const CanvasWindingRule& winding)
 {
   FillRule fillRule = CurrentState().fillRule;
+  if(winding == CanvasWindingRuleValues::Evenodd)
+    fillRule = FILL_EVEN_ODD;
 
   if (!mPath && !mPathBuilder && !mDSPathBuilder) {
     EnsureTarget();
     mPathBuilder = mTarget->CreatePathBuilder(fillRule);
   }
 
   if (mPathBuilder) {
     mPath = mPathBuilder->Finish();
@@ -2831,29 +2833,31 @@ CanvasRenderingContext2D::SetMozDashOffs
 {
   ContextState& state = CurrentState();
   if (!state.dash.IsEmpty()) {
     state.dashOffset = mozDashOffset;
   }
 }
 
 bool
-CanvasRenderingContext2D::IsPointInPath(double x, double y)
+CanvasRenderingContext2D::IsPointInPath(double x, double y, const CanvasWindingRule& winding)
 {
   if (!FloatValidate(x,y)) {
     return false;
   }
 
-  EnsureUserSpacePath();
+  EnsureUserSpacePath(winding);
   if (!mPath) {
     return false;
   }
+
   if (mPathTransformWillUpdate) {
     return mPath->ContainsPoint(Point(x, y), mPathToDS);
   }
+
   return mPath->ContainsPoint(Point(x, y), mTarget->GetTransform());
 }
 
 bool
 CanvasRenderingContext2D::IsPointInStroke(double x, double y)
 {
   if (!FloatValidate(x,y)) {
     return false;
--- a/content/canvas/src/CanvasRenderingContext2D.h
+++ b/content/canvas/src/CanvasRenderingContext2D.h
@@ -12,16 +12,17 @@
 #include "nsColor.h"
 #include "mozilla/dom/HTMLCanvasElement.h"
 #include "nsHTMLVideoElement.h"
 #include "CanvasUtils.h"
 #include "gfxFont.h"
 #include "mozilla/ErrorResult.h"
 #include "mozilla/dom/ImageData.h"
 #include "mozilla/dom/UnionTypes.h"
+#include "mozilla/dom/CanvasRenderingContext2DBinding.h"
 
 #define NS_CANVASGRADIENTAZURE_PRIVATE_IID \
     {0x28425a6a, 0x90e0, 0x4d42, {0x9c, 0x75, 0xff, 0x60, 0x09, 0xb3, 0x10, 0xa8}}
 #define NS_CANVASPATTERNAZURE_PRIVATE_IID \
     {0xc9bacc25, 0x28da, 0x421e, {0x9a, 0x4b, 0xbb, 0xd6, 0x93, 0x05, 0x12, 0xbc}}
 
 class nsIDOMXULElement;
 
@@ -233,20 +234,20 @@ public:
     StyleColorToString(CurrentState().shadowColor, shadowColor);
   }
 
   void SetShadowColor(const nsAString& shadowColor);
   void ClearRect(double x, double y, double w, double h);
   void FillRect(double x, double y, double w, double h);
   void StrokeRect(double x, double y, double w, double h);
   void BeginPath();
-  void Fill();
+  void Fill(const CanvasWindingRule& winding);
   void Stroke();
-  void Clip();
-  bool IsPointInPath(double x, double y);
+  void Clip(const CanvasWindingRule& winding);
+  bool IsPointInPath(double x, double y, const CanvasWindingRule& winding);
   bool IsPointInStroke(double x, double y);
   void FillText(const nsAString& text, double x, double y,
                 const mozilla::dom::Optional<double>& maxWidth,
                 mozilla::ErrorResult& error);
   void StrokeText(const nsAString& text, double x, double y,
                   const mozilla::dom::Optional<double>& maxWidth,
                   mozilla::ErrorResult& error);
   already_AddRefed<nsIDOMTextMetrics>
@@ -582,17 +583,17 @@ protected:
   /* This function ensures there is a writable pathbuilder available, this
    * pathbuilder may be working in user space or in device space or
    * device space.
    * After calling this function mPathTransformWillUpdate will be false
    */
   void EnsureWritablePath();
 
   // Ensures a path in UserSpace is available.
-  void EnsureUserSpacePath();
+  void EnsureUserSpacePath(const CanvasWindingRule& winding = CanvasWindingRuleValues::Nonzero);
 
   /**
    * Needs to be called before updating the transform. This makes a call to
    * EnsureTarget() so you don't have to.
    */
   void TransformWillUpdate();
 
   // Report the fillRule has changed.
--- a/content/canvas/test/Makefile.in
+++ b/content/canvas/test/Makefile.in
@@ -31,16 +31,17 @@ MOCHITEST_FILES = \
 	image_red.png \
 	image_transparent.png \
 	image_green.png \
 	image_green-redirect \
 	image_green-redirect^headers^ \
 	test_drawImageIncomplete.html \
 	test_canvas_font_setter.html \
 	test_2d.clearRect.image.offscreen.html \
+	test_2d.clip.winding.html \
 	test_2d.composite.canvas.destination-atop.html \
 	test_2d.composite.canvas.destination-in.html \
 	test_2d.composite.canvas.source-in.html \
 	test_2d.composite.canvas.source-out.html \
 	test_2d.composite.canvas.multiply.html \
 	test_2d.composite.canvas.screen.html \
 	test_2d.composite.canvas.overlay.html \
 	test_2d.composite.canvas.darken.html \
@@ -88,16 +89,18 @@ MOCHITEST_FILES = \
 	test_2d.composite.uncovered.fill.soft-light.html \
 	test_2d.composite.uncovered.fill.difference.html \
 	test_2d.composite.uncovered.fill.exclusion.html \
 	test_2d.composite.uncovered.fill.hue.html \
 	test_2d.composite.uncovered.fill.saturation.html \
 	test_2d.composite.uncovered.fill.color.html \
 	test_2d.composite.uncovered.fill.luminosity.html \
 	test_2d.drawImage.zerocanvas.html \
+	test_2d.fill.winding.html \
+	test_2d.isPointInPath.winding.html \
 	test_2d.strokeRect.zero.5.html \
 	test_toBlob.html \
 	test_toDataURL_alpha.html \
 	test_toDataURL_lowercase_ascii.html \
 	test_toDataURL_parameters.html \
 	test_mozGetAsFile.html \
 	test_canvas_strokeStyle_getter.html \
 	test_bug613794.html \
new file mode 100644
--- /dev/null
+++ b/content/canvas/test/test_2d.clip.winding.html
@@ -0,0 +1,53 @@
+<!DOCTYPE HTML>
+<title>Canvas test: 2d.clip.winding</title>
+<script src="/MochiKit/MochiKit.js"></script>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css">
+<body>
+<canvas id="c" width="100" height="100"><p class="fallback">FAIL (fallback content)</p></canvas>
+<script>
+function isPixel(ctx, x,y, r,g,b,a, pos, colour, d) {
+    var pixel = ctx.getImageData(x, y, 1, 1);
+    var pr = pixel.data[0],
+        pg = pixel.data[1],
+        pb = pixel.data[2],
+        pa = pixel.data[3];
+    ok(r-d <= pr && pr <= r+d &&
+       g-d <= pg && pg <= g+d &&
+       b-d <= pb && pb <= b+d &&
+       a-d <= pa && pa <= a+d,
+       "pixel "+pos+" is "+pr+","+pg+","+pb+","+pa+"; expected "+colour+" +/- "+d);
+}
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(function () {
+
+var canvas = document.getElementById('c');
+var ctx = canvas.getContext('2d');
+
+ctx.fillStyle = 'rgb(255,0,0)';
+ctx.fillRect(0, 0, 100, 100);
+ctx.fillStyle = 'rgb(0,255,0)';
+ctx.beginPath();
+ctx.rect(0, 0, 100, 100);
+ctx.rect(25, 25, 50, 50);
+ctx.clip();
+ctx.beginPath();
+ctx.fillRect(0, 0, 100, 100);
+isPixel(ctx, 50,50, 0,255,0,255, "50,50", "0,255,0,255", 5);
+
+ctx.fillStyle = 'rgb(255,0,0)';
+ctx.fillRect(0, 0, 100, 100);
+ctx.fillStyle = 'rgb(0,255,0)';
+ctx.beginPath();
+ctx.rect(0, 0, 100, 100);
+ctx.rect(25, 25, 50, 50);
+ctx.clip('evenodd');
+ctx.fillRect(0, 0, 100, 100);
+isPixel(ctx, 50,50, 255,0,0,255, "50,50", "255,0,0,255", 5);
+
+SimpleTest.finish();
+
+});
+</script>
+
new file mode 100644
--- /dev/null
+++ b/content/canvas/test/test_2d.fill.winding.html
@@ -0,0 +1,51 @@
+<!DOCTYPE HTML>
+<title>Canvas test: 2d.fill.winding</title>
+<script src="/MochiKit/MochiKit.js"></script>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css">
+<body>
+<canvas id="c" width="100" height="100"><p class="fallback">FAIL (fallback content)</p></canvas>
+<script>
+function isPixel(ctx, x,y, r,g,b,a, pos, colour, d) {
+    var pixel = ctx.getImageData(x, y, 1, 1);
+    var pr = pixel.data[0],
+        pg = pixel.data[1],
+        pb = pixel.data[2],
+        pa = pixel.data[3];
+    ok(r-d <= pr && pr <= r+d &&
+       g-d <= pg && pg <= g+d &&
+       b-d <= pb && pb <= b+d &&
+       a-d <= pa && pa <= a+d,
+       "pixel "+pos+" is "+pr+","+pg+","+pb+","+pa+"; expected "+colour+" +/- "+d);
+}
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(function () {
+
+var canvas = document.getElementById('c');
+var ctx = canvas.getContext('2d');
+
+ctx.fillStyle = 'rgb(255,0,0)';
+ctx.beginPath();
+ctx.fillRect(0, 0, 100, 100);
+ctx.fillStyle = 'rgb(0,255,0)';
+ctx.beginPath();
+ctx.rect(0, 0, 100, 100);
+ctx.rect(25, 25, 50, 50);
+ctx.fill();
+isPixel(ctx, 50,50, 0,255,0,255, "50,50", "0,255,0,255", 5);
+
+ctx.fillStyle = 'rgb(255,0,0)';
+ctx.fillRect(0, 0, 100, 100);
+ctx.fillStyle = 'rgb(0,255,0)';
+ctx.beginPath();
+ctx.rect(0, 0, 100, 100);
+ctx.rect(25, 25, 50, 50);
+ctx.fill('evenodd');
+isPixel(ctx, 50,50, 255,0,0,255, "50,50", "255,0,0,255", 5);
+
+SimpleTest.finish();
+
+});
+</script>
+
new file mode 100644
--- /dev/null
+++ b/content/canvas/test/test_2d.isPointInPath.winding.html
@@ -0,0 +1,29 @@
+<!DOCTYPE HTML>
+<title>Canvas test: 2d.isPointInPath.winding</title>
+<script src="/MochiKit/MochiKit.js"></script>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css">
+<body>
+<canvas id="c" width="100" height="100"><p class="fallback">FAIL (fallback content)</p></canvas>
+<script>
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(function () {
+
+var canvas = document.getElementById('c');
+var ctx = canvas.getContext('2d');
+
+ctx.beginPath();
+ctx.rect(0, 0, 100, 100);
+ctx.rect(25, 25, 50, 50);
+ok(ctx.isPointInPath(50, 50));
+
+ctx.beginPath();
+ctx.rect(0, 0, 100, 100);
+ctx.rect(25, 25, 50, 50);
+ok(ctx.isPointInPath(50, 50, 'evenodd') == false);
+
+SimpleTest.finish();
+
+});
+</script>
+
--- a/dom/webidl/CanvasRenderingContext2D.webidl
+++ b/dom/webidl/CanvasRenderingContext2D.webidl
@@ -15,16 +15,18 @@ interface CanvasGradient;
 interface CanvasPattern;
 interface HitRegionOptions;
 interface HTMLCanvasElement;
 interface HTMLVideoElement;
 interface TextMetrics;
 interface Window;
 interface XULElement;
 
+enum CanvasWindingRule { "nonzero", "evenodd" };
+
 interface CanvasRenderingContext2D {
 
   // back-reference to the canvas.  Might be null if we're not
   // associated with a canvas.
   readonly attribute HTMLCanvasElement? canvas;
 
   // state
   void save(); // push state on state stack
@@ -75,30 +77,30 @@ interface CanvasRenderingContext2D {
   void clearRect(double x, double y, double w, double h);
   [LenientFloat]
   void fillRect(double x, double y, double w, double h);
   [LenientFloat]
   void strokeRect(double x, double y, double w, double h);
 
   // path API (see also CanvasPathMethods)
   void beginPath();
-  void fill();
+  void fill(optional CanvasWindingRule winding = "nonzero");
 // NOT IMPLEMENTED  void fill(Path path);
   void stroke();
 // NOT IMPLEMENTED  void stroke(Path path);
 // NOT IMPLEMENTED  void drawSystemFocusRing(Element element);
 // NOT IMPLEMENTED  void drawSystemFocusRing(Path path, Element element);
 // NOT IMPLEMENTED  boolean drawCustomFocusRing(Element element);
 // NOT IMPLEMENTED  boolean drawCustomFocusRing(Path path, Element element);
 // NOT IMPLEMENTED  void scrollPathIntoView();
 // NOT IMPLEMENTED  void scrollPathIntoView(Path path);
-  void clip();
+  void clip(optional CanvasWindingRule winding = "nonzero");
 // NOT IMPLEMENTED  void clip(Path path);
 // NOT IMPLEMENTED  void resetClip();
-  boolean isPointInPath(unrestricted double x, unrestricted double y);
+  boolean isPointInPath(unrestricted double x, unrestricted double y, optional CanvasWindingRule winding = "nonzero");
 // NOT IMPLEMENTED  boolean isPointInPath(Path path, unrestricted double x, unrestricted double y);
   boolean isPointInStroke(double x, double y);
 
   // text (see also the CanvasDrawingStyles interface)
   [Throws, LenientFloat]
   void fillText(DOMString text, double x, double y, optional double maxWidth);
   [Throws, LenientFloat]
   void strokeText(DOMString text, double x, double y, optional double maxWidth);