fourier-draw is a Python tool for turning simple drawings into Fourier epicycle animations.
It can take a hand-drawn image or SVG, extract path geometry, compute Fourier coefficients, and render an animation showing rotating vectors tracing the drawing.
The project supports both:
- single-curve Fourier animation
- multi-stroke Fourier animation for disconnected SVG paths
- Convert
.png,.jpg,.jpeg, or.svginputs into usable SVG paths - Sample SVG paths into complex-valued curves
- Compute Fourier coefficients from sampled drawing paths
- Render epicycle animations with Matplotlib
- Optionally render polished math-style animations with Manim
- Support disconnected SVG paths as separate strokes
- Run the full pipeline with one command
Clone the repository:
git clone https://github.com/KaiyueZou/fourier-draw.git
cd fourier-drawCreate and activate a virtual environment:
python3 -m venv .venv
source .venv/bin/activateInstall the package in editable mode:
python -m pip install --upgrade pip setuptools wheel
python -m pip install -e ".[dev]"For raster image vectorization, install Potrace:
brew install potraceFor MP4 export with Matplotlib, install FFmpeg:
brew install ffmpegFor Manim support, install the optional Manim dependencies:
brew install cairo pkg-config pango ffmpeg
python -m pip install -e ".[dev,manim]"Check that the command-line tool works:
fourier-draw helloExpected output:
fourier-draw is installed and working.
fourier-draw run examples/input/test_k.png \
--output examples/output/test_k_animation.mp4 \
--terms 100 \
--n-samples 2000 \
--frames 600 \
--fps 30For an SVG containing multiple separate path elements:
fourier-draw run-strokes examples/input/test_kz_two_strokes.svg \
--output examples/output/test_kz_animation.mp4 \
--terms 60 \
--samples-per-path 1000 \
--frames-per-stroke 180 \
--pause-frames 30 \
--fps 30Hide the epicycle circles and vectors:
fourier-draw run-strokes examples/input/test_kz_two_strokes.svg \
--output examples/output/test_kz_clean.mp4 \
--terms 60 \
--samples-per-path 1000 \
--frames-per-stroke 180 \
--pause-frames 30 \
--fps 30 \
--no-circles \
--no-vectorsfourier-draw run-strokes examples/input/test_kz_two_strokes.svg \
--output examples/output/test_kz_manim.mp4 \
--backend manim \
--terms 30 \
--samples-per-path 1000 \
--run-time-per-stroke 3 \
--pause-time 0.5 \
--quality lowThe one-command pipeline is convenient, but the full workflow can also be run step by step.
fourier-draw vectorize examples/input/test_k.png \
--output examples/output/test_k.svgFor SVG input, this command validates and copies the SVG:
fourier-draw vectorize examples/input/test_kz_two_strokes.svg \
--output examples/output/test_kz_two_strokes.svgSingle-curve sampling:
fourier-draw sample examples/output/test_k.svg \
--output examples/output/test_k_points.npy \
--n-samples 2000Multi-stroke sampling:
fourier-draw sample-strokes examples/output/test_kz_two_strokes.svg \
--output examples/output/test_kz_strokes_points.npz \
--samples-per-path 1000Single-curve coefficients:
fourier-draw coeffs examples/output/test_k_points.npy \
--output examples/output/test_k_coeffs.npz \
--terms 100Multi-stroke coefficients:
fourier-draw coeffs-strokes examples/output/test_kz_strokes_points.npz \
--output examples/output/test_kz_strokes_coeffs.npz \
--terms 60Single-curve Matplotlib animation:
fourier-draw animate examples/output/test_k_coeffs.npz \
--output examples/output/test_k_animation.mp4 \
--frames 600 \
--fps 30Single-curve Manim animation:
fourier-draw animate-manim examples/output/test_k_coeffs.npz \
--output examples/output/test_k_manim.mp4 \
--run-time 8 \
--fps 30 \
--quality lowMulti-stroke Matplotlib animation:
fourier-draw animate-strokes examples/output/test_kz_strokes_coeffs.npz \
--output examples/output/test_kz_strokes_animation.mp4 \
--frames-per-stroke 180 \
--pause-frames 30 \
--fps 30Multi-stroke Manim animation:
fourier-draw animate-strokes-manim examples/output/test_kz_strokes_coeffs.npz \
--output examples/output/test_kz_strokes_manim.mp4 \
--run-time-per-stroke 3 \
--pause-time 0.5 \
--fps 30 \
--quality lowfourier-draw supports:
.png.jpg.jpeg.svg
For the first version, the raster vectorization pipeline works best for:
dark handwriting or line art on a light background
For light drawing on dark background, use:
--invertExample:
fourier-draw run input.png \
--output output.mp4 \
--invertThere are two common types of SVG path input.
These are paths where the SVG path itself represents the pen trajectory, with visual thickness supplied by stroke-width.
Example:
<path d="M 0 0 L 100 100" stroke="black" stroke-width="10" fill="none" />fourier-draw samples the centerline geometry.
This is best for handwriting-style animation.
When raster images are vectorized with Potrace, the resulting SVG usually represents the outline or contour of the foreground shape.
This is best for logos, silhouettes, and filled shapes.
Single-curve mode concatenates SVG paths into one curve.
Use it for:
- closed shapes
- logos
- one continuous drawing
- debugging
Commands:
fourier-draw run ...
fourier-draw sample ...
fourier-draw coeffs ...
fourier-draw animate ...Multi-stroke mode gives each SVG path its own Fourier series.
Use it for:
- disconnected letters
- separate handwriting strokes
- dots and crossbars
- multi-path SVG drawings
Commands:
fourier-draw run-strokes ...
fourier-draw sample-strokes ...
fourier-draw coeffs-strokes ...
fourier-draw animate-strokes ...from fourier_draw import run_single_pipeline, run_strokes_pipeline
run_single_pipeline(
input_path="examples/input/test_k.png",
output_path="examples/output/test_k_animation.mp4",
terms=100,
n_samples=2000,
)
run_strokes_pipeline(
input_path="examples/input/test_kz_two_strokes.svg",
output_path="examples/output/test_kz_animation.mp4",
terms=60,
samples_per_path=1000,
)Install development dependencies:
python -m pip install -e ".[dev]"Run tests:
pytestInstall Manim extras:
python -m pip install -e ".[dev,manim]"- Raster vectorization currently uses Potrace and usually extracts contours, not handwriting centerlines.
- Multi-stroke mode treats each SVG
<path>element as one stroke. It does not yet split subpaths inside one path. - Automatic grouping of paths into letters is not yet supported.
- Manim rendering can be slower than Matplotlib, especially with many Fourier terms.
- Very noisy raster images may require preprocessing before vectorization.
- The package currently works best for simple line art, handwriting, logos, and symbolic shapes.
Possible future features:
- centerline tracing / skeletonization for raster handwriting
- automatic path grouping
- better stroke ordering
- SVG preview utilities
- richer color/style customization
- web demo
- direct GIF optimization
- support for 3D Fourier curves
MIT License.