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.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        draw_polygon_mut(&mut self.image, &points, srgba_to_rgba8(color));
157    }
158
159    fn render_circle(&mut self, position: DVec2, radius: f64, color: Srgba) {
160        let position = self.map_dvec2(position).round().as_ivec2();
161        let radius = (radius * self.scale * self.supersampling as f64).round() as u32;
162
163        draw_filled_circle_mut(
164            &mut self.image,
165            position.into(),
166            radius as i32,
167            srgba_to_rgba8(color),
168        );
169    }
170
171    fn render_circle_lines(&mut self, position: DVec2, radius: f64, thickness: f64, color: Srgba) {
172        let position = self.map_dvec2(position).round().as_ivec2();
173        let radius = (radius * self.scale * self.supersampling as f64).round();
174        let thickness = (thickness * self.scale * self.supersampling as f64).round();
175
176        let mut circle_renderer = ImageRenderer::new(
177            2 * radius as u32 + 1,
178            2 * radius as u32 + 1,
179            self.scale,
180            self.scaling_target,
181            self.supersampling,
182            self.font.clone(),
183        );
184
185        circle_renderer.render_circle(
186            dvec2(radius, radius),
187            radius,
188            color,
189        );
190
191        circle_renderer.render_circle(
192            dvec2(radius, radius),
193            radius - thickness,
194            Srgba::new(0.0, 0.0, 0.0, 0.0),
195        );
196
197        overlay(
198            &mut self.image,
199            &circle_renderer.render_image_onto(circle_renderer.transparent()),
200            (position.x - radius as i32) as i64,
201            (position.y - radius as i32) as i64,
202        );
203    }
204
205    fn render_arc(
206        &mut self,
207        position: DVec2,
208        radius: f64,
209        _rotation: f64,
210        _arc: f64,
211        thickness: f64,
212        color: Srgba,
213    ) {
214        self.render_circle_lines(position, radius, thickness, color); //TODO
215    }
216
217    fn render_text(
218        &mut self,
219        text: &str,
220        position: DVec2,
221        anchor: Anchor2D,
222        size: f64,
223        color: Srgba,
224    ) {
225        let position = self.map_dvec2(position);
226        let size = size * self.scale * self.supersampling as f64;
227
228        let (text_width, _) = text_size(size as f32, &self.font, text);
229
230        let x = match anchor.get_horizontal() {
231            HorizontalAnchor::Left => position.x,
232            HorizontalAnchor::Center => position.x - text_width as f64 / 2.0,
233            HorizontalAnchor::Right => position.x - text_width as f64,
234        };
235
236        let vertical_anchor = anchor.get_vertical();
237
238        let y = match (vertical_anchor.get_context(), vertical_anchor.get_value()) {
239            (VerticalAnchorContext::Graphics, VerticalAnchorValue::Bottom) => {
240                position.y - size / 1.25
241            }
242            (VerticalAnchorContext::Math, VerticalAnchorValue::Bottom) => position.y,
243            (_, VerticalAnchorValue::Center) => position.y - size / 1.25 / 2.0,
244            (VerticalAnchorContext::Graphics, VerticalAnchorValue::Top) => position.y,
245            (VerticalAnchorContext::Math, VerticalAnchorValue::Top) => position.y - size / 1.25,
246        };
247
248        draw_text_mut(
249            &mut self.image,
250            srgba_to_rgba8(color),
251            x as i32,
252            y as i32,
253            size as f32,
254            &self.font,
255            text,
256        );
257    }
258
259    fn render_rectangle(
260        &mut self,
261        position: DVec2,
262        width: f64,
263        height: f64,
264        offset: DVec2,
265        rotation: f64,
266        color: Srgba,
267    ) {
268        let width = (width - 1.0) * self.scale * self.supersampling as f64;
269        let height = (height - 1.0) * self.scale * self.supersampling as f64;
270
271        let calculated_offset = dvec2(
272            width * offset.x,
273            height * offset.y,
274        );
275
276        let position = self.map_dvec2(position) - calculated_offset;
277
278        let axis = dvec2(
279            position.x + calculated_offset.x, // 4.5
280            position.y + calculated_offset.y, // 4.5
281        );
282
283        let p1 = position; // 0.0,0.0
284        let p2 = p1 + DVec2::X * width; // 9.0,0.0
285        let p3 = p2 + DVec2::Y * height; // 9.0,9.0
286        let p4 = p3 - DVec2::X * width; // 0.0,9.0
287
288        let q1 = rotate_point_around(p1, axis, rotation); // 0.0,0.0
289        let q2 = rotate_point_around(p2, axis, rotation); // 9.0,0.0
290        let q3 = rotate_point_around(p3, axis, rotation); // 9.0,9.0
291        let q4 = rotate_point_around(p4, axis, rotation); // 0.0,9.0
292
293        let r1 = q1.round().as_ivec2(); // 0,0
294        let r2 = q2.round().as_ivec2(); // 9,0
295        let r3 = q3.round().as_ivec2(); // 9,9
296        let r4 = q4.round().as_ivec2(); // 0,9
297
298        let mut points = vec![
299            Point::new(r1.x, r1.y),
300            Point::new(r2.x, r2.y),
301            Point::new(r3.x, r3.y),
302            Point::new(r4.x, r4.y),
303        ];
304
305        while points.len() > 1 && points.first().is_some_and(|first_point| {
306            points
307                .last()
308                .is_some_and(|last_point| first_point == last_point)
309        }) {
310            points.remove(points.len() - 1);
311        }
312
313        if points.len() == 1 {
314            let point = points.first().unwrap();
315
316            if point.x >= 0 && point.y >= 0 {
317                self.image.put_pixel(point.x as u32, point.y as u32, srgba_to_rgba8(color));
318            }
319        } else {
320            draw_polygon_mut(&mut self.image, &points, srgba_to_rgba8(color));
321        }
322
323    }
324
325    fn render_rectangle_lines(
326        &mut self,
327        position: DVec2,
328        width: f64,
329        height: f64,
330        offset: DVec2,
331        rotation: f64,
332        thickness: f64,
333        color: Srgba,
334    ) {
335        let adjusted_position = self.map_dvec2(position);
336        let adjusted_width = (width - 1.0) * self.scale * self.supersampling as f64;
337        let adjusted_height = (height - 1.0) * self.scale * self.supersampling as f64;
338
339        let axis = dvec2(
340            adjusted_position.x + adjusted_width * offset.x, // 4.5
341            adjusted_position.y + adjusted_height * offset.y, // 4.5
342        );
343
344        let p1 = adjusted_position; // 0.0,0.0
345        let p2 = p1 + DVec2::X * adjusted_width; // 9.0,0.0
346        let p3 = p2 + DVec2::Y * adjusted_height; // 9.0,9.0
347        let p4 = p3 - DVec2::X * adjusted_width; // 0.0,9.0
348
349        let q1 = rotate_point_around(p1, axis, rotation); // 0.0,0.0
350        let q2 = rotate_point_around(p2, axis, rotation); // 9.0,0.0
351        let q3 = rotate_point_around(p3, axis, rotation); // 9.0,9.0
352        let q4 = rotate_point_around(p4, axis, rotation); // 0.0,9.0
353
354        let r1 = q1.round().as_ivec2(); // 0,0
355        let r2 = q2.round().as_ivec2(); // 9,0
356        let r3 = q3.round().as_ivec2(); // 9,9
357        let r4 = q4.round().as_ivec2(); // 0,9
358
359        let min_x = r1.x.min(r2.x).min(r3.x).min(r4.x);
360        let max_x = r1.x.max(r2.x).max(r3.x).max(r4.x);
361
362        let min_y = r1.y.min(r2.y).min(r3.y).min(r4.y);
363        let max_y = r1.y.max(r2.y).max(r3.y).max(r4.y);
364
365        let renderer_width = max_x - min_x + 1;
366        let renderer_height = max_y - min_y + 1;
367
368        let mut rectangle_renderer = ImageRenderer::new(
369            renderer_width as u32,
370            renderer_height as u32,
371            self.scale,
372            self.scaling_target,
373            self.supersampling,
374            self.font.clone(),
375        );
376
377        rectangle_renderer.render_rectangle(
378            dvec2(
379                (renderer_width as f64 / 2.0).floor(),
380                (renderer_height as f64 / 2.0).floor(),
381            ),
382            width,
383            height,
384            DVec2::splat(0.5),
385            rotation,
386            color,
387        );
388
389        rectangle_renderer.render_rectangle(
390            dvec2(
391                (renderer_width as f64 / 2.0).floor(),
392                (renderer_height as f64 / 2.0).floor(),
393            ),
394            width - 2.0 * thickness,
395            height - 2.0 * thickness,
396            DVec2::splat(0.5),
397            rotation,
398            Srgba::new(0.0, 0.0, 0.0, 0.0),
399        );
400
401        overlay(
402            &mut self.image,
403            &rectangle_renderer.render_image_onto(rectangle_renderer.transparent()),
404            (min_x as f64 - (adjusted_width * offset.x).floor()) as i64,
405            (min_y as f64 - (adjusted_height * offset.y).floor()) as i64,
406        );
407    }
408
409    fn render_equilateral_triangle(
410        &mut self,
411        position: DVec2,
412        radius: f64,
413        rotation: f64,
414        color: Srgba,
415    ) {
416        let points = (0..3)
417            .map(|i| position + radius * DVec2::from_angle(i as f64 * 2.0 * PI / 3.0 + rotation))
418            .collect::<Vec<DVec2>>();
419
420        let mut points = points
421            .into_iter()
422            .map(|point| Point::new(point.x.round() as i32, point.y.round() as i32))
423            .collect::<Vec<Point<i32>>>();
424
425        while points.first().is_some_and(|first_point| {
426            points
427                .last()
428                .is_some_and(|last_point| first_point == last_point)
429        }) {
430            points.remove(points.len() - 1);
431        }
432
433        draw_polygon_mut(&mut self.image, &points, srgba_to_rgba8(color));
434    }
435
436    fn render_equilateral_triangle_lines(
437        &mut self,
438        position: DVec2,
439        radius: f64,
440        rotation: f64,
441        thickness: f64,
442        color: Srgba,
443    ) {
444        let points = (0..3)
445            .map(|i| position + radius * DVec2::from_angle(i as f64 * 2.0 * PI / 3.0 + rotation))
446            .collect::<Vec<DVec2>>();
447
448        let integer_points = points
449            .iter()
450            .map(|point| point.floor().as_ivec2())
451            .collect::<Vec<IVec2>>();
452
453        let min_x = integer_points
454            .iter()
455            .map(|integer_point| integer_point.x)
456            .min()
457            .expect("triangles have more than 0 points");
458        let max_x = integer_points
459            .iter()
460            .map(|integer_point| integer_point.x)
461            .max()
462            .expect("triangles have more than 0 points");
463        let min_y = integer_points
464            .iter()
465            .map(|integer_point| integer_point.y)
466            .min()
467            .expect("triangles have more than 0 points");
468        let max_y = integer_points
469            .iter()
470            .map(|integer_point| integer_point.y)
471            .max()
472            .expect("triangles have more than 0 points");
473
474        let min_point = ivec2(min_x, min_y);
475
476        let renderer_width = (max_x - min_x + 1) as u32;
477        let renderer_height = (max_y - min_y + 1) as u32;
478
479        let mut triangle_renderer = ImageRenderer::new(
480            renderer_width,
481            renderer_height,
482            self.scale,
483            self.scaling_target,
484            self.supersampling,
485            self.font.clone(),
486        );
487
488        triangle_renderer.render_equilateral_triangle(
489            (position - min_point.as_dvec2()).floor(),
490            radius,
491            rotation,
492            color,
493        );
494
495        triangle_renderer.render_equilateral_triangle(
496            (position - min_point.as_dvec2()).floor(),
497            radius - thickness,
498            rotation,
499            Srgba::new(0.0, 0.0, 0.0, 0.0),
500        );
501
502        overlay(
503            &mut self.image,
504            &triangle_renderer.render_image_onto(triangle_renderer.transparent()),
505            min_x as i64,
506            min_y as i64,
507        );
508    }
509}
510
511fn rotate_point_around(point: DVec2, axis: DVec2, theta: f64) -> DVec2 {
512    if theta == 0.0 {
513        return point;
514    }
515
516    let relative = point - axis;
517    let relative_theta = relative.to_angle();
518    let new_relative_theta = relative_theta + theta;
519    let new_relative = DVec2::from_angle(new_relative_theta) * relative.length();
520    new_relative + axis
521}