Out of the box, JavaFX 8 supports JPEG, PNG, GIF and BMP images, which should be sufficient for most use-cases. By additionally using the JavaFX WebView to display images, you can even extend the list of supported image formats for example by SVG. This might however be insufficient, since some JavaFX components require an Image object, whereas you usually cannot use WebViews when defining images using CSS. If you want to use SVG as a button graphic or as a background image using CSS, you thus need to teach JavaFX how to create Image objects from SVG files.
In this blog post, I describe how to add a custom image renderer to JavaFX 8 for SVG. With the resulting code, you can use SVG images anywhere in your project just like any already supported image format. For the sake of brevity, I focus on the most interesting code sections. Additionally, I created a complete example on GitHub that you can directly use in your own project.
JavaFX manages all supported image formats within the ImageStorage class. Adding a new format is supported by adding a respective ImageLoaderFactory using the following static method:
public static void addImageLoaderFactory(ImageLoaderFactory factory);
Unfortunately, this method is not part of the official JavaFX public API, which may result in an discouraged access warning when using it. The ImageLoaderFactory that needs to be provided has two main responsibilities, i.e. describing the supported image file format and converting the raw image data into a JavaFX intermediate representation. The former is done using a ImageFormatDescription class and the latter requires an implementation of the ImageLoader interface.
In order to determine whether a particular factory can be used to create images from a given input stream, the ImageFormatDescription is used to compare the the first few bytes of an image file with a set of signatures. It is interesting to note that JavaFX only uses magic bytes to determine the image format and does not care about the actual file ending. Since the image format description was designed to match binary files, the used signatures consist of a static byte sequence. For SVG, we can use these two signatures:
However, this signature matching is rather inflexible and not well suited to match text based image files like SVG. For example, it does not allow for matching files starting with whitespaces or comments. Unfortunately, subclassing the utilized Signature class is not permitted by JavaFX, so we cannot easily alter the signature matching behavior. As a result, we’ll leave it at that for now as it is probably easier to just trim the SVG images files than to hook into the signature matching routines.
Now, as JavaFX knows that it should use the added custom ImageLoaderFactory for files starting with the provided signatures, we implement the actual ImageLoader. This class is instantiated with an input stream of the image file and provides a means to transforms this stream into an ImageFrame object. The core function that needs to be implemented has the following signature:
public ImageFrame load(int imageIndex, int width, int height, boolean preserveAspectRatio, boolean smooth) throws IOException;
imageIndex parameter is used to identify a frame number in animated images. Since there is no method to determine the total amount of frames, JavaFX calls this method multiple times with increasing indexes until the method returns null. For static images like SVG, an ImageFrame should only be returned for
imageIndex == 0. Width and height may be zero if no image dimensions are explicitly defined within the JavaFX application. In this case, the image should be loaded using its actual size. The final
smooth parameter indicates whether or not a smooth downscaling algorithm should be used.