Export SVG from Raphael JS to create a PNG bitmap

2 minute read

04 Jun 2010

Update

I'm very pleased to be able to say that Jonathan Spies has taken the roots of this blog post and produced a fully fledged plugin. I've used it on a couple of projects already. Oh how I love the web sometimes.

http://github.com/jspies/raphael.serialize

Introduction

I’ve been playing around with RaphaelJS recently (if you haven’t seen it, check it out) for an ‘avatar builder’ type project to avoid the use of Flash. It’s a really fun bit of kit to use, but I needed a way to actually save the results of the avatar to a bitmap to avoid having heavy javascript libraries every time the avatar is displayed on a page. After a bit of poking around on forums/blogs I thought I’d tie up all the key pieces in one blog post in case anyone else was in the same boat.

The output is essentially a PNG-24 alpha channel bitmap image, which can then be treated as normal with the likes of ImageMagick (I’ve been adding drop shadows to the avatars for example).

Extracting SVG data from Raphael

Unfortunately this isn’t as easy as could be. In browsers that support SVG, we can simply extract all the data from the DOM itself to reproduce it, but as Raphael uses VML on non-SVG browsers (namely, IE) that idea won’t work.

However, the Raphael Javascript object does store the data we need in order to re-produce the SVG. This requires a little bit of Javascript, and for demonstration purposes I’m using PHP.

The Javascript

So the first thing I do build up a JSON object describing the data needed to produce the SVG file, with a little help from jQuery. I’ve put the code for this on this gist. It essentially loops through the ‘paper’ variable that RaphaelJS is using to build up our own array. In mine I was using just paths and images, so that was the only data I needed, however this could easily be expanded to include all the other properties as well.

The Server Side Code (PHP)

All this file does is decode the JSON data into an associative array for use in PHP, loop through the properties and outputs an SVN file. I’ve put the PHP I’m using below as there’s not much to it:

    $json = json_decode($_GET['json'], true);

    $output = '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="'.$json[0]['width'].'" height="'.$json[0]['height'].'" xml:space="preserve"><desc>Created with Raphael</desc><defs></defs>';

    for ($i=1; $i &lt; count($json); $i++) {
        if ($json[$i]['type'] == "image") {
            $base64 = base64_encode(file_get_contents($json[$i]['src']));

            $output .= '<image overflow="visible" x="'.$json[$i]["x"].'" y="'.$json[$i]["y"].'" width="'.$json[$i]["width"].'" height="'.$json[$i]["height"].'" transform="'.$json[$i]["transform"].'" preserveAspectRatio="none" xlink:href="data:image/png;base64,'.$base64.'"></image>';
        }

        if ($json[$i]['type'] == "path") {
            $output .= '<path fill="'.$json[$i]['fill'].'" stroke="'.$json[$i]['stroke'].'" d="'.$json[$i]['path'].'" style="opacity: '.$json[$i]['opacity'].';" opacity="'.$json[$i]['opacity'].'" transform="'.$json[$i]['transform'].'"></path>';
        }
    }

    $output .= '</svg>';

Save the SVG and send to Apache Batik for Rasterization

So this part is fairly simple. I recommend using Apache Batik for the SVG rasterization as it seemed to have better results compared to ImageMagick. The first part is simply to write the ‘SVG string’ to a file.

file_put_contents('generated/avatar.svg', $output);

Now, just send it across to Apache Batik with the exec() command, and we have a PNG!

exec("java -jar batik-rasterizer.jar /path/to/avatar.svg");

Just the beginning

So this was just a basic introduction to exporting an SVG from Raphael, and will need more work for more complex shapes and transformations. I’m planning on writing a RaphaelJS extension to make all this a bit easier, and perhaps offer out the server side code on a public domain so there’s no need to write it out…

So stay tuned. Or, if you’re already doing this, I’d be more than happy to help out.

Updated: