use serde::Deserialize; use std::{collections::HashMap, error::Error, fs::File, io::BufReader, path::Path}; fn load_img(path: &Path) -> Result<(Vec, u32, u32), Box> { let mut decoder = png::Decoder::new(BufReader::new(File::open(path)?)); decoder.set_transformations(png::Transformations::IDENTITY); let mut reader = decoder.read_info()?; // only None if it's too big for memory let mut buf = vec![0; reader.output_buffer_size().unwrap()]; let info = reader.next_frame(&mut buf)?; if info.color_type != png::ColorType::Indexed { return Err(format!("{:#?}: not indexed", path.to_str()).into()); } if info.bit_depth != png::BitDepth::Eight { return Err(format!( "{:#?}: expected 8-bit, got {:?}", path.to_str(), info.bit_depth ) .into()); } buf.truncate(info.buffer_size()); Ok((buf, info.width, info.height)) } #[derive(Deserialize, Clone, Copy, Debug)] pub struct TileDimensions { pub short: u32, pub long: u32, } #[derive(Deserialize, Clone, Copy, Debug)] pub enum TilesetPixelType { Void = 0, Terrain, } #[derive(Deserialize, Debug)] struct TilesetManifest { dimensions: TileDimensions, palette: HashMap, } fn load_manifest(path: &Path) -> Result> { Ok(toml::from_str(&std::fs::read_to_string(path)?)?) } #[derive(Debug)] pub struct Tile { pub orientation: TileOrientation, pub dimensions: TileDimensions, pub pixels: Vec, } #[derive(Debug)] pub struct Tileset { pub dimensions: TileDimensions, pub vertical_tiles: Vec, pub horizontal_tiles: Vec, } #[derive(Debug, Clone, Copy)] pub enum TileOrientation { Horizontal, Vertical, } fn parse_row( y: u32, img: &(Vec, u32, u32), manifest: &TilesetManifest, orientation: TileOrientation, ) -> Result, String> { let ey = match orientation { TileOrientation::Horizontal => y + manifest.dimensions.short, TileOrientation::Vertical => y + manifest.dimensions.long, }; let mut tiles = Vec::new(); let mut x = 1; loop { if x >= img.1 { break; } let left_border = img.0[(x - 1 + y * img.1) as usize]; // 2 = border color if left_border != 2 { break; } let mut tile_pixels = Vec::new(); let ex = match orientation { TileOrientation::Horizontal => x + manifest.dimensions.long, TileOrientation::Vertical => x + manifest.dimensions.short, }; for py in y..ey { for px in x..ex { let idx = img.0[(px + py * img.1) as usize]; let palette = manifest.palette.get(&idx); match palette { Some(pixel_type) => tile_pixels.push(*pixel_type), None => { return Err(format!( "Pixel ({px},{py}) resolved to idx {idx} which isn't in the palette" )); } } } } debug_assert!( tile_pixels.len() == (manifest.dimensions.long * manifest.dimensions.short) as usize ); tiles.push(Tile { orientation, dimensions: manifest.dimensions, pixels: tile_pixels, }); x = ex + 3; } Ok(tiles) } fn parse_tiles( img: &(Vec, u32, u32), manifest: &TilesetManifest, ) -> Result<(Vec, Vec), String> { let mut horizontal_tiles = Vec::new(); let mut vertical_tiles = Vec::new(); let mut y = 0; loop { if y >= img.2 { break; } let edge = img.0[((y) * img.1) as usize] == 2; if !edge { y += 1; continue; } let orientation = if img.0[((y + manifest.dimensions.short + 2) * img.1) as usize] == 0 { TileOrientation::Horizontal } else { TileOrientation::Vertical }; let parse_result = parse_row(y + 1, img, manifest, orientation); match parse_result { Ok(mut tiles) => match orientation { TileOrientation::Horizontal => { println!("Parsed horizontal row {y}. {n} entries", n = tiles.len()); horizontal_tiles.append(&mut tiles); y += manifest.dimensions.short + 3; } TileOrientation::Vertical => { println!("Parsed vertical row {y}. {n} entries", n = tiles.len()); vertical_tiles.append(&mut tiles); y += manifest.dimensions.long + 3; } }, Err(e) => { return Err(format!( "Failed to parse row {} ({:#?}): {e}", y + 1, orientation )); } } } Ok((horizontal_tiles, vertical_tiles)) } pub fn load_tileset(path: &str) -> Tileset { let img_result = load_img(&Path::join(Path::new(path), Path::new("img.png"))); match img_result { Ok(img_result) => { let manifest_result = load_manifest(&Path::join(Path::new(path), Path::new("manifest.toml"))); match manifest_result { Ok(manifest) => { debug_assert!(manifest.dimensions.long == manifest.dimensions.short * 2); let (horizontal_tiles, vertical_tiles) = parse_tiles(&img_result, &manifest).unwrap(); Tileset { dimensions: manifest.dimensions, vertical_tiles, horizontal_tiles, } } Err(e) => { panic!("Couldn't load sprite: {}", e) } } } Err(e) => { panic!("Couldn't load sprite: {}", e) } } }