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_fox_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.56) as i32;
    let mut image =
        ImageBuffer::from_pixel(spec.width, spec.height, Color::rgb(255, 255, 255).into());
    let bg = hsl_to_color(22.0 + identity.unit_f32(0) * 26.0, 0.26, 0.92);
    let orange = hsl_to_color(18.0 + identity.unit_f32(1) * 20.0, 0.76, 0.58);
    let deep_orange = hsl_to_color(16.0 + identity.unit_f32(2) * 12.0, 0.72, 0.42);
    let cream = hsl_to_color(40.0 + identity.unit_f32(3) * 10.0, 0.32, 0.93);
    let eye = Color::rgb(34, 28, 24);
    let nose = Color::rgb(55, 40, 34);
    image
        .pixels_mut()
        .for_each(|pixel| *pixel = background_fill(background, bg).into());

    let head_rx = (width as f32 * (0.25 + identity.unit_f32(4) * 0.08)) as i32;
    let head_ry = (height as f32 * (0.22 + identity.unit_f32(5) * 0.08)) as i32;
    let ear_h = (height as f32 * (0.16 + identity.unit_f32(6) * 0.09)) as i32;
    let ear_w = (width as f32 * (0.12 + identity.unit_f32(7) * 0.05)) as i32;
    draw_background_accent(
        &mut image,
        center_x,
        center_y,
        head_rx,
        head_ry,
        deep_orange,
        0.35,
        background,
        identity,
    );
    draw_ear(
        &mut image,
        EarSpec::left(center_x, center_y, head_rx, head_ry, ear_w, ear_h, -0.2),
        orange,
        cream,
        deep_orange,
    );
    draw_ear(
        &mut image,
        EarSpec::right(center_x, center_y, head_rx, head_ry, ear_w, ear_h, 0.2),
        orange,
        cream,
        deep_orange,
    );
    draw_filled_ellipse_mut(
        &mut image,
        (center_x, center_y),
        head_rx,
        head_ry,
        orange.into(),
    );
    draw_filled_ellipse_mut(
        &mut image,
        (center_x, center_y + head_ry / 4),
        head_rx / 2,
        head_ry / 3,
        cream.into(),
    );
    draw_polygon_mut(
        &mut image,
        &[
            Point::new(center_x - head_rx / 2, center_y - head_ry / 8),
            Point::new(center_x, center_y + head_ry / 3),
            Point::new(center_x - head_rx / 8, center_y + head_ry / 2),
        ],
        cream.into(),
    );
    draw_polygon_mut(
        &mut image,
        &[
            Point::new(center_x + head_rx / 2, center_y - head_ry / 8),
            Point::new(center_x, center_y + head_ry / 3),
            Point::new(center_x + head_rx / 8, center_y + head_ry / 2),
        ],
        cream.into(),
    );

    let eye_y = center_y - head_ry / 7;
    let eye_offset = head_rx / 3;
    for x in [center_x - eye_offset, center_x + eye_offset] {
        draw_filled_ellipse_mut(
            &mut image,
            (x, eye_y),
            head_rx / 10,
            head_ry / 8,
            Color::rgb(255, 255, 255).into(),
        );
        draw_filled_ellipse_mut(
            &mut image,
            (x, eye_y),
            head_rx / 18,
            head_ry / 7,
            eye.into(),
        );
    }
    let nose_y = center_y + head_ry / 4;
    draw_polygon_mut(
        &mut image,
        &[
            Point::new(center_x - head_rx / 10, nose_y),
            Point::new(center_x + head_rx / 10, nose_y),
            Point::new(center_x, nose_y + head_ry / 10),
        ],
        nose.into(),
    );
    draw_smile_arc(
        &mut image,
        center_x - head_rx / 12,
        nose_y + head_ry / 10,
        head_rx / 7,
        nose,
        0.45,
    );
    draw_smile_arc(
        &mut image,
        center_x + head_rx / 12,
        nose_y + head_ry / 10,
        head_rx / 7,
        nose,
        0.45,
    );
    Ok(image)
}