How to Draw a Crisp, Opaque Hairline in Javafx 2.2

How to draw a crisp, opaque hairline in JavaFX 2.2?

You can use a Region subclass, such as a Pane for your Parent, with snapToPixel set to true.

Additionally, refer to the Node documentation on the co-ordinate system.

At the device pixel level, integer coordinates map onto the corners
and cracks between the pixels and the centers of the pixels appear at
the midpoints between integer pixel locations. Because all coordinate
values are specified with floating point numbers, coordinates can
precisely point to these corners (when the floating point values have
exact integer values) or to any location on the pixel. For example, a
coordinate of (0.5, 0.5) would point to the center of the upper left
pixel on the Stage. Similarly, a rectangle at (0, 0) with dimensions
of 10 by 10 would span from the upper left corner of the upper left
pixel on the Stage to the lower right corner of the 10th pixel on the
10th scanline. The pixel center of the last pixel inside that
rectangle would be at the coordinates (9.5, 9.5).

Also see the Shape documentation:

Most nodes tend to have only integer translations applied to them and
quite often they are defined using integer coordinates as well. For
this common case, fills of shapes with straight line edges tend to be
crisp since they line up with the cracks between pixels that fall on
integer device coordinates and thus tend to naturally cover entire
pixels. On the other hand, stroking those same shapes can often lead
to fuzzy outlines because the default stroking attributes specify both
that the default stroke width is 1.0 coordinates which often maps to
exactly 1 device pixel and also that the stroke should straddle the
border of the shape, falling half on either side of the border. Since
the borders in many common shapes tend to fall directly on integer
coordinates and those integer coordinates often map precisely to
integer device locations, the borders tend to result in 50% coverage
over the pixel rows and columns on either side of the border of the
shape rather than 100% coverage on one or the other. Thus, fills may
typically be crisp, but strokes are often fuzzy.

Two common solutions to avoid these fuzzy outlines are to use wider
strokes that cover more pixels completely - typically a stroke width
of 2.0 will achieve this if there are no scale transforms in effect -
or to specify either the StrokeType.INSIDE or StrokeType.OUTSIDE
stroke styles - which will bias the default single unit stroke onto
one of the full pixel rows or columns just inside or outside the
border of the shape.

So, if you leave your Nodes in a Group or Region which does not snapToPixel, you can follow the above instructions from the Shape documentation.

Here is some sample code:

import javafx.application.Application;
import javafx.scene.*;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Line;
import javafx.scene.shape.LineBuilder;
import javafx.scene.shape.StrokeType;
import javafx.scene.text.Text;
import javafx.stage.Stage;

/** http://stackoverflow.com/questions/11886230/how-to-draw-a-crisp-opaque-hairline-in-javafx-2-2 */
public class LineWidths extends Application {
public static void main(String[] args) { launch(args); }

@Override public void start(Stage stage) {
Line fuzzyline = LineBuilder.create()
.startX(5).startY(50)
.endX(90).endY(50)
.stroke(Color.BLACK).strokeWidth(1)
.build();
Line hairline = LineBuilder.create()
.startX(4.5).startY(99.5)
.endX(89.5).endY(99.5)
.stroke(Color.BLACK).strokeWidth(1)
.build();
Line fatline = LineBuilder.create()
.startX(5).startY(150)
.endX(90).endY(150)
.stroke(Color.BLACK).strokeWidth(1).strokeType(StrokeType.OUTSIDE)
.build();
Pane snappedPane = new Pane();
Line insideline = LineBuilder.create()
.startX(5).startY(25)
.endX(90).endY(25)
.stroke(Color.BLACK).strokeWidth(1)
.build();
snappedPane.setSnapToPixel(true);
snappedPane.getChildren().add(insideline);
snappedPane.setPrefSize(100, 50);
snappedPane.relocate(-0.5, 174.5);

stage.setScene(
new Scene(
new Group(
fuzzyline, hairline, fatline, snappedPane,
new Text(10, 40, "fuzzyline"),
new Text(10, 90, "hairline"),
new Text(10, 140, "fatline"),
new Text(10, 190, "snappedPane")
), 100, 250
)
);
stage.show();
}
}

Line Type Sample

JavaFX : draw sharp thin lines

If you use StrokeType.CENTERED, and start the x/y values on a half unit, then the lines appear to be hairlines to me.

public void start(Stage primaryStage) {
try {
BorderPane root = new BorderPane();
root.setSnapToPixel(true);
Scene scene = new Scene(root, 400, 400);

Line line = new Line();
Line line2 = new Line();

line.setStartX(0.5);
line.setEndX(100.5);
line.setStartY(30.5);
line.setEndY(30.5);
line.setStrokeWidth(1.0);
line.setStrokeType(StrokeType.CENTERED);
line.setStroke(Color.BLACK);

line2.setStartX(50.5);
line2.setEndX(200.5);
line2.setStartY(100.5);
line2.setEndY(100.5);
line2.setStrokeWidth(1.0);
line2.setStrokeType(StrokeType.CENTERED);
line2.setStroke(Color.BLACK);

root.getChildren().addAll(line, line2);

primaryStage.setScene(scene);
primaryStage.show();
} catch(Exception e) {
e.printStackTrace();
}
}

My guess is that unit numbers in JavaFX are for the corners of the pixels, so specifying location + 0.5 places the line in the middle of said pixel.

JavaFx thin lines color distortion

You can prevent it in 2 ways:

1) Use setSnapToPixel

final AnchorPane root = new AnchorPane();
root.setSnapToPixel(true);

2) Or, define lines' startY and endY values as half:

final Line line1 = new Line(20, 10.5, 300, 10.5);
final Line line2 = new Line(250, 10.5, 400, 10.5);

For the technical details of these I will leave to jewelsea's great answers:

What are a line's exact dimensions in JavaFX 2?

How to draw a crisp, opaque hairline in JavaFX 2.2?

Line3D in JavaFX

JavaFX 2.2 3D support is rather rudimentary. You can create 2D shapes, then apply transforms to them to get them into 3D space, but you are not directly defining the shapes in 3D coordinate. So to get a 3D line you can define 2D line, then rotate it around the x and y axes and set a z coordinate on the node.

Future JavaFX versions will provide 3D specific shapes and meshes so that use of 3D features is more natural. For instance you could use a thin cylinder to represent a 3D line.

General mechanism to snap to pixel and avoid blurryness

Don't snap the fill. The question I linked to explains that lines are drawn on the pixels(through the middle - which takes a whole pixel), which means +0.5 since the coords are between pixels.

Fills need to be drawn over the whole pixel to the space between them.

Sample Image

public HealthBar() {

double height = 10;

double outerWidth = 60;
double innerWidth = 40;

double x=10,y=10;//away from pane edge just to see better

outerHealthRect = new Rectangle( snap(x), snap(y), outerWidth, height);
outerHealthRect.setStroke(Color.BLACK);
outerHealthRect.setFill(Color.WHITE);

//just move green bar right a bit to see edges
//and move it down 1 pix so it's not over the black edge
//this means height is -1 as well
innerHealthRect = new Rectangle( x+11, y+1, innerWidth, height-1);
innerHealthRect.setFill(Color.LIMEGREEN);
//no need to draw transparent anything

getChildren().addAll( outerHealthRect);
getChildren().addAll( innerHealthRect);

}


Related Topics



Leave a reply



Submit