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}