rvimage_domain/
line.rs

1use image::{ImageBuffer, Pixel};
2use imageproc::drawing::{draw_filled_circle_mut, BresenhamLineIter};
3use serde::{Deserialize, Serialize};
4use std::cmp::Ordering;
5
6use crate::{rverr, BbF, OutOfBoundsMode, RvResult, BB};
7
8use super::core::{
9    color_with_intensity, dist_lineseg_point, max_from_partial, Point, PtF, ShapeI, TPtF,
10};
11
12#[derive(Deserialize, Serialize, Clone, Debug, Default, PartialEq)]
13pub struct BrushLine {
14    pub line: Line,
15    pub intensity: TPtF,
16    pub thickness: TPtF,
17}
18impl Eq for BrushLine {}
19
20impl BrushLine {
21    pub fn bb(&self, orig_shape: Option<ShapeI>) -> RvResult<BbF> {
22        let thickness = self.thickness;
23        let thickness_half = thickness * 0.5;
24        let bb = BB::from_points_iter(self.line.points_iter())?;
25
26        let bb_x = bb.x - if thickness > 1.0 { thickness_half } else { 0.0 };
27        let bb_y = bb.y - if thickness > 1.0 { thickness_half } else { 0.0 };
28        let w = bb.w + thickness;
29        let h = bb.h + thickness;
30        let xywh = [bb_x, bb_y, w, h];
31        let bb = match orig_shape {
32            Some(orig_shape) => BB::new_shape_checked(
33                xywh[0],
34                xywh[1],
35                xywh[2],
36                xywh[3],
37                orig_shape,
38                OutOfBoundsMode::Resize((xywh[2], xywh[3]).into()),
39            )
40            .ok_or_else(|| rverr!("Could not create bounding box for line"))?,
41            None => BB::from_arr(&xywh),
42        };
43        Ok(bb)
44    }
45}
46#[derive(Serialize, Deserialize, Clone, Debug, Default, PartialEq)]
47pub struct Line {
48    pub points: Vec<PtF>,
49}
50
51impl Line {
52    pub fn push(&mut self, p: PtF) {
53        self.points.push(p);
54    }
55    #[must_use]
56    pub fn new() -> Self {
57        Self { points: vec![] }
58    }
59    #[allow(clippy::needless_lifetimes)]
60    pub fn points_iter<'a>(&'a self) -> impl Iterator<Item = PtF> + 'a + Clone {
61        self.points.iter().copied()
62    }
63    #[must_use]
64    pub fn last_point(&self) -> Option<PtF> {
65        self.points.last().copied()
66    }
67    pub fn dist_to_point(&self, p: PtF, nan_warn: Option<impl Fn(&str)>) -> Option<f64> {
68        match self.points.len().cmp(&1) {
69            Ordering::Greater => (0..(self.points.len() - 1))
70                .map(|i| {
71                    let ls: (PtF, PtF) = (self.points[i], self.points[i + 1]);
72                    dist_lineseg_point(&ls, p)
73                })
74                .min_by(|x, y| {
75                    if let Some(o) = x.partial_cmp(y) {
76                        o
77                    } else {
78                        if let Some(nan_warn) = &nan_warn {
79                            nan_warn("NaN appeared in distance to line computation.");
80                        }
81                        std::cmp::Ordering::Greater
82                    }
83                }),
84            Ordering::Equal => Some(p.dist_square(&PtF::from(self.points[0])).sqrt()),
85            Ordering::Less => None,
86        }
87    }
88    pub fn max_dist_squared(&self) -> Option<f64> {
89        (0..self.points.len())
90            .filter_map(|i| {
91                (0..self.points.len())
92                    .map(|j| self.points[i].dist_square(&self.points[j]))
93                    .max_by(max_from_partial)
94            })
95            .max_by(max_from_partial)
96    }
97    #[must_use]
98    pub fn mean(&self) -> Option<PtF> {
99        let n_points = self.points.len() as u32;
100        if n_points == 0 {
101            None
102        } else {
103            Some(
104                PtF::from(
105                    self.points_iter()
106                        .fold(Point { x: 0.0, y: 0.0 }, |p1, p2| p1 + p2),
107                ) / f64::from(n_points),
108            )
109        }
110    }
111}
112impl From<PtF> for Line {
113    fn from(p: PtF) -> Self {
114        Self { points: vec![p] }
115    }
116}
117
118pub enum RenderTargetOrShape<CLR>
119where
120    CLR: Pixel,
121{
122    Image(ImageBuffer<CLR, Vec<u8>>),
123    Shape(ShapeI),
124}
125impl<CLR> RenderTargetOrShape<CLR>
126where
127    CLR: Pixel<Subpixel = u8>,
128{
129    #[must_use]
130    pub fn make_buffer(self) -> ImageBuffer<CLR, Vec<u8>> {
131        match self {
132            RenderTargetOrShape::Image(im) => im,
133            RenderTargetOrShape::Shape(shape) => ImageBuffer::<CLR, Vec<u8>>::new(shape.w, shape.h),
134        }
135    }
136}
137pub fn bresenham_iter<'a>(
138    points: impl Iterator<Item = PtF> + 'a + Clone,
139) -> impl Iterator<Item = (i32, i32)> + 'a {
140    let p1_iter = points.clone();
141    let mut p2_iter = points;
142    p2_iter.next();
143    p1_iter.zip(p2_iter).flat_map(|(p1, p2)| {
144        BresenhamLineIter::new((p1.x as f32, p1.y as f32), (p2.x as f32, p2.y as f32))
145    })
146}
147
148pub fn render_line<'a, CLR>(
149    line_points: impl Iterator<Item = PtF> + 'a + Clone,
150    intensity: TPtF,
151    thickness: TPtF,
152    image_or_shape: RenderTargetOrShape<CLR>,
153    color: CLR,
154) -> ImageBuffer<CLR, Vec<u8>>
155where
156    CLR: Pixel<Subpixel = u8>,
157{
158    let mut im = image_or_shape.make_buffer();
159    let color = color_with_intensity(color, intensity);
160    for center in bresenham_iter(line_points) {
161        draw_filled_circle_mut(&mut im, center, (thickness * 0.5) as i32, color);
162    }
163    im
164}