Anger. Frustration. Sadness. I cycled through all the emotions, but always ended up in the same place, with the same realisation: drawing arcs with the little green robot sucked, and I had absolutely no clue how to fix it.
I am an Android developer. It’s my job, but I do it out of passion: every day I can’t wait to build something new with this amazing platform, which I often praise for its openness and huge set of features over, well, alternative mobile systems.
Yet sometimes — actually, more often than it should be — my love is truly put to the test. Such a case presented itself on a cursed Friday, 2 weeks ago, when something simple turned out to be… Not.
I work at Clue, and as I’m developing the Android app from scratch, I need to do a lot of custom drawing (see the image before this text, which is the main screen). All things considered, it’s generally a straightforward process: every complex shape can be divided into a set of primitive shapes like lines, squares, circles or… Arcs.
That day my goal was to draw a circle with some overlapping arcs; as expected the framework provides the handy function Canvas.drawArc(). Looks easy, right?
This is what appeared on the screen of my device:
The arcs are visibly “wobbly” and if two arcs don’t have the same start/length, then the start/end points won’t perfectly match!
As a professional, I followed protocol:
- google for a solution (and find none)
- don’t panic
- try turning on/off hardware acceleration
- try adding the arc inside a Path and then drawing that on the Canvas
- try every combination of Canvas/Path rotation
- google again for a solution (and find none, again)
- stare blankly at the screen for several minutes
- restart the computer/device several times compulsively
- go in a dark corner and cry
As the steps above didn’t fix the problem, I started digging into the source code and found that, unsurprisingly, Canvas.drawArc() wraps a native call to Skia, the library that Android uses to render everything. Further exploring uncovered that Skia is not able to draw a “real” arc — as it does with circles, for example — but approximates it to a certain numbers of Bézier curves, and does it badly.
At this point, I could think of these options:
- create a circle in an offscreen Bitmap and then “cut away” the unnecessary portion: ugly and inefficient, but would do the trick
- use OpenGL: total overkill
- more crying in the dark corner: better than the above
The work day was over but I simply couldn’t get it out of my head. That’s why, over the weekend, I searched and experimented, until I found this blog post (“More About Approximating Circular Arcs With a Cubic Bezier Path”) that links to and corrects this technical paper (“APPROXIMATION OF A CUBIC BEZIER CURVE BY CIRCULAR ARCS AND VICE VERSA”). In short, it explains that a Bézier curve can’t perfectly “describe” an arc, but it can approximate it very well, and the more curves are utilised, the better. It also provides various equations, which are what I used to implement a working example of the technique.
The results speak for themselves:
Now not only are the arcs consistent (as far as they can be), but the start/end points finally match! The mystery as to why Google engineers never fixed this, when it took me about half a day to research and create a working solution, are beyond my comprehension.
At Clue we care about sharing, that’s why we decided to open-source this finding (and more to come).
The lesson here is that despite dark corners being useful, from time to time, for the occasional cry of despair, never give up when looking for a way to beat the system… The resistance needs you!
I would love to hear other stories related to this or similar problems. They are frustrating, make you want to assume fetal position and hum quietly, but at the end of the day, when you finally solve them, they give you an incomparable satisfaction! How would you, or have you, solved this? I should also mention that we are always on the look for amazing developers of all kinds, either for hire or just to have some nice exchange of ideas… Feel free to get in touch!