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}