Using the SVG evenodd fill-rule in the canvas element - javascript

Does anyone know if it's possible, either natively or mathematically with JavaScript, to implement the evenodd fill-rule from SVG in the HTML5 canvas element? Links to projects that do this would be helpful.

Yes, it should be possible with globalCompositeOperation. If I'm not mistaken, default "source-over" value should correspond to SVG's "evenodd" (otherwise, try few others one and see if resulting image looks identical).

By mathematically do you mean an algorithm implementation ? It is certainly possible, see http://gpolo.awardspace.info/fill/main.html. It is more a demo than anything, but it solves this issue "mathematically".

I asked myself the same question and came across this Mozilla Bugreport.
Seems as it gets proposed to the WHATWG (canvas specs) by the bugreporters:
Chris Jones, 2011-06-10:
Let's wait for docs until this spec is looking solid on whatwg (I'll post today).

That's a really old question, so things must have been different at the time, but since a long time ago, you can pass an fillrule parameter to the fill() method.
This fillrule can be either "nonzero", the default, or "evenodd".
var ctx = c.getContext('2d');
drawPath();
ctx.fill();
ctx.translate(70, 0);
drawPath();
ctx.fill('evenodd');
ctx.translate(70, 0);
drawPath();
ctx.stroke();
function drawPath(){
ctx.beginPath();
ctx.arc(30,30,20,0,Math.PI*2);
ctx.lineTo(60,60);
ctx.lineTo(0,0);
ctx.closePath();
}
<canvas id="c"></canvas>

Related

Strange HTML5 Canvas closePath rendering artifact

