3

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.

illfang
  • 65
  • 1
  • 4
  • What you'd like to achieve, how does it look in an image example? Either you do have the Path node and set the opacity or you don't and you redraw the path with background color. – Roland Mar 04 '15 at 05:13
  • I have edited the question with more details to try and make the questions clearer – illfang Mar 04 '15 at 05:55
  • But what do you expect when you paint on the canvas with a transparent color? It won't have any effect. Just paint it over with the background color. I suspect what you want is different layers. That's not what a single Canvas is. – Roland Mar 04 '15 at 06:19
  • Please take a look at the article about [How to work with Canvas](http://docs.oracle.com/javafx/2/canvas/jfxpub-canvas.htm). – Roland Mar 04 '15 at 06:27
  • The canvas is just one Node in the hierarchy and I would like to set some pixels back to transparent to make the lower layers visible in this area. As this is a common operation in other Canvas implementations I'm curious if there is no way in javafx to achieve that. e.g. [HTML5 Canvas](http://www.w3schools.com/tags/playcanvas.asp?filename=playcanvas_globalcompop&preval=destination-out) e.g [Android Canvas](http://developer.android.com/reference/android/graphics/PorterDuff.Mode.html#SRC) – illfang Mar 04 '15 at 06:30

2 Answers2

2

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

Roland
  • 18,114
  • 12
  • 62
  • 93
  • Thank you for your detailed answer. As your answer showed a different approach of solving the problem I will accept your answer, as it seems like no one knows a way to implement this with BlendModes or similar techniques which would surely require less resources. Thank you. – illfang Mar 16 '15 at 07:23
0

After seeing several posts from 2015 like this one, and older, I find it amazing that this is still not something that has been added to javaFX. From what i could tell there was a "DST-OUT" blending option in AWT, so then why not in javaFX?!

Anyways.. I came back here after figuring out my own solution, so i could share my solution and hopefully help the next restless soul that comes by here in search of answers.

My specific need was to make a canvas transparent in an area based on an SVGPath. So here is my solution:

private void clearPath(GraphicsContext gc, SVGPath path) {
    int xstart = (int) path.getLayoutX();
    int xend = (int) (xstart + path.getLayoutBounds().getMaxX());
    int ystart = (int) path.getLayoutY();
    int yend = (int) (ystart + path.getLayoutBounds().getMaxY());

    PixelWriter pw = gc.getPixelWriter();
    for (int x = xstart; x <= xend; x++) {
        for (int y = ystart; y <= yend; y++) {
            if(path.contains(new Point2D(x, y))) {
                pw.setColor(x, y, Color.TRANSPARENT);
            }
        }
    }
}
Raj Paliwal
  • 943
  • 1
  • 9
  • 22
berg3390
  • 11
  • 2