Generating SVG Images from C++ Programs

Philip W. L. Fong   <pwlfong@cs.uregina.ca>

$Date: 2005/09/19 20:00:41 $


A Drawing Problem

Suppose we are to write a program that takes an input file containing two positive integers and generates an SVG image that shows the relative magnitudes of the numbers. To be specific, we would like to draw a rectangular bar containing two segments of different colors. The length of each segment should be proportional to the magnitude of a corresponding number.

Here is an example input file, with filename gensvg.txt.

32 64

The program should generate the following image.

Solution

Let us put the bar into an SVG image of size 100 × 30. We also want to give a margin of 10 on each side of the image. Let us document these decisions into a number of constant declarations.

const unsigned IMG_WIDTH = 100;
const unsigned IMG_HEIGHT = 30;
const unsigned IMG_MARGIN = 10;

Given these numbers we then want to deduce the dimensions of the retangular bar.

const unsigned BAR_WIDTH = IMG_WIDTH - 2 * IMG_MARGIN;
const unsigned BAR_HEIGHT = IMG_HEIGHT - 2 * IMG_MARGIN;

Basically, the width of the bar is the width of the image minus the margins on the two sides. The height of the bar is calculated in a similar formula.

With the dimensions fixed, let us get down to the generation of the image. Every SVG image begins with a standard prolog, and ends with a standard epilog. Let us write a pair of void functions that generates the prolog and epilog of our image.

#include <iostream>

using namespace std;

void header() {
  cout << "<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>" << endl
       << "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.0//EN\"" << endl
       << " \"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd\">" << endl 
       << "<svg width=\"" << IMG_WIDTH 
       << "\" height=\"" << IMG_HEIGHT << "\">" << endl
       << "<title>Bar</title>" << endl;
}

void footer() {
  cout << "</svg>" << endl;
}

The hardest job is to paint the two segments representing the relative magnitudes of the input numbers. Suppose the two numbers are m and n. Then the segment representing m should have the following length.

BAR_WIDTH * m / (m + n)

Basically, m / (m + n) is a ratio representing the proportion of the bar that should represent the magnitude of m. Multiplying this ratio by the width of the bar (BAR_WIDTH) gives us the length of the segment representing m.

The length of the segment representing the magnitude of n can be easily obtained by subtracting the above quantity from BAR_WIDTH.

Here is a function that draws the two segments.

void bar(unsigned m, unsigned n) {
  unsigned m_width = (unsigned) (((double) m) / ((double) m + n) * BAR_WIDTH);
  unsigned n_width = BAR_WIDTH - m_width;
  cout << "<rect x=\"" << IMG_MARGIN << "\" y=\"" << IMG_MARGIN << "\""
       << " width=\"" << m_width << "\" height=\"" << BAR_HEIGHT << "\""
       << " style=\"stroke: none; fill: red;\" />" << endl;
  cout << "<rect x=\"" << IMG_MARGIN + m_width << "\" y=\"" << IMG_MARGIN << "\""
       << " width=\"" << n_width << "\" height=\"" << BAR_HEIGHT << "\""
       << " style=\"stroke: none; fill: green;\" />" << endl;
}

We use double precision floating point arithmatic to compute the ratio, and casting the result back to integer. We also have not forgotten to skip over the margins when we specify the placement of the segments. We have decided to use red to highlight the segment representing m, and green for n.

We then put everything together, and obtain the following program.

//
// gensvg.cpp
//

#include <iostream>

using namespace std;

// Constant declarations specifying the dimensions of the image and
// the rectangular bar.

const unsigned IMG_WIDTH = 100;
const unsigned IMG_HEIGHT = 30;
const unsigned IMG_MARGIN = 10;
const unsigned BAR_WIDTH = IMG_WIDTH - 2 * IMG_MARGIN;
const unsigned BAR_HEIGHT = IMG_HEIGHT - 2 * IMG_MARGIN;

// Prolog for the SVG image

void header() {
  cout << "<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>" << endl
       << "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.0//EN\"" << endl
       << " \"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd\">" << endl 
       << "<svg width=\"" << IMG_WIDTH 
       << "\" height=\"" << IMG_HEIGHT << "\">" << endl
       << "<title>Bar</title>" << endl;
}

// Epilog for the SVG image

void footer() {
  cout << "</svg>" << endl;
}

// Generation of the two segments

void bar(unsigned m, unsigned n) {
  unsigned m_width = (unsigned) (((double) m) / ((double) m + n) * BAR_WIDTH);
  unsigned n_width = BAR_WIDTH - m_width;
  cout << "<rect x=\"" << IMG_MARGIN << "\" y=\"" << IMG_MARGIN << "\""
       << " width=\"" << m_width << "\" height=\"" << BAR_HEIGHT << "\""
       << " style=\"stroke: none; fill: red;\" />" << endl;
  cout << "<rect x=\"" << IMG_MARGIN + m_width << "\" y=\"" << IMG_MARGIN << "\""
       << " width=\"" << n_width << "\" height=\"" << BAR_HEIGHT << "\""
       << " style=\"stroke: none; fill: green;\" />" << endl;
}

// Read input and generate SVG image

int main() {
  unsigned m, n;

  cin >> m;
  cin >> n;

  header();
  bar(m, n);
  footer();

  return 0;
}

To compile the program, we type in the following command.

$ g++ gensvg.cpp -o gensvg

Let us execute the program to see if the right SVG tags are generated.

$ ./gensvg < gensvg.txt
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN"
 "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg width="100" height="30">
<title>Bar</title>
<rect x="10" y="10" width="26" height="10" style="stroke: none; fill: red;" />
<rect x="36" y="10" width="54" height="10" style="stroke: none; fill: green;" />
</svg>

The output seems to look fine. Let us store it into a file gensvg.svg.

$ ./gensvg < gensvg.txt > gensvg.svg

Let us then rasterize the SVG image, so that we may check if the image looks as we have expected.

$ rsvg gensvg.svg gensvg.png

A PNG image file gensvg.png is generated as a result. To view this image, we fire up mozilla (or Netscape), point to the File menu, and select Open File to open the PNG image.

You can tell that the program is correct, because the red segment is half of the length of the gree segment, reflecting the relative magnitude of 32 and 64.

Subtleties

We could also use the command xv to view the image.

$ xv &

You will notice that the background becomes black. What happens is that the image has a transparent background: we only draw the bar without filling the background. This does not happen in Mozilla because the browser assumes white as the default background color.


$Id: gensvg.html,v 1.1 2005/09/19 20:00:41 pwlfong Exp $

Valid XHTML 1.0!