Tip of the Day: Vector rendering in Android

Post date: 04-Apr-2010 19:16:08

As part of getting to know the Android API better (and inspired by a passion for retro vector game art) I decided to jump into Android's 2D graphics system and see what damage I could do. On the right is the result, the source is over here, and below I'll talk a bit about what's going on. For those a little new to this kind of thing, vector graphics are a common alternative to bitmap graphics - where bitmaps involve sending around a grid of numbers representing the colour of pixels, a vector image is a set of instructions describing how do draw the image, using basic drawing operations. If you've ever seen programs like Inkscape or Illustrator or used Flash, then you've seen vector graphics in action - lines, curves, flat colour fills and gradients, and more.Android has an extensive vector drawing API, which it uses to build UI components and other graphical stuff - but we can use it for anything, including fancy animated effects, even games. On to my little app. TeethApp is a just a regular Activity, with the standard boilerplate OnCreate method. Ignoring a little bit of setup, the only interesting thing we're doing is not using the default layout XML file in the setContentView call - instead we're declaring a new instance of a nested class. So let's start there.class TeethView extends View Everything starts with a View. Views are the primary display building block in Android - everything from a full page window to the smallest widget subclasses this workhorse. TeethView is an incredibly simple example of this, overriding only two methods - onTouchEvent and onDraw (I'll get to the touch stuff later). onDraw is where we get to take control of how our View gets rendered, and we get passed in a Canvas object to work with. Canvas is a class that wraps all the drawing logic we need around a destination bitmap - the 'canvas' we'll be drawing on to. It does a lot of the heavy lifting for us like tracking where on the bitmap we're currently drawing (through different translations and rotations) so that all we have to do is pass it the drawing instructions, and some information about the style we want everything drawn in. Style? Well, we'll need to pick colours, keep track of details like drawing dotted or solid lines, and whether we want solid shapes or outlines... the details of which are kept in a Paint object. Here's some of the settings I use in this app:

Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setStrokeWidth(3);

That's anti-aliasing (smoothing) being switched on, and the width of lines that will be drawn being set to three pixels wide. Later in the code you can see I set the drawing colour using setColor to one selected from the colors list and, depending on whether the cog we're drawing is currently selected we set the drawing style to FILL (solid) or STROKE (outline) mode. (Please forgive my switching spelling of 'colour' between the Australian in text, and the American in code - when in Rome...)

The only remaining piece of the puzzle is the drawing instructions themselves, and the API gives us lots of options. The Canvas object can draw arcs, lines, do colour fills, draw pictures and text in any style, but I knew from the outset that I wanted to manage quite complex sets of instructions, and for that I needed a Path.

The Path class lets us encapsulate a group of drawing instructions, and makes it easy to build what we think of as a shape - a closed outline made up of straight and curved lines. You're not restricted, though, paths don't have to be closed - and It'll even let you build hollow shapes (like my cogs) if you're so inclined. The cogs I'm building aren't much more than just fancy Path objects so that's where I subclassed from, and the only tricky thing going on inside is a little bit of logic in the constructor that cooks a tooth count out of the cog's radius.

buildPath is where the action is - a lot of magic trigonometry to generate the points along the outline of the cog shape, then the real meat - the moveTo and LineTo calls. moveTo sets the next place we'll start drawing, LineTo describes a straight line from the current place to a new point somewhere else. In my code I generate points in advance and then draw between them, with a close call afterwards to connect the last point of the path with the first - not required, as I said, but appropriate for the shape we're trying to make.

Once we've built drawing instructions for the outer border we add an inner circle for style (oriented counter-clockwise to gel properly with the clockwise contour of the teeth) and we're done populating our Path. Back in my onDraw method the whole lot gets passed to Canvas.drawPath, with our painting instructions as a parameter - very simple, very neat. For fun I added an onTouchEvent handler so the user can drag the cogs around, and if I ever get really inspired there'll be enough brains to keep the cogs from intersecting, and maybe even cogs driving each other - who knows.

For now, I'm impressed with what I've seen - I could build a decent number of quite complex shapes and with virtually no optimisation I got a quite nice frame rate on my old G1. Take it for a spin yourself and let me know what you think. :)