SVGRoundTrip, Part 3: Convert SVG to Java 2D

As I mentioned in the first post on SVGRoundTrip, my initial interest in SVG images came from wanting scalable images that I could use within the UI of my applications.

When I originally started work on 3DAssembler I used PNG versions of the icons from the Tango and Gnome Desktop icon sets as I didn't know how to deal with the original SVG versions. During development I noticed that the Flamingo component suite (which I was using for the ribbon) also contained support for converting SVG files into Java 2D based classes that would draw the icons at the correct size we required. For version 2 of 3DAssembler I changed from all PNG images to all SVG and the different sized icons look much better. Whilst most of the SVG files I wanted to used converted without any problems some didn't and so I set about figuring out why.

The first problem that I noticed was that the clipping wasn't correct. This usually showed up where I had an image that went outside the bounds of the SVG page size. When exporting to a PNG from Inkscape everything outside the page boundaries was removed and I expected the same behavior when the images were converted to Java code. It was definitely a bug as the image being drawn in my app was actually a lot wider than it should have been and because it wasn't being clipped was actually spilling out over other components. I initially fixed the issue by simply setting a clip on the Graphics2D object before passing it to the generated Java code. Whilst this fixed the overflow issue it didn't fix clipping issues within the bounds of the image. Each element in an SVG file can specify a clip and this was being ignored when the code within Flamingo was converting the SVG file to Java code. After a little trial and error I managed to add support for clipping any SVG element that required it.

The second problem related to scaling. Whilst SVG images can be scaled at will without a loss in quality they are originally drawn at a specific size. Knowing how to scale all the SVG elements requires knowing this original size. Unfortunately, the code in the Flamingo library didn't extract the page size from the original SVG file rather it used the bounds of the image (i.e. the rectangle that fully encloses all the elements) for scaling. Usually (at least in the files I was using) the bounding rectangle only differed from the page size by a pixel or so and therefore the images drawn using the wrong information were almost identical to those using the correct page size. The problem really only appeared when I fixed the clipping issue. With correct clipping I kept seeing the right/bottom column/row of the image being clipped. It turns out that the correct page size information is available (although it's well buried) in the information provided by Batik when it parses the SVG file so it was fairly easy to fix this problem as well.

The final problem that I noticed was that the code wasn't transcoding alpha values properly either. Each SVG element can have an alpha value associated with it to set the transparency. The problem arose when elements were nested. So for example if you have a set of nested shapes and you set alpha to 0.5 for the root element all the nested elements should have this value to start with before any other values are applied. Again once I knew what the problem was fixing it was easy.

These three fixes allowed me to convert all the SVG files I wanted to use for 3DAssembler without any problems. I patched my copy of Flamingo and then posted the patch to the Flamingo discussion forum. I would probably have left it at that, but a few days later a message was posted by the developer of Flamingo to say that he was no longer supporting or developing the library, and so SVGRoundTrip was born to hold these fixes but also as a place for new features and other SVG related ideas.

The original library (as well as having the bugs outlined above) doesn't support all SVG files as it doesn't support embedded raster images or text -- there might be other things as well but these are the things I know about. So I set about adding support for these as well. Supporting embedded raster images is easy, I just store the images on disk and then add code to the generated classes to read the images back when they are needed. Supporting all other unsupported elements (including text) is also easy -- I just convert the elements to images and then treat them like raster nodes. This isn't ideal as now they don't scale well and so by default raster images and other elements not supported by the original library are still not supported.

Anyway, enough waffle, I've wrapped up all the SVG to Java 2D code into a simple to use command line application (it can also be accessed via an API), the usage of which is given below

SVGBatchConverter: Convert SVG files into Java2D based classes

Usage: java -jar SVGRoundTrip.jar [OPTIONS] SVGFile1 ... SVGFileN
  -f       by defualt classes are not generated if they exist and are newer
           than the SVG files, use this option to force them to be regenerated
  -i       by default raster images embedded in SVG files are not supported
           enabling this option generates PNG files for raster nodes which
           are loaded and drawn as required by the generated code
  -n name  classname format -- by default the generated class will have the
           same name as the SVG file use this option to specify
           'prefix+suffix', if the format doesn't contain a + then it just
           specifies a prefix
  -o dir   output directory (required), this should be the root of the source
           tree the correct sub-directory will be used based upon specified
           package name
  -p name  the name of the package the generated classes should be a member of
  -t name  the template name or a file containing a template -- valid template
           names are currently 'plain' and 'flamingo', see the docs for details
           if unspecified the 'plain' template will be used
  -u name  specify how to treat unsupported SVG elements -- possible values are
           'fail' the default behaviour which causes conversion to fail
           'skip' unsupported elements will be skipped, conversion will succeed
           'image' unsupported elements will be converted to images to maintain
           the look of the original SVG file -- image support must (-i) must
           also be enabled for this to work

If directories are specified instead of single SVG files then all SVG files in
the directories will be converted
So now you know how I convert SVG files into Java 2D based classes for use in my applications. If you want to try this yourself then check out the code and have a go.

4 comments:

  1. Hey Mark! This library looks very interesting and I am trying to use it for similar reasons (batik is slooow)
    I have been playing with the library and I found one bug, when generating an the Java2D class using this svg: http://upload.wikimedia.org/wikipedia/en/2/20/UofTsystem_seal.svg

    I get the following error from the generated class: The code of method paint(Graphics2D) is exceeding the 65535 bytes limit

    Apparently there is a size limit to the number of characters in a method in Java! who knew? lol
    I am going to try and split the method into multiple calls to see if that helps, and maybe the library can be changed to do that automagically ;)

    Do you have a bugtracker, etc?

    ReplyDelete
  2. Hi Milando, glad to hear you think the library is going to be useful. The class size issue is a real pain. I do have some ideas for splitting but if you have some thoughts that would be really helpful as well.

    I don't have a bug tracker at the moment (although maybe I should), but feel free to use the comments on here to post bugs/fixes/suggestions etc. You can find all the source in the SVN server (link at the top) and the library is being built by Jenkins as well (link also above).

    ReplyDelete
  3. Hey Mark,
    I have checked out the source but have not looked through it to closely yet. Out of curiosity I tried to find what other limitations there are on class files and found this reference:

    http://java.sun.com/docs/books/jvms/second_edition/html/ClassFile.doc.html#88659

    Looks like 16-bits of things is a pretty common theme, but nothing seems to suggest classes themselves have a max file size. The spec says each method is a max of 65535 bytes and a class can have up to 65535 methods which gives 4GB of head room. Seems like splitting into multiple methods should be a useable option (I never want to touch anything resembling a 4GB svg) :)

    If I decide to jump into it ill post back here. Maybe you could host on a Google code? You could just use the wiki/ bug tracker or host in the svn/mercurial. Haven't seen Jenkins before, pretty interesting.

    ReplyDelete
  4. I ran into another size issue when testing this with a large svg file:
    Too many constants, the constant pool for would exceed 65536 entries.

    ReplyDelete