FXG to Java2D converter

convertersplash.png

During the last months i was working a lot on Java Swing components which really is a lot of fun. It took me some time to establish a workflow to transfer my design prototypes from Adobe Fireworks into Java2D elements.

With not too complex shapes this was not a big deal even if it was a lot of coding (see also Drawing in code part I and Drawing in code part II) but during the work on my current component (which will be a combination of weather and time of day display for different cities) i needed to create clouds…

Did you ever create clouds in vector programs ? It’s really not so easy if they should look nice and you will need a lot of ellipses in different sizes to get a result which looks not too bad.
After creating my first cloud out of 49 circles (all the same size) i figured out that it would take a looooooooong time to complete these clouds.

And so i tried a different approach by creating a spreadsheet where i „only“ have to fill in the x,y coordinates of the shapes and it creates the resulting sourcecode out of it so that i could copy and paste it to my netbeans project.

That’s what it looked like:

ishot-1.png

That was not too bad but not the solution i was looking for and so i checked all possibilities to export a Adobe Fireworks png file to a format which i could read and convert with Java.

And i found it…it’s called FXG and is similar to SVG but was created from Adobe to exchange images within the Adobe CS4 suite. When i took a look at the exported fxg file (which is a xml dialect) i found out that it should not be too hard to write a quick and dirty converter that will help me get the shape coordinates from the fxg file into the Java2D sourcecode.
So i started with converting only the coordinates but that was fun and so i continued to convert also gradients, strokes, text etc.

In the meantime Adobe announced the CS5 suite which will make use of the FXG 2.0 standard and i hope my converter will be able to handle this too (if not i’ll make it possible).

Well right now this is NOT a full blown converter but a little tool (unfortunately still a quick and dirty solution) that helps me converting my design prototypes from Adobe Fireworks into Java2d sourcecode.

And that’s what it looks like today:

ishot-1.png

Here’s a short example of how to use it:

This is the file i would like to convert:

Green_eyes.png

(The original file was in eps format and could be downloaded here)

Open the file in Adobe Fireworks and export it to fxg (see screenshot):

FXG_EXPORT.png

I kept a hint of Dave Gilbert (subscribe to his blog) in my mind who told me to keep things scalable and for this reason (thanx Dave) i created the converter in the way that it converts all vector shapes to scalable Java2D shapes. This means you could take a big image like i did in this example (809 px width) and the conversion will create a method that creates a buffered image where you could define the width (and the height will be calculated properly so that you keep the right aspect ratio).

Now simply drag the fxg file to the symbol on the FxgConverter tool and it will show you a preview of the converted image like this:

ishot-2.png

If you now press the convert button the FxgConverter will convert the fxg data into java2d code and copy the code to the clipboard. Additional to that it will create a java file on your desktop which contains the sourcecode.

