Re: [whatwg] Canvas stroke alignment

<4C4ADBFE.60802@home.nl>

Current votes: None.

This is a multi-part message in MIME format.
--------------020606060906030405080505
Content-Type: text/plain; charset=ISO-8859-1; format=flowed
Content-Transfer-Encoding: 7bit

Op 24-7-2010 2:02, David Flanagan schreef:
> Nick wrote:
>> Nice, less math.
>>
>> I think the outside alignment approach will only work on paths that 
>> have a 100% opacity fill.
>
> You'r right.
>
>> The off-screen rectangle approach could work with opacity but it has 
>> the same problem with transparent pixels between the stroke and the 
>> fill as you'd get with a custom path once it curves.
>
> I don't know what you mean.  I don't see any transparent pixels in my 
> testing.

That's because your shape has a 1px black stroke applied before you 
apply the outside stroke, without a duplicate stroke the problem will show.

I've attached your tests with some modifications to show the problem.

Adding the extra 1px stroke with the same fill as the background might 
be a workaround, but only on firefox, opera ignores it. Chrome gives 
only solid pixels on the edge of the custom strokes, probably a 
different problem for them. Maybe none of them got it right.


>
>> It would be nice if Canvas took care of stroke alignment so we can 
>> get rid of the hacks and limitations those bring along.
>
> That would be nice, but I won't hold my breath.  If they're going to 
> mess with strokes, I'd like to have dashed lines, too...

Makes sense to have that, SVG has it, would be nice if Canvas followed. 
SVG could also use stroke-alignment, ah well, SVG will probably beat 
Canvas to it.

--
Nick



>
> The attached file includes my test versions of strokeInside() and 
> strokeOutside().  strokeInside() was easy to implement but has 
> problems handling multiple subpaths.  strokeOutside() seems to work 
> fine, even with curves.
>
>     David
>
>> -- 
>> Nick
>>
>>
>> Op 20-7-2010 19:36, David Flanagan schreef:
>>> Nick wrote:
>>>> Canvas would benefit from a way to set stroke alignment. With the 
>>>> only available alignment being center, which is not very useful, 
>>>> custom paths have to be drawn to mimic inside and outside stroke 
>>>> alignment. That workaround may give unwanted transparency on pixels 
>>>> between a path and its stroke path once a path goes diagonal or 
>>>> curves.
>>>>
>>>> Having Canvas take care of stroke alignment (center, inside and 
>>>> outside) by adding something like strokeAlign can fix these 
>>>> transparency problems and makes adding strokes a lot easier and 
>>>> more useful.
>>>>
>>>> -- 
>>>> Nick Stakenburg
>>>>
>>>
>>> Currently for inside alignment, I think you can do this, with no 
>>> computation of custom path:
>>>
>>> c.save();
>>> c.clip();
>>> c.lineWidth *= 2;
>>> c.stroke();
>>> c.restore();
>>>
>>> Outside alignment is easy if you're also going to fill the path, of 
>>> course.  But if you want to leave the inside of the path untouched 
>>> you could do something like this, I think:
>>>
>>> var url = canvas.toDataURL();  // Back up canvas content
>>> var img = document.createElement("img");
>>> img.src = url;
>>> c.save();
>>> c.linewidth *= 2;
>>> c.stroke();
>>> c.clip();
>>> c.drawImage(img, 0, 0);  // Restore original bitmap inside the path
>>> c.restore();
>>>
>>> You can't use getImageData()/putImageData() for this, since they 
>>> ignore the clipping region.
>>>
>>> Another approach for outside stroke alignment, if you know the 
>>> directionality of your path would be to turn the path inside out by 
>>> drawing an off-screen rectangle around the canvas in the opposite 
>>> direction.  Then the outside of your path becomes the inside of the 
>>> new path and you can use the technique above for inside alignment...
>>>
>>>     David
>>>
>>
>>
>


--------------020606060906030405080505
Content-Type: text/html;
 name="strokeInsideOutsideTest2.html"
Content-Transfer-Encoding: quoted-printable
Content-Disposition: attachment;
 filename="strokeInsideOutsideTest2.html"

<!DOCTYPE html>
<html>
<head>
<style type=3D'text/css'>
body { background: red; }
#zoom { margin-left: 680px; }
</style>
<script>

