[OpenJDK 2D-Dev] HiDPI support issues on Windows

Anton Tarasov anton.tarasov at jetbrains.com
Mon Oct 17 10:03:35 UTC 2016

Thanks a lot, Jim! This sheds light on the subject and really sounds 


On 10/6/2016 9:31 PM, Jim Graham wrote:
> 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?...

More information about the 2d-dev mailing list