I'm using HTML5 Canvas elements in a project I'm working on and while rendering a bunch of stuff I ran into a really strange artifact that I'm wondering if anyone has ever seen before. Basically, in this particular scenario (this is the only case I've seen that produces this behavior), I happened to be panning/zooming in my application and noticed a very bizarre rendering effect on part of the canvas. Upon further inspection, I was able to reproduce the effect in a very simplistic example:
In this case, I have a path (whose coordinates don't change) and all that is changing from the first screenshot to the second is the applied transformation matrix (by a very small amount).
You can access the JSFiddle I used to generate these screenshots at https://jsfiddle.net/ynsv66g8/ and here is the relevant rendering code:
// Clear context
context.setTransform(1, 0, 0, 1, 0, 0);
context.fillStyle = "#000000";
context.fillRect(0, 0, 500, 500);
if (showArtifact) { // This transformation causes the artifact
context.transform(0.42494658722537976, 0, 0, -0.42494658722537976, 243.95776291868646, 373.14630356628857);
} else { // This transformation does not
context.transform(0.4175650109545749, 0, 0, -0.4175650109545749, 243.70987992043368, 371.06797574381795);
}
// Draw path
context.lineWidth = 3.488963446543301;
context.strokeStyle = 'red';
context.beginPath();
var p = path[0];
context.moveTo(p[0], p[1]);
for (var i = 1; i < path.length; ++i) {
p = path[i];
context.lineTo(p[0], p[1]);
}
context.closePath();
context.stroke();
It looks to be related to the call to canvas.closePath() because if you remove the call to context.closePath() and replace it with:
p = path[0];
context.lineTo(p[0], p[1]);
(thus manually "closing" the path) everything works properly (granted, this really doesn't fix my particular issue because I rely on multiple closed paths to apply filling rules).
Another interesting change that can be made that makes the issue go away is to reverse the path array (i.e., by adding a call to path.reverse() right after its definition).
Together, all of this seems to be adding up to some kind of browser rendering bug related to the characteristics of the path, especially since, on my Mac, the issue occurs in both Chrome (v61.0.3163.91) and Firefox (v55.0.3) but not Safari (v11). I've done some extensive searching to try to find this issue (or something similar), but thus far have come up empty.
Any insight into what might be causing this (or the proper way to go about reporting this issue if the consensus is that it is caused by some browser bug) would be greatly appreciated.
it seems a line mitering problem; that is, the renderer fails to correctly join lines when the width is too big relative to the segment size/orientation.
This seems not influenced by path closing ( I can reproduce the artifact with the open path ); note that manually closing the path is not the same as a closePath(), because no line join is performed in the former case.
As far as I can tell, it seems solved by setting lineJoin to 'round' or reducing the line width ... anyway, seems a renderer bug to me ... just my two cents :)

html canvas returning line not connecting

I was trying out a simple script to create a rectangle and it seemed to work find except that the final leg of the polygon doesn't connect with the first leg properly. The strange thing is that all the other legs do connect properly.
Before anyone suggests that I use the built-in drawRect() or similar function, I should emphasize that this is just a simple example of a far more complex figure I'm trying to draw. I'm just confused why this is happening.
newX = 10;
letterHeight = 100;
ctx.strokeStyle = "#999999";
ctx.lineWidth = 6;
ctx.moveTo(newX,letterHeight*0.5);
ctx.lineTo(newX+letterWidth,letterHeight*0.5);
ctx.lineTo(newX+letterWidth,letterHeight);
ctx.lineTo(newX, letterHeight);
ctx.lineTo(newX, letterHeight*0.5);
ctx.stroke();
It seems to me that this probably has something to do with the line width and I could consider some problem-specific crude fix for that but is there a more general fix if that's the case?
Link to how the script rendered (using firefox)
Use ctx.closePath(); just before the stroke command to close the path. You should also be starting the path with ctx.beginPath();

Any insights on how to make a canvas drawing not go on top on the other? [duplicate]

This question already has answers here:
How to clear the canvas for redrawing
(25 answers)
Closed 8 years ago.
It's a preety straight up and basic question. I think that because that must be a often action there must be made function, but I can't find it? Is there one? If there isn't does anybody know how to do it?
three ways (at least) :
you can clip your canvas : only the non clipped part will be drawn.
to clip you build a path with beginPath, moveTo, lineTo, arcTo, ... arc,
(any path building function) then you call clip() : the part inside the path
is the clipped-in part.
do not forget to save the context before and to restore it after (unless you want a permanent clip).
snippet :
http://jsbin.com/oSoSugAZ/1/
var context=document.getElementById('cv').getContext('2d');
context.fillStyle='#F66';
context.fillRect(150,150, 500,500); // draw big rect in red
context.save();
context.lineWidth=0; // to have precise clip
context.beginPath();
context.moveTo(100,100);
context.lineTo(200,100);
context.lineTo(200,200);
context.lineTo(100,200);
context.closePath();
context.clip();
// here we can only draw within the rect (100,100) (200,200)
context.fillStyle='#FCE'; // pink
context.fillRect(150,150, 500,500); // will get clipped
context.beginPath();
context.fillStyle = '#AAF';
context.arc(150,150, 30, 0,6.2);
context.fill(); // won't get clipped
// do not forget to restore !
context.restore();
you might use globalCompositeOperation to choose a way the pixel you draw interact with existing ones It works for both drawing paths or images. There are too many options to discuss here, it all depend on your needs.
a full example is here :
https://developer.mozilla.org/samples/canvas-tutorial/6_1_canvas_composite.html
Note that darker does not work (but it's not usefull anyway, just use normal mode = source-over with low opacity (0.2) and fillRect in black).
another option is to use temporary canvas to make your draws.
It's very easy, especially if you make small helper functions.
function createCanvas(w,h){
var cv = document.createElement('canvas');
cv.width; cv.height = height;
return cv;
}
( in any case you're interested, you can have a look at my small lib to ease working with a canvas :
https://github.com/gamealchemist/CanvasLib/blob/master/canvasLib.js )
You can't. But you can simulate layers with multiple canvas elements with different z-index.
EDIT:
Check this: canvas layers

Using even-odd fill on HTML canvas

Are there any open source libraries (JaveScript) that implement even-odd fill rule on canvas.
If I try to implement it myself then how complex would it be (considering general case which has complex curves also) and would it hit on the performance (due to overhead of doing it for each pixel in JaveScript).
What are the methods for converting even-odd fill to non-zero winding (considering a generic solution that will work for every case). Once method I found was to divide the shape into all non-intersecting polygons and fill them separately.
One option is to use SVG and draw the SVG on canvas, but I also found that native SVG rendering is a bit slow on iPad, but is SVG slow even when I draw it on HTML canvas (on iPad)?
Thanks in advance
I came across this question as I was looking for the same some time ago. That is, use a "evenodd" fill-rule inside a HTML canvas.
After a little research, and going through mailing lists and patches, I noticed that, as it turns out, recent versions of both Firefox and Chrome have support for this, but each in a different, browser-specific, way.
In Firefox it is a matter of using the mozFillRule attribute. Example:
//
// Firefox Example
//
// canv has the HTML canvas element
var ctx = canv.getContext("2d");
ctx.fillStyle = "#ff0000";
ctx.mozFillRule = 'evenodd';
ctx.beginPath();
ctx.moveTo(100, 10);
ctx.lineTo(40, 180);
ctx.lineTo(190, 60);
ctx.lineTo(10,60);
ctx.lineTo(160,180);
ctx.closePath();
ctx.fill();
In Chrome, you do it by passing the string evenodd as a parameter to the fill method. Example:
//
// Chrome Example
//
// canv has the HTML canvas element
var ctx = canv.getContext("2d");
ctx.fillStyle = "#ff0000";
ctx.beginPath();
ctx.moveTo(100, 10);
ctx.lineTo(40, 180);
ctx.lineTo(190, 60);
ctx.lineTo(10,60);
ctx.lineTo(160,180);
ctx.closePath();
ctx.fill('evenodd');
These are the two browsers I researched for, so I don't know about the state of this in other browers. Hopefully in the not-so-distant future we will be able to use this feature via the fillRule attibute that is now part of the HTML standard.
See fill() method API:
void ctx.fill();
void ctx.fill(fillRule);
void ctx.fill(path, fillRule);
fillRule can be "nonzero" or "evenodd"
--- "nonzero": The non-zero winding rule, which is the default rule.
--- "evenodd": The even-odd winding rule.
Browser compatibility:
--- IE 11+, Firefox 31+, Chrome are OK.
--- I didn't test on IE 9/10.
--- Use ctx.mozFillRule = 'evenodd'; with old Firefox 30/30-.

globalCompositeOperation

I have tried to use globalCompositeOperation in a loop passing it different
string (source-atop, source-over etc.) in the same 2D context but I noticed
that Firefox let draw me only few shapes while Opera only the last.
Now, my question is can I use only ONE globalCompositeOperation at time into
the current context?
The reason you're noticing this issue is the modes you're choosing aren't supported by the browser properly. There are some issues between browsers concerning the globalCompositeOperation. At this moment, there are only a few modes that work between browsers (Chrome/Safari/Opera/Firefox) without quirks:
source-over
source-atop
destination-over
destination-out
lighter
xor
To learn more check out the following link;
http://www.rekim.com/2011/02/11/html5-canvas-globalcompositeoperation-browser-handling/
As for your 2nd question, you can only use one mode at a time. This is unfortunate, because "light" and "darker" are more-like "blend-modes", and would be very useful to use with some of the other composite modes. I would love to see this change.
In short, yes.
The last globalCompositeOperation value takes place before a render, e.g. drawImage(),fillRect().
You can change it immediately after drawing to apply it to the next drawing like:
http://jsfiddle.net/eCDRN/
ctx.globalCompositeOperation = "copy";
ctx.fillRect(100, 100, 100, 100);
ctx.globalCompositeOperation = "destination-in";
ctx.fillRect(150, 150, 100, 100);
ctx.globalCompositeOperation = "xor";
ctx.fillRect(175, 175, 100, 100);

Categories