rusty_vision/
image.rs

1/**
2 * A single page Implementation of all the core
3 * functions for an Image type.
4 * The Image structure is purely CPU based and
5 * has size limitations.
6 *  ------------------------------------------------------------
7 * Images are stored as a row major vector.
8 * Formula: (y * self.width + x) * number-of-channels
9 *
10 * For example:
11 *     A 3 x 3 Image is stored as a 1D array of length 27
12 *     Which looks something like: (Displaying a 2D Structure for visualisation)
13 *     [
14 *         R00, G00, B00,         R01, G01, B01,          R02, G02, B02
15 *         R10, G10, B10          R11, G11, B11,          R12, G12, B12,
16 *         R20, G20, B20,         R21, G21, B21,          R22, G22, B22,
17 *     ]
18 * ---------------------------------------------------------------
19 *
20 * To get 1D index of a (x, y) pixel point,
21 *   = (y * image-width + x) * number-of-channels
22 *   where,
23 *      image-width: The width of Image in pixels
24 *      number-of-channels: The total number of color channels.
25 *              (For example, 3 for an RGB image and 4 for RGBA)
26 */
27use std::ops::{Index, IndexMut};
28
29use log::debug;
30
31use crate::color::{Color, ColorSpace};
32use crate::error::Error;
33use crate::geometry::{self, get_index_from_point_and_shape, Point, Shape};
34use crate::traits::*;
35use crate::types::*;
36
37#[derive(Debug, Clone)]
38pub struct Image {
39    shape: Shape,
40    data: Vec<u8>,
41    colorspace: ColorSpace,
42}
43
44impl Image {
45    pub fn new(shape: Shape, colorspace: ColorSpace) -> Self {
46        let data = vec![0; shape.size()];
47        assert_eq!(data.len(), shape.size());
48        debug!("Creating Image of size {}", data.len());
49        dbg!(data.len());
50        Image {
51            shape,
52            data,
53            colorspace,
54        }
55    }
56
57    pub fn from_data(data: Vec<u8>, shape: Shape, colorspace: ColorSpace) -> Self {
58        assert_eq!(data.len(), shape.size());
59        Image {
60            shape,
61            data,
62            colorspace,
63        }
64    }
65
66    pub fn swap(&mut self, idx_a: usize, idx_b: usize) {
67        self.data.swap(idx_a, idx_b);
68    }
69
70    pub fn slice(&self, start: usize, end: usize) -> &[u8] {
71        &self.data[start..end]
72    }
73
74    pub fn mut_slice(&mut self, start: usize, end: usize) -> &mut [u8] {
75        &mut self.data[start..end]
76    }
77
78    pub fn width(&self) -> usize {
79        self.shape.width
80    }
81
82    pub fn height(&self) -> usize {
83        self.shape.height
84    }
85
86    pub fn size(&self) -> usize {
87        self.shape.size()
88    }
89
90    pub fn crop(&self, topleft: Point, shape: Shape) -> Self {
91        let bottomright = topleft + shape;
92
93        let mut data = Vec::new();
94        for hindex in topleft.x..bottomright.x {
95            for vindex in topleft.y..bottomright.y {
96                for cindex in 0..self.shape.ndim {
97                    data.push(self[(hindex, vindex, cindex)]);
98                }
99            }
100        }
101
102        dbg!(data.len());
103
104        let image_shape = Shape::new(shape.width, shape.height, Some(self.colorspace.channels()));
105        Self::from_data(data, image_shape, self.colorspace)
106    }
107
108    ///
109    /// Calculates the 1D Index based on the provided (x, y)
110    /// Just wraps `get_index_from_xy` for sake of convenience
111    /// # Arguments
112    ///
113    /// * `Point` - Point containing desired x and y
114    ///
115    /// # Returns
116    ///
117    /// * usize containing Index if within bounds,
118    ///     otherwise Error
119    ///
120    pub fn get_index(&self, point: &Point) -> Result<usize, Error> {
121        self.get_index_from_xy(point.x, point.y)
122    }
123
124    /// Compute 1D Index (row-major) when provided with
125    /// the x, y coordinate, width and channel value.
126    ///
127    /// # Arguments
128    ///
129    /// * `x` - The x coordinate (should be between 0 - width of Image)
130    /// * `y` - The y coordinate (should be between 0 - height of Image)
131    ///
132    /// # Returns
133    ///
134    /// * usize containing Index if within bounds,
135    ///     otherwise Error
136    ///
137    pub fn get_index_from_xy(&self, x: usize, y: usize) -> Result<usize, Error> {
138        geometry::get_index_from_xywh(
139            x,
140            y,
141            self.width(),
142            self.height(),
143            self.colorspace.channels(),
144        )
145    }
146
147    ///
148    /// Return a slize of 1 pixel (including all channels)
149    ///
150    /// # Arguments
151    ///
152    /// * `x` - The x coordinate
153    /// * `y` - The y coordinate
154    ///
155    /// # Returns
156    ///
157    /// * An immutable &[u8]
158    ///
159    pub fn get_pixel(&self, point: &Point) -> &[u8] {
160        let channels = self.colorspace.channels();
161        let index = self.get_index(point).unwrap();
162        &self.data[index..index + channels]
163    }
164
165    /// Same as `get_pixel` but just a mutable reference
166    pub fn get_mut_pixel(&mut self, point: &Point) -> &mut [u8] {
167        let channels = self.colorspace.channels();
168        let index = self.get_index(point).unwrap();
169        &mut self.data[index..index + channels]
170    }
171
172    pub fn set_pixel(&mut self, point: &Point, color: &Color) -> Result<(), Error> {
173        let channels = self.colorspace.channels();
174        let pixel = self.get_mut_pixel(point);
175        for i in 0..channels {
176            pixel[i] = color[i];
177        }
178        Ok(())
179    }
180}
181
182///
183/// Overriding [] for 1D indexing.
184///
185/// # Returns an immutable reference to the requested Index
186///     
187///
188impl Index<usize> for Image {
189    type Output = u8;
190
191    fn index(&self, index: usize) -> &Self::Output {
192        &self.data[index]
193    }
194}
195
196///
197/// Overriding [] for 1D indexing.
198///
199/// # Returns a mutable reference to the requested Index
200///     
201///
202impl IndexMut<usize> for Image {
203    fn index_mut(&mut self, index: usize) -> &mut Self::Output {
204        &mut self.data[index]
205    }
206}
207
208///
209/// Overriding [] for 2D indexing.
210///
211/// # Returns an immutable reference to a given pixel
212///
213///
214impl Index<Index2D> for Image {
215    type Output = [u8];
216
217    fn index(&self, (x, y): Index2D) -> &Self::Output {
218        let point = Point::new(x, y);
219        self.get_pixel(&point)
220    }
221}
222
223impl IndexMut<Index2D> for Image {
224    fn index_mut(&mut self, (x, y): Index2D) -> &mut Self::Output {
225        let point = Point::new(x, y);
226        self.get_mut_pixel(&point)
227    }
228}
229
230///
231/// Overriding [] for 3D indexing.
232/// This is useful when you want to modify a specific channel
233/// For example, to modify green channel,
234/// image[(2, 2, 1)] = 255
235/// Where `1` = the channel number.
236///
237/// See ColorSpace for more information on Channel Numbers
238///
239impl Index<Index3D> for Image {
240    type Output = u8;
241
242    fn index(&self, (x, y, c): Index3D) -> &Self::Output {
243        let point = Point::new(x, y);
244        let pixel = self.get_pixel(&point);
245        &pixel[c]
246    }
247}
248
249impl IndexMut<Index3D> for Image {
250    fn index_mut(&mut self, (x, y, c): Index3D) -> &mut Self::Output {
251        let point = Point::new(x, y);
252        let pixel = self.get_mut_pixel(&point);
253        &mut pixel[c]
254    }
255}
256
257/* ------------------ DRAWING TRAIT ------------------ */
258impl Drawable<RectParams> for Image {
259    ///
260    /// Draw a rectangle on the Image
261    ///
262    fn draw(&mut self, params: &RectParams) -> Result<(), Error> {
263        let border_width = params.border_width.unwrap_or_default();
264
265        match params.fill_color {
266            Some(color) => {
267                let top_left = params.topleft;
268                let bottom_right = params.topleft + params.shape;
269
270                for i in top_left.x..bottom_right.x + 1 {
271                    for j in top_left.y..bottom_right.y + 1 {
272                        let point = Point::new(i, j);
273                        self.set_pixel(&point, &color)?;
274                    }
275                }
276            }
277            None => {
278                dbg!("Fill not enabled");
279            }
280        };
281
282        for i in 0..params.shape.width + border_width {
283            let range = match params.border_width {
284                Some(value) => (-((value / 2) as i32), ((value / 2) + 1) as i32),
285                None => (0, 1),
286            };
287
288            dbg!(range);
289            for k in range.0..range.1 {
290                let x_top_left = params.topleft.x;
291                let y_top_left = params.topleft.y;
292
293                // Top Edge
294                let top = Point::new(
295                    x_top_left + i - (border_width / 2),
296                    (y_top_left as i32 + k) as usize,
297                );
298                dbg!(top);
299                self.set_pixel(&top, &params.color)?;
300
301                // Left Side
302                let left = Point::new(
303                    (x_top_left as i32 + k) as usize,
304                    y_top_left + i - (border_width / 2),
305                );
306                dbg!(left);
307                self.set_pixel(&left, &params.color)?;
308
309                // Right Edge
310                let right = Point::new(
311                    ((x_top_left + params.shape.width) as i32 - k) as usize,
312                    y_top_left + i - (border_width / 2),
313                );
314                dbg!(right);
315                self.set_pixel(&right, &params.color)?;
316
317                // Bottom Edge
318                let bottom = Point::new(
319                    x_top_left + i - (border_width / 2),
320                    ((y_top_left + params.shape.height) as i32 - k) as usize,
321                );
322                dbg!(bottom);
323                self.set_pixel(&bottom, &params.color)?;
324            }
325        }
326
327        Ok(())
328    }
329}
330
331impl Drawable<CircleParams> for Image {
332    ///
333    /// Draw a circle on the Image using the Midpoint Circle Algorithm.
334    ///
335    fn draw(&mut self, params: &CircleParams) -> Result<(), Error> {
336        println!("Drawing circle {params:?}");
337
338        let mut x = params.radius;
339        let mut y = 0;
340
341        let mut p: f32 = 1.0 - params.radius as f32;
342
343        while x >= y {
344            dbg!(x, y, p);
345
346            let symmetric = [
347                [
348                    Point::new(params.center.x - x, params.center.y + y),
349                    Point::new(params.center.x + x, params.center.y + y),
350                ],
351                [
352                    Point::new(params.center.x - x, params.center.y - y),
353                    Point::new(params.center.x + x, params.center.y - y),
354                ],
355                [
356                    Point::new(params.center.x - y, params.center.y + x),
357                    Point::new(params.center.x + y, params.center.y + x),
358                ],
359                [
360                    Point::new(params.center.x - y, params.center.y - x),
361                    Point::new(params.center.x + y, params.center.y - x),
362                ],
363            ];
364
365            for pair in symmetric {
366                if let Some(color) = params.fill_color {
367                    let mut start = pair[0].clone();
368                    let end = pair[1].clone();
369                    while start.x < end.x - 1 {
370                        start.x += 1;
371                        dbg!(start);
372                        self.set_pixel(&start, &color)?;
373                    }
374                }
375
376                self.set_pixel(&pair[0], &params.color)?;
377                self.set_pixel(&pair[1], &params.color)?;
378            }
379
380            y += 1;
381
382            if p <= 0.0 {
383                p = p + 2.0 * y as f32 + 1.0;
384            } else {
385                x -= 1;
386                p = p + 2.0 * y as f32 - 2.0 * x as f32 + 1.0;
387            }
388        }
389
390        Ok(())
391    }
392}
393/* ------------------ ------- ----- ------------------ */
394
395/* ------------------ RESIZE TRAIT ------------------ */
396impl Resizable<NearestNeighborParams> for Image {
397    fn resize(&mut self, shape: Shape) -> Result<(), Error> {
398        todo!()
399    }
400}
401
402impl Resizable<BiCubicParams> for Image {
403    fn resize(&mut self, shape: Shape) -> Result<(), Error> {
404        todo!()
405    }
406}
407
408impl Resizable<BiLinearParams> for Image {
409    fn resize(&mut self, shape: Shape) -> Result<(), Error> {
410        todo!()
411    }
412}
413/* ------------------ ------ ----- ------------------ */
414
415/* ------------------ ROTATION TRAIT ------------------ */
416
417impl Rotatable<i32> for Image {
418    fn rotate(&mut self, value: i32) -> Result<(), Error> {
419        // Custom rotation by degrees.
420        todo!()
421    }
422}
423
424impl Rotatable<RotationType> for Image {
425    ///
426    /// Perform in-place rotation.
427    ///
428    fn rotate(&mut self, value: RotationType) -> Result<(), Error> {
429        let new_shape = match value {
430            RotationType::Clockwise90
431            | RotationType::Anticlockwise270
432            | RotationType::Clockwise270
433            | RotationType::Anticlockwise90 => Shape {
434                width: self.height(),
435                height: self.width(),
436                ndim: self.shape.ndim,
437            },
438            RotationType::Clockwise180 | RotationType::Anticlockwise180 => self.shape,
439            RotationType::Custom(_) => todo!(),
440        };
441
442        if new_shape == self.shape {
443            for x in 0..self.width() / 2 {
444                for y in 0..self.height() {
445                    let p1 = Point::new(x, y);
446                    let p2 = p1.relocate(&self.shape, value.degree());
447
448                    let idx_a = self.get_index(&p1).unwrap();
449                    let idx_b = get_index_from_point_and_shape(p2, &new_shape).unwrap();
450
451                    for c in 0..self.colorspace.channels() {
452                        self.swap(idx_a + c, idx_b + c);
453                    }
454                }
455            }
456        } else {
457            // TODO: Improve to in-place
458            let mut rotated = vec![0; self.size()];
459            let ndim = self.shape.ndim;
460            for x in 0..self.width() {
461                for y in 0..self.height() {
462                    let p1 = Point::new(x, y);
463                    let p2 = p1.relocate(&self.shape, value.degree());
464
465                    let idx_a = self.get_index(&p1).unwrap();
466                    let idx_b = get_index_from_point_and_shape(p2, &new_shape).unwrap();
467
468                    rotated[idx_b..ndim + idx_b].copy_from_slice(&self.data[idx_a..ndim + idx_a]);
469                }
470            }
471
472            self.data = rotated;
473            self.shape = new_shape;
474        }
475
476        Ok(())
477    }
478}
479/* ------------------ -------- ----- ------------------ */