hashavatar 1.1.0

Stable deterministic procedural avatars in Rust with configurable identity hashing, WebP, optional PNG/JPEG/GIF, and SVG export
Documentation
use super::*;

pub fn render_robot_avatar_for_identity(
    spec: AvatarSpec,
    identity: &AvatarIdentity,
    background: AvatarBackground,
) -> Result<RgbaImage, AvatarSpecError> {
    spec.validate()?;

    let width = spec.width as i32;
    let height = spec.height as i32;
    let mut image =
        ImageBuffer::from_pixel(spec.width, spec.height, Color::rgb(255, 255, 255).into());
    let center_x = width / 2;
    let center_y = (height as f32 * 0.56) as i32;
    let bg = hsl_to_color(
        210.0 + identity.unit_f32(0) * 70.0,
        0.18 + identity.unit_f32(1) * 0.18,
        0.92,
    );
    let accent = hsl_to_color(160.0 + identity.unit_f32(2) * 120.0, 0.48, 0.62);
    let metal = hsl_to_color(200.0 + identity.unit_f32(3) * 28.0, 0.16, 0.74);
    let trim = hsl_to_color(205.0 + identity.unit_f32(4) * 22.0, 0.18, 0.46);
    let light = hsl_to_color(50.0 + identity.unit_f32(5) * 120.0, 0.84, 0.66);
    let dark = Color::rgb(47, 60, 72);
    let bg_fill = background_fill(background, bg);
    image.pixels_mut().for_each(|pixel| *pixel = bg_fill.into());

    let head_w = (width as f32 * (0.44 + identity.unit_f32(6) * 0.12)) as i32;
    let head_h = (height as f32 * (0.34 + identity.unit_f32(7) * 0.10)) as i32;
    let head_x = center_x - head_w / 2;
    let head_y = center_y - head_h / 2;
    draw_background_accent(
        &mut image,
        center_x,
        center_y,
        head_w / 2,
        head_h / 2,
        accent,
        0.5,
        background,
        identity,
    );

    draw_filled_rect_mut(
        &mut image,
        Rect::at(head_x, head_y).of_size(head_w as u32, head_h as u32),
        metal.into(),
    );
    draw_line_segment_mut(
        &mut image,
        (head_x as f32, head_y as f32),
        ((head_x + head_w) as f32, head_y as f32),
        trim.into(),
    );
    draw_line_segment_mut(
        &mut image,
        (head_x as f32, (head_y + head_h) as f32),
        ((head_x + head_w) as f32, (head_y + head_h) as f32),
        trim.into(),
    );
    draw_line_segment_mut(
        &mut image,
        (head_x as f32, head_y as f32),
        (head_x as f32, (head_y + head_h) as f32),
        trim.into(),
    );
    draw_line_segment_mut(
        &mut image,
        ((head_x + head_w) as f32, head_y as f32),
        ((head_x + head_w) as f32, (head_y + head_h) as f32),
        trim.into(),
    );

    let antenna_h = (height as f32 * 0.10) as i32;
    draw_line_segment_mut(
        &mut image,
        (center_x as f32, (head_y - antenna_h / 2) as f32),
        (center_x as f32, head_y as f32),
        dark.into(),
    );
    draw_filled_circle_mut(
        &mut image,
        (center_x, head_y - antenna_h / 2),
        (head_w as f32 * 0.05) as i32,
        accent.into(),
    );

    let eye_y = center_y - head_h / 6;
    let eye_offset = head_w / 4;
    let eye_rx = (head_w as f32 * 0.12) as i32;
    let eye_ry = (head_h as f32 * 0.10) as i32;
    for x in [center_x - eye_offset, center_x + eye_offset] {
        draw_filled_ellipse_mut(&mut image, (x, eye_y), eye_rx, eye_ry, light.into());
        if identity.byte(8).is_multiple_of(2) {
            draw_filled_circle_mut(
                &mut image,
                (x, eye_y),
                (eye_rx as f32 * 0.35) as i32,
                dark.into(),
            );
        }
    }

    let mouth_y = center_y + head_h / 5;
    let mouth_w = (head_w as f32 * 0.42) as i32;
    let mouth_h = (head_h as f32 * 0.12) as i32;
    draw_filled_rect_mut(
        &mut image,
        Rect::at(center_x - mouth_w / 2, mouth_y - mouth_h / 2)
            .of_size(mouth_w as u32, mouth_h as u32),
        dark.into(),
    );
    let teeth = 4 + (identity.byte(9) % 4) as i32;
    for idx in 1..teeth {
        let x = center_x - mouth_w / 2 + idx * mouth_w / teeth;
        draw_line_segment_mut(
            &mut image,
            (x as f32, (mouth_y - mouth_h / 2) as f32),
            (x as f32, (mouth_y + mouth_h / 2) as f32),
            metal.into(),
        );
    }

    let bolt_y = center_y;
    draw_filled_circle_mut(
        &mut image,
        (head_x + head_w / 8, bolt_y),
        (head_w as f32 * 0.035) as i32,
        trim.into(),
    );
    draw_filled_circle_mut(
        &mut image,
        (head_x + head_w - head_w / 8, bolt_y),
        (head_w as f32 * 0.035) as i32,
        trim.into(),
    );
    Ok(image)
}