render_agnostic/renderers/
image.rs

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