Re: [OpenJDK 2D-Dev] Fix for uniformly scaled dashed lines.

2010-07-07 Thread Jim Graham
This looks good, but don't assert that the transform is non-singular. 
Transforms can frequently be singular and that isn't an exception.


Actually, a singular transform simply means that the entire coordinate 
space has collapsed to a line or a point and so nothing need be 
rendered.  If there is a way to shortcut the rendering to a NOP that 
would be the right reaction to a singular transform...


...jim

Denis Lila wrote:

Hello Jim.

Here is a webrev for the patch: 
http://icedtea.classpath.org/~dlila/webrevs/dasherFix/webrev/

I have eliminated most of the setup code, like you suggested.

This is both more efficient, and far clearer than the original code
(and especially my version of the patch. It is also correct in cases 
where the transform is [[n,0],[0,n]]).


I still have my version of the patch (the one with "dashesToCompute"),
and as I mentioned in another e-mail, the allocation of the array
can be eliminated, since it can be turned into a field. So it should
have better performance in pretty much all cases.
If you would like me to send a webrev for that too, e-mail me.

Thank you,
Denis.

- "Jim Graham"  wrote:


Hi Denis,

One request on your code - please don't use the variable "lowercase
L". 
  On my screen with Courier font I cannot distinguish between the
number 
1 and the lowercase L character and so I cannot verify if your code is


correct.

Also, by "inner loop" I meant the single loop.  I use the term to mean

the "loop that does all the work at the innermost level" without
regard 
to whether the case contains only 1 loop and is therefore a degenerate


application of the term.

My comment about the "major axis" stuff was an optimization that is no

longer needed.  I though I saw calls to hypot() in the inner loop, but
I 
just noticed that those were in deleted code and the new code has no 
such calls, so you can ignore it.  If it makes the comment clearer, 
"major axis" is either the X or Y axis depending on whether the line
is 
more horizontal or vertical, but you can ignore it now.


I will note that the 2 arrays you compute are simply scaled versions
of 
the dash array and so we could eliminate the extra allocations by
simply 
computing the values inside the loop at the cost of a multiply per
dash 
segment to offset the cost of an allocation per incoming line segment.


Also, you would no longer need to compute the "dashesToCompute" value

and the setup code would be much, much simpler (basically you just
need 
to compute the untransformed length and the cx and cy values and then


jump into the loop).

I'm leaning towards the multiplies in the loop to greatly simplify the

code...

(One last comment - have you checked what happens with the code in the

presence of a degenerate transform?  A non-invertible transform may
run 
the risk of an infinite loop if you assume that you can reverse
compute 
the line length and end up with a finite value...)


...jim

Denis Lila wrote:

Hello Jim.

Thank you for your reply. It seems my code did not fully take into
account your second point after all.
The dx's of the transformed dashes are di*newx/ (where
di is the untransformed dash length, newx is the transformed x 
coordinate, and  is the untransformed line length). Obviously,

newx/ is constant for all dash segments, so it can be computed
outside of the loop, but I was computing t=di/ inside the loop,
and then t*newx also inside the loop.

I have fixed this and I included an improved version of the patch.

However, I do not understand the second part of your e-mail
("One more optimization ..."). I am not sure what you mean by
"major axis", how one would loop along it, and what the "inner

loop"

is. There are no nested loops in this method.

Also, the computation of the dxi and dyi of the transformed dash

segment

dash[i] involves just 1 multiplication and 1 bit shift (along with

an

overhead of 2 divisions and 2 bit shifts).
The computation of the actual endpoint of the dashes (done in the

while(true)

loop) most of the time involves just 2 additions.
I am not sure how this can be made any simpler.

Thank you,
Denis.

- "Jim Graham"  wrote:


Hi Denis,

Here are my thoughts on it:

- Lines are affinely transformed into lines.  The slope may be
different 
before and after the transform, but both have a single slope.


- The ratio of a line length to its transformed line length is a

scale
factor that depends solely on the angle of the line.  Thus, for 
determining dashing you can simply compute this scale factor once

for
a 
given line and then that single scale factor can be applied to
every 

dash segment.

It appears that your setup code takes these factors into account,
though 
I haven't done a grueling line by line analysis as to whether you

got

