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