Canvas : 2 pixel thick line width

Jim Graham james.graham at
Thu Apr 2 22:49:56 UTC 2015

Hi Damien,

The main problem is that the definition of a stroked path and the 
definition of a filled path are at odds.  If the default stroke width is 
1.0 and the default stroke type is "CENTERED", then the stroke is 
centered on the outline of a path and half of it falls on either side of 
the outline.  For those defaults, if you have the coordinates at pixel 
centers then integer coordinate fills end up with fuzzy sides (and basic 
rectangle fills are the most common operation in any system) even though 
that may help stroked lines and paths.  If you have the coordinates at 
pixel edges then integer fills are nice and crisp and you then only need 
to adjust for stroked shapes.  (Note that on a retina screen with 2x 
pixels you get both crisp fills and strokes either way, so all hail the 
new HiDPI world.  ;)

For non-AA rendering you actually don't get any fuzziness since only 
whole pixels are included or not, but you do have the phenomenon of 
coordinate stability.  If you compute an outline and you are off by just 
a few rounding bits and your coordinates are at the same location as 
your sampling points then your shape shifts.  If the coordinates are 
halfway between the sampling points then a few bits of rounding will not 
affect the shape.

Our default is center of pixel sampling, as it is with most rendering 
systems, so having integer coordinates halfway between them (i.e. at the 
edges of pixels) makes sense for non-AA.

Java2D has a concept called "stroke control" which applies some rounding 
process to all coordinates so as to help them end up creating crisper 
outlines, but it has its drawbacks - particularly in the fact that it 
fights your ability to have a nice round circle or oval since this 
concept of shifting points fights the exact geometry involved in the 
control points of "perfectly round" shapes.

When we were first creating FX we decided that:

- stroke control created some problems that we'd like to avoid even if 
it does provide some security for new programmers

- the vast majority of control styling was shifting to using concentric 
filled backgrounds rather than strokes so the fact that default strokes 
can be fuzzy was a non-issue for the controls team

- we introduced the concepts of inner and outer strokes which play 
better with outlining controls than the typical graphics library's 
default of centered strokes and they have similar needs of where integer 
coordinates should be placed to avoid fuzziness as fills do

- it is time to advance this issue to an exposed one where developers 
will need to be cognizant of our rasterization rules and stroke 
geometries to render correctly.

This should be documented better, though.  That much is true...


On 4/1/15 3:14 AM, Damien Dudouit wrote:
> Hello,
> I have found the following answer :
> *Imagine each pixel as a (small) rectangle (instead of a point). The
> integer coordinates are the boundaries between pixels; so a (horizontal or
> vertical) line with integer coordinates falls "between pixels". This is
> rendered via antialising, approximating half of the line on one pixel and
> half on the other. Moving the line 0.5 pixels left or right moves it to the
> center of the pixel, getting around the issue.*
> I have done quite a bit of custom controls drawing (canvas) with swing and
> swt and it's quite a shift of approach that plain coordinates to not match
> pixels on display.
> In swing/swt, if I want to draw a rectangle from pixel (0,0) to pixel
> (2,2), ie. a square of 3x3 pixels I do :
> gc.drawRectangle(0, 0, 3, 3); // x, y, width, height
> In JavaFX I must do :
> gc.strokeRect(0.5, 0.5, 2, 2)
> Have a good day,
> Damien
> 2015-04-01 11:42 GMT+02:00 Damien Dudouit <ddudouit at>:
>> Hello,
>> I'm using a Canvas to display some content (mostly text with lines also
>> for underline).
>> I've just noticed something that is a big problem for me : with
>> line-width=1 and no scaling, actual line-width on display takes 2 pixels.
>>      Canvas canvas = new Canvas(300, 300);
>>      GraphicsContext gc = canvas.getGraphicsContext2D();
>>      gc.setStroke(Color.BLACK);
>>      gc.setLineWidth(1);
>>      gc.strokeLine(10, 10, 110, 10);
>> I'm running Win7 64bits and I have made the test with Oracle jdk8_40 and
>> jdk7_71 and the result is the same.
>> That 2 pixel thick line is not perfectly black but dark gray.
>> If I do 'gc.setLineWidth(2)', then I get a 2 pixel thick line perfectly
>> black.
>> If I do 'gc.setLineWidth(0.5)', then I get a 2 pixel thick line with a
>> lighter gray.
>> I want to display underline text in the canvas and a 2 pixel thick
>> underline looks bad.
>> Any help would be greatly appreciated.
>> Damien

More information about the openjfx-dev mailing list