Deterministic Saul Bass bird avatars generated from an email address in Rust
Find a file
Rémy Sanchez bf1b0ecf73 Initial release of bird-id Rust crate and CLI for Saul Bass bird avatars
Provide a zero-dependency library and CLI tool that deterministically generates geometric mid-century bird avatars in SVG format from email addresses:
- Include 20 hand-crafted 512x512 SVG bird-head templates inscribed within the r = 256 circle
- Implement email normalization and 64-bit FNV-1a + SplitMix64 hashing
- Implement OKLab/OKLCH color space conversions with binary-search sRGB gamut mapping and 8 mid-century silkscreen colorway families
- Support both square and circle-clipped SVG rendering via the Rust API and birdid CLI
- Add unit, integration, and doc tests, README documentation, showcase image, Apache-2.0 license, and GitHub Actions CI
2026-10-21 09:10:23 +02:00
.github/workflows Initial release of bird-id Rust crate and CLI for Saul Bass bird avatars 2026-10-21 09:10:23 +02:00
docs Initial release of bird-id Rust crate and CLI for Saul Bass bird avatars 2026-10-21 09:10:23 +02:00
src Initial release of bird-id Rust crate and CLI for Saul Bass bird avatars 2026-10-21 09:10:23 +02:00
templates Initial release of bird-id Rust crate and CLI for Saul Bass bird avatars 2026-10-21 09:10:23 +02:00
tests Initial release of bird-id Rust crate and CLI for Saul Bass bird avatars 2026-10-21 09:10:23 +02:00
.gitignore Initial release of bird-id Rust crate and CLI for Saul Bass bird avatars 2026-10-21 09:10:23 +02:00
Cargo.lock Initial release of bird-id Rust crate and CLI for Saul Bass bird avatars 2026-10-21 09:10:23 +02:00
Cargo.toml Initial release of bird-id Rust crate and CLI for Saul Bass bird avatars 2026-10-21 09:10:23 +02:00
LICENSE Initial release of bird-id Rust crate and CLI for Saul Bass bird avatars 2026-10-21 09:10:23 +02:00
README.md Initial release of bird-id Rust crate and CLI for Saul Bass bird avatars 2026-10-21 09:10:23 +02:00

bird-id

CI License: Apache-2.0

Deterministic Saul Bass airline-poster bird avatars generated from an email address in pure, zero-dependency Rust.

bird-id showcase

Given any email address, bird-id:

  1. Normalises & hashes the email into a 64-bit value (FNV-1a + SplitMix64), folding case, whitespace, mailto: prefixes, and Display Name <user@domain> wrappers so equivalent addresses always map to the same bird.
  2. Selects one of 20 hand-crafted SVG bird-head models (01-finch through 20-quail). Every model is built in a 512×512 coordinate space with all bird geometry strictly inside the inscribed r = 256 circle so circular cropping never clips a beak, crest, or plume.
  3. Computes a mid-century silkscreen colour palette in OKLCH (src/oklch.rs, src/palette.rs) across 8 curated colourways (Cardinal Crimson, Kingfisher Teal, Meadow Green, Blue Jay Cobalt, Goldfinch Olive, Tanager Mulberry, Canyon Ochre, and Continental Amber) with binary-search chroma gamut mapping into sRGB.
  4. Renders a standalone square or circle-clipped SVG (String) ready to serve over HTTP, inline into HTML, or rasterise.

Installation

Add bird-id to your Cargo.toml:

[dependencies]
bird-id = { git = "https://github.com/OffworldNexus/bird-id", branch = "develop" }

Rust API Examples

1. One-line square or circular SVG

use bird_id::{avatar_circle_svg, avatar_svg};

fn main() {
    // 512×512 full-bleed square SVG
    let square_svg: String = avatar_svg("ada@lovelace.org");
    assert!(square_svg.starts_with("<svg"));

    // 512×512 circular-clipped SVG
    let circle_svg: String = avatar_circle_svg("Ada Lovelace <ADA@lovelace.org>");
    assert!(circle_svg.contains("clipPath id=\"bird-id-circle\""));
}

2. Inspecting the Avatar, model, and OKLCH palette

use bird_id::Avatar;

fn main() {
    let av = Avatar::from_email("Grace Hopper <grace.hopper@navy.mil>");

    println!("Normalised email: {}", av.email);          // "grace.hopper@navy.mil"
    println!("64-bit hash:      0x{:016x}", av.hash);
    println!("Template ID:      {}", av.template.id);    // e.g. "12-toucan"
    println!("Species:          {}", av.template.species);
    println!("Colourway:        {}", av.palette.scheme.name());
    println!("Primary hue:      {:.1}°", av.palette.hue);

    // Every semantic token resolved for the SVG template
    for (token, hex) in av.palette.tokens() {
        println!("  {token:<8} -> {hex}");
    }
}

3. Custom pixel sizes and RenderOptions

The SVG viewBox is always 0 0 512 512, while width and height can be set to any pixel dimension:

use bird_id::{Avatar, RenderOptions, Shape};

