Xanatos
Xanatos

Reputation: 1636

How can I export a JavaFX node to an SVG image?

In other words, I'm trying to do something with JavaFX like what Batik allows you to do with Swing.

I want to be able to capture the appearance of an arbitrary node in my JavaFX UI, much like Node.snapshot() does, except that I need my image in a vector format like SVG, not a raster image. (And inserting a raster snapshot of my node into an SVG image is not good enough; it needs to be a proper, scalable vector image.)

This is a long-term project, so I'm even willing to go as far as implementing my own GraphicsContext, or whatever the equivalent is in JavaFX's retained mode API.

Does anyone know if there is a way to do this? Is what I'm hoping to do even possible in JavaFX?

Upvotes: 14

Views: 6454

Answers (5)

Hervé Girod
Hervé Girod

Reputation: 567

The idea is that if you are able to convert the JavaFX Nodes tree structure to a series of Graphics2D orders, then you can use Batik which has a Graphics2D driver.

The thing is that converting the JavaFX tree structure to Graphics2D orders is not as difficult as you might think (even if you process the CSS properties of the Nodes).

You should create a SVGGraphics2D from a new empty SVGDocument, for example:

Document doc = SVGDOMImplementation.getDOMImplementation().createDocument(SVGDOMImplementation.SVG_NAMESPACE_URI, "svg", null);
SVGGraphics2D g2D = new SVGGraphics2D(doc);

Then you get the root node of the Scene you want to convert, and for this node, you get the type of the Node, which can be a Shape, Control, Region, ImageView, Group, SubScene, Shape3D

Depending on each node, you can get the characteristics of the node. For example, for a Shape, if its a Line, you can drawthe line in the SVGGraphics2D. For example:

g2D.drawLine((int) line.getStartX(), (int) line.getStartY(), (int) line.getEndX(), (int) line.getEndY());

Note that you will also need to take care of the transforms applied to the node, and the fill or draw of the Node.

Then you iterate on the Node children and do the same thing recursively.

At the end you should be able to save the document in SVG as Batik allows to do it natively.

Upvotes: 0

Stefan
Stefan

Reputation: 12380

I started to write a JavaFx Node to SVG converter which "extends" the ShapeConverter from Gerrit Grunwald which only converts Shape geometries:

https://github.com/stefaneidelloth/JavaFxNodeToSvg

... and stopped it after a certain amount of frustration.

Please feel free to improve it, add features and fix bugs.

The converter works for simple Node examples but fails for advanced examples like a Chart. My failed converter might serve you as a starting point. The current state is illustrated by the following figures. The left side shows the original JavaFx node and the right side shows the resulting svg output.

Simple node example (works): enter image description here

Chart example (does not work): enter image description here

The corresponging svg files are available here:

https://github.com/stefaneidelloth/JavaFxNodeToSvg/tree/master/output

Some further notes

In addition to the positioning problems that are illustrated by the Chart example above, some further issues have to be considered:

JavaFx provides more css functionality than the SVG standard elements. For example a (rectangular) JavaFx Region can have individual line styles for each of the four border lines. Such a Region can not simply be converted to a SVG rect. Instead, the four border lines of the Region need to be drawn individually. Furthermore, each end of such a border line can have two individual edge radii: a vertical radius and a horizontal radius. In order to convert the "four" border lines to corresponding SVG lines ... it might be necessary to further split the border line: draw two rounded parts and a straight part for each of the four border lines. Therefore, in some cases there might be 12 SVG path elements to draw the border of a single JavaFx Region. In addition, the background fill of the Region can have different radii than the border of the Region. Drawing the background of the Region might requires some more SVG elements. Therefore, even the conversion of a "rectangular Region" can get quite complex.

Please also note that JavaFx nodes might be animated. For example the opacity of a line is 0 at the beginning and fades to another value after a few milliseconds.

FadeTransition ft = new FadeTransition(Duration.millis(250), borderline);
                    ft.setToValue(0);                       
                    ft.play();

Therefore it only makes sense to convert Nodes where animations are disabled or to wait until the Nodes are in a stable state.

I would be very happy if the conversion of JavaFx Charts works one day, so that I can use JavaFx plotting with SVG export in one of my projects.

I decided to stop the development of the converter for the time being and to investigate the plotting and SVG export with JavaScript libraries (d3) instead. If that strategy turns out to be even worse, I might come back to the JavaFxNodeToSvgConverter.

Edit The plotting with d3.js works very well and I decided not to use JavaFx for the purpose of plotting/svg creation. https://github.com/stefaneidelloth/javafx-d3

Upvotes: 5

Hervé Girod
Hervé Girod

Reputation: 567

The idea is that if you are able to convert the JavaFX Nodes tree structure to a series of Graphics2D orders, then you can use Batik which has a Graphics2D driver.

The thing is that converting the JavaFX tree structure to Graphics2D orders is not as difficult as you might think (even if you process the CSS properties of the Nodes).

Some readers suggested that I should include some code, and not just the link to the library and pictures of it working. It is not so easy to do, because even if it is not so difficult to do, it still has 5000 lines of code.

How to perform the conversion:

  • you must have a Graphics2D to convert to SVG (for example the Batik SVGGraphics2D class);
  • iterate through the JavaFX structure;
  • for each Node in the structure, first convert the current javaFX transforms on this Node to an AffineTransform, and apply the transformation to the Node (you must do it in a Stack to be sure to revert to the initial AffineTransform at the end of the Node);
  • and for each Node, you have to transform to the appropriate Graphics2D orders.

Note that you don't need to support a lot of Node types, mainly:

  • Regions (controls are a special type of Region which can have an associated Graphics)
  • Groups
  • Shapes (Text, Rectangle, etc...)
  • ImageView for images

You may also need to take care of the Node Clipping to apply the associated Clipping in the Graphics2D.

Also you will have to take care of the CSS properties of the Node.

For all its worth, the library I used (which apllies this algorithm) is here: http://sourceforge.net/projects/jfxconverter/

Upvotes: 1

Has QUIT--Anony-Mousse
Has QUIT--Anony-Mousse

Reputation: 77505

There is an open bug request in JFX JIRA at

https://javafx-jira.kenai.com/browse/RT-38559

(registration required; you can vote for the issue then). It currently says

Consider for a future version.

And is marked for version 9.

Upvotes: 1

jewelsea
jewelsea

Reputation: 159586

There is a simple JavaFX shape to SVG string converter, it will only convert basic shapes without css applied, not arbitrary nodes, but perhaps that might be all you need.

Upvotes: 1

Related Questions