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}