JavaFX clipping produces a 'lottery scratch ticket'-Effect

I started playing with JavaFX GraphicsContext. Especially the Clipping part is interesting to me.

so i tried to create some graphics and create a clipping mask for it (a simple rectangle, which moves around)

but i noticed some strange behaviour with it (not sure if this is a bug or due incorrect code useage)

below you can find an example application to show the issue.

Description what i expected from my code: white Canvas with a MAGENTA rectangle with text, which is only visible above the magenta (though it is drawn across)

actually this is exactly what you see first!

when you move the Application window, the MAGENTA rectangle moves around (as expected)! but the ANTIQUEWHITE fill gets visible (which i never expected) and any area which was ever covered with MAGENTA is now visible (no clipping)

ANTIQUEWHITE and MAGENTA stuff, is used to make it more obvious something is going wrong. as the whole canvas is cleared on beginning and only one clipping is done, it should not be a problem on repaint (or paint over an old drawing)

start the Application and move it around to see the 'lottery scratch ticket' effect

public class ClippingExampleApp extends Application {

    private boolean clip = true;
    private static Bounds clippingArea = new BoundingBox(100, 50, 300, 300);

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

    @Override
    public void start(Stage primaryStage) {
        primaryStage.setTitle("Clipping Test App");
        primaryStage.setX(100);
        primaryStage.setY(50);

        Group root = new Group();
        Canvas canvas = new Canvas(640, 480);
        root.getChildren().add(canvas);
        primaryStage.setScene(new Scene(root));

        ChangeListener<Number> updateBounds = new ChangeListener<Number>() {

            @Override
            public void changed(ObservableValue<? extends Number> arg0, Number arg1, Number arg2) {
                clippingArea = new BoundingBox(primaryStage.getX(), primaryStage.getY(), 300, 300);
                draw(canvas, clip);
            }
        };
        primaryStage.yProperty().addListener(updateBounds);
        primaryStage.xProperty().addListener(updateBounds);
        primaryStage.widthProperty().addListener(updateBounds);
        primaryStage.heightProperty().addListener(updateBounds);

        primaryStage.show();
        clippingArea = new BoundingBox(primaryStage.getX(), primaryStage.getY(), 300, 300);
        draw(canvas, clip);
    }

    private static void draw(Canvas canvas, boolean clip) {
        GraphicsContext gc = canvas.getGraphicsContext2D();
        // CLEAR THE COMPLETE CANVAS
        gc.clearRect(0, 0, canvas.getWidth(), canvas.getHeight());
        gc.save();
        if (clip) {
            // clipping rect
            gc.rect(clippingArea.getMinX(), clippingArea.getMinY(), clippingArea.getWidth(), clippingArea.getHeight());
            gc.clip();

            // fill the whole background (this should only affect the clipped
            // area 
            gc.setFill(Color.ANTIQUEWHITE);
            gc.fillRect(0, 0, canvas.getWidth(), canvas.getHeight());
            // this should overlap the Color.ANTIQUEWHITE - so no ANTIQUEWHITE is visible
            gc.setFill(Color.MAGENTA);
            gc.fillRect(clippingArea.getMinX(), clippingArea.getMinY(), clippingArea.getWidth(), clippingArea.getHeight());

            // finally fill the text, which sould only be visible where the magenta rect is...
            String text = "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.";
            gc.setFill(Color.BLACK);
            gc.fillText(text, 50, 100);
        }
        gc.restore();
    }
}

1 answer

  • answered 2017-10-20 10:33 James_D

    From the documentation for restore():

    Note that the current path is not restored.

    So all the gc.rect() calls you make accumulate into a single path that is used as the clip.

    If you add

    gc.beginPath();
    

    to clear the path at the beginning of the if (clip) block, you see the behavior I think you are expecting.