rvimage_domain/
core.rs

1use crate::{result::RvResult, rverr};
2use image::{GenericImage, Pixel};
3use serde::{Deserialize, Serialize};
4use std::{
5    cmp::Ordering,
6    ops::{Add, Div, Mul, Neg, Sub},
7};
8
9pub trait Abs {
10    fn abs(self) -> Self;
11}
12pub trait Min {
13    fn min(self, other: Self) -> Self;
14}
15pub trait Max {
16    fn max(self, other: Self) -> Self;
17}
18
19impl<T> Min for T
20where
21    T: Calc,
22{
23    fn min(self, other: Self) -> Self {
24        min(self, other)
25    }
26}
27impl<T> Max for T
28where
29    T: Calc,
30{
31    fn max(self, other: Self) -> Self {
32        max(self, other)
33    }
34}
35
36macro_rules! impl_trait {
37    ($trait_name:ident, $method:ident, $($T:ty),+) => {
38        $(impl $trait_name for $T {
39            fn $method(self) -> Self {
40                self.$method()
41            }
42        })+
43    };
44}
45impl Abs for TPtI {
46    fn abs(self) -> Self {
47        self
48    }
49}
50impl_trait!(Abs, abs, f32, f64, i32, i64);
51
52pub trait CoordinateBox {
53    fn size_addon() -> Self;
54    fn is_close_to(&self, other: Self) -> bool;
55}
56
57impl CoordinateBox for TPtI {
58    fn size_addon() -> Self {
59        Self::one()
60    }
61    fn is_close_to(&self, other: Self) -> bool {
62        *self == other
63    }
64}
65impl CoordinateBox for TPtS {
66    fn size_addon() -> Self {
67        1
68    }
69    fn is_close_to(&self, other: Self) -> bool {
70        *self == other
71    }
72}
73impl CoordinateBox for TPtF {
74    fn size_addon() -> Self {
75        TPtF::zero()
76    }
77    fn is_close_to(&self, other: Self) -> bool {
78        floats_close(*self, other)
79    }
80}
81
82pub trait Calc:
83    Add<Output = Self>
84    + Sub<Output = Self>
85    + Mul<Output = Self>
86    + Div<Output = Self>
87    + Sized
88    + PartialOrd
89    + Abs
90    + From<u32>
91    + Clone
92    + Copy
93{
94    #[must_use]
95    fn one() -> Self {
96        Self::from(1)
97    }
98    #[must_use]
99    fn zero() -> Self {
100        Self::from(0)
101    }
102}
103impl<T> Calc for T where
104    T: Add<Output = Self>
105        + Sub<Output = Self>
106        + Mul<Output = Self>
107        + Div<Output = Self>
108        + Sized
109        + PartialOrd
110        + Abs
111        + From<u32>
112        + Clone
113        + Copy
114{
115}
116
117fn floats_close(x: TPtF, y: TPtF) -> bool {
118    (x - y).abs() < 1e-10
119}
120
121pub fn min_from_partial<T>(x1: &T, x2: &T) -> Ordering
122where
123    T: PartialOrd,
124{
125    match x1.partial_cmp(x2) {
126        Some(o) => o,
127        None => Ordering::Less,
128    }
129}
130pub fn max_from_partial<T>(x1: &T, x2: &T) -> Ordering
131where
132    T: PartialOrd,
133{
134    match x1.partial_cmp(x2) {
135        Some(o) => o,
136        None => Ordering::Greater,
137    }
138}
139
140pub fn min<T>(x1: T, x2: T) -> T
141where
142    T: PartialOrd,
143{
144    match min_from_partial(&x1, &x2) {
145        Ordering::Greater => x2,
146        _ => x1,
147    }
148}
149pub fn max<T>(x1: T, x2: T) -> T
150where
151    T: PartialOrd,
152{
153    match max_from_partial(&x1, &x2) {
154        Ordering::Less => x2,
155        _ => x1,
156    }
157}
158
159pub type ShapeI = Shape<u32>;
160pub type ShapeF = Shape<f64>;
161
162impl From<ShapeI> for ShapeF {
163    fn from(value: ShapeI) -> Self {
164        Self {
165            w: f64::from(value.w),
166            h: f64::from(value.h),
167        }
168    }
169}
170impl From<ShapeF> for ShapeI {
171    fn from(value: ShapeF) -> Self {
172        Self {
173            w: value.w as u32,
174            h: value.h as u32,
175        }
176    }
177}
178
179#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, Default)]
180pub struct Shape<T>
181where
182    T: Calc,
183{
184    pub w: T,
185    pub h: T,
186}
187impl<T> Shape<T>
188where
189    T: Calc,
190{
191    pub fn new(w: T, h: T) -> Self {
192        Self { w, h }
193    }
194    pub fn rot90_with_image_ntimes(&self, n: u8) -> Self {
195        if n.is_multiple_of(2) {
196            *self
197        } else {
198            Self {
199                w: self.h,
200                h: self.w,
201            }
202        }
203    }
204}
205
206impl ShapeI {
207    pub fn from_im<I>(im: &I) -> Self
208    where
209        I: GenericImage,
210    {
211        Self {
212            w: im.width(),
213            h: im.height(),
214        }
215    }
216}
217
218impl From<[usize; 2]> for ShapeI {
219    fn from(value: [usize; 2]) -> Self {
220        Self::new(value[0] as u32, value[1] as u32)
221    }
222}
223
224impl<T> From<(T, T)> for Shape<T>
225where
226    T: Calc,
227{
228    fn from(value: (T, T)) -> Self {
229        Self {
230            w: value.0,
231            h: value.1,
232        }
233    }
234}
235
236#[derive(Clone, Copy)]
237pub enum OutOfBoundsMode<T>
238where
239    T: Calc,
240{
241    Deny,
242    Resize(Shape<T>), // minimal area the box needs to keep
243}
244
245#[must_use]
246pub fn dist_lineseg_point(ls: &(PtF, PtF), p: PtF) -> f64 {
247    let (p1, p2) = ls;
248    let p1 = *p1;
249    let p2 = *p2;
250    let d = (p1 - p2).len_square().sqrt();
251    let n = (p1 - p2) / d;
252    let proj = p1 + n * (p - p1).dot(&n);
253    if proj.x >= p1.x.min(p2.x)
254        && proj.x <= p1.x.max(p2.x)
255        && proj.y >= p1.y.min(p2.y)
256        && proj.y <= p1.y.max(p2.y)
257    {
258        (p - proj).len_square().sqrt()
259    } else {
260        (p - p1).len_square().min((p - p2).len_square()).sqrt()
261    }
262}
263pub fn max_squaredist<'a, T, I1, I2>(points1: I1, points2: I2) -> (Point<T>, Point<T>, T)
264where
265    T: Calc,
266    I1: Iterator<Item = Point<T>> + 'a + Clone,
267    I2: Iterator<Item = Point<T>> + 'a + Clone,
268{
269    points1
270        .map(|p1| {
271            points2
272                .clone()
273                .map(|p2| {
274                    let dist_x = unsigned_dist(p2.x, p1.x);
275                    let dist_y = unsigned_dist(p2.y, p1.y);
276                    let d = dist_x * dist_x + dist_y * dist_y;
277                    (p1, p2, d)
278                })
279                .max_by(|(_, _, d1), (_, _, d2)| max_from_partial(d1, d2))
280                .unwrap()
281        })
282        .max_by(|(_, _, d1), (_, _, d2)| max_from_partial(d1, d2))
283        .unwrap()
284}
285
286#[cfg(test)]
287#[macro_export]
288macro_rules! point {
289    ($x:literal, $y:literal) => {{
290        if $x < 0.0 || $y < 0.0 {
291            panic!("cannot create point from negative coords, {}, {}", $x, $y);
292        }
293        $crate::domain::PtF { x: $x, y: $y }
294    }};
295}
296
297#[macro_export]
298macro_rules! impl_point_into {
299    ($T:ty) => {
300        impl From<PtI> for ($T, $T) {
301            fn from(p: PtI) -> Self {
302                (p.x as $T, p.y as $T)
303            }
304        }
305        impl From<PtF> for ($T, $T) {
306            fn from(p: PtF) -> Self {
307                (p.x as $T, p.y as $T)
308            }
309        }
310        impl From<($T, $T)> for PtF {
311            fn from((x, y): ($T, $T)) -> Self {
312                Self {
313                    x: x as f64,
314                    y: y as f64,
315                }
316            }
317        }
318        impl From<($T, $T)> for PtI {
319            fn from((x, y): ($T, $T)) -> Self {
320                Self {
321                    x: x as u32,
322                    y: y as u32,
323                }
324            }
325        }
326    };
327}
328
329fn unsigned_dist<T>(x1: T, x2: T) -> T
330where
331    T: Sub<Output = T> + PartialOrd,
332{
333    if x1 > x2 { x1 - x2 } else { x2 - x1 }
334}
335
336pub fn clamp_sub_zero<T>(x1: T, x2: T) -> T
337where
338    T: Calc,
339{
340    if x1 < x2 { T::zero() } else { x1 - x2 }
341}
342#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq, Eq, Default)]
343pub struct Point<T> {
344    pub x: T,
345    pub y: T,
346}
347
348impl<T> Point<T>
349where
350    T: Calc,
351{
352    pub fn len_square(&self) -> T {
353        self.x * self.x + self.y * self.y
354    }
355    pub fn dist_square(&self, other: &Self) -> T
356    where
357        T: PartialOrd,
358    {
359        <(T, T) as Into<Point<T>>>::into((
360            // make this work also for unsigned types
361            unsigned_dist(self.x, other.x),
362            unsigned_dist(self.y, other.y),
363        ))
364        .len_square()
365    }
366    pub fn dot(&self, rhs: &Self) -> T {
367        self.x * rhs.x + self.y * rhs.y
368    }
369
370    fn rot90(&self, w: u32) -> Self
371    where
372        T: CoordinateBox,
373    {
374        Self {
375            x: self.y,
376            y: T::from(w) - self.x - T::size_addon(),
377        }
378    }
379
380    /// Mathematically positively oriented, counter clockwise, like Rot90 tool, different from image crate
381    pub fn rot90_with_image(&self, shape: ShapeI) -> Self
382    where
383        T: Neg<Output = T> + CoordinateBox,
384    {
385        self.rot90(shape.w)
386    }
387    pub fn rot90_with_image_ntimes(&self, shape: ShapeI, n: u8) -> Self
388    where
389        T: Neg<Output = T> + CoordinateBox,
390    {
391        if n > 0 {
392            let mut p = self.rot90_with_image(shape);
393            for i in 1..n {
394                let shape = shape.rot90_with_image_ntimes(i);
395                p = p.rot90_with_image(shape);
396            }
397            p
398        } else {
399            *self
400        }
401    }
402
403    pub fn is_close_to(&self, other: Self) -> bool
404    where
405        T: CoordinateBox,
406    {
407        self.x.is_close_to(other.x) && self.y.is_close_to(other.y)
408    }
409}
410
411impl<T> Mul<T> for Point<T>
412where
413    T: Calc,
414{
415    type Output = Self;
416    fn mul(self, rhs: T) -> Self::Output {
417        Point {
418            x: self.x * rhs,
419            y: self.y * rhs,
420        }
421    }
422}
423impl<T> Mul for Point<T>
424where
425    T: Calc,
426{
427    type Output = Self;
428    fn mul(self, rhs: Self) -> Self::Output {
429        Point {
430            x: self.x * rhs.x,
431            y: self.y * rhs.y,
432        }
433    }
434}
435impl<T> Div<T> for Point<T>
436where
437    T: Calc,
438{
439    type Output = Self;
440    fn div(self, rhs: T) -> Self::Output {
441        Point {
442            x: self.x / rhs,
443            y: self.y / rhs,
444        }
445    }
446}
447impl<T> Div for Point<T>
448where
449    T: Calc,
450{
451    type Output = Self;
452    fn div(self, rhs: Self) -> Self::Output {
453        Point {
454            x: self.x / rhs.x,
455            y: self.y / rhs.y,
456        }
457    }
458}
459
460impl<T> Sub for Point<T>
461where
462    T: Calc,
463{
464    type Output = Point<T>;
465    fn sub(self, rhs: Self) -> Self::Output {
466        Point {
467            x: self.x - rhs.x,
468            y: self.y - rhs.y,
469        }
470    }
471}
472impl<T> Add for Point<T>
473where
474    T: Calc,
475{
476    type Output = Point<T>;
477    fn add(self, rhs: Self) -> Self::Output {
478        Point {
479            x: self.x + rhs.x,
480            y: self.y + rhs.y,
481        }
482    }
483}
484
485impl<T> From<(T, T)> for Point<T>
486where
487    T: Calc,
488{
489    fn from(value: (T, T)) -> Self {
490        Self {
491            x: value.0,
492            y: value.1,
493        }
494    }
495}
496impl<T> From<Point<T>> for (T, T)
497where
498    T: Calc,
499{
500    fn from(p: Point<T>) -> (T, T) {
501        (p.x, p.y)
502    }
503}
504impl_point_into!(i32);
505pub type TPtF = f64;
506pub type TPtI = u32;
507pub type TPtS = i64;
508pub type PtF = Point<TPtF>;
509pub type PtI = Point<TPtI>;
510pub type PtS = Point<TPtS>;
511
512impl PtF {
513    #[must_use]
514    pub fn round_signed(&self) -> Point<i32> {
515        Point {
516            x: self.x.round() as i32,
517            y: self.y.round() as i32,
518        }
519    }
520}
521
522impl PtI {
523    pub fn from_signed(p: (i32, i32)) -> RvResult<Self> {
524        if p.0 < 0 || p.1 < 0 {
525            Err(rverr!(
526                "cannot create point with negative coordinates, {:?}",
527                p
528            ))
529        } else {
530            Ok(Self {
531                x: p.0 as u32,
532                y: p.1 as u32,
533            })
534        }
535    }
536    pub fn equals<U>(&self, other: (U, U)) -> bool
537    where
538        U: PartialEq,
539        PtI: Into<(U, U)>,
540    {
541        <Self as Into<(U, U)>>::into(*self) == other
542    }
543}
544
545impl From<PtI> for PtF {
546    fn from(p: PtI) -> Self {
547        (f64::from(p.x), f64::from(p.y)).into()
548    }
549}
550impl From<PtI> for PtS {
551    fn from(p: PtI) -> Self {
552        (TPtS::from(p.x), TPtS::from(p.y)).into()
553    }
554}
555impl From<PtS> for PtI {
556    fn from(p: PtS) -> Self {
557        ((p.x as u32), (p.y as u32)).into()
558    }
559}
560impl From<PtF> for PtI {
561    fn from(p: PtF) -> Self {
562        ((p.x as u32), (p.y as u32)).into()
563    }
564}
565impl From<(u32, u32)> for PtF {
566    fn from(x: (u32, u32)) -> Self {
567        (f64::from(x.0), f64::from(x.1)).into()
568    }
569}
570impl From<(f32, f32)> for PtI {
571    fn from(x: (f32, f32)) -> Self {
572        ((x.0 as u32), (x.1 as u32)).into()
573    }
574}
575impl From<(usize, usize)> for PtI {
576    fn from(x: (usize, usize)) -> Self {
577        ((x.0 as u32), (x.1 as u32)).into()
578    }
579}
580
581impl From<PtI> for (usize, usize) {
582    fn from(p: PtI) -> Self {
583        (p.x as usize, p.y as usize)
584    }
585}
586
587#[derive(Clone, Copy, Debug, PartialEq)]
588pub struct Circle {
589    pub center: PtF,
590    pub radius: TPtF,
591}
592
593pub fn color_with_intensity<CLR>(mut color: CLR, intensity: f64) -> CLR
594where
595    CLR: Pixel<Subpixel = u8>,
596{
597    let channels = color.channels_mut();
598    for channel in channels {
599        *channel = (f64::from(*channel) * intensity) as u8;
600    }
601    color
602}
603#[test]
604fn test_rot() {
605    let shape = Shape::new(5, 3);
606    let p = PtS { x: 2, y: 1 };
607    let p_rot_1 = p.rot90_with_image(shape);
608    assert!(p_rot_1.is_close_to(PtS { x: 1, y: 2 }));
609    let p_rot_2 = p.rot90_with_image_ntimes(shape, 2);
610    let p_rot_2_ = p_rot_1.rot90_with_image(shape.rot90_with_image_ntimes(1));
611    assert_eq!(p_rot_2, p_rot_2_);
612
613    let p = PtF { x: 2.5, y: 1.0 };
614    let p_rot_1 = p.rot90_with_image(shape);
615    assert!(p_rot_1.is_close_to(PtF { x: 1.0, y: 2.5 }));
616    assert!(
617        p.rot90_with_image_ntimes(shape, 2)
618            .is_close_to(p_rot_1.rot90_with_image(shape.rot90_with_image_ntimes(1)))
619    );
620
621    let shape = Shape::new(5, 10);
622    let p = PtS { x: 1, y: 2 };
623    let p_rot_1 = p.rot90_with_image(shape);
624    assert!(p_rot_1.is_close_to(PtS { x: 2, y: 3 }));
625    assert!(
626        p.rot90_with_image_ntimes(shape, 2)
627            .is_close_to(p_rot_1.rot90_with_image(shape.rot90_with_image_ntimes(1)))
628    );
629    let p = PtF { x: 1.0, y: 2.0 };
630    let p_rot_1 = p.rot90_with_image(shape);
631    assert!(p_rot_1.is_close_to(PtF { x: 2.0, y: 4.0 }));
632    assert!(
633        p.rot90_with_image_ntimes(shape, 2)
634            .is_close_to(p_rot_1.rot90_with_image(shape.rot90_with_image_ntimes(1)))
635    );
636    let p = PtF { x: 2.0, y: 4.0 };
637    let p_rot_1 = p.rot90_with_image(shape);
638    assert!(p_rot_1.is_close_to(PtF { x: 4.0, y: 3.0 }));
639    assert!(
640        p.rot90_with_image_ntimes(shape, 2)
641            .is_close_to(p_rot_1.rot90_with_image(shape.rot90_with_image_ntimes(1)))
642    );
643}