the math right.

One more optimization is that once you know the angle of the line

then

you have a factor for how the length of a segment of that line

relates

to its dx and dy.

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

2010-07-07 Thread Jim Graham
The first part means that if the scale is uniform in X and Y 
(AffineTransform has some logic to determine this property in its 
getType() method) then we can use X11 to do line widening by just giving 
it a scaled line width.  Also, X11 is limited to integer line widths so 
we would only want to do this if the SPEED hint is specified (not 
QUALITY) or if the scaled line width was close to an integer.


If we are going to use a software rasterizer to widen the line and then 
send over spans to render, it may be faster to just give X11 the 
original path and a scaled line width and ask it to widen the line. 
Even if it uses a software renderer the reduction in protocol traffic is 
a win, and their rasterizer is probably optimized for integer polygons 
and may likely be faster than our more general curve-handling code.


But, I would rank this low on optimizations at this point...

...jim

Clemens Eisserer wrote:

Hi Denis,


In sun.java2d.x11.X11Renderer, line 340, it says:
   // REMIND: X11 can handle uniform scaled wide lines
   // and dashed lines itself if we set the appropriate
   // XGC attributes (TBD).


Also, it is a known issue that Pisces does not support the STROKE_CONTROL
hint.

I have been wanting to implement these two features, and I have a few questions:
Has anything been decided on the first issue? Do we still want to implement it?
If yes, can anyone give me some rough suggestions as to how I can get started?


Its just my personal opinion, but I would recommend not implementing it.
Xorg falls back to software anyway for anything more complex than
solid rectangles and blits
and those code-paths will only be triggered for non-antialised
rendering with solid colors.

Implementing it in Pisces would help every backend OpenJDK supports :)

Just checked and I also ignore the STROKE_CONTROL stuff completly in
the cairo based Jules rasterizer.
Curious how that could be mapped to Cairo, do you know any more
in-depth explanation how it works - or examples how it should look
like?

Thanks, Clemens


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

2010-07-07 Thread Jim Graham
For AA this is exactly what we do (round to nearest pixel centers for 
strokes).  Note that this is done prior to any line widening code is 
executed.


For non-AA we normalize coordinates to, I believe the (0.25, 0.25) 
sub-pixel location.  This is so that the transitions between widening of 
lines occurs evenly (particularly for horizontal and vertical wide 
lines).  If you round to pixel edges then you have the following 
progression (note that the line width grows by half on either side of 
the original geometry so you have to consider the "line widths" where 
you encounter the pixel centers to your left and right (or above and 
below) which govern when that column (or row) of pixels first turns on):


width 0.00 => 0.99  nothing drawn (except we kludge this)
width 1.00 => 1.00  1 pixel wide (col to left turns on)
width 1.01 => 2.99  2 pixels wide (col to right turns on)
width 3.00 => 3.00  3 pixels wide (etc.)
width 3.01 => 4.99  4 pixels wide

Note that it is nearly impossible to get an odd-width line.  You 
basically have to have exactly an integer width to get an odd-width 
line.  This is because at the odd widths you reach the "half pixel" 
locations on both sides of the line at the same time.  Due to the 
"half-open" insideness rules only one of the pixels will be chosen to be 
inside this path.  Just below these sizes and you fail to hit either 
pixel center.  Just at the integer size you reach both pixel centers at 
the same time.  Just slightly larger than that width and now you've 
fully enclosed both pixel centers and the line width has to increase by 
nearly 2.0 until you reach the next pixel centers.


(The kludge I talk about above is that we set a minimum pen width so 
that we never fail to draw a line even if the line width is set to 0.0, 
but the above table was a theoretical description of the absolute rules.)


If we rounded them to pixel centers, then the transitions look like this:

width 0.00 => 0.00  nothing drawn (modulo kludge)
width 0.01 => 1.99  1 pixel wide (column you are in turns on)
width 2.00 => 2.00  2 pixels wide (column to left turns on)
width 2.01 => 3.99  3 pixels wide (column to right turns on)
width 4.00 => 4.00  4 pixels wide (etc.)
width 4.01 => 5.99  5 pixels wide

We have a similar effect as above, but biased towards making even line 
widths harder.


So, by locating lines at (0.25, 0.25) subpixel location we end up with a 
 very even progression:


