[OpenJDK 2D-Dev] X11 uniform scaled wide lines and dashed lines; STROKE_CONTROL in Pisces
dlila at redhat.com
Mon Aug 2 22:17:10 UTC 2010
I implemented STROKE_CONTROL today. I used the intermediate
NormalizingPathIterator, instead of implementing flattening in
pisces, because I wanted to get something working asap, and this
would be the easiest way.
The webrev is http://icedtea.classpath.org/~dlila/webrevs/fpWithStrokeControl/webrev/
I guess I'm not asking that you take a look at it, because it's probably
not the way we're going to end up doing things, but I wrote it, so
I'm sending the link just in case anyone wants to see it.
The webrev is big because it includes the floating point conversion, but all the
STROKE_CONTROL changes are in PiscesRenderingEngine.java.
----- "Jim Graham" <james.graham at oracle.com> wrote:
> Hi Denis,
> It would be ill-advised to normalize the coordinates after flattening.
> The quality would be really bad.
> Perhaps this is a good reason to start looking at updating Pisces to
> take curves and flatten at the lowest level?
> Or, I suppose we could get a non-flattened iterator from the source
> path, send it through a "normalizing" filter, and then through a
> flattening filter (the way many of the existing objects do flattening
> to just get their regular iterator and run it through an instance of
> FlatteningPathIterator so we could do this manually with an
> "NormalizingPathIterator" if normalization is needed)...
> Denis Lila wrote:
> > Hello Jim.
> > Thanks for that. I'll get to work on implementing it.
> > One thing though, about normalizing the control points of bezier
> > curves: pisces never gets any bezier curves as input. It only gets
> > lines that are the product of flattening bezier curves.
> > Pisces receives its input from flattening path iterators which get
> > from other path iterators. Of course we can't require these to send
> > normalized points. In order to normalize the control points we need
> > be able to look at the bezier curves in Pisces, so we can't just
> > all the input from the flattener. However, pisces can't handle
> > (yet, hopefully), so after the normalization, they must be
> flattened, and
> > this is the problem. I think it's a pretty good idea to do this by
> > storing the input form the iterator into pisces (after
> > creating a nameless path iterator that just iterates through all
> > and using this iterator to create a flattening iterator, which then
> > is used as before.
> > Does anyone have any other ideas?
> > Thank you,
> > Denis.
> > ----- "Jim Graham" <james.graham at oracle.com> wrote:
> >> For AA this is exactly what we do (round to nearest pixel centers
> >> strokes). Note that this is done prior to any line widening code
> >> 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
> >> 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
> >> the original geometry so you have to consider the "line widths"
> >> you encounter the pixel centers to your left and right (or above
> >> below) which govern when that column (or row) of pixels first
> >> 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
> >> be
> >> inside this path. Just below these sizes and you fail to hit
> >> pixel center. Just at the integer size you reach both pixel
> >> 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
> >> 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
> >> 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
> >> 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
> >> widths harder.
> >> So, by locating lines at (0.25, 0.25) subpixel location we end up
> >> 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
> >> 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
> >> if
> >> what we did was really the best idea. For quads we adjust the
> >> point by the average of the adjustments that we applied to its 2
> >> 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
> >> amount we moved the final endpoint. The jury is out on whether
> >> is
> >> the most aesthetic technique...
> >> ...jim
> >> Denis Lila wrote:
> >>> Regarding VALUE_STROKE_NORMALIZE the API says:
> >>> Stroke normalization control hint value --
> >> 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
> >> visible
> >>> difference between the closed source implementation and OpenJDK:
> >>> when drawing anti-aliased horizontal or vertical lines of width
> >> Pisces
> >>> draws a 2 pixel wide line with half intensity (because integer
> >> coordinates
> >>> are between pixels). Sun's jdk with VALUE_SROKE_NORMALIZE turned
> >> draws
> >>> a 1 pixel line with full intensity. This could to achieved by
> >>> checking for normalization and rounding coordinates to the
> >> half
> >>> pixel, but this solution seems too simple, and I'm not sure
> >> 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
> >> and
> >>> vertical lines.
> >>> Regards,
> >>> Denis.
More information about the 2d-dev