A high-performance genetic algorithm implementation in Rust that evolves random shapes (triangles or circles) to recreate famous paintings. Watch as natural selection, crossover breeding, and mutation combine to transform chaos into art!
- Rust 1.91+ (install from rustup.rs)
- A target image (PNG, JPEG, etc.)
# Clone the repository
git clone <your-repo-url>
cd rust_genetic_algorithm
# Build the release version (optimized for speed)
cargo build --release
# The binary will be at: ./target/release/genetic-art# Run a quick test with triangles (100 generations)
./target/release/genetic-art \
--input input/starry_night.jpg \
--generations 100 \
--shapes 100
# Or try with circles!
./target/release/genetic-art \
--input input/starry_night.jpg \
--shape circle \
--generations 100 \
--shapes 100
# Check the result
open output/latest.png # macOS
# or
xdg-open output/latest.png # LinuxThe algorithm uses a genetic approach inspired by natural evolution:
- Initialize: Create 200 random "paintings" (each made of 150 shapes - triangles or circles)
- Evaluate: Compare each painting to the target image (fitness score)
- Select: Keep only the best 5% (natural selection)
- Breed: Cross-breed survivors to create offspring
- Mutate: Apply random changes to introduce variation
- Repeat: Continue for thousands of generations
Over time, the paintings evolve to increasingly resemble the target!
This implementation is 10-20x faster than equivalent Python code:
- Zero-cost abstractions: High-level code compiles to efficient machine code
- Parallel processing: Automatically uses all CPU cores (via Rayon)
- Memory safety: No garbage collection pauses, no memory leaks
- Optimized rendering: Fast triangle rasterization
genetic-art --input <PATH_TO_IMAGE>This uses sensible defaults for all parameters. The algorithm will run for 5000 generations and save progress images every 50 generations to ./output/.
genetic-art [OPTIONS] --input <INPUT>| Flag | Description |
|---|---|
-i, --input <PATH> |
Path to target image (PNG, JPEG, GIF, BMP, etc.) |
| Flag | Default | Range | Description |
|---|---|---|---|
-s, --shape <TYPE> |
triangle | triangle, circle | Type of shape to use (triangle or circle) |
-n, --shapes <NUM> |
150 | 10-500 | Number of shapes per painting. More = more detail possible but slower evolution |
-p, --population <NUM> |
200 | 50-1000 | Population size. Larger = more diversity but slower per generation |
-g, --generations <NUM> |
5000 | 100-50000 | Number of generations. More = better results but takes longer |
| Flag | Default | Range | Description |
|---|---|---|---|
--mutation-rate <RATE> |
0.04 | 0.0-1.0 | Fraction of shapes to mutate. Higher = more variation |
--survival-rate <RATE> |
0.05 | 0.0-1.0 | Fraction that survives. Lower = stronger selection pressure |
--sigma <STRENGTH> |
1.0 | 0.0-2.0 | Mutation strength. Higher = larger random changes |
| Flag | Default | Options | Description |
|---|---|---|---|
--fitness-function <FUNC> |
mad | mad, edge-weighted, ms-ssim | Fitness function for image comparison |
--edge-power <POWER> |
2.0 | 0.5-3.0 | Edge emphasis power (edge-weighted only) |
--edge-scale <SCALE> |
4.0 | 1.0-10.0 | Edge weight scale factor (edge-weighted only) |
--detail-weight <WEIGHT> |
2.0 | 1.0-5.0 | Detail scale weight (ms-ssim only) |
| Flag | Default | Description |
|---|---|---|
-o, --output <DIR> |
./output | Directory for generated images |
--save-interval <NUM> |
50 | Save image every N generations |
| Flag | Default | Description |
|---|---|---|
-t, --threads <NUM> |
All cores | Number of threads for parallel processing. Set lower to reduce CPU usage |
# Fast test with fewer generations and shapes
./target/release/genetic-art \
--input input/image.jpg \
--shapes 50 \
--generations 500# Full quality evolution
./target/release/genetic-art \
--input input/image.jpg \
--shapes 150 \
--generations 5000# Very high detail (slow but beautiful)
./target/release/genetic-art \
--input input/image.jpg \
--shapes 250 \
--population 300 \
--generations 10000 \
--save-interval 100# Run with only 4 threads (useful when multitasking)
./target/release/genetic-art \
--input input/image.jpg \
--threads 4 \
--generations 5000# Prioritizes edges and fine details over uniform areas
./target/release/genetic-art \
--input input/image.jpg \
--fitness-function edge-weighted \
--edge-power 2.0 \
--edge-scale 4.0 \
--generations 5000# Multi-scale structural similarity (slower but best quality)
./target/release/genetic-art \
--input input/image.jpg \
--fitness-function ms-ssim \
--detail-weight 2.0 \
--generations 5000# Strong selection pressure, higher mutation
./target/release/genetic-art \
--input input/image.jpg \
--survival-rate 0.02 \
--mutation-rate 0.08 \
--sigma 1.5# Gentle selection, lower mutation
./target/release/genetic-art \
--input input/image.jpg \
--survival-rate 0.10 \
--mutation-rate 0.02 \
--sigma 0.5What it does: Sets how many shapes (triangles or circles) compose each painting.
- 50-100: Abstract style, fast evolution
- 150-200: Good balance (recommended)
- 250-500: High detail, slower but more accurate
Trade-off: More shapes = more detail possible, but each generation takes longer to compute.
What it does: How many paintings evolve simultaneously.
- 50-100: Fast, less diversity
- 200-300: Good balance (recommended)
- 400-1000: More diversity, slower
Trade-off: Larger population explores more solutions but takes longer per generation.
What it does: How many evolution cycles to run.
- 100-500: Quick preview
- 1000-5000: Good results (recommended)
- 10000+: Refinement stage
Trade-off: More generations = better results, but diminishing returns after a point.
What it does: Fraction of shapes that mutate in each offspring (0.0-1.0).
- 0.02-0.03: Conservative (small changes)
- 0.04-0.06: Balanced (recommended)
- 0.08-0.10: Aggressive (large changes)
Trade-off: Higher = more exploration but less stability. Lower = more refinement but slower progress.
What it does: Fraction of population that survives each generation (0.0-1.0).
- 0.02-0.03: Very strong selection
- 0.05: Strong selection (recommended)
- 0.10-0.20: Weak selection, maintains diversity
Trade-off: Lower = faster improvement but less diversity. Higher = more diversity but slower improvement.
What it does: Controls the magnitude of mutations (0.0-2.0).
- 0.5: Small adjustments (fine-tuning)
- 1.0: Standard changes (recommended)
- 1.5-2.0: Large jumps (exploration)
Trade-off: Higher values make bigger random changes, good for escaping local optima but can disrupt good solutions.
What it does: Determines how the algorithm compares rendered images to the target.
- Speed: Fastest (baseline)
- Behavior: Treats all pixels uniformly
- Best for: General use, quick iterations
- Formula:
sum(|pixel_source - pixel_target|) / (width × height × 3)
--fitness-function mad- Speed: ~2x slower than MAD
- Behavior: Emphasizes pixels near edges and details
- Best for: Images with important fine details (portraits, text, intricate patterns)
- How it works:
- Detects edges in target image using Sobel operator
- Assigns higher weights to pixels near edges
- Getting edge details wrong costs more than uniform areas
- Parameters:
--edge-power: Controls emphasis strength (1.0 = linear, 2.0 = quadratic)--edge-scale: Maximum weight multiplier (4.0 = edges 5x more important)
--fitness-function edge-weighted --edge-power 2.0 --edge-scale 4.0Example: For a portrait, getting the eyes and facial features right matters more than the background sky.
- Speed: ~5-10x slower than MAD
- Behavior: Evaluates similarity at multiple scales (resolutions)
- Best for: Highest quality results when time permits
- How it works:
- Creates image pyramid (5 scales: 1x, 0.5x, 0.25x, etc.)
- Compares structure, contrast, and luminance at each scale
- Finer scales capture details, coarser scales capture overall composition
- Weights can emphasize fine details
- Parameters:
--detail-weight: Exponential weight for finer scales (2.0 = each finer scale 2x more important)
--fitness-function ms-ssim --detail-weight 2.0Example: Better at capturing both overall composition AND fine details simultaneously.
| Use Case | Recommended Function | Reason |
|---|---|---|
| Quick experiments | MAD | Fastest, good general results |
| Portraits/faces | Edge-Weighted | Emphasizes facial features |
| Text/logos | Edge-Weighted | Sharp edges are critical |
| Landscapes | MAD or MS-SSIM | Balance of detail and speed |
| Artistic quality | MS-SSIM | Best perceptual quality |
| Limited time | MAD | Best speed/quality ratio |
Color Handling: All fitness functions compare full RGB colors correctly. Edge-weighted uses grayscale ONLY to detect edges, but still compares colors when computing fitness.
The algorithm saves images to the output directory:
output/
├── generation_00000.png # Initial random state
├── generation_00050.png # After 50 generations
├── generation_00100.png # After 100 generations
├── ...
├── generation_05000.png # Final result
└── latest.png # Always the most recent (for quick viewing)
Selection:
- Elitist strategy: Best individual always survives
- Plus random selection for second parent (maintains diversity)
Crossover:
- Uniform crossover: Each shape randomly chosen from either parent
- 50/50 chance for each gene
Mutation:
- Shape shift: Move entire shape
- Point mutation: Move single vertex (triangles) or center (circles)
- Color mutation: Change RGB and alpha values
- Reset: Complete randomization (rare)
- Z-order swap: Change shape rendering order
- Radius mutation: Change circle size (circles only)
Three fitness functions available via --fitness-function:
MAD (Mean Absolute Difference) - Default:
fitness = sum(|pixel_source - pixel_target|) / (width × height × 3)
Edge-Weighted MAD:
fitness = sum(weight_pixel × |pixel_source - pixel_target|) / sum(weights)
Where weights are higher near edges detected in the target image.
MS-SSIM (Multi-Scale Structural Similarity): Compares images at multiple resolutions using structural similarity metrics.
Lower score = better match. Perfect match = 0.0 (for MAD and Edge-Weighted) or 255.0 (for MS-SSIM converted to same scale).
The algorithm automatically uses all your CPU cores:
- Fitness evaluation: All individuals evaluated in parallel
- Image comparison: Parallel pixel-by-pixel comparison
- Performance scaling: Near-linear with core count (4 cores ≈ 4x faster)
# Development build (fast compile, slow runtime)
cargo build
# Release build (slow compile, fast runtime)
cargo build --release
# Always use --release for actual evolution!# Run all tests
cargo test
# Run specific test
cargo test test_triangle_creation
# Run with output
cargo test -- --nocapturesrc/
├── main.rs # CLI application entry point
├── lib.rs # Library root
├── genes/
│ ├── mod.rs # Gene module
│ └── triangle.rs # Triangle gene implementation
├── painting.rs # Painting (collection of triangles)
├── population.rs # Population management
├── evolution.rs # Genetic operators
└── fitness.rs # Image comparison
- Use release mode:
cargo build --release(10-100x faster than debug!) - Start small: Test with
--generations 100first - Reduce shapes: Start with
--shapes 50for quick iteration - Use MAD fitness:
--fitness-function mad(fastest option) - Increase save interval:
--save-interval 100reduces I/O overhead - Use smaller images: Resize target to 400-800px width
- More generations: 5000-10000 generations
- More shapes: 150-250 shapes
- Larger population: 200-300 individuals
- Try MS-SSIM fitness:
--fitness-function ms-ssimfor perceptually better results - Use edge-weighted for portraits:
--fitness-function edge-weightedfor detailed faces - Let it run overnight: Best results take time!
On a modern 6-core CPU (2.5 GHz):
| Configuration | Time |
|---|---|
| 100 gen, 50 tri, pop 100 | ~30 seconds |
| 1000 gen, 150 tri, pop 200 | ~5 minutes |
| 5000 gen, 150 tri, pop 200 | ~20 minutes |
| 10000 gen, 250 tri, pop 300 | ~90 minutes |
Note: Times vary significantly based on CPU, image size, and parameters.
Good targets:
- High contrast images
- Clear subjects
- Not too much fine detail
- Medium size (400-1000px)
Avoid:
- Very noisy/grainy images
- Extremely detailed photographs
- Very large images (resize first)
For portraits:
--shapes 200 --sigma 0.8 --mutation-rate 0.05 \
--fitness-function edge-weighted --edge-power 2.0For landscapes:
--shapes 150 --sigma 1.2 --mutation-rate 0.06 \
--fitness-function madFor abstract art:
--shapes 100 --sigma 1.5 --survival-rate 0.03 \
--fitness-function madFor maximum detail:
--shapes 250 --fitness-function ms-ssim \
--detail-weight 2.5 --generations 10000- The Rust Book - Official Rust learning resource
- Rust by Example - Learn by doing
- image - Image loading and processing
- imageproc - Image processing primitives
- rayon - Data parallelism
- clap - Command line parsing
- indicatif - Progress bars
This project was created as a learning exercise. Feel free to:
- Report bugs
- Suggest improvements
- Submit pull requests
- Share your evolved art!
MIT License - see LICENSE file for details.
- Inspired by Roger Johansson's genetic art algorithm
- Based on the Python implementation