SVG to CoreGraphics Conversion
I’ve got another tutorial type post for today, but it’s really equal parts: “here’s what I found that helped but didn’t quite work,” “here’s what I did,” and, “anybody have any better ideas?” If you already know something about Core Graphics and why/when to use it and just want the gist of what I did to convert SVG files to Core Graphics calls, go ahead and skip on down to “SVG to the Rescue.” Otherwise, read on to hear about my experience.
Why Core Graphics?
If you’ve spent any time programming for iOS or OSX, you’ve probably been exposed at some level to the Core Graphics frameworks, otherwise known as Quartz. (BTW, if you’re looking for documentation in Xcode, you have to search “Quartz” in Xcode to find the Programming Guide. Searching “Core Graphics” won’t get you anything helpful. A common occurrence with the Xcode documentation browser in my experience, but, that’s a rant for a different day.) The documentation is actually quite good at getting you up and running with the APIs. There’s also some great tutorials in the Graphics and Animation section here from Ray Wenderlich’s site. As a framework, Quartz is the name for the complete 2D drawing engine on iOS and OSX, and it covers everything from drawing to the screen, to working with images and PDFs, to color management, and also includes low-level support for drawing text. It’s resolution and device independent, meaning it’s great for generating interface elements for your iOS apps. No need to manually create multiple versions of each resource – iPhone, iPad, @2x for Retina displays (presumably @2x iPad Retina at some point in the future) – just render the interface at runtime, and as long as you do it right, the OS will handle all the scaling and render everything at the right resolution. It’s also perfect for those times when you need to draw dynamic interfaces that are based on some kind of data or input rather than a static “look” that you design and then display. Although it’s not exactly easy to reverse-engineer the Apple apps and see exactly what they’re doing, it’s safe to say that many of them are rendering directly in app with Core Graphics, rather than loading static images. The WWDC 2011 video, “Practical Drawing for iOS Developers” shows step by step how the Stocks app renders its views, entirely in Quartz. If you’re starting from scratch, the docs, WWDC videos, and tutorials will have you drawing lines, arcs, and basic shapes in no time, all with strokes, fills, and even gradients.
The problem I ran into was how to get past just the basics. The API’s for Quartz path drawing go something like this: move to this point, add a line to this point, add a bezier curve to this point with control points at these locations, etc. It’s relatively easy to think about and describe basic geometric shapes in these kind of terms, and Core Graphics even provides convenient methods for creating things like rounded rectangles and ellipses. Even complex views like the stocks app are still very much data/number driven types of views, and even though the drawing process itself is more complicated, it’s not hard to imagine how you would programmatically calculate and describe, say, points on a graph. But, what if you want to draw something a little more organic? What about more complex shapes with lots of curves?
Take this Quarter Rest symbol, for example. As a vector graphic in Illustrator, it contains 3 straight lines and 13 different Bezier curves, each with two control points – and even that is after trying to simplify it as much as possible without losing the desired shape. The problem quickly becomes apparent – it’s virtually impossible to establish a good mental connection between the graphic as conceived by the artist/designer, and the actual code used to produce it on screen. Bret Victor has a great write-up on this artistic disconnect when it comes to dynamic and interactive images/interfaces. It was immediately evident to me that trying to build this graphic in code, line by line – guesstimating and then tweaking the coordinates of the lines, curves and control points – could only end in one way: much swearing and me throwing my computer out the window in frustration.
The main reason I wanted to use Core Graphics rather than static images is to be able to display these musical symbols with dynamic coloring and shadows for some highlight/glow effects. Now, the shadow part of this is actually possible using pre-rendered images. You can set shadow properties like color, radius, distance, on any UIView (technically, on the CALayer of the UIView), including UIImageViews. Quartz will use the alpha values of the pixels to calculate where the edges are, and will generate a nice shadow behind the elements of the image. I say it’s possible, but it’s not actually that practical. Doing it this way requires an extra offscreen rendering pass, and the performance will very likely suffer. In my case, it completely tanked, from somewhere around 53-54 fps with normal content, to around 15 fps when adding a shadow to static images. In some situations, you could work around this by using the shouldRasterize feature of CALayer, but for dynamic content, this could actually make performance even worse. After my experiment, I knew there was no way around it but to keep working on some way to convert my vector images in Illustrator into something I could use in my app. Enter the .svg format.
SVG To the Rescue!
SVG – scalable vector graphics – is a widely used standard for storing, well, just what the name says. Most vector based graphics programs, including Adobe Illustrator, can open SVG, and will also export to SVG. Since SVG is a web standard, one option is to use a UIWebView to render the SVG to the iPhone screen, but that option doesn’t work for me. I googled far and wide for some kind of svg to Quartz converter, and had a bit of luck:
This site was a good start. It provides a resource where you can copy and paste the path data from an svg file, and it will export Quartz source code. I had some weird results from my files, though and a little difficulty figuring out the proper data to paste into the form.
Here’s an Objective-C class, under a Creative Commons Attribution license, which will also take the path data from an svg and output a UIBezier path (iOS) or NSBezier path (OSX).
I also found this library, but as of right now, the site appears to be down. Perhaps just a temporary issue. (UPDATE: Looks like it’s back up, and now that I can see it again, this page is mostly just a link to this on github. A library that takes SVG files and turns them into CAShapeLayers.)
I didn’t test any of these options extensively, but they appear to be good options, especially the second. If they work for you, then great! What they all have in common though is that you first need to extract the path data from the .svg file, meaning, I had to do some research on the standard anyway. Turns out, .svg is just an XML format that you can open in any text editor. And, even better, the svg path commands are very similar to the Core Graphics API’s. Each individual path is contained in a <path> tag, and the specific commands are in the “d” attribute. Here’s the file for that Quarter Rest symbol – open in the browser to see it rendered, or download and open in a text editor and you’ll see the path data clearly separated. The svg standard includes lots of fancy things like fills, strokes, gradients, patterns, masking, even animation, but all I’m using here is a simple, single path. Path commands in svg are single letters, with parameters following.
- M (x, y) – move to point.
- C (x1, y1, x2, y2, x, y) – Add cubic bezier curve to point (x, y) with control points (x1, y1 and x2, y2).
- L (x, y) – add line to point.
- Z is the command to close the current path.
- There’s also H, and V for horizontal and vertical lines, and S for curves with an assumed first control point relative to the last command.
Once I got the file into this format, each move was easily converted to Quartz API calls to build a path:
- CGPathAddCurveToPoint (where the point parameters are even in the same order as the SVG command)
- The “H,” “V,” and “S” commands don’t have a Quartz counterpart, so they need to be adapted.
Parsing the svg file by hand turned out to be a little challenging. For one thing, in the interest of keeping file size small, there are almost no separators between items. No whitespace, but also not many commas or other delimiters, wherever it can be omitted. For example, a “move” command, followed by an “add curve” command might look something like this: “M60.482,613.46c0,0-17.859,0.518-26.997,0″ Each place there’s a negative number, the separating comma is eliminated as unnecessary, and each command runs right into the next one, so it’s important to know that the parameters for each command come after the letter. Also, when the same command is used multiple times in a row, it’s possible according to the standard to omit the command the second time. So, a “c” followed by 12 numbers is actually two separate curve commands. One other catch: each of these svg commands is using absolute coordinates, but most of them also have a corresponding command using relative coordinates. These use the same single letters, but in lower case. For example, M 10 10 m 5 5 means move to absolute point (10, 10) and then move to (5, 5) relative to this point – so (15, 15) absolute. Unfortunately, Illustrator exports svg files using mostly these relative commands, so I also needed to convert them to absolute point values for Quartz.
You’re Still Reading? Wow!
OK, this was a long post, so if you’ve read this far, that means you’ve got some actual interest in the subject and/or need for an efficient way to get path data into Core Graphics. So here’s the part where you’re hoping I have the link to some parsing code or maybe a script I wrote that will do this work for you. But, I have to confess, I did all mine by hand. I only had a handful of symbols I needed for this project, each of which is a pretty simple, single path, so I did the parsing and converting by hand along with some rounding and cleaning up along the way. Maybe next time I’ll get it automated, but for now, it was a good exercise in exploring a new file format and diving deeper into Core Graphics. But, if you’ve given some of this a try yourself, I’d love to hear what you came up with! Did any of these other resources work well for you? Got any nice scripts to share with everyone else? Or, perhaps even more likely, am I way off track? Is there an even better workflow for getting from design in a visual editor to Quartz source code? If so, I’d be grateful to hear your ideas.
BONUS: of course, just as I’m finishing this post, I stumbled across yet another resource. From the looks of it, it might be the best yet. It’s an Objective-C parser that will take a portion of an Illustrator eps file and convert it to CGPath. Guess I’ll have to try that out now too! Method for Interpreting Illustrator Art Assets as Cocoa CGPathRef