strandify/line.rs
1use image;
2use std::iter::zip;
3
4#[derive(Debug)]
5/// Helper struct that represents a line between 2 [`Pegs`](crate::peg::Peg). Holds the vectors of the pixel coordinates of the line.
6pub struct Line {
7 /// X coordinates of the pixels in the line.
8 pub x: Vec<u32>,
9 /// Y coordinates of the pixels in the line.
10 pub y: Vec<u32>,
11 /// The distance of the line in pixels.
12 pub dist: u32,
13}
14
15impl Line {
16 /// Creates a new [`Line`].
17 pub fn new(x: Vec<u32>, y: Vec<u32>, dist: u32) -> Self {
18 assert_eq!(x.len(), y.len(), "`x` and `y` should have the same length");
19 Self { x, y, dist }
20 }
21
22 /// Returns the length of this [`Line`].
23 pub fn len(&self) -> usize {
24 self.x.len()
25 }
26
27 /// Returns the is empty of this [`Line`].
28 pub fn is_empty(&self) -> bool {
29 self.x.is_empty()
30 }
31
32 /// Returns the zip of this [`Line`].
33 ///
34 /// Zips over both coordinates.
35 ///
36 /// # Examples
37 ///
38 /// ```
39 /// use strandify::line::Line;
40 /// let line = Line::new(vec![0, 1], vec![0, 0], 1);
41 /// for (x, y) in line.zip() {
42 /// println!("x: {x:?}, y: {y:?}");
43 /// }
44 /// assert_eq!(line.zip().len(), 2);
45 /// ```
46 pub fn zip(&self) -> std::iter::Zip<std::slice::Iter<u32>, std::slice::Iter<u32>> {
47 zip(&self.x, &self.y)
48 }
49
50 /// Returns the copy of this [`Line`].
51 pub fn copy(&self) -> Self {
52 Self::new(self.x.clone(), self.y.clone(), self.dist)
53 }
54
55 /// Compute the loss of this [`Line`] over the provided single channel [`image::ImageBuffer`].
56 ///
57 /// # Arguments:
58 ///
59 /// * `image`: The [`image::ImageBuffer`] to compute the loss over.
60 ///
61 /// # Returns:
62 ///
63 /// * `f64`: The loss of this [`Line`].
64 pub fn loss(&self, image: &image::ImageBuffer<image::Luma<u8>, Vec<u8>>) -> f64 {
65 self.zip()
66 .map(|(x, y)| image.get_pixel(*x, *y))
67 .fold(0.0, |acc, &pixel| acc + (pixel.0[0] as f64))
68 / (255. * self.len() as f64)
69 }
70
71 /// Draw the [`Line`] on the image, with alpha compositing.
72 ///
73 /// # Arguments:
74 ///
75 /// * `image`: the [`image::ImageBuffer`] to draw the line on, should be single channel.
76 /// * `line_opacity`: the opacity of the line.
77 /// * `line_color`: the grey scale color of the line.
78 pub fn draw(
79 &self,
80 image: &mut image::ImageBuffer<image::Luma<u8>, Vec<u8>>,
81 line_opacity: f64,
82 line_color: f64,
83 ) {
84 self.zip().for_each(|(x, y)| {
85 let pixel = image.get_pixel_mut(*x, *y);
86 pixel.0[0] = ((1. - line_opacity) * pixel.0[0] as f64 + line_color)
87 .round()
88 .min(255.0) as u8;
89 });
90 }
91}