Write Inkscape extensions that modify objects

Tutorial – Tremble

Article from Issue 240/2020
Author(s):

Writing your own extension for Inkscape opens up a whole world of possibilities. Apart from creating new objects, you can modify existing objects and even animate them.

In last month's issue [1], we saw how to write an extension that rendered an object (a circle) in an Inkscape document. But apart from creating new objects, Inkscape extensions can also be used to modify existing objects. Let's see how this can be done be creating an extension that will generate "wobbly" animation. The idea is to take a given path – say, a piece of text – and to move its nodes around a little bit. Then save the result as a frame, move the nodes a little more, save again, and so on. When you put the frames together, it will give the impression that the text wobbles. This video shows how to do it in After Effects [2], but it seems like a lot of work for something that could be done with a relatively simple script.

Unfortunately, documentation and tutorials explaining how to create this type of script are all but nonexistent. Again the only way forward is to wade through source code and comments within code [3] to try and figure out what tools are available to achieve our ends.

What Is a Node?

Any object in Inkscape can be broken down into a bunch of paths, and paths, in turn, are made up by a bunch of nodes. So the key to modifying any object is modifying its nodes. But what is a node? You may think it is the point on a path where the path can change direction. In fact, that is just one control point, and a node is made up of three control points.

If you have used Inkscape to any extent, you will be familiar with this: Draw a Bézier line with several segments from top to bottom. Using the Edit Paths by Nodes tool, select all the nodes (Ctrl+A) and make the segments curve using the button in the toolbar. For added clarity, make the selected nodes symmetric, and you will end up with something like what you can see in Figure 1 (left).

Figure 1: A node is made up of three control points: the point where the path changes direction and the two handles that define the curvature at said point.

The three control points, in order, are as follows: control point 0 is the control handle at the top, control point 1 is on the curve itself, and control point 2 is the handle at the bottom. If you drew the curve from bottom to top (Figure 1, right), the order would be inverted. Likewise if you drew from left to right, the first control point would be on the left, and drawing from right to left puts the first control point by default on the right.

Of course, you can grab the control points and move them anywhere you want, inverting their position, for example, but then you get loops and whorls in your path.

How do we know all this? Not through any documentation on Inkscape, but you can figure it out by experimenting with the extension shown in Listings 1 and 2.

Listing 1, cps.inx, is a very basic interface to the extension. Read the sister article to this one to get the details [1]. In essence, it reads in three parameters, CPS0, CPS1 ,and CPS2 (lines 7, 8, and 9), one for each control point of each node. The parameters then get passed off to a Python script (line 19) that does the actual work.

Listing 1

cps.inx

01 <?xml version="1.0" encoding="UTF-8"?>
02 <inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
03
04  <name>CPS</name>
05  <id>org.linuxmagazine.inkscape.effects.cps</id>
06
07  <param name="CPS0" type="float" gui-text="CPS0:" min="-10" max="10">1</param>
08  <param name="CPS1" type="float" gui-text="CPS1:" min="-10" max="10">1</param>
09  <param name="CPS2" type="float" gui-text="CPS2:" min="-10" max="10">1</param>
10
11  <effect>
12   <object-type>path</object-type>
13   <effects-menu>
14    <submenu name="Modify Path"/>
15   </effects-menu>
16  </effect>
17
18  <script>
19   <command location="inx" interpreter="python">cps.py</command>
20  </script>
21 </inkscape-extension>

The cps.py script (Listing 2) receives the parameters on lines 9, 10, and 11, and adds them to the coordinates of each of the control points of each node in the selected path (lines 24 to 29). Note that each node has an x and a y coordinate, so for example, the coordinates for control point 0 are csp [0][0] (x coordinate) and csp [0][1] (y coordinate).

Listing 2

cps.py

01 #!/usr/bin/env python
02 # coding=utf-8
03
04 import inkex
05
06 class CPS (inkex.EffectExtension):
07
08  def add_arguments (self, pars):
09   pars.add_argument ("--CPS0", type=float, default=1, help="CPS0")
10   pars.add_argument ("--CPS1", type=float, default=1, help="CPS1")
11   pars.add_argument ("--CPS2", type=float, default=1, help="CPS2")
12
13  def effect(self):
14   for node in self.svg.get_selected(inkex.PathElement):
15    path = node.path.to_superpath()
16
17    for subpath in path:
18     closed = subpath[0] == subpath[-1]
19     for index, csp in enumerate(subpath):
20      if closed and index == len(subpath) - 1:
21       subpath[index] = subpath[0]
22       break
23      else:
24       csp[0][0] += self.options.CPS0
25       csp[0][1] += self.options.CPS0
26       csp[1][0] += self.options.CPS1
27       csp[1][1] += self.options.CPS1
28       csp[2][0] += self.options.CPS2
29       csp[2][1] += self.options.CPS2
30
31    node.path = path
32
33 if __name__ == '__main__':
34  CPS().run()

