Wednesday, May 23, 2012

How basic SVG curves work

I've had to deal with SVG graphics for a website and have been using the excellent (and free) Inkscape to work with them. However the graphical interface can be a little inexact when it comes to drawing lines, particularly curves, with alignment and symmetry being hard to control at times.

Fortunately as SVG is an XML language all the points can be manually altered provided one knows what the points mean. I didn't and looking around to find out how they worked didn't provide me with any great insight.  After experimentation it really is quite logical provided one keeps track of which co-ordinate one is changing when dealing in relative terms.

I'm going to create a curve like so in Inkscape:

First off draw a straight line using the" Draw Bezier curves and straight line" tool (Shift+F6). It doesn't matter where this is drawn this is just to create the element so it can modified. Now open the Edit|XML Editor (Shift+Ctrl+X). The straight line element should be be selected in the editor. The attribute that is going to be modified is "d"
That's a whole bunch of numbers with the odd letter thrown in, but what do they mean? Firstly if a letter is upper-case it's an absolute reference; if it's lower-case it's a relative reference. Absolute means move directly to those co-ordinates; a relative reference means move that distance away from where you currently are. By default relative references are used; they allow the object to be moved more easily around without messing up the shape.

Onto the letters and the first we see is "m" this means move to the following co-ordinates; in this case 102.56047, 505.74392. This represents where the first point or node of our object is to be. At this juncture however there is a small anomaly. By default SVG treats the origin (0,0) as being in the top left hand corner; Inkscape treats it as being in the bottom right which is a little insane. This means this first co-ordinate is unlikely to match up with what Inkscape displays. It also has another affect which I'll get to. Just bear it in mind as we continue.

To keep life simple I'm going to change this to m 100,500 click "Set" and the line will jump slightly upwards.

Our next letter is c which means I'm drawing a curve using relative positioning. Reading various other guides it states that a curve starts at point A heads towards B and then approaches D from C; yeah. In graphical terms B and C are the control points by the end it'll look like this:

Delete all the points between c and z we're going to write our own in. As the the first point is already defined by "m" the next co-ordinate is going to be a control point. I want the line to arch up quite high and to the right say 5 to the right and 15 up; as this is a lower case "c" that'll be relative to the initial "m" start point. So that makes c 5,-15.

Wait why "-15"? It's that origin being set at the top left. If you head away from the origin it's positive; towards it's negative. So going right is positive, going left is negative and going down is positive, going up is negative.

With the first control point set the next co-ordinate is another control point. I want this curve to be symmetrical so it'll have the same height, but I want it a little further to the right say another 5 pixels. Given this is relative one might expect that it should be 5,0; just another five pixels to the right of the last co-ordinate. That would be too easy. It is in fact relative to the last node which is still at "m" so it's still 15 pixels up and another 5 pixels to the right making 10,-15.

Both control points set makes the next co-ordinate a node. The last one at "m" was 5 pixels away from its control node so this one should match up. Again from the "m" node I want to move 15 points to the right, but stay at the same level, thus 15,0. At this point hitting "Set" produces a flat arch:
I don't want it to be flat I want it to curve. That means another control point. As another node has just been placed the next set of co-ordinates are going to be relative to that. I want the control point to the left and not too high say -3,-2. Another control point follows. As I know the curve is 15 pixels wide and I want it to be symmetrical it needs to be at the same height and 3 pixels away from the end point at "m". 15-3=12 so the next set of co-ordinates are -12,-2.

Finally I need to set the last node which is the same place as "m". Still relative to the last node this makes it -15,0.

One last letter "z"  this means close the path; but the path is already closed when the last co-ordinate was added so this can be deleted. Why not miss the last co-ordinate and just add a "z"? Because it won't draw the final bottom line due to needing all four co-ordinates.

The "d" attribute should now appear like this:

m 100,500 c 5,-15 10,-15 15,0 -3,-2 -12,-2 -15,0

Hitting "Set" will produce this curve. Select "Edit paths by node" (F2) in the main window and click on one of the nodes and you'll see our curve which is made up of the following nodes and control points:
Now I could have drawn it the other way around:

m 100,500 c 3,-2 12,-2 15,0 -5,-15 -10,-15 -15,0

and produced exactly the same shape. I could have started at the other node and drawn it from right to left; it doesn't matter. Knowing how the co-ordinates are set allows perfect positioning with symmetry and identical curves across one object that would be time consuming to attempt using a mouse.

Have a play with one set of co-ordinates to see how they pull and push at the curve; note how if the last control nodes are set to -3,-5 and -12,-5 the ends become flat (with default settings). This is due to the stroke style and mitre limit that can be found in the "Fill and Stroke" dialog box (Shift+Ctrl_F). Changing those will create or remove the sharp end points.