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}