use std::io;
use std::ops;
pub const SCREEN_WIDTH: usize = 320;
pub const SCREEN_HEIGHT: usize = 200;
pub const SCREEN_PIXELS: usize = SCREEN_WIDTH * SCREEN_HEIGHT;
pub const COLORS: usize = 256;
pub const HEADER_SIZE: usize = 6;
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Image13h {
data: Vec<u8>,
width: usize,
height: usize,
}
impl Image13h {
pub fn width(&self) -> usize {
self.width
}
pub fn height(&self) -> usize {
self.height
}
pub fn data(&self) -> &[u8] {
&self.data[..]
}
pub fn data_mut(&mut self) -> &mut [u8] {
&mut self.data[..]
}
pub fn line(&self, line: usize) -> &[u8] {
assert!(line < self.height);
&self.data[line * self.width..(line + 1) * self.width]
}
pub fn mut_line(&mut self, line: usize) -> &mut [u8] {
&mut self.data[line * self.width..(line + 1) * self.width]
}
pub fn load<T: io::Read>(mut reader: T) -> Option<Image13h> {
let mut buffer = [0, 0];
let width = match reader.read_exact(&mut buffer) {
Err(_) => return None,
Ok(_) => u16::from_le_bytes(buffer) as usize,
};
let height = match reader.read_exact(&mut buffer) {
Err(_) => return None,
Ok(_) => u16::from_le_bytes(buffer) as usize,
};
if width == 0 || height == 0 {
return None;
}
match reader.read_exact(&mut buffer) {
Err(_) => return None,
Ok(_) => match buffer {
[1, 0] => (),
_ => return None,
},
}
let mut data = vec![0; width * height];
if reader.read_exact(&mut data).is_err() {
return None;
}
Some(Image13h {
width,
height,
data,
})
}
pub fn empty(width: usize, height: usize) -> Image13h {
Image13h::filled_with_color(width, height, 0)
}
pub fn empty_screen_sized() -> Image13h {
Image13h::empty(SCREEN_WIDTH, SCREEN_HEIGHT)
}
pub fn filled_with_color(width: usize, height: usize, color: u8) -> Image13h {
Image13h {
width,
height,
data: vec![color; width * height],
}
}
pub fn save<T: io::Write>(&self, mut writer: T) {
for dim in &[self.width, self.height] {
writer.write_all(&(*dim as u16).to_le_bytes()).unwrap();
}
writer.write_all(&[1, 0]).unwrap();
writer.write_all(&self.data).unwrap();
}
pub fn subimage(&self, rect: &Rect) -> Image13h {
let mut subimage = Self::empty(rect.width, rect.height);
for (dst_line, src_line) in (rect.top..rect.beyond_bottom()).enumerate() {
subimage
.mut_line(dst_line)
.copy_from_slice(&self.line(src_line)[rect.left..rect.beyond_right()]);
}
subimage
}
pub fn blit(&mut self, image: &Image13h, rect: &Rect) {
for (src_line, dst_line) in (rect.top..rect.beyond_bottom()).enumerate() {
self.mut_line(dst_line)[rect.left..rect.beyond_right()]
.copy_from_slice(image.line(src_line));
}
}
pub fn blit_whole(&mut self, image: &Image13h, x: usize, y: usize) {
self.blit(
image,
&Rect::from_ranges(x..x + image.width(), y..y + image.height()),
);
}
pub fn fill(&mut self, color: u8) {
let len = self.data.len();
self.data.clear();
self.data.resize(len, color);
}
}
#[derive(Debug)]
pub struct Rect {
pub left: usize,
pub top: usize,
pub width: usize,
pub height: usize,
}
impl Rect {
pub fn from_ranges(x: ops::Range<usize>, y: ops::Range<usize>) -> Rect {
Rect {
left: x.start,
top: y.start,
width: x.end - x.start,
height: y.end - y.start,
}
}
pub fn right_inclusive(&self) -> usize {
self.beyond_right() - 1
}
pub fn beyond_right(&self) -> usize {
self.left + self.width
}
pub fn bottom_inclusive(&self) -> usize {
self.beyond_bottom() - 1
}
pub fn beyond_bottom(&self) -> usize {
self.top + self.height
}
}
pub fn indices_to_rgb<T: io::Write>(indices: &[u8], palette: &[u8], mut writer: T) {
for color_index in indices {
let palette_offset = *color_index as usize * 3;
writer
.write_all(&palette[palette_offset..palette_offset + 3])
.unwrap();
}
}
#[cfg(test)]
mod tests {
use crate::image13h::{indices_to_rgb, Image13h, Rect};
static GOOD_DATA: [u8; 13] = [3, 0, 2, 0, 1, 0, 1, 2, 3, 4, 5, 6, 7];
#[test]
fn test_not_enough_data_is_an_error() {
for size in 0..12 {
dbg!(size);
assert!(Image13h::load(&GOOD_DATA[0..size]).is_none());
}
}
#[test]
fn test_invalid_header_is_an_error() {
let bad_data1 = [0, 0, 1, 0, 1, 0];
let bad_data2 = [1, 0, 0, 0, 1, 0];
let bad_data3 = [1, 0, 1, 0, 0, 0, 0, 0];
assert!(Image13h::load(&bad_data1[..]).is_none());
assert!(Image13h::load(&bad_data2[..]).is_none());
assert!(Image13h::load(&bad_data3[..]).is_none());
}
#[test]
fn test_loading_works() {
let image13h = Image13h::load(&GOOD_DATA[..]).unwrap();
assert_eq!(image13h.width(), 3);
assert_eq!(image13h.height(), 2);
assert_eq!(image13h.line(0), [1, 2, 3]);
assert_eq!(image13h.line(1), [4, 5, 6]);
}
#[test]
fn test_saving_works() {
let image13h = Image13h::load(&GOOD_DATA[..]).unwrap();
let mut buffer = Vec::new();
image13h.save(&mut buffer);
assert_eq!(buffer, &GOOD_DATA[0..buffer.len()]);
}
#[test]
fn test_rect_works() {
let rect = Rect::from_ranges(0..10, 10..14);
assert_eq!(rect.left, 0);
assert_eq!(rect.top, 10);
assert_eq!(rect.width, 10);
assert_eq!(rect.height, 4);
assert_eq!(rect.right_inclusive(), 9);
assert_eq!(rect.beyond_right(), 10);
assert_eq!(rect.bottom_inclusive(), 13);
assert_eq!(rect.beyond_bottom(), 14);
}
#[test]
fn test_subimage_works() {
let image = Image13h::load(&GOOD_DATA[..]).unwrap();
let subimage = image.subimage(&Rect::from_ranges(0..2, 0..2));
let mut expected_subimage = Image13h::empty(2, 2);
expected_subimage.mut_line(0).copy_from_slice(&[1, 2]);
expected_subimage.mut_line(1).copy_from_slice(&[4, 5]);
assert_eq!(subimage, expected_subimage);
}
#[test]
fn test_fill_works() {
let mut image = Image13h::empty(2, 1);
let mut expected_image = Image13h::empty(2, 1);
expected_image.mut_line(0).copy_from_slice(&[1, 1]);
image.fill(1);
assert_eq!(image, expected_image);
}
#[test]
fn test_blit_works() {
let mut main_image = Image13h::empty(3, 2);
let mut subimage = Image13h::empty(2, 1);
subimage.fill(1);
main_image.blit(&subimage, &Rect::from_ranges(1..3, 1..2));
let mut expected_image = Image13h::empty(3, 2);
expected_image.mut_line(1)[1..3].copy_from_slice(&[1, 1]);
assert_eq!(main_image, expected_image);
}
#[test]
fn test_indices_to_rgb_works() {
let indices = [1, 2, 0];
let palette = [0, 1, 2, 10, 11, 12, 20, 21, 22];
let expected_rgb = [10, 11, 12, 20, 21, 22, 0, 1, 2];
let mut buffer = Vec::new();
indices_to_rgb(&indices, &palette, &mut buffer);
assert_eq!(buffer, expected_rgb);
}
}