rustyphoenixpng 1.1.2

This project eases manipulation of ong image inside Phoenix ecosystem
Documentation
/***************************************
	Auteur : Pierre Aubert
	Mail : pierre.aubert@lapp.in2p3.fr
	Licence : CeCILL-C
****************************************/

use std::io;
use std::path::Path;
use std::fs::File;
use std::io::{BufWriter, BufReader};

use crate::pcolorvalue::PColorMap;

///Handle an Image in Png format
pub struct PImage{
	///Width of the image in pixels
	width: usize,
	///Height of the image in pixels
	height: usize,
	///Vector of rgba values
	vec_rgba: Vec<u8>,
}

impl PImage {
	///Constructor of a PImage
	/// # Parameters
	/// - `width` : width of the image in pixels
	/// - `height` : height of the image in pixels
	pub fn new(width: usize, height: usize) -> Self{
		PImage {
			width: width,
			height: height,
			vec_rgba: vec![0; width*height*4]
		}
	}
	///Constructor of a PImage from a file
	/// # Parameters
	/// - `filename` : name of the png file to be read
	/// # Return
	/// Initialised PImage
	pub fn from(filename: &Path) -> Self{
		let mut image: PImage = PImage { width: 0, height: 0, vec_rgba: vec![] };
		image.read(filename).unwrap();
		return image;
	}
	///Get the width of the PImage
	/// # Returns
	/// Number of pixels of the PImage width
	pub fn get_width(&self) -> usize{
		self.width
	}
	///Get the height of the PImage
	/// # Returns
	/// Number of pixels of the PImage height
	pub fn get_height(&self) -> usize{
		self.height
	}
	///Set the color of the PImage from a vector of data castable in f32
	/// # Parameters
	/// - `vec_data` : vector of data (of size width*height)
	/// - `colormap` : PColorMap which convert single value into color
	pub fn set_color(&mut self, vec_data: &Vec<f32>, colormap: &PColorMap){
		assert_eq!(self.width*self.height, vec_data.len());
		for i in 0..self.height{
			for j in 0..self.width{
				let index: usize = i*self.width + j;
				let color = colormap.interpolate(*vec_data.get(index).unwrap());
				self.set_component(index*4, color.0);
				self.set_component(index*4 + 1, color.1);
				self.set_component(index*4 + 2, color.2);
				self.set_component(index*4 + 3, color.3);
			}
		}
	}
	///Set the rgba value of a given pixel
	/// # Parameters
	/// `windex` : index of the pixel on the width axis
	/// `hindex` : index of the pixel on the height axis
	/// `r` : red color component
	/// `g` : green color component
	/// `b` : blue color component
	/// `a` : alpha color component (transparency)
	pub fn set_rgba(&mut self, windex: usize, hindex: usize, r: u8, g: u8, b: u8, a: u8){
		if windex > self.width {
			panic!("PImage::set_rgba : index on width = {} greater than image width = {}", windex, self.width);
		}
		if hindex > self.height {
			panic!("PImage::set_rgba : index on height = {} greater than image height = {}", hindex, self.height);
		}
		let index = (hindex*self.width + windex)*4;
		self.set_component(index, r);
		self.set_component(index + 1, g);
		self.set_component(index + 2, b);
		self.set_component(index + 3, a);
	}
	///Set the component of a pixel
	/// # Parameters
	/// - `index` ! index of the component in the PImage
	fn set_component(&mut self, index: usize, value: u8){
		match self.vec_rgba.get_mut(index) {
			Some(pixel) => *pixel = value,
			None => {}
		}
	}
	///Write the current PImage in a png file
	/// # Parameters
	/// - `filename` : name of the png file to write
	/// # Errors
	/// If the image cannot be saved
	pub fn write(&self, filename: &Path) -> io::Result<()>{
		let file = File::create(filename)?;
		let ref mut w = BufWriter::new(file);
		
		let mut encoder = png::Encoder::new(w, self.width as u32, self.height as u32);
		encoder.set_color(png::ColorType::Rgba);
		encoder.set_depth(png::BitDepth::Eight);
		encoder.set_source_gamma(png::ScaledFloat::from_scaled(45455));	// 1.0 / 2.2, scaled by 100000
		// encoder.set_source_gamma(png::ScaledFloat::new(1.0 / 2.2));	// 1.0 / 2.2, unscaled, but rounded
		let source_chromaticities = png::SourceChromaticities::new(	// Using unscaled instantiation here
			(0.31270, 0.32900),
			(0.64000, 0.33000),
			(0.30000, 0.60000),
			(0.15000, 0.06000)
		);
		encoder.set_source_chromaticities(source_chromaticities);
		let mut writer = encoder.write_header().unwrap();
		writer.write_image_data(&self.vec_rgba)?; // Save
		Ok(())
	}
	///Load the current png file into a PImage
	/// # Parameters
	/// - `filename` : name of the png file to read
	/// # Errors
	/// If the image cannot be read
	pub fn read(&mut self, filename: &Path) -> io::Result<()>{
		// The decoder is a build for reader and can be used to set various decoding options
		// via `Transformations`. The default output transformation is `Transformations::IDENTITY`.
		let decoder = png::Decoder::new(BufReader::new(File::open(filename).unwrap()));
		let mut reader = decoder.read_info().unwrap();
		// Allocate the output buffer.
		let mut buf = vec![0; reader.output_buffer_size().unwrap()];
		// Read the next frame. An APNG might contain multiple frames.
		let info = reader.next_frame(&mut buf).unwrap();
		
		*self = PImage::new(info.width as usize, info.height as usize);
		// Grab the bytes of the image.
		let bytes = &buf[..info.buffer_size()];
		self.vec_rgba = bytes.to_vec();
		// Inspect more details of the last read frame.
		// let in_animation = reader.info().frame_control.is_some();
		Ok(())
	}
}

#[cfg(test)]
mod tests {
	use super::*;
	use std::fs;
	use std::path::PathBuf;
	
	///Test the write of a PImage
	#[test]
	fn test_pimage_write() {
		fs::create_dir_all("generated").unwrap();
		let filename = PathBuf::from("generated/image.png");
		let image: PImage = PImage::new(100, 50);
		image.write(&filename).unwrap();
		let other: PImage = PImage::from(&filename);
		assert_eq!(other.get_width(), 100);
		assert_eq!(other.get_height(), 50);
	}
	///Test the set pixel
	#[test]
	fn test_pimage_set_pixel(){
		fs::create_dir_all("generated").unwrap();
		let filename = PathBuf::from("generated/set_pixel.png");
		let mut image: PImage = PImage::new(250, 200);
		for j in 0..image.get_height() {
			for i in 0..image.get_width() {
				let g: u8 = (i % 255) as u8;
				let b: u8 = (j % 255) as u8;
				image.set_rgba(i, j, 12, g, b, 255);
			}
		}
		image.write(&filename).unwrap();
	}
	///Test the set_color method of the PImage
	#[test]
	fn test_set_color(){
		fs::create_dir_all("generated").unwrap();
		let filename = PathBuf::from("generated/set_color.png");
		let vec_data: Vec<f32> = (0..250*200).into_iter().map(|index| ((index as f32)*0.025).cos().powf(2.0)*0.6).collect::<Vec<_>>();
		let mut image: PImage = PImage::new(250, 200);
		let colormap: PColorMap = PColorMap::gray_scott();
		image.set_color(&vec_data, &colormap);
		image.write(&filename).unwrap();
	}
}