render_agnostic/renderers/
image.rs

1use std::{
2    f64::consts::{FRAC_PI_2, PI},
3    iter::once,
4};
5
6use ab_glyph::FontArc;
7use anchor2d::{Anchor2D, HorizontalAnchor, VerticalAnchorContext, VerticalAnchorValue};
8use glam::{DVec2, IVec2, dvec2, ivec2};
9use image::{
10    Rgba, RgbaImage,
11    imageops::{FilterType, overlay, resize},
12};
13use imageproc::{
14    drawing::{
15        draw_filled_circle_mut, draw_filled_rect_mut, draw_polygon_mut, draw_text_mut, text_size,
16    },
17    point::Point,
18    rect::Rect,
19};
20use itertools::Itertools;
21use palette::Srgba;
22
23use crate::Renderer;
24
25fn srgba_to_rgba8(color: Srgba) -> Rgba<u8> {
26    let red = (color.red * 255.0).round().clamp(0.0, 255.0) as u8;
27    let green = (color.green * 255.0).round().clamp(0.0, 255.0) as u8;
28    let blue = (color.blue * 255.0).round().clamp(0.0, 255.0) as u8;
29    let alpha = (color.alpha * 255.0).round().clamp(0.0, 255.0) as u8;
30    Rgba([red, green, blue, alpha])
31}
32
33#[derive(Clone)]
34pub struct ImageRenderer {
35    virtual_width: u32,
36    virtual_height: u32,
37    image: RgbaImage,
38    scale: f64,
39    scaling_target: DVec2,
40    supersampling: u32,
41    font: FontArc,
42}
43
44impl ImageRenderer {
45    pub fn new(
46        width: u32,
47        height: u32,
48        scale: f64,
49        scaling_target: DVec2,
50        supersampling: u32,
51        font: FontArc,
52    ) -> Self {
53        Self {
54            virtual_width: width,
55            virtual_height: height,
56            image: RgbaImage::new(width * supersampling, height * supersampling),
57            scale,
58            scaling_target,
59            supersampling,
60            font,
61        }
62    }
63
64    pub fn get_font(&self) -> &FontArc {
65        &self.font
66    }
67
68    pub fn set_font(&mut self, font: FontArc) {
69        self.font = font;
70    }
71
72    fn get_supersampled_width(&self) -> u32 {
73        self.virtual_width * self.supersampling
74    }
75
76    fn get_supersampled_height(&self) -> u32 {
77        self.virtual_height * self.supersampling
78    }
79
80    fn map_value(&self, value: f64) -> f64 {
81        value * self.scale * self.supersampling as f64
82    }
83
84    fn map_x(&self, x: f64) -> f64 {
85        let target_x = self.get_supersampled_width() as f64 * self.scaling_target.x;
86        (x * self.supersampling as f64 - target_x) * self.scale + target_x
87    }
88
89    fn map_y(&self, y: f64) -> f64 {
90        let target_y = self.get_supersampled_height() as f64 * self.scaling_target.y;
91        (y * self.supersampling as f64 - target_y) * self.scale + target_y
92    }
93
94    fn map_dvec2(&self, v: DVec2) -> DVec2 {
95        dvec2(self.map_x(v.x), self.map_y(v.y))
96    }
97
98    pub fn reset(&mut self) {
99        self.image = self.transparent();
100    }
101
102    pub fn get_image(&self) -> &RgbaImage {
103        &self.image
104    }
105
106    pub fn render_image_onto(&self, mut image: RgbaImage) -> RgbaImage {
107        overlay(&mut image, &self.image, 0, 0);
108
109        resize(
110            &image,
111            self.virtual_width,
112            self.virtual_height,
113            FilterType::Lanczos3,
114        )
115    }
116
117    pub fn transparent(&self) -> RgbaImage {
118        RgbaImage::new(
119            self.get_supersampled_width(),
120            self.get_supersampled_height(),
121        )
122    }
123
124    pub fn black(&self) -> RgbaImage {
125        RgbaImage::from_pixel(
126            self.get_supersampled_width(),
127            self.get_supersampled_height(),
128            Rgba([0, 0, 0, 255]),
129        )
130    }
131
132    fn get_base_points(&self, position: DVec2, width: f64, height: f64) -> Vec<DVec2> {
133        vec![
134            position,
135            position + DVec2::X * width,
136            position + DVec2::X * width + DVec2::Y * height,
137            position + DVec2::Y * height,
138        ]
139    }
140
141    fn get_offset_vec(&self, width: f64, height: f64, offset: DVec2) -> DVec2 {
142        let offset_width = width * offset.x;
143        let offset_height = height * offset.y;
144
145        dvec2(offset_width, offset_height)
146    }
147
148    fn get_offset_points(&self, points: &[DVec2], offset_vec: DVec2) -> Vec<DVec2> {
149        points
150            .iter()
151            .copied()
152            .map(|base_point| base_point - offset_vec)
153            .collect::<Vec<DVec2>>()
154    }
155
156    fn get_rotated_points(&self, points: &[DVec2], axis: DVec2, rotation: f64) -> Vec<DVec2> {
157        points
158            .iter()
159            .copied()
160            .map(|point| rotate_point_around(point, axis, rotation))
161            .collect::<Vec<DVec2>>()
162    }
163
164    fn get_unique_integer_points(&self, points: &[DVec2]) -> Vec<IVec2> {
165        points
166            .iter()
167            .map(|point| point.round().as_ivec2())
168            .unique()
169            .collect::<Vec<IVec2>>()
170    }
171}
172
173impl Renderer for ImageRenderer {
174    fn render_point(&mut self, position: DVec2, color: Srgba) {
175        let position = self.map_dvec2(position);
176        let width = self.map_value(1.0);
177        let height = self.map_value(1.0);
178
179        let integer_position = position.round().as_ivec2();
180
181        let integer_width = width.round() as u32;
182        let integer_height = height.round() as u32;
183
184        if integer_width > 0 && integer_height > 0 {
185            draw_filled_rect_mut(
186                &mut self.image,
187                Rect::at(integer_position.x, integer_position.y)
188                    .of_size(integer_width, integer_height),
189                srgba_to_rgba8(color),
190            );
191        }
192    }
193
194    fn render_line(&mut self, start: DVec2, end: DVec2, thickness: f64, color: Srgba) {
195        let start = self.map_dvec2(start);
196        let end = self.map_dvec2(end);
197
198        let thickness = self.map_value(thickness);
199        let offset = thickness / 2.0;
200        let normal = DVec2::from_angle((end - start).to_angle() + FRAC_PI_2);
201
202        let points = vec![
203            start + normal * offset,
204            start - normal * offset,
205            end - normal * offset,
206            end + normal * offset,
207        ];
208
209        let integer_points = self
210            .get_unique_integer_points(&points)
211            .iter()
212            .map(|integer_point| Point::new(integer_point.x, integer_point.y))
213            .collect::<Vec<Point<i32>>>();
214
215        if integer_points.len() == 1 {
216            let integer_point = integer_points.first().unwrap();
217
218            self.render_point(dvec2(integer_point.x as f64, integer_point.y as f64), color);
219        } else {
220            draw_polygon_mut(&mut self.image, &integer_points, srgba_to_rgba8(color));
221        }
222    }
223
224    fn render_circle(&mut self, position: DVec2, radius: f64, color: Srgba) {
225        let position = self.map_dvec2(position).round().as_ivec2();
226        let radius = self.map_value(radius).round() as u32;
227
228        draw_filled_circle_mut(
229            &mut self.image,
230            position.into(),
231            radius as i32,
232            srgba_to_rgba8(color),
233        );
234    }
235
236    fn render_circle_lines(&mut self, position: DVec2, radius: f64, thickness: f64, color: Srgba) {
237        let position = self.map_dvec2(position).round().as_ivec2();
238        let radius = self.map_value(radius).round();
239        let thickness = self.map_value(thickness).round();
240
241        let mut circle_renderer = ImageRenderer::new(
242            2 * radius as u32 + 1,
243            2 * radius as u32 + 1,
244            self.scale,
245            self.scaling_target,
246            self.supersampling,
247            self.font.clone(),
248        );
249
250        circle_renderer.render_circle(dvec2(radius, radius), radius, color);
251
252        circle_renderer.render_circle(
253            dvec2(radius, radius),
254            radius - thickness,
255            Srgba::new(0.0, 0.0, 0.0, 0.0),
256        );
257
258        overlay(
259            &mut self.image,
260            &circle_renderer.render_image_onto(circle_renderer.transparent()),
261            (position.x - radius as i32) as i64,
262            (position.y - radius as i32) as i64,
263        );
264    }
265
266    fn render_arc(&mut self, position: DVec2, radius: f64, rotation: f64, arc: f64, color: Srgba) {
267        if arc == 0.0 {
268            return;
269        }
270
271        let position = self.map_dvec2(position);
272        let radius = self.map_value(radius);
273
274        let points =
275            once(position)
276                .chain((0..32).map(|i| {
277                    position + radius * DVec2::from_angle(rotation + arc * i as f64 / 31.0)
278                }))
279                .collect::<Vec<DVec2>>();
280
281        let integer_points = self
282            .get_unique_integer_points(&points)
283            .iter()
284            .map(|integer_point| Point::new(integer_point.x, integer_point.y))
285            .collect::<Vec<Point<i32>>>();
286
287        if integer_points.len() == 1 {
288            let integer_point = integer_points.first().unwrap();
289
290            self.render_point(dvec2(integer_point.x as f64, integer_point.y as f64), color);
291        } else {
292            draw_polygon_mut(&mut self.image, &integer_points, srgba_to_rgba8(color));
293        }
294    }
295
296    fn render_arc_lines(
297        &mut self,
298        position: DVec2,
299        radius: f64,
300        rotation: f64,
301        arc: f64,
302        thickness: f64,
303        color: Srgba,
304    ) {
305        if arc == 0.0 {
306            return;
307        }
308
309        let position = self.map_dvec2(position).round().as_ivec2();
310        let radius = self.map_value(radius).round();
311        let thickness = self.map_value(thickness).round();
312
313        let mut circle_renderer = ImageRenderer::new(
314            2 * radius as u32 + 1,
315            2 * radius as u32 + 1,
316            self.scale,
317            self.scaling_target,
318            self.supersampling,
319            self.font.clone(),
320        );
321
322        circle_renderer.render_arc(dvec2(radius, radius), radius, rotation, arc, color);
323
324        circle_renderer.render_circle(
325            dvec2(radius, radius),
326            radius - thickness,
327            Srgba::new(0.0, 0.0, 0.0, 0.0),
328        );
329
330        overlay(
331            &mut self.image,
332            &circle_renderer.render_image_onto(circle_renderer.transparent()),
333            (position.x - radius as i32) as i64,
334            (position.y - radius as i32) as i64,
335        );
336    }
337
338    fn render_text(
339        &mut self,
340        text: &str,
341        position: DVec2,
342        anchor: Anchor2D,
343        size: f64,
344        color: Srgba,
345    ) {
346        let position = self.map_dvec2(position);
347        let size = self.map_value(size);
348
349        let (text_width, _) = text_size(size as f32, &self.font, text);
350
351        let x = match anchor.get_horizontal() {
352            HorizontalAnchor::Left => position.x,
353            HorizontalAnchor::Center => position.x - text_width as f64 / 2.0,
354            HorizontalAnchor::Right => position.x - text_width as f64,
355        };
356
357        let vertical_anchor = anchor.get_vertical();
358
359        let y = match (vertical_anchor.get_context(), vertical_anchor.get_value()) {
360            (VerticalAnchorContext::Graphics, VerticalAnchorValue::Bottom) => {
361                position.y - size / 1.25
362            }
363            (VerticalAnchorContext::Math, VerticalAnchorValue::Bottom) => position.y,
364            (_, VerticalAnchorValue::Center) => position.y - size / 1.25 / 2.0,
365            (VerticalAnchorContext::Graphics, VerticalAnchorValue::Top) => position.y,
366            (VerticalAnchorContext::Math, VerticalAnchorValue::Top) => position.y - size / 1.25,
367        };
368
369        draw_text_mut(
370            &mut self.image,
371            srgba_to_rgba8(color),
372            x as i32,
373            y as i32,
374            size as f32,
375            &self.font,
376            text,
377        );
378    }
379
380    fn render_text_outline(
381        &mut self,
382        text: &str,
383        position: DVec2,
384        anchor: Anchor2D,
385        size: f64,
386        outline_thickness: f64,
387        color: Srgba,
388        outline_color: Srgba,
389    ) {
390        let position = self.map_dvec2(position);
391        let size = self.map_value(size);
392        let outline_thickness = self.map_value(outline_thickness);
393
394        let (text_width, _) = text_size(size as f32, &self.font, text);
395
396        let x = match anchor.get_horizontal() {
397            HorizontalAnchor::Left => position.x,
398            HorizontalAnchor::Center => position.x - text_width as f64 / 2.0,
399            HorizontalAnchor::Right => position.x - text_width as f64,
400        };
401
402        let vertical_anchor = anchor.get_vertical();
403
404        let y = match (vertical_anchor.get_context(), vertical_anchor.get_value()) {
405            (VerticalAnchorContext::Graphics, VerticalAnchorValue::Bottom) => {
406                position.y - size / 1.25
407            }
408            (VerticalAnchorContext::Math, VerticalAnchorValue::Bottom) => position.y,
409            (_, VerticalAnchorValue::Center) => position.y - size / 1.25 / 2.0,
410            (VerticalAnchorContext::Graphics, VerticalAnchorValue::Top) => position.y,
411            (VerticalAnchorContext::Math, VerticalAnchorValue::Top) => position.y - size / 1.25,
412        };
413
414        for i in -1..=1 {
415            for j in -1..=1 {
416                if i != 0 || j != 0 {
417                    draw_text_mut(
418                        &mut self.image,
419                        srgba_to_rgba8(outline_color),
420                        (x - i as f64 * outline_thickness).round() as i32,
421                        (y - j as f64 * outline_thickness).round() as i32,
422                        size as f32,
423                        &self.font,
424                        text,
425                    );
426                }
427            }
428        }
429
430        draw_text_mut(
431            &mut self.image,
432            srgba_to_rgba8(color),
433            x as i32,
434            y as i32,
435            size as f32,
436            &self.font,
437            text,
438        );
439    }
440
441    fn render_rectangle(
442        &mut self,
443        position: DVec2,
444        width: f64,
445        height: f64,
446        offset: DVec2,
447        rotation: f64,
448        color: Srgba,
449    ) {
450        let position = self.map_dvec2(position);
451        let width = self.map_value(width) - 1.0;
452        let height = self.map_value(height) - 1.0;
453
454        let base_points = self.get_base_points(position, width, height);
455        let offset_vec = self.get_offset_vec(width, height, offset);
456        let offset_points = self.get_offset_points(&base_points, offset_vec);
457        let rotated_points = self.get_rotated_points(&offset_points, position, rotation);
458
459        let integer_points = self
460            .get_unique_integer_points(&rotated_points)
461            .iter()
462            .map(|integer_point| Point::new(integer_point.x, integer_point.y))
463            .collect::<Vec<Point<i32>>>();
464
465        if integer_points.len() == 1 {
466            let integer_point = integer_points.first().unwrap();
467
468            self.render_point(dvec2(integer_point.x as f64, integer_point.y as f64), color);
469        } else {
470            draw_polygon_mut(&mut self.image, &integer_points, srgba_to_rgba8(color));
471        }
472    }
473
474    fn render_rectangle_lines(
475        &mut self,
476        position: DVec2,
477        width: f64,
478        height: f64,
479        offset: DVec2,
480        rotation: f64,
481        thickness: f64,
482        color: Srgba,
483    ) {
484        let position = self.map_dvec2(position);
485        let width = self.map_value(width) - 1.0;
486        let height = self.map_value(height) - 1.0;
487        let thickness = self.map_value(thickness);
488
489        let base_points = self.get_base_points(position, width, height);
490        let offset_vec = self.get_offset_vec(width, height, offset);
491        let offset_points = self.get_offset_points(&base_points, offset_vec);
492        let rotated_points = self.get_rotated_points(&offset_points, position, rotation);
493
494        let integer_points = self
495            .get_unique_integer_points(&rotated_points)
496            .iter()
497            .map(|integer_point| Point::new(integer_point.x, integer_point.y))
498            .collect::<Vec<Point<i32>>>();
499
500        let min_x = integer_points
501            .iter()
502            .map(|integer_point| integer_point.x)
503            .min()
504            .unwrap();
505        let max_x = integer_points
506            .iter()
507            .map(|integer_point| integer_point.x)
508            .max()
509            .unwrap();
510
511        let min_y = integer_points
512            .iter()
513            .map(|integer_point| integer_point.y)
514            .min()
515            .unwrap();
516        let max_y = integer_points
517            .iter()
518            .map(|integer_point| integer_point.y)
519            .max()
520            .unwrap();
521
522        let min_vec = ivec2(min_x, min_y).as_dvec2();
523
524        let renderer_width = max_x - min_x + 1;
525        let renderer_height = max_y - min_y + 1;
526
527        let mut rectangle_renderer = ImageRenderer::new(
528            renderer_width as u32,
529            renderer_height as u32,
530            1.0,
531            DVec2::ZERO,
532            1,
533            self.font.clone(),
534        );
535
536        rectangle_renderer.render_rectangle(
537            position - min_vec,
538            width + 1.0,
539            height + 1.0,
540            offset,
541            rotation,
542            color,
543        );
544
545        let midpoint = rotated_points
546            .iter()
547            .copied()
548            .map(|rotated_point| rotated_point - min_vec)
549            .sum::<DVec2>()
550            / 4.0;
551
552        rectangle_renderer.render_rectangle(
553            midpoint,
554            width + 1.0 - 2.0 * thickness,
555            height + 1.0 - 2.0 * thickness,
556            DVec2::splat(0.5),
557            rotation,
558            Srgba::new(0.0, 0.0, 0.0, 0.0),
559        );
560
561        overlay(
562            &mut self.image,
563            &rectangle_renderer.render_image_onto(rectangle_renderer.transparent()),
564            min_x as i64,
565            min_y as i64,
566        );
567    }
568
569    fn render_equilateral_triangle(
570        &mut self,
571        position: DVec2,
572        radius: f64,
573        rotation: f64,
574        color: Srgba,
575    ) {
576        let position = self.map_dvec2(position);
577        let radius = self.map_value(radius);
578
579        let points = (0..3)
580            .map(|i| position + radius * DVec2::from_angle(i as f64 * 2.0 * PI / 3.0 + rotation))
581            .collect::<Vec<DVec2>>();
582
583        let integer_points = self
584            .get_unique_integer_points(&points)
585            .iter()
586            .map(|integer_point| Point::new(integer_point.x, integer_point.y))
587            .collect::<Vec<Point<i32>>>();
588
589        if integer_points.len() == 1 {
590            let integer_point = integer_points.first().unwrap();
591
592            self.render_point(dvec2(integer_point.x as f64, integer_point.y as f64), color);
593        } else {
594            draw_polygon_mut(&mut self.image, &integer_points, srgba_to_rgba8(color));
595        }
596    }
597
598    fn render_equilateral_triangle_lines(
599        &mut self,
600        position: DVec2,
601        radius: f64,
602        rotation: f64,
603        thickness: f64,
604        color: Srgba,
605    ) {
606        let position = self.map_dvec2(position);
607        let radius = self.map_value(radius);
608        let thickness = self.map_value(thickness);
609
610        let points = (0..3)
611            .map(|i| position + radius * DVec2::from_angle(i as f64 * 2.0 * PI / 3.0 + rotation))
612            .collect::<Vec<DVec2>>();
613
614        let integer_points = self
615            .get_unique_integer_points(&points)
616            .iter()
617            .map(|integer_point| Point::new(integer_point.x, integer_point.y))
618            .collect::<Vec<Point<i32>>>();
619
620        let min_x = integer_points
621            .iter()
622            .map(|integer_point| integer_point.x)
623            .min()
624            .expect("triangles have more than 0 points");
625        let max_x = integer_points
626            .iter()
627            .map(|integer_point| integer_point.x)
628            .max()
629            .expect("triangles have more than 0 points");
630        let min_y = integer_points
631            .iter()
632            .map(|integer_point| integer_point.y)
633            .min()
634            .expect("triangles have more than 0 points");
635        let max_y = integer_points
636            .iter()
637            .map(|integer_point| integer_point.y)
638            .max()
639            .expect("triangles have more than 0 points");
640
641        let min_point = ivec2(min_x, min_y);
642
643        let renderer_width = (max_x - min_x + 1) as u32;
644        let renderer_height = (max_y - min_y + 1) as u32;
645
646        let mut triangle_renderer = ImageRenderer::new(
647            renderer_width,
648            renderer_height,
649            1.0,
650            DVec2::ZERO,
651            1,
652            self.font.clone(),
653        );
654
655        triangle_renderer.render_equilateral_triangle(
656            (position - min_point.as_dvec2()).round(),
657            radius,
658            rotation,
659            color,
660        );
661
662        triangle_renderer.render_equilateral_triangle(
663            (position - min_point.as_dvec2()).round(),
664            radius - thickness,
665            rotation,
666            Srgba::new(0.0, 0.0, 0.0, 0.0),
667        );
668
669        overlay(
670            &mut self.image,
671            &triangle_renderer.render_image_onto(triangle_renderer.transparent()),
672            min_x as i64,
673            min_y as i64,
674        );
675    }
676}
677
678fn rotate_point_around(point: DVec2, axis: DVec2, theta: f64) -> DVec2 {
679    if theta == 0.0 {
680        return point;
681    }
682
683    let relative = point - axis;
684    let relative_theta = relative.to_angle();
685    let new_relative_theta = relative_theta + theta;
686    let new_relative = DVec2::from_angle(new_relative_theta) * relative.length();
687    new_relative + axis
688}