1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
use image::{ImageBuffer, Pixel};
use imageproc::drawing::{draw_filled_circle_mut, BresenhamLineIter};
use serde::{Deserialize, Serialize};
use std::cmp::Ordering;

use super::core::{
    color_with_intensity, dist_lineseg_point, max_from_partial, Point, PtF, ShapeI, TPtF,
};

#[derive(Deserialize, Serialize, Clone, Debug, Default, PartialEq)]
pub struct BrushLine {
    pub line: Line,
    pub intensity: TPtF,
    pub thickness: TPtF,
}
impl Eq for BrushLine {}

#[derive(Serialize, Deserialize, Clone, Debug, Default, PartialEq)]
pub struct Line {
    pub points: Vec<PtF>,
}

impl Line {
    pub fn push(&mut self, p: PtF) {
        self.points.push(p);
    }
    pub fn new() -> Self {
        Self { points: vec![] }
    }
    #[allow(clippy::needless_lifetimes)]
    pub fn points_iter<'a>(&'a self) -> impl Iterator<Item = PtF> + 'a + Clone {
        self.points.iter().copied()
    }
    pub fn last_point(&self) -> Option<PtF> {
        self.points.last().copied()
    }
    pub fn dist_to_point(&self, p: PtF, nan_warn: Option<impl Fn(&str)>) -> Option<f64> {
        match self.points.len().cmp(&1) {
            Ordering::Greater => (0..(self.points.len() - 1))
                .map(|i| {
                    let ls: (PtF, PtF) = (self.points[i], self.points[i + 1]);
                    dist_lineseg_point(&ls, p)
                })
                .min_by(|x, y| match x.partial_cmp(y) {
                    Some(o) => o,
                    None => {
                        if let Some(nan_warn) = &nan_warn {
                            nan_warn("NaN appeared in distance to line computation.");
                        }
                        std::cmp::Ordering::Greater
                    }
                }),
            Ordering::Equal => Some(p.dist_square(&PtF::from(self.points[0])).sqrt()),
            Ordering::Less => None,
        }
    }
    pub fn max_dist_squared(&self) -> Option<f64> {
        (0..self.points.len())
            .flat_map(|i| {
                (0..self.points.len())
                    .map(|j| self.points[i].dist_square(&self.points[j]))
                    .max_by(max_from_partial)
            })
            .max_by(max_from_partial)
    }
    pub fn mean(&self) -> Option<PtF> {
        let n_points = self.points.len() as u32;
        if n_points == 0 {
            None
        } else {
            Some(
                PtF::from(
                    self.points_iter()
                        .fold(Point { x: 0.0, y: 0.0 }, |p1, p2| p1 + p2),
                ) / n_points as f64,
            )
        }
    }
}
impl From<PtF> for Line {
    fn from(p: PtF) -> Self {
        Self { points: vec![p] }
    }
}

pub enum RenderTargetOrShape<CLR>
where
    CLR: Pixel,
{
    Image(ImageBuffer<CLR, Vec<u8>>),
    Shape(ShapeI),
}
impl<CLR> RenderTargetOrShape<CLR>
where
    CLR: Pixel<Subpixel = u8>,
{
    pub fn make_buffer(self) -> ImageBuffer<CLR, Vec<u8>> {
        match self {
            RenderTargetOrShape::Image(im) => im,
            RenderTargetOrShape::Shape(shape) => ImageBuffer::<CLR, Vec<u8>>::new(shape.w, shape.h),
        }
    }
}
pub fn bresenham_iter<'a>(
    points: impl Iterator<Item = PtF> + 'a + Clone,
) -> impl Iterator<Item = (i32, i32)> + 'a {
    let p1_iter = points.clone();
    let mut p2_iter = points;
    p2_iter.next();
    p1_iter.zip(p2_iter).flat_map(|(p1, p2)| {
        BresenhamLineIter::new((p1.x as f32, p1.y as f32), (p2.x as f32, p2.y as f32))
    })
}

pub fn render_line<'a, CLR>(
    line_points: impl Iterator<Item = PtF> + 'a + Clone,
    intensity: TPtF,
    thickness: TPtF,
    image_or_shape: RenderTargetOrShape<CLR>,
    color: CLR,
) -> ImageBuffer<CLR, Vec<u8>>
where
    CLR: Pixel<Subpixel = u8>,
{
    let mut im = image_or_shape.make_buffer();
    let color = color_with_intensity(color, intensity);
    for center in bresenham_iter(line_points) {
        draw_filled_circle_mut(&mut im, center, (thickness * 0.5) as i32, color);
    }
    im
}