diff --git a/src/image/mod.rs b/src/image/mod.rs index e38ce21..467133f 100644 --- a/src/image/mod.rs +++ b/src/image/mod.rs @@ -40,7 +40,7 @@ pub trait Sample: Into + PartialEq + Default + Copy + From + PartialOrd impl + PartialEq + Default + Copy + From + PartialOrd> Sample for T {} #[allow(unused)] -#[derive(Default)] +#[derive(Default, Debug)] pub struct Image where T: Sample, @@ -91,6 +91,9 @@ where pub fn path(&self) -> &PathBuf { &self.path } + pub fn pixels(&self) -> &Vec<(T, T, T, T)> { + &self.pixels + } /// Returns the iterator of the pixels vector pub fn iter(&self) -> Iter<'_, (T, T, T, T)> { diff --git a/src/image_loader/mod.rs b/src/image_loader/mod.rs new file mode 100644 index 0000000..c3625bd --- /dev/null +++ b/src/image_loader/mod.rs @@ -0,0 +1,343 @@ +use crate::image::Image; +use png::BitDepth; +use std::fs::File; +use std::path::Path; + +///# Image Loader +/// The image_loader function is a function which can use a path of an image to return some Metadata from the image.
+///It can also retrieve the rgba values of every pixel from the image and the path.
+///
+///# IMPORTANT
+/// Doesn't support pictures with the color type indexed!
+///
+///# Parameter +///The function has the parameter path with the type &Path.
+///
+///# Return variables +///The return value is a struct which includes four variables.
+///1. width [u32]: width contains the number of pixels in a row of the given png.
+///2. height [u32]: height contains the height of the picture in measured pixels.
+///3. pixel_vec [Vec<(f32, f32, f32, f32)>]: pixel_vec contains the rgba values of every pixel in the picture, saved in a vec themselves.
+///4. path [path.to_path_buf()]: is the path from the parameter.
+pub fn image_loader(path: &Path) -> Result, &'static str> { + let decoder = match File::open(path) { + Ok(file) => png::Decoder::new(file), + Err(_) => return Err("failed the decoder"), + }; + + let mut reader = match decoder.read_info() { + Ok(reader) => reader, + Err(_) => return Err("Failed to read PNG info"), + }; + + let mut buf = vec![0; reader.output_buffer_size()]; + + let info = match reader.next_frame(&mut buf) { + Ok(info) => info, + Err(_) => return Err("Failed to read PNG frame"), + }; + + let bit_depth = reader.info().bit_depth; + let color_type = reader.info().color_type; + let width = reader.info().width; + let height = reader.info().height; + let palette = &reader.info().palette; + + let idat = &buf[..info.buffer_size()]; + + println!("idat {:?} idat ", idat); + println!("palette {:?} palette", palette); + println!("depth {:?} depth", bit_depth); + + let pixel_vec = match color_type { + png::ColorType::Grayscale => grayscale_to_rgba(idat, bit_depth), + png::ColorType::GrayscaleAlpha => grayscale_alpha_to_rgba(idat, bit_depth), + png::ColorType::Rgb => rgb_to_rgba(idat, bit_depth), + png::ColorType::Rgba => decode_rgba(idat, bit_depth), + _ => panic!("Unsupported color type or bit depth"), + }?; + + let image: Image = Image::new( + width as usize, + height as usize, + pixel_vec, + path.to_path_buf(), + ); + + Ok(image) +} + +///# Grayscale to RGBA +///The grayscale_to_rgba function converts a IDAT chunk from an picture with the color type grayscale and the bit depth into an RGBA value.
+///
+///# Parameter +///The function has the following two parameters:
+///1. IDAT [&[u8]]: This array cointans the IDAT chunk of the given image.
+///2. bit_depth [BitDepth]: This variable contains the bit depth of the given image.
+///
+///# Return variables +///This function returns a vector filled with vectors filled with four [f32] variables, which are the RGBA values.
+///Also this function sets the alpha channel of the RGBA value to 255. +fn grayscale_to_rgba( + idat: &[u8], + bit_depth: BitDepth, +) -> Result, &'static str> { + let mut rgba_values = Vec::new(); + + let max_value: u32 = (1 << bit_depth as u32) - 1; + + for byte in idat { + let grayscale = match bit_depth { + BitDepth::One => ((*byte & 0x01) as u32 * max_value) as f32, + BitDepth::Two => ((*byte & 0x03) as u32 * max_value / 3) as f32, + BitDepth::Four => ((*byte & 0x0F) as u32 * max_value / 15) as f32, + BitDepth::Eight => *byte as f32, + BitDepth::Sixteen => ((*byte as u32 * 255) / max_value) as f32, + }; + + let rgba = (grayscale, grayscale, grayscale, 255_f32); + rgba_values.push(rgba); + } + + Ok(rgba_values) +} + +///# Grayscale Alpha to RGBA +///The grayscale_alpha_to_rgba function converts a IDAT chunk from a picture with the color type grayscale alpha and the bit depth into an RGBA value.
+///
+///# Parameter +///The function has the following two parameters:
+///1. IDAT [&[u8]]: This array contains the IDAT chunk of the given image.
+///2. bit_depth [BitDepth]: This variable contains the bit depth of the given image.
+///
+///# Return variables +///This function returns a vector filled with vectors filled with four [f32] variables, which are the RGBA values. +fn grayscale_alpha_to_rgba( + idat: &[u8], + bit_depth: BitDepth, +) -> Result, &'static str> { + let mut rgba_values = Vec::new(); + + let chunk_size; + + if bit_depth == BitDepth::Eight { + chunk_size = 2; + } else if bit_depth == BitDepth::Sixteen { + chunk_size = 4; + } else { + return Err("Invalid Bit Depth"); + } + + for pair in idat.chunks(chunk_size) { + if pair.len() < 2 { + return Err("Insufficient data"); + } + + let grayscale = match bit_depth { + BitDepth::Eight => pair[0] as f32, + BitDepth::Sixteen => ((pair[0] as u16) << 8 | pair[1] as u16) as f32, + _ => return Err("Unsupported bit depth"), + }; + + let alpha = match bit_depth { + BitDepth::Eight => pair[1] as f32, + BitDepth::Sixteen => ((pair[2] as u16) << 8 | pair[3] as u16) as f32, + _ => return Err("Unsupported bit depth"), + }; + + let rgba = (grayscale, grayscale, grayscale, alpha); + rgba_values.push(rgba); + } + + Ok(rgba_values) +} + +///# RGB to RGBA +///The tgb_to_rgba function converts a IDAT chunk from a picture with the color type rgb and the bit depth into an RGBA value.
+///
+///# Parameter +///The function has the following two parameters:
+///1. IDAT [&[u8]]: This array contains the IDAT chunk of the given image.
+///2. bit_depth [BitDepth]: This variable contains the bit depth of the given image.
+///
+///# Return variables +///This function returns a vector filled with vectors filled with four [f32] variables, which are the RGBA values. +///Also this function sets the alpha channel of the RGBA value to 255. +fn rgb_to_rgba( + idat: &[u8], + bit_depth: BitDepth, +) -> Result, &'static str> { + let mut rgba_values = Vec::new(); + + let chunk_size; + if bit_depth == BitDepth::Eight { + chunk_size = 3; + } else if bit_depth == BitDepth::Sixteen { + chunk_size = 6; + } else { + return Err("Invalid Bit Depth"); + } + + for group in idat.chunks(chunk_size) { + if group.len() < 3 { + return Err("Insufficient data"); + } + + let red = match bit_depth { + BitDepth::Eight => group[0] as f32, + BitDepth::Sixteen => ((group[0] as u16) << 8 | group[1] as u16) as f32, + _ => return Err("Unsupported bit depth"), + }; + + let green = match bit_depth { + BitDepth::Eight => group[1] as f32, + BitDepth::Sixteen => ((group[2] as u16) << 8 | group[3] as u16) as f32, + _ => return Err("Unsupported bit depth"), + }; + + let blue = match bit_depth { + BitDepth::Eight => group[2] as f32, + BitDepth::Sixteen => ((group[4] as u16) << 8 | group[5] as u16) as f32, + _ => return Err("Unsupported bit depth"), + }; + + let rgba = (red, green, blue, 255_f32); + rgba_values.push(rgba); + } + + Ok(rgba_values) +} + +///# Decode RGBA +///The decode_rgba function converts a IDAT chunk from a picture with the color type rgba and the bit depth into an RGBA value.
+///
+///# Parameter +///The function has the following two parameters:
+///1. IDAT [&[u8]]: This array contains the IDAT chunk of the given image.
+///2. bit_depth [BitDepth]: This variable contains the bit depth of the given image.
+///
+///# Return variables +///This function returns a vector filled with vectors filled with four [f32] variables, which are the RGBA values. +fn decode_rgba( + idat: &[u8], + bit_depth: BitDepth, +) -> Result, &'static str> { + let mut rgba_values = Vec::new(); + + let chunk_size; + if bit_depth == BitDepth::Eight { + chunk_size = 4; + } else if bit_depth == BitDepth::Sixteen { + chunk_size = 8; + } else { + return Err("Invalid Bit Depth"); + } + + for group in idat.chunks(chunk_size) { + if group.len() < 4 { + return Err("Insufficient data"); + } + + let red = match bit_depth { + BitDepth::Eight => group[0] as f32, + BitDepth::Sixteen => ((group[0] as u16) << 8 | group[1] as u16) as f32, + _ => return Err("Unsupported bit depth"), + }; + + let green = match bit_depth { + BitDepth::Eight => group[1] as f32, + BitDepth::Sixteen => ((group[2] as u16) << 8 | group[3] as u16) as f32, + _ => return Err("Unsupported bit depth"), + }; + + let blue = match bit_depth { + BitDepth::Eight => group[2] as f32, + BitDepth::Sixteen => ((group[4] as u16) << 8 | group[5] as u16) as f32, + _ => return Err("Unsupported bit depth"), + }; + + let alpha = match bit_depth { + BitDepth::Eight => group[3] as f32, + BitDepth::Sixteen => ((group[6] as u16) << 8 | group[7] as u16) as f32, + _ => return Err("Unsupported bit depth"), + }; + + let rgba = (red, green, blue, alpha); + rgba_values.push(rgba); + } + + Ok(rgba_values) +} + +#[cfg(test)] +mod test { + use std::path::PathBuf; + + use super::*; + + #[test] + fn test_image_loader() { + let path = Path::new("test_img/red_image.png"); + let test = image_loader(path); + + let image = Image::new( + 4, + 4, + vec![ + (255., 0., 0., 255.), + (0., 255., 0., 255.), + (0., 0., 255., 255.), + (255., 255., 255., 255.), + (127., 127., 127., 255.), + (0., 255., 255., 255.), + (255., 255., 0., 255.), + (255., 0., 0., 255.), + (127., 127., 127., 255.), + (255., 0., 255., 255.), + (255., 0., 0., 255.), + (255., 0., 0., 255.), + (255., 0., 0., 255.), + (255., 0., 0., 255.), + (255., 0., 0., 255.), + (0., 0., 0., 255.), + ], + PathBuf::default(), + ); + + assert_eq!(test.unwrap().pixels(), image.pixels()) + } + + #[test] + #[should_panic] + fn test_wrong_img() { + let path = Path::new("test_img/wrong pixel count.png"); + let test = image_loader(path); + + let image = Image::new( + 4, + 4, + vec![ + (255., 0., 0., 255.), + (255., 0., 0., 255.), + (255., 0., 0., 255.), + (255., 0., 0., 255.), + (255., 0., 0., 255.), + (255., 0., 0., 255.), + (255., 0., 0., 255.), + (255., 0., 0., 255.), + (255., 0., 0., 255.), + (255., 0., 0., 255.), + (255., 0., 0., 255.), + (255., 0., 0., 255.), + (255., 0., 0., 255.), + (255., 0., 0., 255.), + (255., 0., 0., 255.), + (255., 0., 0., 255.), + ], + PathBuf::default(), + ); + + //should panic because we are looking at a corrupt picture + assert_eq!(test.unwrap().pixels(), image.pixels()) + } +} diff --git a/src/lib.rs b/src/lib.rs index 2b26d05..38383f3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,7 @@ extern crate core; pub mod image; +pub mod image_loader; pub mod multithreading; pub fn add(left: usize, right: usize) -> usize { diff --git a/test_img/gray_image.png b/test_img/gray_image.png new file mode 100644 index 0000000..fb26f09 Binary files /dev/null and b/test_img/gray_image.png differ diff --git a/test_img/hut.png b/test_img/hut.png new file mode 100644 index 0000000..61ce192 Binary files /dev/null and b/test_img/hut.png differ diff --git a/test_img/indexed_image.png b/test_img/indexed_image.png new file mode 100644 index 0000000..6e98605 Binary files /dev/null and b/test_img/indexed_image.png differ diff --git a/test_img/red_image.png b/test_img/red_image.png new file mode 100644 index 0000000..896198b Binary files /dev/null and b/test_img/red_image.png differ diff --git a/test_img/rot.png b/test_img/rot.png new file mode 100644 index 0000000..a40594e Binary files /dev/null and b/test_img/rot.png differ diff --git a/test_img/town_blue.png b/test_img/town_blue.png new file mode 100644 index 0000000..a0a903d Binary files /dev/null and b/test_img/town_blue.png differ diff --git a/test_img/wrong colorspace_bitdepth.png b/test_img/wrong colorspace_bitdepth.png new file mode 100644 index 0000000..cdf1b48 Binary files /dev/null and b/test_img/wrong colorspace_bitdepth.png differ diff --git a/test_img/wrong pixel count.png b/test_img/wrong pixel count.png new file mode 100644 index 0000000..5c0e1cb Binary files /dev/null and b/test_img/wrong pixel count.png differ diff --git a/test_img/wrong size.png b/test_img/wrong size.png new file mode 100644 index 0000000..146a603 Binary files /dev/null and b/test_img/wrong size.png differ