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_wizard_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 head_rx = (width as f32 * (0.16 + identity.unit_f32(15) * 0.06)) as i32;
    let head_ry = (height as f32 * (0.16 + identity.unit_f32(16) * 0.06)) as i32;
    let layout = FaceLayout {
        center_x: width / 2,
        center_y: (height as f32 * (0.57 + identity.unit_f32(17) * 0.07)) as i32,
        head_rx,
        head_ry,
    };
    let bg = hsl_to_color(
        220.0 + identity.unit_f32(10) * 85.0,
        0.20 + identity.unit_f32(18) * 0.12,
        0.90 + identity.unit_f32(19) * 0.04,
    );
    let hat = hsl_to_color(
        210.0 + identity.unit_f32(11) * 110.0,
        0.34 + identity.unit_f32(20) * 0.22,
        0.28 + identity.unit_f32(21) * 0.16,
    );
    let hat_band = hsl_to_color(
        24.0 + identity.unit_f32(12) * 160.0,
        0.62 + identity.unit_f32(22) * 0.24,
        0.48 + identity.unit_f32(23) * 0.18,
    );
    let skin = hsl_to_color(
        18.0 + identity.unit_f32(13) * 28.0,
        0.22 + identity.unit_f32(24) * 0.20,
        0.74 + identity.unit_f32(25) * 0.12,
    );
    let beard = hsl_to_color(
        35.0 + identity.unit_f32(14) * 45.0,
        0.06 + identity.unit_f32(26) * 0.12,
        0.80 + identity.unit_f32(27) * 0.16,
    );
    let hat_width = (layout.head_rx as f32 * (1.0 + identity.unit_f32(28) * 0.55)) as i32;
    let hat_height = (layout.head_ry as f32 * (1.7 + identity.unit_f32(29) * 0.75)) as i32;
    let tip_shift = (layout.head_rx as f32 * (identity.unit_f32(30) - 0.5) * 0.8) as i32;
    let brim_width = (layout.head_rx as f32 * (2.45 + identity.unit_f32(31) * 0.75)) as i32;
    let brim_height = (layout.head_ry as f32 * (0.22 + identity.unit_f32(32) * 0.16)) as i32;
    let mut image = ImageBuffer::from_pixel(
        spec.width,
        spec.height,
        background_fill(background, bg).into(),
    );
    draw_background_accent(
        &mut image,
        layout.center_x,
        layout.center_y,
        layout.head_rx,
        layout.head_ry,
        hat_band,
        0.20,
        background,
        identity,
    );
    draw_polygon_mut(
        &mut image,
        &[
            Point::new(
                layout.center_x - hat_width,
                layout.center_y - layout.head_ry / 2,
            ),
            Point::new(
                layout.center_x + hat_width,
                layout.center_y - layout.head_ry / 2,
            ),
            Point::new(
                layout.center_x + tip_shift,
                layout.center_y - layout.head_ry / 2 - hat_height,
            ),
        ],
        hat.into(),
    );
    draw_filled_rect_mut(
        &mut image,
        Rect::at(
            layout.center_x - brim_width / 2,
            layout.center_y - layout.head_ry / 2,
        )
        .of_size(brim_width.max(2) as u32, brim_height.max(2) as u32),
        hat_band.into(),
    );
    let star_count = 1 + (identity.byte(33) % 4) as i32;
    for star in 0..star_count {
        let sx = layout.center_x - hat_width / 2
            + (identity.byte(34 + star as usize) as i32 % hat_width.max(1));
        let sy = layout.center_y - layout.head_ry / 2 - hat_height / 2
            + (identity.byte(39 + star as usize) as i32 % (hat_height / 2).max(1));
        draw_filled_circle_mut(
            &mut image,
            (sx, sy),
            (layout.head_rx / 12).max(2),
            Color::rgba(hat_band.0[0], hat_band.0[1], hat_band.0[2], 210).into(),
        );
    }
    draw_filled_circle_mut(
        &mut image,
        (layout.center_x, layout.center_y),
        layout.head_rx,
        skin.into(),
    );
    draw_polygon_mut(
        &mut image,
        &[
            Point::new(
                layout.center_x
                    - layout.head_rx / 2
                    - (identity.byte(44) as i32 % layout.head_rx.max(1)) / 6,
                layout.center_y + layout.head_ry / 3,
            ),
            Point::new(
                layout.center_x
                    + layout.head_rx / 2
                    + (identity.byte(45) as i32 % layout.head_rx.max(1)) / 6,
                layout.center_y + layout.head_ry / 3,
            ),
            Point::new(
                layout.center_x + (identity.unit_f32(46) * layout.head_rx as f32 * 0.4) as i32
                    - layout.head_rx / 5,
                layout.center_y
                    + layout.head_ry
                    + (layout.head_ry as f32 * (0.35 + identity.unit_f32(47) * 0.55)) as i32,
            ),
        ],
        beard.into(),
    );
    draw_creature_eyes(
        &mut image,
        layout,
        if identity.byte(48).is_multiple_of(7) {
            1
        } else {
            2
        },
        if identity.byte(49).is_multiple_of(2) {
            CreatureEyeStyle::Round
        } else {
            CreatureEyeStyle::Tall
        },
        Color::rgb(255, 255, 255),
        Color::rgb(36, 30, 52),
    );
    draw_creature_mouth(
        &mut image,
        layout,
        CreatureMouthStyle::Smile,
        Color::rgb(86, 64, 58),
    );
    draw_filled_circle_mut(
        &mut image,
        (
            layout.center_x + tip_shift + layout.head_rx / 2,
            layout.center_y - layout.head_ry / 2 - hat_height,
        ),
        (layout.head_rx / 6).max(3),
        hat_band.into(),
    );
    Ok(image)
}