Skip to main content

shuyi_ui/frame/
portrait.rs

1//! Drawing a picture in an area of cells (game ADR-0043): one sprite item, the
2//! image at the largest whole scale that fits, centred. A game's
3//! portraits are drawn this way.
4
5#[cfg(not(feature = "std"))]
6use crate::float::Float;
7use shuyi_assets::image::{ImageId, ImageInfo};
8
9use crate::frame::glyph_buffer::{GlyphBuffer, Layer, PxRect, Rect, Sprite};
10
11/// Where an image of `size` is drawn in `frame` (console pixels): at the
12/// largest whole scale that fits, centred (an odd pixel left over goes to
13/// the right or the bottom). `None` if it doesn't fit even at 1×, or has no
14/// pixels.
15pub fn fit_whole_scale(size: ImageInfo, frame: PxRect) -> Option<PxRect> {
16    let (w, h) = (i64::from(size.width), i64::from(size.height));
17    if w == 0 || h == 0 {
18        return None;
19    }
20    let scale = (i64::from(frame.w) / w).min(i64::from(frame.h) / h);
21    if scale < 1 {
22        return None;
23    }
24    // Both are at most the frame's side, so they fit in i32.
25    let (w, h) = (
26        i32::try_from(w * scale).ok()?,
27        i32::try_from(h * scale).ok()?,
28    );
29    Some(Rect::new(
30        frame.x + (frame.w - w) / 2,
31        frame.y + (frame.h - h) / 2,
32        w,
33        h,
34    ))
35}
36
37/// The opacity of a picture dimmed by `dim` (0 = full color, 1 = gone):
38/// the renderer fades every pixel toward what is behind it (game ADR-0018).
39#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)] // clamped to 0..=255
40pub fn dim_opacity(dim: f32) -> u8 {
41    (255.0 * (1.0 - dim)).round().clamp(0.0, 255.0) as u8
42}
43
44/// Draws `image` (of `size` pixels) in the area of `(w, h)` cells whose
45/// top-left cell is `(x, y)`, at the largest whole scale that fits it,
46/// centred, as one sprite under the glyphs: a transparent pixel shows the
47/// cells' background, so clear the area first. `dim` (0 = full color,
48/// 1 = gone) fades it toward that background; `mirror` flips it left to
49/// right. Clipped to the buffer. Draws nothing if the picture is too big
50/// for the area even at 1×.
51pub fn draw_fitted(
52    buf: &mut GlyphBuffer,
53    (x, y): (i32, i32),
54    (w, h): (i32, i32),
55    (image, size): (ImageId, ImageInfo),
56    dim: f32,
57    mirror: bool,
58) {
59    let (cw, ch) = buf.spec().cell_px();
60    let frame = Rect::new(
61        x.saturating_mul(cw),
62        y.saturating_mul(ch),
63        w.saturating_mul(cw),
64        h.saturating_mul(ch),
65    );
66    let src = (i32::try_from(size.width), i32::try_from(size.height));
67    if let (Some(dest), (Ok(w), Ok(h))) = (fit_whole_scale(size, frame), src) {
68        buf.add_sprite(Sprite {
69            flip_x: mirror,
70            opacity: dim_opacity(dim),
71            ..Sprite::new(image, Rect::new(0, 0, w, h), dest, Layer::Under)
72        });
73    }
74}
75
76#[cfg(test)]
77mod tests;