Canvas : 2 pixel thick line width
james.graham at oracle.com
Thu Apr 2 22:49:56 UTC 2015
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:
> 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,
> 2015-04-01 11:42 GMT+02:00 Damien Dudouit <ddudouit at clio.ch>:
>> 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.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
>> 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.
More information about the openjfx-dev