visioncortex/
image.rs

1use std::fmt;
2use std::fmt::Write;
3
4pub use bit_vec::BitVec;
5
6use crate::{BoundingRect, Color, ColorName, ColorType, Field, PointF32, PointF64, PointI32};
7
8/// Image with 1 bit per pixel
9#[derive(Debug, Clone, Default)]
10pub struct BinaryImage {
11    pub pixels: BitVec,
12    pub width: usize,
13    pub height: usize,
14}
15
16/// Generalization of 2D array of pixels with any Item
17#[derive(Clone, Default)]
18pub struct ScalerField<T> {
19    field: Field<T>,
20}
21
22/// Component of `MonoImage`
23pub type MonoImageItem = u16;
24/// Image with grayscale values
25pub type MonoImage = ScalerField<MonoImageItem>;
26
27/// Image with 4 bytes per pixel
28#[derive(Clone, Default)]
29pub struct ColorImage {
30    pub pixels: Vec<u8>,
31    pub width: usize,
32    pub height: usize,
33}
34
35/// Iterate over each pixel of ColorImage
36pub struct ColorImageIter<'a> {
37    im: &'a ColorImage,
38    curr: usize,
39    stop: usize,
40}
41
42impl BinaryImage {
43    pub fn new_w_h(width: usize, height: usize) -> BinaryImage {
44        BinaryImage {
45            pixels: BitVec::from_elem(width * height, false),
46            width,
47            height,
48        }
49    }
50
51    pub fn get_pixel_at(&self, p: PointI32) -> bool {
52        self.get_pixel(p.x as usize, p.y as usize)
53    }
54
55    pub fn get_pixel(&self, x: usize, y: usize) -> bool {
56        let i = y * self.width + x;
57        self.pixels.get(i).unwrap()
58    }
59
60    pub fn get_pixel_at_safe(&self, p: PointI32) -> bool {
61        self.get_pixel_safe(p.x, p.y)
62    }
63
64    pub fn get_pixel_safe(&self, x: i32, y: i32) -> bool {
65        if  x >= 0 && x < self.width as i32 &&
66            y >= 0 && y < self.height as i32 {
67            return self.get_pixel(x as usize, y as usize);
68        }
69        false
70    }
71
72    pub fn set_pixel(&mut self, x: usize, y: usize, v: bool) {
73        let i = y * self.width + x;
74        self.pixels.set(i, v);
75    }
76
77    pub fn set_pixel_at(&mut self, p: PointI32, v: bool) {
78        self.set_pixel(p.x as usize, p.y as usize, v);
79    }
80
81    pub fn set_pixel_index(&mut self, i: usize, v: bool) {
82        self.pixels.set(i, v);
83    }
84
85    pub fn set_pixel_safe(&mut self, x: i32, y: i32, v: bool) -> bool {
86        if  x >= 0 && x < self.width as i32 &&
87            y >= 0 && y < self.height as i32 {
88            self.set_pixel(x as usize, y as usize, v);
89            return true;
90        }
91        false
92    }
93
94    pub fn set_pixel_at_safe(&mut self, p: PointI32, v: bool) {
95        self.set_pixel_safe(p.x, p.y, v);
96    }
97
98    pub fn bounding_rect(&self) -> BoundingRect {
99        let mut rect = BoundingRect::default();
100        for y in 0..self.height {
101            for x in 0..self.width {
102                if self.get_pixel(x, y) {
103                    rect.add_x_y(x as i32, y as i32);
104                }
105            }
106        }
107        rect
108    }
109
110    pub fn area(&self) -> u64 {
111        self.pixels.iter().filter(|x| *x).count() as u64
112    }
113
114    /// crop image to fit content
115    pub fn crop(&self) -> BinaryImage {
116        self.crop_with_rect(self.bounding_rect())
117    }
118
119    /// crop a specific area from image
120    pub fn crop_with_rect(&self, rect: BoundingRect) -> BinaryImage {
121        let mut image = BinaryImage::new_w_h(rect.width() as usize, rect.height() as usize);
122        for y in rect.top..rect.bottom {
123            for x in rect.left..rect.right {
124                if self.get_pixel(x as usize, y as usize) {
125                    image.set_pixel(
126                        x as usize - rect.left as usize,
127                        y as usize - rect.top as usize,
128                        true,
129                    );
130                }
131            }
132        }
133        image
134    }
135
136    /// expand the image while center original image so that there will be more space surrounding
137    pub fn uncrop(&self, new_width: usize, new_height: usize) -> BinaryImage {
138        let xx = (new_width - self.width) >> 1;
139        let yy = (new_height - self.height) >> 1;
140        let mut new_image = BinaryImage::new_w_h(new_width, new_height);
141        for y in 0..self.height {
142            for x in 0..self.width {
143                if self.get_pixel(x, y) {
144                    new_image.set_pixel(x + xx, y + yy, true);
145                }
146            }
147        }
148        new_image
149    }
150
151    pub fn from_string(string: &str) -> Self {
152        let mut width = 0;
153        let mut height = 0;
154        for line in string.lines() {
155            if height == 0 {
156                width = line.len();
157            }
158            height += 1;
159        }
160        let mut image = Self::new_w_h(width, height);
161        for (y, line) in string.lines().enumerate() {
162            for (x, c)   in line.chars().enumerate() {
163                image.set_pixel(x, y, c == '*');
164            }
165        }
166        image
167    }
168
169    pub fn rotate(&self, angle: f64) -> BinaryImage {
170        let rotated_width = (self.width as f64 * angle.cos().abs() + self.height as f64 * angle.sin().abs()).round() as usize;
171        let rotated_height = (self.width as f64 * angle.sin().abs() + self.height as f64 * angle.cos().abs()).round() as usize;
172        let mut rotated_image = BinaryImage::new_w_h(rotated_width, rotated_height);
173        let origin = PointF64::new(rotated_width as f64 / 2.0, rotated_height as f64 / 2.0);
174        let offset = PointF64::new(
175            (rotated_width as i32 - self.width as i32) as f64 / 2.0,
176            (rotated_height as i32 - self.height as i32) as f64 / 2.0
177        );
178        for y in 0..rotated_image.height {
179            for x in 0..rotated_image.width {
180                let rotated = PointF64::new(x as f64, y as f64).rotate(origin, -angle).translate(-offset);
181                rotated_image.set_pixel(
182                    x, y,
183                    self.get_pixel_safe(rotated.x.round() as i32, rotated.y.round() as i32)
184                );
185            }
186        }
187        rotated_image
188    }
189
190    /// Paste the content of `src` into `self`, with `offset` with respective to the upper-left corner.
191    pub fn paste_from(&mut self, src: &BinaryImage, offset: PointI32) {
192        for y in 0..src.height {
193            for x in 0..src.width {
194                if src.get_pixel(x, y) {
195                    self.set_pixel(
196                        x + offset.x as usize,
197                        y + offset.y as usize,
198                        true
199                    );
200                }
201            }
202        }
203    }
204
205    pub fn to_color_image(&self) -> ColorImage {
206        let mut image = ColorImage::new_w_h(self.width, self.height);
207        let black = Color::color(&ColorName::Black);
208        let white = Color::color(&ColorName::White);
209        for y in 0..self.height {
210            for x in 0..self.width {
211                image.set_pixel(x, y, if self.get_pixel(x, y) {
212                    &black
213                } else {
214                    &white
215                });
216            }
217        }
218        image
219    }
220}
221
222impl fmt::Display for BinaryImage {
223    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
224        for y in 0..self.height {
225            for x in 0..self.width {
226                f.write_char(if self.get_pixel(x, y) { '*' } else { '-' })?;
227            }
228            f.write_char('\n')?;
229        }
230        Ok(())
231    }
232}
233
234impl<T> ScalerField<T> where T: Default {
235    pub fn new_w_h(width: usize, height: usize) -> Self {
236        Self {
237            field: Field::with_default(width, height),
238        }
239    }
240}
241
242impl<T> ScalerField<T> where T: Clone {
243    pub fn get_pixel(&self, x: usize, y: usize) -> T {
244        self.field.get(self.field.index_at(x, y)).unwrap()
245    }
246
247    pub fn set_pixel(&mut self, x: usize, y: usize, v: T) {
248        self.field.replace(self.field.index_at(x, y), v);
249    }
250}
251
252impl Iterator for ColorImageIter<'_> {
253    type Item = Color;
254
255    fn next(&mut self) -> Option<Color> {
256        if self.curr < self.stop {
257            let res = self.im.get_pixel_at(self.curr);
258            self.curr += 1;
259            Some(res)
260        } else {
261            None
262        }
263    }
264}
265
266impl ColorImage {
267    pub fn new() -> Self {
268        Default::default()
269    }
270
271    pub fn new_w_h(width: usize, height: usize) -> Self {
272        Self {
273            pixels: vec![0; width * height * 4],
274            width,
275            height,
276        }
277    }
278
279    pub fn iter(&self) -> ColorImageIter {
280        ColorImageIter {
281            im: self,
282            curr: 0,
283            stop: self.width * self.height,
284        }
285    }
286
287    pub fn get_pixel(&self, x: usize, y: usize) -> Color {
288        let index = y * self.width + x;
289        self.get_pixel_at(index)
290    }
291
292    pub fn get_pixel_at_point_safe(&self, p: PointI32) -> Option<Color> {
293        self.get_pixel_safe(p.x, p.y)
294    }
295
296    pub fn get_pixel_safe(&self, x: i32, y: i32) -> Option<Color> {
297        if  x >= 0 && x < self.width as i32 &&
298            y >= 0 && y < self.height as i32 {
299            return Some(self.get_pixel(x as usize, y as usize));
300        }
301        None
302    }
303
304    pub fn get_pixel_at(&self, index: usize) -> Color {
305        let index = index * 4;
306        let r = self.pixels[index];
307        let g = self.pixels[index + 1];
308        let b = self.pixels[index + 2];
309        let a = self.pixels[index + 3];
310
311        Color::new_rgba(r, g, b, a)
312    }
313
314    pub fn set_pixel(&mut self, x: usize, y: usize, color: &Color) {
315        let index = y * self.width + x;
316        self.set_pixel_at(index, color);
317    }
318
319    pub fn set_pixel_at(&mut self, index: usize, color: &Color) {
320        let index = index * 4;
321        self.pixels[index] = color.r;
322        self.pixels[index + 1] = color.g;
323        self.pixels[index + 2] = color.b;
324        self.pixels[index + 3] = color.a;
325    }
326
327    pub fn to_binary_image<F>(&self, f: F) -> BinaryImage
328        where F: Fn(Color) -> bool {
329        let mut image = BinaryImage::new_w_h(self.width, self.height);
330        for y in 0..self.height {
331            for x in 0..self.width {
332                image.set_pixel(x, y, f(self.get_pixel(x, y)));
333            }
334        }
335        image
336    }
337
338    pub fn sample_pixel_at(&self, p: PointF32) -> Color {
339        bilinear_interpolate(self, p)
340    }
341
342    pub fn sample_pixel_at_safe(&self, p:PointF32) -> Option<Color> {
343        bilinear_interpolate_safe(self, p)
344    }
345}
346
347pub fn bilinear_interpolate_safe(im: &ColorImage, p: PointF32) -> Option<Color> {
348    if p.x.is_sign_negative() || p.y.is_sign_negative() || p.x > (im.width - 1) as f32 || p.y > (im.height - 1) as f32 {
349        None
350    } else {
351        Some(bilinear_interpolate(im, p))
352    }
353}
354
355pub fn bilinear_interpolate(im: &ColorImage, p: PointF32) -> Color {
356    let x_0 = p.x.floor() as usize;
357    let x_1 = p.x.ceil() as usize;
358    let y_0 = p.y.floor() as usize;
359    let y_1 = p.y.ceil() as usize;
360    let c_00 = im.get_pixel(x_0, y_0);
361    let c_01 = im.get_pixel(x_0, y_1);
362    let c_10 = im.get_pixel(x_1, y_0);
363    let c_11 = im.get_pixel(x_1, y_1);
364
365    let interpolate = |channel: usize| {
366        let f_00 = c_00.channel(channel).unwrap() as f32;
367        let f_01 = c_01.channel(channel).unwrap() as f32;
368        let f_10 = c_10.channel(channel).unwrap() as f32;
369        let f_11 = c_11.channel(channel).unwrap() as f32;
370        let x = p.x - p.x.floor();
371        let y = p.y - p.y.floor();
372
373        (f_00 * (1.0 - x) * (1.0 - y) +
374        f_10 * x * (1.0 - y) +
375        f_01 * (1.0 - x) * y +
376        f_11 * x * y) as u8
377    };
378
379    Color::new_rgba(
380        interpolate(0),
381        interpolate(1), 
382        interpolate(2), 
383        interpolate(3),
384    )
385}
386
387#[cfg(test)]
388mod tests {
389    use super::*;
390
391    #[test]
392    fn binary_image_crop() {
393        let mut image = BinaryImage::new_w_h(4, 4);
394        image.set_pixel(1, 1, true);
395        image.set_pixel(2, 2, true);
396        let crop = image.crop();
397        assert_eq!(crop.width, 2);
398        assert_eq!(crop.height, 2);
399        assert_eq!(crop.get_pixel(0, 0), true);
400        assert_eq!(crop.get_pixel(0, 1), false);
401        assert_eq!(crop.get_pixel(1, 0), false);
402        assert_eq!(crop.get_pixel(1, 1), true);
403    }
404
405    #[test]
406    fn image_as_string() {
407        let mut image = BinaryImage::new_w_h(2,2);
408        image.set_pixel(0,0,true);
409        image.set_pixel(1,1,true);
410        assert_eq!(image.to_string(),
411            "*-\n".to_owned()+
412            "-*\n");
413        let recover = BinaryImage::from_string(&image.to_string());
414        assert_eq!(image.width, recover.width);
415        assert_eq!(image.height, recover.height);
416        for y in 0..image.height {
417            for x in 0..image.width {
418                assert_eq!(image.get_pixel(x, y), recover.get_pixel(x, y));
419            }
420        }
421    }
422
423    #[test]
424    fn rotate_test() {
425        assert_eq!(
426            BinaryImage::from_string(&(
427            "-----------*************---------\n".to_owned()+
428            "---------*****************-------\n"+
429            "-------*********************-----\n"+
430            "-----************************----\n"+
431            "----**************************---\n"+
432            "---****************************--\n"+
433            "--*****************************--\n"+
434            "--******************************-\n"+
435            "-*******************************-\n"+
436            "-********************************\n"+
437            "*********************************\n"+
438            "*********************************\n"+
439            "********************************-\n"+
440            "********************************-\n"+
441            "********************************-\n"+
442            "*******************************--\n"+
443            "-******************************--\n"+
444            "-*****************************---\n"+
445            "--***************************----\n"+
446            "---*************************-----\n"+
447            "----***********************------\n"+
448            "-----*********************-------\n"+
449            "-------*****************---------\n"+
450            "---------************------------\n"
451            )).rotate(1.3962634015954636).to_string(),
452            "-----------------------------\n".to_owned()+
453            "-----------------------------\n"+
454            "-----------****-*------------\n"+
455            "---------**********----------\n"+
456            "-------*************---------\n"+
457            "------***************--------\n"+
458            "-----*****************-------\n"+
459            "-----*******************-----\n"+
460            "-----*******************-----\n"+
461            "----*********************----\n"+
462            "----*********************----\n"+
463            "---***********************---\n"+
464            "---************************--\n"+
465            "--*************************--\n"+
466            "---************************--\n"+
467            "---************************--\n"+
468            "---************************--\n"+
469            "---************************--\n"+
470            "---*************************-\n"+
471            "---*************************-\n"+
472            "----************************-\n"+
473            "----************************-\n"+
474            "----************************-\n"+
475            "----************************-\n"+
476            "----************************-\n"+
477            "-----***********************-\n"+
478            "------*********************--\n"+
479            "------*********************--\n"+
480            "-------*******************---\n"+
481            "--------*****************----\n"+
482            "---------****************----\n"+
483            "-----------**************----\n"+
484            "------------***********------\n"+
485            "---------------******--------\n"+
486            "------------------*----------\n"+
487            "-----------------------------\n"+
488            "-----------------------------\n"
489        );
490    }
491}