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::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
117impl Renderer for ImageRenderer {
118 fn render_line(&mut self, start: DVec2, end: DVec2, thickness: f64, color: Srgba) {
119 let thickness = thickness * self.scale * self.supersampling as f64;
120
121 let offset = (thickness / 2.0).round();
122
123 let normal = DVec2::from_angle((end - start).to_angle() + PI / 2.0);
124
125 let mapped_start = self.map_dvec2(start);
126 let mapped_end = self.map_dvec2(end);
127
128 let p1 = mapped_start + normal * offset;
129 let p2 = mapped_start - normal * offset;
130 let p3 = mapped_end - normal * offset;
131 let p4 = mapped_end + normal * offset;
132
133 let mut points = vec![
134 Point::new(p1.x.round() as i32, p1.y.round() as i32),
135 Point::new(p2.x.round() as i32, p2.y.round() as i32),
136 Point::new(p3.x.round() as i32, p3.y.round() as i32),
137 Point::new(p4.x.round() as i32, p4.y.round() as i32),
138 ];
139
140 while points.first().is_some_and(|first_point| {
141 points
142 .last()
143 .is_some_and(|last_point| first_point == last_point)
144 }) {
145 points.remove(points.len() - 1);
146 }
147
148 draw_polygon_mut(&mut self.image, &points, srgba_to_rgba8(color));
149 }
150
151 fn render_circle(&mut self, position: DVec2, radius: f64, color: Srgba) {
152 let position = self.map_dvec2(position).round().as_ivec2();
153 let radius = (radius * self.scale * self.supersampling as f64).round() as u32;
154
155 draw_filled_circle_mut(
156 &mut self.image,
157 position.into(),
158 radius as i32,
159 srgba_to_rgba8(color),
160 );
161 }
162
163 fn render_circle_lines(&mut self, position: DVec2, radius: f64, thickness: f64, color: Srgba) {
164 let position = self.map_dvec2(position).round().as_ivec2();
165 let radius = (radius * self.scale * self.supersampling as f64).round() as u32;
166 let thickness = (thickness * self.scale * self.supersampling as f64).round() as u32;
167
168 let mut circle_image = RgbaImage::new(2 * radius + 1, 2 * radius + 1);
169
170 draw_filled_circle_mut(
171 &mut circle_image,
172 (radius as i32, radius as i32),
173 radius as i32,
174 srgba_to_rgba8(color),
175 );
176
177 draw_filled_circle_mut(
178 &mut circle_image,
179 (radius as i32, radius as i32),
180 radius as i32 - thickness as i32,
181 Rgba([0, 0, 0, 0]),
182 );
183
184 overlay(
185 &mut self.image,
186 &circle_image,
187 (position.x - radius as i32) as i64,
188 (position.y - radius as i32) as i64,
189 );
190 }
191
192 fn render_arc(
193 &mut self,
194 position: DVec2,
195 radius: f64,
196 _rotation: f64,
197 _arc: f64,
198 thickness: f64,
199 color: Srgba,
200 ) {
201 self.render_circle_lines(position, radius, thickness, color); }
203
204 fn render_text(
205 &mut self,
206 text: &str,
207 position: DVec2,
208 anchor: Anchor2D,
209 size: f64,
210 color: Srgba,
211 ) {
212 let position = self.map_dvec2(position);
213 let size = size * self.scale * self.supersampling as f64;
214
215 let (text_width, _) = text_size(size as f32, &self.font, text);
216
217 let x = match anchor.get_horizontal() {
218 HorizontalAnchor::Left => position.x,
219 HorizontalAnchor::Center => position.x - text_width as f64 / 2.0,
220 HorizontalAnchor::Right => position.x - text_width as f64,
221 };
222
223 let vertical_anchor = anchor.get_vertical();
224
225 let y = match (vertical_anchor.get_context(), vertical_anchor.get_value()) {
226 (VerticalAnchorContext::Graphics, VerticalAnchorValue::Bottom) => {
227 position.y - size / 1.25
228 }
229 (VerticalAnchorContext::Math, VerticalAnchorValue::Bottom) => position.y,
230 (_, VerticalAnchorValue::Center) => position.y - size / 1.25 / 2.0,
231 (VerticalAnchorContext::Graphics, VerticalAnchorValue::Top) => position.y,
232 (VerticalAnchorContext::Math, VerticalAnchorValue::Top) => position.y - size / 1.25,
233 };
234
235 draw_text_mut(
236 &mut self.image,
237 srgba_to_rgba8(color),
238 x as i32,
239 y as i32,
240 size as f32,
241 &self.font,
242 text,
243 );
244 }
245
246 fn render_rectangle(
247 &mut self,
248 position: DVec2,
249 width: f64,
250 height: f64,
251 offset: DVec2,
252 rotation: f64,
253 color: Srgba,
254 ) {
255 let position = self.map_dvec2(position);
256 let width = width * self.scale * self.supersampling as f64;
257 let height = height * self.scale * self.supersampling as f64;
258
259 let absolute_offset_x = width * offset.x;
260 let absolute_offset_y = height * offset.y;
261
262 let absolute_offset = dvec2(absolute_offset_x, absolute_offset_y);
263
264 let p1 = -absolute_offset;
265 let p2 = p1 + DVec2::X * width;
266 let p3 = p2 + DVec2::Y * height;
267 let p4 = p1 + DVec2::Y * height;
268
269 let theta1 = p1.to_angle();
270 let theta2 = p2.to_angle();
271 let theta3 = p3.to_angle();
272 let theta4 = p4.to_angle();
273
274 let q1 = position + DVec2::from_angle(theta1 + rotation) * p1.length();
275 let q2 = position + DVec2::from_angle(theta2 + rotation) * p2.length();
276 let q3 = position + DVec2::from_angle(theta3 + rotation) * p3.length();
277 let q4 = position + DVec2::from_angle(theta4 + rotation) * p4.length();
278
279 let mut points = vec![
280 Point::new(q1.x.round() as i32, q1.y.round() as i32),
281 Point::new(q2.x.round() as i32, q2.y.round() as i32),
282 Point::new(q3.x.round() as i32, q3.y.round() as i32),
283 Point::new(q4.x.round() as i32, q4.y.round() as i32),
284 ];
285
286 while points.first().is_some_and(|first_point| {
287 points
288 .last()
289 .is_some_and(|last_point| first_point == last_point)
290 }) {
291 points.remove(points.len() - 1);
292 }
293
294 draw_polygon_mut(&mut self.image, &points, srgba_to_rgba8(color));
295 }
296
297 fn render_rectangle_lines(
298 &mut self,
299 position: DVec2,
300 width: f64,
301 height: f64,
302 offset: DVec2,
303 rotation: f64,
304 thickness: f64,
305 color: Srgba,
306 ) {
307 let position = self.map_dvec2(position);
308 let width = (width * self.scale * self.supersampling as f64).round();
309 let height = (height * self.scale * self.supersampling as f64).round();
310 let thickness = (thickness * self.scale * self.supersampling as f64).round() as u32;
311
312 let absolute_offset_x = width * offset.x;
313 let absolute_offset_y = height * offset.y;
314
315 let absolute_offset = dvec2(absolute_offset_x, absolute_offset_y);
316
317 let p1 = -absolute_offset;
318 let p2 = p1 + DVec2::X * width;
319 let p3 = p2 + DVec2::Y * height;
320 let p4 = p1 + DVec2::Y * height;
321
322 let theta1 = p1.to_angle();
323 let theta2 = p2.to_angle();
324 let theta3 = p3.to_angle();
325 let theta4 = p4.to_angle();
326
327 let q1 = position + DVec2::from_angle(theta1 + rotation) * p1.length();
328 let q2 = position + DVec2::from_angle(theta2 + rotation) * p2.length();
329 let q3 = position + DVec2::from_angle(theta3 + rotation) * p3.length();
330 let q4 = position + DVec2::from_angle(theta4 + rotation) * p4.length();
331
332 let r1 = q1.round().as_ivec2();
333 let r2 = q2.round().as_ivec2();
334 let r3 = q3.round().as_ivec2();
335 let r4 = q4.round().as_ivec2();
336
337 let min_x = r1.x.min(r2.x).min(r3.x).min(r4.x);
338 let max_x = r1.x.max(r2.x).max(r3.x).max(r4.x);
339
340 let min_y = r1.y.min(r2.y).min(r3.y).min(r4.y);
341 let max_y = r1.y.max(r2.y).max(r3.y).max(r4.y);
342
343 let renderer_width = (max_x - min_x + 1) as u32;
344 let renderer_height = (max_y - min_y + 1) as u32;
345
346 let mut rectangle_renderer = ImageRenderer::new(
347 renderer_width,
348 renderer_height,
349 self.scale,
350 self.scaling_target,
351 self.supersampling,
352 self.font.clone(),
353 );
354
355 rectangle_renderer.render_rectangle(
356 dvec2(
357 (renderer_width as f64 / 2.0).floor(),
358 (renderer_height as f64 / 2.0).floor(),
359 ),
360 width,
361 height,
362 offset,
363 rotation,
364 color,
365 );
366
367 rectangle_renderer.render_rectangle(
368 dvec2(
369 (renderer_width as f64 / 2.0).floor(),
370 (renderer_height as f64 / 2.0).floor(),
371 ),
372 width - 2.0 * thickness as f64,
373 height - 2.0 * thickness as f64,
374 offset,
375 rotation,
376 Srgba::new(0.0, 0.0, 0.0, 0.0),
377 );
378
379 overlay(
380 &mut self.image,
381 &rectangle_renderer.image,
382 (position.x - renderer_width as f64 / 2.0).floor() as i64,
383 (position.y - renderer_height as f64 / 2.0).floor() as i64,
384 );
385 }
386
387 fn render_equilateral_triangle(
388 &mut self,
389 position: DVec2,
390 radius: f64,
391 rotation: f64,
392 color: Srgba,
393 ) {
394 let points = (0..3)
395 .map(|i| position + radius * DVec2::from_angle(i as f64 * 2.0 * PI / 3.0 + rotation))
396 .collect::<Vec<DVec2>>();
397
398 let mut points = points
399 .into_iter()
400 .map(|point| Point::new(point.x.round() as i32, point.y.round() as i32))
401 .collect::<Vec<Point<i32>>>();
402
403 while points.first().is_some_and(|first_point| {
404 points
405 .last()
406 .is_some_and(|last_point| first_point == last_point)
407 }) {
408 points.remove(points.len() - 1);
409 }
410
411 draw_polygon_mut(&mut self.image, &points, srgba_to_rgba8(color));
412 }
413
414 fn render_equilateral_triangle_lines(
415 &mut self,
416 position: DVec2,
417 radius: f64,
418 rotation: f64,
419 thickness: f64,
420 color: Srgba,
421 ) {
422 let points = (0..3)
423 .map(|i| position + radius * DVec2::from_angle(i as f64 * 2.0 * PI / 3.0 + rotation))
424 .collect::<Vec<DVec2>>();
425
426 let integer_points = points
427 .iter()
428 .map(|point| point.floor().as_ivec2())
429 .collect::<Vec<IVec2>>();
430
431 let min_x = integer_points
432 .iter()
433 .map(|integer_point| integer_point.x)
434 .min()
435 .expect("triangles have more than 0 points");
436 let max_x = integer_points
437 .iter()
438 .map(|integer_point| integer_point.x)
439 .max()
440 .expect("triangles have more than 0 points");
441 let min_y = integer_points
442 .iter()
443 .map(|integer_point| integer_point.y)
444 .min()
445 .expect("triangles have more than 0 points");
446 let max_y = integer_points
447 .iter()
448 .map(|integer_point| integer_point.y)
449 .max()
450 .expect("triangles have more than 0 points");
451
452 let min_point = ivec2(min_x, min_y);
453
454 let renderer_width = (max_x - min_x + 1) as u32;
455 let renderer_height = (max_y - min_y + 1) as u32;
456
457 let mut triangle_renderer = ImageRenderer::new(
458 renderer_width,
459 renderer_height,
460 self.scale,
461 self.scaling_target,
462 self.supersampling,
463 self.font.clone(),
464 );
465
466 triangle_renderer.render_equilateral_triangle(
467 (position - min_point.as_dvec2()).floor(),
468 radius,
469 rotation,
470 color,
471 );
472
473 triangle_renderer.render_equilateral_triangle(
474 (position - min_point.as_dvec2()).floor(),
475 radius - thickness,
476 rotation,
477 Srgba::new(0.0, 0.0, 0.0, 0.0),
478 );
479
480 overlay(
481 &mut self.image,
482 &triangle_renderer.image,
483 min_x as i64,
484 min_y as i64,
485 );
486 }
487}