Skip to main content

maple_render_core/
input.rs

1use std::path::Path;
2
3use ab_glyph::{Font, FontRef, PxScale, ScaleFont};
4use image::{Rgba, RgbaImage};
5use imageproc::drawing::draw_text_mut;
6
7use crate::error::{Error, Result};
8
9#[derive(Clone)]
10pub struct TextOptions {
11    pub font_size: f32,
12    pub color: Rgba<u8>,
13    pub background: Rgba<u8>,
14    pub padding: u32,
15}
16
17impl Default for TextOptions {
18    fn default() -> Self {
19        TextOptions {
20            font_size: 72.0,
21            color: Rgba([0, 0, 0, 255]),
22            background: Rgba([255, 255, 255, 255]),
23            padding: 40,
24        }
25    }
26}
27
28#[derive(Clone)]
29pub struct Input {
30    image: RgbaImage,
31    opaque: bool,
32    pub layer: u8,
33    pub xs: f64,
34    pub ys: f64,
35    pub xo: f64,
36    pub yo: f64,
37    pub xa: f64,
38    pub ya: f64,
39    pub in_scale: i32,
40    pub in_x0: f64,
41    pub in_y0: f64,
42}
43
44impl Default for Input {
45    fn default() -> Self {
46        Input {
47            image: RgbaImage::new(1, 1),
48            opaque: false,
49            layer: 1,
50            xs: 1.0,
51            ys: 1.0,
52            xo: 0.0,
53            yo: 0.0,
54            xa: 1.0,
55            ya: 0.0,
56            in_scale: 0,
57            in_x0: 0.0,
58            in_y0: 0.0,
59        }
60    }
61}
62
63impl Input {
64    pub fn new() -> Self {
65        Input::default()
66    }
67
68    pub fn load<P: AsRef<Path>>(path: P) -> Result<Self> {
69        Self::load_with_layer(path, 1)
70    }
71
72    pub fn load_with_layer<P: AsRef<Path>>(path: P, layer: u8) -> Result<Self> {
73        let path = path.as_ref();
74
75        if !path.exists() {
76            return Err(Error::FileNotFound(path.to_path_buf()));
77        }
78
79        let img = image::open(path)?;
80        let opaque = !img.color().has_alpha();
81        let rgba = img.to_rgba8();
82
83        let mut input = Input { image: rgba, opaque, layer, ..Default::default() };
84
85        input.compute_scale_params();
86
87        Ok(input)
88    }
89
90    pub fn from_image(image: RgbaImage, layer: u8) -> Self {
91        let opaque = image.pixels().all(|pixel| pixel[3] == 255);
92        let mut input = Input { image, opaque, layer, ..Default::default() };
93        input.compute_scale_params();
94        input
95    }
96
97    pub fn from_text(
98        text: &str,
99        layer: u8,
100        options: &TextOptions,
101        font_data: &[u8],
102    ) -> Result<Self> {
103        let font = FontRef::try_from_slice(font_data)
104            .map_err(|e| Error::TextRender(format!("Failed to load font: {}", e)))?;
105
106        let scale = PxScale::from(options.font_size);
107        let scaled_font = font.as_scaled(scale);
108
109        let mut width = 0.0f32;
110        for c in text.chars() {
111            let glyph_id = font.glyph_id(c);
112            width += scaled_font.h_advance(glyph_id);
113        }
114
115        let height = scaled_font.height();
116        let img_width = (width.ceil() as u32).max(1) + options.padding * 2;
117        let img_height = (height.ceil() as u32).max(1) + options.padding * 2;
118
119        let mut image = RgbaImage::from_pixel(img_width, img_height, options.background);
120
121        let x = options.padding as i32;
122        let y = options.padding as i32;
123
124        draw_text_mut(&mut image, options.color, x, y, scale, &font, text);
125
126        let opaque = options.color[3] == 255 && options.background[3] == 255;
127        let mut input = Input { image, opaque, layer, ..Default::default() };
128        input.compute_scale_params();
129
130        Ok(input)
131    }
132
133    fn compute_scale_params(&mut self) {
134        let w = self.image.width() as i32;
135        let h = self.image.height() as i32;
136
137        self.in_scale = w;
138        self.in_x0 = 0.0;
139        self.in_y0 = 0.0;
140
141        if h > w {
142            self.in_scale = h;
143            self.in_x0 = -((-w + h) as f64) / 2.0;
144        } else {
145            self.in_y0 = -((w - h) as f64) / 2.0;
146        }
147
148        self.xa = 1.0;
149        self.ya = 0.0;
150    }
151
152    pub fn get(&self) -> &RgbaImage {
153        &self.image
154    }
155
156    pub fn get_mut(&mut self) -> &mut RgbaImage {
157        self.opaque = false;
158        &mut self.image
159    }
160
161    pub fn is_opaque(&self) -> bool {
162        self.opaque
163    }
164
165    pub fn width(&self) -> u32 {
166        self.image.width()
167    }
168
169    pub fn height(&self) -> u32 {
170        self.image.height()
171    }
172
173    pub fn safe_pixel(&self, x: i32, y: i32) -> Rgba<u8> {
174        let w = self.image.width() as i32;
175        let h = self.image.height() as i32;
176
177        if x < 0 || y < 0 || x >= w || y >= h {
178            Rgba([0, 0, 0, 0])
179        } else {
180            *self.image.get_pixel(x as u32, y as u32)
181        }
182    }
183
184    pub fn set_rotation(&mut self, theta: f64) {
185        self.xa = theta.cos();
186        self.ya = theta.sin();
187    }
188}
189
190#[derive(Clone, Default)]
191pub struct Inputs {
192    data: Vec<Input>,
193}
194
195impl Inputs {
196    pub fn new() -> Self {
197        Inputs { data: Vec::new() }
198    }
199
200    pub fn add(&mut self) -> &mut Input {
201        self.data.push(Input::new());
202        self.data.last_mut().unwrap()
203    }
204
205    pub fn push(&mut self, input: Input) {
206        self.data.push(input);
207    }
208
209    pub fn get(&self) -> &[Input] {
210        &self.data
211    }
212
213    pub fn get_mut(&mut self) -> &mut [Input] {
214        &mut self.data
215    }
216
217    pub fn len(&self) -> usize {
218        self.data.len()
219    }
220
221    pub fn is_empty(&self) -> bool {
222        self.data.is_empty()
223    }
224
225    pub fn iter(&self) -> impl Iterator<Item = &Input> {
226        self.data.iter()
227    }
228
229    pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut Input> {
230        self.data.iter_mut()
231    }
232}
233
234impl std::ops::Index<usize> for Inputs {
235    type Output = Input;
236
237    fn index(&self, index: usize) -> &Self::Output {
238        &self.data[index]
239    }
240}
241
242impl std::ops::IndexMut<usize> for Inputs {
243    fn index_mut(&mut self, index: usize) -> &mut Self::Output {
244        &mut self.data[index]
245    }
246}
247
248#[cfg(test)]
249mod tests {
250    use super::*;
251
252    #[test]
253    fn opacity_is_detected_and_invalidated_by_mutable_access() {
254        let image = RgbaImage::from_pixel(2, 2, Rgba([10, 20, 30, 255]));
255        let mut input = Input::from_image(image, 1);
256        assert!(input.is_opaque());
257
258        input.get_mut().put_pixel(0, 0, Rgba([10, 20, 30, 255]));
259        assert!(!input.is_opaque());
260
261        let image = RgbaImage::from_pixel(2, 2, Rgba([10, 20, 30, 254]));
262        assert!(!Input::from_image(image, 1).is_opaque());
263    }
264
265    #[test]
266    fn text_opacity_follows_foreground_and_background_alpha() {
267        let font = include_bytes!("../../../fonts/DejaVuSans-Bold.ttf");
268        let opaque = Input::from_text("opaque", 1, &TextOptions::default(), font)
269            .expect("render opaque text");
270        assert!(opaque.is_opaque());
271
272        for options in [
273            TextOptions { color: Rgba([0, 0, 0, 254]), ..Default::default() },
274            TextOptions { background: Rgba([255, 255, 255, 254]), ..Default::default() },
275        ] {
276            let transparent =
277                Input::from_text("transparent", 1, &options, font).expect("render text");
278            assert!(!transparent.is_opaque());
279        }
280    }
281}