Canvas path transformations

Some rough comments on the recent canvas changes (which made 
transformations take effect when paths are constructed, rather than when 
they're drawn, for compatibility with Firefox and Safari):

"The transformation matrix can become infinite, at which point nothing 
is drawn anymore." - that doesn't really make sense now, since it's not 
a rule that can be applied when drawing. You could make the 
transformation matrix infinite, then make it finite again, then create 
some path, then fill, and it should probably draw the path. Or you could 
make it infinite, create a bit of path, make it finite, create more 
path, then draw, and it should probably draw just the second bit of path.

At least that's what Safari does. Firefox throws exceptions whenever you 
pass in infinity to a method. I wouldn't mind if it was specified to 
always throw exceptions when passing infinity/NaN to a method, or maybe 
if it was explicitly undefined - implementation details already mean you 
can't get theoretically correct results when scaling by e.g. 10^38, so 
it's not much worse if infinity is treated the same way...

createLinearGradient, createRadialGradient: How are those coordinates 
affected by the CTM?

"The coordinates given in the arguments to these methods must be 
transformed according to the current transformation matrix before 
applying the calculations described below and before adding any points 
to the path." - that is true for moveTo, lineTo, quadraticCurveTo, 
bezierCurveTo. It is not true for the others:

arcTo: Transforming the coordinates is not equivalent to transforming 
the whole path - e.g. a non-uniform scale should result in a path that's 
not a circular arc, since it'll be stretched in some direction. (See in WebKit (nobody 
else is remotely near implementing arcTo properly).)

So the curve should be calculated in a local space, then transformed 
back into canvas space with the CTM. That means (x0, y0) needs to be the 
last point in the subpath transformed from canvas space into local space 
(i.e. the inverse of the CTM), so that it's correct when transformed 
back. So I think arcTo could be defined like:

"""[No implicit transformation of the arguments, and then] Let the point 
(x0, y0) be the last point in the subpath, transformed by the inverse of 
the current transformation matrix. Blah blah The Arc blah blah. Blah. 
The Arc and the start and end tangent points must then be transformed by 
the current transformation matrix.

Blah blah.

Otherwise, the method must connect the last point in the subpath to the 
start tangent point by a straight line, then connect the start tangent 
point to the end tangent point by The Arc, and finally add the start and 
end tangent points to the subpath."""

(Of course the inverse of the CTM is undefined if e.g. you call scale(0, 
0). That's an edge case that probably needs more investigation...)

arc: Same problem as arcTo.

"""Consider a circle blah blah. The points at blah are the start and end 
points respectively. The arc is blah. The arc and the start and end 
points must then be transformed by the current transformation matrix."""

rect: The four corner coordinates are what need to be transformed, not 
the arguments to the method.

"""The rect(x, y, w, h) method must create a new subpath containing just 
the four points (x, y), (x+w, y), (x+w, y+h), (x, y+h), transformed by 
the current transformation matrix, with those four points connected by 
straight lines, and must then mark the subpath as closed. It must then 
create a new subpath with the point (x, y), transformed by the current 
transformation matrix, as the only point in the subpath."

And also:

"The stroke() method must stroke each subpath of the current path in 
turn, using the strokeStyle, lineWidth, lineJoin, and (if appropriate) 
miterLimit attributes." - that should also say something like "The line 
width must be scaled by the current transformation matrix." (that's not 
really true (it needs to at least be skewed as well, and I'm not sure of 
the exact details), but stroke drawing is totally underdefined anyway 
and so there's not much point being precise here now), since I think 
it's clearer to say it here (as it's directly relevant to the outcome of 
stroke()) rather than hidden in the ' Drawing model' 
section. (Also, 'Drawing model' shouldn't need to say that fill/stroke 
styles etc are honoured, since the definitions of stroke() and fill() 
already say that.)

"Paths, when filled or stroked, must be painted without affecting the 
current path, and must be subject to transformations, shadow effects, 
global alpha, clipping paths, and global composition operators." - 
remove the "transformations" since the paths aren't transformated when 
filling or stroking.

"Note: The transformation is applied to the path when it is drawn, not 
when the path is constructed. Thus, a single path can be constructed and 
then drawn according to different transformations without recreating the 
path." - delete that since it's lying now.

Philip Taylor

Received on Wednesday, 16 January 2008 01:25:02 UTC