1use std::f64::consts::PI;
2
3use ab_glyph::FontVec;
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, PartialEq)]
27pub struct ImageRenderer {
28 width: u32,
29 height: u32,
30 image: RgbaImage,
31 scale: f64,
32 scaling_target: DVec2,
33 supersampling: u32,
34}
35
36impl ImageRenderer {
37 pub fn new(
38 width: u32,
39 height: u32,
40 scale: f64,
41 scaling_target: DVec2,
42 supersampling: u32,
43 ) -> Self {
44 Self {
45 width,
46 height,
47 image: RgbaImage::new(width * supersampling, height * supersampling),
48 scale,
49 scaling_target,
50 supersampling,
51 }
52 }
53
54 fn get_supersampled_width(&self) -> u32 {
55 self.width * self.supersampling
56 }
57
58 fn get_supersampled_height(&self) -> u32 {
59 self.height * self.supersampling
60 }
61
62 fn map_x(&self, x: f64) -> f64 {
63 let target_x = self.get_supersampled_width() as f64 * self.scaling_target.x;
64 (x * self.supersampling as f64 - target_x) * self.scale + target_x
65 }
66
67 fn map_y(&self, y: f64) -> f64 {
68 let target_y = self.get_supersampled_height() as f64 * self.scaling_target.y;
69 (y * self.supersampling as f64 - target_y) * self.scale + target_y
70 }
71
72 fn map_dvec2(&self, v: DVec2) -> DVec2 {
73 dvec2(self.map_x(v.x), self.map_y(v.y))
74 }
75
76 pub fn reset(&mut self) {
77 self.image = self.transparent();
78 }
79
80 pub fn get_image(&self) -> RgbaImage {
81 let mut image = self.black();
82
83 overlay(&mut image, &self.image, 0, 0);
84
85 resize(&image, self.width, self.height, FilterType::Lanczos3)
86 }
87
88 fn transparent(&self) -> RgbaImage {
89 RgbaImage::new(
90 self.get_supersampled_width(),
91 self.get_supersampled_height(),
92 )
93 }
94
95 fn black(&self) -> RgbaImage {
96 RgbaImage::from_par_fn(
97 self.get_supersampled_width(),
98 self.get_supersampled_height(),
99 |_, _| Rgba([0, 0, 0, 255]),
100 )
101 }
102}
103
104impl Renderer for ImageRenderer {
105 type Font = FontVec;
106
107 fn render_line(&mut self, start: DVec2, end: DVec2, thickness: f64, color: Srgba) {
108 let thickness = thickness * self.scale * self.supersampling as f64;
109
110 let offset = (thickness / 2.0).round();
111
112 let normal = DVec2::from_angle((end - start).to_angle() + PI / 2.0);
113
114 let mapped_start = self.map_dvec2(start);
115 let mapped_end = self.map_dvec2(end);
116
117 let p1 = mapped_start + normal * offset;
118 let p2 = mapped_start - normal * offset;
119 let p3 = mapped_end - normal * offset;
120 let p4 = mapped_end + normal * offset;
121
122 let points = vec![
123 Point::new(p1.x.round() as i32, p1.y.round() as i32),
124 Point::new(p2.x.round() as i32, p2.y.round() as i32),
125 Point::new(p3.x.round() as i32, p3.y.round() as i32),
126 Point::new(p4.x.round() as i32, p4.y.round() as i32),
127 ];
128
129 draw_polygon_mut(&mut self.image, &points, srgba_to_rgba8(color));
130 }
131
132 fn render_circle(&mut self, position: DVec2, radius: f64, color: Srgba) {
133 let position = self.map_dvec2(position).round().as_ivec2();
134 let radius = (radius * self.scale * self.supersampling as f64).round() as u32;
135
136 draw_filled_circle_mut(
137 &mut self.image,
138 position.into(),
139 radius as i32,
140 srgba_to_rgba8(color),
141 );
142 }
143
144 fn render_circle_lines(&mut self, position: DVec2, radius: f64, thickness: f64, color: Srgba) {
145 let position = self.map_dvec2(position).round().as_ivec2();
146 let radius = (radius * self.scale * self.supersampling as f64).round() as u32;
147 let thickness = (thickness * self.scale * self.supersampling as f64).round() as u32;
148
149 let mut circle_image = RgbaImage::new(2 * radius + 1, 2 * radius + 1);
150
151 draw_filled_circle_mut(
152 &mut circle_image,
153 (radius as i32, radius as i32),
154 radius as i32,
155 srgba_to_rgba8(color),
156 );
157
158 draw_filled_circle_mut(
159 &mut circle_image,
160 (radius as i32, radius as i32),
161 radius as i32 - thickness as i32,
162 Rgba([0, 0, 0, 0]),
163 );
164
165 overlay(
166 &mut self.image,
167 &circle_image,
168 (position.x - radius as i32) as i64,
169 (position.y - radius as i32) as i64,
170 );
171 }
172
173 fn render_arc(
174 &mut self,
175 position: DVec2,
176 radius: f64,
177 _rotation: f64,
178 _arc: f64,
179 thickness: f64,
180 color: Srgba,
181 ) {
182 self.render_circle_lines(position, radius, thickness, color); }
184
185 fn render_text(
186 &mut self,
187 text: &str,
188 position: DVec2,
189 anchor: Anchor2D,
190 size: f64,
191 color: Srgba,
192 font: Self::Font,
193 ) {
194 let position = self.map_dvec2(position);
195 let size = size * self.scale * self.supersampling as f64;
196
197 let (text_width, _) = text_size(size as f32, &font, text);
198
199 let x = match anchor.get_horizontal() {
200 HorizontalAnchor::Left => position.x,
201 HorizontalAnchor::Center => position.x - text_width as f64 / 2.0,
202 HorizontalAnchor::Right => position.x - text_width as f64,
203 };
204
205 let vertical_anchor = anchor.get_vertical();
206
207 let y = match (vertical_anchor.get_context(), vertical_anchor.get_value()) {
208 (VerticalAnchorContext::Graphics, VerticalAnchorValue::Bottom) => {
209 position.y - size / 1.25
210 }
211 (VerticalAnchorContext::Math, VerticalAnchorValue::Bottom) => position.y,
212 (_, VerticalAnchorValue::Center) => position.y - size / 1.25 / 2.0,
213 (VerticalAnchorContext::Graphics, VerticalAnchorValue::Top) => position.y,
214 (VerticalAnchorContext::Math, VerticalAnchorValue::Top) => position.y - size / 1.25,
215 };
216
217 draw_text_mut(
218 &mut self.image,
219 srgba_to_rgba8(color),
220 x as i32,
221 y as i32,
222 size as f32,
223 &font,
224 text,
225 );
226 }
227
228 fn render_rectangle(
229 &mut self,
230 position: DVec2,
231 width: f64,
232 height: f64,
233 offset: DVec2,
234 rotation: f64,
235 color: Srgba,
236 ) {
237 let position = self.map_dvec2(position);
238 let width = width * self.scale * self.supersampling as f64;
239 let height = height * self.scale * self.supersampling as f64;
240
241 let absolute_offset_x = width * offset.x;
242 let absolute_offset_y = height * offset.y;
243
244 let absolute_offset = dvec2(absolute_offset_x, absolute_offset_y);
245
246 let p1 = -absolute_offset;
247 let p2 = p1 + DVec2::X * width;
248 let p3 = p2 + DVec2::Y * height;
249 let p4 = p1 + DVec2::Y * height;
250
251 let theta1 = p1.to_angle();
252 let theta2 = p2.to_angle();
253 let theta3 = p3.to_angle();
254 let theta4 = p4.to_angle();
255
256 let q1 = position + DVec2::from_angle(theta1 + rotation) * p1.length();
257 let q2 = position + DVec2::from_angle(theta2 + rotation) * p2.length();
258 let q3 = position + DVec2::from_angle(theta3 + rotation) * p3.length();
259 let q4 = position + DVec2::from_angle(theta4 + rotation) * p4.length();
260
261 draw_polygon_mut(
262 &mut self.image,
263 &[
264 Point::new(q1.x.round() as i32, q1.y.round() as i32),
265 Point::new(q2.x.round() as i32, q2.y.round() as i32),
266 Point::new(q3.x.round() as i32, q3.y.round() as i32),
267 Point::new(q4.x.round() as i32, q4.y.round() as i32),
268 ],
269 srgba_to_rgba8(color),
270 );
271 }
272
273 fn render_rectangle_lines(
274 &mut self,
275 position: DVec2,
276 width: f64,
277 height: f64,
278 offset: DVec2,
279 rotation: f64,
280 thickness: f64,
281 color: Srgba,
282 ) {
283 let position = self.map_dvec2(position);
284 let width = (width * self.scale * self.supersampling as f64).round();
285 let height = (height * self.scale * self.supersampling as f64).round();
286 let thickness = (thickness * self.scale * self.supersampling as f64).round() as u32;
287
288 let absolute_offset_x = width * offset.x;
289 let absolute_offset_y = height * offset.y;
290
291 let absolute_offset = dvec2(absolute_offset_x, absolute_offset_y);
292
293 let p1 = -absolute_offset;
294 let p2 = p1 + DVec2::X * width;
295 let p3 = p2 + DVec2::Y * height;
296 let p4 = p1 + DVec2::Y * height;
297
298 let theta1 = p1.to_angle();
299 let theta2 = p2.to_angle();
300 let theta3 = p3.to_angle();
301 let theta4 = p4.to_angle();
302
303 let q1 = position + DVec2::from_angle(theta1 + rotation) * p1.length();
304 let q2 = position + DVec2::from_angle(theta2 + rotation) * p2.length();
305 let q3 = position + DVec2::from_angle(theta3 + rotation) * p3.length();
306 let q4 = position + DVec2::from_angle(theta4 + rotation) * p4.length();
307
308 let r1 = q1.round().as_ivec2();
309 let r2 = q2.round().as_ivec2();
310 let r3 = q3.round().as_ivec2();
311 let r4 = q4.round().as_ivec2();
312
313 let min_x = r1.x.min(r2.x).min(r3.x).min(r4.x);
314 let max_x = r1.x.max(r2.x).max(r3.x).max(r4.x);
315
316 let min_y = r1.y.min(r2.y).min(r3.y).min(r4.y);
317 let max_y = r1.y.max(r2.y).max(r3.y).max(r4.y);
318
319 let renderer_width = (max_x - min_x + 1) as u32;
320 let renderer_height = (max_y - min_y + 1) as u32;
321
322 let mut rectangle_renderer = ImageRenderer::new(
323 renderer_width,
324 renderer_height,
325 self.scale,
326 self.scaling_target,
327 self.supersampling,
328 );
329
330 rectangle_renderer.render_rectangle(
331 dvec2(
332 (renderer_width as f64 / 2.0).floor(),
333 (renderer_height as f64 / 2.0).floor(),
334 ),
335 width,
336 height,
337 offset,
338 rotation,
339 color,
340 );
341
342 rectangle_renderer.render_rectangle(
343 dvec2(
344 (renderer_width as f64 / 2.0).floor(),
345 (renderer_height as f64 / 2.0).floor(),
346 ),
347 width - 2.0 * thickness as f64,
348 height - 2.0 * thickness as f64,
349 offset,
350 rotation,
351 Srgba::new(0.0, 0.0, 0.0, 0.0),
352 );
353
354 overlay(
355 &mut self.image,
356 &rectangle_renderer.image,
357 (position.x - renderer_width as f64 / 2.0).floor() as i64,
358 (position.y - renderer_height as f64 / 2.0).floor() as i64,
359 );
360 }
361
362 fn render_equilateral_triangle(
363 &mut self,
364 position: DVec2,
365 radius: f64,
366 rotation: f64,
367 color: Srgba,
368 ) {
369 let points = (0..3)
370 .map(|i| position + radius * DVec2::from_angle(i as f64 * 2.0 * PI / 3.0 + rotation))
371 .collect::<Vec<DVec2>>();
372
373 draw_polygon_mut(
374 &mut self.image,
375 &points
376 .iter()
377 .map(|point| Point::new(point.x.round() as i32, point.y.round() as i32))
378 .collect::<Vec<Point<i32>>>(),
379 srgba_to_rgba8(color),
380 );
381 }
382
383 fn render_equilateral_triangle_lines(
384 &mut self,
385 position: DVec2,
386 radius: f64,
387 rotation: f64,
388 thickness: f64,
389 color: Srgba,
390 ) {
391 let points = (0..3)
392 .map(|i| position + radius * DVec2::from_angle(i as f64 * 2.0 * PI / 3.0 + rotation))
393 .collect::<Vec<DVec2>>();
394
395 let integer_points = points
396 .iter()
397 .map(|point| point.floor().as_ivec2())
398 .collect::<Vec<IVec2>>();
399
400 let min_x = integer_points
401 .iter()
402 .map(|integer_point| integer_point.x)
403 .min()
404 .expect("triangles have more than 0 points");
405 let max_x = integer_points
406 .iter()
407 .map(|integer_point| integer_point.x)
408 .max()
409 .expect("triangles have more than 0 points");
410 let min_y = integer_points
411 .iter()
412 .map(|integer_point| integer_point.y)
413 .min()
414 .expect("triangles have more than 0 points");
415 let max_y = integer_points
416 .iter()
417 .map(|integer_point| integer_point.y)
418 .max()
419 .expect("triangles have more than 0 points");
420
421 let min_point = ivec2(min_x, min_y);
422
423 let renderer_width = (max_x - min_x + 1) as u32;
424 let renderer_height = (max_y - min_y + 1) as u32;
425
426 let mut triangle_renderer = ImageRenderer::new(
427 renderer_width,
428 renderer_height,
429 self.scale,
430 self.scaling_target,
431 self.supersampling,
432 );
433
434 triangle_renderer.render_equilateral_triangle(
435 (position - min_point.as_dvec2()).floor(),
436 radius,
437 rotation,
438 color,
439 );
440
441 triangle_renderer.render_equilateral_triangle(
442 (position - min_point.as_dvec2()).floor(),
443 radius - thickness,
444 rotation,
445 Srgba::new(0.0, 0.0, 0.0, 0.0),
446 );
447
448 overlay(
449 &mut self.image,
450 &triangle_renderer.image,
451 min_x as i64,
452 min_y as i64,
453 );
454 }
455}