width 0.00 => 0.50  nothing drawn (modulo kludge)
width 0.51 => 1.50  1 pixel wide (column you are in turns on)
width 1.51 => 2.50  2 pixel wide (column to left gets added)
width 2.51 => 3.50  3 pixel wide (column to right gets added)
width 3.51 => 4.50  4 pixel wide (etc.)

This gives us nice even and gradual widening of the lines as we increase 
the line width by sub-pixel amounts and the line widths are fairly 
stable around integer widths.


Also, note that we don't say "when stroking" as you might want to 
normalize both strokes and fills so that they continue to match.  I 
believe that we normalize both strokes and fills for non-AA and we only 
normalize strokes for AA (and leave AA fills as "pure").  AA is less 
problematic with respect to creating gaps if your stroke and fill 
normalization are not consistent.


The rounding equations are along the lines of:

v = Math.floor(v + rval) + aval;

For center of pixel you use (rval=0.0, aval=0.5)
For 0.25,0.25 rounding use  (rval=0.25, aval=0.25)
For edge of pixel you use   (rval=0.5, aval=0.0)

Also, we came up with an interesting way of adjusting the control points 
of quads and cubics if we adjusted their end points, but I don't know if 
what we did was really the best idea.  For quads we adjust the control 
point by the average of the adjustments that we applied to its 2 end 
points.  For cubics, we move the first control point by the same amount 
as we moved the starting endpoint and the second control point by the 
amount we moved the final endpoint.  The jury is out on whether that is 
the most aesthetic technique...


...jim

Denis Lila wrote:

Regarding VALUE_STROKE_NORMALIZE the API says:
Stroke normalization control hint value -- geometry should
be normalized to improve uniformity or spacing of lines and
overall aesthetics. Note that different normalization 
algorithms may be more successful than others for given 
input paths. 


I can only think of one example where VALUE_STROKE_NORMALIZE makes a visible
difference between the closed source implementation and OpenJDK:
when drawing anti-aliased horizontal or vertical lines of width 1, Pisces 
draws a 2 pixel wide line with half intensity (because integer coordinates

are between pixels). Sun's jdk with VALUE_SROKE_NORMALIZE turned on draws
a 1 pixel line with full intensity. This could to achieved by just
checking for normalizati

Re: [OpenJDK 2D-Dev] Fix for drawing round endcaps on scaled lines.

2010-07-07 Thread Jim Graham
You can google and find a dozen sites that detail the math that shows 
that Bezier approximations to quarter circles approximate the original 
arc to within about .1%.  We use that kind of math in the geom classes 
(see ArcIterator and EllipseIterator)...


...jim

Denis Lila wrote:

I also have two questions about computing a good bezier approximation
to circle arcs.

1. Given the arc (1,0)->(cos(a),sin(a)) where 0 wrote:


That's true.

Well, if we're worried about the generated paths being verbose
and taking long to process then the problem extends beyond just
drawing round end caps. As far as I can see, whenever a path is
drawn that doesn't consist only of straight lines (i.e. an ellipse),
a flattening path iterator is being used to feed Stroker. So all
the bezier curves are still broken down into tiny straight lines,
just not by Stroker itself.

So, my question is, given a bezier curve C and a number w, is
there a way of quickly computing the control points of two bezier
curves C1, C2 such that the stuff between C1 and C2 is the widened
path?
More formally: compute the control points of C1, C2, where
C1 = {(x,y) + N(x,y)*(w/2)  | (x,y) in C}
C1 = {(x,y) - N(x,y)*(w/2)  | (x,y) in C}, where N(x,y) is the normal
of C at (x,y).

If we could do this easily, then we can just make a new class that
outputs bezier curves that is similar in purpose to Stroker, but that
is used only when the output can handle bezier curves. This way, the
only use left for Stroker would be when anti-aliasing, and for
every thing else we wouldn't have to use a flattening path iterator.

Thanks,
Denis.

- "Jim Graham"  wrote:


Hi Denis,

Consider the case of using BasicStroke.createStrokedShape().  How do
you
know how many pixels the resulting path will occupy?  You can't

reduce

to concrete samples if you don't know the transform.

