Skip to main content

render_agnostic/renderers/
image.rs

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