1pub mod font;
2
3use crate::core::types::{Point, Rect, Scalar};
4use crate::error::Result;
5use crate::image::Image;
6use burn::tensor::{Tensor, TensorData, backend::Backend};
7use font::FONT_5X7;
8
9impl<B: Backend> Image<B> {
10 pub fn draw_line(self, p1: Point<usize>, p2: Point<usize>, color: Scalar) -> Result<Self> {
13 let dims = self.tensor.dims();
14 let c = dims[0];
15 let h = dims[1];
16 let w = dims[2];
17
18 let device = self.tensor.device();
19 let tensor_data = self.tensor.into_data();
20 let mut flat_vals: Vec<f32> = tensor_data.iter::<f32>().collect();
21
22 let mut x0 = p1.x as isize;
23 let mut y0 = p1.y as isize;
24 let x1 = p2.x as isize;
25 let y1 = p2.y as isize;
26
27 let dx = (x1 - x0).abs();
28 let dy = -(y1 - y0).abs();
29 let sx = if x0 < x1 { 1 } else { -1 };
30 let sy = if y0 < y1 { 1 } else { -1 };
31 let mut err = dx + dy;
32
33 loop {
34 if x0 >= 0 && x0 < w as isize && y0 >= 0 && y0 < h as isize {
35 for ch in 0..c {
36 let val = color.0[ch] as f32;
37 flat_vals[ch * h * w + (y0 as usize) * w + (x0 as usize)] = val;
38 }
39 }
40 if x0 == x1 && y0 == y1 {
41 break;
42 }
43 let e2 = 2 * err;
44 if e2 >= dy {
45 err += dy;
46 x0 += sx;
47 }
48 if e2 <= dx {
49 err += dx;
50 y0 += sy;
51 }
52 }
53
54 let new_data = TensorData::new(flat_vals, [c, h, w]);
55 let new_tensor = Tensor::<B, 3>::from_data(new_data, &device);
56 Ok(Image::new(new_tensor))
57 }
58
59 pub fn draw_rectangle(self, rect: Rect<usize>, color: Scalar, thickness: i32) -> Result<Self> {
61 if thickness >= 0 {
62 let p1 = Point::new(rect.x, rect.y);
64 let p2 = Point::new(rect.x + rect.width, rect.y);
65 let p3 = Point::new(rect.x + rect.width, rect.y + rect.height);
66 let p4 = Point::new(rect.x, rect.y + rect.height);
67
68 self.draw_line(p1, p2, color)?
69 .draw_line(p2, p3, color)?
70 .draw_line(p3, p4, color)?
71 .draw_line(p4, p1, color)
72 } else {
73 let dims = self.tensor.dims();
75 let c = dims[0];
76 let h = dims[1];
77 let w = dims[2];
78
79 let device = self.tensor.device();
80 let tensor_data = self.tensor.into_data();
81 let mut flat_vals: Vec<f32> = tensor_data.iter::<f32>().collect();
82
83 let x_start = rect.x;
84 let y_start = rect.y;
85 let x_end = (rect.x + rect.width).min(w);
86 let y_end = (rect.y + rect.height).min(h);
87
88 for y in y_start..y_end {
89 for x in x_start..x_end {
90 for ch in 0..c {
91 flat_vals[ch * h * w + y * w + x] = color.0[ch] as f32;
92 }
93 }
94 }
95
96 let new_data = TensorData::new(flat_vals, [c, h, w]);
97 let new_tensor = Tensor::<B, 3>::from_data(new_data, &device);
98 Ok(Image::new(new_tensor))
99 }
100 }
101
102 pub fn draw_circle(
104 self,
105 center: Point<usize>,
106 radius: usize,
107 color: Scalar,
108 thickness: i32,
109 ) -> Result<Self> {
110 let dims = self.tensor.dims();
111 let c = dims[0];
112 let h = dims[1];
113 let w = dims[2];
114
115 let device = self.tensor.device();
116 let tensor_data = self.tensor.into_data();
117 let mut flat_vals: Vec<f32> = tensor_data.iter::<f32>().collect();
118
119 let xc = center.x as isize;
120 let yc = center.y as isize;
121 let r = radius as isize;
122
123 let draw_pixel = |px: isize, py: isize, vals: &mut [f32]| {
124 if px >= 0 && px < w as isize && py >= 0 && py < h as isize {
125 for ch in 0..c {
126 vals[ch * h * w + (py as usize) * w + (px as usize)] = color.0[ch] as f32;
127 }
128 }
129 };
130
131 if thickness >= 0 {
132 let mut x = 0isize;
134 let mut y = r;
135 let mut d = 3 - 2 * r;
136
137 let draw_sym = |x_s: isize, y_s: isize, vals: &mut [f32]| {
138 draw_pixel(xc + x_s, yc + y_s, vals);
139 draw_pixel(xc - x_s, yc + y_s, vals);
140 draw_pixel(xc + x_s, yc - y_s, vals);
141 draw_pixel(xc - x_s, yc - y_s, vals);
142 draw_pixel(xc + y_s, yc + x_s, vals);
143 draw_pixel(xc - y_s, yc + x_s, vals);
144 draw_pixel(xc + y_s, yc - x_s, vals);
145 draw_pixel(xc - y_s, yc - x_s, vals);
146 };
147
148 draw_sym(x, y, &mut flat_vals);
149 while y >= x {
150 x += 1;
151 if d > 0 {
152 y -= 1;
153 d = d + 4 * (x - y) + 10;
154 } else {
155 d = d + 4 * x + 6;
156 }
157 draw_sym(x, y, &mut flat_vals);
158 }
159 } else {
160 for y in 0..h {
162 for x in 0..w {
163 let dx = x as isize - xc;
164 let dy = y as isize - yc;
165 if dx * dx + dy * dy <= r * r {
166 for ch in 0..c {
167 flat_vals[ch * h * w + y * w + x] = color.0[ch] as f32;
168 }
169 }
170 }
171 }
172 }
173
174 let new_data = TensorData::new(flat_vals, [c, h, w]);
175 let new_tensor = Tensor::<B, 3>::from_data(new_data, &device);
176 Ok(Image::new(new_tensor))
177 }
178
179 pub fn draw_text(
181 self,
182 text: &str,
183 org: Point<usize>,
184 scale: usize,
185 color: Scalar,
186 ) -> Result<Self> {
187 let dims = self.tensor.dims();
188 let c = dims[0];
189 let h = dims[1];
190 let w = dims[2];
191
192 let device = self.tensor.device();
193 let tensor_data = self.tensor.into_data();
194 let mut flat_vals: Vec<f32> = tensor_data.iter::<f32>().collect();
195
196 let scale = scale.max(1);
197 let mut cursor_x = org.x;
198
199 for byte in text.bytes() {
200 let char_idx = (byte as usize).min(127);
201 let bitmap = FONT_5X7[char_idx];
202
203 for (col, &col_data) in bitmap.iter().enumerate() {
204 for row in 0..7 {
205 if (col_data & (1 << row)) != 0 {
206 let px_start = cursor_x + col * scale;
208 let py_start = org.y + row * scale;
209
210 for sy in 0..scale {
211 for sx in 0..scale {
212 let x = px_start + sx;
213 let y = py_start + sy;
214 if x < w && y < h {
215 for ch in 0..c {
216 flat_vals[ch * h * w + y * w + x] = color.0[ch] as f32;
217 }
218 }
219 }
220 }
221 }
222 }
223 }
224 cursor_x += 6 * scale; }
226
227 let new_data = TensorData::new(flat_vals, [c, h, w]);
228 let new_tensor = Tensor::<B, 3>::from_data(new_data, &device);
229 Ok(Image::new(new_tensor))
230 }
231
232 #[allow(clippy::too_many_arguments)]
237 pub fn draw_ellipse(
238 self,
239 center: Point<usize>,
240 axes: (usize, usize),
241 angle: f32,
242 start_angle: f32,
243 end_angle: f32,
244 color: Scalar,
245 thickness: i32,
246 ) -> Result<Self> {
247 let dims = self.tensor.dims();
248 let c = dims[0];
249 let h = dims[1];
250 let w = dims[2];
251
252 let device = self.tensor.device();
253 let tensor_data = self.tensor.into_data();
254 let mut flat_vals: Vec<f32> = tensor_data.iter::<f32>().collect();
255
256 let cx = center.x as f32;
257 let cy = center.y as f32;
258 let (a, b) = (axes.0 as f32, axes.1 as f32);
259 let angle_rad = angle * std::f32::consts::PI / 180.0;
260 let start_rad = start_angle * std::f32::consts::PI / 180.0;
261 let end_rad = end_angle * std::f32::consts::PI / 180.0;
262
263 let cos_a = angle_rad.cos();
264 let sin_a = angle_rad.sin();
265
266 let draw_px = |px: isize, py: isize, vals: &mut [f32]| {
267 if px >= 0 && px < w as isize && py >= 0 && py < h as isize {
268 for ch in 0..c {
269 vals[ch * h * w + py as usize * w + px as usize] = color.0[ch] as f32;
270 }
271 }
272 };
273
274 if thickness >= 0 {
275 for t_idx in 0..720 {
277 let t = start_rad + (end_rad - start_rad) * t_idx as f32 / 720.0;
278 let ex = a * t.cos();
279 let ey = b * t.sin();
280 let rx = ex * cos_a - ey * sin_a;
281 let ry = ex * sin_a + ey * cos_a;
282 draw_px((cx + rx) as isize, (cy + ry) as isize, &mut flat_vals);
283 }
284 } else {
285 let max_r = a.max(b) as usize;
287 let x0 = (cx as isize - max_r as isize).max(0) as usize;
288 let x1 = (cx as usize + max_r).min(w);
289 let y0 = (cy as isize - max_r as isize).max(0) as usize;
290 let y1 = (cy as usize + max_r).min(h);
291
292 for py in y0..y1 {
293 for px in x0..x1 {
294 let dx = px as f32 - cx;
295 let dy = py as f32 - cy;
296 let tx = dx * cos_a + dy * sin_a;
298 let ty = -dx * sin_a + dy * cos_a;
299 if a > 0.0 && b > 0.0 && (tx / a).powi(2) + (ty / b).powi(2) <= 1.0 {
300 for ch in 0..c {
301 flat_vals[ch * h * w + py * w + px] = color.0[ch] as f32;
302 }
303 }
304 }
305 }
306 }
307
308 let new_data = TensorData::new(flat_vals, [c, h, w]);
309 let new_tensor = Tensor::<B, 3>::from_data(new_data, &device);
310 Ok(Image::new(new_tensor))
311 }
312
313 pub fn draw_polyline(
315 self,
316 points: &[Point<usize>],
317 color: Scalar,
318 _thickness: i32,
319 ) -> Result<Self> {
320 if points.len() < 2 {
321 return Ok(self);
322 }
323 let mut current = self;
324 for i in 0..points.len() - 1 {
325 current = current.draw_line(points[i], points[i + 1], color)?;
326 }
327 Ok(current)
328 }
329
330 pub fn fill_poly(self, points: &[Point<usize>], color: Scalar) -> Result<Self> {
332 let dims = self.tensor.dims();
333 let c = dims[0];
334 let h = dims[1];
335 let w = dims[2];
336
337 let device = self.tensor.device();
338 let tensor_data = self.tensor.into_data();
339 let mut flat_vals: Vec<f32> = tensor_data.iter::<f32>().collect();
340
341 if points.len() < 3 {
342 return Ok(Image::new(Tensor::<B, 3>::from_data(
343 TensorData::new(flat_vals, [c, h, w]),
344 &device,
345 )));
346 }
347
348 let min_y = points.iter().map(|p| p.y).min().unwrap_or(0);
350 let max_y = points.iter().map(|p| p.y).max().unwrap_or(h - 1);
351 let max_y = max_y.min(h - 1);
352
353 for y in min_y..=max_y {
355 let mut intersections = Vec::new();
356 let n = points.len();
357 for i in 0..n {
358 let j = (i + 1) % n;
359 let (p1, p2) = (points[i], points[j]);
360 if (p1.y <= y && p2.y > y) || (p2.y <= y && p1.y > y) {
361 let x_intersect = p1.x as f64
362 + (y as f64 - p1.y as f64) * (p2.x as f64 - p1.x as f64)
363 / (p2.y as f64 - p1.y as f64);
364 intersections.push(x_intersect as usize);
365 }
366 }
367 intersections.sort_unstable();
368
369 for pair in intersections.chunks(2) {
370 if pair.len() == 2 {
371 let x_start = pair[0];
372 let x_end = pair[1].min(w - 1);
373 for x in x_start..=x_end {
374 for ch in 0..c {
375 flat_vals[ch * h * w + y * w + x] = color.0[ch] as f32;
376 }
377 }
378 }
379 }
380 }
381
382 let new_data = TensorData::new(flat_vals, [c, h, w]);
383 let new_tensor = Tensor::<B, 3>::from_data(new_data, &device);
384 Ok(Image::new(new_tensor))
385 }
386
387 pub fn draw_arrowed_line(
389 self,
390 p1: Point<usize>,
391 p2: Point<usize>,
392 color: Scalar,
393 _thickness: i32,
394 tip_length: f32,
395 ) -> Result<Self> {
396 let img = self.draw_line(p1, p2, color)?;
397
398 let dx = p2.x as f64 - p1.x as f64;
400 let dy = p2.y as f64 - p1.y as f64;
401 let len = (dx * dx + dy * dy).sqrt();
402 if len < 1.0 {
403 return Ok(img);
404 }
405
406 let ux = dx / len;
407 let uy = dy / len;
408 let tip_size = len * tip_length as f64;
409 let angle = std::f64::consts::FRAC_PI_6; let left = (
412 (p2.x as f64 - tip_size * (ux * angle.cos() - uy * angle.sin())) as usize,
413 (p2.y as f64 - tip_size * (uy * angle.cos() + ux * angle.sin())) as usize,
414 );
415 let right = (
416 (p2.x as f64 - tip_size * (ux * angle.cos() + uy * angle.sin())) as usize,
417 (p2.y as f64 - tip_size * (uy * angle.cos() - ux * angle.sin())) as usize,
418 );
419
420 img.draw_line(p2, Point::new(left.0, left.1), color)?
421 .draw_line(p2, Point::new(right.0, right.1), color)
422 }
423
424 pub fn draw_marker(
426 self,
427 center: Point<usize>,
428 color: Scalar,
429 marker_type: MarkerType,
430 marker_size: usize,
431 ) -> Result<Self> {
432 match marker_type {
433 MarkerType::Cross => {
434 let half = marker_size / 2;
435 self.draw_line(
436 Point::new(center.x.saturating_sub(half), center.y),
437 Point::new(center.x + half, center.y),
438 color,
439 )?
440 .draw_line(
441 Point::new(center.x, center.y.saturating_sub(half)),
442 Point::new(center.x, center.y + half),
443 color,
444 )
445 }
446 MarkerType::TiltedCross => {
447 let half = marker_size / 2;
448 self.draw_line(
449 Point::new(center.x.saturating_sub(half), center.y.saturating_sub(half)),
450 Point::new(center.x + half, center.y + half),
451 color,
452 )?
453 .draw_line(
454 Point::new(center.x + half, center.y.saturating_sub(half)),
455 Point::new(center.x.saturating_sub(half), center.y + half),
456 color,
457 )
458 }
459 MarkerType::Diamond => {
460 let half = marker_size / 2;
461 self.draw_polyline(
462 &[
463 Point::new(center.x, center.y.saturating_sub(half)),
464 Point::new(center.x + half, center.y),
465 Point::new(center.x, center.y + half),
466 Point::new(center.x.saturating_sub(half), center.y),
467 Point::new(center.x, center.y.saturating_sub(half)),
468 ],
469 color,
470 1,
471 )
472 }
473 MarkerType::Square => self.draw_rectangle(
474 Rect::new(
475 center.x.saturating_sub(marker_size / 2),
476 center.y.saturating_sub(marker_size / 2),
477 marker_size,
478 marker_size,
479 ),
480 color,
481 1,
482 ),
483 MarkerType::Circle => self.draw_circle(center, marker_size / 2, color, 1),
484 MarkerType::Filled => self.draw_circle(center, marker_size / 2, color, -1),
485 }
486 }
487}
488
489#[derive(Clone, Copy, Debug, PartialEq)]
491pub enum MarkerType {
492 Cross,
493 TiltedCross,
494 Diamond,
495 Square,
496 Circle,
497 Filled,
498}
499
500#[cfg(test)]
501mod tests {
502 use super::*;
503 use crate::test_helpers::{TestBackend, test_device};
504
505 #[test]
506 fn test_drawing_operations() {
507 let device = test_device();
508 let flat_data = vec![0.0f32; 3 * 100 * 100];
509 let tensor =
510 Tensor::<TestBackend, 3>::from_data(TensorData::new(flat_data, [3, 100, 100]), &device);
511 let img = Image::new(tensor);
512
513 let img = img
514 .draw_line(Point::new(10, 10), Point::new(90, 90), Scalar::all(1.0))
515 .unwrap();
516 let img = img
517 .draw_rectangle(Rect::new(20, 20, 30, 40), Scalar::all(0.5), 1)
518 .unwrap();
519 let img = img
520 .draw_circle(Point::new(50, 50), 20, Scalar::all(0.8), -1)
521 .unwrap();
522 let img = img
523 .draw_text("Hello", Point::new(10, 80), 2, Scalar::all(0.9))
524 .unwrap();
525
526 assert_eq!(img.shape(), [3, 100, 100]);
527 }
528
529 #[test]
530 fn test_draw_ellipse() {
531 let device = test_device();
532 let data = vec![0.0f32; 3 * 60 * 60];
533 let tensor =
534 Tensor::<TestBackend, 3>::from_data(TensorData::new(data, [3, 60, 60]), &device);
535 let img = Image::new(tensor);
536
537 let img = img
539 .draw_ellipse(
540 Point::new(30, 30),
541 (15, 10),
542 30.0,
543 0.0,
544 360.0,
545 Scalar::all(1.0),
546 1,
547 )
548 .unwrap();
549 assert_eq!(img.shape(), [3, 60, 60]);
550
551 let img = img
553 .draw_ellipse(
554 Point::new(30, 30),
555 (15, 10),
556 30.0,
557 0.0,
558 360.0,
559 Scalar::all(0.5),
560 -1,
561 )
562 .unwrap();
563 assert_eq!(img.shape(), [3, 60, 60]);
564 }
565
566 #[test]
567 fn test_draw_polyline() {
568 let device = test_device();
569 let data = vec![0.0f32; 3 * 50 * 50];
570 let tensor =
571 Tensor::<TestBackend, 3>::from_data(TensorData::new(data, [3, 50, 50]), &device);
572 let img = Image::new(tensor);
573
574 let points = vec![
575 Point::new(10, 10),
576 Point::new(40, 10),
577 Point::new(40, 40),
578 Point::new(10, 40),
579 Point::new(10, 10),
580 ];
581 let img = img.draw_polyline(&points, Scalar::all(1.0), 1).unwrap();
582 assert_eq!(img.shape(), [3, 50, 50]);
583 }
584
585 #[test]
586 fn test_fill_poly() {
587 let device = test_device();
588 let data = vec![0.0f32; 3 * 50 * 50];
589 let tensor =
590 Tensor::<TestBackend, 3>::from_data(TensorData::new(data, [3, 50, 50]), &device);
591 let img = Image::new(tensor);
592
593 let points = vec![
594 Point::new(10, 10),
595 Point::new(40, 10),
596 Point::new(40, 40),
597 Point::new(10, 40),
598 ];
599 let img = img.fill_poly(&points, Scalar::all(0.8)).unwrap();
600 assert_eq!(img.shape(), [3, 50, 50]);
601 }
602
603 #[test]
604 fn test_draw_arrowed_line() {
605 let device = test_device();
606 let data = vec![0.0f32; 3 * 50 * 50];
607 let tensor =
608 Tensor::<TestBackend, 3>::from_data(TensorData::new(data, [3, 50, 50]), &device);
609 let img = Image::new(tensor);
610
611 let img = img
612 .draw_arrowed_line(
613 Point::new(10, 10),
614 Point::new(40, 40),
615 Scalar::all(1.0),
616 1,
617 0.3,
618 )
619 .unwrap();
620 assert_eq!(img.shape(), [3, 50, 50]);
621 }
622
623 #[test]
624 fn test_draw_marker() {
625 let device = test_device();
626 let data = vec![0.0f32; 3 * 50 * 50];
627 let tensor =
628 Tensor::<TestBackend, 3>::from_data(TensorData::new(data, [3, 50, 50]), &device);
629 let img = Image::new(tensor);
630
631 let img = img
632 .draw_marker(Point::new(25, 25), Scalar::all(1.0), MarkerType::Cross, 10)
633 .unwrap();
634 let img = img
635 .draw_marker(Point::new(25, 25), Scalar::all(0.5), MarkerType::Circle, 10)
636 .unwrap();
637 let img = img
638 .draw_marker(Point::new(25, 25), Scalar::all(0.8), MarkerType::Filled, 10)
639 .unwrap();
640 assert_eq!(img.shape(), [3, 50, 50]);
641 }
642}