render_agnostic/renderers/
image.rs

1use std::f64::consts::PI;
2
3use ab_glyph::FontVec;
4use anchor2d::{Anchor2D, HorizontalAnchor, VerticalAnchorContext, VerticalAnchorValue};
5use glam::{DVec2, IVec2, dvec2, ivec2};
6use image::{
7    Rgba, RgbaImage,
8    imageops::{FilterType, overlay, resize},
9};
10use imageproc::{
11    drawing::{draw_filled_circle_mut, draw_polygon_mut, draw_text_mut, text_size},
12    point::Point,
13};
14use palette::Srgba;
15
16use crate::Renderer;
17
18fn srgba_to_rgba8(color: Srgba) -> Rgba<u8> {
19    let red = (color.red * 255.0).round().clamp(0.0, 255.0) as u8;
20    let green = (color.green * 255.0).round().clamp(0.0, 255.0) as u8;
21    let blue = (color.blue * 255.0).round().clamp(0.0, 255.0) as u8;
22    let alpha = (color.alpha * 255.0).round().clamp(0.0, 255.0) as u8;
23    Rgba([red, green, blue, alpha])
24}
25
26#[derive(Clone, PartialEq)]
27pub struct ImageRenderer {
28    width: u32,
29    height: u32,
30    image: RgbaImage,
31    scale: f64,
32    scaling_target: DVec2,
33    supersampling: u32,
34}
35
36impl ImageRenderer {
37    pub fn new(
38        width: u32,
39        height: u32,
40        scale: f64,
41        scaling_target: DVec2,
42        supersampling: u32,
43    ) -> Self {
44        Self {
45            width,
46            height,
47            image: RgbaImage::new(width * supersampling, height * supersampling),
48            scale,
49            scaling_target,
50            supersampling,
51        }
52    }
53
54    fn get_supersampled_width(&self) -> u32 {
55        self.width * self.supersampling
56    }
57
58    fn get_supersampled_height(&self) -> u32 {
59        self.height * self.supersampling
60    }
61
62    fn map_x(&self, x: f64) -> f64 {
63        let target_x = self.get_supersampled_width() as f64 * self.scaling_target.x;
64        (x * self.supersampling as f64 - target_x) * self.scale + target_x
65    }
66
67    fn map_y(&self, y: f64) -> f64 {
68        let target_y = self.get_supersampled_height() as f64 * self.scaling_target.y;
69        (y * self.supersampling as f64 - target_y) * self.scale + target_y
70    }
71
72    fn map_dvec2(&self, v: DVec2) -> DVec2 {
73        dvec2(self.map_x(v.x), self.map_y(v.y))
74    }
75
76    pub fn reset(&mut self) {
77        self.image = self.transparent();
78    }
79
80    pub fn get_image(&self) -> RgbaImage {
81        let mut image = self.black();
82
83        overlay(&mut image, &self.image, 0, 0);
84
85        resize(&image, self.width, self.height, FilterType::Lanczos3)
86    }
87
88    fn transparent(&self) -> RgbaImage {
89        RgbaImage::new(
90            self.get_supersampled_width(),
91            self.get_supersampled_height(),
92        )
93    }
94
95    fn black(&self) -> RgbaImage {
96        RgbaImage::from_par_fn(
97            self.get_supersampled_width(),
98            self.get_supersampled_height(),
99            |_, _| Rgba([0, 0, 0, 255]),
100        )
101    }
102}
103
104impl Renderer for ImageRenderer {
105    type Font = FontVec;
106
107    fn render_line(&mut self, start: DVec2, end: DVec2, thickness: f64, color: Srgba) {
108        let thickness = thickness * self.scale * self.supersampling as f64;
109
110        let offset = (thickness / 2.0).round();
111
112        let normal = DVec2::from_angle((end - start).to_angle() + PI / 2.0);
113
114        let mapped_start = self.map_dvec2(start);
115        let mapped_end = self.map_dvec2(end);
116
117        let p1 = mapped_start + normal * offset;
118        let p2 = mapped_start - normal * offset;
119        let p3 = mapped_end - normal * offset;
120        let p4 = mapped_end + normal * offset;
121
122        let points = vec![
123            Point::new(p1.x.round() as i32, p1.y.round() as i32),
124            Point::new(p2.x.round() as i32, p2.y.round() as i32),
125            Point::new(p3.x.round() as i32, p3.y.round() as i32),
126            Point::new(p4.x.round() as i32, p4.y.round() as i32),
127        ];
128
129        draw_polygon_mut(&mut self.image, &points, srgba_to_rgba8(color));
130    }
131
132    fn render_circle(&mut self, position: DVec2, radius: f64, color: Srgba) {
133        let position = self.map_dvec2(position).round().as_ivec2();
134        let radius = (radius * self.scale * self.supersampling as f64).round() as u32;
135
136        draw_filled_circle_mut(
137            &mut self.image,
138            position.into(),
139            radius as i32,
140            srgba_to_rgba8(color),
141        );
142    }
143
144    fn render_circle_lines(&mut self, position: DVec2, radius: f64, thickness: f64, color: Srgba) {
145        let position = self.map_dvec2(position).round().as_ivec2();
146        let radius = (radius * self.scale * self.supersampling as f64).round() as u32;
147        let thickness = (thickness * self.scale * self.supersampling as f64).round() as u32;
148
149        let mut circle_image = RgbaImage::new(2 * radius + 1, 2 * radius + 1);
150
151        draw_filled_circle_mut(
152            &mut circle_image,
153            (radius as i32, radius as i32),
154            radius as i32,
155            srgba_to_rgba8(color),
156        );
157
158        draw_filled_circle_mut(
159            &mut circle_image,
160            (radius as i32, radius as i32),
161            radius as i32 - thickness as i32,
162            Rgba([0, 0, 0, 0]),
163        );
164
165        overlay(
166            &mut self.image,
167            &circle_image,
168            (position.x - radius as i32) as i64,
169            (position.y - radius as i32) as i64,
170        );
171    }
172
173    fn render_arc(
174        &mut self,
175        position: DVec2,
176        radius: f64,
177        _rotation: f64,
178        _arc: f64,
179        thickness: f64,
180        color: Srgba,
181    ) {
182        self.render_circle_lines(position, radius, thickness, color); //TODO
183    }
184
185    fn render_text(
186        &mut self,
187        text: &str,
188        position: DVec2,
189        anchor: Anchor2D,
190        size: f64,
191        color: Srgba,
192        font: Self::Font,
193    ) {
194        let position = self.map_dvec2(position);
195        let size = size * self.scale * self.supersampling as f64;
196
197        let (text_width, _) = text_size(size as f32, &font, text);
198
199        let x = match anchor.get_horizontal() {
200            HorizontalAnchor::Left => position.x,
201            HorizontalAnchor::Center => position.x - text_width as f64 / 2.0,
202            HorizontalAnchor::Right => position.x - text_width as f64,
203        };
204
205        let vertical_anchor = anchor.get_vertical();
206
207        let y = match (vertical_anchor.get_context(), vertical_anchor.get_value()) {
208            (VerticalAnchorContext::Graphics, VerticalAnchorValue::Bottom) => {
209                position.y - size / 1.25
210            }
211            (VerticalAnchorContext::Math, VerticalAnchorValue::Bottom) => position.y,
212            (_, VerticalAnchorValue::Center) => position.y - size / 1.25 / 2.0,
213            (VerticalAnchorContext::Graphics, VerticalAnchorValue::Top) => position.y,
214            (VerticalAnchorContext::Math, VerticalAnchorValue::Top) => position.y - size / 1.25,
215        };
216
217        draw_text_mut(
218            &mut self.image,
219            srgba_to_rgba8(color),
220            x as i32,
221            y as i32,
222            size as f32,
223            &font,
224            text,
225        );
226    }
227
228    fn render_rectangle(
229        &mut self,
230        position: DVec2,
231        width: f64,
232        height: f64,
233        offset: DVec2,
234        rotation: f64,
235        color: Srgba,
236    ) {
237        let position = self.map_dvec2(position);
238        let width = width * self.scale * self.supersampling as f64;
239        let height = height * self.scale * self.supersampling as f64;
240
241        let absolute_offset_x = width * offset.x;
242        let absolute_offset_y = height * offset.y;
243
244        let absolute_offset = dvec2(absolute_offset_x, absolute_offset_y);
245
246        let p1 = -absolute_offset;
247        let p2 = p1 + DVec2::X * width;
248        let p3 = p2 + DVec2::Y * height;
249        let p4 = p1 + DVec2::Y * height;
250
251        let theta1 = p1.to_angle();
252        let theta2 = p2.to_angle();
253        let theta3 = p3.to_angle();
254        let theta4 = p4.to_angle();
255
256        let q1 = position + DVec2::from_angle(theta1 + rotation) * p1.length();
257        let q2 = position + DVec2::from_angle(theta2 + rotation) * p2.length();
258        let q3 = position + DVec2::from_angle(theta3 + rotation) * p3.length();
259        let q4 = position + DVec2::from_angle(theta4 + rotation) * p4.length();
260
261        draw_polygon_mut(
262            &mut self.image,
263            &[
264                Point::new(q1.x.round() as i32, q1.y.round() as i32),
265                Point::new(q2.x.round() as i32, q2.y.round() as i32),
266                Point::new(q3.x.round() as i32, q3.y.round() as i32),
267                Point::new(q4.x.round() as i32, q4.y.round() as i32),
268            ],
269            srgba_to_rgba8(color),
270        );
271    }
272
273    fn render_rectangle_lines(
274        &mut self,
275        position: DVec2,
276        width: f64,
277        height: f64,
278        offset: DVec2,
279        rotation: f64,
280        thickness: f64,
281        color: Srgba,
282    ) {
283        let position = self.map_dvec2(position);
284        let width = (width * self.scale * self.supersampling as f64).round();
285        let height = (height * self.scale * self.supersampling as f64).round();
286        let thickness = (thickness * self.scale * self.supersampling as f64).round() as u32;
287
288        let absolute_offset_x = width * offset.x;
289        let absolute_offset_y = height * offset.y;
290
291        let absolute_offset = dvec2(absolute_offset_x, absolute_offset_y);
292
293        let p1 = -absolute_offset;
294        let p2 = p1 + DVec2::X * width;
295        let p3 = p2 + DVec2::Y * height;
296        let p4 = p1 + DVec2::Y * height;
297
298        let theta1 = p1.to_angle();
299        let theta2 = p2.to_angle();
300        let theta3 = p3.to_angle();
301        let theta4 = p4.to_angle();
302
303        let q1 = position + DVec2::from_angle(theta1 + rotation) * p1.length();
304        let q2 = position + DVec2::from_angle(theta2 + rotation) * p2.length();
305        let q3 = position + DVec2::from_angle(theta3 + rotation) * p3.length();
306        let q4 = position + DVec2::from_angle(theta4 + rotation) * p4.length();
307
308        let r1 = q1.round().as_ivec2();
309        let r2 = q2.round().as_ivec2();
310        let r3 = q3.round().as_ivec2();
311        let r4 = q4.round().as_ivec2();
312
313        let min_x = r1.x.min(r2.x).min(r3.x).min(r4.x);
314        let max_x = r1.x.max(r2.x).max(r3.x).max(r4.x);
315
316        let min_y = r1.y.min(r2.y).min(r3.y).min(r4.y);
317        let max_y = r1.y.max(r2.y).max(r3.y).max(r4.y);
318
319        let renderer_width = (max_x - min_x + 1) as u32;
320        let renderer_height = (max_y - min_y + 1) as u32;
321
322        let mut rectangle_renderer = ImageRenderer::new(
323            renderer_width,
324            renderer_height,
325            self.scale,
326            self.scaling_target,
327            self.supersampling,
328        );
329
330        rectangle_renderer.render_rectangle(
331            dvec2(
332                (renderer_width as f64 / 2.0).floor(),
333                (renderer_height as f64 / 2.0).floor(),
334            ),
335            width,
336            height,
337            offset,
338            rotation,
339            color,
340        );
341
342        rectangle_renderer.render_rectangle(
343            dvec2(
344                (renderer_width as f64 / 2.0).floor(),
345                (renderer_height as f64 / 2.0).floor(),
346            ),
347            width - 2.0 * thickness as f64,
348            height - 2.0 * thickness as f64,
349            offset,
350            rotation,
351            Srgba::new(0.0, 0.0, 0.0, 0.0),
352        );
353
354        overlay(
355            &mut self.image,
356            &rectangle_renderer.image,
357            (position.x - renderer_width as f64 / 2.0).floor() as i64,
358            (position.y - renderer_height as f64 / 2.0).floor() as i64,
359        );
360    }
361
362    fn render_equilateral_triangle(
363        &mut self,
364        position: DVec2,
365        radius: f64,
366        rotation: f64,
367        color: Srgba,
368    ) {
369        let points = (0..3)
370            .map(|i| position + radius * DVec2::from_angle(i as f64 * 2.0 * PI / 3.0 + rotation))
371            .collect::<Vec<DVec2>>();
372
373        draw_polygon_mut(
374            &mut self.image,
375            &points
376                .iter()
377                .map(|point| Point::new(point.x.round() as i32, point.y.round() as i32))
378                .collect::<Vec<Point<i32>>>(),
379            srgba_to_rgba8(color),
380        );
381    }
382
383    fn render_equilateral_triangle_lines(
384        &mut self,
385        position: DVec2,
386        radius: f64,
387        rotation: f64,
388        thickness: f64,
389        color: Srgba,
390    ) {
391        let points = (0..3)
392            .map(|i| position + radius * DVec2::from_angle(i as f64 * 2.0 * PI / 3.0 + rotation))
393            .collect::<Vec<DVec2>>();
394
395        let integer_points = points
396            .iter()
397            .map(|point| point.floor().as_ivec2())
398            .collect::<Vec<IVec2>>();
399
400        let min_x = integer_points
401            .iter()
402            .map(|integer_point| integer_point.x)
403            .min()
404            .expect("triangles have more than 0 points");
405        let max_x = integer_points
406            .iter()
407            .map(|integer_point| integer_point.x)
408            .max()
409            .expect("triangles have more than 0 points");
410        let min_y = integer_points
411            .iter()
412            .map(|integer_point| integer_point.y)
413            .min()
414            .expect("triangles have more than 0 points");
415        let max_y = integer_points
416            .iter()
417            .map(|integer_point| integer_point.y)
418            .max()
419            .expect("triangles have more than 0 points");
420
421        let min_point = ivec2(min_x, min_y);
422
423        let renderer_width = (max_x - min_x + 1) as u32;
424        let renderer_height = (max_y - min_y + 1) as u32;
425
426        let mut triangle_renderer = ImageRenderer::new(
427            renderer_width,
428            renderer_height,
429            self.scale,
430            self.scaling_target,
431            self.supersampling,
432        );
433
434        triangle_renderer.render_equilateral_triangle(
435            (position - min_point.as_dvec2()).floor(),
436            radius,
437            rotation,
438            color,
439        );
440
441        triangle_renderer.render_equilateral_triangle(
442            (position - min_point.as_dvec2()).floor(),
443            radius - thickness,
444            rotation,
445            Srgba::new(0.0, 0.0, 0.0, 0.0),
446        );
447
448        overlay(
449            &mut self.image,
450            &triangle_renderer.image,
451            min_x as i64,
452            min_y as i64,
453        );
454    }
455}