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 render_point(&mut self, position: DVec2, color: Srgba) {
288 let position = self.map_dvec2(position);
289 let width = self.map_value(1.0);
290 let height = self.map_value(1.0);
291
292 let integer_position = position.round().as_ivec2();
293
294 let integer_width = width.round() as u32;
295 let integer_height = height.round() as u32;
296
297 if integer_width > 0 && integer_height > 0 {
298 draw_filled_rect_mut(
299 &mut self.image,
300 Rect::at(integer_position.x, integer_position.y)
301 .of_size(integer_width, integer_height),
302 srgba_to_rgba8(color),
303 );
304 }
305 }
306
307 fn render_line(&mut self, start: DVec2, end: DVec2, thickness: f64, color: Srgba) {
308 let start = self.map_dvec2(start);
309 let end = self.map_dvec2(end);
310
311 let thickness = self.map_value(thickness);
312 let offset = thickness / 2.0;
313 let normal = DVec2::from_angle((end - start).to_angle() + FRAC_PI_2);
314
315 let points = vec![
316 start + normal * offset,
317 start - normal * offset,
318 end - normal * offset,
319 end + normal * offset,
320 ];
321
322 let integer_points = self
323 .get_unique_integer_points(&points)
324 .iter()
325 .map(|integer_point| Point::new(integer_point.x, integer_point.y))
326 .collect::<Vec<Point<i32>>>();
327
328 if integer_points.len() == 1 {
329 let integer_point = integer_points.first().unwrap();
330
331 self.render_point(dvec2(integer_point.x as f64, integer_point.y as f64), color);
332 } else {
333 draw_polygon_mut(&mut self.image, &integer_points, srgba_to_rgba8(color));
334 }
335 }
336
337 fn render_circle(&mut self, position: DVec2, radius: f64, color: Srgba) {
338 let position = self.map_dvec2(position).round().as_ivec2();
339 let radius = self.map_value(radius).round() as u32;
340
341 draw_filled_circle_mut(
342 &mut self.image,
343 position.into(),
344 radius as i32,
345 srgba_to_rgba8(color),
346 );
347 }
348
349 fn render_circle_lines(&mut self, position: DVec2, radius: f64, thickness: f64, color: Srgba) {
350 let position = self.map_dvec2(position).round().as_ivec2();
351 let radius = self.map_value(radius).round();
352 let thickness = self.map_value(thickness).round();
353
354 let mut circle_renderer = ImageRenderer::new(
355 2 * radius as u32 + 1,
356 2 * radius as u32 + 1,
357 1.0,
358 DVec2::ZERO,
359 1,
360 self.font.clone(),
361 ImageImageRegistry::default(),
362 );
363
364 circle_renderer.render_circle(dvec2(radius, radius), radius, color);
365
366 circle_renderer.render_circle(
367 dvec2(radius, radius),
368 radius - thickness,
369 Srgba::new(0.0, 0.0, 0.0, 0.0),
370 );
371
372 overlay(
373 &mut self.image,
374 &circle_renderer.render_image_onto(circle_renderer.transparent()),
375 (position.x - radius as i32) as i64,
376 (position.y - radius as i32) as i64,
377 );
378 }
379
380 fn render_arc(
381 &mut self,
382 position: DVec2,
383 radius: f64,
384 rotation: f64,
385 sides: u8,
386 arc: f64,
387 color: Srgba,
388 ) {
389 if arc == 0.0 {
390 return;
391 }
392
393 let position = self.map_dvec2(position);
394 let radius = self.map_value(radius);
395
396 let points = once(position)
397 .chain((0..sides).map(|i| {
398 position
399 + radius * DVec2::from_angle(rotation + arc * i as f64 / (sides - 1) as f64)
400 }))
401 .collect::<Vec<DVec2>>();
402
403 let integer_points = self
404 .get_unique_integer_points(&points)
405 .iter()
406 .map(|integer_point| Point::new(integer_point.x, integer_point.y))
407 .collect::<Vec<Point<i32>>>();
408
409 if integer_points.len() == 1 {
410 let integer_point = integer_points.first().unwrap();
411
412 self.render_point(dvec2(integer_point.x as f64, integer_point.y as f64), color);
413 } else {
414 draw_polygon_mut(&mut self.image, &integer_points, srgba_to_rgba8(color));
415 }
416 }
417
418 fn render_arc_lines(
419 &mut self,
420 position: DVec2,
421 radius: f64,
422 rotation: f64,
423 sides: u8,
424 arc: f64,
425 thickness: f64,
426 color: Srgba,
427 ) {
428 if arc == 0.0 {
429 return;
430 }
431
432 let position = self.map_dvec2(position).round().as_ivec2();
433 let radius = self.map_value(radius).round();
434 let thickness = self.map_value(thickness).round();
435
436 let mut circle_renderer = ImageRenderer::new(
437 2 * radius as u32 + 1,
438 2 * radius as u32 + 1,
439 1.0,
440 DVec2::ZERO,
441 1,
442 self.font.clone(),
443 ImageImageRegistry::default(),
444 );
445
446 circle_renderer.render_arc(dvec2(radius, radius), radius, rotation, sides, arc, color);
447
448 circle_renderer.render_circle(
449 dvec2(radius, radius),
450 radius - thickness,
451 Srgba::new(0.0, 0.0, 0.0, 0.0),
452 );
453
454 overlay(
455 &mut self.image,
456 &circle_renderer.render_image_onto(circle_renderer.transparent()),
457 (position.x - radius as i32) as i64,
458 (position.y - radius as i32) as i64,
459 );
460 }
461
462 fn render_text(
463 &mut self,
464 text: &str,
465 position: DVec2,
466 anchor: Anchor2D,
467 size: f64,
468 color: Srgba,
469 ) {
470 for (i, line) in text.split("\n").enumerate() {
471 self.render_line(
472 line,
473 position + DVec2::Y * size * i as f64,
474 anchor,
475 size,
476 color,
477 );
478 }
479 }
480
481 fn render_text_outline(
482 &mut self,
483 text: &str,
484 position: DVec2,
485 anchor: Anchor2D,
486 size: f64,
487 outline_thickness: f64,
488 color: Srgba,
489 outline_color: Srgba,
490 ) {
491 for (i, line) in text.split("\n").enumerate() {
492 self.render_line_outline(
493 line,
494 position + DVec2::Y * size * i as f64,
495 anchor,
496 size,
497 outline_thickness,
498 color,
499 outline_color,
500 );
501 }
502 }
503
504 fn render_rectangle(
505 &mut self,
506 position: DVec2,
507 width: f64,
508 height: f64,
509 offset: DVec2,
510 rotation: f64,
511 color: Srgba,
512 ) {
513 let position = self.map_dvec2(position);
514 let width = self.map_value(width) - 1.0;
515 let height = self.map_value(height) - 1.0;
516
517 let base_points = self.get_base_points(position, width, height);
518 let offset_vec = self.get_offset_vec(width, height, offset);
519 let offset_points = self.get_offset_points(&base_points, offset_vec);
520 let rotated_points = self.get_rotated_points(&offset_points, position, rotation);
521
522 let integer_points = self
523 .get_unique_integer_points(&rotated_points)
524 .iter()
525 .map(|integer_point| Point::new(integer_point.x, integer_point.y))
526 .collect::<Vec<Point<i32>>>();
527
528 if integer_points.len() == 1 {
529 let integer_point = integer_points.first().unwrap();
530
531 self.render_point(dvec2(integer_point.x as f64, integer_point.y as f64), color);
532 } else {
533 draw_polygon_mut(&mut self.image, &integer_points, srgba_to_rgba8(color));
534 }
535 }
536
537 fn render_rectangle_lines(
538 &mut self,
539 position: DVec2,
540 width: f64,
541 height: f64,
542 offset: DVec2,
543 rotation: f64,
544 thickness: f64,
545 color: Srgba,
546 ) {
547 let position = self.map_dvec2(position);
548 let width = self.map_value(width) - 1.0;
549 let height = self.map_value(height) - 1.0;
550 let thickness = self.map_value(thickness);
551
552 let base_points = self.get_base_points(position, width, height);
553 let offset_vec = self.get_offset_vec(width, height, offset);
554 let offset_points = self.get_offset_points(&base_points, offset_vec);
555 let rotated_points = self.get_rotated_points(&offset_points, position, rotation);
556
557 let integer_points = self
558 .get_unique_integer_points(&rotated_points)
559 .iter()
560 .map(|integer_point| Point::new(integer_point.x, integer_point.y))
561 .collect::<Vec<Point<i32>>>();
562
563 let min_x = integer_points
564 .iter()
565 .map(|integer_point| integer_point.x)
566 .min()
567 .unwrap();
568 let max_x = integer_points
569 .iter()
570 .map(|integer_point| integer_point.x)
571 .max()
572 .unwrap();
573
574 let min_y = integer_points
575 .iter()
576 .map(|integer_point| integer_point.y)
577 .min()
578 .unwrap();
579 let max_y = integer_points
580 .iter()
581 .map(|integer_point| integer_point.y)
582 .max()
583 .unwrap();
584
585 let min_vec = ivec2(min_x, min_y).as_dvec2();
586
587 let renderer_width = max_x - min_x + 1;
588 let renderer_height = max_y - min_y + 1;
589
590 let mut rectangle_renderer = ImageRenderer::new(
591 renderer_width as u32,
592 renderer_height as u32,
593 1.0,
594 DVec2::ZERO,
595 1,
596 self.font.clone(),
597 ImageImageRegistry::default(),
598 );
599
600 rectangle_renderer.render_rectangle(
601 position - min_vec,
602 width + 1.0,
603 height + 1.0,
604 offset,
605 rotation,
606 color,
607 );
608
609 let midpoint = rotated_points
610 .iter()
611 .copied()
612 .map(|rotated_point| rotated_point - min_vec)
613 .sum::<DVec2>()
614 / 4.0;
615
616 rectangle_renderer.render_rectangle(
617 midpoint,
618 width + 1.0 - 2.0 * thickness,
619 height + 1.0 - 2.0 * thickness,
620 DVec2::splat(0.5),
621 rotation,
622 Srgba::new(0.0, 0.0, 0.0, 0.0),
623 );
624
625 overlay(
626 &mut self.image,
627 &rectangle_renderer.render_image_onto(rectangle_renderer.transparent()),
628 min_x as i64,
629 min_y as i64,
630 );
631 }
632
633 fn render_equilateral_triangle(
634 &mut self,
635 position: DVec2,
636 radius: f64,
637 rotation: f64,
638 color: Srgba,
639 ) {
640 let position = self.map_dvec2(position);
641 let radius = self.map_value(radius);
642
643 let points = (0..3)
644 .map(|i| position + radius * DVec2::from_angle(i as f64 * 2.0 * PI / 3.0 + rotation))
645 .collect::<Vec<DVec2>>();
646
647 let integer_points = self
648 .get_unique_integer_points(&points)
649 .iter()
650 .map(|integer_point| Point::new(integer_point.x, integer_point.y))
651 .collect::<Vec<Point<i32>>>();
652
653 if integer_points.len() == 1 {
654 let integer_point = integer_points.first().unwrap();
655
656 self.render_point(dvec2(integer_point.x as f64, integer_point.y as f64), color);
657 } else {
658 draw_polygon_mut(&mut self.image, &integer_points, srgba_to_rgba8(color));
659 }
660 }
661
662 fn render_equilateral_triangle_lines(
663 &mut self,
664 position: DVec2,
665 radius: f64,
666 rotation: f64,
667 thickness: f64,
668 color: Srgba,
669 ) {
670 let position = self.map_dvec2(position);
671 let radius = self.map_value(radius);
672 let thickness = self.map_value(thickness);
673
674 let points = (0..3)
675 .map(|i| position + radius * DVec2::from_angle(i as f64 * 2.0 * PI / 3.0 + rotation))
676 .collect::<Vec<DVec2>>();
677
678 let integer_points = self
679 .get_unique_integer_points(&points)
680 .iter()
681 .map(|integer_point| Point::new(integer_point.x, integer_point.y))
682 .collect::<Vec<Point<i32>>>();
683
684 let min_x = integer_points
685 .iter()
686 .map(|integer_point| integer_point.x)
687 .min()
688 .expect("triangles have more than 0 points");
689 let max_x = integer_points
690 .iter()
691 .map(|integer_point| integer_point.x)
692 .max()
693 .expect("triangles have more than 0 points");
694 let min_y = integer_points
695 .iter()
696 .map(|integer_point| integer_point.y)
697 .min()
698 .expect("triangles have more than 0 points");
699 let max_y = integer_points
700 .iter()
701 .map(|integer_point| integer_point.y)
702 .max()
703 .expect("triangles have more than 0 points");
704
705 let min_point = ivec2(min_x, min_y);
706
707 let renderer_width = (max_x - min_x + 1) as u32;
708 let renderer_height = (max_y - min_y + 1) as u32;
709
710 let mut triangle_renderer = ImageRenderer::new(
711 renderer_width,
712 renderer_height,
713 1.0,
714 DVec2::ZERO,
715 1,
716 self.font.clone(),
717 ImageImageRegistry::default(),
718 );
719
720 triangle_renderer.render_equilateral_triangle(
721 (position - min_point.as_dvec2()).round(),
722 radius,
723 rotation,
724 color,
725 );
726
727 triangle_renderer.render_equilateral_triangle(
728 (position - min_point.as_dvec2()).round(),
729 radius - thickness,
730 rotation,
731 Srgba::new(0.0, 0.0, 0.0, 0.0),
732 );
733
734 overlay(
735 &mut self.image,
736 &triangle_renderer.render_image_onto(triangle_renderer.transparent()),
737 min_x as i64,
738 min_y as i64,
739 );
740 }
741
742 fn render_image(
743 &mut self,
744 image_name: &str,
745 position: ::glam::DVec2,
746 width: f64,
747 height: f64,
748 offset: ::glam::DVec2,
749 rotation: f64,
750 ) {
751 let position = self.map_dvec2(position);
752 let width = self.map_value(width) - 1.0;
753 let height = self.map_value(height) - 1.0;
754
755 if let Some(image) = self.image_registry.borrow().get_image(image_name) {
756 let resized_image = resize(image, width as u32, height as u32, FilterType::Nearest);
757 let mut base_image = self.transparent();
758 overlay(
759 &mut base_image,
760 &resized_image,
761 (position.x - width * offset.x) as i64,
762 (position.y - height * offset.y) as i64,
763 );
764 let rotated_image = rotate(
765 &base_image,
766 (position.x as f32, position.y as f32),
767 rotation as f32,
768 Interpolation::Nearest,
769 Border::Constant(Rgba::from([0, 0, 0, 0])),
770 );
771
772 overlay(&mut self.image, &rotated_image, 0, 0);
773 }
774 }
775}
776
777fn rotate_point_around(point: DVec2, axis: DVec2, theta: f64) -> DVec2 {
778 if theta == 0.0 {
779 return point;
780 }
781
782 let relative = point - axis;
783 let relative_theta = relative.to_angle();
784 let new_relative_theta = relative_theta + theta;
785 let new_relative = DVec2::from_angle(new_relative_theta) * relative.length();
786 new_relative + axis
787}