We’ve talked about two of the biggest players in Android image format world (JPG & PNG) but it’s worth noting, that even with these advanced algorithms, there’s still a level of compression that these impressive formats can’t get to. To get there, we have to kick our concept of image creation to the curb, for something… a little more algorithmic.
Typically, images are arranged in a two dimensional grid of pixels, which represent the colors of the image itself. Well, to be more clear, a pixel is a set of color scalars that when blended together, make a final color, like RGB. Now, when viewed from a distance, the edging between the pixels disappears, and the human eye can see smooth color gradients. This type of image is called a “Raster” image. Basically, the image data is rasterized into a single 2D image, and that’s what’s sent around to other people.
The gist with raster images, is that their uncompressed growth is pretty linear; The larger the image dimensions, the more pixels. The more pixels, the more data. The more data the bigger the file. Taking a 2 megapixel image on your phone, and sending it (uncompressed) to a friend would yield a ~6mb image.
Compressing raster images, as we’ve mentioned, is a relationship of self similarity in an image. The more duplicate colors there are, the easier it is to compress. The more unique colors there are, the less. So you end up in a situation that photos of a field of trees or grass will generally compress less well than a flat image of white colors.
For example, take this image of a red circle on a white background. Even though it’s a pretty simple image, you’re going to end up having to compress all the raster pixels to the file.
This is a particularly difficult problem for Android developers, since supporting mdpi, hdpi, xhdpi, xxhdpi, xxhdpi and ludicrousHDPI means having the same image in many sizes inside your APK file. Having the above 3 images would result in an additional 55k that users would have to download, and even with awesome tech like Multiple APKs, chances are you still have some duplicate images sitting around at various sizes.
What if, instead of just sending around the final image of raster pixels, we instead, sent around a descriptor of how that image was made? We can draw the same image above, using commands of drawing operations:
This is the concept behind Vector Graphics. Rather than a file with a bunch of pixels, these image formats contain commands which draw primitives to the screen with various visual properties (FWIW this process is called rasterization). When all the commands are executed and combined together they produce a final output raster image.
Here’s the important part :
A single vector graphics file can generate a raster image @ any resolution; and the file size of the vector graphic stays the same.
It doesn’t matter if the image you’re generating is 16x16 pixels, or 8092x8092 pixels, a single vector file can generate them all.
The Scalable Vector Graphics (SVG) file format
One of the most popular implementations of Vector graphics has to be SVG. This SVG defines a quantized set of drawing operations that can be listed in the file format that have generally been agreed upon across various web & graphics editor platforms. Below is an SVG file, and the results of it being rasterized.
Of course, one of the downsides of being widely popular, is that the SVG format tends to be pretty bloated. As the standard has evolved, so has the need to add new drawing modes, blending modes, support for legacy operations, and more verbose syntax to describe the visuals needed.
Also, SVG uses XML style syntax, and in most implementations, exists entirely in human readable form. Which means complex numerals, like “43.00102302” will be represented in 11 bytes of a string, rather than 4 bytes of a floating point value. (And even though most web developers GZIP their SVG files, the savings isn’t as aggressive as a binary format could be)
This is why Android doesn’t provide native support for SVG: Android is size conscious. Supporting the full range of SVG features would allow for very large, very complex files to be created, which is kinda the opposite of what we’re going for here…
As such, Android has defined its own vector format called VectorDrawable; It works much like SVG, but uses a smaller portion of drawing commands. The process is identical : Load a VD file, rasterize it to a bitmap, use in your app. Boom.
Although VDs are also using XML as their format, they end up getting compiled into a binary serialization format for distribution in the APK, reducing their overall size.
So the savings here is pretty simple, using a VectorDrawable to generate ever resolution of PNGs you need, rather than storing the PNGs directly, can save you a lot of space.
One other nifty benefit of VectorDrawable, is that they can define animations. If you try to do animations with PNG files, you typically end up with each frame of the animation saved out to file, looking something like this :
With VectorDrawables, you could define that same animation, but entirely through XML markup, reducing the file size significantly.
Not a silver bullet
While Vector Formats have the win when it comes to file size, there’s a few caveats that you need to be aware of, which can limit when, and how they are used in your Android apps.
Smaller is better
For Android, the rasterization process which converts a VectorDrawable to a bitmap happens on the CPU. As such, there’s mostly a linear relation between the processing time required to create an image, and the resolution / dimensions of the image itself. Rasterizing a triangle into a 16x16 pixel texture may only require computing color fragments for 8–12 pixels total. Doing the same to a 1024x1024 texture would touch over a hundred thousand pixels, which obviously is going to take more time to do.
As such, as the texture size your createing gets larger, there’s going to become a break-even point where the load time hit becomes significant, and it no longer makes sense to use a Vector Drawable.
Tip: VectorDrawables are best used for smaller images
Keep it simple
One of the limitations of the Vector Graphics format is that it can only represent a certain type of image quality. That is to say that vector images tend to be overly simplistic, only using a set of primitive types to define how to generate colors on the screen . The more complex your image, the more shapes you’re going to end up with in order to represent those visuals, which will increase file size, and load time.
Basically, when it comes to VDs, try to stick to visuals that can be represented with simple shapes. This makes them super useful for any type of icon, or UI resource that you might need.
Tip: VectorDrawables are best used for low-complexity images
Compress the planet
Vector formats are exceptionally powerful with respect to how much content you can generate for such a small data footprint. But the concept extends well beyond the mobile & web frameworks.
If you’ve ever wondered how the DemoScene gets their content into such a small format, it’s from a very similar process. Basically their tooling system combines together various mathematical algorithms in a graph form, to produce the visuals that are used on the screen, accounting for both 2D and 3D assets. .werkkzeug is one of the more famous tools for the DemoScene, although Substance has been gaining traction with game developers over the past decade or so.
Which goes back to that old saying “When in doubt, use math.”
Want to know more about how JPG files work, and how to make them smaller?
What about how PNG files work, or how to make them smaller?
Want to become a data compression expert?
Want to write faster, smoother Android Applications?