Join GitHub today
GitHub is home to over 28 million developers working together to host and review code, manage projects, and build software together.Sign up
Framework for generating geometrical animations using Python
Fetching latest commit…
Cannot retrieve the latest commit at this time.
|Type||Name||Latest commit message||Commit time|
|Failed to load latest commit information.|
anim_framework is a set of Python 3 classes for constructing non-real-time animations using Cairo graphics via the Qahirah wrapper ([GitLab](https://gitlab.com/ldo/qahirah), [GitHub](https://github.com/ldo/qahirah)). The module `anim.common` contains common definitions for managing time and drawing, while additional modules `anim.lissa`, `anim.maurer`, `anim.rose`, `anim.spirolat`, `anim.troch` and `anim.whirl` build on this to render animations of Lissajous, Maurer, rose, spirolateral and trochoid and whirl curves respectively. Installation instructions are in the `setup.py` script. `anim.common` provides a convenience routine, `render_anim`, which will render out an entire animation to a sequence of numbered PNG frames. These can then be re-encoded through a tool like [FFmpeg](http://ffmpeg.org) into any convenient delivery format, like OGV, MPEG-4 etc. To illustrate the process, let’s start by defining some example parameters: the duration of the animation in seconds, the width and height of each frame, the frame rate, and the number of linear steps used to approximate curves: anim_duration = 10.0 dimensions = qahirah.Vector(1280, 720) frame_rate = 25.0 nr_steps = 500 Now let’s make a simple animating Lissajous curve: lissa = anim.lissa.make_draw \ ( x_amp = 300, x_freq = 2, x_phase = anim.common.linear_interpolator(0, anim_duration, 0, 1), y_amp = 300, y_freq = 3, y_phase = 0, nr_steps = nr_steps, ) Note that in this example all the parameters are constant, except for the `x_phase`, which is set to vary linearly from 0 to 1 over the entire duration of the animation. In general, any curve parameter can be either a constant value or the result of evaluating a time-dependent interpolator. The framework provides a range of basic interpolator types, as well as functions for transforming and combining them in various ways, and it is easy enough to define your own. So long as you put your custom interpolators through `anim.common.interpolator` (which can be used as a decorator), the framework will treat them exactly like built-in interpolators. The result returned by the above `make_draw` call is a “draw procedure”, which is a procedure that takes two arguments: a Cairo graphics context, and the current time. It does whatever is necessary to render the image into that context at that time. The framework provides functions for combining draw procedures, remapping them through time, etc. And of course you can define your own. And finally we can render the curve out to actual images: def anim_init(g) : g.translate(dimensions / 2) g.source_colour = qahirah.Colour.grey(1) g.paint() g.source_colour = qahirah.Colour.from_hsva((0.25, 0.9, 0.9)) g.line_width = 4 #end anim_init anim.common.render_anim \ ( dimensions = dimensions, start_time = 0.0, end_time = anim_duration, frame_rate = frame_rate, draw_frame = lissa, overall_presetup = anim_init, out_dir = "frames", start_frame_nr = 1 ) which will run for a few seconds or so to generate 250 frames. This requires that a subdirectory called “`frames`” be created, into which the frames will be written under the names “`0001.png`”, “`0002.png`” etc. The FFmpeg package provides the “`ffplay`” command, which can directly play the PNG frames using a command like ffplay -autoexit frames/%04d.png or, if you want playback to loop: ffplay -loop 0 frames/%04d.png Note the definition of `anim_init`, which is called by `render_anim` after it creates the Cairo drawing context but before rendering any frames, in order to do any special setup that you need. Here it sets the drawing origin to the centre of the frame (for positioning the curve), clears the background to white, and sets a pen colour and size for the curve. HSV tends to be a more convenient space for experimenting with colours than RGB, which is why I prefer to specify colours in that space. If you try playing back the resulting animation from the above, it won’t look right. What happens is that the Lissajous curves from previous frames remain on succeeding frames instead of being erased, until the result at the end of the animation is a solid green square. What you want is to start with a white background for each frame. This can be done with a custom draw procedure as simple as def init_frame(g, t) : g.source_colour = qahirah.Colour.grey(1) # background colour g.paint() g.source_colour = qahirah.Colour.from_hsva((0.25, 0.9, 0.9)) # curve colour #end init_frame (Note to be a draw procedure it has to take two arguments, even though it pays no attention to the second one.) Now we need a way to ensure this is invoked immediately prior to the lissa draw procedure for each frame. We can use `draw_compose` for this, to construct a draw procedure which invokes its first argument followed by its second argument: anim.common.render_anim \ ( dimensions = dimensions, start_time = 0.0, end_time = anim_duration, frame_rate = frame_rate, draw_frame = anim.common.draw_compose(init_frame, lissa), overall_presetup = anim_init, out_dir = "frames", start_frame_nr = 1 ) (Note the erasure of the background and setting of the pen colour in `anim_init` can now be removed, since it is just repeating what is being done in `init_frame`.) If you try playing back the new frames, you should now see a nicely-animating clean Lissajous curve. Now, supposing we want to animate the colour of the curve. The draw procedure returned by `anim.lissa.make_draw` doesn’t have any option for adjusting Cairo settings like the pen size, colour etc; it simply uses whatever settings are currently in effect. To control these settings in an animated fashion, we can add a suitable `set_source_colour` call to our `init_frame` procedure, and animate it based on the second argument (which we were ignoring before). But since `init_frame` consists of nothing but Cairo calls, there is a convenience routine to make it easier to define the whole procedure, complete with animation: init_frame = anim.common.make_draw \ ( ("set_source_colour", (Colour.grey(1),)), ("paint", ()), ( "set_source_colour", ( anim.common.hsva_to_colour_interpolator ( h = anim.common.linear_interpolator(0, anim_duration, 0, 1), s = 0.9, v = 0.9, a = 1.0 ), ) ), ) Now the exact same `render_anim` call as before will produce a Lissajous animation where the curve goes through all the colours of the spectrum. Hopefully that gives you the flavour of the framework, and how easy it is to do some quite elaborate animations. By all means, delve further into it, try things out, and have fun. Further code examples are available in my anim_framework_examples repo ([GitLab](https://gitlab.com/ldo/anim_framework_examples), [GitHub](https://github.com/ldo/anim_framework_examples)). Lawrence D'Oliveiro <firstname.lastname@example.org> 2017 April 7