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

Jim Graham james.graham at oracle.com
Thu Jul 8 01:29:05 UTC 2010

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 

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


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 normalization and rounding coordinates to the nearest half 
> pixel, but this solution seems too simple, and I'm not sure whether I'm missing
> anything. It would also probably cause problems when drawing anti-aliased 
> short lines (which is done when drawing any sort of curve)
> Unless, of course, this rounding was restricted to just horizontal and 
> vertical lines.
> Regards,
> Denis.

More information about the 2d-dev mailing list