0

I'm trying to understand how the JavaFX canvas setStroke method works. It doesn't set the color of pixels to the desired value. No problem with the setFill method though.

Canvas canvas = new Canvas(500, 500);
GraphicsContext gc = canvas.getGraphicsContext2D();
gc.setFill(Color.RED);
gc.fillRect(0, 0, canvas.getWidth(), canvas.getHeight());

int x = 10;
int y = 10;
printPixelRGB(x, y);    // displays:  red=255, green=0, blue=0      -> OK

// scenario 1: using fill-methods
gc.setStroke(Color.WHITE);
gc.strokeRect(x, y, 200, 200);
printPixelRGB(x, y);    // displays:  red=255, green=191, blue=191  -> ????

// scenario 2: using stroke-methods 
gc.setFill(Color.WHITE);
gc.fillRect(x, y, 200, 200);
printPixelRGB(x, y);    // displays:  red=255, green=255, blue=255  -> OK


private void printPixelRGB(int x, int y) {
    WritableImage snap = gc.getCanvas().snapshot(null, null);
    int color = snap.getPixelReader().getArgb(x, y);
int red = (color >> 16) & 0xff;
    int green = (color >> 8) & 0xff;
    int blue = color & 0xff;
    System.out.printf("red=%-3d, green=%-3d, blue=%-3d \n", red, green, blue);
}  // printPixelRGB()

The result from scenario 2 is as expected. The result from scenario 1 on the other hand is very strange: the pixel is not completely white!! How come?

How can I fix this?

Thank you

ChrisPeeters
  • 153
  • 2
  • 13
  • It may be anti-aliasing, specified by `clip()`; alternatively, try `BufferedImage`, shown [here](https://stackoverflow.com/a/45685372/230513). – trashgod Jan 30 '19 at 11:08

1 Answers1

2

This is the effect of anti-aliasing.

The pixel is not 100% covered by the line. Therefore the resulting color is interpolated between the pixel color before the drawing operation and the stroke color.

The following code allows you to observe the effect:

@Override
public void start(Stage primaryStage) {
    Canvas canvas = new Canvas(800, 300);

    GraphicsContext gc = canvas.getGraphicsContext2D();
    gc.setFill(Color.RED);
    gc.fillRect(0, 0, 800, 300);

    gc.setStroke(Color.WHITE);

    final double dx = 0;

    for (double x = 10, s = 1; x < (800 - 40); s++, x += 50) {
        gc.setLineWidth(s);
        gc.strokeLine(x + dx, 0, x + dx, 300);
    }

    WritableImage snap = canvas.snapshot(null, null);
    PixelReader reader = snap.getPixelReader();

    for (int x = 10; x < (800 - 40); x += 50) {
        Color color = reader.getColor(x, 150);
        System.out.printf("red=%-3d, green=%-3d, blue=%-3d\n", (int) (color.getRed() * 0xFF),
                (int) (color.getGreen() * 0xFF), (int) (color.getBlue() * 0xFF));
    }

    primaryStage.setScene(new Scene(new StackPane(canvas)));
    primaryStage.show();
}

The first line drawn covers the x interval of [10-lineWidth/2, 10+lineWidth/2] = [9.5, 10.5]. The column with index 10 is only half covered which is why the resulting color is ((255, 0, 0) + (255, 255, 255)) / 2 = (255, 127, 127) (already rounded to integral values)

Modify dx to cover the column completely and you get the stroke color:

final double dx = 0.5;

The pixel is covered completely by the rectangle you fill in your code and the color is set to the fill color for this reason. Add 0.5 to one of the starting coordinates of the rectangle you fill, and you'll observe a similar effect as with the stroke.

fabian
  • 80,457
  • 12
  • 86
  • 114