[OpenJDK 2D-Dev] X11 uniform scaled wide lines and dashed lines; STROKE_CONTROL in Pisces

Denis Lila dlila at redhat.com
Tue Dec 7 18:47:49 UTC 2010


Hi Jim.

> I'm sure you will likely find a root, but the method you are using is
> "roots*inAB*" which may throw the root out because it is out of range,
> no?

I'm sure some roots will be thrown out, but I think in every call of
getTCloseTo there will be at least one root that isn't thrown out. That's
because our [A,B) is always [0, 1), and the point x,y that we pass
to getTCloseTo lies on the line joining the endpoints of the curve on
which getTCloseTo is called, so there must be some root in [0, 1). In fact,
there will be two roots, one for each parametric polynomial.

> Read the IEEE spec on NaN.  It's a special value that has this bizarre
> property that it is the only number that is not equal to itself. ;-) 
> In fact, the test for NaN is usually "if (x == x) <notNaN> else <NaN>". 
> If you want to be explicit and formal then you can use the static 
> Float.isNaN() method (which is essentially that test - x!=x).

Interesting. I did not know that. I fixed these.

> A lot of the lines before you test MaxAcc are not needed unless you go
> into the if.  In particular, x,y,[xy][01] are only used if you call 
> getTCloseTo().

Fixed.

> Actually I think you've updated the AFD code so I should really take a
> look... :-(
> 
> ;-)

Oh, of course! I completely forgot about that. I think I only changed
the quadratic AFD code to take into account the constant acceleration of
quadratic curves.

> The problem is that normalization needs proper sub-pixel positioning
> so you need to hand it the true device space coordinates with proper 
> translation.  You need this:

Right, so I was wrong about normalization and translation being commutative.

> Using these two methods I don't think you need any transforms other
> than the original one - so all you need is "strokerat" which replaces both
> outat and inat and is either null (no special transform needed) or the
> original AT when special transforms are needed...

I implemented your way. I couldn't get rid of outat, however. In that case
where we have to apply a non orthogonal transform with no normalization we
want to just apply the transform at the end of stroking and feed stroker
untransformed paths. So, now I have both outat and strokerat. At least
one of them must always be null. In the case I mention above, strokerat will
be null, and the calls to *deltaTransformConsumer(pc2d, strokerat) won't
do anything, but the transformConsumer(pc2d, outat) call will take care of
the post stroker transformation.

I don't think the TranslateFilter will ever be used, because transformConsumer
is now called only with outat, which cannot be a translation. So we may want
to remove it.

I also fixed the filterOutNotInAB function. It was violating cubicRootsInAB's
spec by filtering out everything not in (A, B), as opposed to everything not
in [A, B), which is what it should do.

I uploaded the new webrev.

Thank you,
Denis.

----- "Jim Graham" <james.graham at oracle.com> wrote:

