Optimizing Vector Artwork

2020-06-03T00:00:00+00:00
Justin Brooks (justin@jzbrooks.com, https://jzbrooks.com/)

Vector artwork is pretty useful. It can scale nearly infinitely without graphical artifacting, interesting animations are straightforward, and it can frequently be smaller than the same image represented as a grid of pixels (often referred to as raster images). Many UI systems support some form of vector artwork. SVG, vector drawables, and vector PDFs are just a few.

Path Data ¶

Vector images build their visuals from paths. These paths can be drawn with a stroke or fill.

M <point> - Move to the specified coordinates without drawing a line, marking the beginning of a subpath.
L <point> - Draw a stright line from the current point to the specified coordinate.
H <number> - Draw a horizontal line from the current x position to the specified x position.
V <number> - Draw a horizontal line from the current y position to the specified y position.
C/S <points> - Draw a cubic bezier curve from the current point to the specified coordinate.
Q/T <points> - Draw a quadratic bezier curve from the current point to the specified coordinate.
A <arc_params> - Draw an elliptical arc.
Z - Draw a line from the current point to the last ’M’ command’s end coordinate, closing the path.

Here’s a pretty simple path and the resulting image.

M 10,30
A 20,20 0,0,1 50,30 # red
A 20,20 0,0,1 90,30 # yellow
Q 90,60 50,90       # blue
Q 10,60 10,30       # green

There’s a version of each of these path commands that use relative coordinates, rather than absolute coordinates. You’ll notice they begin with lower case letters. They can sometimes be a little more compact.

M 10,30
a 20,20 0,0,1 40,0
a 20,20 0,0,1 40,0
q 0,30 -40,60
Q 10,60 10,30

This is the same heart from above represented by fewer characters. It turns out there are many ways a vector’s path data can be made a little more compact, often with more pronounced results. Here are a few other ways we can stretch the savings even further:

Replace bezier curves that are basically straight with lines

Replace multiple bezier curves that lie on a circle with an elliptical arc (or two)

Use horizontal and vertical line curves if a line command only changes x or y

Bake geometric transformations directly into path data

Collapse or remove empty groups

Merge multiple paths into a single path

There are also tricks that allow omission of some whitespace. For example, you can write the ’q’ command from above as q0,30-40,60. ’-’ pulls double duty as an effective digit separator and negative sign.

Optimization Status Quo ¶

Optimizing vector artwork can make assets smaller, which is beneficial for reducing network overhead. App download sizes can impact an apps reach. Nobody is fond of slow websites. Being good stewards of our users resources by minimizing our footprint where we can is worthwhile. What’s more, certain optimizations can save clients a little rendering work. For example, baking transformations into path data helps vector drawables avoid doing a few matrix multiplications by way of the underlying graphics library, Skia, returning early if there’s nothing to do. Skia is also used by Chromium for 2D rendering, so the same should apply for SVG as well.

There are a few tools around that will do this sort of work for you. svgo is by far the most popular. It’s been around for quite some time and is still actively maintained. It largely operates on the image data directly as a string read from the file at each stage in the pipeline.

Android makes use of a different vector image format, vector drawables. This format is largely a modified subset of SVG. It has some quirky deviations that will hopefully be ironed out over time. It’s understood deeply by the rendering system. There’s also an adjacent format, animated vector drawables, that makes vector animations dead simple. The folks working on Jetpack Compose seem to be working on even newer representaitons. avocado is a pretty popular port of svgo to operate on vector drawables.

iOS provides some support for vector assets as vector PDFs. The build tools there generate device-specific raster images for each vector PDF, which reduces the utility of runtime scalability, app size, and animation potential. With the recent addition of SF Symbols, it seems like they’re working toward expanding vector support, but their documentation is unclear. macOS treats vector PDFs properly though by avoiding rasterizing them until the image needs to be rendered to the GPU framebuffer. So maybe proper vector support on iOS really is on the way. I’m unaware of tools a command line tool similar to svgo that operates on vector PDFs.

vgo ¶

vgo is a vector optimization tool. It differs from other tools in that it parses vectors artwork into an intermediate representation on which all of the optimizations are performed. There are a few benefits to this. The first is that it’s relatively easy to support new formats for vector artwork. Ideally, it only requires format specific file I/O operations that know how to read the target format into the IR and write the file from the IR. The second benefit is that the tool can also serve as a conversion mechanism between the formats. It isn’t uncommon for a designer to provide an SVG, which you eventually convert to a vector drawable and vector PDF for Android and iOS respectively. Since vgo maintains an intermediate representation, conversion can be pretty simple.

I’ve been working on it in my spare time for a little while now, but the tool is pretty young. I’d like to add support for more vector formats and improve the conversion feature to handle more complicated scenarios. A Gradle plugin for Android projects would also be nice. But hey, you’ve gotta start somewhere.

So if you’re in the market, try out the tool and provide feedback via issues on the GitHub issue tracker.