I've been working on a multi-person drawing application in HTML and JS, but I've run into a strange issue.
To draw, a brush image (loaded from .png) is drawn to the canvas (using drawImage
) as many times as needed to make a continuous line where the mouse has moved. This all worked fine until I added opacity support. Now I can see that when I turn the opacity right down, even if I scribble back and forth for a long time the image on the canvas never reaches the full colour I'm trying to paint. This results in an odd situation when I can paint a solid, full-opacity black line, then set the opacity really low and attempt to progressively colour over the black line with a transparent red, but the black never ever fades completely to red.
I don't understand what's going on here. I've made a fiddle that demonstrates the issue. In the fiddle, no matter how many times the rectangle is drawn over the top of the previous one it never reaches solid black.
Html:
<canvas id="myCanvas" width="300" height="150"></canvas>
Javascript:
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
for(var i=0;i<10000;i++) {
ctx.save();
ctx.globalAlpha=0.01;
ctx.fillStyle='black';
ctx.fillRect(40, 40, 130, 80);
ctx.restore();
}
This makes no sense to me. Does anyone know why this would happen, and how I can fix it?
When you draw using a colour with opacity A%, the result will be A% of the way between the colour that's already there, and the (opaque) colour you're drawing with.
In this case, you're drawing with 1% opaque red on solid black. The result will be 1% of the way from black to red. Call this "reddish-black".
Do it again, and the result will be 1% of the way between "reddish-black" and red, which is 1.99% red. Next will be ~2.97% red, then ~3.94% red, and so on. Note that you're not adding 1% each time, but advancing 1% of the way to the end from where the previous stroke left you.
Mathematically speaking, you'll keep getting infinitely closer to full-red, but never actually reach it.
But there's an additional factor for graphics: there are only 256 discrete values for how much red you can have: 0 for none, up to 255 for full. At the start your 1% will add 2-3 "units" of red (depending on rounding). This will continue for a while, and gradually fall to just adding 1 "unit" at a time. Finally, you reach 207. At this point there are 48 "units" left to full red, and 1% of that is 0.48 "units". That's not enough, and due to rounding you'll be left with 207 again. This is why you stop seeing any effect after a certain number of strokes.
For a more obvious example, say you're using 50% opaque red to draw. The first stroke will get you 1/2 way there. The second gives another half of that, so 1/2 + 1/4 = 3/4. Then 7/8, then 15/16... Again you'll never actually reach it. Although interestingly in this case the 255-unit thing helps you out, because you'll always be adding at least 0.5 "units" which will round up to +1 until you reach full red.