globalCompositeOperation - javascript

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);

Related

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-.

Multiple image patterns in 2d context of HTML5 canvas?

I am having difficulties rendering several patterns (each with different texture) in the 2d context of HTML5 canvas.
Assuming I have three separate canvases, two off-screen containing different textures and one for rendering. Let these offline canvases be A and B.
Then:
var patternA = ctx.createPattern(A, "repeat-x");
ctx.fillStyle = patternA;
ctx.fillRect(100,100,20,20);
var patternB = ctx.createPattern(B, "repeat-y");
ctx.fillStyle = patternB;
ctx.fillRect(150,100,20,20);
There should be two 20x20 rectangles, each with their own pattern, however the second rectangle doesn't render at all. I've tried everything to get them working, but to no avail.
Why is that? How should I render multiple tiling textures onto the same canvas?
What browsers are you trying? With FireFox and Chrome, I couldn't get either pattern to render with repeat-x or repeat-y. Instead, I was able to get both to render with just repeat. (See http://jsfiddle.net/ZthsS/1/)
It is possible that browsers have an incomplete implementation of the specification. According to the implementation status at http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#dom-context-2d-createpattern, IE beta and FF nightly pass all test cases but other browsers don't. I would recommend just using repeat for the time being. You could emulate repeat-x and repeat-y by simply limiting the width of the fillRect to the width of the pattern:
var patternA = ctx.createPattern(A, "repeat");
ctx.fillStyle = patternA;
ctx.fillRect(100,100,20,Math.min(20, A.height));
var patternB = ctx.createPattern(B, "repeat");
ctx.fillStyle = patternB;
ctx.fillRect(150,100,Math.min(20, B.width), 20);

Invalid blending results across all browsers with HTML5 canvas

