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, crop_imm, overlay, resize, thumbnail},
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, rotate_about_center},
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
181impl Renderer for ImageRenderer {
182 fn render_point(&mut self, position: DVec2, color: Srgba) {
183 let position = self.map_dvec2(position);
184 let width = self.map_value(1.0);
185 let height = self.map_value(1.0);
186
187 let integer_position = position.round().as_ivec2();
188
189 let integer_width = width.round() as u32;
190 let integer_height = height.round() as u32;
191
192 if integer_width > 0 && integer_height > 0 {
193 draw_filled_rect_mut(
194 &mut self.image,
195 Rect::at(integer_position.x, integer_position.y)
196 .of_size(integer_width, integer_height),
197 srgba_to_rgba8(color),
198 );
199 }
200 }
201
202 fn render_line(&mut self, start: DVec2, end: DVec2, thickness: f64, color: Srgba) {
203 let start = self.map_dvec2(start);
204 let end = self.map_dvec2(end);
205
206 let thickness = self.map_value(thickness);
207 let offset = thickness / 2.0;
208 let normal = DVec2::from_angle((end - start).to_angle() + FRAC_PI_2);
209
210 let points = vec![
211 start + normal * offset,
212 start - normal * offset,
213 end - normal * offset,
214 end + normal * offset,
215 ];
216
217 let integer_points = self
218 .get_unique_integer_points(&points)
219 .iter()
220 .map(|integer_point| Point::new(integer_point.x, integer_point.y))
221 .collect::<Vec<Point<i32>>>();
222
223 if integer_points.len() == 1 {
224 let integer_point = integer_points.first().unwrap();
225
226 self.render_point(dvec2(integer_point.x as f64, integer_point.y as f64), color);
227 } else {
228 draw_polygon_mut(&mut self.image, &integer_points, srgba_to_rgba8(color));
229 }
230 }
231
232 fn render_circle(&mut self, position: DVec2, radius: f64, color: Srgba) {
233 let position = self.map_dvec2(position).round().as_ivec2();
234 let radius = self.map_value(radius).round() as u32;
235
236 draw_filled_circle_mut(
237 &mut self.image,
238 position.into(),
239 radius as i32,
240 srgba_to_rgba8(color),
241 );
242 }
243
244 fn render_circle_lines(&mut self, position: DVec2, radius: f64, thickness: f64, color: Srgba) {
245 let position = self.map_dvec2(position).round().as_ivec2();
246 let radius = self.map_value(radius).round();
247 let thickness = self.map_value(thickness).round();
248
249 let mut circle_renderer = ImageRenderer::new(
250 2 * radius as u32 + 1,
251 2 * radius as u32 + 1,
252 1.0,
253 DVec2::ZERO,
254 1,
255 self.font.clone(),
256 );
257
258 circle_renderer.render_circle(dvec2(radius, radius), radius, color);
259
260 circle_renderer.render_circle(
261 dvec2(radius, radius),
262 radius - thickness,
263 Srgba::new(0.0, 0.0, 0.0, 0.0),
264 );
265
266 overlay(
267 &mut self.image,
268 &circle_renderer.render_image_onto(circle_renderer.transparent()),
269 (position.x - radius as i32) as i64,
270 (position.y - radius as i32) as i64,
271 );
272 }
273
274 fn render_arc(&mut self, position: DVec2, radius: f64, rotation: f64, arc: f64, color: Srgba) {
275 if arc == 0.0 {
276 return;
277 }
278
279 let position = self.map_dvec2(position);
280 let radius = self.map_value(radius);
281
282 let points =
283 once(position)
284 .chain((0..32).map(|i| {
285 position + radius * DVec2::from_angle(rotation + arc * i as f64 / 31.0)
286 }))
287 .collect::<Vec<DVec2>>();
288
289 let integer_points = self
290 .get_unique_integer_points(&points)
291 .iter()
292 .map(|integer_point| Point::new(integer_point.x, integer_point.y))
293 .collect::<Vec<Point<i32>>>();
294
295 if integer_points.len() == 1 {
296 let integer_point = integer_points.first().unwrap();
297
298 self.render_point(dvec2(integer_point.x as f64, integer_point.y as f64), color);
299 } else {
300 draw_polygon_mut(&mut self.image, &integer_points, srgba_to_rgba8(color));
301 }
302 }
303
304 fn render_arc_lines(
305 &mut self,
306 position: DVec2,
307 radius: f64,
308 rotation: f64,
309 arc: f64,
310 thickness: f64,
311 color: Srgba,
312 ) {
313 if arc == 0.0 {
314 return;
315 }
316
317 let position = self.map_dvec2(position).round().as_ivec2();
318 let radius = self.map_value(radius).round();
319 let thickness = self.map_value(thickness).round();
320
321 let mut circle_renderer = ImageRenderer::new(
322 2 * radius as u32 + 1,
323 2 * radius as u32 + 1,
324 1.0,
325 DVec2::ZERO,
326 1,
327 self.font.clone(),
328 );
329
330 circle_renderer.render_arc(dvec2(radius, radius), radius, rotation, arc, color);
331
332 circle_renderer.render_circle(
333 dvec2(radius, radius),
334 radius - thickness,
335 Srgba::new(0.0, 0.0, 0.0, 0.0),
336 );
337
338 overlay(
339 &mut self.image,
340 &circle_renderer.render_image_onto(circle_renderer.transparent()),
341 (position.x - radius as i32) as i64,
342 (position.y - radius as i32) as i64,
343 );
344 }
345
346 fn render_text(
347 &mut self,
348 text: &str,
349 position: DVec2,
350 anchor: Anchor2D,
351 size: f64,
352 color: Srgba,
353 ) {
354 let position = self.map_dvec2(position);
355 let size = self.map_value(size);
356
357 let (text_width, _) = text_size(size as f32, &self.font, text);
358
359 let x = match anchor.get_horizontal() {
360 HorizontalAnchor::Left => position.x,
361 HorizontalAnchor::Center => position.x - text_width as f64 / 2.0,
362 HorizontalAnchor::Right => position.x - text_width as f64,
363 };
364
365 let vertical_anchor = anchor.get_vertical();
366
367 let y = match (vertical_anchor.get_context(), vertical_anchor.get_value()) {
368 (VerticalAnchorContext::Graphics, VerticalAnchorValue::Bottom) => {
369 position.y - size / 1.25
370 }
371 (VerticalAnchorContext::Math, VerticalAnchorValue::Bottom) => position.y,
372 (_, VerticalAnchorValue::Center) => position.y - size / 1.25 / 2.0,
373 (VerticalAnchorContext::Graphics, VerticalAnchorValue::Top) => position.y,
374 (VerticalAnchorContext::Math, VerticalAnchorValue::Top) => position.y - size / 1.25,
375 };
376
377 draw_text_mut(
378 &mut self.image,
379 srgba_to_rgba8(color),
380 x as i32,
381 y as i32,
382 size as f32,
383 &self.font,
384 text,
385 );
386 }
387
388 fn render_text_outline(
389 &mut self,
390 text: &str,
391 position: DVec2,
392 anchor: Anchor2D,
393 size: f64,
394 outline_thickness: f64,
395 color: Srgba,
396 outline_color: Srgba,
397 ) {
398 let position = self.map_dvec2(position);
399 let size = self.map_value(size);
400 let outline_thickness = self.map_value(outline_thickness);
401
402 let (text_width, _) = text_size(size as f32, &self.font, text);
403
404 let x = match anchor.get_horizontal() {
405 HorizontalAnchor::Left => position.x,
406 HorizontalAnchor::Center => position.x - text_width as f64 / 2.0,
407 HorizontalAnchor::Right => position.x - text_width as f64,
408 };
409
410 let vertical_anchor = anchor.get_vertical();
411
412 let y = match (vertical_anchor.get_context(), vertical_anchor.get_value()) {
413 (VerticalAnchorContext::Graphics, VerticalAnchorValue::Bottom) => {
414 position.y - size / 1.25
415 }
416 (VerticalAnchorContext::Math, VerticalAnchorValue::Bottom) => position.y,
417 (_, VerticalAnchorValue::Center) => position.y - size / 1.25 / 2.0,
418 (VerticalAnchorContext::Graphics, VerticalAnchorValue::Top) => position.y,
419 (VerticalAnchorContext::Math, VerticalAnchorValue::Top) => position.y - size / 1.25,
420 };
421
422 for i in -1..=1 {
423 for j in -1..=1 {
424 if i != 0 || j != 0 {
425 draw_text_mut(
426 &mut self.image,
427 srgba_to_rgba8(outline_color),
428 (x - i as f64 * outline_thickness).round() as i32,
429 (y - j as f64 * outline_thickness).round() as i32,
430 size as f32,
431 &self.font,
432 text,
433 );
434 }
435 }
436 }
437
438 draw_text_mut(
439 &mut self.image,
440 srgba_to_rgba8(color),
441 x as i32,
442 y as i32,
443 size as f32,
444 &self.font,
445 text,
446 );
447 }
448
449 fn render_rectangle(
450 &mut self,
451 position: DVec2,
452 width: f64,
453 height: f64,
454 offset: DVec2,
455 rotation: f64,
456 color: Srgba,
457 ) {
458 let position = self.map_dvec2(position);
459 let width = self.map_value(width) - 1.0;
460 let height = self.map_value(height) - 1.0;
461
462 let base_points = self.get_base_points(position, width, height);
463 let offset_vec = self.get_offset_vec(width, height, offset);
464 let offset_points = self.get_offset_points(&base_points, offset_vec);
465 let rotated_points = self.get_rotated_points(&offset_points, position, rotation);
466
467 let integer_points = self
468 .get_unique_integer_points(&rotated_points)
469 .iter()
470 .map(|integer_point| Point::new(integer_point.x, integer_point.y))
471 .collect::<Vec<Point<i32>>>();
472
473 if integer_points.len() == 1 {
474 let integer_point = integer_points.first().unwrap();
475
476 self.render_point(dvec2(integer_point.x as f64, integer_point.y as f64), color);
477 } else {
478 draw_polygon_mut(&mut self.image, &integer_points, srgba_to_rgba8(color));
479 }
480 }
481
482 fn render_rectangle_lines(
483 &mut self,
484 position: DVec2,
485 width: f64,
486 height: f64,
487 offset: DVec2,
488 rotation: f64,
489 thickness: f64,
490 color: Srgba,
491 ) {
492 let position = self.map_dvec2(position);
493 let width = self.map_value(width) - 1.0;
494 let height = self.map_value(height) - 1.0;
495 let thickness = self.map_value(thickness);
496
497 let base_points = self.get_base_points(position, width, height);
498 let offset_vec = self.get_offset_vec(width, height, offset);
499 let offset_points = self.get_offset_points(&base_points, offset_vec);
500 let rotated_points = self.get_rotated_points(&offset_points, position, rotation);
501
502 let integer_points = self
503 .get_unique_integer_points(&rotated_points)
504 .iter()
505 .map(|integer_point| Point::new(integer_point.x, integer_point.y))
506 .collect::<Vec<Point<i32>>>();
507
508 let min_x = integer_points
509 .iter()
510 .map(|integer_point| integer_point.x)
511 .min()
512 .unwrap();
513 let max_x = integer_points
514 .iter()
515 .map(|integer_point| integer_point.x)
516 .max()
517 .unwrap();
518
519 let min_y = integer_points
520 .iter()
521 .map(|integer_point| integer_point.y)
522 .min()
523 .unwrap();
524 let max_y = integer_points
525 .iter()
526 .map(|integer_point| integer_point.y)
527 .max()
528 .unwrap();
529
530 let min_vec = ivec2(min_x, min_y).as_dvec2();
531
532 let renderer_width = max_x - min_x + 1;
533 let renderer_height = max_y - min_y + 1;
534
535 let mut rectangle_renderer = ImageRenderer::new(
536 renderer_width as u32,
537 renderer_height as u32,
538 1.0,
539 DVec2::ZERO,
540 1,
541 self.font.clone(),
542 );
543
544 rectangle_renderer.render_rectangle(
545 position - min_vec,
546 width + 1.0,
547 height + 1.0,
548 offset,
549 rotation,
550 color,
551 );
552
553 let midpoint = rotated_points
554 .iter()
555 .copied()
556 .map(|rotated_point| rotated_point - min_vec)
557 .sum::<DVec2>()
558 / 4.0;
559
560 rectangle_renderer.render_rectangle(
561 midpoint,
562 width + 1.0 - 2.0 * thickness,
563 height + 1.0 - 2.0 * thickness,
564 DVec2::splat(0.5),
565 rotation,
566 Srgba::new(0.0, 0.0, 0.0, 0.0),
567 );
568
569 overlay(
570 &mut self.image,
571 &rectangle_renderer.render_image_onto(rectangle_renderer.transparent()),
572 min_x as i64,
573 min_y as i64,
574 );
575 }
576
577 fn render_equilateral_triangle(
578 &mut self,
579 position: DVec2,
580 radius: f64,
581 rotation: f64,
582 color: Srgba,
583 ) {
584 let position = self.map_dvec2(position);
585 let radius = self.map_value(radius);
586
587 let points = (0..3)
588 .map(|i| position + radius * DVec2::from_angle(i as f64 * 2.0 * PI / 3.0 + rotation))
589 .collect::<Vec<DVec2>>();
590
591 let integer_points = self
592 .get_unique_integer_points(&points)
593 .iter()
594 .map(|integer_point| Point::new(integer_point.x, integer_point.y))
595 .collect::<Vec<Point<i32>>>();
596
597 if integer_points.len() == 1 {
598 let integer_point = integer_points.first().unwrap();
599
600 self.render_point(dvec2(integer_point.x as f64, integer_point.y as f64), color);
601 } else {
602 draw_polygon_mut(&mut self.image, &integer_points, srgba_to_rgba8(color));
603 }
604 }
605
606 fn render_equilateral_triangle_lines(
607 &mut self,
608 position: DVec2,
609 radius: f64,
610 rotation: f64,
611 thickness: f64,
612 color: Srgba,
613 ) {
614 let position = self.map_dvec2(position);
615 let radius = self.map_value(radius);
616 let thickness = self.map_value(thickness);
617
618 let points = (0..3)
619 .map(|i| position + radius * DVec2::from_angle(i as f64 * 2.0 * PI / 3.0 + rotation))
620 .collect::<Vec<DVec2>>();
621
622 let integer_points = self
623 .get_unique_integer_points(&points)
624 .iter()
625 .map(|integer_point| Point::new(integer_point.x, integer_point.y))
626 .collect::<Vec<Point<i32>>>();
627
628 let min_x = integer_points
629 .iter()
630 .map(|integer_point| integer_point.x)
631 .min()
632 .expect("triangles have more than 0 points");
633 let max_x = integer_points
634 .iter()
635 .map(|integer_point| integer_point.x)
636 .max()
637 .expect("triangles have more than 0 points");
638 let min_y = integer_points
639 .iter()
640 .map(|integer_point| integer_point.y)
641 .min()
642 .expect("triangles have more than 0 points");
643 let max_y = integer_points
644 .iter()
645 .map(|integer_point| integer_point.y)
646 .max()
647 .expect("triangles have more than 0 points");
648
649 let min_point = ivec2(min_x, min_y);
650
651 let renderer_width = (max_x - min_x + 1) as u32;
652 let renderer_height = (max_y - min_y + 1) as u32;
653
654 let mut triangle_renderer = ImageRenderer::new(
655 renderer_width,
656 renderer_height,
657 1.0,
658 DVec2::ZERO,
659 1,
660 self.font.clone(),
661 );
662
663 triangle_renderer.render_equilateral_triangle(
664 (position - min_point.as_dvec2()).round(),
665 radius,
666 rotation,
667 color,
668 );
669
670 triangle_renderer.render_equilateral_triangle(
671 (position - min_point.as_dvec2()).round(),
672 radius - thickness,
673 rotation,
674 Srgba::new(0.0, 0.0, 0.0, 0.0),
675 );
676
677 overlay(
678 &mut self.image,
679 &triangle_renderer.render_image_onto(triangle_renderer.transparent()),
680 min_x as i64,
681 min_y as i64,
682 );
683 }
684
685 fn render_image(
686 &mut self,
687 image_name: &str,
688 position: ::glam::DVec2,
689 width: f64,
690 height: f64,
691 offset: ::glam::DVec2,
692 rotation: f64,
693 ) {
694 let position = self.map_dvec2(position);
695 let width = self.map_value(width) - 1.0;
696 let height = self.map_value(height) - 1.0;
697
698 if let Some(image) = self.images.get(image_name) {
699 let resized_image = resize(image, width as u32, height as u32, FilterType::Nearest);
700 let mut base_image = self.transparent();
701 overlay(
702 &mut base_image,
703 &resized_image,
704 (position.x - width * offset.x) as i64,
705 (position.y - height * offset.y) as i64,
706 );
707 let rotated_image = rotate(
708 &base_image,
709 (position.x as f32, position.y as f32),
710 rotation as f32,
711 Interpolation::Nearest,
712 Rgba::from([0, 0, 0, 0]),
713 );
714
715 overlay(
716 &mut self.image,
717 &rotated_image,
718 0,
719 0,
720 );
721 }
722 }
723}
724
725fn rotate_point_around(point: DVec2, axis: DVec2, theta: f64) -> DVec2 {
726 if theta == 0.0 {
727 return point;
728 }
729
730 let relative = point - axis;
731 let relative_theta = relative.to_angle();
732 let new_relative_theta = relative_theta + theta;
733 let new_relative = DVec2::from_angle(new_relative_theta) * relative.length();
734 new_relative + axis
735}