Ah, I see.

There are a lot of mistaken assumptions in the rendering there. It's not just line thickness, it is assumptions about stroke control and positioning of strokes.

The biggest issue seems to be that developers (including our own internal Swing developers who heard this lecture from me decades ago, but ignored it because it was inconvenient and not yet relevant at the time) who use the integer-based draw commands assume that their lines are centered on the pixel that they are naming. In other words "if I draw a line along x=5 then I am telling the system to fill all the pixels with an X coordinate of 5", but that is not what that drawing request is asking for. The coordinates are at pixel edges and it is an artifact of our fill/stroke rules and stroke control hinting that has made this work out at 1:1 scaling. As soon as you scale you can see the issues. We used to only scale for printing, but now we are starting to scale for the screen.

g.drawRect(0,0,w-1,h-1) is a completely disfunctional way to outline a component with any settings other than 1:1 coordinates and STROKE_CONTROL on. I've been mentioning this for years (going on decades now), but Swing was addicted to that boilerplate for drawing a border. Thankfully, FX and its CSS-focused skinning has gone with a different mechanism (primarily most FX components are outlined using successive fills - optimized in implementation to avoid overdrawing - rather than using strokes based on mistaken assumptions about where the line is drawn).

In particular, the line in that example g.drawRect(0,0,w-1,h-1) technically 
occurs at the following coordinates:

outer part of outline goes from 0.0-0.5,0.0-0.5 to w-1+0.5,h-1+0.5
   (i.e. -0.5,-0.5 to w-0.5,h-0.5)
inner part of outline goes from 0.0+0.5,0.0+0.5 to w-1-0.5,h-1-0.5
   (i.e. +0.5,+0.5 to w-1.5,h-1.5)

At a high enough scale you can see the stroke starting to separate from the fill on the right and bottom edges where the closest it gets to the edge is 0.5 scaled coordiantes. That rounds to 0 for 1:1 and with the biasing from STROKE_CONTROL, but at higher resolutions it becomes a non-zero number of pixels.

Even at 1:1 scale if you follow our filling rules explicitly (which would require setting STROKE_CONTROL to PURE) then that would fill the rows at y=-1 and y=h-2 and the columns at x=-1 and x=w-2, which is not what you want at all. Setting STROKE_CONTROL on allows us to bias the location of the stroke a little and it ends up filling the correct pixels as a side effect (though STROKE_CONTROL is meant to achieve consistency in strokes, it also ends up shifting the line by enough that the fill rules choose different pixels in this case). The STROKE_CONTROL=on version of the path is assumed to be (0.25,0.25,w-0.75,h-0.75) because we snap all coordinates in a stroke-controlled non-AA path to the nearest location that is 0.25,0.25 within a pixel. This snapping to a consistent sub-pixel location biasing at 0.25 was chosen because line widths grow more evenly at that offset, but it offsets the outline enough so that the outline considered for filling becomes:

outer part of outline goes from 0.25-0.5,0.25-0.5 to 0.25+w-1+0.5,0.25+h-1+0.5
   (i.e. -0.25,-0.25 to w-0.25,h-0.25)
inner part of outline goes from 0.25+0.5,0.25+0.5 to 0.25+w-1-0.5,0.25+h-1-0.5
   (i.e. +0.75,+0.75 to w-1.25,h-1.25)

which renders the rows columns at 0 and wh-1 at a 1:1 scale using our fill rules. Note that if you scale high enough you can still see separation between fill and outline, but the gap is only 0.25 scaled coordinates so it would take a scale of at least 4x to see it.

The technically accurate way to render the first/last pixel/1-unit-coordinate boundary of a component would be to drawRect(0.5,0.5,w-1,h-1) (with no stroke control set) which would place the rectangle at the following coordinates:

outer part of outline goes from 0.5-0.5,0.5-0.5 to 0.5+w-1+0.5,0.5+h-1+0.5
   (i.e. 0,0, to w,h)
inner part of outline goes form 0.5+0.5,0.5+0.5 to 0.5+w-1-0.5,0.5+h-1-0.5
   (i.e. 1,1 to w-1,h-1)

which completely encloses the first/last row/column of pixels on a 1:1 coordinate system and accurately covers the first/last N pixels in any arbitrary N-scaled coordinate system. The rounding for scales like 1.5 still might not work out the way you wanted, but at least the exact geometry is consistent with respect to the placement of pixels. With AA you will get a consistent border all around if w,h are snapped to a pixel size, but with non-AA then rounding error might lead to an extra pixel on one pair of sides. I haven't done the analysis to see how the above technique would be affected by STROKE_CONTROL because really what you are looking for is to render the a consistent edge around the component and so successive fills as is done with most of our CSS skinning files in FX is a better solution overall. There are just too many considerations in filling to make it worthwhile for simple rectangular regions...

                                ...jim

On 10/4/16 1:46 PM, Anton Tarasov wrote:
On 10/4/2016 11:30 PM, Jim Graham wrote:
I wasn't familiar with the test code for this particular case.  Is it in a bug 
report somewhere?
Jim, I'm re-attaching it (it was in my first e-mail).

Thanks,
Anton.


            ...jim

On 10/4/16 1:01 PM, Anton Tarasov wrote:
Also, the problem with primitives rendering 
(http://cr.openjdk.java.net/%7Eant/hidpi_pics/Scaling-150-percent.png) is
still there. But it seems to relate to line-thikness (border-thickness) 
rounding inaccuracy. What we can do with
that?...

Reply via email to