render_agnostic/renderers/
image.rs

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