May 2013

Dev diary: SVG markup optimizations

1 SVG optimizations

So far, L-systems generated by Malsys as SVG were rendered in very primitive way. Every line segment was one tag of SVG line element. Thanks to GZip compression the files were not that huge because average compression ratio was more than 90%. The files looked like code in Code listing 1. There were many sources of data redundancies and ineffectiveness:

  1. Ending point of many line segments was often starting point of the very next one so lines could be concatenated to polyline element,
  2. coordinates had often unnecessary too many digits like 0.333333333 because of used double precision floating point format,
  3. some color codes could be shortened like #FFFFFF to #FFF, and finally
  4. colors and stroke widths were often the same so groups could be used to optimize the markup.

Following text talks about mentioned SVG markup optimizations together with some stats.

Code listing 1: Original non-optimized SVG code.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC ...>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-3 -169.277 582 172.277" ...>
 
<line x1="0" y1="0" x2="64" y2="0" stroke="#000000" stroke-width="2" />
<line x1="64" y1="0" x2="96" y2="-55.426" stroke="#000000" stroke-width="2" />
<line x1="96" y1="-55.426" x2="128" y2="0" stroke="#000000" stroke-width="2" />
<line x1="128" y1="0" x2="192" y2="0" stroke="#000000" stroke-width="2" />
...
<line x1="384" y1="0" x2="448" y2="0" stroke="#000000" stroke-width="2" />
<line x1="448" y1="0" x2="480" y2="-55.426" stroke="#000000" stroke-width="2" />
<line x1="480" y1="-55.426" x2="512" y2="0" stroke="#000000" stroke-width="2" />
<line x1="512" y1="0" x2="576" y2="0" stroke="#000000" stroke-width="2" />
 
</svg>

1.1 Lines to paths

The biggest source of redundancy are lines. SVG has very nice tag called path which enables to concatenate consequent line segments. Path tag works that you can specify move (M) and line (L) actions similarly to L-systems' MoveTo and DrawTo actions. Code showed in the introduction Code listing 1 is optimized to code in Code listing 2.

This brings huge savings among L-systems which are one line with same thickness and color. This optimization is enabled by default but it can be toggled with optimizeSvg settable property of SvgRenderer2D.

Code listing 2: SVG optimized by converting lines to paths.
1
2
3
4
5
6
7
8
9
10
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC ...>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-3 -169.277 582 172.277" ...>
<path stroke="#000000" stroke-width="2" d="
M 0 0
L 64 0 L 96 -55.426 L 128 0 L 192 0
L 224 -55.426 L 192 -110.9 L 256 -110.9 L 288 -166.3
L 320 -110.9 L 384 -110.9 L 352 -55.426 L 384 0
L 448 0 L 480 -55.426 L 512 0 L 576 0" />
</svg>

1.2 Coordinates precision

Previously, floating point numbers were always rounded to 3 decimal digits. This was unnecessary for large numbers and negatively affected precision of small numbers. Here are some exaples:

  • -87.002 → 87
  • -105.579 → -105.6
  • 0.005 → 0.004921

Better solution is to round to certain number of significant digits. This will save some characters and even improves the precision. The default number of significant digits was set to 4. Why 4? Empirical experiment on Hexa-Gosper curve which walks on non-whole point locations shown that 3 is not enough (see Figure 1).

It is also possible to change this number to any value with outputSignificantDigitsCount settable property of SvgRenderer2D component.

The rounding algorithm was improved to not loose precision if it will not save any characters. See the following example.

  • 11564.781 → 11564 (not 11560)
  • 20247.025 → 20247 (not 20250)

Since SVG standard supports scientific notation of floating point numbers, number in scientific notation it is placed to places where it save some characters. This is useful only when L-system is very tiny or very huge.

  • 0.0005791 → 5791e-6 (not 0.0005791)
  • 202478.025 → 2025e2 (not 202478)
  • 20001.2 → 2e4 (not 20001, this is neat trick)
2 significant digits — Comparison of impact of significant digits on L-system precision (zoomed 2 times).
(a) 2 significant digits
3 significant digits — Comparison of impact of significant digits on L-system precision (zoomed 2 times).
(b) 3 significant digits
4 significant digits — Comparison of impact of significant digits on L-system precision (zoomed 2 times).
(c) 4 significant digits

1.3 Simplification of some color codes

This is very simple simplification. If hexadecimal code of color has same pair of digits for all three channels R, G and B, they can be expressed as three-digit number instead of six. This might sound like very rare case but many L-systems are just black or solid color.

  • #000000 → #000
  • #FFBB00 → #FB0
  • #33AA00 → #3A0

1.4 Results

Following charts in Figure 2 present size reduction using all described techniques.

JavaScript is needed for visualization of this chart.
(a) Raw SVG sizes comparison
JavaScript is needed for visualization of this chart.
(b) G-zippled SVG sizes comparison
Circular tile — L-systems listed in Figure 2
(a) Circular tile
Sierpinski trangle — L-systems listed in Figure 2
(b) Sierpinski trangle
Dekkings church — L-systems listed in Figure 2
(c) Dekkings church