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