#[derive(Clone, Debug, Eq, PartialEq)] pub struct Image { pub pixels: Vec, // 64 for SD, 256 for HD } impl Image { /// flip image vertically fn flip(&mut self) { let mut pixels = Vec::with_capacity(64); for row in self.pixels.chunks(8).rev() { for pixel in row { pixels.push(*pixel); } } self.pixels = pixels; } /// mirror image horizontally fn mirror(&mut self) { let mut pixels = Vec::with_capacity(64); for row in self.pixels.chunks(8) { for i in (0..8).rev() { pixels.push(row[i]); } } self.pixels = pixels; } /// rotate image 90° clockwise fn rotate(&mut self) { let mut pixels = Vec::with_capacity(64); // start from bottom-left corner, work upward in that column, // then work on the next column to the right for x in 0..8 { for y in (0..8).rev() { pixels.push(self.pixels[(y * 8) + x]); } } self.pixels = pixels; } } impl From for Image { fn from(string: String) -> Image { let string = string.replace("NaN", "0"); let string = string.trim(); let lines: Vec<&str> = string.lines().collect(); let dimension = lines.len(); let mut pixels: Vec = Vec::new(); for line in lines { let line = &line[..dimension]; for char in line.chars().into_iter() { pixels.push(match char {'1' => 1, _ => 0}); } } Image { pixels } } } impl ToString for Image { fn to_string(&self) -> String { let mut string = String::new(); let sqrt = (self.pixels.len() as f64).sqrt() as usize; // 8 for SD, 16 for HD for line in self.pixels.chunks(sqrt) { for pixel in line { string.push_str(&format!("{}", *pixel)); } string.push('\n'); } string.pop(); // remove trailing newline string } } pub(crate) fn animation_frames_from_string(string: String) -> Vec { let frames: Vec<&str> = string.split(">").collect(); frames.iter().map(|&frame| Image::from(frame.to_string())).collect() } #[cfg(test)] mod test { use crate::image::{Image, animation_frames_from_string}; use crate::mock; #[test] fn test_image_from_string() { let output = Image::from( include_str!("test-resources/image").to_string() ); let expected = Image { pixels: vec![ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, ], }; assert_eq!(output, expected); } #[test] fn test_image_to_string() { let output = mock::image::chequers_1().to_string(); let expected = include_str!("test-resources/image-chequers-1").to_string(); assert_eq!(output, expected); } #[test] fn test_animation_frames_from_string() { let output = animation_frames_from_string( include_str!("test-resources/animation_frames").to_string() ); let expected = mock::image::animation_frames(); assert_eq!(output, expected); } /// lots of Bitsy games have editor errors where pixels can be placed out of bounds /// check that these extraneous pixels are stripped out #[test] fn test_image_out_of_bounds() { let output = Image::from( include_str!("test-resources/image-oob").to_string() ); let expected = Image { pixels: vec![ 1,1,1,1,1,1,1,1, 1,1,0,0,1,1,1,1, 1,0,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 0,0,0,0,0,0,0,0, ] }; assert_eq!(output, expected); } #[test] fn flip() { let mut image = crate::mock::image::asymmetrical(); image.flip(); let flipped = Image { pixels: vec![ 0,0,0,1,0,0,0,0, 0,0,1,0,0,0,0,0, 0,1,0,0,0,0,0,0, 1,0,0,0,0,0,0,0, 1,0,0,0,0,0,0,0, 0,1,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, ]}; assert_eq!(image, flipped); } #[test] fn mirror() { let mut image = crate::mock::image::asymmetrical(); image.mirror(); let mirrored = Image { pixels: vec![ 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,1,0, 0,0,0,0,0,0,0,1, 0,0,0,0,0,0,0,1, 0,0,0,0,0,0,1,0, 0,0,0,0,0,1,0,0, 0,0,0,0,1,0,0,0, ]}; assert_eq!(image, mirrored); } #[test] fn rotate() { let mut image = crate::mock::image::asymmetrical(); image.rotate(); let rotated = Image { pixels: vec![ 0,0,0,1,1,0,0,0, 0,0,1,0,0,1,0,0, 0,1,0,0,0,0,0,0, 1,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, ]}; assert_eq!(image, rotated); } }