[OpenJDK 2D-Dev] getClip()/getClipBounds() bug fix (of 8004859) causes new bug
Jim Graham
james.graham at oracle.com
Mon Oct 14 22:47:27 UTC 2013
There is a bug here because we should choose non-interfering sets of
pixels for abutting shapes, like we do for rendered shapes (and clipping
should use the same rules as non-AA rendered shapes). Adjacent shapes
that share a common border will divide the pixels along that border
between them such that any given pixel is in exactly one and only one of
the 2 shapes. The same rule should hold for clipping, but it isn't
holding here.
The fact that this particular test case works "at float precision" is
only incidental and simply lowering our calculation resolution isn't
fixing the real bug, which is that we are using the wrong calculations
to turn a transformed rectangle into a rectangular integer
pixel-oriented device region.
I think the problem here is the floor/ceil processing. The place for
"floor/ceil" would be in "dirty region calculations". In those cases
you compute the exact bounds of everything that changed which may be
floating point bounds. You then typically floor/ceil those dirty bounds
so that you redraw every pixel affected in its entirety - i.e. you are
making sure that you draw the full content of any pixel that intersected
the dirty region even if it only intersected a tiny bit.
But, for clipping, we "rasterize the shape to an integer region" and
that process should use pixel inclusion rules which are:
- if the center of a pixel is inside the transformed shape, then that
pixel is included in the rendering (clipping) process
- if the center of a pixel is on the infinitely thin boundary of a
shape, then it is included iff the space to its right is inside the txshape
- or if it is on a horizontal edge, then iff the space below it is
inside the txshape
That rule typically matches what happens when you simply round the
results "down", i.e. "ceil(x-0.5), ceil(y-0.5)". (Those calculations
will return floor(v) for a value that ends in 0.5, but ceil(v) for a
value that ends above 0.5.) Note that the round() function performs a
"round-up" operation which maps 0.5 values to the next largest integer,
but we want to map them to the next smallest integer so we would use
this modified "rounding formula". That same exact rounding operation is
applied to both the min and max values of the transformed bounds so that
if the floating point "max" values for one rectangle are equal to the
"min" values of the next, then the resulting integer boundary between
the two device-clip-regions would be rounded the same way and have the
same value, and since the regions are considered "half open", then that
means that the pixels will be included in only one of those resulting
regions...
...jim
On 10/13/13 7:11 PM, Nicolas wrote:
> The getClip()/getClipBounds bug fix
> (http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=8004859) changed
> sun.java2d.SunGraphics2D.transformShape(AffineTransform tx, Shape clip)
> to use a Rectangle2D.Double instead of Float and this change causes the
> rounding (done by casting from double to float) to be lost. The lack of
> rounding causes a bug on my program when using the jre 1.7.0.40, which
> is not present on 1.7.0.25. Take a look at the test case
> ClippingTest.java (attached), it draws clippingOn40.png (attached) when
> running the JRE 1.7.0.40, and clippingOn25.png (attached) when running
> JRE 1.7.0.25. Notice that on 1.7.0.25 the drawing does not show the
> center darker line... the clipping regions do not overlap.
>
> A fix to this new bug is to change:
> ----
> return new Rectangle2D.Double(matrix[0], matrix[1],
> matrix[2] - matrix[0],
> matrix[3] - matrix[1]);
> ----
> on line 1947 of sun.java2d.SunGraphics2D to:
> ----
> new Rectangle2D.Float((float)matrix[0], (float)matrix[1],
> (float)(matrix[2] - matrix[0]),
> (float)(matrix[3] - matrix[1])
> );
> ----
>
> My application calculates the clipping regions on screen using doubles
> and a procedure slightly different (but equivalent if we take away the
> insignificant digits of the calculation) compared to the one used by
> SunGraphics2D, so when SunGraphics2D transform them (calculates the
> usrClip) there are small differences (due to insignificant digits) that
> in some cases end up being magnified to a full pixel [as the clipRegion
> is calculated using floor() and ceil() inside the getBounds() called by
> SunGraphics2D.validateCompClip()] by the current implementation
> (1.7.0.40) and were nicely cut off by the previous (1.7.0.25) when it
> rounded the result using Rectangle2D.Float.
>
> Please let me know if I can be of more help,
> Nicolas
>
More information about the 2d-dev
mailing list