rvlib/
image_util.rs

1use core::cmp::Ordering::{Greater, Less};
2use std::ops::{Add, Sub};
3
4use image::{
5    buffer::ConvertBuffer, DynamicImage, GenericImage, GenericImageView, ImageBuffer, Luma, Pixel,
6    Rgb, Rgba,
7};
8use imageproc::definitions::Clamp;
9
10use crate::{
11    file_util::PixelEffect,
12    types::{ResultImage, ViewImage},
13};
14use rvimage_domain::{rverr, to_rv, ShapeI};
15
16pub fn read_image(path: &str) -> ResultImage {
17    image::ImageReader::open(path)
18        .map_err(to_rv)?
19        .with_guessed_format()
20        .map_err(to_rv)?
21        .decode()
22        .map_err(|e| rverr!("could not decode image {:?}. {:?}", path, e))
23}
24
25pub fn clipped_add<T>(x1: T, x2: T, clip_value: T) -> T
26where
27    T: Add<Output = T> + Sub<Output = T> + PartialOrd + Copy,
28{
29    if x1 >= clip_value || x2 >= clip_value || clip_value - x1 < x2 {
30        clip_value
31    } else {
32        x1 + x2
33    }
34}
35
36pub fn apply_to_matched_image<FnRgb8, FnRgba8, FnLuma8, FnRgb32F, T>(
37    im_d: &DynamicImage,
38    fn_rgb8: FnRgb8,
39    fn_rgba8: FnRgba8,
40    fn_luma8: FnLuma8,
41    fn_rgb32f: FnRgb32F,
42) -> T
43where
44    FnRgb8: Fn(&ImageBuffer<Rgb<u8>, Vec<u8>>) -> T,
45    FnRgba8: Fn(&ImageBuffer<Rgba<u8>, Vec<u8>>) -> T,
46    FnLuma8: Fn(&ImageBuffer<Luma<u8>, Vec<u8>>) -> T,
47    FnRgb32F: Fn(&ImageBuffer<Rgb<f32>, Vec<f32>>) -> T,
48{
49    match im_d {
50        DynamicImage::ImageRgb8(im) => fn_rgb8(im),
51        DynamicImage::ImageRgba8(im) => fn_rgba8(im),
52        DynamicImage::ImageLuma8(im) => fn_luma8(im),
53        DynamicImage::ImageRgb32F(im) => fn_rgb32f(im),
54        _ => panic!("Unsupported image type. {:?}", im_d.color()),
55    }
56}
57
58pub fn orig_to_0_255(
59    im_orig: &DynamicImage,
60    im_mask: &Option<ImageBuffer<Luma<u8>, Vec<u8>>>,
61) -> ViewImage {
62    let fn_rgb32f = |im: &ImageBuffer<Rgb<f32>, Vec<f32>>| {
63        let mut im = im.clone();
64        let max_val = im
65            .as_raw()
66            .iter()
67            .copied()
68            .max_by(|a, b| {
69                if a.is_nan() {
70                    Greater
71                } else if b.is_nan() {
72                    Less
73                } else {
74                    a.partial_cmp(b).unwrap()
75                }
76            })
77            .expect("an image should have a maximum value");
78        if max_val <= 1.0 {
79            for y in 0..im.height() {
80                for x in 0..im.width() {
81                    let p = im.get_pixel_mut(x, y);
82                    p.0 = [p.0[0] * 255.0, p.0[1] * 255.0, p.0[2] * 255.0];
83                }
84            }
85        } else if max_val > 255.0 {
86            for y in 0..im.height() {
87                for x in 0..im.width() {
88                    let is_pixel_relevant = if let Some(im_mask) = im_mask {
89                        im_mask.get_pixel(x, y)[0] > 0
90                    } else {
91                        true
92                    };
93                    let p = im.get_pixel_mut(x, y);
94                    p.0 = if is_pixel_relevant {
95                        [
96                            p.0[0] / max_val * 255.0,
97                            p.0[1] / max_val * 255.0,
98                            p.0[2] / max_val * 255.0,
99                        ]
100                    } else {
101                        [0.0, 0.0, 0.0]
102                    };
103                }
104            }
105        }
106        im.convert()
107    };
108    apply_to_matched_image(
109        im_orig,
110        |im| im.clone(),
111        |im| im.convert(),
112        |im| im.convert(),
113        fn_rgb32f,
114    )
115}
116pub fn effect_per_pixel<F: PixelEffect>(shape: ShapeI, mut f: F) {
117    for y in 0..shape.h {
118        for x in 0..shape.w {
119            f(x, y);
120        }
121    }
122}
123
124pub fn to_i64(x: (u32, u32)) -> (i64, i64) {
125    ((x.0 as i64), (x.1 as i64))
126}
127pub fn to_u32(x: (usize, usize)) -> (u32, u32) {
128    ((x.0 as u32), (x.1 as u32))
129}
130
131pub fn to_01(x: u8) -> f32 {
132    x as f32 / 255.0
133}
134
135pub fn apply_alpha(pixel_rgb: &[u8; 3], color: &[u8; 3], alpha: u8) -> Rgb<u8> {
136    let alpha_amount = to_01(alpha);
137    let apply_alpha_scalar = |x_anno, x_res| {
138        ((to_01(x_anno) * alpha_amount + (1.0 - alpha_amount) * to_01(x_res)) * 255.0) as u8
139    };
140    let [r_pixel, g_pixel, b_pixel] = pixel_rgb;
141    let [r_clr, g_clr, b_clr] = color;
142    Rgb([
143        apply_alpha_scalar(*r_pixel, *r_clr),
144        apply_alpha_scalar(*g_pixel, *g_clr),
145        apply_alpha_scalar(*b_pixel, *b_clr),
146    ])
147}
148
149pub fn draw_on_image<I: GenericImage, F: Fn(&I::Pixel) -> I::Pixel>(
150    mut im: I,
151    mut boundary_points: impl Iterator<Item = (u32, u32)>,
152    inner_points: impl Iterator<Item = (u32, u32)>,
153    color: &I::Pixel,
154    fn_inner_color: F,
155) -> I
156where
157    <<I as GenericImageView>::Pixel as image::Pixel>::Subpixel: Clamp<f32>,
158    f32: From<<<I as GenericImageView>::Pixel as Pixel>::Subpixel>,
159{
160    if let Some(first) = boundary_points.next() {
161        for inner_point in inner_points {
162            let (x, y) = inner_point;
163            let rgb = im.get_pixel(x, y);
164            im.put_pixel(x, y, fn_inner_color(&rgb));
165        }
166        let first = (first.0 as i32, first.1 as i32);
167        let mut prev = (first.0, first.1);
168        let blend = imageproc::pixelops::interpolate::<I::Pixel>;
169        for bp in boundary_points {
170            let start = prev;
171            let end = (bp.0 as i32, bp.1 as i32);
172            imageproc::drawing::draw_antialiased_line_segment_mut(
173                &mut im, start, end, *color, blend,
174            );
175            prev = end;
176        }
177    }
178    im
179}