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_dragon_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 center_x = width / 2;
    let center_y = (height as f32 * 0.57) as i32;
    let head_rx = (width as f32 * 0.27) as i32;
    let head_ry = (height as f32 * 0.23) as i32;
    let scale = hsl_to_color(105.0 + identity.unit_f32(1) * 70.0, 0.46, 0.46);
    let belly = hsl_to_color(70.0 + identity.unit_f32(2) * 35.0, 0.42, 0.72);
    let horn = hsl_to_color(40.0 + identity.unit_f32(3) * 20.0, 0.34, 0.84);
    let flame = hsl_to_color(14.0 + identity.unit_f32(4) * 25.0, 0.78, 0.56);
    let bg = hsl_to_color(120.0 + identity.unit_f32(5) * 45.0, 0.18, 0.92);
    let dark = Color::rgb(24, 48, 34);
    let mut image = ImageBuffer::from_pixel(
        spec.width,
        spec.height,
        background_fill(background, bg).into(),
    );
    draw_background_accent(
        &mut image, center_x, center_y, head_rx, head_ry, flame, 0.40, background, identity,
    );

    for side in [-1, 1] {
        let horn_points = [
            Point::new(center_x + side * head_rx / 2, center_y - head_ry),
            Point::new(center_x + side * head_rx / 4, center_y - head_ry * 8 / 5),
            Point::new(center_x + side * head_rx / 8, center_y - head_ry),
        ];
        draw_polygon_mut(&mut image, &horn_points, horn.into());
    }
    draw_filled_ellipse_mut(
        &mut image,
        (center_x, center_y),
        head_rx,
        head_ry,
        scale.into(),
    );
    draw_filled_ellipse_mut(
        &mut image,
        (center_x, center_y + head_ry / 4),
        head_rx / 2,
        head_ry / 3,
        belly.into(),
    );
    for x in [center_x - head_rx / 3, center_x + head_rx / 3] {
        draw_filled_circle_mut(
            &mut image,
            (x, center_y - head_ry / 5),
            (head_rx / 10).max(3),
            Color::rgb(255, 255, 255).into(),
        );
        draw_filled_circle_mut(
            &mut image,
            (x, center_y - head_ry / 5),
            (head_rx / 20).max(2),
            dark.into(),
        );
    }
    for x in [center_x - head_rx / 7, center_x + head_rx / 7] {
        draw_filled_circle_mut(
            &mut image,
            (x, center_y + head_ry / 3),
            (head_rx / 24).max(2),
            dark.into(),
        );
    }
    for offset in [-1, 0, 1] {
        let x = center_x + offset * head_rx / 5;
        let spike_y = center_y - head_ry;
        let spike = [
            Point::new(x - head_rx / 14, spike_y),
            Point::new(x, spike_y - head_ry / 4),
            Point::new(x + head_rx / 14, spike_y),
        ];
        draw_polygon_mut(&mut image, &spike, flame.into());
    }

    Ok(image)
}