fn main() {
    let av = Avatar::from_email("claude.shannon@bell-labs.com");

    // Shorthand helpers
    let square_128 = av.svg_sized(128);
    let circle_64 = av.svg_circle_sized(64);

    // Or via explicit RenderOptions
    let custom = av.render(RenderOptions {
        size: 256,
        shape: Shape::Circle,
    });

    assert!(square_128.contains("width=\"128\" height=\"128\""));
    assert!(circle_64.contains("width=\"64\" height=\"64\""));
    assert!(custom.contains("width=\"256\" height=\"256\""));
}

4. Selecting or iterating over specific bird models

All 20 embedded templates are exposed via bird_id::TEMPLATES and bird_id::template::by_name:

use bird_id::{template, Avatar, TEMPLATES};

fn main() {
    // Force a specific model index (0..20) while keeping the email's palette
    let owl = Avatar::with_template("edsger.dijkstra@utexas.edu", 14);
    assert_eq!(owl.template.species, "owl");

    // Lookup a template by species name or slug
    let hoopoe = template::by_name("hoopoe").expect("known species");
    assert_eq!(hoopoe.id, "11-hoopoe");

    // Render all 20 species using a single user's palette
    for tpl in &TEMPLATES {
        let svg = Avatar::with_template("collector@example.com", tpl.index).svg_circle();
        assert!(!svg.is_empty());
    }
}

5. Direct OKLCH colour & palette generation

bird-id exposes its perceptual colour maths (Oklch) and Palette::from_seed directly:

use bird_id::{Oklch, Palette};

fn main() {
    // Gamut-map an out-of-gamut OKLCH colour into sRGB by binary-searching chroma
    let colour = Oklch::new(0.72, 0.25, 45.0).gamut_mapped();
    assert!(colour.in_gamut());
    println!("Hex: {}", colour.to_hex());

    // Build a deterministic palette from any u64 seed
    let palette = Palette::from_seed(0xCAFE_BABE_DEAD_BEEF);
    println!("Highlight warm0: {}", palette.warm[0]);
    println!("Accent 0:        {}", palette.accent[0]);
}

6. Serving avatars from an HTTP handler (e.g. axum)

use axum::{
    extract::{Path, Query},
    http::{header, StatusCode},
    response::IntoResponse,
};
use bird_id::{Avatar, RenderOptions, Shape};
use serde::Deserialize;

#[derive(Deserialize)]
pub struct AvatarParams {
    #[serde(default = "default_size")]
    size: u32,
    #[serde(default)]
    circle: bool,
}

fn default_size() -> u32 {
    256
}

pub async fn avatar_handler(
    Path(email): Path<String>,
    Query(params): Query<AvatarParams>,
) -> impl IntoResponse {
    let svg = Avatar::from_email(&email).render(RenderOptions {
        size: params.size.clamp(16, 2048),
        shape: if params.circle { Shape::Circle } else { Shape::Square },
    });

    (
        StatusCode::OK,
        [
            (header::CONTENT_TYPE, "image/svg+xml; charset=utf-8"),
            (header::CACHE_CONTROL, "public, max-age=31536000, immutable"),
        ],
        svg,
    )
}

Command-Line Interface (birdid)

# Print a 512×512 square SVG to stdout
cargo run -- ada@lovelace.org

# Write a 256×256 circle-clipped SVG to a file
cargo run -- ada@lovelace.org --circle --size 256 -o avatar.svg

# Force a specific bird model by species name, slug, or 1-based number
cargo run -- ada@lovelace.org --model owl --circle -o owl.svg

# Inspect the normalised email, 64-bit hash, model, and OKLCH palette tokens
cargo run -- ada@lovelace.org --palette

# Render all 20 models into a directory
cargo run -- --all ./avatars --circle --size 256

The 20 Bird Models

# Slug Species Silhouette Character
01 01-finch finch Round head, olive cap, two-tone seed-cracker cone beak
02 02-cardinal cardinal Tall three-spike crest, angular mask, stout conical beak
03 03-robin robin Domed crown, slim straight beak, teardrop throat
04 04-goldfinch goldfinch Faceted head, top tuft, two-tone conical beak
05 05-warbler warbler High rounded crown, horizontal eye-stripe, needle beak
06 06-tanager tanager Angular notched crown, heavy two-tone triangular beak
07 07-oriole oriole Faceted nape, long straight dagger beak
08 08-chickadee chickadee Double back-tuft, bold black cap/mask, tiny stub beak
09 09-jay jay Swept back-crest, angular cheek facets, hooked beak
10 10-kingfisher kingfisher Compact low-slung head, oversized straight spear beak
11 11-hoopoe hoopoe Eight-blade fan crest, long down-curved sickle beak
12 12-toucan toucan Polygonal head, oversized four-band boat beak
13 13-parrot parrot Swept crown feathers, deep curved hook beak
14 14-macaw macaw Layered nape plumes, striped eye patch, heavy black hook beak
15 15-owl owl Ear tuft, diagonal brow, concentric round facial disc & iris
16 16-hummingbird hummingbird Angular high crown, white brow slash, long downward needle beak
17 17-sparrow sparrow Round domed head, stout two-tone seed beak, chin bib
18 18-waxwing waxwing Swept double crest, sleek mask, short notched beak
19 19-puffin puffin Domed head, triangular eye mask, tall ribbed seabird beak
20 20-quail quail Forward-curling teardrop topknot plume, bandit eye-bar

License

Licensed under the Apache License, Version 2.0.