rustautogui/imgtools/
mod.rs

1/*
2Functions used throughout the code that have more of a general purpose, like
3loading images from disk, converting image to black-white or RGB, cutting image
4and converting image to vector.
5*/
6use crate::errors::AutoGuiError;
7#[cfg(not(feature = "lite"))]
8use image::{
9    error::LimitError, DynamicImage, GrayImage, ImageBuffer, Luma, Pixel, Primitive, Rgb, Rgba,
10};
11#[cfg(not(feature = "lite"))]
12use rustfft::{num_complex::Complex, num_traits::ToPrimitive};
13
14#[cfg(not(feature = "lite"))]
15/// Loads image from the provided path and converts to black-white format
16/// Returns image in image::ImageBuffer format
17pub fn load_image_bw(location: &str) -> Result<ImageBuffer<Luma<u8>, Vec<u8>>, AutoGuiError> {
18    let img = image::ImageReader::open(location)?;
19
20    let img = img.decode()?;
21
22    let gray_image: ImageBuffer<Luma<u8>, Vec<u8>> = img.to_luma8();
23    Ok(gray_image)
24}
25#[cfg(not(feature = "lite"))]
26/// Loads image from the provided path and converts to RGBA format
27/// Returns image in image::ImageBuffer format
28pub fn load_image_rgba(location: &str) -> Result<ImageBuffer<Rgba<u8>, Vec<u8>>, AutoGuiError> {
29    let img = image::ImageReader::open(location)?;
30    let img = img.decode()?;
31    Ok(img.to_rgba8()) // return rgba image
32}
33#[cfg(not(feature = "lite"))]
34pub fn check_imagebuffer_color_scheme<P, T>(
35    image: &ImageBuffer<P, Vec<T>>,
36) -> Result<u32, AutoGuiError>
37where
38    P: Pixel<Subpixel = T> + 'static,
39    T: Primitive + ToPrimitive + 'static,
40{
41    let buff_len = image.as_raw().len() as u32;
42    let (img_w, img_h) = image.dimensions();
43    if (img_w * img_h) == 0 {
44        let err = "Error: The buffer provided is empty and has no size".to_string();
45        return Err(AutoGuiError::ImgError(err));
46    }
47    Ok(buff_len / (img_w * img_h))
48}
49#[cfg(not(feature = "lite"))]
50pub fn convert_t_imgbuffer_to_luma<P, T>(
51    image: &ImageBuffer<P, Vec<T>>,
52    color_scheme: u32,
53) -> Result<ImageBuffer<Luma<u8>, Vec<u8>>, AutoGuiError>
54where
55    P: Pixel<Subpixel = T> + 'static,
56    T: Primitive + ToPrimitive + 'static,
57{
58    let (img_w, img_h) = image.dimensions();
59    match color_scheme {
60        1 => {
61            // Black and white image (Luma)
62            // convert from Vec<T> to Vec<u8>
63            let raw_img: Result<Vec<u8>, AutoGuiError> = image
64                .as_raw()
65                .iter()
66                .map(|x| {
67                    x.to_u8().ok_or(AutoGuiError::ImgError(
68                        "Pixel conversion to raw failed".to_string(),
69                    ))
70                })
71                .collect();
72
73            ImageBuffer::<Luma<u8>, Vec<u8>>::from_raw(img_w, img_h, raw_img?).ok_or(
74                AutoGuiError::ImgError("failed to convert to Luma".to_string()),
75            )
76        }
77        3 => {
78            // Rgb
79            let raw_img: Result<Vec<u8>, AutoGuiError> = image
80                .as_raw()
81                .iter()
82                .map(|x| {
83                    x.to_u8().ok_or(AutoGuiError::ImgError(
84                        "Pixel conversion to raw failed".to_string(),
85                    ))
86                })
87                .collect();
88            let rgb_img = ImageBuffer::<Rgb<u8>, Vec<u8>>::from_raw(img_w, img_h, raw_img?).ok_or(
89                AutoGuiError::ImgError("Failed conversion to RGB".to_string()),
90            )?;
91            Ok(DynamicImage::ImageRgb8(rgb_img).to_luma8())
92        }
93        4 => {
94            // Rgba
95            let raw_img: Result<Vec<u8>, AutoGuiError> = image
96                .as_raw()
97                .iter()
98                .map(|x| {
99                    x.to_u8().ok_or(AutoGuiError::ImgError(
100                        "Pixel conversion to raw failed".to_string(),
101                    ))
102                })
103                .collect();
104            let rgba_img = ImageBuffer::<Rgba<u8>, Vec<u8>>::from_raw(img_w, img_h, raw_img?)
105                .ok_or(AutoGuiError::ImgError(
106                    "Failed conversion to RGBA".to_string(),
107                ))?;
108            Ok(DynamicImage::ImageRgba8(rgba_img).to_luma8())
109        }
110        _ => Err(AutoGuiError::ImgError(
111            "Unknown image format. Load works only for Rgb/Rgba/Luma(BW) formats".to_string(),
112        )),
113    }
114}
115#[cfg(not(feature = "lite"))]
116/// Does conversion from ImageBuffer RGBA to ImageBuffer Black and White(Luma)
117pub fn convert_rgba_to_bw(
118    image: ImageBuffer<Rgba<u8>, Vec<u8>>,
119) -> Result<ImageBuffer<Luma<u8>, Vec<u8>>, AutoGuiError> {
120    let (img_w, img_h) = image.dimensions();
121    let raw_img: Result<Vec<u8>, AutoGuiError> = image
122        .as_raw()
123        .iter()
124        .map(|x| {
125            x.to_u8().ok_or(AutoGuiError::ImgError(
126                "Pixel conversion to raw failed".to_string(),
127            ))
128        })
129        .collect();
130    let rgba_img = ImageBuffer::<Rgba<u8>, Vec<u8>>::from_raw(img_w, img_h, raw_img?).ok_or(
131        AutoGuiError::ImgError("Failed to convert to RGBA".to_string()),
132    )?;
133    Ok(DynamicImage::ImageRgba8(rgba_img).to_luma8())
134}
135#[cfg(not(feature = "lite"))]
136/// Does conversion from ImageBuffer RGBA to ImageBuffer Black and White(Luma)
137pub fn convert_rgba_to_bw_old(
138    image: ImageBuffer<Rgba<u8>, Vec<u8>>,
139) -> Result<ImageBuffer<Luma<u8>, Vec<u8>>, AutoGuiError> {
140    let mut grayscale_data: Vec<u8> = Vec::with_capacity(image.len());
141    let image_width = image.width();
142    let image_height = image.height();
143    for chunk in image.chunks_exact(4) {
144        let r = chunk[2] as u32;
145        let g = chunk[1] as u32;
146        let b = chunk[0] as u32;
147        // Calculate the grayscale value using the luminance formula
148        let gray_value = ((r * 30 + g * 59 + b * 11) / 100) as u8;
149        grayscale_data.push(gray_value);
150    }
151    GrayImage::from_raw(image_width, image_height, grayscale_data).ok_or(AutoGuiError::ImgError(
152        "Failed to convert to grayscale".to_string(),
153    ))
154}
155
156#[cfg(not(feature = "lite"))]
157/// Cuts Region of image. Inputs are top left x , y pixel coordinates on image,
158///     width and height of region and the image being cut.
159///     Returns image os same datatype
160pub fn cut_screen_region<T>(
161    x: u32,
162    y: u32,
163    width: u32,
164    height: u32,
165    screen_image: &ImageBuffer<T, Vec<u8>>,
166) -> ImageBuffer<T, Vec<u8>>
167where
168    T: Pixel<Subpixel = u8> + 'static,
169{
170    assert!(x + width <= screen_image.width());
171    assert!(y + height <= screen_image.height());
172
173    let mut sub_image: ImageBuffer<T, Vec<u8>> = ImageBuffer::new(width, height);
174
175    // copy pixels from the original image buffer to the sub-image buffer
176    for y_sub in 0..height {
177        for x_sub in 0..width {
178            let pixel = screen_image.get_pixel(x + x_sub, y + y_sub);
179            sub_image.put_pixel(x_sub, y_sub, *pixel);
180        }
181    }
182    sub_image
183}
184
185#[cfg(not(feature = "lite"))]
186///Converts Imagebuffer to Vector format
187pub fn imagebuffer_to_vec<T: Copy + Primitive + 'static>(
188    image: &ImageBuffer<Luma<T>, Vec<T>>,
189) -> Vec<Vec<T>> {
190    let (width, height) = image.dimensions();
191    let zero_pixel = image.get_pixel(0, 0)[0];
192    let mut vec: Vec<Vec<T>> = vec![vec![zero_pixel; width as usize]; height as usize];
193
194    for y in 0..height {
195        for x in 0..width {
196            vec[y as usize][x as usize] = image.get_pixel(x, y)[0];
197        }
198    }
199    vec
200}