14

I'm working on a doodling app in HTML5 and I would like to do a sort of bucket feature. The idea is to draw a path and it will be closed and filled with the selected colour (colour of the stroke). It works quite well with solid colours, but if I want to have a transparent stroke and fill, I run into this problem:

What happens is the fill is done until the middle of the stroke (the actual sampling point of the path) so there's a line of half the size of the stroke inside the shape which is darker because it's the intersection of the fill and the stroke.

You should be able to see what I'm talking about live in this sandbox.

Mathieu
  • 622
  • 1
  • 6
  • 12

3 Answers3

14

Sure, use ctx.globalCompositeOperation = 'destination-atop'; and it ought to look the way you're expecting.

Like so: http://jsfiddle.net/UcyX4/

(Take out that line in order to see the problem you're having)

Assuming its not the only thing drawn on canvas, you're probably going to have to draw this to a temporary canvas and then draw that canvas onto your normal one, otherwise it may ruin all the previously drawn shapes. So you'd need a system like so: http://jsfiddle.net/dATfj/

edit: code pasted in case of jsfiddle failure:

HTML:

<canvas id="canvas1" width="500" height="500"></canvas>

Script:

var can = document.getElementById('canvas1');
var ctx = can.getContext('2d');

var can2 = document.createElement('canvas');
can2.width = can.width;
can2.height = can.height;
ctx2 = can2.getContext('2d');

ctx.strokeStyle = 'rgba(0,0,0,0.7)';
ctx.fillStyle = 'rgba(0,0,0,0.7)';
ctx.lineWidth = 10;


// Stuff drawn normally before
// Here I draw one rect in the old way just to show the old way
// and show something on the canvas before:
ctx.beginPath();
ctx.rect(50,50,100,100);
ctx.fill();
ctx.stroke();


// Draw on can2 then draw can2 to can
ctx2.strokeStyle = 'rgba(0,0,0,0.7)';
ctx2.fillStyle = 'rgba(0,0,0,0.7)';
ctx2.lineWidth = 10;
ctx2.beginPath();
ctx2.rect(50,250,100,100);
ctx2.globalCompositeOperation = 'destination-atop';
ctx2.fill();
ctx2.stroke();

ctx.drawImage(can2, 0, 0);
funie200
  • 3,688
  • 5
  • 21
  • 34
Simon Sarris
  • 62,212
  • 13
  • 141
  • 171
  • Unfortunately, JSFiddle gives me a 404 but I've tried changing the Composite mode and it seems to work. But you are also right, I need to draw my "intermediate" canvas I'm using to draw shapes onto the final one with the final shapes. I will try to find how to do this whilst JSFiddle is recovering :) Thanks again! – Mathieu Feb 10 '12 at 17:24
  • Ah I added all the code from the fiddle just in case. Best of luck with your project! – Simon Sarris Feb 10 '12 at 17:33
  • I helped myself from this one in the meantime: http://stackoverflow.com/questions/4405336/how-to-copy-contents-of-one-canvas-to-another-canvas-locally . It might be good to mention to JQuery users that to get the type needed for can2 in your example, you have to do $("#myCanvas").get(0).getContext('2d').canvas . It works like a charm now. Thank you very much one more time! – Mathieu Feb 10 '12 at 18:00
  • This doesn't work anymore (already didn't work on Firefox). I'm currently looking at an alternative. More info here: https://bugzilla.mozilla.org/show_bug.cgi?id=898375 – Mathieu May 26 '14 at 11:06
6

2018 answer : use context.globalAlpha

ex : context.globalAlpha = 0.2;

4b0
  • 21,981
  • 30
  • 95
  • 142
PhilMaGeo
  • 551
  • 6
  • 8
5

Simon's answer was correct at the time but it now seems that Chrome 36 has corrected a bug which affects his solution and it no longer works. It already didn't work on Firefox and it seems to be the expected behaviour: https://bugzilla.mozilla.org/show_bug.cgi?id=898375

So, how do you get this done?
You first need another canvas.
Draw your filled and stroked shape on this canvas without opacity (not in the colour and no globalAlpha).
Now, draw set the globalAlpha to whatever you want on your main canvas.
Draw the first canvas on your main one.
Set the globalAlpha to whatever you had on your main canvas.
Done.

Mathieu
  • 622
  • 1
  • 6
  • 12