• Home
  • Events
  • About
  • Take care of the JavaFX scene graph…

    In JavaFX it’s easy to create stunning graphical effects which have bee hard to implement in Java Swing before. In principle this is a good thing but on the other hand this could also lead to problems if you don’t take care of the scene graph that handles all these nodes that you create.

    To give you an idea of what I’m talking about let us take a look at an example. Let’s say I would like to create a set of particles (in this case bubbles) that should move around on the screen. Sounds easy to realize and in fact it is really easy to do in JavaFX.

    First of all I have to create some graphical prototype to get a rough idea of how it should look like in the end.

    The prototype

    Prototype

    So the bubbles should have a random size and the opacity should also vary a bit from bubble to bubble.

    Now that we now how it should look like in the end we could start to code, first of all we will need a method that creates one bubble for us…

     

    The pure Node approach

    public final Group createBubbleNode(final double SIZE) {
      final double WIDTH  = SIZE;
      final double HEIGHT = SIZE;

      final Group BUBBLE = new Group();  

      final Shape IBOUNDS = new Rectangle(0, 0, WIDTH, HEIGHT);
      IBOUNDS.setOpacity(0.0);
      BUBBLE.getChildren().add(IBOUNDS);

      final Circle MAIN      = new Circle(0.5 * WIDTH, 0.5 * HEIGHT, 0.48 * WIDTH);
      final Paint  MAIN_FILL = new LinearGradient(0.5 * WIDTH, 0.02 * HEIGHT,
                                                  0.50 * WIDTH, 0.98 * HEIGHT,
                                                  false, CycleMethod.NO_CYCLE,
                                                  new Stop(0.0, Color.color(1, 1, 1, 0.0470588235)),
                                                  new Stop(0.85, Color.color(1, 1, 1, 0.0470588235)),
                                                  new Stop(0.851, Color.color(1, 1, 1, 0.0745098039)),
                                                  new Stop(1.0, Color.color(1, 1, 1, 0.8980392157)));
      MAIN.setFill(MAIN_FILL);
      MAIN.setStroke(null);

      final Circle FRAME      = new Circle(0.5 * WIDTH, 0.5 * HEIGHT, 0.48 * WIDTH);
      final Paint  FRAME_FILL = new RadialGradient(0, 0,
                                                   0.5 * WIDTH, 0.5 * HEIGHT,
                                                   0.48 * WIDTH,
                                                   false, CycleMethod.NO_CYCLE,
                                                   new Stop(0.0, Color.color(1, 1, 1, 0)),
                                                   new Stop(0.92, Color.color(1, 1, 1, 0.4549019608)),
                                                   new Stop(1.0, Color.color(1, 1, 1, 0.4980392157)));
      FRAME.setFill(FRAME_FILL);
      FRAME.setStroke(null);

      final Ellipse HIGHLIGHT      = new Ellipse(0.5 * WIDTH, 0.27 * HEIGHT,
                                                 0.36 * WIDTH, 0.23 * HEIGHT);
      final Paint   HIGHLIGHT_FILL = new LinearGradient(0.5 * WIDTH, 0.04 * HEIGHT,
                                                       0.5 * WIDTH, 0.5 * HEIGHT,
                                                       false, CycleMethod.NO_CYCLE,
                                                       new Stop(0.0, Color.color(1, 1, 1, 0.6980392157)),
                                                       new Stop(1.0, Color.color(1, 1, 1, 0)));
      HIGHLIGHT.setFill(HIGHLIGHT_FILL);
      HIGHLIGHT.setStroke(null);

      BUBBLE.getChildren().addAll(MAIN, FRAME, HIGHLIGHT);
      BUBBLE.setCache(true);

      BUBBLE.setCacheHint(CacheHint.SCALE);

      return bubble;
    }

    As you could see we create two circles (one for the background and one for the frame of the bubble) and one ellipse for the highlight on top of the bubble. We add another rectangle that defines the bounds of the bubble. This is needed because the Group node that will contain the bubble after all will calculate it’s size from the bounds of all child nodes.

    That means if we add one bubble to the JavaFX scene graph we will add five nodes.

    1. Rectangle (bounds of the bubble)
    2. Circle (background of the bubble)
    3. Circle (frame of the bubble)
    4. Ellipse (highlight of the bubble)
    5. Group (contains all mentioned nodes)

    If I would like 50 bubbles moving around this would result in adding 250 nodes to the JavaFX scene graph!

    Ok, let us check if there is a way to reduce the number of nodes…

     

    The Image approach

    Instead of creating a bubble each time we could also use an image. Because I have created the prototype anyway I have the image already, here it is…

    Bubble

    It’s a png image of the bubble with the size of 50×50 pixels. But you could also create an image in code and use the created image instead of loading an image.

    So we load the image once and use only scaled instances of this image in an ImageView instead of creating groups of nodes each time. Well that’s a nice solution but it has one drawback…the scaling of the image. In our case this is not a big deal because we only shrink the image but if you have to enlarge the image you will get artifacts that will be visible (blur).

    Ok, but what does this mean for our scene graph ?

    In this case we add one ImageView node for each bubble which leads to 50 nodes that we add to the JavaFX scene graph for our 50 bubbles. That’s not bad, we reduced the amount of bubbles by the factor of 5 and gain some performance because moving 50 nodes is less expensive than moving 250 nodes. You won’t realize the difference between 50 and 250 nodes but I made some tests with this example and if you use 5000 bubbles you will easily see the difference between 5000 nodes and 25000 nodes. 

    That means you should always take care about the nodes that you add to the scene graph because you never know where your code will be used and how many nodes the final scene graph will contain.

    But wait…is there maybe another way to even more reduce the number of nodes in the scene graph…

     

    The Canvas approach

    Since JavaFX 2.2 there is the Canvas node available which is in principle a node that offers you a canvas where you could draw something without having nodes like in the scene graph. If you are familiar with HTML5 Canvas it won’t be a big deal for you to use the JavaFX Canvas node directly because it offers in principle the same as the HTML5 Canvas. There are differences but now is not the time to explain them. How could we make use of the Canvas node in our example? 

    Well we could use different ways to use the Canvas node, one would be to create the bubble as a canvas instead of loading an image. The advantage over the image approach would be that we would have not problems with the scaling of the images because we create the canvas bubble each time we need one in the appropriate size. But that’s the only advantage because we still have 50 Canvas nodes for 50 bubbles that we have to add to the JavaFX scene graph.

    The second approach is a combination of using the Canvas node and using images. Here we add one Canvas node to the JavaFX scene graph and do the complete drawing in this Canvas node. To understand the difference let me show you the code that draws one bubble image to a Canvas node.


    Canvas canvas = new Canvas(500, 500);

    GraphicsContext ctx = canvas.getGraphicsContext2D();

    ctx.setFill(Color.BLACK);
    ctx.fillRect(0, 0, 500, 500);    

    ctx.save();
    ctx.translate(x, y);
    ctx.scale(size, size);
    ctx.translate(image.getWidth() * (-0.5), image.getHeight() * (-0.5));
    ctx.setGlobalAlpha(opacity);
    ctx.drawImage(image, 0, 0);
    ctx.restore();

    The code above creates a Canvas object and the related GraphicsContext from that Canvas. This GraphicsContext will then be filled with black and an image will be placed on the position x,y. To get a scaled version of the image we scale the GraphicsContext, translate it to the center of the image and set the opacity of the GraphicsContext before we draw the image. The save() will save the state of the GraphicsContext and restore() will restore it to the last saved state. Like I said already, if you know HTML5 Canvas you will be able to use the JavaFX 2.2 Canvas directly.

    So, what does this mean for our example?

    We only add one Canvas node to the JavaFX scene graph, no matter how many bubbles we use in our example !!!!! 

    Nothing is for free which means if you use the canvas approach you will definitely need more cpu power because you have to handle the complete drawing by yourself (refreshing the GraphicsContext etc.).

    On the next image you see the example with 5000 bubbles using the Canvas approach and the animation was really smooth…

    120921 0001

     

    Conclusion

    So don’t get me wrong, this doesn’t mean that you should not use the nodes but you should be careful if you use nodes for complex graphic stuff or for particle systems. In those cases it’s a good idea to use the Canvas node instead. In general it’s a good advice to prerender parts of your graphics (makes most sense for graphics that won’t change very often) using the Canvas node because of it’s API. With this approach you could reduce the number of nodes in your scene graph.

    Ok, I hope this post was a bit useful for one or the other and if you have further questions do not hesitate to contact me.

    Take care of your JavaFX scene graph and keep coding…

    Share and Enjoy: These icons link to social bookmarking sites where readers can share and discover new web pages.
    • email
    • Print
    • Twitter
    • LinkedIn
    • XING
    • Facebook
    • Google Bookmarks

    1 Comment »

    1. Shovra said,

      December 13, 2015 @ 16:15

      The article was really a helpful one. Thanks a ton!

    RSS feed for comments on this post

    Leave a Comment

    Time limit is exhausted. Please reload the CAPTCHA.

    css.php