render_agnostic/renderers/
image.rs

1use std::f64::consts::PI;
2
3use ab_glyph::FontArc;
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::{num::Round, 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)]
27pub struct ImageRenderer {
28    width: u32,
29    height: u32,
30    image: RgbaImage,
31    scale: f64,
32    scaling_target: DVec2,
33    supersampling: u32,
34    font: FontArc,
35}
36
37impl ImageRenderer {
38    pub fn new(
39        width: u32,
40        height: u32,
41        scale: f64,
42        scaling_target: DVec2,
43        supersampling: u32,
44        font: FontArc,
45    ) -> Self {
46        Self {
47            width,
48            height,
49            image: RgbaImage::new(width * supersampling, height * supersampling),
50            scale,
51            scaling_target,
52            supersampling,
53            font,
54        }
55    }
56
57    pub fn get_font(&self) -> &FontArc {
58        &self.font
59    }
60
61    pub fn set_font(&mut self, font: FontArc) {
62        self.font = font;
63    }
64
65    fn get_supersampled_width(&self) -> u32 {
66        self.width * self.supersampling
67    }
68
69    fn get_supersampled_height(&self) -> u32 {
70        self.height * self.supersampling
71    }
72
73    fn map_x(&self, x: f64) -> f64 {
74        let target_x = self.get_supersampled_width() as f64 * self.scaling_target.x;
75        (x * self.supersampling as f64 - target_x) * self.scale + target_x
76    }
77
78    fn map_y(&self, y: f64) -> f64 {
79        let target_y = self.get_supersampled_height() as f64 * self.scaling_target.y;
80        (y * self.supersampling as f64 - target_y) * self.scale + target_y
81    }
82
83    fn map_dvec2(&self, v: DVec2) -> DVec2 {
84        dvec2(self.map_x(v.x), self.map_y(v.y))
85    }
86
87    pub fn reset(&mut self) {
88        self.image = self.transparent();
89    }
90
91    pub fn get_image(&self) -> &RgbaImage {
92        &self.image
93    }
94
95    pub fn render_image_onto(&self, mut image: RgbaImage) -> RgbaImage {
96        overlay(&mut image, &self.image, 0, 0);
97
98        resize(&image, self.width, self.height, FilterType::Lanczos3)
99    }
100
101    pub fn transparent(&self) -> RgbaImage {
102        RgbaImage::new(
103            self.get_supersampled_width(),
104            self.get_supersampled_height(),
105        )
106    }
107
108    pub fn black(&self) -> RgbaImage {
109        RgbaImage::from_par_fn(
110            self.get_supersampled_width(),
111            self.get_supersampled_height(),
112            |_, _| Rgba([0, 0, 0, 255]),
113        )
114    }
115
116    fn red(&self) -> RgbaImage {
117        RgbaImage::from_par_fn(
118            self.get_supersampled_width(),
119            self.get_supersampled_height(),
120            |_, _| Rgba([255, 0, 0, 255]),
121        )
122    }
123}
124
125impl Renderer for ImageRenderer {
126    fn render_line(&mut self, start: DVec2, end: DVec2, thickness: f64, color: Srgba) {
127        let thickness = thickness * self.scale * self.supersampling as f64;
128
129        let offset = (thickness / 2.0).round();
130
131        let normal = DVec2::from_angle((end - start).to_angle() + PI / 2.0);
132
133        let mapped_start = self.map_dvec2(start);
134        let mapped_end = self.map_dvec2(end);
135
136        let p1 = mapped_start + normal * offset;
137        let p2 = mapped_start - normal * offset;
138        let p3 = mapped_end - normal * offset;
139        let p4 = mapped_end + normal * offset;
140
141        let mut points = vec![
142            Point::new(p1.x.round() as i32, p1.y.round() as i32),
143            Point::new(p2.x.round() as i32, p2.y.round() as i32),
144            Point::new(p3.x.round() as i32, p3.y.round() as i32),
145            Point::new(p4.x.round() as i32, p4.y.round() as i32),
146        ];
147
148        while points.len() > 1 && points.first().is_some_and(|first_point| {
149            points
150                .last()
151                .is_some_and(|last_point| first_point == last_point)
152        }) {
153            points.remove(points.len() - 1);
154        }
155
156        if points.len() == 1 {
157            let point = points.first().unwrap();
158
159            if point.x >= 0 && point.y >= 0 && point.x < self.image.width() as i32 && point.y < self.image.height() as i32 {
160                self.image.put_pixel(point.x as u32, point.y as u32, srgba_to_rgba8(color));
161            }
162        } else {
163            draw_polygon_mut(&mut self.image, &points, srgba_to_rgba8(color));
164        }
165
166    }
167
168    fn render_circle(&mut self, position: DVec2, radius: f64, color: Srgba) {
169        let position = self.map_dvec2(position).round().as_ivec2();
170        let radius = (radius * self.scale * self.supersampling as f64).round() as u32;
171
172        draw_filled_circle_mut(
173            &mut self.image,
174            position.into(),
175            radius as i32,
176            srgba_to_rgba8(color),
177        );
178    }
179
180    fn render_circle_lines(&mut self, position: DVec2, radius: f64, thickness: f64, color: Srgba) {
181        let position = self.map_dvec2(position).round().as_ivec2();
182        let radius = (radius * self.scale * self.supersampling as f64).round();
183        let thickness = (thickness * self.scale * self.supersampling as f64).round();
184
185        let mut circle_renderer = ImageRenderer::new(
186            2 * radius as u32 + 1,
187            2 * radius as u32 + 1,
188            self.scale,
189            self.scaling_target,
190            self.supersampling,
191            self.font.clone(),
192        );
193
194        circle_renderer.render_circle(
195            dvec2(radius, radius),
196            radius,
197            color,
198        );
199
200        circle_renderer.render_circle(
201            dvec2(radius, radius),
202            radius - thickness,
203            Srgba::new(0.0, 0.0, 0.0, 0.0),
204        );
205
206        overlay(
207            &mut self.image,
208            &circle_renderer.render_image_onto(circle_renderer.transparent()),
209            (position.x - radius as i32) as i64,
210            (position.y - radius as i32) as i64,
211        );
212    }
213
214    fn render_arc(
215        &mut self,
216        position: DVec2,
217        radius: f64,
218        _rotation: f64,
219        _arc: f64,
220        thickness: f64,
221        color: Srgba,
222    ) {
223        self.render_circle_lines(position, radius, thickness, color); //TODO
224    }
225
226    fn render_text(
227        &mut self,
228        text: &str,
229        position: DVec2,
230        anchor: Anchor2D,
231        size: f64,
232        color: Srgba,
233    ) {
234        let position = self.map_dvec2(position);
235        let size = size * self.scale * self.supersampling as f64;
236
237        let (text_width, _) = text_size(size as f32, &self.font, text);
238
239        let x = match anchor.get_horizontal() {
240            HorizontalAnchor::Left => position.x,
241            HorizontalAnchor::Center => position.x - text_width as f64 / 2.0,
242            HorizontalAnchor::Right => position.x - text_width as f64,
243        };
244
245        let vertical_anchor = anchor.get_vertical();
246
247        let y = match (vertical_anchor.get_context(), vertical_anchor.get_value()) {
248            (VerticalAnchorContext::Graphics, VerticalAnchorValue::Bottom) => {
249                position.y - size / 1.25
250            }
251            (VerticalAnchorContext::Math, VerticalAnchorValue::Bottom) => position.y,
252            (_, VerticalAnchorValue::Center) => position.y - size / 1.25 / 2.0,
253            (VerticalAnchorContext::Graphics, VerticalAnchorValue::Top) => position.y,
254            (VerticalAnchorContext::Math, VerticalAnchorValue::Top) => position.y - size / 1.25,
255        };
256
257        draw_text_mut(
258            &mut self.image,
259            srgba_to_rgba8(color),
260            x as i32,
261            y as i32,
262            size as f32,
263            &self.font,
264            text,
265        );
266    }
267
268    fn render_rectangle(
269        &mut self,
270        position: DVec2,
271        width: f64,
272        height: f64,
273        offset: DVec2,
274        rotation: f64,
275        color: Srgba,
276    ) {
277        let width = (width - 1.0) * self.scale * self.supersampling as f64;
278        let height = (height - 1.0) * self.scale * self.supersampling as f64;
279
280        let calculated_offset = dvec2(
281            width * offset.x,
282            height * offset.y,
283        );
284
285        let position = self.map_dvec2(position) - calculated_offset;
286
287        let axis = dvec2(
288            position.x + calculated_offset.x, // 4.5
289            position.y + calculated_offset.y, // 4.5
290        );
291
292        let p1 = position; // 0.0,0.0
293        let p2 = p1 + DVec2::X * width; // 9.0,0.0
294        let p3 = p2 + DVec2::Y * height; // 9.0,9.0
295        let p4 = p3 - DVec2::X * width; // 0.0,9.0
296
297        let q1 = rotate_point_around(p1, axis, rotation); // 0.0,0.0
298        let q2 = rotate_point_around(p2, axis, rotation); // 9.0,0.0
299        let q3 = rotate_point_around(p3, axis, rotation); // 9.0,9.0
300        let q4 = rotate_point_around(p4, axis, rotation); // 0.0,9.0
301
302        let r1 = q1.round().as_ivec2(); // 0,0
303        let r2 = q2.round().as_ivec2(); // 9,0
304        let r3 = q3.round().as_ivec2(); // 9,9
305        let r4 = q4.round().as_ivec2(); // 0,9
306
307        let mut points = vec![
308            Point::new(r1.x, r1.y),
309            Point::new(r2.x, r2.y),
310            Point::new(r3.x, r3.y),
311            Point::new(r4.x, r4.y),
312        ];
313
314        while points.len() > 1 && points.first().is_some_and(|first_point| {
315            points
316                .last()
317                .is_some_and(|last_point| first_point == last_point)
318        }) {
319            points.remove(points.len() - 1);
320        }
321
322        if points.len() == 1 {
323            let point = points.first().unwrap();
324
325            if point.x >= 0 && point.y >= 0 && point.x < self.image.width() as i32 && point.y < self.image.height() as i32 {
326                self.image.put_pixel(point.x as u32, point.y as u32, srgba_to_rgba8(color));
327            }
328        } else {
329            draw_polygon_mut(&mut self.image, &points, srgba_to_rgba8(color));
330        }
331
332    }
333
334    fn render_rectangle_lines(
335        &mut self,
336        position: DVec2,
337        width: f64,
338        height: f64,
339        offset: DVec2,
340        rotation: f64,
341        thickness: f64,
342        color: Srgba,
343    ) {
344        let adjusted_position = self.map_dvec2(position);
345        let adjusted_width = (width - 1.0) * self.scale * self.supersampling as f64;
346        let adjusted_height = (height - 1.0) * self.scale * self.supersampling as f64;
347
348        let axis = dvec2(
349            adjusted_position.x + adjusted_width * offset.x, // 4.5
350            adjusted_position.y + adjusted_height * offset.y, // 4.5
351        );
352
353        let p1 = adjusted_position; // 0.0,0.0
354        let p2 = p1 + DVec2::X * adjusted_width; // 9.0,0.0
355        let p3 = p2 + DVec2::Y * adjusted_height; // 9.0,9.0
356        let p4 = p3 - DVec2::X * adjusted_width; // 0.0,9.0
357
358        let q1 = rotate_point_around(p1, axis, rotation); // 0.0,0.0
359        let q2 = rotate_point_around(p2, axis, rotation); // 9.0,0.0
360        let q3 = rotate_point_around(p3, axis, rotation); // 9.0,9.0
361        let q4 = rotate_point_around(p4, axis, rotation); // 0.0,9.0
362
363        let r1 = q1.round().as_ivec2(); // 0,0
364        let r2 = q2.round().as_ivec2(); // 9,0
365        let r3 = q3.round().as_ivec2(); // 9,9
366        let r4 = q4.round().as_ivec2(); // 0,9
367
368        let min_x = r1.x.min(r2.x).min(r3.x).min(r4.x);
369        let max_x = r1.x.max(r2.x).max(r3.x).max(r4.x);
370
371        let min_y = r1.y.min(r2.y).min(r3.y).min(r4.y);
372        let max_y = r1.y.max(r2.y).max(r3.y).max(r4.y);
373
374        let renderer_width = max_x - min_x + 1;
375        let renderer_height = max_y - min_y + 1;
376
377        let mut rectangle_renderer = ImageRenderer::new(
378            renderer_width as u32,
379            renderer_height as u32,
380            self.scale,
381            self.scaling_target,
382            self.supersampling,
383            self.font.clone(),
384        );
385
386        rectangle_renderer.render_rectangle(
387            dvec2(
388                (renderer_width as f64 / 2.0).floor(),
389                (renderer_height as f64 / 2.0).floor(),
390            ),
391            width,
392            height,
393            DVec2::splat(0.5),
394            rotation,
395            color,
396        );
397
398        rectangle_renderer.render_rectangle(
399            dvec2(
400                (renderer_width as f64 / 2.0).floor(),
401                (renderer_height as f64 / 2.0).floor(),
402            ),
403            width - 2.0 * thickness,
404            height - 2.0 * thickness,
405            DVec2::splat(0.5),
406            rotation,
407            Srgba::new(0.0, 0.0, 0.0, 0.0),
408        );
409
410        overlay(
411            &mut self.image,
412            &rectangle_renderer.render_image_onto(rectangle_renderer.transparent()),
413            (min_x as f64 - (adjusted_width * offset.x).floor()) as i64,
414            (min_y as f64 - (adjusted_height * offset.y).floor()) as i64,
415        );
416    }
417
418    fn render_equilateral_triangle(
419        &mut self,
420        position: DVec2,
421        radius: f64,
422        rotation: f64,
423        color: Srgba,
424    ) {
425        let points = (0..3)
426            .map(|i| position + radius * DVec2::from_angle(i as f64 * 2.0 * PI / 3.0 + rotation))
427            .collect::<Vec<DVec2>>();
428
429        let mut points = points
430            .into_iter()
431            .map(|point| Point::new(point.x.round() as i32, point.y.round() as i32))
432            .collect::<Vec<Point<i32>>>();
433
434        while points.len() > 1 && points.first().is_some_and(|first_point| {
435            points
436                .last()
437                .is_some_and(|last_point| first_point == last_point)
438        }) {
439            points.remove(points.len() - 1);
440        }
441
442        if points.len() == 1 {
443            let point = points.first().unwrap();
444
445            if point.x >= 0 && point.y >= 0 && point.x < self.image.width() as i32 && point.y < self.image.height() as i32 {
446                self.image.put_pixel(point.x as u32, point.y as u32, srgba_to_rgba8(color));
447            }
448        } else {
449            draw_polygon_mut(&mut self.image, &points, srgba_to_rgba8(color));
450        }
451    }
452
453    fn render_equilateral_triangle_lines(
454        &mut self,
455        position: DVec2,
456        radius: f64,
457        rotation: f64,
458        thickness: f64,
459        color: Srgba,
460    ) {
461        let points = (0..3)
462            .map(|i| position + radius * DVec2::from_angle(i as f64 * 2.0 * PI / 3.0 + rotation))
463            .collect::<Vec<DVec2>>();
464
465        let integer_points = points
466            .iter()
467            .map(|point| point.floor().as_ivec2())
468            .collect::<Vec<IVec2>>();
469
470        let min_x = integer_points
471            .iter()
472            .map(|integer_point| integer_point.x)
473            .min()
474            .expect("triangles have more than 0 points");
475        let max_x = integer_points
476            .iter()
477            .map(|integer_point| integer_point.x)
478            .max()
479            .expect("triangles have more than 0 points");
480        let min_y = integer_points
481            .iter()
482            .map(|integer_point| integer_point.y)
483            .min()
484            .expect("triangles have more than 0 points");
485        let max_y = integer_points
486            .iter()
487            .map(|integer_point| integer_point.y)
488            .max()
489            .expect("triangles have more than 0 points");
490
491        let min_point = ivec2(min_x, min_y);
492
493        let renderer_width = (max_x - min_x + 1) as u32;
494        let renderer_height = (max_y - min_y + 1) as u32;
495
496        let mut triangle_renderer = ImageRenderer::new(
497            renderer_width,
498            renderer_height,
499            self.scale,
500            self.scaling_target,
501            self.supersampling,
502            self.font.clone(),
503        );
504
505        triangle_renderer.render_equilateral_triangle(
506            (position - min_point.as_dvec2()).floor(),
507            radius,
508            rotation,
509            color,
510        );
511
512        triangle_renderer.render_equilateral_triangle(
513            (position - min_point.as_dvec2()).floor(),
514            radius - thickness,
515            rotation,
516            Srgba::new(0.0, 0.0, 0.0, 0.0),
517        );
518
519        overlay(
520            &mut self.image,
521            &triangle_renderer.render_image_onto(triangle_renderer.transparent()),
522            min_x as i64,
523            min_y as i64,
524        );
525    }
526}
527
528fn rotate_point_around(point: DVec2, axis: DVec2, theta: f64) -> DVec2 {
529    if theta == 0.0 {
530        return point;
531    }
532
533    let relative = point - axis;
534    let relative_theta = relative.to_angle();
535    let new_relative_theta = relative_theta + theta;
536    let new_relative = DVec2::from_angle(new_relative_theta) * relative.length();
537    new_relative + axis
538}