use super::*;
pub fn render_monster_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.58) as i32;
let mut image =
ImageBuffer::from_pixel(spec.width, spec.height, Color::rgb(255, 255, 255).into());
let skin = hsl_to_color(
identity.unit_f32(0) * 360.0,
0.48 + identity.unit_f32(1) * 0.24,
0.46 + identity.unit_f32(2) * 0.20,
);
let shade = hsl_to_color(
identity.unit_f32(3) * 360.0,
0.38 + identity.unit_f32(4) * 0.18,
0.24 + identity.unit_f32(5) * 0.10,
);
let accent = hsl_to_color(
20.0 + identity.unit_f32(6) * 320.0,
0.34 + identity.unit_f32(7) * 0.26,
0.86,
);
let mouth = Color::rgb(48, 18, 24);
let eye_white = Color::rgb(252, 248, 236);
let pupil = Color::rgb(24, 20, 28);
image
.pixels_mut()
.for_each(|pixel| *pixel = background_fill(background, accent).into());
let head_rx = (width as f32 * (0.23 + identity.unit_f32(8) * 0.10)) as i32;
let head_ry = (height as f32 * (0.22 + identity.unit_f32(9) * 0.11)) as i32;
let horn_height = (height as f32 * (0.08 + identity.unit_f32(10) * 0.10)) as i32;
let horn_width = (width as f32 * (0.06 + identity.unit_f32(11) * 0.05)) as i32;
let eye_count = 1 + (identity.byte(12) % 3) as usize;
let mouth_style = identity.byte(13) % 3;
let body_style = identity.byte(14) % 3;
let spot_count = 3 + (identity.byte(15) % 5) as i32;
let tentacle_count = 2 + (identity.byte(16) % 4) as i32;
draw_background_accent(
&mut image,
center_x,
center_y,
head_rx,
head_ry,
shade,
0.30 + identity.unit_f32(17) * 0.25,
background,
identity,
);
match body_style {
0 => draw_filled_ellipse_mut(
&mut image,
(center_x, center_y),
head_rx,
head_ry,
skin.into(),
),
1 => {
draw_filled_ellipse_mut(
&mut image,
(center_x, center_y + head_ry / 8),
head_rx,
head_ry - head_ry / 8,
skin.into(),
);
draw_polygon_mut(
&mut image,
&[
Point::new(center_x - head_rx, center_y),
Point::new(center_x, center_y - head_ry),
Point::new(center_x + head_rx, center_y),
Point::new(center_x + head_rx / 2, center_y + head_ry),
Point::new(center_x - head_rx / 2, center_y + head_ry),
],
skin.into(),
);
}
_ => {
draw_filled_rect_mut(
&mut image,
Rect::at(center_x - head_rx, center_y - head_ry)
.of_size((head_rx * 2) as u32, (head_ry * 2) as u32),
skin.into(),
);
draw_filled_circle_mut(
&mut image,
(center_x - head_rx, center_y - head_ry / 2),
head_ry / 2,
skin.into(),
);
draw_filled_circle_mut(
&mut image,
(center_x + head_rx, center_y - head_ry / 2),
head_ry / 2,
skin.into(),
);
draw_filled_circle_mut(
&mut image,
(center_x - head_rx, center_y + head_ry / 2),
head_ry / 2,
skin.into(),
);
draw_filled_circle_mut(
&mut image,
(center_x + head_rx, center_y + head_ry / 2),
head_ry / 2,
skin.into(),
);
}
}
if identity.byte(18).is_multiple_of(2) {
draw_polygon_mut(
&mut image,
&[
Point::new(center_x - head_rx / 2, center_y - head_ry),
Point::new(
center_x - head_rx / 3 - horn_width,
center_y - head_ry - horn_height,
),
Point::new(center_x - head_rx / 8, center_y - head_ry / 2),
],
shade.into(),
);
draw_polygon_mut(
&mut image,
&[
Point::new(center_x + head_rx / 2, center_y - head_ry),
Point::new(
center_x + head_rx / 3 + horn_width,
center_y - head_ry - horn_height,
),
Point::new(center_x + head_rx / 8, center_y - head_ry / 2),
],
shade.into(),
);
} else {
for spike in 0..3 {
let spike_x = center_x - head_rx / 2 + spike * head_rx / 2;
draw_polygon_mut(
&mut image,
&[
Point::new(spike_x - horn_width / 2, center_y - head_ry / 2),
Point::new(spike_x, center_y - head_ry - horn_height / 2),
Point::new(spike_x + horn_width / 2, center_y - head_ry / 2),
],
shade.into(),
);
}
}
for index in 0..spot_count {
let x = center_x - head_rx / 2 + (index * head_rx) / spot_count;
let y = center_y - head_ry / 3 + ((index * 37 + identity.byte(19) as i32) % head_ry.max(1));
let radius =
(head_rx as f32 * (0.05 + ((index + 1) as f32 / spot_count as f32) * 0.06)) as i32;
draw_filled_circle_mut(
&mut image,
(x, y),
radius.max(3),
Color::rgba(shade.0[0], shade.0[1], shade.0[2], 168).into(),
);
}
let eye_y = center_y - head_ry / 5;
let eye_rx = (head_rx as f32 * (0.10 + identity.unit_f32(20) * 0.08)) as i32;
let eye_ry = (head_ry as f32 * (0.10 + identity.unit_f32(21) * 0.10)) as i32;
let eye_spacing = if eye_count == 1 {
0
} else {
(head_rx as f32 * 0.46 / (eye_count - 1) as f32) as i32
};
let eye_start = center_x - eye_spacing * ((eye_count.saturating_sub(1)) as i32) / 2;
for index in 0..eye_count {
let x = eye_start + eye_spacing * index as i32;
draw_filled_ellipse_mut(&mut image, (x, eye_y), eye_rx, eye_ry, eye_white.into());
if identity.byte(22).is_multiple_of(2) {
draw_filled_ellipse_mut(
&mut image,
(x, eye_y),
(eye_rx / 3).max(2),
(eye_ry - 1).max(2),
pupil.into(),
);
} else {
draw_filled_circle_mut(&mut image, (x, eye_y), (eye_ry / 2).max(2), pupil.into());
}
draw_filled_circle_mut(
&mut image,
(x - eye_rx / 3, eye_y - eye_ry / 3),
(eye_rx / 5).max(1),
Color::rgba(255, 255, 255, 220).into(),
);
}
let mouth_y = center_y + head_ry / 3;
match mouth_style {
0 => {
draw_filled_ellipse_mut(
&mut image,
(center_x, mouth_y),
head_rx / 3,
head_ry / 8,
mouth.into(),
);
for fang_x in [center_x - head_rx / 8, center_x + head_rx / 8] {
draw_polygon_mut(
&mut image,
&[
Point::new(fang_x - head_rx / 24, mouth_y - 2),
Point::new(fang_x + head_rx / 24, mouth_y - 2),
Point::new(fang_x, mouth_y + head_ry / 5),
],
eye_white.into(),
);
}
}
1 => {
draw_smile_arc(
&mut image,
center_x - head_rx / 10,
mouth_y,
head_rx / 4,
mouth,
0.50,
);
draw_smile_arc(
&mut image,
center_x + head_rx / 10,
mouth_y,
head_rx / 4,
mouth,
0.50,
);
draw_line_segment_mut(
&mut image,
((center_x - head_rx / 4) as f32, mouth_y as f32),
((center_x + head_rx / 4) as f32, mouth_y as f32),
mouth.into(),
);
}
_ => {
draw_filled_rect_mut(
&mut image,
Rect::at(center_x - head_rx / 3, mouth_y - head_ry / 10)
.of_size((head_rx * 2 / 3) as u32, (head_ry / 5).max(1) as u32),
mouth.into(),
);
for tooth in 0..4 {
let tooth_x = center_x - head_rx / 4 + tooth * head_rx / 6;
draw_polygon_mut(
&mut image,
&[
Point::new(tooth_x - head_rx / 30, mouth_y - head_ry / 10),
Point::new(tooth_x + head_rx / 30, mouth_y - head_ry / 10),
Point::new(tooth_x, mouth_y + head_ry / 14),
],
eye_white.into(),
);
}
}
}
if identity.byte(23).is_multiple_of(2) {
for index in 0..tentacle_count {
let start_x = center_x - head_rx / 2 + (index * head_rx) / tentacle_count;
let start_y = center_y + head_ry - 4;
let end_x = start_x + ((index % 2) * 2 - 1) * head_rx / 6;
let end_y = start_y + head_ry / 2;
draw_antialiased_line_segment_mut(
&mut image,
(start_x, start_y),
(end_x, end_y),
shade.into(),
interpolate,
);
draw_filled_circle_mut(
&mut image,
(end_x, end_y),
(head_rx / 18).max(2),
shade.into(),
);
}
}
Ok(image)
}
#[derive(Clone, Copy)]
pub(crate) struct FaceLayout {
pub(crate) center_x: i32,
pub(crate) center_y: i32,
pub(crate) head_rx: i32,
pub(crate) head_ry: i32,
}
#[derive(Clone, Copy)]
pub(crate) enum CreatureEyeStyle {
Round,
Tall,
Hollow,
}
#[derive(Clone, Copy)]
#[allow(dead_code)]
pub(crate) enum CreatureMouthStyle {
Smile,
Fang,
Flat,
}
pub(crate) fn draw_creature_eyes(
image: &mut RgbaImage,
layout: FaceLayout,
count: usize,
style: CreatureEyeStyle,
eye_white: Color,
pupil: Color,
) {
let spacing = if count <= 1 {
0
} else {
(layout.head_rx as f32 * 0.48 / (count - 1) as f32) as i32
};
let start_x = layout.center_x - spacing * (count.saturating_sub(1) as i32) / 2;
let eye_y = layout.center_y - layout.head_ry / 5;
let eye_rx = (layout.head_rx as f32 * 0.12) as i32;
let eye_ry = (layout.head_ry as f32 * 0.12) as i32;
for index in 0..count {
let x = start_x + spacing * index as i32;
match style {
CreatureEyeStyle::Round => {
draw_filled_circle_mut(image, (x, eye_y), eye_rx.max(3), eye_white.into());
draw_filled_circle_mut(image, (x, eye_y), (eye_rx / 2).max(2), pupil.into());
}
CreatureEyeStyle::Tall => {
draw_filled_ellipse_mut(image, (x, eye_y), eye_rx, eye_ry + 4, eye_white.into());
draw_filled_ellipse_mut(
image,
(x, eye_y),
(eye_rx / 3).max(2),
(eye_ry + 2).max(2),
pupil.into(),
);
}
CreatureEyeStyle::Hollow => {
draw_filled_ellipse_mut(image, (x, eye_y), eye_rx + 3, eye_ry + 5, pupil.into());
draw_filled_ellipse_mut(
image,
(x, eye_y + 1),
(eye_rx / 2).max(2),
(eye_ry / 2).max(2),
Color::rgba(255, 255, 255, 20).into(),
);
}
}
}
}
pub(crate) fn draw_creature_mouth(
image: &mut RgbaImage,
layout: FaceLayout,
style: CreatureMouthStyle,
color: Color,
) {
let mouth_y = layout.center_y + layout.head_ry / 3;
match style {
CreatureMouthStyle::Smile => {
draw_smile_arc(
image,
layout.center_x - layout.head_rx / 10,
mouth_y,
layout.head_rx / 4,
color,
0.45,
);
draw_smile_arc(
image,
layout.center_x + layout.head_rx / 10,
mouth_y,
layout.head_rx / 4,
color,
0.45,
);
}
CreatureMouthStyle::Fang => {
draw_filled_ellipse_mut(
image,
(layout.center_x, mouth_y),
layout.head_rx / 3,
layout.head_ry / 8,
color.into(),
);
for tooth_x in [
layout.center_x - layout.head_rx / 7,
layout.center_x + layout.head_rx / 7,
] {
draw_polygon_mut(
image,
&[
Point::new(tooth_x - layout.head_rx / 26, mouth_y - 1),
Point::new(tooth_x + layout.head_rx / 26, mouth_y - 1),
Point::new(tooth_x, mouth_y + layout.head_ry / 5),
],
Color::rgb(248, 246, 238).into(),
);
}
}
CreatureMouthStyle::Flat => {
draw_filled_rect_mut(
image,
Rect::at(layout.center_x - layout.head_rx / 3, mouth_y - 3)
.of_size((layout.head_rx * 2 / 3) as u32, 6),
color.into(),
);
}
}
}