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