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, overlay, resize},
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},
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(
275        &mut self,
276        position: DVec2,
277        radius: f64,
278        rotation: f64,
279        sides: u8,
280        arc: f64,
281        color: Srgba,
282    ) {
283        if arc == 0.0 {
284            return;
285        }
286
287        let position = self.map_dvec2(position);
288        let radius = self.map_value(radius);
289
290        let points = once(position)
291            .chain((0..sides).map(|i| {
292                position
293                    + radius * DVec2::from_angle(rotation + arc * i as f64 / (sides - 1) as f64)
294            }))
295            .collect::<Vec<DVec2>>();
296
297        let integer_points = self
298            .get_unique_integer_points(&points)
299            .iter()
300            .map(|integer_point| Point::new(integer_point.x, integer_point.y))
301            .collect::<Vec<Point<i32>>>();
302
303        if integer_points.len() == 1 {
304            let integer_point = integer_points.first().unwrap();
305
306            self.render_point(dvec2(integer_point.x as f64, integer_point.y as f64), color);
307        } else {
308            draw_polygon_mut(&mut self.image, &integer_points, srgba_to_rgba8(color));
309        }
310    }
311
312    fn render_arc_lines(
313        &mut self,
314        position: DVec2,
315        radius: f64,
316        rotation: f64,
317        sides: u8,
318        arc: f64,
319        thickness: f64,
320        color: Srgba,
321    ) {
322        if arc == 0.0 {
323            return;
324        }
325
326        let position = self.map_dvec2(position).round().as_ivec2();
327        let radius = self.map_value(radius).round();
328        let thickness = self.map_value(thickness).round();
329
330        let mut circle_renderer = ImageRenderer::new(
331            2 * radius as u32 + 1,
332            2 * radius as u32 + 1,
333            1.0,
334            DVec2::ZERO,
335            1,
336            self.font.clone(),
337        );
338
339        circle_renderer.render_arc(dvec2(radius, radius), radius, rotation, sides, arc, color);
340
341        circle_renderer.render_circle(
342            dvec2(radius, radius),
343            radius - thickness,
344            Srgba::new(0.0, 0.0, 0.0, 0.0),
345        );
346
347        overlay(
348            &mut self.image,
349            &circle_renderer.render_image_onto(circle_renderer.transparent()),
350            (position.x - radius as i32) as i64,
351            (position.y - radius as i32) as i64,
352        );
353    }
354
355    fn render_text(
356        &mut self,
357        text: &str,
358        position: DVec2,
359        anchor: Anchor2D,
360        size: f64,
361        color: Srgba,
362    ) {
363        let position = self.map_dvec2(position);
364        let size = self.map_value(size);
365
366        let (text_width, _) = text_size(size as f32, &self.font, text);
367
368        let x = match anchor.get_horizontal() {
369            HorizontalAnchor::Left => position.x,
370            HorizontalAnchor::Center => position.x - text_width as f64 / 2.0,
371            HorizontalAnchor::Right => position.x - text_width as f64,
372        };
373
374        let vertical_anchor = anchor.get_vertical();
375
376        let y = match (vertical_anchor.get_context(), vertical_anchor.get_value()) {
377            (VerticalAnchorContext::Graphics, VerticalAnchorValue::Bottom) => {
378                position.y - size / 1.25
379            }
380            (VerticalAnchorContext::Math, VerticalAnchorValue::Bottom) => position.y,
381            (_, VerticalAnchorValue::Center) => position.y - size / 1.25 / 2.0,
382            (VerticalAnchorContext::Graphics, VerticalAnchorValue::Top) => position.y,
383            (VerticalAnchorContext::Math, VerticalAnchorValue::Top) => position.y - size / 1.25,
384        };
385
386        draw_text_mut(
387            &mut self.image,
388            srgba_to_rgba8(color),
389            x as i32,
390            y as i32,
391            size as f32,
392            &self.font,
393            text,
394        );
395    }
396
397    fn render_text_outline(
398        &mut self,
399        text: &str,
400        position: DVec2,
401        anchor: Anchor2D,
402        size: f64,
403        outline_thickness: f64,
404        color: Srgba,
405        outline_color: Srgba,
406    ) {
407        let position = self.map_dvec2(position);
408        let size = self.map_value(size);
409        let outline_thickness = self.map_value(outline_thickness);
410
411        let (text_width, _) = text_size(size as f32, &self.font, text);
412
413        let x = match anchor.get_horizontal() {
414            HorizontalAnchor::Left => position.x,
415            HorizontalAnchor::Center => position.x - text_width as f64 / 2.0,
416            HorizontalAnchor::Right => position.x - text_width as f64,
417        };
418
419        let vertical_anchor = anchor.get_vertical();
420
421        let y = match (vertical_anchor.get_context(), vertical_anchor.get_value()) {
422            (VerticalAnchorContext::Graphics, VerticalAnchorValue::Bottom) => {
423                position.y - size / 1.25
424            }
425            (VerticalAnchorContext::Math, VerticalAnchorValue::Bottom) => position.y,
426            (_, VerticalAnchorValue::Center) => position.y - size / 1.25 / 2.0,
427            (VerticalAnchorContext::Graphics, VerticalAnchorValue::Top) => position.y,
428            (VerticalAnchorContext::Math, VerticalAnchorValue::Top) => position.y - size / 1.25,
429        };
430
431        for i in -1..=1 {
432            for j in -1..=1 {
433                if i != 0 || j != 0 {
434                    draw_text_mut(
435                        &mut self.image,
436                        srgba_to_rgba8(outline_color),
437                        (x - i as f64 * outline_thickness).round() as i32,
438                        (y - j as f64 * outline_thickness).round() as i32,
439                        size as f32,
440                        &self.font,
441                        text,
442                    );
443                }
444            }
445        }
446
447        draw_text_mut(
448            &mut self.image,
449            srgba_to_rgba8(color),
450            x as i32,
451            y as i32,
452            size as f32,
453            &self.font,
454            text,
455        );
456    }
457
458    fn render_rectangle(
459        &mut self,
460        position: DVec2,
461        width: f64,
462        height: f64,
463        offset: DVec2,
464        rotation: f64,
465        color: Srgba,
466    ) {
467        let position = self.map_dvec2(position);
468        let width = self.map_value(width) - 1.0;
469        let height = self.map_value(height) - 1.0;
470
471        let base_points = self.get_base_points(position, width, height);
472        let offset_vec = self.get_offset_vec(width, height, offset);
473        let offset_points = self.get_offset_points(&base_points, offset_vec);
474        let rotated_points = self.get_rotated_points(&offset_points, position, rotation);
475
476        let integer_points = self
477            .get_unique_integer_points(&rotated_points)
478            .iter()
479            .map(|integer_point| Point::new(integer_point.x, integer_point.y))
480            .collect::<Vec<Point<i32>>>();
481
482        if integer_points.len() == 1 {
483            let integer_point = integer_points.first().unwrap();
484
485            self.render_point(dvec2(integer_point.x as f64, integer_point.y as f64), color);
486        } else {
487            draw_polygon_mut(&mut self.image, &integer_points, srgba_to_rgba8(color));
488        }
489    }
490
491    fn render_rectangle_lines(
492        &mut self,
493        position: DVec2,
494        width: f64,
495        height: f64,
496        offset: DVec2,
497        rotation: f64,
498        thickness: f64,
499        color: Srgba,
500    ) {
501        let position = self.map_dvec2(position);
502        let width = self.map_value(width) - 1.0;
503        let height = self.map_value(height) - 1.0;
504        let thickness = self.map_value(thickness);
505
506        let base_points = self.get_base_points(position, width, height);
507        let offset_vec = self.get_offset_vec(width, height, offset);
508        let offset_points = self.get_offset_points(&base_points, offset_vec);
509        let rotated_points = self.get_rotated_points(&offset_points, position, rotation);
510
511        let integer_points = self
512            .get_unique_integer_points(&rotated_points)
513            .iter()
514            .map(|integer_point| Point::new(integer_point.x, integer_point.y))
515            .collect::<Vec<Point<i32>>>();
516
517        let min_x = integer_points
518            .iter()
519            .map(|integer_point| integer_point.x)
520            .min()
521            .unwrap();
522        let max_x = integer_points
523            .iter()
524            .map(|integer_point| integer_point.x)
525            .max()
526            .unwrap();
527
528        let min_y = integer_points
529            .iter()
530            .map(|integer_point| integer_point.y)
531            .min()
532            .unwrap();
533        let max_y = integer_points
534            .iter()
535            .map(|integer_point| integer_point.y)
536            .max()
537            .unwrap();
538
539        let min_vec = ivec2(min_x, min_y).as_dvec2();
540
541        let renderer_width = max_x - min_x + 1;
542        let renderer_height = max_y - min_y + 1;
543
544        let mut rectangle_renderer = ImageRenderer::new(
545            renderer_width as u32,
546            renderer_height as u32,
547            1.0,
548            DVec2::ZERO,
549            1,
550            self.font.clone(),
551        );
552
553        rectangle_renderer.render_rectangle(
554            position - min_vec,
555            width + 1.0,
556            height + 1.0,
557            offset,
558            rotation,
559            color,
560        );
561
562        let midpoint = rotated_points
563            .iter()
564            .copied()
565            .map(|rotated_point| rotated_point - min_vec)
566            .sum::<DVec2>()
567            / 4.0;
568
569        rectangle_renderer.render_rectangle(
570            midpoint,
571            width + 1.0 - 2.0 * thickness,
572            height + 1.0 - 2.0 * thickness,
573            DVec2::splat(0.5),
574            rotation,
575            Srgba::new(0.0, 0.0, 0.0, 0.0),
576        );
577
578        overlay(
579            &mut self.image,
580            &rectangle_renderer.render_image_onto(rectangle_renderer.transparent()),
581            min_x as i64,
582            min_y as i64,
583        );
584    }
585
586    fn render_equilateral_triangle(
587        &mut self,
588        position: DVec2,
589        radius: f64,
590        rotation: f64,
591        color: Srgba,
592    ) {
593        let position = self.map_dvec2(position);
594        let radius = self.map_value(radius);
595
596        let points = (0..3)
597            .map(|i| position + radius * DVec2::from_angle(i as f64 * 2.0 * PI / 3.0 + rotation))
598            .collect::<Vec<DVec2>>();
599
600        let integer_points = self
601            .get_unique_integer_points(&points)
602            .iter()
603            .map(|integer_point| Point::new(integer_point.x, integer_point.y))
604            .collect::<Vec<Point<i32>>>();
605
606        if integer_points.len() == 1 {
607            let integer_point = integer_points.first().unwrap();
608
609            self.render_point(dvec2(integer_point.x as f64, integer_point.y as f64), color);
610        } else {
611            draw_polygon_mut(&mut self.image, &integer_points, srgba_to_rgba8(color));
612        }
613    }
614
615    fn render_equilateral_triangle_lines(
616        &mut self,
617        position: DVec2,
618        radius: f64,
619        rotation: f64,
620        thickness: f64,
621        color: Srgba,
622    ) {
623        let position = self.map_dvec2(position);
624        let radius = self.map_value(radius);
625        let thickness = self.map_value(thickness);
626
627        let points = (0..3)
628            .map(|i| position + radius * DVec2::from_angle(i as f64 * 2.0 * PI / 3.0 + rotation))
629            .collect::<Vec<DVec2>>();
630
631        let integer_points = self
632            .get_unique_integer_points(&points)
633            .iter()
634            .map(|integer_point| Point::new(integer_point.x, integer_point.y))
635            .collect::<Vec<Point<i32>>>();
636
637        let min_x = integer_points
638            .iter()
639            .map(|integer_point| integer_point.x)
640            .min()
641            .expect("triangles have more than 0 points");
642        let max_x = integer_points
643            .iter()
644            .map(|integer_point| integer_point.x)
645            .max()
646            .expect("triangles have more than 0 points");
647        let min_y = integer_points
648            .iter()
649            .map(|integer_point| integer_point.y)
650            .min()
651            .expect("triangles have more than 0 points");
652        let max_y = integer_points
653            .iter()
654            .map(|integer_point| integer_point.y)
655            .max()
656            .expect("triangles have more than 0 points");
657
658        let min_point = ivec2(min_x, min_y);
659
660        let renderer_width = (max_x - min_x + 1) as u32;
661        let renderer_height = (max_y - min_y + 1) as u32;
662
663        let mut triangle_renderer = ImageRenderer::new(
664            renderer_width,
665            renderer_height,
666            1.0,
667            DVec2::ZERO,
668            1,
669            self.font.clone(),
670        );
671
672        triangle_renderer.render_equilateral_triangle(
673            (position - min_point.as_dvec2()).round(),
674            radius,
675            rotation,
676            color,
677        );
678
679        triangle_renderer.render_equilateral_triangle(
680            (position - min_point.as_dvec2()).round(),
681            radius - thickness,
682            rotation,
683            Srgba::new(0.0, 0.0, 0.0, 0.0),
684        );
685
686        overlay(
687            &mut self.image,
688            &triangle_renderer.render_image_onto(triangle_renderer.transparent()),
689            min_x as i64,
690            min_y as i64,
691        );
692    }
693
694    fn render_image(
695        &mut self,
696        image_name: &str,
697        position: ::glam::DVec2,
698        width: f64,
699        height: f64,
700        offset: ::glam::DVec2,
701        rotation: f64,
702    ) {
703        let position = self.map_dvec2(position);
704        let width = self.map_value(width) - 1.0;
705        let height = self.map_value(height) - 1.0;
706
707        if let Some(image) = self.images.get(image_name) {
708            let resized_image = resize(image, width as u32, height as u32, FilterType::Nearest);
709            let mut base_image = self.transparent();
710            overlay(
711                &mut base_image,
712                &resized_image,
713                (position.x - width * offset.x) as i64,
714                (position.y - height * offset.y) as i64,
715            );
716            let rotated_image = rotate(
717                &base_image,
718                (position.x as f32, position.y as f32),
719                rotation as f32,
720                Interpolation::Nearest,
721                Rgba::from([0, 0, 0, 0]),
722            );
723
724            overlay(&mut self.image, &rotated_image, 0, 0);
725        }
726    }
727}
728
729fn rotate_point_around(point: DVec2, axis: DVec2, theta: f64) -> DVec2 {
730    if theta == 0.0 {
731        return point;
732    }
733
734    let relative = point - axis;
735    let relative_theta = relative.to_angle();
736    let new_relative_theta = relative_theta + theta;
737    let new_relative = DVec2::from_angle(new_relative_theta) * relative.length();
738    new_relative + axis
739}