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