#![forbid(unsafe_code)]
mod badge;
mod crest;
mod draws;
mod grid;
mod monster;
mod nerd;
use draws::Draws;
pub type Rgb = [u8; 3];
const FACE_CONTEXT: &str = "gsichtl 2026-10-02 face v1";
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum Kind {
Monster,
Nerd,
Crest,
Badge,
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Avatar {
kind: Kind,
side: u8,
background: Rgb,
cells: Vec<Option<Rgb>>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Rgba {
pub width: u32,
pub height: u32,
pub pixels: Vec<u8>,
}
pub fn face(seed: &[u8]) -> Avatar {
let mut draws = Draws::new(FACE_CONTEXT, seed);
if draws.pick(2) == 0 {
monster::build(monster::draw(&mut draws))
} else {
nerd::build(nerd::draw(&mut draws))
}
}
pub fn monster(seed: &[u8]) -> Avatar {
monster::build(monster::draw(&mut Draws::new(monster::CONTEXT, seed)))
}
pub fn nerd(seed: &[u8]) -> Avatar {
nerd::build(nerd::draw(&mut Draws::new(nerd::CONTEXT, seed)))
}
pub fn crest(seed: &[u8]) -> Avatar {
crest::build(crest::draw(&mut Draws::new(crest::CONTEXT, seed)))
}
pub fn badge(seed: &[u8]) -> Avatar {
badge::build(badge::draw(&mut Draws::new(badge::CONTEXT, seed)))
}
impl Avatar {
pub fn kind(&self) -> Kind {
self.kind
}
pub fn side(&self) -> u8 {
self.side
}
pub fn background(&self) -> Rgb {
self.background
}
pub fn cell(&self, x: u8, y: u8) -> Option<Rgb> {
if x >= self.side || y >= self.side {
return None;
}
let index = usize::from(y) * usize::from(self.side) + usize::from(x);
self.cells.get(index).copied().flatten()
}
pub fn to_rgba(&self, cell_px: u8, margin_px: u8) -> Rgba {
let cell = u32::from(cell_px);
let margin = u32::from(margin_px);
let grid = u32::from(self.side) * cell;
let side = grid + 2 * margin;
let mut pixels = Vec::with_capacity(side as usize * side as usize * 4);
for py in 0..side {
for px in 0..side {
let color = if (margin..margin + grid).contains(&px)
&& (margin..margin + grid).contains(&py)
{
let x = u8::try_from((px - margin) / cell).unwrap_or(u8::MAX);
let y = u8::try_from((py - margin) / cell).unwrap_or(u8::MAX);
self.cell(x, y).unwrap_or(self.background)
} else {
self.background
};
pixels.extend_from_slice(&color);
pixels.push(u8::MAX);
}
}
Rgba {
width: side,
height: side,
pixels,
}
}
#[cfg(feature = "png")]
pub fn to_png(&self, cell_px: u8, margin_px: u8) -> Result<Vec<u8>, png::EncodingError> {
let image = self.to_rgba(cell_px, margin_px);
let mut out = Vec::new();
let mut encoder = png::Encoder::new(&mut out, image.width, image.height);
encoder.set_color(png::ColorType::Rgba);
encoder.set_depth(png::BitDepth::Eight);
let mut writer = encoder.write_header()?;
writer.write_image_data(&image.pixels)?;
writer.finish()?;
Ok(out)
}
}