11

I've been googling and searching, found some some related questions/posts but none of the address my problem.

I am drawing lines directly on canvas (JavaFX) using:

gc.setStroke(color);
gc.setLineWidth(lineWidth);
gc.strokeLine(startX, startY, endX, endY);

I want 1 pixel width lines. So I set lineWidth=1. I get this: enter image description here

Note that the lines are blurred. It is not 1 pixel. I've tried to set lineWidth to 0.1 or 0.01, etc. It does not change the result.

By the way... I do not understand why this parameter is a double. I read somewhere that it has to do with DPI. But I do not understand what is the unit and how it is converted to pixels. Oracle's documentation does not help. (or I did not find the one that helps)

I'd like to get this instead:

enter image description here

This was implemented in another platform. Note that lines are sharp and have just one 1 pixel.

Chocksmith
  • 1,188
  • 2
  • 12
  • 40
  • I'm guessing it is the same issue as described in the [`Shape` documentation](http://docs.oracle.com/javase/8/javafx/api/javafx/scene/shape/Shape.html) under "interaction with coordinate systems"; though I do not know how to solve the problem in the context of a canvas. – James_D Jan 08 '15 at 18:10
  • The following post is very intriguing:https://dlemmermann.wordpress.com/2014/04/10/javafx-tip-2-sharp-drawing-with-canvas-api/ But the workaround did not work for me and I did not understood why it would. – Chocksmith Jan 08 '15 at 18:19
  • I've just found this: https://community.oracle.com/thread/2465226 It explains how it works... I will test and if it works I will answer my own question posting the solution. – Chocksmith Jan 08 '15 at 18:32
  • James_D your explanation is precise – Chocksmith Jan 08 '15 at 18:33

2 Answers2

36

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.

Here's a sample:

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

public class SharpCanvasTest extends Application {

    @Override
    public void start(Stage primaryStage) {
        Canvas sharpCanvas = createCanvasGrid(600, 300, true);
        Canvas blurryCanvas = createCanvasGrid(600, 300, false);
        VBox root = new VBox(5, sharpCanvas, blurryCanvas);
        primaryStage.setScene(new Scene(root));
        primaryStage.show();
    }
    
    private Canvas createCanvasGrid(int width, int height, boolean sharp) {
        Canvas canvas = new Canvas(width, height);
        GraphicsContext gc = canvas.getGraphicsContext2D() ;
        gc.setLineWidth(1.0);
        for (double x = sharp ? 0.5 : 0.0; x < width; x+=10) {
            gc.moveTo(x, 0);
            gc.lineTo(x, height);
            gc.stroke();
        }
        
        for (double y = sharp ? 0.5 : 0.0; y < height; y+=10) {
            gc.moveTo(0, y);
            gc.lineTo(width, y);
            gc.stroke();
        }
        return canvas ;
    }

    public static void main(String[] args) {
        launch(args);
    }
}

And the results:

enter image description here

swpalmer
  • 3,890
  • 2
  • 23
  • 31
James_D
  • 201,275
  • 16
  • 291
  • 322
  • 4
    OMG why isn't that the very first paragraph in the canvas documentation? Thanks for the information, that changes a lot. – Roland Jan 08 '15 at 19:32
  • Hi. I know it is almost 4 years from last reply, but... Can we add this in a class that extends JPanel? As example, I have a class that extends JPanel where I have to draw 5 lines, one vertical, on the middle width of the JPanel and 4 horizontal. In this way I will divide JPanel in 10 squares ... I tried your solution, but it gives me some error like "Internal graphics not initialized yet" ... Thanks – Vali Maties Sep 14 '18 at 23:17
1

Use coordinates in this notation x.5.

Look my example:

    gc.setFill(Color.BLACK);
    gc.setLineWidth(1.0);

    gc.strokeRect(50, 100, 25.0, 25.0);
    gc.strokeRect(100.5, 100.5, 25.0, 25.0);

You will get two squares, the second sharp.

Reference: https://dlsc.com/2014/04/10/javafx-tip-2-sharp-drawing-with-canvas-api/