bitsy-parser/src/image.rs

252 lines
6.6 KiB
Rust
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

use std::fmt;
use crate::error::ImageError;
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Image {
pub pixels: Vec<u8>, // 64 for SD, 256 for HD
}
impl Image {
pub fn invert(&mut self) {
self.pixels = self.pixels.iter().map(|pixel| 1 - pixel).collect();
}
/// flip image vertically
pub 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
pub 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
pub 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 counter-clockwise, just reverse x instead of y
for x in 0..8 {
for y in (0..8).rev() {
pixels.push(self.pixels[(y * 8) + x]);
}
}
self.pixels = pixels;
}
fn from_str(str: &str) -> Result<(Image, Vec<crate::Error>), crate::Error> {
let mut warnings = Vec::new();
if str.contains("NaN") {
warnings.push(crate::Error::Image { err: ImageError::MalformedPixel });
}
let string = str.trim().replace("NaN", "0");
let lines: Vec<&str> = string.lines().collect();
let dimension = lines.len();
let mut pixels: Vec<u8> = Vec::new();
for line in lines {
let line = &line[..dimension];
for char in line.chars().into_iter() {
pixels.push(match char {
'0' => 0,
'1' => 1,
_ => {
warnings.push(
crate::Error::Image { err: ImageError::MalformedPixel }
);
0
}
});
}
}
// 8×8 (normal) or 16×16 (Bitsy HD)
if [64, 256].contains(&pixels.len()) {
Ok((Image { pixels }, warnings))
} else {
Err(crate::Error::Image { err: ImageError::WrongSize })
}
}
}
impl fmt::Display for Image {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
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
write!(f, "{}", string)
}
}
/// todo return Result<(Vec<Image>, Vec<crate::Error>), crate::Error>
pub fn animation_frames_from_str(str: &str) -> Vec<Image> {
str
.split('>')
.collect::<Vec<&str>>()
.iter()
.map(|&frame| Image::from_str(frame).unwrap().0)
.collect()
}
#[cfg(test)]
mod test {
use crate::image::{Image, animation_frames_from_str};
use crate::mock;
#[test]
fn image_from_string() {
let (output, _) = Image::from_str(include_str!("test-resources/image")).unwrap();
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 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_str(
include_str!("test-resources/animation_frames")
);
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 image_out_of_bounds() {
let (output, _) = Image::from_str(include_str!("test-resources/image-oob")).unwrap();
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 invert() {
let mut output = crate::mock::image::chequers_1();
output.invert();
let expected = crate::mock::image::chequers_2();
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);
}
}