Generating SVG with genx

As an exercise to learn genx, Tim Bray's new C library for generating XML, I used it the generate SVG pictures. (I suggested the name of the library, so I guess I should try to use it :)

I used this same model to exercise a Java SVG generation library I wrote a while back and it was interesting to compare approaches of Java vs. C.

programs and pictures

The general flow of the programs is to randomly generate graphic elements with varying parameters like size and position. The pictures and SVG code you see here were inspired by some Ruby scripts posted to the SVG Yahoo group.

The pictures, which remind me a bit of Piet Mondrian's work, are JPEG files rendered at 0.75 quality, with the Batik SVG rasterizer.

Note that these programs lack proper error checking, but they show the basic patterns.

The first attempt: circle.c -- creates circles on a 500x500 canvas, with randomly generated size, color (rgb triples, 0-255), and position.

[random circles]
/*
 *  generate random "bubbles" in SVG
 *
 *  Anthony Starks
 *  Licensed under the Creative Commons 
 *  Attribution-NonCommercial-ShareAlike license
 */

#include "genx.h"

int main(int argc, char *argv[])
{
    genxWriter w;
    int n;
    char cy[10], cx[10], cr[10], rgb[20];
    
    
    srandomdev();
    
    if (argc > 1)
        n = atoi(argv[1]);
    else
        n = 250;

    w = genxNew(NULL, NULL, NULL);
    genxStartDocFile(w, stdout);
    genxStartElementLiteral(w, NULL, "svg");
    genxAddAttributeLiteral(w, NULL, "width", "500");
    genxAddAttributeLiteral(w, NULL, "height", "500");
    genxStartElementLiteral(w, NULL, "rect");
    genxAddAttributeLiteral(w, NULL, "x", "0");
    genxAddAttributeLiteral(w, NULL, "y", "0");
    genxAddAttributeLiteral(w, NULL, "width", "500");
    genxAddAttributeLiteral(w, NULL, "height", "500");
    genxAddAttributeLiteral(w, NULL, "fill", "black");
    genxEndElement(w);
    
    while (n--) {
        snprintf(cx, 10, "%ld", random() % 500);
        snprintf(cy, 10, "%ld", random() % 500);
        snprintf(cr, 10, "%ld", random() % 50);
        
        snprintf(rgb, 20, "rgb(%03ld,%03ld,%03ld)", 
           random() % 255, random() % 255, random() % 255);
        genxStartElementLiteral(w, NULL, "circle");
        genxAddAttributeLiteral(w, NULL, "cx", cx);
        genxAddAttributeLiteral(w, NULL, "cy", cy);
        genxAddAttributeLiteral(w, NULL, "r", cr);
        genxAddAttributeLiteral(w, NULL, "fill", rgb);
        genxAddAttributeLiteral(w, NULL, "stroke", "black");
        genxAddAttributeLiteral(w, NULL, "stroke-width", "3");
        
        genxEndElement(w);
    }
    genxEndElement(w);
    genxEndDocument(w);
}
 

The next refactoring, added the additional random selections of square or circle.

The next iteration uses predeclared items and additionally makes the shapes either solid or translucent (opacity = 0.5).

[random objects]
/*
 *  grand -- generate random graphics in SVG
 *
 *  Anthony Starks
 *  Licensed under the Creative Commons 
 *  Attribution-NonCommercial-ShareAlike license
 */

#include "genx.h"