So, for rendering, then you may be correct.  But for cases where the
path is being asked for then beziers are the only responsible
solution...

...jim

Denis Lila wrote:

Hello Jim.

I thought about checking the output and changing the behaviour
depending on whether the output is a PC2D or a LineSink, but I

didn't

implement it because I thought the point was to get rid of the

sampling

at this stage. However, if performance is the issue, then I guess

I'll

start working on it.

Although, I wonder whether it is really worth it. I think most

lines

drawn

won't be wider than about 5 pixels, which means that the current

way

will

emit about 7 lines, so that's 14 coordinates. 2 bezier quarter

circles will

require 12 coordinates. In terms of storage, there isn't much

difference, and

for lines of width 4 or smaller the current method is more

efficient.

I'm also guessing that it's harder for the rasterizer to deal with

bezier

curves than with straight lines, so is it possible that replacing

the

3.14*lineWidth/2 lines generated by the current method with 2

bezier

quarter circles isn't worth it (for small lineWidths)?

Thanks,
Denis.

- "Jim Graham"  wrote:


Sigh - that makes sense.  One issue is that the resulting paths

it

generates are much more "verbose" than they need to be.  This

would

generally mean that it takes far more storage than it would

otherwise

need - and it means that if the result needs to be transformed

then

it

would take many more computations to transform each segment than

the

bezier.

So, perhaps it would be worth having it check the type of the

output

and
do either a bezier or a bunch of lines depending on if it is a

PC2D

or

a
LineSink?

Also, it isn't really that difficult to for Renderer to include

its

own
Cubic/Quadratic flattening code, but it might involve more
calculations
than the round-cap code since it would have to be written for
arbitrary
beziers whereas if you know it is a quarter circle then it is

easier