The method that was copied to the clipboard in this case looks like that (only the start):

    private java.awt.image.BufferedImage createGreenEyesImage(int width)
    {
        final java.awt.GraphicsConfiguration GFX_CONF = java.awt.GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration();
        final java.awt.image.BufferedImage IMAGE = GFX_CONF.createCompatibleImage(width, (int) (0.45982694684796044 * width), java.awt.Transparency.TRANSLUCENT);
        java.awt.Graphics2D g2 = IMAGE.createGraphics();
        g2.setRenderingHint(java.awt.RenderingHints.KEY_ANTIALIASING, java.awt.RenderingHints.VALUE_ANTIALIAS_ON);
        g2.setRenderingHint(java.awt.RenderingHints.KEY_ALPHA_INTERPOLATION, java.awt.RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
        g2.setRenderingHint(java.awt.RenderingHints.KEY_COLOR_RENDERING, java.awt.RenderingHints.VALUE_COLOR_RENDER_QUALITY);
        g2.setRenderingHint(java.awt.RenderingHints.KEY_STROKE_CONTROL, java.awt.RenderingHints.VALUE_STROKE_PURE);
        final int IMAGE_WIDTH = IMAGE.getWidth();
        final int IMAGE_HEIGHT = IMAGE.getHeight();

        final java.awt.geom.GeneralPath PATH1_1 = new java.awt.geom.GeneralPath();
        PATH1_1.setWindingRule(java.awt.geom.GeneralPath.WIND_NON_ZERO);
        PATH1_1.moveTo(IMAGE_WIDTH * 0.20024721878862795, IMAGE_HEIGHT * 0.3763440860215054);
        PATH1_1.curveTo(IMAGE_WIDTH * 0.19901112484548825, IMAGE_HEIGHT * 0.3763440860215054, IMAGE_WIDTH * 0.19777503090234858, IMAGE_HEIGHT * 0.3736559139784946, IMAGE_WIDTH * 0.1965389369592089, IMAGE_HEIGHT * 0.3736559139784946);
        PATH1_1.curveTo(IMAGE_WIDTH * 0.18912237330037082, IMAGE_HEIGHT * 0.3655913978494624, IMAGE_WIDTH * 0.18170580964153277, IMAGE_HEIGHT * 0.3548387096774194, IMAGE_WIDTH * 0.17428924598269468, IMAGE_HEIGHT * 0.3467741935483871);
        PATH1_1.curveTo(IMAGE_WIDTH * 0.1668726823238566, IMAGE_HEIGHT * 0.3387096774193548, IMAGE_WIDTH * 0.15945611866501855, IMAGE_HEIGHT * 0.3333333333333333, IMAGE_WIDTH * 0.15203955500618047, IMAGE_HEIGHT * 0.32526881720430106);
        PATH1_1.curveTo(IMAGE_WIDTH * 0.1508034610630408, IMAGE_HEIGHT * 0.33064516129032256, IMAGE_WIDTH * 0.14833127317676142, IMAGE_HEIGHT * 0.3333333333333333, IMAGE_WIDTH * 0.14709517923362175, IMAGE_HEIGHT * 0.3387096774193548);
        PATH1_1.curveTo(IMAGE_WIDTH * 0.14585908529048208, IMAGE_HEIGHT * 0.3629032258064516, IMAGE_WIDTH * 0.1446229913473424, IMAGE_HEIGHT * 0.4112903225806452, IMAGE_WIDTH * 0.1508034610630408, IMAGE_HEIGHT * 0.4381720430107527);
        PATH1_1.curveTo(IMAGE_WIDTH * 0.15822002472187885, IMAGE_HEIGHT * 0.4731182795698925, IMAGE_WIDTH * 0.14956736711990112, IMAGE_HEIGHT * 0.4381720430107527, IMAGE_WIDTH * 0.14956736711990112, IMAGE_HEIGHT * 0.4381720430107527);
        PATH1_1.curveTo(IMAGE_WIDTH * 0.1508034610630408, IMAGE_HEIGHT * 0.4435483870967742, IMAGE_WIDTH * 0.15327564894932014, IMAGE_HEIGHT * 0.45161290322580644, IMAGE_WIDTH * 0.15451174289245984, IMAGE_HEIGHT * 0.45698924731182794);
        PATH1_1.curveTo(IMAGE_WIDTH * 0.1619283065512979, IMAGE_HEIGHT * 0.4946236559139785, IMAGE_WIDTH * 0.173053152039555, IMAGE_HEIGHT * 0.5268817204301075, IMAGE_WIDTH * 0.18912237330037082, IMAGE_HEIGHT * 0.5591397849462365);
        PATH1_1.curveTo(IMAGE_WIDTH * 0.1965389369592089, IMAGE_HEIGHT * 0.5698924731182796, IMAGE_WIDTH * 0.20519159456118666, IMAGE_HEIGHT * 0.5806451612903226, IMAGE_WIDTH * 0.21508034610630408, IMAGE_HEIGHT * 0.5860215053763441);
        PATH1_1.curveTo(IMAGE_WIDTH * 0.24721878862793573, IMAGE_HEIGHT * 0.6129032258064516, IMAGE_WIDTH * 0.28059332509270707, IMAGE_HEIGHT * 0.6102150537634409, IMAGE_WIDTH * 0.30407911001236093, IMAGE_HEIGHT * 0.5860215053763441);
        PATH1_1.curveTo(IMAGE_WIDTH * 0.28059332509270707, IMAGE_HEIGHT * 0.5, IMAGE_WIDTH * 0.242274412855377, IMAGE_HEIGHT * 0.43010752688172044, IMAGE_WIDTH * 0.20024721878862795, IMAGE_HEIGHT * 0.3763440860215054);
        PATH1_1.closePath();
        final java.awt.Color FILL_COLOR_PATH1_1 = new java.awt.Color(new java.awt.Color(0xFFFFFF).getRed(), new java.awt.Color(0xFFFFFF).getGreen(), new java.awt.Color(0xFFFFFF).getBlue(), 255);
        g2.setColor(FILL_COLOR_PATH1_1);
        g2.fill(PATH1_1);
...
        g2.dispose();
        return IMAGE;
    }
//g2.drawImage(createGreenEyesImage(100), 0, 0, null);

As you could see the last row will contain the code which draws the image, just copy this row and paste it into your painting code (e.g. paintComponent method).
So the result of the 809 px wide original image converted to Java2D looks like this at 128px, 256px and 512px:

ishot-3.png

If you name the elements in Fireworks the converter will name the shapes in the sourcecode with the same name (keep in mind that you do not use the same name twice). It’s not needed to give each element a name but in complex designs it makes things easier, especialy if you would like to make changes to it later in the sourcecode.

Fireworks:

ishot-2.png

Resulting sourcecode:

ishot-3.png

Thanx to Rémy Rakic (find him here on twitter or subscribe to his blog) i got some fxg files that have been converted by Adobe Illustrator. Unfortunately the fxg format from Illustrator is different from the Fireworks fxg format and so i had to rework parts of the converter. To get the best results you should ungroup all elements in Fireworks before you export them to fxg.

For me the most important thing was to get the shapes from my Adobe Fireworks files into Java2D code to make the creation of custom java swing components more easy the converter won’t make use of bitmaps that are linked to the fxg file etc.
It will only convert (hopefully) all kinds of shapes with their filling and strokes to java2d code.

In the Netbeans project you will find two converters, one will create a buffered image on the fly (this was needed for the preview) and the other will copy the sourcecode to the clipboard and save it as a java file.

KEEP IN MIND:

In Java a single method is not allowed to be larger than 64 kb !!! You might think that you will never reach this size but when it comes to complex vectorgraphics it’s not a big deal to create single method with more than 10000 lines of code !!!
I just use a simple trick to avoid methods that large by splitting them up into methods of 1000 lines of code. So if you have a very large fxg file to convert you might find multiple versions of the image creating method (in the example above it would look like createGreenEyesImage(int width, createGreenEyesImage1(int width), etc.).
With this trick i could still create one big image by calling the methods for example like this g2.drawImage(createImage2(createImage1(createImage(100))));
Unfortunately i still found images that will blow up my code and it won’t work but for smaller fxg designs it works ok.

KNOWN ISSUES:

  • I do not care about grouping in Fireworks so it’s a good advice to ungroup all elements in Fireworks before you export it to fxg
  • There seems to be a problem with the fxg export of copied elements with gradients. So if you create something try not to copy structures with gradients
  • The conversion of big fxg files (unfortunately i could not define big) might lead to methods bigger than 64 kb, so you could try to decrease the no. of lines per method in the converter class to avoid it
  • FXG files exported by Illustrator might not look perfect (unfortunately i do not have Illustrator for testing)
  • At the moment there is basic support for text but it could be better
  • It seems to be a problem if you have a path that was made of only a few cubic splines, if you add some more points, it will convert fine. I did not found the time to take a closer look to the problem.
    Here is a short example:Path with only a few points:

    ishot-1.png

    and the result in the converter:

    ishot-3.png

    Path with more points:

    ishot-2.png

    and the result in the converter:

    ishot-4.png

I hope that with Adobe’s CS5 the fxg export in Illustrator and Fireworks will lead to the same result, please keep in mind that this is still not finished yet and whenever i find some time i try to improve the conversion.

For those of you that would like to see it in action…here we go…

So feel free to play around with it:

 

jws_launch.gif

As always you will get the source as Netbeans project: FxgConverter.zip

Follow me on twitter if you like…