1use std::{
2 f64::consts::{FRAC_PI_2, PI},
3 iter::once,
4};
5
6use ab_glyph::FontArc;
7use anchor2d::{Anchor2D, HorizontalAnchor, VerticalAnchorContext, VerticalAnchorValue};
8use glam::{DVec2, IVec2, dvec2, ivec2};
9use image::{
10 Rgba, RgbaImage,
11 imageops::{FilterType, overlay, resize},
12};
13use imageproc::{
14 drawing::{
15 draw_filled_circle_mut, draw_filled_rect_mut, draw_polygon_mut, draw_text_mut, text_size,
16 },
17 point::Point,
18 rect::Rect,
19};
20use itertools::Itertools;
21use palette::Srgba;
22
23use crate::Renderer;
24
25fn srgba_to_rgba8(color: Srgba) -> Rgba<u8> {
26 let red = (color.red * 255.0).round().clamp(0.0, 255.0) as u8;
27 let green = (color.green * 255.0).round().clamp(0.0, 255.0) as u8;
28 let blue = (color.blue * 255.0).round().clamp(0.0, 255.0) as u8;
29 let alpha = (color.alpha * 255.0).round().clamp(0.0, 255.0) as u8;
30 Rgba([red, green, blue, alpha])
31}
32
33#[derive(Clone)]
34pub struct ImageRenderer {
35 virtual_width: u32,
36 virtual_height: u32,
37 image: RgbaImage,
38 scale: f64,
39 scaling_target: DVec2,
40 supersampling: u32,
41 font: FontArc,
42}
43
44impl ImageRenderer {
45 pub fn new(
46 width: u32,
47 height: u32,
48 scale: f64,
49 scaling_target: DVec2,
50 supersampling: u32,
51 font: FontArc,
52 ) -> Self {
53 Self {
54 virtual_width: width,
55 virtual_height: height,
56 image: RgbaImage::new(width * supersampling, height * supersampling),
57 scale,
58 scaling_target,
59 supersampling,
60 font,
61 }
62 }
63
64 pub fn get_font(&self) -> &FontArc {
65 &self.font
66 }
67
68 pub fn set_font(&mut self, font: FontArc) {
69 self.font = font;
70 }
71
72 fn get_supersampled_width(&self) -> u32 {
73 self.virtual_width * self.supersampling
74 }
75
76 fn get_supersampled_height(&self) -> u32 {
77 self.virtual_height * self.supersampling
78 }
79
80 fn map_value(&self, value: f64) -> f64 {
81 value * self.scale * self.supersampling as f64
82 }
83
84 fn map_x(&self, x: f64) -> f64 {
85 let target_x = self.get_supersampled_width() as f64 * self.scaling_target.x;
86 (x * self.supersampling as f64 - target_x) * self.scale + target_x
87 }
88
89 fn map_y(&self, y: f64) -> f64 {
90 let target_y = self.get_supersampled_height() as f64 * self.scaling_target.y;
91 (y * self.supersampling as f64 - target_y) * self.scale + target_y
92 }
93
94 fn map_dvec2(&self, v: DVec2) -> DVec2 {
95 dvec2(self.map_x(v.x), self.map_y(v.y))
96 }
97
98 pub fn reset(&mut self) {
99 self.image = self.transparent();
100 }
101
102 pub fn get_image(&self) -> &RgbaImage {
103 &self.image
104 }
105
106 pub fn render_image_onto(&self, mut image: RgbaImage) -> RgbaImage {
107 overlay(&mut image, &self.image, 0, 0);
108
109 resize(
110 &image,
111 self.virtual_width,
112 self.virtual_height,
113 FilterType::Lanczos3,
114 )
115 }
116
117 pub fn transparent(&self) -> RgbaImage {
118 RgbaImage::new(
119 self.get_supersampled_width(),
120 self.get_supersampled_height(),
121 )
122 }
123
124 pub fn black(&self) -> RgbaImage {
125 RgbaImage::from_pixel(
126 self.get_supersampled_width(),
127 self.get_supersampled_height(),
128 Rgba([0, 0, 0, 255]),
129 )
130 }
131
132 fn get_base_points(&self, position: DVec2, width: f64, height: f64) -> Vec<DVec2> {
133 vec![
134 position,
135 position + DVec2::X * width,
136 position + DVec2::X * width + DVec2::Y * height,
137 position + DVec2::Y * height,
138 ]
139 }
140
141 fn get_offset_vec(&self, width: f64, height: f64, offset: DVec2) -> DVec2 {
142 let offset_width = width * offset.x;
143 let offset_height = height * offset.y;
144
145 dvec2(offset_width, offset_height)
146 }
147
148 fn get_offset_points(&self, points: &[DVec2], offset_vec: DVec2) -> Vec<DVec2> {
149 points
150 .iter()
151 .copied()
152 .map(|base_point| base_point - offset_vec)
153 .collect::<Vec<DVec2>>()
154 }
155
156 fn get_rotated_points(&self, points: &[DVec2], axis: DVec2, rotation: f64) -> Vec<DVec2> {
157 points
158 .iter()
159 .copied()
160 .map(|point| rotate_point_around(point, axis, rotation))
161 .collect::<Vec<DVec2>>()
162 }
163
164 fn get_unique_integer_points(&self, points: &[DVec2]) -> Vec<IVec2> {
165 points
166 .iter()
167 .map(|point| point.round().as_ivec2())
168 .unique()
169 .collect::<Vec<IVec2>>()
170 }
171}
172
173impl Renderer for ImageRenderer {
174 fn render_point(&mut self, position: DVec2, color: Srgba) {
175 let position = self.map_dvec2(position);
176 let width = self.map_value(1.0);
177 let height = self.map_value(1.0);
178
179 let integer_position = position.round().as_ivec2();
180
181 let integer_width = width.round() as u32;
182 let integer_height = height.round() as u32;
183
184 if integer_width > 0 && integer_height > 0 {
185 draw_filled_rect_mut(
186 &mut self.image,
187 Rect::at(integer_position.x, integer_position.y)
188 .of_size(integer_width, integer_height),
189 srgba_to_rgba8(color),
190 );
191 }
192 }
193
194 fn render_line(&mut self, start: DVec2, end: DVec2, thickness: f64, color: Srgba) {
195 let start = self.map_dvec2(start);
196 let end = self.map_dvec2(end);
197
198 let thickness = self.map_value(thickness);
199 let offset = thickness / 2.0;
200 let normal = DVec2::from_angle((end - start).to_angle() + FRAC_PI_2);
201
202 let points = vec![
203 start + normal * offset,
204 start - normal * offset,
205 end - normal * offset,
206 end + normal * offset,
207 ];
208
209 let integer_points = self
210 .get_unique_integer_points(&points)
211 .iter()
212 .map(|integer_point| Point::new(integer_point.x, integer_point.y))
213 .collect::<Vec<Point<i32>>>();
214
215 if integer_points.len() == 1 {
216 let integer_point = integer_points.first().unwrap();
217
218 self.render_point(dvec2(integer_point.x as f64, integer_point.y as f64), color);
219 } else {
220 draw_polygon_mut(&mut self.image, &integer_points, srgba_to_rgba8(color));
221 }
222 }
223
224 fn render_circle(&mut self, position: DVec2, radius: f64, color: Srgba) {
225 let position = self.map_dvec2(position).round().as_ivec2();
226 let radius = self.map_value(radius).round() as u32;
227
228 draw_filled_circle_mut(
229 &mut self.image,
230 position.into(),
231 radius as i32,
232 srgba_to_rgba8(color),
233 );
234 }
235
236 fn render_circle_lines(&mut self, position: DVec2, radius: f64, thickness: f64, color: Srgba) {
237 let position = self.map_dvec2(position).round().as_ivec2();
238 let radius = self.map_value(radius).round();
239 let thickness = self.map_value(thickness).round();
240
241 let mut circle_renderer = ImageRenderer::new(
242 2 * radius as u32 + 1,
243 2 * radius as u32 + 1,
244 self.scale,
245 self.scaling_target,
246 self.supersampling,
247 self.font.clone(),
248 );
249
250 circle_renderer.render_circle(dvec2(radius, radius), radius, color);
251
252 circle_renderer.render_circle(
253 dvec2(radius, radius),
254 radius - thickness,
255 Srgba::new(0.0, 0.0, 0.0, 0.0),
256 );
257
258 overlay(
259 &mut self.image,
260 &circle_renderer.render_image_onto(circle_renderer.transparent()),
261 (position.x - radius as i32) as i64,
262 (position.y - radius as i32) as i64,
263 );
264 }
265
266 fn render_arc(&mut self, position: DVec2, radius: f64, rotation: f64, arc: f64, color: Srgba) {
267 if arc == 0.0 {
268 return;
269 }
270
271 let position = self.map_dvec2(position);
272 let radius = self.map_value(radius);
273
274 let points =
275 once(position)
276 .chain((0..32).map(|i| {
277 position + radius * DVec2::from_angle(rotation + arc * i as f64 / 31.0)
278 }))
279 .collect::<Vec<DVec2>>();
280
281 let integer_points = self
282 .get_unique_integer_points(&points)
283 .iter()
284 .map(|integer_point| Point::new(integer_point.x, integer_point.y))
285 .collect::<Vec<Point<i32>>>();
286
287 if integer_points.len() == 1 {
288 let integer_point = integer_points.first().unwrap();
289
290 self.render_point(dvec2(integer_point.x as f64, integer_point.y as f64), color);
291 } else {
292 draw_polygon_mut(&mut self.image, &integer_points, srgba_to_rgba8(color));
293 }
294 }
295
296 fn render_arc_lines(
297 &mut self,
298 position: DVec2,
299 radius: f64,
300 rotation: f64,
301 arc: f64,
302 thickness: f64,
303 color: Srgba,
304 ) {
305 if arc == 0.0 {
306 return;
307 }
308
309 let position = self.map_dvec2(position).round().as_ivec2();
310 let radius = self.map_value(radius).round();
311 let thickness = self.map_value(thickness).round();
312
313 let mut circle_renderer = ImageRenderer::new(
314 2 * radius as u32 + 1,
315 2 * radius as u32 + 1,
316 self.scale,
317 self.scaling_target,
318 self.supersampling,
319 self.font.clone(),
320 );
321
322 circle_renderer.render_arc(dvec2(radius, radius), radius, rotation, arc, color);
323
324 circle_renderer.render_circle(
325 dvec2(radius, radius),
326 radius - thickness,
327 Srgba::new(0.0, 0.0, 0.0, 0.0),
328 );
329
330 overlay(
331 &mut self.image,
332 &circle_renderer.render_image_onto(circle_renderer.transparent()),
333 (position.x - radius as i32) as i64,
334 (position.y - radius as i32) as i64,
335 );
336 }
337
338 fn render_text(
339 &mut self,
340 text: &str,
341 position: DVec2,
342 anchor: Anchor2D,
343 size: f64,
344 color: Srgba,
345 ) {
346 let position = self.map_dvec2(position);
347 let size = self.map_value(size);
348
349 let (text_width, _) = text_size(size as f32, &self.font, text);
350
351 let x = match anchor.get_horizontal() {
352 HorizontalAnchor::Left => position.x,
353 HorizontalAnchor::Center => position.x - text_width as f64 / 2.0,
354 HorizontalAnchor::Right => position.x - text_width as f64,
355 };
356
357 let vertical_anchor = anchor.get_vertical();
358
359 let y = match (vertical_anchor.get_context(), vertical_anchor.get_value()) {
360 (VerticalAnchorContext::Graphics, VerticalAnchorValue::Bottom) => {
361 position.y - size / 1.25
362 }
363 (VerticalAnchorContext::Math, VerticalAnchorValue::Bottom) => position.y,
364 (_, VerticalAnchorValue::Center) => position.y - size / 1.25 / 2.0,
365 (VerticalAnchorContext::Graphics, VerticalAnchorValue::Top) => position.y,
366 (VerticalAnchorContext::Math, VerticalAnchorValue::Top) => position.y - size / 1.25,
367 };
368
369 draw_text_mut(
370 &mut self.image,
371 srgba_to_rgba8(color),
372 x as i32,
373 y as i32,
374 size as f32,
375 &self.font,
376 text,
377 );
378 }
379
380 fn render_text_outline(
381 &mut self,
382 text: &str,
383 position: DVec2,
384 anchor: Anchor2D,
385 size: f64,
386 outline_thickness: f64,
387 color: Srgba,
388 outline_color: Srgba,
389 ) {
390 let position = self.map_dvec2(position);
391 let size = self.map_value(size);
392 let outline_thickness = self.map_value(outline_thickness);
393
394 let (text_width, _) = text_size(size as f32, &self.font, text);
395
396 let x = match anchor.get_horizontal() {
397 HorizontalAnchor::Left => position.x,
398 HorizontalAnchor::Center => position.x - text_width as f64 / 2.0,
399 HorizontalAnchor::Right => position.x - text_width as f64,
400 };
401
402 let vertical_anchor = anchor.get_vertical();
403
404 let y = match (vertical_anchor.get_context(), vertical_anchor.get_value()) {
405 (VerticalAnchorContext::Graphics, VerticalAnchorValue::Bottom) => {
406 position.y - size / 1.25
407 }
408 (VerticalAnchorContext::Math, VerticalAnchorValue::Bottom) => position.y,
409 (_, VerticalAnchorValue::Center) => position.y - size / 1.25 / 2.0,
410 (VerticalAnchorContext::Graphics, VerticalAnchorValue::Top) => position.y,
411 (VerticalAnchorContext::Math, VerticalAnchorValue::Top) => position.y - size / 1.25,
412 };
413
414 for i in -1..=1 {
415 for j in -1..=1 {
416 if i != 0 || j != 0 {
417 draw_text_mut(
418 &mut self.image,
419 srgba_to_rgba8(outline_color),
420 (x - i as f64 * outline_thickness).round() as i32,
421 (y - j as f64 * outline_thickness).round() as i32,
422 size as f32,
423 &self.font,
424 text,
425 );
426 }
427 }
428 }
429
430 draw_text_mut(
431 &mut self.image,
432 srgba_to_rgba8(color),
433 x as i32,
434 y as i32,
435 size as f32,
436 &self.font,
437 text,
438 );
439 }
440
441 fn render_rectangle(
442 &mut self,
443 position: DVec2,
444 width: f64,
445 height: f64,
446 offset: DVec2,
447 rotation: f64,
448 color: Srgba,
449 ) {
450 let position = self.map_dvec2(position);
451 let width = self.map_value(width) - 1.0;
452 let height = self.map_value(height) - 1.0;
453
454 let base_points = self.get_base_points(position, width, height);
455 let offset_vec = self.get_offset_vec(width, height, offset);
456 let offset_points = self.get_offset_points(&base_points, offset_vec);
457 let rotated_points = self.get_rotated_points(&offset_points, position, rotation);
458
459 let integer_points = self
460 .get_unique_integer_points(&rotated_points)
461 .iter()
462 .map(|integer_point| Point::new(integer_point.x, integer_point.y))
463 .collect::<Vec<Point<i32>>>();
464
465 if integer_points.len() == 1 {
466 let integer_point = integer_points.first().unwrap();
467
468 self.render_point(dvec2(integer_point.x as f64, integer_point.y as f64), color);
469 } else {
470 draw_polygon_mut(&mut self.image, &integer_points, srgba_to_rgba8(color));
471 }
472 }
473
474 fn render_rectangle_lines(
475 &mut self,
476 position: DVec2,
477 width: f64,
478 height: f64,
479 offset: DVec2,
480 rotation: f64,
481 thickness: f64,
482 color: Srgba,
483 ) {
484 let position = self.map_dvec2(position);
485 let width = self.map_value(width) - 1.0;
486 let height = self.map_value(height) - 1.0;
487 let thickness = self.map_value(thickness);
488
489 let base_points = self.get_base_points(position, width, height);
490 let offset_vec = self.get_offset_vec(width, height, offset);
491 let offset_points = self.get_offset_points(&base_points, offset_vec);
492 let rotated_points = self.get_rotated_points(&offset_points, position, rotation);
493
494 let integer_points = self
495 .get_unique_integer_points(&rotated_points)
496 .iter()
497 .map(|integer_point| Point::new(integer_point.x, integer_point.y))
498 .collect::<Vec<Point<i32>>>();
499
500 let min_x = integer_points
501 .iter()
502 .map(|integer_point| integer_point.x)
503 .min()
504 .unwrap();
505 let max_x = integer_points
506 .iter()
507 .map(|integer_point| integer_point.x)
508 .max()
509 .unwrap();
510
511 let min_y = integer_points
512 .iter()
513 .map(|integer_point| integer_point.y)
514 .min()
515 .unwrap();
516 let max_y = integer_points
517 .iter()
518 .map(|integer_point| integer_point.y)
519 .max()
520 .unwrap();
521
522 let min_vec = ivec2(min_x, min_y).as_dvec2();
523
524 let renderer_width = max_x - min_x + 1;
525 let renderer_height = max_y - min_y + 1;
526
527 let mut rectangle_renderer = ImageRenderer::new(
528 renderer_width as u32,
529 renderer_height as u32,
530 1.0,
531 DVec2::ZERO,
532 1,
533 self.font.clone(),
534 );
535
536 rectangle_renderer.render_rectangle(
537 position - min_vec,
538 width + 1.0,
539 height + 1.0,
540 offset,
541 rotation,
542 color,
543 );
544
545 let midpoint = rotated_points
546 .iter()
547 .copied()
548 .map(|rotated_point| rotated_point - min_vec)
549 .sum::<DVec2>()
550 / 4.0;
551
552 rectangle_renderer.render_rectangle(
553 midpoint,
554 width + 1.0 - 2.0 * thickness,
555 height + 1.0 - 2.0 * thickness,
556 DVec2::splat(0.5),
557 rotation,
558 Srgba::new(0.0, 0.0, 0.0, 0.0),
559 );
560
561 overlay(
562 &mut self.image,
563 &rectangle_renderer.render_image_onto(rectangle_renderer.transparent()),
564 min_x as i64,
565 min_y as i64,
566 );
567 }
568
569 fn render_equilateral_triangle(
570 &mut self,
571 position: DVec2,
572 radius: f64,
573 rotation: f64,
574 color: Srgba,
575 ) {
576 let position = self.map_dvec2(position);
577 let radius = self.map_value(radius);
578
579 let points = (0..3)
580 .map(|i| position + radius * DVec2::from_angle(i as f64 * 2.0 * PI / 3.0 + rotation))
581 .collect::<Vec<DVec2>>();
582
583 let integer_points = self
584 .get_unique_integer_points(&points)
585 .iter()
586 .map(|integer_point| Point::new(integer_point.x, integer_point.y))
587 .collect::<Vec<Point<i32>>>();
588
589 if integer_points.len() == 1 {
590 let integer_point = integer_points.first().unwrap();
591
592 self.render_point(dvec2(integer_point.x as f64, integer_point.y as f64), color);
593 } else {
594 draw_polygon_mut(&mut self.image, &integer_points, srgba_to_rgba8(color));
595 }
596 }
597
598 fn render_equilateral_triangle_lines(
599 &mut self,
600 position: DVec2,
601 radius: f64,
602 rotation: f64,
603 thickness: f64,
604 color: Srgba,
605 ) {
606 let position = self.map_dvec2(position);
607 let radius = self.map_value(radius);
608 let thickness = self.map_value(thickness);
609
610 let points = (0..3)
611 .map(|i| position + radius * DVec2::from_angle(i as f64 * 2.0 * PI / 3.0 + rotation))
612 .collect::<Vec<DVec2>>();
613
614 let integer_points = self
615 .get_unique_integer_points(&points)
616 .iter()
617 .map(|integer_point| Point::new(integer_point.x, integer_point.y))
618 .collect::<Vec<Point<i32>>>();
619
620 let min_x = integer_points
621 .iter()
622 .map(|integer_point| integer_point.x)
623 .min()
624 .expect("triangles have more than 0 points");
625 let max_x = integer_points
626 .iter()
627 .map(|integer_point| integer_point.x)
628 .max()
629 .expect("triangles have more than 0 points");
630 let min_y = integer_points
631 .iter()
632 .map(|integer_point| integer_point.y)
633 .min()
634 .expect("triangles have more than 0 points");
635 let max_y = integer_points
636 .iter()
637 .map(|integer_point| integer_point.y)
638 .max()
639 .expect("triangles have more than 0 points");
640
641 let min_point = ivec2(min_x, min_y);
642
643 let renderer_width = (max_x - min_x + 1) as u32;
644 let renderer_height = (max_y - min_y + 1) as u32;
645
646 let mut triangle_renderer = ImageRenderer::new(
647 renderer_width,
648 renderer_height,
649 1.0,
650 DVec2::ZERO,
651 1,
652 self.font.clone(),
653 );
654
655 triangle_renderer.render_equilateral_triangle(
656 (position - min_point.as_dvec2()).round(),
657 radius,
658 rotation,
659 color,
660 );
661
662 triangle_renderer.render_equilateral_triangle(
663 (position - min_point.as_dvec2()).round(),
664 radius - thickness,
665 rotation,
666 Srgba::new(0.0, 0.0, 0.0, 0.0),
667 );
668
669 overlay(
670 &mut self.image,
671 &triangle_renderer.render_image_onto(triangle_renderer.transparent()),
672 min_x as i64,
673 min_y as i64,
674 );
675 }
676}
677
678fn rotate_point_around(point: DVec2, axis: DVec2, theta: f64) -> DVec2 {
679 if theta == 0.0 {
680 return point;
681 }
682
683 let relative = point - axis;
684 let relative_theta = relative.to_angle();
685 let new_relative_theta = relative_theta + theta;
686 let new_relative = DVec2::from_angle(new_relative_theta) * relative.length();
687 new_relative + axis
688}