Save cps.inx and cps.py to your $HOME/.config/inkscape/extensions directory and the CPS… extension will appear under the Extensions | Modify Path menu next time you start Inkscape.

If, for example, you input 5 into the CPS0 field in the extension's dialog, the first handles of all the nodes in the selected path will move down and to the right five pixels, millimeters, or whatever unit you are using, from their original position as shown in Figure 2. Note that position (0, 0) is located by default at the upper left-hand corner of the page in Inkscape, so x coordinate plus five is five units to the right and y coordinate plus five is five units down.

Figure 2: The CPS extension will help you figure out which control point is which among the nodes on a Bézier curve.

Moving Nodes

The implication of all of the above is that, to move a node, you have to move each of its three control points (handles and position on curve) all at the same time … or not, if you want an even messier wobble.

This is what the Jitter Nodes… extension does. This extension is shipped by default with Inkscape and you can find its code in the /usr/share/inkscape/extensions directory. Indeed, jitter.py is the inspiration (read "I blatantly ripped it off") for Tremble, my own extension shown in Listings 3 and 4. Tremble is the extension that makes trembling, wibbly-wobbly animations out of selected objects.

The inx file, tremble.inx (Listing 3), is pretty standard. It allows you to decide the amount of wobbliness to apply to each node (line 7), how many frames to generate (line 9), and where to save the frames (line 10).

Listing 3

tremble.inx

01 <?xml version="1.0" encoding="UTF-8"?>
02 <inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
03
04  <name>Tremble</name>
05  <id>org.linuxmagazine.inkscape.effects.tremble</id>
06
07  <param name="radius" type="float" gui-text="Radius:">1</param>
08  <param name="accumulate" type="bool"  gui-text="Accumulate changes">false</param>
09  <param name="frames" type="int"   gui-text="Frames:" min="1" max="250"/>
10  <param name="folder" type="path"  gui-text="Save frames in:" mode="folder"/>
11
12  <effect>
13   <object-type>path</object-type>
14   <effects-menu>
15    <submenu name="Modify Path"/>
16   </effects-menu>
17  </effect>
18
19  <script>
20   <command location="inx" interpreter="python">tremble.py</command>
21  </script>
22 </inkscape-extension>

The latter is the only new parameter type in this interface; all the rest we saw in the previous article. The path parameters lets the user input or select a path to a file (mode = "file") or a folder (mode = "folder"), either to load it into Inkscape or save something later to disk. You can give the user the option of opening several files (mode = "files") or folders (mode = "folders") at the same time or create a new file (mode = "file_new") or folder (mode = "folder_new").

The script that does the actual work, tremble.py (Listing 4), is also not terribly complicated. Up in the heading, you import Inkscape's inkex module, which contains many of the methods and attributes you need to write extensions. You will also need Python's random module. Finally you will be using something from Inkscape's command module.

Listing 4

tremble.py

01 #!/usr/bin/env python
02 # coding=utf-8
03
04 import random
05 import inkex
06 from inkex import command
07
08 class Tremble (inkex.EffectExtension):
09
10  def add_arguments (self, pars):
11   pars.add_argument ("--radius", type=float, default=1, help="Radius")
12   pars.add_argument ("--frames", type=int, default=1, help="Frames")
13   pars.add_argument ("--folder", type=str, help="Path")
14   pars.add_argument ("--accumulate", type=bool, help="Accumulate deformation")
15
16  def effect(self):
17   for node in self.svg.get_selected(inkex.PathElement):
18    path = node.path.to_superpath()
19
20    for frame in range (1, self.options.frames):
21     for subpath in path:
22      closed = subpath[0] == subpath[-1]
23      for index, csp in enumerate(subpath):
24       if closed and index == len(subpath) - 1:
25        subpath[index] = subpath[0]
26        break
27       else:
28        delta = random.uniform (-self.options.radius, self.options.radius)
29        csp[0][0] += delta
30        csp[0][1] += delta
31        csp[1][0] += delta
32        csp[1][1] += delta
33        csp[2][0] += delta
34        csp[2][1] += delta
35
36     node.path = path
37     command.write_svg(self.svg, self.options.folder + "/frame" + f'{frame:03}' + ".svg")
38
39 if __name__ == '__main__':
40  Tremble().run()

The command module includes, among other things, some interesting methods, like take_snapshot (), which saves a bitmap snapshot of the current SVG loaded into Inkscape; and write_svg (), which saves the SVG generated by Inkscape to a file. You will use the latter a bit later in your script as you can see on line 37.

Inkscape's add_arguments () method (lines 10 to 14) is what Inkscape's interpreter expects to find when it needs to read in the parameters coming from the inx file. The --radius parameter (line 11) is read into self.options.radius, the --frames parameter (line 12) is read into self.options.frames, and so on. Notice how there is not a special type for the path held in the --folder parameter: It is just a regular str type.

The real action starts in the effect () method (lines 16 to 37), another of the standard methods that Inkscape's interpreter expects and what it runs when it is done reading in parameters.

