| 10174045 Andy McCall ↶ |
1 |
|
| 2 |
|
| 3 |
|
| 4 |
|
| 5 |
use axum::extract::Path; |
| 6 |
use axum::http::header; |
| 7 |
use axum::response::{IntoResponse, Response}; |
| 8 |
|
| 9 |
|
| 10 |
|
| 11 |
pub fn identicon(name: &str) -> String { |
| 12 |
let h = blake3::hash(name.to_ascii_lowercase().as_bytes()); |
| 13 |
let b = h.as_bytes(); |
| 14 |
let hue = u16::from_le_bytes([b[0], b[1]]) % 360; |
| 15 |
let bits = u32::from_le_bytes([b[2], b[3], b[4], b[5]]); |
| 16 |
let mut cells = String::new(); |
| 17 |
for row in 0..5u32 { |
| 18 |
for col in 0..3u32 { |
| 19 |
if bits >> (row * 3 + col) & 1 == 1 { |
| 20 |
for x in [col, 4 - col] { |
| 21 |
cells.push_str(&format!(r#"<rect x="{}" y="{}" width="1" height="1"/>"#, x + 1, row + 1)); |
| 22 |
if x == 2 { |
| 23 |
break; |
| 24 |
} |
| 25 |
} |
| 26 |
} |
| 27 |
} |
| 28 |
} |
| 29 |
format!( |
| 30 |
r#"<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 7 7" shape-rendering="crispEdges"><rect width="7" height="7" fill="hsl({hue},45%,92%)"/><g fill="hsl({hue},65%,45%)">{cells}</g></svg>"# |
| 31 |
) |
| 32 |
} |
| 33 |
|
| 34 |
|
| 35 |
pub async fn avatar(Path(name): Path<String>) -> Response { |
| 36 |
let name = name.trim_end_matches(".svg"); |
| 37 |
([(header::CONTENT_TYPE, "image/svg+xml"), (header::CACHE_CONTROL, "public, max-age=86400")], identicon(name)).into_response() |
| 38 |
} |
| 39 |
|
| 40 |
#[cfg(test)] |
| 41 |
mod tests { |
| 42 |
use super::identicon; |
| 43 |
|
| 44 |
#[test] |
| 45 |
fn identicons_are_stable_symmetric_and_case_blind() { |
| 46 |
let a = identicon("andy"); |
| 47 |
assert_eq!(a, identicon("Andy"), "names are case-insensitive"); |
| 48 |
assert_ne!(a, identicon("bob")); |
| 49 |
assert!(a.starts_with("<svg") && a.ends_with("</svg>")); |
| 50 |
|
| 51 |
let cells: Vec<(u32, u32)> = a |
| 52 |
.split(r#"<rect x=""#) |
| 53 |
.skip(1) |
| 54 |
.filter_map(|r| { |
| 55 |
let x: u32 = r.split('"').next()?.parse().ok()?; |
| 56 |
let y: u32 = r.split(r#"y=""#).nth(1)?.split('"').next()?.parse().ok()?; |
| 57 |
Some((x, y)) |
| 58 |
}) |
| 59 |
.filter(|(x, _)| (1..=5).contains(x)) |
| 60 |
.collect(); |
| 61 |
for (x, y) in &cells { |
| 62 |
assert!(cells.contains(&(6 - x, *y)), "({x},{y}) has no mirror in {a}"); |
| 63 |
} |
| 64 |
} |
| 65 |
} |