to
know how far to subdivide...  :-(

...jim

Denis Lila wrote:

So, I have been thinking about this, and I can't see a good
way to do it that wouldn't involve heavy changes to Pisces.

In order for Stroker to generate Bezier quarter circles, it

would

have to implement a curveTo method, which means Stroker should
start implementing PathConsumer2D and instead of using a

LineSink

output it would have to use a PathConsumer2D output (either

that,

or

LineSink should include a curveTo method, but then there won't

really

be any difference between a LineSink and a PathConsumer2D. By

the

way,

LineSink doesn't have any implemented methods, so why is it an

abstract

class as opposed to an interface?)

Stroker is used in 3 ways:
1. As an implementation of BasicStroke's createStrokedShape

method.

This

uses a Path2D object as output.
2. As a way of feeding a PathConsumer2D without calling

createStrokedShape

to generate an intermediate Shape. This uses a PathConsumer2D

output.

3. As a way of feeding lines to a Renderer object, which

generates

alpha

tiles us

Re: [OpenJDK 2D-Dev] Fix for drawing round endcaps on scaled lines.

2010-07-07 Thread Jim Graham

Hi Denis,

We need a lot of upgrades in this area, but they will come with some 
engineering cost.


Note that the Ductus line widening code widens curves to curves, but 
many (most?) line wideners flatten everything to do widening so they 
only have to deal with algorithms that widen and join lines.  I haven't 
applied myself to the task of creating an algorithm that can do their 
"direct curve widening" technique yet so I don't have a canned solution 
for that, but it would make the results of 
BasicStroke.createStrokedShape() much less verbose.


Also, for a practical matter the existing down-stream rendering classes 
that take the output from these widening classes only deal with lines, 
but if they took the curves directly then they might be able to do a 
more efficient job of flattening the segments.  Until then, the only 
case where it would matter much for these classes to output curves would 
be for BS.createStrokedShape() and even that would probably require some 
more plumbing from the code that feeds these classes.


But, I want to raise visibility of these issues here because we should 
not be blindly accepting flattening as the law when we consider upgrades 
to this part of the code.  It is currently an "easy way out" and as we 
spend time on the code we should provide resistance to accepting that 
philosophy by default...


...jim

Denis Lila wrote:

That's true.

Well, if we're worried about the generated paths being verbose
and taking long to process then the problem extends beyond just
drawing round end caps. As far as I can see, whenever a path is 
drawn that doesn't consist only of straight lines (i.e. an ellipse),

a flattening path iterator is being used to feed Stroker. So all
the bezier curves are still broken down into tiny straight lines,
just not by Stroker itself.

So, my question is, given a bezier curve C and a number w, is
there a way of quickly computing the control points of two bezier
curves C1, C2 such that the stuff between C1 and C2 is the widened
path?
More formally: compute the control points of C1, C2, where
C1 = {(x,y) + N(x,y)*(w/2)  | (x,y) in C}
C1 = {(x,y) - N(x,y)*(w/2)  | (x,y) in C}, where N(x,y) is the normal
of C at (x,y).

If we could do this easily, then we can just make a new class that
outputs bezier curves that is similar in purpose to Stroker, but that 
is used only when the output can handle bezier curves. This way, the

only use left for Stroker would be when anti-aliasing, and for
every thing else we wouldn't have to use a flattening path iterator.

Thanks,
Denis.

- "Jim Graham"  wrote:


Hi Denis,

Consider the case of using BasicStroke.createStrokedShape().  How do
you 
know how many pixels the resulting path will occupy?  You can't reduce


to concrete samples if you don't know the transform.

So, for rendering, then you may be correct.  But for cases where the 
path is being asked for then beziers are the only responsible

solution...

...jim

Denis Lila wrote:

Hello Jim.

I thought about checking the output and changing the behaviour 
depending on whether the output is a PC2D or a LineSink, but I

didn't

implement it because I thought the point was to get rid of the

sampling

at this stage. However, if performance is the issue, then I guess

I'll

start working on it.

Although, I wonder whether it is really worth it. I think most lines

drawn

won't be wider than about 5 pixels, which means that the current way

will

emit about 7 lines, so that's 14 coordinates. 2 bezier quarter

circles will

require 12 coordinates. In terms of storage, there isn't much

difference, and

for lines of width 4 or smaller the current method is more

efficient.

I'm also guessing that it's harder for the rasterizer to deal with

bezier

curves than with straight lines, so is it possible that replacing
the 

3.14*lineWidth/2 lines generated by the current method with 2 bezier
quarter circles isn't worth it (for small lineWidths)?

Thanks,
Denis.

- "Jim Graham"  wrote:


Sigh - that makes sense.  One issue is that the resulting paths it
generates are much more "verbose" than they need to be.  This would
generally mean that it takes far more storage than it would

otherwise

need - and it means that if the result needs to be transformed then

it

would take many more computations to transform each segment than

the

bezier.

So, perhaps it would be worth having it check the type of the

output
and 
do either a bezier or a bunch of lines depending on if it is a PC2D

or
a 
LineSink?


Also, it isn't really that difficult to for Renderer to include

its
own 
Cubic/Quadratic flattening code, but it might involve more
calculations 
than the round-cap code since it would have to be written for
arbitrary 
beziers whereas if you know it is a quarter circle then it is

easier
to 
know how far to subdivide...  :-(


...jim

Denis Lila wrote:

So, I have been thinking about this, and I can

Re: [OpenJDK 2D-Dev] Fix for uniformly scaled dashed lines.

2010-07-07 Thread Jim Graham

Hi Denis,

Saving the arrays depends on both the original value of the array and 
also the transform being used.  Can a given Dasher be used by more than 
one rendering thread at a time?  (I forget if this is possible.)  Even 
if we currently cannot use them from more than one thread, I prefer 
re-entrant code to non-re-entrant code in general unless there is a big 
performance savings, and then the reuse of data should really be 
carefully planned and accounted for in the interfaces.


Also, scale and shear are both potentially non-invertible.  Scale(0,0) 
is non-invertible and I believe that something like Shear(1,1) is also 
non-invertible.  Also some combinations including rotate can become 
non-invertible even though the rest of the chain of transforms looks 
benign.  Just because the transform is affine does not mean it is 
invertible (witness the fact that we have to throw non-invertible 
exceptions from some methods)...


...jim

Denis Lila wrote:

Hello Jim.

Sorry about the l. It was already there, and I wanted to change as
little as possible.

As for whether to compute the transformed dashes inside the while(true)
or not... the main point of this patch was to fix a bug where zooming 
transforms weren't handled properly. The efficiency improvements were

just something I threw in because a bunch of stuff was being computed
too many times, so I can go either way. I should note that the allocations
can be easily removed by just turning the arrays into fields, and allocating
them at setParameters, since for any one Dasher object their length is 
constant. So the choice is between clearer and slower code and mine. I

have 2 patches ready, one for each way of doing it. Just tell me which
to send.

As for degenerate inputs: I did think about non-invertible transforms, but
since they weren't being handled in the original Dasher and Stroker, and
since the API provides no way of using non-invertible transforms (the only
possible ones are shear, rotate, translate, and scale, all of which are
invertible), I didn't think it was necessary to check for them.
How do you think they should be handled? I think we should just throw
an InvalidInputException if the determinant is 0.
One more thing: it turns out that by computing dashes outside of the while(true)
I introduced the possibility for divide by zero errors. I fixed this.

Thanks,
Denis.
- "Jim Graham"  wrote:


Hi Denis,

One request on your code - please don't use the variable "lowercase
L". 
  On my screen with Courier font I cannot distinguish between the
number 
1 and the lowercase L character and so I cannot verify if your code is


correct.

Also, by "inner loop" I meant the single loop.  I use the term to mean

the "loop that does all the work at the innermost level" without
regard 
to whether the case contains only 1 loop and is therefore a degenerate


application of the term.

My comment about the "major axis" stuff was an optimization that is no

longer needed.  I though I saw calls to hypot() in the inner loop, but
I 
just noticed that those were in deleted code and the new code has no 
such calls, so you can ignore it.  If it makes the comment clearer, 
"major axis" is either the X or Y axis depending on whether the line
is 
more horizontal or vertical, but you can ignore it now.


I will note that the 2 arrays you compute are simply scaled versions
of 
the dash array and so we could eliminate the extra allocations by
simply 
computing the values inside the loop at the cost of a multiply per
dash 
segment to offset the cost of an allocation per incoming line segment.


Also, you would no longer need to compute the "dashesToCompute" value

and the setup code would be much, much simpler (basically you just
need 
to compute the untransformed length and the cx and cy values and then


jump into the loop).

I'm leaning towards the multiplies in the loop to greatly simplify the

code...

(One last comment - have you checked what happens with the code in the

presence of a degenerate transform?  A non-invertible transform may
run 
the risk of an infinite loop if you assume that you can reverse
compute 
the line length and end up with a finite value...)


...jim

Denis Lila wrote:

Hello Jim.

Thank you for your reply. It seems my code did not fully take into
account your second point after all.
The dx's of the transformed dashes are di*newx/ (where
di is the untransformed dash length, newx is the transformed x 
coordinate, and  is the untransformed line length). Obviously,

newx/ is constant for all dash segments, so it can be computed
outside of the loop, but I was computing t=di/ inside the loop,
and then t*newx also inside the loop.

I have fixed this and I included an improved version of the patch.

However, I do not understand the second part of your e-mail
("One more optimization ..."). I am not sure what you mean by
"major axis", how one would loop along it, and what the "inner

loop"

is. Th

Re: [OpenJDK 2D-Dev] 2 small xrender fixes

2010-07-07 Thread Phil Race

I see the problem now. I looked at what T2K (the rasteriser) reports, but
in the non-T2K glue code we then make an adjustment in the case of LCD 
glyphs

which simply should never have been applied in the case of an empty image.
It has never affected any of our code as I think we only truly use the 
width (not the
same as advance) during blitting and the check for a null image stops us 
before that's

an issue. I'll file a bug and fix this.

Thanks,

-phil.




On 7/7/2010 12:28 PM, Clemens Eisserer wrote:

Hi Phil,

Strange, running your test  I get width=-1 when uploading as well as
rendering the glyph - and the pipeline basically just reads the native
GlyphInfo structure by using Unsafe.

   

I'll have to think about it a bit more ..
 

Anything I could to to help to track that down?

Thanks, Clemens
   




Re: [OpenJDK 2D-Dev] 2 small xrender fixes

2010-07-07 Thread Clemens Eisserer
Hi Phil,

Strange, running your test  I get width=-1 when uploading as well as
rendering the glyph - and the pipeline basically just reads the native
GlyphInfo structure by using Unsafe.

> I'll have to think about it a bit more ..
Anything I could to to help to track that down?

Thanks, Clemens