Thursday, February 11, 2010

How to Render a JavaFX Node into an Image

WARNING: This blog entry was imported from my old blog on (which used different blogging software), so formatting and links may not be correct.

Sometimes you want to render a Node tree into an Image. For example, you may want to do it for performance reasons (such as this example), or you may want to create thumbnail views, or perhaps you want to create a nice drag & drop effect where there is a ghost image of the object being manipulated showing both the current position (the real node) and its target position (its image).

There are some solutions posted for this, but they rely on pretty deep ties to AWT and scenegraph internals. There isn't a way to do it using public APIs yet, but here is a simpler solution. This uses a method on Scene to render the scene to an image, so the trick is to temporarily remove the Node from its current location in the node hierarchy, place it in a new Scene, render the image and put the Node back. This is all pretty simple - you just have to take care of some minor details - like the fact that the scene render will render from (0,0) to the size of the scene bounds, rather than from minX to maxX and minY to maxY. So, we have to add a reverse Translation to place the image back. There is also a problem that the scene render will truncate the bottom and rightmost pixels, so we need to explicitly set the scene size and padd it by 1. And finally, with CSS in the picture we want to duplicate the stylesheet reference from the Node's scene in the render scene, and we also need to preserve the style context. (There are more issues here around CSS; it is a key new feature in JavaFX 1.3 and central to the controls, and there are implementation aspects here for when the CSS phase is running, and it turns out currently the layout styles stay intact - they only get recomputed on the next layout pulse - so this all works beautifully. But the implementation is changing a lot these days so this may need tweaking before this ink dries!)

The only limitation here is that we need to be able to move the Node to render it - which means you cannot render any Nodes that are bound to its parent, e.g. you have a parent group whose content property is a bound sequence including the Node.

Note - NOT official APIs - much cleaner.
Couple of necessary tricks: must set scene bounds, and shift it to (0,0); scale

def platformImage = scene.renderToImage(null);
image = Image.impl_fromPlatformImage(platformImage);

Anyway, I wrote some code to do this today. This doesn't only render the node; it also has the possibility to scale the image if its width or height exceeds a certain number. We do that because in the authoring tool we show a little preview of the selected component, and this uses the render to node facility.
(Screenshot here)

No comments:

Post a Comment