Dynamic Rasterization with Scripted SVG

Nicholas Blanchard-Wright (nick@panharmonicon.com)
01/23/2005

SVG is an open standard for describing vector graphics. As of this writing, it has extremely limited support in modern browsers, making it impractical for web designers. However, SVG is an XML language, and like any XML document, it can be wrapped in/produced by your favorite web scripting language. Here we describe the production of dynamically styled raster graphics (in traditional JPEG, or the slightly more daring PNG format) by encapsulating SVG in PHP.

Our goal is a self-contained .svg.php file that will accept arbitrary style variables and return raster image data. The first thing we'll need is some means of rasterizing SVG data on the fly. Fortunately, there are several command-line tools available to accomplish this. We'll give two examples. In both cases, these PHP scripts define a rasterize() function with takes raw SVG data as an argument and returns the rasterized image.

In the first case we use the rsvg utility distributed with librsvg. This necessitates the use of a temporary file, which adds some overhead, but it has the advantage of being generalizable to any other command-line SVG parser. The rsvg syntax is simple – it takes an SVG input file and JPEG or PNG output file as arguments.

rsvg.php

<?php
function rasterize($svg) {
    
header("Content-type: image/png");
    
$svgfile fopen("/tmp/rasterize.svg"'w');
    
fwrite($svgfile$svg);
    
fclose($svgfile);
    
$rsvg exec("/www/bin/rsvg /tmp/rasterize.svg /tmp/rasterize.png");
    
$pngfile fopen("/tmp/rasterize.png"'r');
    while(
$pngfile and !feof($pngfile)) {
        
$png .= fread($pngfile1024);
    }
    
fclose($pngfile);
    return(
stripslashes($png));
}
?>

In the second case we use ImageMagick's convert utility for rasterization. Unlike rsvg, convert can operate on standard input and output, avoiding the use of a temporary file with PHP's process control. ImageMagick is also available as a PEAR module, and this script could be easily adapted to use the integrated function calls, eliminating the overhead of calling an external program.

imsvg.php

<?php
function rasterize($svg) {
    
header("Content-type: image/jpeg");
    
$descriptorspec = array(=> array("pipe""r"), => array("pipe""w"));
    
$convert proc_open("/www/bin/convert -quality 100 svg:- jpg:-"$descriptorspec$pipes);
    
fwrite($pipes[0], $svg);
    
fclose($pipes[0]);
    while(!
feof($pipes[1])) {
        
$jpg .= fread($pipes[1], 1024);
    }
    
fclose($pipes[1]);
    
proc_close($convert);
    return(
stripslashes($jpg));
}
?>

Finally, we need an actual SVG file, wrapped in PHP for dynamic effects. Here, we'll use a simple example — a circle contained within a square. We'll then define two variables $circle and $square that will contain color values to be passed to the image. To make things a little more interesting, we'll also add a stroke to the square, and define its color with $stroke.

This SVG was produced with Adobe Illustrator, but there are open source vector programs that output SVG as well. We used entity references (set in Illustrator's SVG output options) for style, as the entity definitions provide a convenient place to insert the color variables from PHP.

The .svg.php file imports one of the rasterizing scripts above, and sets rasterize() as a callback function on the entire file buffer; a handy little trick that keeps our image files relatively clean.

simple.svg.php

<?php
require('rsvg.php');
ob_start("rasterize"); // Using a buffer callback keeps our script clean.
$circle $_GET['circle']; if(!$circle) { $circle "F67720"; }
$square $_GET['square']; if(!$square) { $square "2D097F"; }
$stroke $_GET['stroke']; if(!$stroke) { $stroke "199332"; }
?>
<?xml version
="1.0" encoding="utf-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN"    "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd" [
    <!ENTITY circle "fill:#<?php echo($circle); ?>;">
    <!ENTITY square "fill:#<?php echo($square); ?>;stroke:#<?php echo($stroke); ?>;stroke-width:5;">
]>
<svg  xmlns="http://www.w3.org/2000/svg"
     width="232.083" height="232.084" viewBox="0 0 232.083 232.084"
     style="overflow:visible;enable-background:new 0 0 232.083 232.084" xml:space="preserve">
    <g id="Layer_1">
        <path style="&square;" d="M229.583,229.584H2.5V2.5h227.083V229.584z"/>
        <path style="&circle;" d="M216.042,116.042c0,55.229-44.772,99.999-100,99.999c-55.229,0-100-44.771-100-99.999s44.771-100,100-100
            C171.27,16.042,216.042,60.813,216.042,116.042z"/>
    </g>
</svg>

And the output (yes, this is live, feel free to change the variables):

simple.svg.php?circle=0055aa&square=ffffff&stroke=000000
[simple image]

And here's a slightly more complex image. It has twelve different variables. Two are given away in the query string. A third is $text, which takes an arbitrary string rather than a color value. See if you can guess the others.

beatnick.svg.php?right=eeeeff&left=221166
[Beatnik Goth Nick]

Of course, neat as this is, you don't want the overhead of generating images unnecessarily. Fully dynamic images might prove a useful tool for prototyping new sites for web design clients ("Can we see it in black?" "Sure, one second!"), but the most practical uses for this technique would probably involve semi-static color values, where the rasterized output could be cached, and new images generated only as needed. User-themeable sites, for example, could generate a new set of images when the user changed their color preferences. Content management systems could use a single set of color values to automatically regenerate stylesheets and images site-wide, whenever the client wished.