int main(int argc, char *argv[])
{
    genxWriter w;
    genxStatus status;
    int n, shapeflag, opflag;
    char chx[10], chy[10], chr[10], rgb[20], opval[20];
    genxElement circle, rect;
    genxAttribute cx, cy, rad, x, y, width, height, fill, stroke, opacity;
    
    
    srandomdev();
    
    if (argc > 1)
        n = atoi(argv[1]);
    else
        n = 250;
    w = genxNew(NULL, NULL, NULL);
 
    if (!(circle = genxDeclareElement(w, NULL, "circle", &status))) gerr(w);
    if (!(cx = genxDeclareAttribute(w, NULL, "cx", &status))) gerr(w);
    if (!(cy = genxDeclareAttribute(w, NULL, "cy", &status))) gerr(w);
    if (!(rad = genxDeclareAttribute(w, NULL, "r", &status))) gerr(w);
    if (!(fill = genxDeclareAttribute(w, NULL, "fill", &status))) gerr(w);
    if (!(stroke = genxDeclareAttribute(w, NULL, "stroke", &status))) gerr(w);
    if (!(opacity = genxDeclareAttribute(w, NULL, "opacity", &status))) gerr(w);
    
    if (!(rect = genxDeclareElement(w, NULL, "rect", &status))) gerr(w);
    if (!(x = genxDeclareAttribute(w, NULL, "x", &status))) gerr(w);
    if (!(y = genxDeclareAttribute(w, NULL, "y", &status))) gerr(w);
    if (!(width = genxDeclareAttribute(w, NULL, "width", &status))) gerr(w);
    if (!(height = genxDeclareAttribute(w, NULL, "height", &status))) gerr(w);
        
    genxStartDocFile(w, stdout);       
    svgstart(w, 500, 500);
    genxStartElement(rect);
    genxAddAttribute(width, "500");
    genxAddAttribute(height, "500");
    genxAddAttribute(fill, "white");
    genxEndElement(w);
    
    genxStartElementLiteral(w, NULL, "g");
    genxAddAttributeLiteral(w, NULL, "style", "stroke: black; stroke-width: 2");
    while (n--) {
        shapeflag = (int)random() % 100;
        opflag = (int)random() % 100;
        
        snprintf(chx, 10, "%ld", random() % 500);
        snprintf(chy, 10, "%ld", random() % 500);
        snprintf(chr, 10, "%ld", random() % 50);
        snprintf(rgb, 20, "rgb(%03d,%03d,%03d)", 
                 (int)random() % 255, (int)random() % 255, (int)random() % 255);
        snprintf(opval, 10,  "%.1f", opflag <= 50 ? 1.0 : 0.5);
        
        if (shapeflag >= 50 ) {
            genxStartElement(circle);
            genxAddAttribute(cx, chx);
            genxAddAttribute(cy, chy);
            genxAddAttribute(rad, chr);
            genxAddAttribute(fill, rgb);
            genxAddAttribute(opacity, opval);
            genxEndElement(w);
        }
        else {
            genxStartElement(rect);
            genxAddAttribute(x, chx);
            genxAddAttribute(y, chy);
            genxAddAttribute(width, chr);
            genxAddAttribute(height, chr);
            genxAddAttribute(fill, rgb);
            genxAddAttribute(opacity, opval);
            genxEndElement(w);
        }
    }
    genxEndElement(w);
    genxEndElement(w);
    genxEndDocument(w);
}



svgstart(genxWriter w, int width, int height) {
    genxStartElementLiteral(w, NULL, "svg");
    genxAddAttributeLiteral(w, NULL, "width", "500");
    genxAddAttributeLiteral(w, NULL, "height", "500");
}

gerr(genxWriter w) {
  fprintf(stderr, "genx error: %s\n", genxLastErrorMessage(w));
  exit(1);
}

The command line:

cc grand.c libgenx.a && ./a.out | tidy -i -xml >f.xml

builds the program, and pretty prints the output via the tidy program; this way I can preview the generated xml in my editor window, and also inspect the graphic in the Batik SVG browser. It's fun playing with these; every picture is different and you can tell by quick inspection if the desired effect is working.

[genx work screenshot]

Observations

It's interesting to compare the sizes of the various representations. Here are the sizes of files that contain 100 circles and squares in varying colors and opacities on a 500x500 canvas:

Format Size (bytes) File
SVG 8,612 100.svg
SVG (pretty-printed) 9,523 100pp.svg
SVG (gzipped) 1,948 100.svg.gz
PDF 92,374 100.pdf
PNG 69,027 100.png
JPG (no compression) 110,303 100.jpg
JPG (0.75 quality) 33,042 100.75.jpg
JPG (0.50 quality) 24,488 100.50.jpg

Draw your own conclusions.

Final note: these programs are very fast (with no user-perceptible difference between generation via Literal vs. Pre-Declared items). On my 667 Mhz PowerBook:

./grand 1000 >1000.svg

runs in 0.131 seconds of real time.


ajs Creative Commons License
This work is licensed under a Creative Commons License.