This project uses an evolutionary algorithm to generate abstract art. Starting from a random set of shapes, the program iteratively refines these shapes to resemble a target character glyph from a specified font. It's an exploration of genetic algorithms applied to creative visual generation.
The core of this project is a genetic algorithm that evolves a population of individuals over several generations to match a target visual.
- Target Glyph: A target image is first generated by rendering a specific character (e.g., 'A') from a font file (e.g.,
fonts/NotoSans-Light.ttf). This becomes the goal for the evolution. - Genotype and Phenotype:
- Each individual in the population has a
Genotype, which is a collection of simple geometric shapes (e.g., circles, polygons) defined by their positions, sizes, and colors. - The
Phenotypeis the visual representation of a genotype, i.e., the image rendered by drawing all its shapes.
- Each individual in the population has a
- Initialization: The process starts with a population of randomly generated
Genotypes. - Evolutionary Loop: For a set number of generations, the following steps are performed:
- Fitness Evaluation: Each individual's
Genotypeis rendered into itsPhenotype(an image). This image is then compared to the target glyph image, and a fitness score is calculated. The fitness is typically the Mean Squared Error (MSE) between the two images; a lower MSE means a better match. - Selection: Individuals with better fitness scores are more likely to be selected for reproduction. This project uses a combination of:
- Elitism: A small number of the best individuals from the current generation are directly carried over to the next generation, unchanged.
- Tournament Selection: Groups of individuals are randomly chosen, and the best individual from each group is selected as a parent.
- Crossover: Two selected parents combine their
Genotypes to create one or more offspring. This involves mixing and matching parts of the parents' shape data. - Mutation: Small, random changes are introduced into the offspring's
Genotype. This could mean altering a shape's color, position, size, or adding/removing shapes. Mutation helps maintain genetic diversity and explore new possibilities.
- Fitness Evaluation: Each individual's
- Output: The program saves an image of the best-fitting individual's phenotype from each generation (or at regular intervals) into the
results/directory. This allows you to see the progression of the evolution. The final best individual is also saved.
The project is organized as follows:
.
├── Cargo.toml # Rust package manifest, defines dependencies
├── Cargo.lock # Exact versions of dependencies
├── make_video.sh # Script to compile images into a video
├── fonts/ # Contains font files (e.g., NotoSans-Light.ttf)
│ └── NotoSans-Light.ttf
├── src/ # Source code directory
│ ├── main.rs # Main application entry point, orchestrates the evolution
│ ├── genotype.rs # Defines the structure of individuals (genotypes) and their shapes
│ ├── evolution.rs # Implements the genetic algorithm operators (selection, crossover, mutation)
│ ├── render.rs # Handles rendering of genotypes to images and fitness calculation
│ └── constants.rs # Defines various constants for the simulation (e.g., population size)
└── results/ # Output directory for generated images (created automatically)
To build and run this project, you need to have the Rust programming language toolchain installed. You can find installation instructions at rust-lang.org.
Once Rust is set up, follow these steps:
- Clone the repository:
git clone <your-repository-url> # Replace <your-repository-url> with the actual URL cd font-evolver
- Build the project:
For development:
For a release version (recommended for speed):
cargo build
cargo build --release
After building the project, you can run the evolutionary process using:
cargo run --release(Using --release is recommended for significantly faster execution.)
The program will:
- Create a
results/directory if it doesn't exist. - Save the target glyph image as
results/_target.png. - Periodically save the phenotype of the best individual in the current generation as
results/best_XXXX.png(where XXXX is a frame number). - Once all generations are complete, save the final best individual's phenotype as
results/_final_best.png. - Print progress, including the best fitness score for each generation, to the console.
A shell script make_video.sh is provided to compile the sequence of generated best_XXXX.png images into a video.
Prerequisites:
- You need
ffmpeginstalled on your system.ffmpegis a widely used multimedia framework. You can download it from ffmpeg.org or install it via your system's package manager (e.g.,sudo apt install ffmpegon Debian/Ubuntu,brew install ffmpegon macOS).
Running the script: Make sure the script is executable:
chmod +x make_video.shThen run it from the project's root directory:
./make_video.shThis will create a video file named evolution_video.mp4 in the results/ directory. You can modify the script to change the output filename, frame rate, or other ffmpeg parameters.
You can customize the evolutionary process and the target:
- Evolution Parameters:
- Open
src/constants.rs. - Modify values like
POPULATION_SIZE,NUM_GENERATIONS,ELITISM_COUNT,MUTATION_RATE,CROSSOVER_RATE, number of shapes in a genotype, etc., to experiment with different evolutionary dynamics. OUTPUT_EVERYcontrols how frequently images of the best individual are saved.
- Open
- Target Glyph:
- Open
src/main.rs. - In the
mainfunction, find the line:let target_buffer = render_target_glyph("fonts/NotoSans-Light.ttf", 'A').unwrap();
- You can change the font file (ensure the path is correct and the font is in the
fonts/directory or provide a full path) and the character (e.g., 'B', 'X', 'g', etc.).
- Open
- Shape Properties:
- The
Genotypestructure insrc/genotype.rsdefines what kind of shapes are used and their properties (e.g., number of vertices for polygons, color ranges). Modifying this file can lead to different visual styles. - The rendering logic in
src/render.rswould also need to be updated if new shape types are added.
- The
Remember to rebuild the project (cargo build --release) after making changes to the source code.
This project relies on the following Rust crates (managed by Cargo):
- fontdue: For parsing font files and rendering glyphs to create the target image.
- image: Used for image buffer manipulation and saving images as PNG files.
- rand: For random number generation, essential for the genetic algorithm's initialization, selection, and mutation steps.
- rayon: For data parallelism, used here to speed up the fitness evaluation of the population.
- ffmpeg: Required by the
make_video.shscript to create a video from the sequence of generated images. This is not a Rust dependency and needs to be installed separately on your system.
This project is licensed under the MIT License. See the LICENSE file for details.
MIT License
Copyright (c) 2025 Frederik De Bleser
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.