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}