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.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 draw_polygon_mut(&mut self.image, &points, srgba_to_rgba8(color));
157 }
158
159 fn render_circle(&mut self, position: DVec2, radius: f64, color: Srgba) {
160 let position = self.map_dvec2(position).round().as_ivec2();
161 let radius = (radius * self.scale * self.supersampling as f64).round() as u32;
162
163 draw_filled_circle_mut(
164 &mut self.image,
165 position.into(),
166 radius as i32,
167 srgba_to_rgba8(color),
168 );
169 }
170
171 fn render_circle_lines(&mut self, position: DVec2, radius: f64, thickness: f64, color: Srgba) {
172 let position = self.map_dvec2(position).round().as_ivec2();
173 let radius = (radius * self.scale * self.supersampling as f64).round();
174 let thickness = (thickness * self.scale * self.supersampling as f64).round();
175
176 let mut circle_renderer = ImageRenderer::new(
177 2 * radius as u32 + 1,
178 2 * radius as u32 + 1,
179 self.scale,
180 self.scaling_target,
181 self.supersampling,
182 self.font.clone(),
183 );
184
185 circle_renderer.render_circle(
186 dvec2(radius, radius),
187 radius,
188 color,
189 );
190
191 circle_renderer.render_circle(
192 dvec2(radius, radius),
193 radius - thickness,
194 Srgba::new(0.0, 0.0, 0.0, 0.0),
195 );
196
197 overlay(
198 &mut self.image,
199 &circle_renderer.render_image_onto(circle_renderer.transparent()),
200 (position.x - radius as i32) as i64,
201 (position.y - radius as i32) as i64,
202 );
203 }
204
205 fn render_arc(
206 &mut self,
207 position: DVec2,
208 radius: f64,
209 _rotation: f64,
210 _arc: f64,
211 thickness: f64,
212 color: Srgba,
213 ) {
214 self.render_circle_lines(position, radius, thickness, color); }
216
217 fn render_text(
218 &mut self,
219 text: &str,
220 position: DVec2,
221 anchor: Anchor2D,
222 size: f64,
223 color: Srgba,
224 ) {
225 let position = self.map_dvec2(position);
226 let size = size * self.scale * self.supersampling as f64;
227
228 let (text_width, _) = text_size(size as f32, &self.font, text);
229
230 let x = match anchor.get_horizontal() {
231 HorizontalAnchor::Left => position.x,
232 HorizontalAnchor::Center => position.x - text_width as f64 / 2.0,
233 HorizontalAnchor::Right => position.x - text_width as f64,
234 };
235
236 let vertical_anchor = anchor.get_vertical();
237
238 let y = match (vertical_anchor.get_context(), vertical_anchor.get_value()) {
239 (VerticalAnchorContext::Graphics, VerticalAnchorValue::Bottom) => {
240 position.y - size / 1.25
241 }
242 (VerticalAnchorContext::Math, VerticalAnchorValue::Bottom) => position.y,
243 (_, VerticalAnchorValue::Center) => position.y - size / 1.25 / 2.0,
244 (VerticalAnchorContext::Graphics, VerticalAnchorValue::Top) => position.y,
245 (VerticalAnchorContext::Math, VerticalAnchorValue::Top) => position.y - size / 1.25,
246 };
247
248 draw_text_mut(
249 &mut self.image,
250 srgba_to_rgba8(color),
251 x as i32,
252 y as i32,
253 size as f32,
254 &self.font,
255 text,
256 );
257 }
258
259 fn render_rectangle(
260 &mut self,
261 position: DVec2,
262 width: f64,
263 height: f64,
264 offset: DVec2,
265 rotation: f64,
266 color: Srgba,
267 ) {
268 let width = (width - 1.0) * self.scale * self.supersampling as f64;
269 let height = (height - 1.0) * self.scale * self.supersampling as f64;
270
271 let calculated_offset = dvec2(
272 width * offset.x,
273 height * offset.y,
274 );
275
276 let position = self.map_dvec2(position) - calculated_offset;
277
278 let axis = dvec2(
279 position.x + calculated_offset.x, position.y + calculated_offset.y, );
282
283 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![
299 Point::new(r1.x, r1.y),
300 Point::new(r2.x, r2.y),
301 Point::new(r3.x, r3.y),
302 Point::new(r4.x, r4.y),
303 ];
304
305 while points.len() > 1 && points.first().is_some_and(|first_point| {
306 points
307 .last()
308 .is_some_and(|last_point| first_point == last_point)
309 }) {
310 points.remove(points.len() - 1);
311 }
312
313 if points.len() == 1 {
314 let point = points.first().unwrap();
315
316 if point.x >= 0 && point.y >= 0 {
317 self.image.put_pixel(point.x as u32, point.y as u32, srgba_to_rgba8(color));
318 }
319 } else {
320 draw_polygon_mut(&mut self.image, &points, srgba_to_rgba8(color));
321 }
322
323 }
324
325 fn render_rectangle_lines(
326 &mut self,
327 position: DVec2,
328 width: f64,
329 height: f64,
330 offset: DVec2,
331 rotation: f64,
332 thickness: f64,
333 color: Srgba,
334 ) {
335 let adjusted_position = self.map_dvec2(position);
336 let adjusted_width = (width - 1.0) * self.scale * self.supersampling as f64;
337 let adjusted_height = (height - 1.0) * self.scale * self.supersampling as f64;
338
339 let axis = dvec2(
340 adjusted_position.x + adjusted_width * offset.x, adjusted_position.y + adjusted_height * offset.y, );
343
344 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);
360 let max_x = r1.x.max(r2.x).max(r3.x).max(r4.x);
361
362 let min_y = r1.y.min(r2.y).min(r3.y).min(r4.y);
363 let max_y = r1.y.max(r2.y).max(r3.y).max(r4.y);
364
365 let renderer_width = max_x - min_x + 1;
366 let renderer_height = max_y - min_y + 1;
367
368 let mut rectangle_renderer = ImageRenderer::new(
369 renderer_width as u32,
370 renderer_height as u32,
371 self.scale,
372 self.scaling_target,
373 self.supersampling,
374 self.font.clone(),
375 );
376
377 rectangle_renderer.render_rectangle(
378 dvec2(
379 (renderer_width as f64 / 2.0).floor(),
380 (renderer_height as f64 / 2.0).floor(),
381 ),
382 width,
383 height,
384 DVec2::splat(0.5),
385 rotation,
386 color,
387 );
388
389 rectangle_renderer.render_rectangle(
390 dvec2(
391 (renderer_width as f64 / 2.0).floor(),
392 (renderer_height as f64 / 2.0).floor(),
393 ),
394 width - 2.0 * thickness,
395 height - 2.0 * thickness,
396 DVec2::splat(0.5),
397 rotation,
398 Srgba::new(0.0, 0.0, 0.0, 0.0),
399 );
400
401 overlay(
402 &mut self.image,
403 &rectangle_renderer.render_image_onto(rectangle_renderer.transparent()),
404 (min_x as f64 - (adjusted_width * offset.x).floor()) as i64,
405 (min_y as f64 - (adjusted_height * offset.y).floor()) as i64,
406 );
407 }
408
409 fn render_equilateral_triangle(
410 &mut self,
411 position: DVec2,
412 radius: f64,
413 rotation: f64,
414 color: Srgba,
415 ) {
416 let points = (0..3)
417 .map(|i| position + radius * DVec2::from_angle(i as f64 * 2.0 * PI / 3.0 + rotation))
418 .collect::<Vec<DVec2>>();
419
420 let mut points = points
421 .into_iter()
422 .map(|point| Point::new(point.x.round() as i32, point.y.round() as i32))
423 .collect::<Vec<Point<i32>>>();
424
425 while points.first().is_some_and(|first_point| {
426 points
427 .last()
428 .is_some_and(|last_point| first_point == last_point)
429 }) {
430 points.remove(points.len() - 1);
431 }
432
433 draw_polygon_mut(&mut self.image, &points, srgba_to_rgba8(color));
434 }
435
436 fn render_equilateral_triangle_lines(
437 &mut self,
438 position: DVec2,
439 radius: f64,
440 rotation: f64,
441 thickness: f64,
442 color: Srgba,
443 ) {
444 let points = (0..3)
445 .map(|i| position + radius * DVec2::from_angle(i as f64 * 2.0 * PI / 3.0 + rotation))
446 .collect::<Vec<DVec2>>();
447
448 let integer_points = points
449 .iter()
450 .map(|point| point.floor().as_ivec2())
451 .collect::<Vec<IVec2>>();
452
453 let min_x = integer_points
454 .iter()
455 .map(|integer_point| integer_point.x)
456 .min()
457 .expect("triangles have more than 0 points");
458 let max_x = integer_points
459 .iter()
460 .map(|integer_point| integer_point.x)
461 .max()
462 .expect("triangles have more than 0 points");
463 let min_y = integer_points
464 .iter()
465 .map(|integer_point| integer_point.y)
466 .min()
467 .expect("triangles have more than 0 points");
468 let max_y = integer_points
469 .iter()
470 .map(|integer_point| integer_point.y)
471 .max()
472 .expect("triangles have more than 0 points");
473
474 let min_point = ivec2(min_x, min_y);
475
476 let renderer_width = (max_x - min_x + 1) as u32;
477 let renderer_height = (max_y - min_y + 1) as u32;
478
479 let mut triangle_renderer = ImageRenderer::new(
480 renderer_width,
481 renderer_height,
482 self.scale,
483 self.scaling_target,
484 self.supersampling,
485 self.font.clone(),
486 );
487
488 triangle_renderer.render_equilateral_triangle(
489 (position - min_point.as_dvec2()).floor(),
490 radius,
491 rotation,
492 color,
493 );
494
495 triangle_renderer.render_equilateral_triangle(
496 (position - min_point.as_dvec2()).floor(),
497 radius - thickness,
498 rotation,
499 Srgba::new(0.0, 0.0, 0.0, 0.0),
500 );
501
502 overlay(
503 &mut self.image,
504 &triangle_renderer.render_image_onto(triangle_renderer.transparent()),
505 min_x as i64,
506 min_y as i64,
507 );
508 }
509}
510
511fn rotate_point_around(point: DVec2, axis: DVec2, theta: f64) -> DVec2 {
512 if theta == 0.0 {
513 return point;
514 }
515
516 let relative = point - axis;
517 let relative_theta = relative.to_angle();
518 let new_relative_theta = relative_theta + theta;
519 let new_relative = DVec2::from_angle(new_relative_theta) * relative.length();
520 new_relative + axis
521}