Using scalable SVG in JavaFX

QWv3 will allow the user to scale the interface by using Ctrl+= and Ctrl+-. Of course, if the interface uses standard image types such as .png files then this won’t scale the images without some kind of intervention (-fx-scale-x, -fx-scale-y manipulation) and if it does scale the images then they are likely to become blurry. 16px by 16px images don’t tend to scale up very well.

JavaFX allows you to use svg paths in the css, such as:

.my-class {
   -fx-shape: "m 0,15.984 5.672,5.664 c 0,0 3.182,-3.18 6.314,-6.312 V 32 h 8.021 V 15.336 l 6.32,6.32 L 32,15.984 16.002,0 Z";
}

This is ok but there are a number of hurdles to turn the image:

Into the path above and have it scale and change color as required.

Limitations

It’s important to note that this technique only applies to “flat” svgs. i.e. only a single color can be applied to the svg shape. There are ways to have multiple colors and layers but that is beyond the scope of this post.

Region

We want the svg shape to scale and have a defined size so the first thing we need is a Region that we will style with “my-class”.

Region r = new Region ();
r.getStyleClass ().add ("my-class");

Sizing

To properly scale the svg we should use “em” units for the size of the my-class region. Think of this size as the “natural” size of the svg, it’s the size that you want the svg to be at without any scaling (up or down) applied at the normal size of the UI, i.e. at 1em. For example, it’s not uncommon for the normal size of UI images to be 16px by 16px for menus.

When we scale the UI we modify the underlying base font size, if all other sizes are defined in em units then the entire UI will scale.

My base font size is 9pt, which is 12px at 96dpi. If we want the shape to have a standard (natural) size of 16px by 16px then we need:

.my-class {
    -fx-pref-width: 1.333em;
    -fx-pref-height: 1.333em;
}

You should modify the sizes to fit the natural size ratio of the svg. Note: by default -fx-snap-to-pixel will be true and 1.333 * 12px = 15.996px which should give a size of 16px. You could use 1.3 * 12px = 15.6px and with half-rounding it should snap to 16px, however Region is a little fuzzy (ironically) on what rounding method is used.

Color

JavaFX uses the background-color style to set the color of the shape, thus:

.my-class {
    -fx-background-color: red;
}

Creating a single path

Only a single -fx-shape value can be applied to a given region. As such you need to reduce any image or svg you have down to a single path. I’ve been using Inkscape (which is free) and the instructions from this StackOverflow post to convert my images which I have in svg format to a single path. It takes a bit of digging around in the resulting svg file to pull out the path but it works.

Putting it all together, applying it to a MenuItem

This technique can be used in MenuItems to have scalable images (and I’m sure you can see where it can be used in other places), for example:

Java
====
Region r = new Region ();
r.getStyleClass ().add ("my-class");
MenuItem m = new MenuItem ();
m.setGraphic (r);

CSS
===
.my-class {
   -fx-shape: "m 0,15.984 5.672,5.664 c 0,0 3.182,-3.18 6.314,-6.312 V 32 h 8.021 V 15.336 l 6.32,6.32 L 32,15.984 16.002,0 Z";
   -fx-pref-width: 1.333em;
   -fx-pref-height: 1.333em;
   -fx-background-color: red;
}

Gives the result:

And scaled up by changing the base font size (note: not changing the size in the my-class style):

Changing the color and base font size:

.my-class {
    -fx-background-color: blue;
}

But what about javafxsvg?

You might be aware of the javafxsvg project, it does allow svg to be used as an image type but in my view it has two major problems. First, it doesn’t seem to size images correctly, even using a Region (Pane) container with an ImageView inside it just wouldn’t size the image to the parent container and every dimension provider I used refused to size it. Second, it uses Apache Batik and as such drags 8MB of code along with it which is far too much in my view (especially for downloaded applications). It’s not the javafxsvg developer’s fault, it’s just a horrible quirk of java’s idiotic packaging mechanism.

Image errors!

The example images I gave above have the svgs at the wrong size due to an initial math error (I wrongly assumed my em pixel size) but I’m not going to do new images since they are illustrative only.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: