Skip to main content

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