// This function has problems with paths that include multiple subpaths.
// First, if the boundaries of two subpaths are close, and
// the stroke outside of one would be inside of the other, then we can
// get artifacts. =20
// Second, when one subpath is entirely enclosed inside the other and
// the windings are the same, then both the inside and outside of the
// inner subpath are "inside".  So should we stroke both sides?
// That is what this method does.  It seems kind of logical, but not real=
ly
// what we'd want.
//
// The upshot is that strokeInside() is not a fully-general replacement f=
or
// stroke().  When multiple subpaths are required they may need to be=20
// stroked individually rather than as a group.
function strokeInside(c) {
    c.save();
    c.clip();
    c.lineWidth *=3D 2;
    c.stroke();
    c.restore();
}

function strokeOutside(c) {
    // Back up the entire canvas to an offscreen canvas
    var offscreen =3D document.createElement("canvas");
    offscreen.width =3D c.canvas.width;
    offscreen.height =3D c.canvas.height;
    var offC =3D offscreen.getContext('2d');
    offC.drawImage(c.canvas, 0, 0);

    // Save current state
    c.save();

    // Stroke the shape with double linewidth
    c.lineWidth *=3D 2;
    c.stroke();

    // Now set the clipping region and restore the interior of the path
    // to its original state before the stroke
    c.clip();
    c.globalCompositeOperation =3D "copy";  // So we don't blend colors
    c.setTransform(1,0,0,1,0,0);          // Back to default coordinate s=
ystem
    c.drawImage(offscreen, 0, 0);         // Copy the image back

    // Restore the graphics state
    c.restore();

    // Deallocate offscreen pixels?
    offscreen.width =3D offscreen.height =3D 0;
}

function shape(c) {
    c.beginPath();

    c.moveTo(100,10);
    c.lineTo(140,170);
    c.lineTo(10,40);
    c.lineTo(150,40);
    c.lineTo(60,175);
    c.closePath();

    c.rect(0,200,200,150);

    c.moveTo(65,265);
    c.arc(65,265,50,0,Math.PI/3,true);
    c.closePath();

    c.moveTo(130,280);
    c.arc(130,280,50,0,Math.PI/3,false);
    c.closePath();

    c.fill();
    c.save();
    c.lineWidth =3D arguments[1] || 0;
    c.strokeStyle =3D c.fillStyle;
    c.stroke();
    c.restore();
}

function demo() {
    var canvas  =3D document.getElementById("canvas");
    var c =3D canvas.getContext('2d');

    c.fillStyle =3D "rgba(246,246,246,1)";
    c.strokeStyle =3D "rgba(150,150,150,1)";
    c.lineWidth =3D 8;

 =20
    c.translate(20,20);
    shape(c);
    c.stroke();       // Regular stroke


    // What does it mean to stroke inside a path when subpaths overlap?
    // if both sides of a subpath are inside a containing path, should
    // both sides be stroked?
    c.translate(250, 0);
    shape(c);
    strokeInside(c);  // Stroke inside the path

    c.translate(250, 0);
    shape(c);
    strokeOutside(c); // Stroke outside the path

		c.translate(250, 0);
    shape(c, 1);
    strokeOutside(c); // Stroke outside the path with a 1px stroke on the=
 shape, only fixes firefox
}

window.onload =3D demo;
</script>
</head>
<body>
<canvas id=3D"canvas" width=3D980 height=3D450></canvas>
<p>
<img id=3D'zoom' src=3D'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIM=
AAABfCAYAAADLYXQyAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAxhp=
VFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNM=
E1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bW=
V0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYwIDYxLjEzNDM0MiwgMjAxMC8=
wMS8xMC0xODowNjo0MyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3=
LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZ=
GY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bW=
xuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY=
9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpD=
cmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wL=
mlpZDo0NjAyMzk1MDk3MTkxMURGODU2QkZGOTgxMDYwNTREOSIgeG1wTU06RG9jdW1lbnRJRD=
0ieG1wLmRpZDo0NjAyMzk1MTk3MTkxMURGODU2QkZGOTgxMDYwNTREOSI+IDx4bXBNTTpEZXJ=
pdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOjQ2MDIzOTRFOTcxOTExREY4NTZC=
RkY5ODEwNjA1NEQ5IiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOjQ2MDIzOTRGOTcxOTExR=
EY4NTZCRkY5ODEwNjA1NEQ5Ii8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveD=
p4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+qZlBLAAAC2VJREFUeNrsXc1uFDkQ7vmfTGZ=
CsiQrbYS4rPYN2NfglhPvRR4DATcegSzKCcF5haLVLoJEwCTzm63PjAePp8qd7nH3TEh90hzS=
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
xZTbe0cAAAAASUVORK5CYII%3D' alt=3D''/>
</p>
</body>
</html>

--------------020606060906030405080505--