Animation¶
A still image says what a design is. An animation says how it is made, which for most of this catalog is the more interesting half — watching a Hilbert curve fill its square tells you something the finished picture cannot.
from geomotif.animate import draw_on
from geomotif.io.gif import save_gif
from geomotif.motifs import HilbertCurve
save_gif(draw_on(HilbertCurve(depth=5).build(), frames=60), "hilbert.gif")
or from the command line:
geomotif render fractal.hilbert --depth 5 --out hilbert.gif --frames 60
geomotif render rose --out rose.gif --motion spin --frames 48 --fps 24
No dependency is involved in any of it — not for the frames, not for the rasterizing, and not for the GIF's LZW compression.
Frames¶
Everything in geomotif.animate returns a plain tuple of
Designs, one per frame, so frames compose with the rest of the library:
transform them, restyle them, export one as SVG, or hand the lot to save_gif.
| Function | What moves |
|---|---|
draw_on |
the pen: the design is revealed progressively |
spin |
the design: it turns about a point |
sweep |
a parameter: the motif is rebuilt for each value |
draw_on(design, frames=60, trail=200.0, hold=10)
spin(design, frames=48, turns=-1.0, hold=6)
sweep(Rose(), "n", range(2, 12))
draw_on measures progress in arc length, not in vertices, so the pen
moves at a constant speed rather than racing through the sparse parts of the
geometry and crawling through the dense ones. A partly drawn closed path comes
back open — half a square is not a square, and drawing it as one would close a
gap the pen has not been round yet.
trail draws only the last n units rather than everything so far, which
turns the pen into a comet. hold — on both draw_on and spin — repeats the
finished drawing, so a looping animation pauses on the result instead of
restarting the instant it arrives. From the command line the same pause is
--hold N; it defaults to a quarter of --frames, and --hold 0 turns it off.
Writing the GIF¶
from geomotif.io.gif import save_gif, to_gif
save_gif(frames, "out.gif", width=480, height=480, fps=20)
blob = to_gif(frames, ink="#0b0b0b", background="#ffffff", thickness=2, loop=3)
Two things are decided for you, because getting either wrong looks like a bug:
One canvas for every frame. Each frame is drawn against the union of all their bounds, not its own — otherwise a drawing that grows would rescale on every frame and crawl about the canvas instead of standing still.
One color table for every frame. Styles pick up their own palette entries, so a two-pen design animates in two colors; but the table is worked out across the whole animation, or an index that meant crimson in one frame and black in the next would make the result flicker.
GIF stores a delay in hundredths of a second and nothing finer, so fps is
rounded to what the format can actually say — 20fps is 5 hundredths, and
anything above 50fps hits the floor. loop=0, the default, repeats forever.
A GIF holds at most 256 colors; a design that needs more is refused rather than quantized, with a message saying so.
Rasterizing on its own¶
rasterize is the piece underneath, and it is
usable by itself when you want pixels rather than an animation:
from geomotif.io.raster import rasterize
raster = rasterize(design, width=800, height=600, thickness=2)
raster.pixels # one palette index per pixel, top-left origin
raster.palette # ("#ffffff", "#0b0b0b", ...)
A Raster carries width and height (the size
you asked for, in pixels), pixels (the indexed buffer), palette (the tuple
of colors it indexes into) and mode (one of "indexed", "rgb", "rgba").
It is the form the GIF writer takes directly; for the PNG and JPEG writers,
which want truecolor, reach for
rasterize_rgba instead — same call,
same size, but the buffer comes back as four bytes a pixel ready for an
encoder that has no palette.
Lines are drawn with Bresenham's algorithm and nothing is antialiased, which is the right answer for an indexed image: there are only a handful of colors and nothing for a blend to blend with.
This is the one raster format here
Everything else geomotif writes is vector, and deliberately: a design is a set of curves, and the formats that keep them curves are the ones worth writing. Pixels exist here for animation, which has no vector form that plays everywhere. For a still image, use SVG.