4

I'm developing a little graphic engine using Canvas in JavaFX. In some point I had to render an off screen image, and then print it on my primary canvas using its GraphicContext.
I'm using this code right now:

private Canvas offScreenCanvas;
private GraphicsContext offScreenGraphic;
private SnapshotParameters parameters;
private WritableImage offScreenImage;

[...]

offScreenCanvas = new Canvas(WIDTH, HEIGHT);
offScreenGraphic = offScreenCanvas.getGraphicsContext2D();

parameters = new SnapshotParameters();
parameters.setFill(Color.TRANSPARENT);

[...]

offScreenImage = offScreenCanvas.snapshot(parameters, offScreenImage);
graphic.setGlobalBlendMode(BlendMode.HARD_LIGHT);
graphic.drawImage(offScreenImage, 0, 0);
graphic.setGlobalBlendMode(BlendMode.SRC_OVER);

My problem is the method snaphot() takes too much time, ~14ms, in each execution. I need to update the canvas at least at 60fps, so this consumes practically all the time I have to draw.

Is there another way to get an Image or WritableImage from a canvas? Maybe another different process?

cjpelaez
  • 141
  • 9
  • Despite `setFill(Paint)`, what else are you doing with `SnapshotParameters`? AFAIK you can adapt the snapshot area. You don't have to use the whole node, maybe this gives you a little boost. – beatngu13 Oct 11 '16 at 23:36
  • Thanks for your idea. In fact I'm not using `SnapshotParameters ` for nothing more, so I deleted it. I get the same image result. About the node size, the canvas should has the same size than the node, so I think that if I specify the snapshot area, I should have the same performance. Anyway, I'll try that. – cjpelaez Oct 12 '16 at 15:25
  • Just to post what my last test results are, I get the same performance if I take I snapshot of the whole canvas, or only a part of it. It looks like it doesn't matter which region I capture, the process takes time to be completed. – cjpelaez Oct 12 '16 at 15:35
  • I thought that the off-screen area is smaller than the actual node size. But yeah, if both are the same, that makes no difference at all. Sorry for that. – beatngu13 Oct 12 '16 at 15:49
  • Have you tried wiith the PixelWriter/Reader? https://docs.oracle.com/javase/8/javafx/api/javafx/scene/image/PixelWriter.html – vl4d1m1r4 Oct 15 '16 at 18:09
  • Yes, I tried. Unfortunately `Canvas` and its `GraphicsContext` only have `PixelWriter`. The idea would be to use a `PixelReader` to write on a Image's `PixelWriter`, but I can't find a way to do it. That's could be a good trick to avoid the `snapshot()` method. Thanks for your idea. – cjpelaez Oct 27 '16 at 19:43
  • It is unlikely to be the actual pixel copy operation in the snapshot call which is taking the time. The JavaFX canvas rendering is deferred even though it appears immediate. It buffers up the drawing calls in a queue and replays them when a [pulse](https://docs.oracle.com/javase/8/javafx/get-started-tutorial/jfx-architecture.htm#A1107438) occurs or when it must render (for example to generate a snapshot). So, if you perform a lot of drawing operations, most of the time taken by snapshot is probably the element rendering time, not the pixel copy to image time. – jewelsea Dec 16 '16 at 20:16
  • Note, you can use the [async version of snapshot](https://docs.oracle.com/javase/8/javafx/api/javafx/scene/Node.html#snapshot-javafx.util.Callback-javafx.scene.SnapshotParameters-javafx.scene.image.WritableImage-) if you would like to do other things while the snapshot is being processed. – jewelsea Dec 16 '16 at 20:18

1 Answers1

3

This is another method to obtain a visual equivalent result, without reduce performance.

I have used java.awt clases, instead of JavaFX clases. The creation of a java.awt.image.BufferedImage offers the possibility to get a java.awt.Graphics2D where you can draw using other methods. The main problem here is that draw big images consumes a lot of time using this libraries, but you can create a scaled image. In my case, I have created a quarter-size BufferedImage, and I have drawn all the objects using that scale factor.

At the end of the draw process, just convert the BufferedImage, to a javafx.scene.image.Image, using:

 SwingFXUtils.WritableImage toFXImage(BufferedImage bimg, WritableImage wimg);

Then print it on the main canvas using:

 graphic.drawImage(Image image, 0, 0, WIDTH, HEIGHT);

To fill all the canvas with the image.

Finally, the scale factor is configurable, so if you need a detailed image, just use a higher value. For me, a 25-percent-size image is enough because I am drawing gradients. Now, it takes 1-3ms to draw the image, this is much better than before.

Hope it helps someone.

cjpelaez
  • 141
  • 9