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

Jim Graham james.graham at oracle.com
Tue Dec 7 02:00:07 UTC 2010

```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.

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?

In looking at that method it looks like the cubic handling code tries 0
and 1 in addition to the critical points it finds using a root, but the
quadratic code that it chooses if a==0 will throw out all roots outside
the 0,1 range and may end up with 0 answers.  The cubic code further can
reject all of the points (if they are all non-zero and same sign) and
also return no answers, but may have fewer cases where it would do that.

Still, my point was not that you might fail to find a root, but that the
roots may get rejected and end up with no answers in range.

>> 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;
>          	}

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).

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.

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

>> Renderer.java:  Is this just a straight copy of what I was working on?
>
> I'm pretty sure it is, yes.

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

;-)

>> 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...
>
> 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

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:

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);
}
}

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...

...jim

```