Hi Laurent,

On 3/11/16 9:06 AM, Laurent Bourgès wrote:
By the way I started refactoring MRE.strokeTo () to get rid of outat ie
I propose to remove the optimisation for non-uniform at as I prefer
filtering transformed coordinates once even if it requires invDelta /
Delta transform stages in the pipeline.
I hope it will not impact too much the performance but it simplifies
notably the logic and the code.
Do you have an alternative solution (simple) ?

Yes, if we can take a non-uniform transform and express the pen size as a tilted ellipse, then we can supply the tilted ellipse parameters to the stroker and it could perform device-space widening of non-uniform pens. Basically, we have a method right now that takes the dx,dy of a path and turns it into a unit vector, does a simplistic 90 degree transform, and then scales it by the line width. It looks something like (adapted from Stroker.computeOffset()):

mdx =  dy / len(dx,dy) * halfwidth;
mdy = -dx / len(dx,dy) * halfwidth;

which is shorthand for:

(1) normalize dx,dy
(2) rotate dx,dy by 90 degrees
(3) multiply by half the transformed line width

What we'd need to do for the general case is:

(1) inverse delta transform to user space
(2) normalize dx,dy to unit vector
(3) rotate by 90 degrees
(4) scale by (half) user space line width
(5) transform back to device space

(1) and (3) through (5) are all 2x2 transforms and they could be able to be flattened into a single transform, but we have that pesky normalize step at (2) that gets in the way. If not for that, we could replace the contents of computeOffset with just a 2x2 matrix multiply, but I punted when I looked at how to introduce the normalization step. I'm pretty sure that (1) and (2) could be swapped somehow because all we really have is an elliptical pen and we should be able to take a normalized vector in device space and compute a transform that rotates it to the proper orientation that comes from "inverseTx, rotate90, Tx" and applies the proper scale, but how to compute that? When done we'd just have a 2x2 matrix to multiply by, but since the construction of that 2x2 matrix wasn't immediately obvious from concatenating component matrices I punted to other problems.

Another thing to consider is that dash length will vary based on angle so we'd need a way to compute "user length of device dx,dy" quickly. It's essentially computing "len(inverseTransform(dx,dy))", which is:

udx = inverseMxx * dx + inverseMxy * dy;
udy = inverseMyx * dx + inverseMyy * dy;
return sqrt(udx * udx + udy * udy);

which is (renaming inverseM* to I*), and please check my math:

sqrt((Ixx * dx + Ixy * dy)^2 + (Iyx * dx + Iyy * dy)^2);
sqrt(Ixx*Ixx*dx*dx + 2Ixx*Ixy*dx*dy + Ixy*Ixy*dy*dy +
     Iyx*Iyx*dx*dx + 2Iyx*Iyy*dx*dy + Iyy*Iyy*dy*dy);
sqrt( (Ixx*Ixx + Iyx*Iyx)*dx*dx +
     2(Ixx*Ixy + Iyx*Iyy)*dx*dy +
      (Ixy*Ixy + Iyy*Iyy)*dy*dy);
sqrt(K1 * dx * dx + K2 * dx * dy + K3 * dy * dy);

For a uniform scale K1 == K3 == squared inverse scale and K2 == 0 and K1,K3 can then be factored out of the sqrt into a constant and applied to the dash lengths instead.

which is only a little more complicated than a regular length computation. Unfortunately, since the angle changes over the course of a curve, it might prove a little tricky for the iterator that computes the length of the dashes along the curve, but it could be as simple as calling a different length method as it iterates.

Besides, we should also check early if the transformation matrix
contains NaN or Infinity values avoiding the path iteration. I advocate
it is an extreme case.

That could be a short-cut optimization, but it depends on how much it costs compared to just letting the path degeneracy elimination turn it all into a NOP segment by segment...

                        ...jim

Reply via email to