Summary
When repeatedly drawing anything (apparently with a low alpha value) on a canvas, regardless of if it's with drawImage() or a fill function, the resulting colors are significantly inaccurate in all browsers that I've tested. Here's a sample of the results I'm getting with a particular blend operation:
Problem Demonstration
For an example and some code to play with, check out this jsFiddle I worked up:
http://jsfiddle.net/jMjFh/2/
The top set of data is a result of a test that you can customize by editing iters and rgba at the top of the JS code. It takes whatever color your specify, RGBA all in range [0, 255], and stamps it on a completely clean, transparent canvas iters number of times. At the same time, it keeps a running calculation of what the standard Porter-Duff source-over-dest blend function would produce for the same procedure (ie. what the browsers should be running and coming up with).
The bottom set of data presents a boundary case that I found where blending [127, 0, 0, 2] on top of [127, 0, 0, 63] produces an inaccurate result. Interestingly, blending [127, 0, 0, 2] on top of [127, 0, 0, 62] and all [127, 0, 0, x] colors where x <= 62 produces the expected result. Note that this boundary case is only valid in Firefox and IE on Windows. In every other browser on every other operating system I've tested, the results are far worse.
In addition, if you run the test in Firefox on Windows and Chrome on Windows, you'll notice a significant difference in the blending results. Unfortunately we're not talking about one or two values off--it's far worse.
Background Information
I am aware of the fact that the HTML5 canvas spec points out that doing a drawImage() or putImageData() and then a getImageData() call can show slightly varying results due to rounding errors. In that case, and based on everything I've run into thus far, we're talking about something miniscule like the red channel being 122 instead of 121.
As a bit of background material, I asked this question a while ago where #NathanOstgard did some excellent research and drew the conclusion that alpha premultiplication was to blame. While that certainly could be at work here, I think the real underlying issue is something a bit larger.
The Question
Does anyone have any clue how I could possibly end up with the (wildly inaccurate) color values I'm seeing? Furthermore, and more importantly, does anyone have any ideas how to circumvent the issue and produce consistent results in all browsers? Manually blending the pixels is not an option due to performance reasons.
Thanks!
Edit: I just added a stack trace of sorts that outputs each intermediate color value and the expected value for that stage in the process.
Edit: After researching this a bit, I think I've made some slight progress.
Reasoning for Chrome14 on Win7
In Chrome14 on Win7 the resulting color after the blend operation is gray, which is many values off from the expected and desired reddish result. This led me to believe that Chrome doesn't have an issue with rounding and precision because the difference is so large. Instead, it seems like Chrome stores all pixel values with premultiplied alpha.
This idea can be demonstrated by drawing a single color to the canvas. In Chrome, if you apply the color [127, 0, 0, 2] once to the canvas, you get [127, 0, 0, 2] when you read it back. However, if you apply [127, 0, 0, 2] to the canvas twice, Chrome gives you [85, 0, 0, 3] if you check the resulting color. This actually makes some sense when you consider that the premultiplied equivalent of [127, 0, 0, 2] is [1, 0, 0, 2].
Apparently, when Chrome14 on Win7 performs the blending operation, it references the premultiplied color value [1, 0, 0, 2] for the dest component and the non-premultiplied color value [127, 0, 0, 2] for the source component. When these two colors are blended together, we end up with [85, 0, 0, 3] using the Porter-Duff source-over-dest approach, as expected.
So, it seems like Chrome14 on Win7 inconsistently references the pixel values when you work with them. The information is stored in a premultiplied state internally, is presented back to you in non-premultiplied form, and is manipulated using values of both forms.
I'm thinking it might be possible to circumvent this by doing some additional calls to getImageData() and/or putImageData(), but the performance implications don't seem that great. Furthermore, this doesn't seem to be the same issue that Firefox7 on Win7 is exhibiting, so some more research will have to be done on that side of things.
Edit: Below is one possible approach that seems likely to work.
Thoughts on a Solution
I haven't gotten back around to working on this yet, but one WebGL approach that I came up with recently was to run all drawing operations through a simple pixel shader that performs the Porter-Duff source-over-dest blend itself. It seems as though the issue here only occurs when interpolating values for blending purposes. So, if the shader reads the two pixel values, calculates the result, and writes (not blends) the value into the destination, it should mitigate the problem. At the very least, it shouldn't compound. There will definitely still be minor rounding inaccuracies, though.
I know in my initial question I mentioned that manually blending the pixels wouldn't be viable. For a 2D canvas implementation of an application that needs real-time feedback, I don't see a way to fix this. All of the blending would be done on the CPU and would prevent other JS code from executing. With WebGL, however, your pixel shader runs on the GPU so I'm pretty sure there shouldn't be any performance hit.
The tricky part is being able to feed the dest canvas in as a texture to the shader because you also need to render it on a canvas to be viewable. Ultimately, you want to avoid having to generate a WebGL texture object from your viewable canvas every time it needs to be updated. Maintaining two copies of the data, with one as an in-memory texture and one as a viewable canvas (that's updated by stamping the texture on as needed), should solve this.
Oh geez. I think we just stepped into the twilight zone here. I'm afraid I don't have an answer but I can provide some more information at least. Your hunch is right that at least something is larger here.
I'd prefer an example that is drastically more simple (and thus less prone to any sort of error - I fear you might have a problem with 0-255 somewhere instead of 0-1 but didnt look thoroughly) than yours so I whipped up something tiny. What I found wasn't pleasant:
<canvas id="canvas1" width="128" height="128"></canvas>
<canvas id="canvas2" width="127" height="127"></canvas>
+
var can = document.getElementById('canvas1');
var ctx = can.getContext('2d');
var can2 = document.getElementById('canvas2');
var ctx2 = can2.getContext('2d');
var alpha = 2/255.0;
ctx.fillStyle = 'rgba(127, 0, 0, ' + alpha + ')';
ctx2.fillStyle = 'rgba(127, 0, 0, ' + alpha + ')'; // this is grey
//ctx2.fillStyle = 'rgba(171, 0, 0, ' + alpha + ')'; // this works
for (var i = 0; i < 32; i++) {
ctx.fillRect(0,0,500,500);
ctx2.fillRect(0,0,500,500);
}
Yields this:
See for yourself here:
http://jsfiddle.net/jjAMX/
It seems that if your surface area is too small (not the fillRect area, but the canvas area itself) then the drawing operation will be a wash.
It seems to work fine in IE9 and FF7. I submitted it as a bug to Chromium. (We'll see what they say, but their typical M.O. so far is to ignore most bug reports unless they are security issues).
I'd urge you to check your example - I think you might have a 0-255 to 0-1 issue somewhere. The code I have here seems to work just dandy, save for maybe the expected reddish color, but it is a consistent resultant color across all browsers with the exception of this Chrome issue.
In the end Chrome is probably doing something to optimize canvases with a surface area smaller than 128x128 that is fouling things up.
Setting globalCompositeOperation to "lighter" (the operation where overlapping pixels values are added together, it is not the default operation for many browsers) yields same color and results in ie9, chrome 14 and latest firefox aurora:
Expected: [126.99999999999999, 0, 0, 63.51372549019608]
Result: [127, 0, 0, 64]
Expected: [126.99999999999999, 0, 0, 63.51372549019608]
Result: [127, 0, 0, 64]
http://jsfiddle.net/jMjFh/11/
edit: you may learn what the different composite operations mean here:
https://developer.mozilla.org/en/Canvas_tutorial/Compositing#globalCompositeOperation
for example, "copy" was useful to me when I needed to do fade animation in certain parts of a canvas

Using the SVG evenodd fill-rule in the canvas element

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>

Categories