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
use image::GenericImageView;
use nalgebra::Point2;

#[inline(always)]
fn saturate_coordinate(value: u32, bound: u32) -> u32 {
    if value < bound {
        value
    } else {
        bound - 1
    }
}

#[inline(always)]
pub fn saturating_get_pixel<I>(image: &I, x: i32, y: i32) -> I::Pixel
where
    I: GenericImageView,
{
    let x = if x.is_negative() {
        0u32
    } else {
        saturate_coordinate(x as u32, image.width())
    };
    let y = if y.is_negative() {
        0u32
    } else {
        saturate_coordinate(y as u32, image.height())
    };
    unsafe { image.unsafe_get_pixel(x, y) }
}

#[inline(always)]
pub fn get_pixel_with_fallback<I>(image: &I, x: i32, y: i32, fallback: I::Pixel) -> I::Pixel
where
    I: GenericImageView,
{
    if x.is_negative() || y.is_negative() {
        fallback
    } else {
        let x = x as u32;
        let y = y as u32;
        if image.in_bounds(x, y) {
            unsafe { image.unsafe_get_pixel(x, y) }
        } else {
            fallback
        }
    }
}

/// (x, y, size) -> (x0, x1, y0, y1)
#[inline]
pub fn roi_to_bbox(point: Point2<f32>, size: f32) -> (Point2<f32>, Point2<f32>) {
    let h = size / 2.0;
    (
        Point2::new(
            point.x - h,
            point.y - h
        ),
        Point2::new(
            point.x + h,
            point.y + h
        ),
    )
}

pub fn odd_median_mut(numbers: &mut[f32]) -> f32 {
    numbers.sort_by(|a, b| a.partial_cmp(b).unwrap());
    numbers[numbers.len() / 2]
}

#[cfg(test)]
mod tests {
    use super::*;
    use image::{GrayImage, Luma};

    #[test]
    fn check_get_pixel() {
        let (width, height) = (64u32, 48u32);
        let mut image = GrayImage::new(width, height);
        image.put_pixel(0, 0, Luma::from([42u8]));
        image.put_pixel(width - 1, height - 1, Luma::from([255u8]));

        let test_coords = vec![
            (0f32, 0f32),
            (-10f32, -10f32),
            ((width as f32 - 1f32), (height as f32 - 1f32)),
            (width as f32, height as f32),
        ];

        let lum_values = vec![42u8, 42u8, 255u8, 255u8];

        let fallbacks = vec![false, true, false, true];

        for ((x, y), (lum_value, should_fallback)) in test_coords
            .iter()
            .zip(lum_values.iter().zip(fallbacks.iter()))
        {
            let x = *x as i32;
            let y = *y as i32;
            println!("x: {}, y: {}", x, y);
            let lum = Luma::from([*lum_value]);
            assert_eq!(saturating_get_pixel(&image, x, y), lum);

            let fallback = Luma::from([0u8]);
            assert_eq!(
                get_pixel_with_fallback(&image, x, y, fallback.clone()),
                if *should_fallback { fallback } else { lum }
            );
        }
    }
}