Search code examples
javafxjavafx-8graphicscontext

Override path with transparent color in javafx Canvas/GraphicsContext


I'm wondering if there is any way in the GraphicsContext of a Canvas in JavaFX to override all pixels in a path in such a way that they become transparent again. Like the clearRect function but for any path.

I have looked at the different BlendModes and Effects but it seems like there is no way to actually override the alpha value which could be achieved by e.g

  • rendering a transparent color with the Porter/Duff Src operator
  • rendering a non transparent color with globalCompositeOperation destination-out in HTML5

I would be very glad if someone knew a way to achieve this without tessellating the path and using multiple clearRect calls. Thank you.

http://docs.oracle.com/javase/8/javafx/api/javafx/scene/canvas/GraphicsContext.html


Addition in response to the first comment: Instead of using a Path node I'm using a Canvas node to perform rendering using the GraphicsContext and thus, it is not possible to modify the color of a previously rendered path. I’m trying to do something like

Canvas canvas = …;
final GraphicsContext context = canvas.getGraphicsContext();
context.setFill(Color.BLUE);
context.fillRect(0,0,100,100);
…
…

and later on I want to reset the pixels in a path to transparent

context.setStroke(Color.TRANSPARENT);
context.beginPath();
context.moveTo(0, 0);
context.lineTo(10,10);
…
context.stroke();

However, as blending is applied this does not work. In other rendering libraries it is possible to use e.g. one of the above mentioned blending / compositing methods to achieve this but until now I was not able to identify such a functionality in javafx.


Solution

  • I haven't found a mechanism to clear a canvas depending on a Path. But here's how you could do it manually:

    • Create a mask on which you draw the path
    • Blend image and mask manually

    Example with a blue rectangle that's been applied a mask with a path:

    public class ManualBlendWithMask extends Application {
    
        public static void main(String[] args) {
            launch(args);
        }
    
        @Override
        public void start(Stage primaryStage) {
    
            Group root = new Group();
    
            // create canvas
            Canvas canvas = new Canvas(200, 200);
            canvas.setTranslateX(100);
            canvas.setTranslateY(100);
    
            // create canvas with blue rectangle
            GraphicsContext canvasGC = canvas.getGraphicsContext2D();
            canvasGC.setFill(Color.BLUE);
            canvasGC.fillRect(0, 0, canvas.getWidth(), canvas.getHeight());
    
            // create mask
            Canvas mask = new Canvas( canvas.getWidth(), canvas.getHeight());
            GraphicsContext maskGC = mask.getGraphicsContext2D();
            maskGC.setFill(Color.WHITE);
            maskGC.fillRect(0, 0, mask.getWidth(), mask.getHeight());
    
            // draw path
            maskGC.setStroke(Color.BLACK);
            maskGC.setLineWidth(20);
            maskGC.beginPath();
            maskGC.moveTo(0, 0);
            maskGC.lineTo(400, 400);
            maskGC.stroke();
    
            // get image from canvas
            WritableImage srcImage = new WritableImage((int) canvas.getWidth(), (int) canvas.getHeight());
            srcImage = canvas.snapshot(null, srcImage);
    
            // get image from mask
            WritableImage srcMask = new WritableImage((int) mask.getWidth(), (int) mask.getHeight());
            srcMask = mask.snapshot(null, srcMask);
    
            PixelReader maskReader = srcMask.getPixelReader();
            PixelReader imageReader = srcImage.getPixelReader();
    
            int width = (int) srcMask.getWidth();
            int height = (int) srcMask.getHeight();
    
            // create dest image
            WritableImage dest = new WritableImage(width, height);
            PixelWriter writer = dest.getPixelWriter();
    
            // blend image and mask
            for (int x = 0; x < width; x++) {
                for (int y = 0; y < height; y++) {
    
                    Color maskColor = maskReader.getColor(x, y);
                    Color imageColor = imageReader.getColor(x, y);
    
                    if (maskColor.equals(Color.WHITE)) {
                        writer.setColor(x, y, imageColor);
                    }
    
                }
            }
    
            // clear canvas and fill it with the blended image
            canvasGC = canvas.getGraphicsContext2D();
            canvasGC.clearRect(0, 0, canvas.getWidth(), canvas.getHeight());
            canvasGC.drawImage(dest, 0, 0);
    
            // draw background with gradient
            Rectangle rect = new Rectangle(400, 400);
            rect.setFill(new LinearGradient(0, 0, 1, 1, true, CycleMethod.REFLECT, new Stop(0, Color.RED), new Stop(1, Color.YELLOW)));
    
            // show nodes
            root.getChildren().addAll(rect, canvas);
    
            primaryStage.setScene(new Scene(root, 400, 400));
            primaryStage.show();
    
        }
    
    }
    

    Of course it would be better if someone could come up with an example using BlendModes. enter image description here