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
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
//!  A module for generic representation of image.

// from rust
use std::collections::HashMap;

// from external crate

// from local crate
use error::{RasterError, RasterResult};
use color::Color;

/// A struct for easily representing a raster image.
#[derive(Debug, Clone)]
pub struct Image {
    /// Width of image in pixels.
    pub width: i32, //  i32 type is used as computation with negative integers is common.

    /// Height of image in pixels.
    pub height: i32,

    /// Vector containing sequence of bytes in RGBA format.
    pub bytes: Vec<u8>,
}

impl<'a> Image {
    /// Create a blank image. Default color is black.
    ///
    /// # Examples
    ///
    /// ```
    /// use raster::Image;
    ///
    /// let image = Image::blank(2, 2);
    ///
    /// println!("{:?}", image.bytes);
    ///
    /// assert_eq!(image.width, 2);
    /// assert_eq!(image.height, 2);
    /// ```
    pub fn blank(w: i32, h: i32) -> Image {
        let mut bytes = Vec::with_capacity((w * h) as usize * 4);
        for _ in 0..h {
            for _ in 0..w {
                bytes.extend_from_slice(&[0, 0, 0, 255]);
            }
        }
        Image {
            width: w,
            height: h,
            bytes: bytes,
        }
    }

    /// Check if there is a pixel at this location given by x and y.
    ///
    /// # Examples
    ///
    /// ```
    /// use raster::Image;
    ///
    /// let image = Image::blank(2, 2);
    ///
    /// assert_eq!(image.check_pixel(0, 0), true);
    /// assert_eq!(image.check_pixel(3, 3), false);
    /// ```
    pub fn check_pixel(&self, x: i32, y: i32) -> bool {
        if y < 0 || y > self.height {
            // TODO: check on actual vectors and not just width and height?
            false
        } else {
            !(x < 0 || x > self.width)
        }
    }

    /// Get the histogram of the image.
    ///
    /// # Examples
    ///
    /// Visualizing the histogram of the red channel of this image:
    ///
    /// Image:
    ///
    /// ![](https://kosinix.github.io/raster/in/sample.png)
    ///
    /// Code:
    ///
    /// ```
    /// use raster::Image;
    /// use raster::Color;
    ///
    /// let image = raster::open("tests/in/sample.png").unwrap();
    ///
    /// let (r_bin, _, _, _) = image.histogram().unwrap();
    ///
    /// let mut max_r_bin = 0;
    /// for (_, count) in &r_bin {
    ///     if *count > max_r_bin {
    ///         max_r_bin = *count;
    ///     }
    /// }
    ///
    /// let canvas_w = 256;
    /// let canvas_h: i32 = 100;
    /// let mut image = Image::blank(canvas_w, canvas_h);
    /// raster::editor::fill(&mut image, Color::rgb(214, 214, 214)).unwrap();
    ///
    /// for x in 0..256 as i32 { // 0-255
    ///     let key = x as u8;
    ///     match r_bin.get(&key) {
    ///         Some(count) => {
    ///             let height = (canvas_h as f32 * (*count as f32 / max_r_bin as f32)).round() as i32;
    ///
    ///             for y in canvas_h-height..canvas_h {
    ///                 image.set_pixel(x, y, &Color::hex("#e22d11").unwrap()).unwrap();
    ///             }
    ///         },
    ///         None => {}
    ///     }
    /// }
    ///
    /// raster::save(&image, "tests/out/histogram.png").unwrap();
    /// ```
    ///
    /// Histogram:
    ///
    /// ![](https://kosinix.github.io/raster/out/histogram.png)
    ///
    /// Photoshop's result:
    ///
    /// ![](https://kosinix.github.io/raster/in/histogram-ps.png)
    ///
    pub fn histogram(&self) -> RasterResult<Histogram> {
        let w = self.width;
        let h = self.height;

        let mut r_bin: HashMap<u8, u32> = HashMap::new();
        let mut g_bin: HashMap<u8, u32> = HashMap::new();
        let mut b_bin: HashMap<u8, u32> = HashMap::new();
        let mut a_bin: HashMap<u8, u32> = HashMap::new();
        for y in 0..h {
            for x in 0..w {
                let pixel = self.get_pixel(x, y)?;

                // Insert the key with a value of 0 if key does not exist yet. Then return the
                // count (which is zero).
                let r_bin_c = r_bin.entry(pixel.r).or_insert(0);
                *r_bin_c += 1; // +1 to the count.

                let g_bin_c = g_bin.entry(pixel.g).or_insert(0);
                *g_bin_c += 1;

                let b_bin_c = b_bin.entry(pixel.b).or_insert(0);
                *b_bin_c += 1;

                let a_bin_c = a_bin.entry(pixel.a).or_insert(0);
                *a_bin_c += 1;
            }
        }

        Ok((r_bin, g_bin, b_bin, a_bin))
    }

    /// Get pixel in a given x and y location of an image.
    ///
    /// # Errors
    ///
    /// If either the x or y coordinate falls out of bounds, this will fail with
    /// `RasterError::PixelOutOfBounds`.
    ///
    /// # Examples
    ///
    /// ```
    /// use raster::Image;
    /// use raster::Color;
    ///
    /// let mut image = Image::blank(2, 2); // Creates a 2x2 black image.
    ///
    /// let pixel = image.get_pixel(0, 0).unwrap();
    ///
    /// assert_eq!(0, pixel.r);
    /// assert_eq!(0, pixel.g);
    /// assert_eq!(0, pixel.b);
    /// assert_eq!(255, pixel.a);
    /// ```
    pub fn get_pixel(&self, x: i32, y: i32) -> RasterResult<Color> {
        let rgba = 4;
        let start = (y * self.width) + x;
        let start = start * rgba;
        let end = start + rgba;
        let len = self.bytes.len();

        if start as usize > len || end as usize > len {
            Err(RasterError::PixelOutOfBounds(x, y))
        } else {
            let slice = &self.bytes[start as usize..end as usize];
            Ok(Color {
                r: slice[0],
                g: slice[1],
                b: slice[2],
                a: slice[3],
            })
        }
    }

    /// Set pixel in a given x and y location of an image.
    ///
    /// # Errors
    ///
    /// If either the x or y coordinate falls out of bounds, this will fail with
    /// `RasterError::PixelOutOfBounds`.
    ///
    /// If the calculated byte start index is less than 0, this will fail with
    /// `RasterError::InvalidStartIndex`.
    ///
    /// # Examples
    ///
    /// ```
    /// use raster::Image;
    /// use raster::Color;
    ///
    /// let mut image = Image::blank(2, 2); // Creates a 2x2 black image.
    ///
    /// let _ = image.set_pixel(0, 0, &Color::rgba(255, 0, 0, 255)); // Set first pixel to red
    ///
    /// let pixel = image.get_pixel(0, 0).unwrap();
    ///
    /// assert_eq!(255, pixel.r);
    /// assert_eq!(0, pixel.g);
    /// assert_eq!(0, pixel.b);
    /// assert_eq!(255, pixel.a);
    /// ```
    pub fn set_pixel(&mut self, x: i32, y: i32, color: &Color) -> RasterResult<()> {
        let rgba = 4; // length
        let start = (y * &self.width) + x;
        let start = start * rgba;

        if x >= self.width || y >= self.height {
            Err(RasterError::PixelOutOfBounds(x, y))
        } else if start < 0 {
            Err(RasterError::InvalidStartIndex(start))
        } else {
            self.bytes[start as usize] = color.r;
            self.bytes[start as usize + 1] = color.g;
            self.bytes[start as usize + 2] = color.b;
            self.bytes[start as usize + 3] = color.a;

            Ok(())
        }
    }
}

/// Holds histogram information.
pub type Histogram = (
    HashMap<u8, u32>,
    HashMap<u8, u32>,
    HashMap<u8, u32>,
    HashMap<u8, u32>,
);

/// Enumeration of supported raster formats.
#[derive(Debug)]
pub enum ImageFormat {
    Gif,
    Jpeg,
    Png,
}