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 % 2 == 0 {
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 {
334        x1 - x2
335    } else {
336        x2 - x1
337    }
338}
339
340pub fn clamp_sub_zero<T>(x1: T, x2: T) -> T
341where
342    T: Calc,
343{
344    if x1 < x2 {
345        T::zero()
346    } else {
347        x1 - x2
348    }
349}
350#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq, Eq, Default)]
351pub struct Point<T> {
352    pub x: T,
353    pub y: T,
354}
355
356impl<T> Point<T>
357where
358    T: Calc,
359{
360    pub fn len_square(&self) -> T {
361        self.x * self.x + self.y * self.y
362    }
363    pub fn dist_square(&self, other: &Self) -> T
364    where
365        T: PartialOrd,
366    {
367        <(T, T) as Into<Point<T>>>::into((
368            // make this work also for unsigned types
369            unsigned_dist(self.x, other.x),
370            unsigned_dist(self.y, other.y),
371        ))
372        .len_square()
373    }
374    pub fn dot(&self, rhs: &Self) -> T {
375        self.x * rhs.x + self.y * rhs.y
376    }
377
378    fn rot90(&self, w: u32) -> Self
379    where
380        T: CoordinateBox,
381    {
382        Self {
383            x: self.y,
384            y: T::from(w) - self.x - T::size_addon(),
385        }
386    }
387
388    /// Mathematically positively oriented, counter clockwise, like Rot90 tool, different from image crate
389    pub fn rot90_with_image(&self, shape: ShapeI) -> Self
390    where
391        T: Neg<Output = T> + CoordinateBox,
392    {
393        self.rot90(shape.w)
394    }
395    pub fn rot90_with_image_ntimes(&self, shape: ShapeI, n: u8) -> Self
396    where
397        T: Neg<Output = T> + CoordinateBox,
398    {
399        if n > 0 {
400            let mut p = self.rot90_with_image(shape);
401            for i in 1..n {
402                let shape = shape.rot90_with_image_ntimes(i);
403                p = p.rot90_with_image(shape);
404            }
405            p
406        } else {
407            *self
408        }
409    }
410
411    pub fn is_close_to(&self, other: Self) -> bool
412    where
413        T: CoordinateBox,
414    {
415        self.x.is_close_to(other.x) && self.y.is_close_to(other.y)
416    }
417}
418
419impl<T> Mul<T> for Point<T>
420where
421    T: Calc,
422{
423    type Output = Self;
424    fn mul(self, rhs: T) -> Self::Output {
425        Point {
426            x: self.x * rhs,
427            y: self.y * rhs,
428        }
429    }
430}
431impl<T> Mul for Point<T>
432where
433    T: Calc,
434{
435    type Output = Self;
436    fn mul(self, rhs: Self) -> Self::Output {
437        Point {
438            x: self.x * rhs.x,
439            y: self.y * rhs.y,
440        }
441    }
442}
443impl<T> Div<T> for Point<T>
444where
445    T: Calc,
446{
447    type Output = Self;
448    fn div(self, rhs: T) -> Self::Output {
449        Point {
450            x: self.x / rhs,
451            y: self.y / rhs,
452        }
453    }
454}
455impl<T> Div for Point<T>
456where
457    T: Calc,
458{
459    type Output = Self;
460    fn div(self, rhs: Self) -> Self::Output {
461        Point {
462            x: self.x / rhs.x,
463            y: self.y / rhs.y,
464        }
465    }
466}
467
468impl<T> Sub for Point<T>
469where
470    T: Calc,
471{
472    type Output = Point<T>;
473    fn sub(self, rhs: Self) -> Self::Output {
474        Point {
475            x: self.x - rhs.x,
476            y: self.y - rhs.y,
477        }
478    }
479}
480impl<T> Add for Point<T>
481where
482    T: Calc,
483{
484    type Output = Point<T>;
485    fn add(self, rhs: Self) -> Self::Output {
486        Point {
487            x: self.x + rhs.x,
488            y: self.y + rhs.y,
489        }
490    }
491}
492
493impl<T> From<(T, T)> for Point<T>
494where
495    T: Calc,
496{
497    fn from(value: (T, T)) -> Self {
498        Self {
499            x: value.0,
500            y: value.1,
501        }
502    }
503}
504impl<T> From<Point<T>> for (T, T)
505where
506    T: Calc,
507{
508    fn from(p: Point<T>) -> (T, T) {
509        (p.x, p.y)
510    }
511}
512impl_point_into!(i32);
513pub type TPtF = f64;
514pub type TPtI = u32;
515pub type TPtS = i64;
516pub type PtF = Point<TPtF>;
517pub type PtI = Point<TPtI>;
518pub type PtS = Point<TPtS>;
519
520impl PtF {
521    #[must_use]
522    pub fn round_signed(&self) -> Point<i32> {
523        Point {
524            x: self.x.round() as i32,
525            y: self.y.round() as i32,
526        }
527    }
528}
529
530impl PtI {
531    pub fn from_signed(p: (i32, i32)) -> RvResult<Self> {
532        if p.0 < 0 || p.1 < 0 {
533            Err(rverr!(
534                "cannot create point with negative coordinates, {:?}",
535                p
536            ))
537        } else {
538            Ok(Self {
539                x: p.0 as u32,
540                y: p.1 as u32,
541            })
542        }
543    }
544    pub fn equals<U>(&self, other: (U, U)) -> bool
545    where
546        U: PartialEq,
547        PtI: Into<(U, U)>,
548    {
549        <Self as Into<(U, U)>>::into(*self) == other
550    }
551}
552
553impl From<PtI> for PtF {
554    fn from(p: PtI) -> Self {
555        (f64::from(p.x), f64::from(p.y)).into()
556    }
557}
558impl From<PtI> for PtS {
559    fn from(p: PtI) -> Self {
560        (TPtS::from(p.x), TPtS::from(p.y)).into()
561    }
562}
563impl From<PtS> for PtI {
564    fn from(p: PtS) -> Self {
565        ((p.x as u32), (p.y as u32)).into()
566    }
567}
568impl From<PtF> for PtI {
569    fn from(p: PtF) -> Self {
570        ((p.x as u32), (p.y as u32)).into()
571    }
572}
573impl From<(u32, u32)> for PtF {
574    fn from(x: (u32, u32)) -> Self {
575        (f64::from(x.0), f64::from(x.1)).into()
576    }
577}
578impl From<(f32, f32)> for PtI {
579    fn from(x: (f32, f32)) -> Self {
580        ((x.0 as u32), (x.1 as u32)).into()
581    }
582}
583impl From<(usize, usize)> for PtI {
584    fn from(x: (usize, usize)) -> Self {
585        ((x.0 as u32), (x.1 as u32)).into()
586    }
587}
588
589impl From<PtI> for (usize, usize) {
590    fn from(p: PtI) -> Self {
591        (p.x as usize, p.y as usize)
592    }
593}
594
595#[derive(Clone, Copy, Debug, PartialEq)]
596pub struct Circle {
597    pub center: PtF,
598    pub radius: TPtF,
599}
600
601pub fn color_with_intensity<CLR>(mut color: CLR, intensity: f64) -> CLR
602where
603    CLR: Pixel<Subpixel = u8>,
604{
605    let channels = color.channels_mut();
606    for channel in channels {
607        *channel = (f64::from(*channel) * intensity) as u8;
608    }
609    color
610}
611#[test]
612fn test_rot() {
613    let shape = Shape::new(5, 3);
614    let p = PtS { x: 2, y: 1 };
615    let p_rot_1 = p.rot90_with_image(shape);
616    assert!(p_rot_1.is_close_to(PtS { x: 1, y: 2 }));
617    let p_rot_2 = p.rot90_with_image_ntimes(shape, 2);
618    let p_rot_2_ = p_rot_1.rot90_with_image(shape.rot90_with_image_ntimes(1));
619    assert_eq!(p_rot_2, p_rot_2_);
620
621    let p = PtF { x: 2.5, y: 1.0 };
622    let p_rot_1 = p.rot90_with_image(shape);
623    assert!(p_rot_1.is_close_to(PtF { x: 1.0, y: 2.5 }));
624    assert!(p
625        .rot90_with_image_ntimes(shape, 2)
626        .is_close_to(p_rot_1.rot90_with_image(shape.rot90_with_image_ntimes(1))));
627
628    let shape = Shape::new(5, 10);
629    let p = PtS { x: 1, y: 2 };
630    let p_rot_1 = p.rot90_with_image(shape);
631    assert!(p_rot_1.is_close_to(PtS { x: 2, y: 3 }));
632    assert!(p
633        .rot90_with_image_ntimes(shape, 2)
634        .is_close_to(p_rot_1.rot90_with_image(shape.rot90_with_image_ntimes(1))));
635    let p = PtF { x: 1.0, y: 2.0 };
636    let p_rot_1 = p.rot90_with_image(shape);
637    assert!(p_rot_1.is_close_to(PtF { x: 2.0, y: 4.0 }));
638    assert!(p
639        .rot90_with_image_ntimes(shape, 2)
640        .is_close_to(p_rot_1.rot90_with_image(shape.rot90_with_image_ntimes(1))));
641    let p = PtF { x: 2.0, y: 4.0 };
642    let p_rot_1 = p.rot90_with_image(shape);
643    assert!(p_rot_1.is_close_to(PtF { x: 4.0, y: 3.0 }));
644    assert!(p
645        .rot90_with_image_ntimes(shape, 2)
646        .is_close_to(p_rot_1.rot90_with_image(shape.rot90_with_image_ntimes(1))));
647}