> Hi Denis,
> 
> On 12/6/2010 4:21 PM, Denis Lila wrote:
> > Hi Jim.
> >
> >> line 134 - what if numx or numy == 0 because only roots outside
> [0,1]
> >> were found?
> >
> > In this case lines 151-162 will execute, and nothing is wrong. The
> only
> > problem is when both numx and numy are 0. This is certainly possible
> in
> > the general case (but only with quadratic curves), but the way we're
> using
> > this function, the intermediate value theorem guarantees at least
> one root
> > will be found. Of course, finite precision math doesn't necessarily
> care
> > what calculus has to say, but in this case I can't see how the root
> computation
> > could fail. On the other hand, one could argue that this is exactly
> why we need
> > to defend against this case, so I've added some checks.
> 
> 
> >> line 145 - what if d0 and d1 are both 0?  NaN results.  What if
> you
> >> just used a simple "d0<  d1 ? tx : ty" - how far off would that
> be?
> >
> > I tried that out on a curve with very high acceleration, and it
> makes a noticeable
> > difference. So, instead I'm using
> >          	if (w0 == Float.NaN) {
> >          		return tx;
> >          	}
> 
> Same thing on Dasher line 363 where you also test for NaN.
> 
> >> line 357 - another optimization would be to check the acceleration
> in
> >> the range and if it is below a threshold then simply use the t
> from
> >> line 348 as the t for the split
> >
> > I like this. I tried implementing it. I haven't tested it yet
> though, and
> > I still have to pick a good cutoff acceleration. For now I'm using
> 1/leaflen.
> > We definitely don't want it to just be a constant, since the longer
> the leaf,
> > the worse it will be to allow acceleration, so for longer leaves we
> want to
> > skip the getTCloseTo call only if the acceleration is smaller.
> 
> >> Renderer.java:  Is this just a straight copy of what I was working
> on?
> >
> > I'm pretty sure it is, yes.
> 
> 
> >> TransformingPathConsumer2D:
> >>
> >> Any thoughts on whether we need translation in the scale filter
> and
> >> whether a 4-element non-translation filter would be useful?  I
> think
> >> the current code that drives this passes in the full transform and
> its
> >> inverse, but it could just as easily do delta transforms instead
> and
> >> save the adding of the translation components...
> >
> > I thought about this long ago, but I wasn't sure it wouldn't break
> anything.
> > Today, I got a bit more formal with the math, and I think it's ok
> > to eliminate the translation. I've implemented this (though, I
> haven't had
> > time to test it yet. That's for tomorrow).
> 
> Right now you have (note that the common terminology for "transform 
> without translation" is "delta transform"):
> 
> 	PathIterator
> 	    => DeltaAT => Normalize
> 	    => DeltaInverseAT => strokers
> 	    => FullAT => renderer

> 
> 	PathIterator
> 	    => FullAT => Normalize
> 	    => DeltaInverseAT => strokers
> 	    => DeltaAT => renderer
> 
> I would skip the creation of atNotTranslationPart and just inverse the
> 
> original transform (since I don't think the inversion is affected by 
> translation - you can see this in the calculations in 
> AT.createInverse()), and then have the transforming consumers apply a
> 
> delta transform - i.e. create a "TPC2D.deltaTransformConsumer()"
> method 
> which would apply just the non-translation parts of AT to the
> consumer.
> 
> If you want to get really fancy with optimizations you could have an 
> "inverseDeltaTransformConsumer() method that would calculate the 
> inversions on the fly to avoid construction of a scratch AT.  Since it
> 
> is just "weird transpose with signs and divide by the determinant" in
> 
> the most general case and even simpler (invert Mxx and Myy) in the 
> scale-only case it would be trivial to incorporate these calculations.
> 
> It would also eliminate a need to catch NonInvertible... ;-)
> 
>      public static PathConsumer2D
>          deltaTransformConsumer(PathConsumer2D out,
>                                 AffineTransform at)
>      {
>          if (at == null) {
>              return out;
>          }
>          float Mxx = (float) at.getScaleX();
>          float Mxy = (float) at.getShearX();
>          float Myx = (float) at.getShearY();
>          float Myy = (float) at.getScaleY();
>          if (Mxy == 0f && Myx == 0f) {
>              if (Mxx == 1f && Myy == 1f) {
>                  return out;
>              } else {
>                  return new DeltaScaleFilter(out, Mxx, Myy);
>              }
>          } else {
>              return new DeltaTransformFilter(out, Mxx, Mxy, Myx,
> Myy);
>          }
>      }
> 
>      public static PathConsumer2D
>          inverseDeltaTransformConsumer(PathConsumer2D out,
>                                        AffineTransform at)
>      {
>          if (at == null) {
>              return out;
>          }
>          float Mxx = (float) at.getScaleX();
>          float Mxy = (float) at.getShearX();
>          float Myx = (float) at.getShearY();
>          float Myy = (float) at.getScaleY();
>          if (Mxy == 0f && Myx == 0f) {
>              if (Mxx == 1f && Myy == 1f) {
>                  return out;
>              } else {
>                  return new DeltaScaleFilter(out, 1.0f/Mxx,
> 1.0f/Myy);
>              }
>          } else {
>              det = Mxx * Myy - Mxy * Myx;
>              return new DeltaTransformFilter(out,
>                                              Myy / det,
>                                              -Mxy / det,
>                                              -Myx / det,
>                                              Mxx / det);
>          }
>      }
> 

> 
> 			...jim



More information about the 2d-dev mailing list