1use std::f64::consts::PI;
2
3use ab_glyph::FontArc;
4use anchor2d::{Anchor2D, HorizontalAnchor, VerticalAnchorContext, VerticalAnchorValue};
5use glam::{DVec2, IVec2, dvec2, ivec2};
6use image::{
7 Rgba, RgbaImage,
8 imageops::{FilterType, overlay, resize},
9};
10use imageproc::{
11 drawing::{draw_filled_circle_mut, draw_polygon_mut, draw_text_mut, text_size},
12 point::Point,
13};
14use palette::{num::Round, Srgba};
15
16use crate::Renderer;
17
18fn srgba_to_rgba8(color: Srgba) -> Rgba<u8> {
19 let red = (color.red * 255.0).round().clamp(0.0, 255.0) as u8;
20 let green = (color.green * 255.0).round().clamp(0.0, 255.0) as u8;
21 let blue = (color.blue * 255.0).round().clamp(0.0, 255.0) as u8;
22 let alpha = (color.alpha * 255.0).round().clamp(0.0, 255.0) as u8;
23 Rgba([red, green, blue, alpha])
24}
25
26#[derive(Clone)]
27pub struct ImageRenderer {
28 width: u32,
29 height: u32,
30 image: RgbaImage,
31 scale: f64,
32 scaling_target: DVec2,
33 supersampling: u32,
34 font: FontArc,
35}
36
37impl ImageRenderer {
38 pub fn new(
39 width: u32,
40 height: u32,
41 scale: f64,
42 scaling_target: DVec2,
43 supersampling: u32,
44 font: FontArc,
45 ) -> Self {
46 Self {
47 width,
48 height,
49 image: RgbaImage::new(width * supersampling, height * supersampling),
50 scale,
51 scaling_target,
52 supersampling,
53 font,
54 }
55 }
56
57 pub fn get_font(&self) -> &FontArc {
58 &self.font
59 }
60
61 pub fn set_font(&mut self, font: FontArc) {
62 self.font = font;
63 }
64
65 fn get_supersampled_width(&self) -> u32 {
66 self.width * self.supersampling
67 }
68
69 fn get_supersampled_height(&self) -> u32 {
70 self.height * self.supersampling
71 }
72
73 fn map_x(&self, x: f64) -> f64 {
74 let target_x = self.get_supersampled_width() as f64 * self.scaling_target.x;
75 (x * self.supersampling as f64 - target_x) * self.scale + target_x
76 }
77
78 fn map_y(&self, y: f64) -> f64 {
79 let target_y = self.get_supersampled_height() as f64 * self.scaling_target.y;
80 (y * self.supersampling as f64 - target_y) * self.scale + target_y
81 }
82
83 fn map_dvec2(&self, v: DVec2) -> DVec2 {
84 dvec2(self.map_x(v.x), self.map_y(v.y))
85 }
86
87 pub fn reset(&mut self) {
88 self.image = self.transparent();
89 }
90
91 pub fn get_image(&self) -> &RgbaImage {
92 &self.image
93 }
94
95 pub fn render_image_onto(&self, mut image: RgbaImage) -> RgbaImage {
96 overlay(&mut image, &self.image, 0, 0);
97
98 resize(&image, self.width, self.height, FilterType::Lanczos3)
99 }
100
101 pub fn transparent(&self) -> RgbaImage {
102 RgbaImage::new(
103 self.get_supersampled_width(),
104 self.get_supersampled_height(),
105 )
106 }
107
108 pub fn black(&self) -> RgbaImage {
109 RgbaImage::from_par_fn(
110 self.get_supersampled_width(),
111 self.get_supersampled_height(),
112 |_, _| Rgba([0, 0, 0, 255]),
113 )
114 }
115
116 fn red(&self) -> RgbaImage {
117 RgbaImage::from_par_fn(
118 self.get_supersampled_width(),
119 self.get_supersampled_height(),
120 |_, _| Rgba([255, 0, 0, 255]),
121 )
122 }
123}
124
125impl Renderer for ImageRenderer {
126 fn render_line(&mut self, start: DVec2, end: DVec2, thickness: f64, color: Srgba) {
127 let thickness = thickness * self.scale * self.supersampling as f64;
128
129 let offset = (thickness / 2.0).round();
130
131 let normal = DVec2::from_angle((end - start).to_angle() + PI / 2.0);
132
133 let mapped_start = self.map_dvec2(start);
134 let mapped_end = self.map_dvec2(end);
135
136 let p1 = mapped_start + normal * offset;
137 let p2 = mapped_start - normal * offset;
138 let p3 = mapped_end - normal * offset;
139 let p4 = mapped_end + normal * offset;
140
141 let mut points = vec![
142 Point::new(p1.x.round() as i32, p1.y.round() as i32),
143 Point::new(p2.x.round() as i32, p2.y.round() as i32),
144 Point::new(p3.x.round() as i32, p3.y.round() as i32),
145 Point::new(p4.x.round() as i32, p4.y.round() as i32),
146 ];
147
148 while points.len() > 1 && points.first().is_some_and(|first_point| {
149 points
150 .last()
151 .is_some_and(|last_point| first_point == last_point)
152 }) {
153 points.remove(points.len() - 1);
154 }
155
156 if points.len() == 1 {
157 let point = points.first().unwrap();
158
159 if point.x >= 0 && point.y >= 0 && point.x < self.image.width() as i32 && point.y < self.image.height() as i32 {
160 self.image.put_pixel(point.x as u32, point.y as u32, srgba_to_rgba8(color));
161 }
162 } else {
163 draw_polygon_mut(&mut self.image, &points, srgba_to_rgba8(color));
164 }
165
166 }
167
168 fn render_circle(&mut self, position: DVec2, radius: f64, color: Srgba) {
169 let position = self.map_dvec2(position).round().as_ivec2();
170 let radius = (radius * self.scale * self.supersampling as f64).round() as u32;
171
172 draw_filled_circle_mut(
173 &mut self.image,
174 position.into(),
175 radius as i32,
176 srgba_to_rgba8(color),
177 );
178 }
179
180 fn render_circle_lines(&mut self, position: DVec2, radius: f64, thickness: f64, color: Srgba) {
181 let position = self.map_dvec2(position).round().as_ivec2();
182 let radius = (radius * self.scale * self.supersampling as f64).round();
183 let thickness = (thickness * self.scale * self.supersampling as f64).round();
184
185 let mut circle_renderer = ImageRenderer::new(
186 2 * radius as u32 + 1,
187 2 * radius as u32 + 1,
188 self.scale,
189 self.scaling_target,
190 self.supersampling,
191 self.font.clone(),
192 );
193
194 circle_renderer.render_circle(
195 dvec2(radius, radius),
196 radius,
197 color,
198 );
199
200 circle_renderer.render_circle(
201 dvec2(radius, radius),
202 radius - thickness,
203 Srgba::new(0.0, 0.0, 0.0, 0.0),
204 );
205
206 overlay(
207 &mut self.image,
208 &circle_renderer.render_image_onto(circle_renderer.transparent()),
209 (position.x - radius as i32) as i64,
210 (position.y - radius as i32) as i64,
211 );
212 }
213
214 fn render_arc(
215 &mut self,
216 position: DVec2,
217 radius: f64,
218 _rotation: f64,
219 _arc: f64,
220 thickness: f64,
221 color: Srgba,
222 ) {
223 self.render_circle_lines(position, radius, thickness, color); }
225
226 fn render_text(
227 &mut self,
228 text: &str,
229 position: DVec2,
230 anchor: Anchor2D,
231 size: f64,
232 color: Srgba,
233 ) {
234 let position = self.map_dvec2(position);
235 let size = size * self.scale * self.supersampling as f64;
236
237 let (text_width, _) = text_size(size as f32, &self.font, text);
238
239 let x = match anchor.get_horizontal() {
240 HorizontalAnchor::Left => position.x,
241 HorizontalAnchor::Center => position.x - text_width as f64 / 2.0,
242 HorizontalAnchor::Right => position.x - text_width as f64,
243 };
244
245 let vertical_anchor = anchor.get_vertical();
246
247 let y = match (vertical_anchor.get_context(), vertical_anchor.get_value()) {
248 (VerticalAnchorContext::Graphics, VerticalAnchorValue::Bottom) => {
249 position.y - size / 1.25
250 }
251 (VerticalAnchorContext::Math, VerticalAnchorValue::Bottom) => position.y,
252 (_, VerticalAnchorValue::Center) => position.y - size / 1.25 / 2.0,
253 (VerticalAnchorContext::Graphics, VerticalAnchorValue::Top) => position.y,
254 (VerticalAnchorContext::Math, VerticalAnchorValue::Top) => position.y - size / 1.25,
255 };
256
257 draw_text_mut(
258 &mut self.image,
259 srgba_to_rgba8(color),
260 x as i32,
261 y as i32,
262 size as f32,
263 &self.font,
264 text,
265 );
266 }
267
268 fn render_rectangle(
269 &mut self,
270 position: DVec2,
271 width: f64,
272 height: f64,
273 offset: DVec2,
274 rotation: f64,
275 color: Srgba,
276 ) {
277 let width = (width - 1.0) * self.scale * self.supersampling as f64;
278 let height = (height - 1.0) * self.scale * self.supersampling as f64;
279
280 let calculated_offset = dvec2(
281 width * offset.x,
282 height * offset.y,
283 );
284
285 let position = self.map_dvec2(position) - calculated_offset;
286
287 let axis = dvec2(
288 position.x + calculated_offset.x, position.y + calculated_offset.y, );
291
292 let p1 = position; let p2 = p1 + DVec2::X * width; let p3 = p2 + DVec2::Y * height; let p4 = p3 - DVec2::X * width; let q1 = rotate_point_around(p1, axis, rotation); let q2 = rotate_point_around(p2, axis, rotation); let q3 = rotate_point_around(p3, axis, rotation); let q4 = rotate_point_around(p4, axis, rotation); let r1 = q1.round().as_ivec2(); let r2 = q2.round().as_ivec2(); let r3 = q3.round().as_ivec2(); let r4 = q4.round().as_ivec2(); let mut points = vec![
308 Point::new(r1.x, r1.y),
309 Point::new(r2.x, r2.y),
310 Point::new(r3.x, r3.y),
311 Point::new(r4.x, r4.y),
312 ];
313
314 while points.len() > 1 && points.first().is_some_and(|first_point| {
315 points
316 .last()
317 .is_some_and(|last_point| first_point == last_point)
318 }) {
319 points.remove(points.len() - 1);
320 }
321
322 if points.len() == 1 {
323 let point = points.first().unwrap();
324
325 if point.x >= 0 && point.y >= 0 && point.x < self.image.width() as i32 && point.y < self.image.height() as i32 {
326 self.image.put_pixel(point.x as u32, point.y as u32, srgba_to_rgba8(color));
327 }
328 } else {
329 draw_polygon_mut(&mut self.image, &points, srgba_to_rgba8(color));
330 }
331
332 }
333
334 fn render_rectangle_lines(
335 &mut self,
336 position: DVec2,
337 width: f64,
338 height: f64,
339 offset: DVec2,
340 rotation: f64,
341 thickness: f64,
342 color: Srgba,
343 ) {
344 let adjusted_position = self.map_dvec2(position);
345 let adjusted_width = (width - 1.0) * self.scale * self.supersampling as f64;
346 let adjusted_height = (height - 1.0) * self.scale * self.supersampling as f64;
347 let adjusted_thickness = thickness * self.scale * self.supersampling as f64;
348
349 let axis = dvec2(
350 adjusted_position.x + adjusted_width * offset.x, adjusted_position.y + adjusted_height * offset.y, );
353
354 let p1 = adjusted_position; let p2 = p1 + DVec2::X * adjusted_width; let p3 = p2 + DVec2::Y * adjusted_height; let p4 = p3 - DVec2::X * adjusted_width; let q1 = rotate_point_around(p1, axis, rotation); let q2 = rotate_point_around(p2, axis, rotation); let q3 = rotate_point_around(p3, axis, rotation); let q4 = rotate_point_around(p4, axis, rotation); let r1 = q1.round().as_ivec2(); let r2 = q2.round().as_ivec2(); let r3 = q3.round().as_ivec2(); let r4 = q4.round().as_ivec2(); let min_x = r1.x.min(r2.x).min(r3.x).min(r4.x);
370 let max_x = r1.x.max(r2.x).max(r3.x).max(r4.x);
371
372 let min_y = r1.y.min(r2.y).min(r3.y).min(r4.y);
373 let max_y = r1.y.max(r2.y).max(r3.y).max(r4.y);
374
375 let renderer_width = max_x - min_x + 1;
376 let renderer_height = max_y - min_y + 1;
377
378 let mut rectangle_renderer = ImageRenderer::new(
379 renderer_width as u32,
380 renderer_height as u32,
381 self.scale,
382 self.scaling_target,
383 self.supersampling,
384 self.font.clone(),
385 );
386
387 rectangle_renderer.render_rectangle(
388 dvec2(
389 (renderer_width as f64 / 2.0).floor(),
390 (renderer_height as f64 / 2.0).floor(),
391 ),
392 adjusted_width,
393 adjusted_height,
394 DVec2::splat(0.5),
395 rotation,
396 color,
397 );
398
399 rectangle_renderer.render_rectangle(
400 dvec2(
401 (renderer_width as f64 / 2.0).floor(),
402 (renderer_height as f64 / 2.0).floor(),
403 ),
404 adjusted_width - 2.0 * adjusted_thickness,
405 adjusted_height - 2.0 * adjusted_thickness,
406 DVec2::splat(0.5),
407 rotation,
408 Srgba::new(0.0, 0.0, 0.0, 0.0),
409 );
410
411 overlay(
412 &mut self.image,
413 &rectangle_renderer.render_image_onto(rectangle_renderer.transparent()),
414 (min_x as f64 - (adjusted_width * offset.x).floor()) as i64,
415 (min_y as f64 - (adjusted_height * offset.y).floor()) as i64,
416 );
417 }
418
419 fn render_equilateral_triangle(
420 &mut self,
421 position: DVec2,
422 radius: f64,
423 rotation: f64,
424 color: Srgba,
425 ) {
426 let points = (0..3)
427 .map(|i| position + radius * DVec2::from_angle(i as f64 * 2.0 * PI / 3.0 + rotation))
428 .collect::<Vec<DVec2>>();
429
430 let mut points = points
431 .into_iter()
432 .map(|point| Point::new(point.x.round() as i32, point.y.round() as i32))
433 .collect::<Vec<Point<i32>>>();
434
435 while points.len() > 1 && points.first().is_some_and(|first_point| {
436 points
437 .last()
438 .is_some_and(|last_point| first_point == last_point)
439 }) {
440 points.remove(points.len() - 1);
441 }
442
443 if points.len() == 1 {
444 let point = points.first().unwrap();
445
446 if point.x >= 0 && point.y >= 0 && point.x < self.image.width() as i32 && point.y < self.image.height() as i32 {
447 self.image.put_pixel(point.x as u32, point.y as u32, srgba_to_rgba8(color));
448 }
449 } else {
450 draw_polygon_mut(&mut self.image, &points, srgba_to_rgba8(color));
451 }
452 }
453
454 fn render_equilateral_triangle_lines(
455 &mut self,
456 position: DVec2,
457 radius: f64,
458 rotation: f64,
459 thickness: f64,
460 color: Srgba,
461 ) {
462 let points = (0..3)
463 .map(|i| position + radius * DVec2::from_angle(i as f64 * 2.0 * PI / 3.0 + rotation))
464 .collect::<Vec<DVec2>>();
465
466 let integer_points = points
467 .iter()
468 .map(|point| point.floor().as_ivec2())
469 .collect::<Vec<IVec2>>();
470
471 let min_x = integer_points
472 .iter()
473 .map(|integer_point| integer_point.x)
474 .min()
475 .expect("triangles have more than 0 points");
476 let max_x = integer_points
477 .iter()
478 .map(|integer_point| integer_point.x)
479 .max()
480 .expect("triangles have more than 0 points");
481 let min_y = integer_points
482 .iter()
483 .map(|integer_point| integer_point.y)
484 .min()
485 .expect("triangles have more than 0 points");
486 let max_y = integer_points
487 .iter()
488 .map(|integer_point| integer_point.y)
489 .max()
490 .expect("triangles have more than 0 points");
491
492 let min_point = ivec2(min_x, min_y);
493
494 let renderer_width = (max_x - min_x + 1) as u32;
495 let renderer_height = (max_y - min_y + 1) as u32;
496
497 let mut triangle_renderer = ImageRenderer::new(
498 renderer_width,
499 renderer_height,
500 self.scale,
501 self.scaling_target,
502 self.supersampling,
503 self.font.clone(),
504 );
505
506 triangle_renderer.render_equilateral_triangle(
507 (position - min_point.as_dvec2()).floor(),
508 radius,
509 rotation,
510 color,
511 );
512
513 triangle_renderer.render_equilateral_triangle(
514 (position - min_point.as_dvec2()).floor(),
515 radius - thickness,
516 rotation,
517 Srgba::new(0.0, 0.0, 0.0, 0.0),
518 );
519
520 overlay(
521 &mut self.image,
522 &triangle_renderer.render_image_onto(triangle_renderer.transparent()),
523 min_x as i64,
524 min_y as i64,
525 );
526 }
527}
528
529fn rotate_point_around(point: DVec2, axis: DVec2, theta: f64) -> DVec2 {
530 if theta == 0.0 {
531 return point;
532 }
533
534 let relative = point - axis;
535 let relative_theta = relative.to_angle();
536 let new_relative_theta = relative_theta + theta;
537 let new_relative = DVec2::from_angle(new_relative_theta) * relative.length();
538 new_relative + axis
539}