Media

Identicons

GitHub-style identicons from any text, as SVG or PNG: a symmetric pattern in a colour of its own, the same text always the same picture.

Version 1.0.0 · 3 functions · 1 crate

#images#avatars#users

Functions

What use identicon.qwer gives a program, with the types Quillo sees. To take only some, or under other names: use identicon_svg, identicon_png from identicon (Quillo 0.6).

identicon_svg(text: str, size: int) → str

The identicon of text as SVG, size pixels square (16 to 2048), on a light grey background with a margin of half a cell. Lower and upper case give the same picture.

identicon_png(text: str, path: str, size: int) → stror an error

The same identicon as a PNG file at path (folders made), size pixels square (16 to 2048): the path.

identicon_color(text: str) → str

The identicon's colour for text, as "#rrggbb": to match a profile's accent to its picture.

Get it

$ quillo install identicon               # identicon.qwer into this folder$ quillo install identicon --example     # and its example, identicon.qwe

Or with curl:

$ curl -O https://quillolang.org/plugins/files/identicon.qwer$ curl -O https://quillolang.org/plugins/files/identicon.qwe

Then, in your program, all of it or only what you call:

main.qwe
use identicon.qweruse identicon_svg, identicon_png from identicon      // Quillo 0.6: only these

It uses this crate, which cargo fetches the first time the plugin is built:

[dependencies]
image = { version = "0.25", default-features = false, features = ["png"] }

Example

identicon.qwe
// Default profile pictures: each user gets an identicon from their id until they upload a photo.////   quillo run identicon.qwe      then open http://localhost:8080/use "identicon.qwer"let users = [{ id: "ada@example.com", name: "Ada" }, { id: "grace@example.com", name: "Grace" }, { id: "alan@example.com", name: "Alan" }]route GET /avatars/:id = respond(200, identicon_svg(id, 96), { "content-type": "image/svg+xml", "cache-control": "max-age=31536000" })route GET /avatars/:id/png {    let path = identicon_png(id, `data/avatars/${crypto.sha256(id)}.png`, 128)?    file(path)}route GET / = <html><body style="font-family: sans-serif">    {for u in users => <div style={`display: flex; gap: 8px; align-items: center; color: ${identicon_color(u.id)}`}>        <img src={`/avatars/${url_encode(u.id)}`} width="48" height="48" alt=""/> {u.name}    </div>}</body></html>route GET /same = { same: identicon_svg("Ada@Example.com", 64) == identicon_svg("ada@example.com", 64), differ: identicon_svg("ada", 64) != identicon_svg("grace", 64), color: identicon_color("ada@example.com") }
$ quillo run identicon.qwe$ curl 'localhost:8080/same'$ curl 'localhost:8080/avatars/ada@example.com'$ curl 'localhost:8080/'

Source

identicon.qwer
//! GitHub-style identicons from any text (a user id, an email): a symmetric 5×5 pattern in a colour of its own, as SVG or PNG, the same text always giving the same picture.//! version: 1.0.0//!//! [dependencies]//! image = { version = "0.25", default-features = false, features = ["png"] }// FNV-1a, then SplitMix64 to spread the bits: 128 bits from the text.fn bits(text: &str) -> (u64, u64) {    let mut h: u64 = 0xcbf29ce484222325;    for b in text.as_bytes() {        h ^= *b as u64;        h = h.wrapping_mul(0x100000001b3);    }    let mix = |mut z: u64| {        z = (z ^ (z >> 30)).wrapping_mul(0xbf58476d1ce4e5b9);        z = (z ^ (z >> 27)).wrapping_mul(0x94d049bb133111eb);        z ^ (z >> 31)    };    let a = mix(h.wrapping_add(0x9e3779b97f4a7c15));    (a, mix(a.wrapping_add(0x9e3779b97f4a7c15)))}fn hsl(h: f64, s: f64, l: f64) -> [u8; 3] {    let c = (1.0 - (2.0 * l - 1.0).abs()) * s;    let x = c * (1.0 - ((h / 60.0) % 2.0 - 1.0).abs());    let m = l - c / 2.0;    let (r, g, b) = match (h / 60.0) as u32 { 0 => (c, x, 0.0), 1 => (x, c, 0.0), 2 => (0.0, c, x), 3 => (0.0, x, c), 4 => (x, 0.0, c), _ => (c, 0.0, x) };    [((r + m) * 255.0).round() as u8, ((g + m) * 255.0).round() as u8, ((b + m) * 255.0).round() as u8]}// The 5×5 cells (the right two columns mirror the left two) and the colour.fn pattern(text: &str) -> ([[bool; 5]; 5], [u8; 3]) {    let (a, b) = bits(&text.trim().to_lowercase());    let mut cells = [[false; 5]; 5];    for row in 0..5 {        for col in 0..3 {            let on = (a >> (row * 3 + col)) & 1 == 1;            cells[row][col] = on;            cells[row][4 - col] = on;        }    }    let hue = (b % 360) as f64;    let sat = 0.45 + ((b >> 16) % 20) as f64 / 100.0;    let light = 0.45 + ((b >> 24) % 15) as f64 / 100.0;    (cells, hsl(hue, sat, light))}fn hex(c: [u8; 3]) -> String {    format!("#{:02x}{:02x}{:02x}", c[0], c[1], c[2])}/// The identicon of `text` as SVG, `size` pixels square (16 to 2048), on a light grey background with a margin of half a cell. Lower and upper case give the same picture.pub fn identicon_svg(text: &str, size: i64) -> String {    let size = size.clamp(16, 2048);    let (cells, c) = pattern(text);    let mut d = String::new();    for (r, row) in cells.iter().enumerate() {        for (k, on) in row.iter().enumerate() {            if *on {                d += &format!("M{} {}h2v2h-2z", 1 + k * 2, 1 + r * 2);            }        }    }    format!(r##"<svg xmlns="http://www.w3.org/2000/svg" width="{size}" height="{size}" viewBox="0 0 12 12" shape-rendering="crispEdges"><rect width="12" height="12" fill="#f0f0f0"/><path fill="{}" d="{}"/></svg>"##, hex(c), d)}/// The same identicon as a PNG file at `path` (folders made), `size` pixels square (16 to 2048): the path.pub fn identicon_png(text: &str, path: &str, size: i64) -> Result<String, String> {    let size = size.clamp(16, 2048) as u32;    let (cells, c) = pattern(text);    let img = image::RgbImage::from_fn(size, size, |x, y| {        let (cx, cy) = (x as f64 * 12.0 / size as f64, y as f64 * 12.0 / size as f64);        let (col, row) = (((cx - 1.0) / 2.0).floor(), ((cy - 1.0) / 2.0).floor());        let on = (0.0..5.0).contains(&col) && (0.0..5.0).contains(&row) && cells[row as usize][col as usize];        image::Rgb(if on { c } else { [240, 240, 240] })    });    if let Some(dir) = std::path::Path::new(path).parent().filter(|d| !d.as_os_str().is_empty()) {        std::fs::create_dir_all(dir).map_err(|e| format!("{}: {}", dir.display(), e))?;    }    img.save_with_format(path, image::ImageFormat::Png).map_err(|e| format!("{}: {}", path, e))?;    Ok(path.to_string())}/// The identicon's colour for `text`, as "#rrggbb": to match a profile's accent to its picture.pub fn identicon_color(text: &str) -> String {    hex(pattern(text).1)}
Thread
77Useful? Like it, or add to its thread below.

Thread 0 posts

On Identicons: questions, tips, corrections, and code that uses it.

No posts yet. The first one opens the thread.