The first thing you need to do is to grab the information from the selected object. Your script can find the nodes of the currently selected object using the get_selected () method from inx's svg module (line 17). The parameter inkex.PathElement tells get_selected () what sort of object it should expect, in this case, a path.

This allows you to loop over each path the nodes belong to and turn them into superpaths (line 18)!!! Okay … to be fair, there is nothing that much to get excited about here, despite the name. Superpaths are just an internal construct that makes it simpler for Inkscape's interpreter to calculate the changes that you will inflict on the path by modifying the node.

Let's hold line 20 for a moment and move on to line 21.

As each object can be made up of several paths (an object created from the letter "i" for example, has two paths: the body of the letter and the dot), you have to iterate over each subpath (line 21) and iterate over every control point of each individual node (line 23).

First, though, you have to check to see if the object is closed (line 22). The last node in a closed body coincides in its location with the first one, but internally the first and last nodes are two different items in the SVG markup. You check by comparing the first subpath (subpath [0]) with the last subpath (subpath [-1]). If they are the same, you must treat the object as closed (lines 24 to 26). This means that when you reach the last node, you need to skip messing with it and quit the loop (line 26), because you are done.

Just to get back to the to_superpath () method a second, it splits all the nodes into two parts: the index, which you can see being used on lines 23, 24, and 25, is the number of the node (the first node is 0, the second is 1, etc.). This allows you to know when you have reached the last node in the subpath, as you can see in line 24.

The second part is the data regarding the control points, which we talked about above. Once you calculate the delta that you are going to apply to each node (i.e., the amount by which you are going to move the node), you add it to each of the x and y coordinates of the handles (lines 29, 30 and 33, 34) and the point on the curve itself (lines 31 and 32).

Once you have dealt with all the nodes in all the subpaths, you can dump the superpath path, with the modified values, back into nodes.path. The changes get written to the internal SVG structure, and you will see the changes appear in your Inkscape drawing.

The script then saves the svg object to the folder you chose on line 37.

Of course, you have to do this as many times as the number of frames you want. That is why you envelop the path-wobbling process in a loop on line 20.

Copy both tremble.inx and tremble.py to your $HOME/.config/share/inkscape/extensions folder, and it will be ready to go the next time you start Inkscape.

Workflow

You make the most of this extension like this:

  1. Draw the thing you want to animate and select it. Convert it to a path by picking Path | Object to Path from the menus or by pressing Ctrl+Shift+C. If the object is made up of different bits, like different letters in a piece of text, make sure to combine them all together into one path by selecting all nodes (Ctrl+A) and choosing Path | Combine from the menus or by pressing Ctrl+K.
  2. If the object does not have many nodes, like a rectangle or a circle, you may want to add more to get more wobbliness. You can do this by hand by adding nodes at strategic points on the curves, or you can add nodes automatically using Extensions | Modify Path | Add Nodes….
  3. Now is the moment to use Tremble. Make sure your object is selected, navigate to Extensions | Modify Path | Tremble…, fill in the form, and press Apply. A bunch of SVGs will pop up in the folder you chose (Figure 3). Note that, after running the extension, the object in Inkscape will be a version of your object that has been modified by the script. If you want to keep the original, do NOT save it! You will overwrite your original image. You can always press Ctrl+Z to undo the changes.

  4. Most video editors will not load SVGs as a sequence, so you may want to convert your images into something they will accept, like PNGs. You can do that en masse by changing to the directory where your frames are located and running:

    for i in *.svg; do convert $i ${i%svg}png; rm $i; done

Note this will delete the original SVGs. Get rid of rm $i if that is not what you want.

  1. Use a video editor like Kdenlive or FFmpeg to convert the sequence of images into a movie.
  2. Impress your friends and family … or not.

Buy Linux Magazine

SINGLE ISSUES
 
SUBSCRIPTIONS
 
TABLET & SMARTPHONE APPS
Get it on Google Play

US / Canada

Get it on Google Play

UK / Australia

Related content

  • Magic Circle

    Inkscape's extensions add many useful features. Here's how to write your own.

  • Inkscape 0.45.1

    Inkscape has always been good, but now version 0.45.1 of the vector drawing program shows a totally new creative aspect.

  • Tutorial – Papercraft

    Papercraft is coming back into fashion. Linux users can turn to Inkscape and plugins such as Boxes.py, Paperfold, and Tabgen to create templates from 3D objects for printing.

  • Inkscape

    The Inkscape vector graphics tool replaces expensive commercial solutions such as Adobe Illustrator. This article shows how to get started with Inkscape.

  • Inkscape 0.47 Makes Drawing Freely Easier

    The Inkscape team has come out after a year's time with a new release of their vector drawing program. It provides not only new features but countless detailed enhancements.

comments powered by Disqus
Subscribe to our Linux Newsletters
Find Linux and Open Source Jobs
Subscribe to our ADMIN Newsletters

Support Our Work

Linux Magazine content is made possible with support from readers like you. Please consider contributing when you’ve found an article to be beneficial.

Learn More

News