bitsy_parser/
image.rs

1use std::fmt;
2
3#[derive(Clone, Debug, Eq, PartialEq)]
4pub struct Image {
5    pub pixels: Vec<u8>, // 64 for SD, 256 for HD
6}
7
8impl Image {
9    pub fn invert(&mut self) {
10        self.pixels = self.pixels.iter().map(|pixel| 1 - pixel).collect();
11    }
12
13    /// flip image vertically
14    pub fn flip(&mut self) {
15        let mut pixels = Vec::with_capacity(64);
16
17        for row in self.pixels.chunks(8).rev() {
18            for pixel in row {
19                pixels.push(*pixel);
20            }
21        }
22
23        self.pixels = pixels;
24    }
25
26    /// mirror image horizontally
27    pub fn mirror(&mut self) {
28        let mut pixels = Vec::with_capacity(64);
29
30        for row in self.pixels.chunks(8) {
31            for i in (0..8).rev() {
32                pixels.push(row[i]);
33            }
34        }
35
36        self.pixels = pixels;
37    }
38
39    /// rotate image 90° clockwise
40    pub fn rotate(&mut self) {
41        let mut pixels = Vec::with_capacity(64);
42
43        // start from bottom-left corner, work upward in that column,
44        // then work on the next column to the right
45        // for counter-clockwise, just reverse x instead of y
46        for x in 0..8 {
47            for y in (0..8).rev() {
48                pixels.push(self.pixels[(y * 8) + x]);
49            }
50        }
51
52        self.pixels = pixels;
53    }
54
55    fn from_str(str: &str) -> Result<(Image, Vec<crate::Error>), crate::Error> {
56        let mut warnings = Vec::new();
57
58        if str.contains("NaN") {
59            warnings.push(crate::Error::Image);
60        }
61
62        let string = str.trim().replace("NaN", "0");
63
64        let lines: Vec<&str> = string.lines().collect();
65        let dimension = lines.len();
66        let mut pixels: Vec<u8> = Vec::new();
67
68        for line in lines {
69            let line = &line[..dimension];
70            for char in line.chars().into_iter() {
71                // todo push warning on integers other than 0/1
72                pixels.push(match char {'1' => 1, _ => 0});
73            }
74        }
75
76        if [64, 256].contains(&pixels.len()) {
77            Ok((Image { pixels }, warnings))
78        } else {
79            Err(crate::Error::Image)
80        }
81    }
82}
83
84impl fmt::Display for Image {
85    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
86        let mut string = String::new();
87
88        let sqrt = (self.pixels.len() as f64).sqrt() as usize; // 8 for SD, 16 for HD
89        for line in self.pixels.chunks(sqrt) {
90            for pixel in line {
91                string.push_str(&format!("{}", *pixel));
92            }
93            string.push('\n');
94        }
95
96        string.pop(); // remove trailing newline
97
98        write!(f, "{}", string)
99    }
100}
101
102/// todo return Result<(Vec<Image>, Vec<crate::Error>), crate::Error>
103pub fn animation_frames_from_str(str: &str) -> Vec<Image> {
104    str
105        .split('>')
106        .collect::<Vec<&str>>()
107        .iter()
108        .map(|&frame| Image::from_str(frame).unwrap().0)
109        .collect()
110}
111
112#[cfg(test)]
113mod test {
114    use crate::image::{Image, animation_frames_from_str};
115    use crate::mock;
116
117    #[test]
118    fn image_from_string() {
119        let (output, _) = Image::from_str(include_str!("test-resources/image")).unwrap();
120
121        let expected = Image {
122            pixels: vec![
123                1, 1, 1, 1, 1, 1, 1, 1,
124                1, 1, 0, 0, 1, 1, 1, 1,
125                1, 0, 1, 1, 1, 1, 1, 1,
126                1, 1, 1, 1, 1, 1, 1, 1,
127                1, 1, 1, 1, 1, 1, 1, 1,
128                1, 1, 1, 1, 1, 1, 1, 1,
129                1, 1, 1, 1, 1, 1, 1, 1,
130                1, 1, 1, 1, 1, 1, 1, 1,
131            ],
132        };
133
134        assert_eq!(output, expected);
135    }
136
137    #[test]
138    fn image_to_string() {
139        let output = mock::image::chequers_1().to_string();
140        let expected = include_str!("test-resources/image-chequers-1").to_string();
141        assert_eq!(output, expected);
142    }
143
144    #[test]
145    fn test_animation_frames_from_string() {
146        let output = animation_frames_from_str(
147            include_str!("test-resources/animation_frames")
148        );
149
150        let expected = mock::image::animation_frames();
151
152        assert_eq!(output, expected);
153    }
154
155    /// lots of Bitsy games have editor errors where pixels can be placed out of bounds
156    /// check that these extraneous pixels are stripped out
157    #[test]
158    fn image_out_of_bounds() {
159        let (output, _) = Image::from_str(include_str!("test-resources/image-oob")).unwrap();
160
161        let expected = Image {
162            pixels: vec![
163                1,1,1,1,1,1,1,1,
164                1,1,0,0,1,1,1,1,
165                1,0,1,1,1,1,1,1,
166                1,1,1,1,1,1,1,1,
167                1,1,1,1,1,1,1,1,
168                1,1,1,1,1,1,1,1,
169                1,1,1,1,1,1,1,1,
170                0,0,0,0,0,0,0,0,
171            ]
172        };
173
174        assert_eq!(output, expected);
175    }
176
177    #[test]
178    fn invert() {
179        let mut output = crate::mock::image::chequers_1();
180        output.invert();
181        let expected = crate::mock::image::chequers_2();
182        assert_eq!(output, expected);
183    }
184
185    #[test]
186    fn flip() {
187        let mut image = crate::mock::image::asymmetrical();
188        image.flip();
189
190        let flipped = Image { pixels: vec![
191            0,0,0,1,0,0,0,0,
192            0,0,1,0,0,0,0,0,
193            0,1,0,0,0,0,0,0,
194            1,0,0,0,0,0,0,0,
195            1,0,0,0,0,0,0,0,
196            0,1,0,0,0,0,0,0,
197            0,0,0,0,0,0,0,0,
198            0,0,0,0,0,0,0,0,
199        ]};
200
201        assert_eq!(image, flipped);
202    }
203
204    #[test]
205    fn mirror() {
206        let mut image = crate::mock::image::asymmetrical();
207        image.mirror();
208
209        let mirrored = Image { pixels: vec![
210            0,0,0,0,0,0,0,0,
211            0,0,0,0,0,0,0,0,
212            0,0,0,0,0,0,1,0,
213            0,0,0,0,0,0,0,1,
214            0,0,0,0,0,0,0,1,
215            0,0,0,0,0,0,1,0,
216            0,0,0,0,0,1,0,0,
217            0,0,0,0,1,0,0,0,
218        ]};
219
220        assert_eq!(image, mirrored);
221    }
222
223    #[test]
224    fn rotate() {
225        let mut image = crate::mock::image::asymmetrical();
226        image.rotate();
227
228        let rotated = Image { pixels: vec![
229            0,0,0,1,1,0,0,0,
230            0,0,1,0,0,1,0,0,
231            0,1,0,0,0,0,0,0,
232            1,0,0,0,0,0,0,0,
233            0,0,0,0,0,0,0,0,
234            0,0,0,0,0,0,0,0,
235            0,0,0,0,0,0,0,0,
236            0,0,0,0,0,0,0,0,
237        ]};
238
239        assert_eq!(image, rotated);
240    }
241}