Ah, this is an issue of what an empty rectangle means.
Filling 1.5,1.5,0,0 produces no output because it has no interior.
But, stroking 1.5,1.5,0,0 can produce output because it is simply a
rectangle that spins in place.
As a result there are really 3 different containment properties for a
rectangle - one that has area, one that has only location/no area, and
one that has neither location nor area. This is discussed in the class
comments for java.awt.Rectangle. isEmpty() indicates whether any area
is covered, but the Rectangle may still accumulate into bounds
calculations (via union operations) and other similar operations will
treat its location as valid even if it encompasses no area.
The issue is what should Rectangle2D.getBounds() return? We decided to
do a min/max on the origin coordinate if the width/height are 0 because
of this "it exists even if it covers no area" issue.
But, clip areas are governed by what pixels would be filled, which is
not reflected in the way that getBounds() works.
What is going wrong in SG2D is that it is using Rectangle2D.getBounds()
to compute the containment of the clip, but that does a min/max on the
area rather than compute which pixels would be hit by a fill operation.
We should replace that calculation with a more accurate computation on
which centers of pixels are included in the Rectangle2D, something like:
Rectangle2D rClip = (Rectangle2D) usrClip;
int x0 = (int) Math.ceil(rClip.getMinX() - 0.5);
int y0 = (int) Math.ceil(rClip.getMinY() - 0.5);
int x1 = (int) Math.ceil(rClip.getMaxX() - 0.5);
int x1 = (int) Math.ceil(rClip.getMaxY() - 0.5);
clipRegion = devClip.getIntersectionXYXY(x0, y0, x1, y1);
Some work and testing should be done to see what happens when any of the
min/max/xy coordinates overflow an integer, but that would be the basic
gist of what we should be doing in that case in validateCompClip()...
...jim
On 07/26/2016 02:50 PM, Sergey Bylokhov wrote:
On 13.07.16 5:42, Jim Graham wrote:
What does the compClip end up being in that case?
BufferedImage bi = new
BufferedImage(10,10,BufferedImage.TYPE_INT_ARGB);
SunGraphics2D graphics = (SunGraphics2D) bi.createGraphics();
graphics.scale(1.5, 1.5);
graphics.setClip(1, 1, 0, 0);
System.out.println("clip = " + graphics.getClip().getBounds2D());
System.out.println("clip.isEmpty = " +
graphics.getClip().getBounds2D().isEmpty());
boolean hit = graphics.hitClip(1, 1, 1, 1);
System.out.println("hit = " + hit);
System.out.println("compClip = " + graphics.getCompClip());
System.out.println("compClip.isEmpty = " +
graphics.getCompClip().isEmpty());
The output:
clip = java.awt.geom.Rectangle2D$Double[x=1.0,y=1.0,w=0.0,h=0.0]
clip.isEmpty = true
hit = true
compClip = Region[[1, 1 => 2, 2]]
compClip.isEmpty = false
So the graphics.getClip() returns empty clip, but compClip is not empty.
It seems this occurs when we intersect usrClip
=[x=1.5,y=1.5,w=0.0,h=0.0] and devClip=Region[[0, 0 => 10, 10]] because
result is clipRegion=Region[[1, 1 => 2, 2]]
The code in question is:
protected void validateCompClip() {
.....
clipRegion = devClip.getIntersection(usrClip.getBounds());
.....
}
The usrClip.getBounds() method will change [x=1.5,y=1.5,w=0.0,h=0.0] to
[x=1,y=1,w=1,h=1], so the empty Rectangle2D became non-empty.
...jim
On 7/4/16 1:12 AM, Sergey Bylokhov wrote:
On 01.07.16 2:49, Jim Graham wrote:
How is it returning true? If the clip really is empty, then
intersectsQuickCheck() should never return true. Or are you saying
that
an empty clip shape produces a non-empty composite clip region?
This code will test such situation:
BufferedImage bi = new
BufferedImage(10,10,BufferedImage.TYPE_INT_ARGB);
Graphics2D graphics = bi.createGraphics();
graphics.scale(1.5, 1.5);
graphics.setClip(1, 1, 0, 0);
System.out.println("empty = " +
graphics.getClip().getBounds2D().isEmpty());
boolean hit = graphics.hitClip(1, 1, 1, 1);
System.out.println("hit = " + hit);
if "graphics.scale(1.5, 1.5);" will be removed then correct false will
be printed. In both cases the clip will be empty
but hitCLip will return different result in case of scaled SG2D.
...jim
On 06/30/2016 10:02 AM, Sergey Bylokhov wrote:
It looks strange that the empty clip became "non-empty"(at least
hitClip
reports this) when we apply transform to it, no? I guess that at the
beginning of hitClip() we should check that the clip.isEmpty(), and we
should return "false" in this case(I think this is not strictly
related
to this bug)?
On 24.06.16 1:14, Jim Graham wrote:
Think of this method as asking:
I don't want you to waste a lot of time, but tell me if it is silly
for
me to even consider rendering something with these bounds.
And the answer is either "Oh, yeah, it is inconceivable that those
bounds would be rendered", or "Not sure, maybe, just render it and
see". There may be some cases where it knows "I know for sure that
lots
of stuff will render through the clip", but that is not where the
divining line is here in terms of when the answer becomes true - it
becomes true when there is a chance that it might render something.
Arguably, the user-space comparison against the user-space clip
that you
added here can never be accurate even if you allow for "false
positives". The clip is rasterized and whole pixels are chosen
based on
various criteria that affect clip rasterization. Thus, while the
theoretical clip is at N.5, our rasterization choice has us render
anything that hits pixel N, even if the contribution of the rendered
primitive is only for the first half of N. That pixel might be
rendered
if anything hits any part of it, depending on what rendering
operation
is being done.
So, your "fix" actually breaks the functionality because it is quite
possible that something with a bounding box that stops before N.5
might
affect pixel N and cause it to be rendered even though your new
answer
suggested that it wouldn't happen. Your code might actually cause a
bug, not fix one.
(There are also some potential theoretical failures related to how AA
and STROKE_CONTROL might perturb our rendering, effects which are
ignore
by the current implementation of hitClip(), however I believe that
all
of those effects fit within the current implementation - it's just
that
I don't think anyone has ever proven this, or written an exhaustive
test
suite to verify that none of our rendering hints can perturb
rendering
by enough to create some surprises here...)
...jim
On 6/23/16 3:00 PM, Jim Graham wrote:
Since "return true" would be a compliant implementation of
Graphics.hitClip(), this is not a bug...
Read the documentation, it is allowed to use fast math that can
return
true when technically the answer is false...
...jim
On 6/23/16 5:04 AM, Alexandr Scherbatiy wrote:
Hello,
Could you review the fix:
bug: https://bugs.openjdk.java.net/browse/JDK-8160124
webrev: http://cr.openjdk.java.net/~alexsch/8160124/webrev.00
Let's set the clip [x=5, y=5, width=5, height=5] to a graphics
and
call the hitClip() with the passed rectangle [x=0,
y=0, width=5, height=5].
The result is false for the graphics with scale 1 and true if the
scale is floating point 1.5.
This is because the transformed clip which has floating point
bounds [7.5, 7.5, 7.5, 7.5] for the scale 1.5 has bounds
with rounded down upper-left and rounded up lower-right corners
[7,
7, 8, 8] which now intersects with the transformed
rectangle [0, 0, 7.5, 7.5].
The proposed fix adds additional check for the user clip and the
user rectangle intersection if the intersection with
the region clip passes.
Thanks,
Alexandr.