use std::path::Path;
use anyhow::{bail, Context, Result};
use dotzuki_engine::render::Rgba;
pub struct PngTileset {
pixels: Vec<Rgba>,
tile_w: u32,
tile_h: u32,
count: usize,
}
impl PngTileset {
pub fn from_png_bytes(bytes: &[u8], tile_w: u32, tile_h: u32) -> Result<Self> {
if tile_w == 0 || tile_h == 0 {
bail!("tileset tile size must be non-zero (got {tile_w}x{tile_h})");
}
let img = image::load_from_memory(bytes)
.context("failed to decode tileset PNG")?
.to_rgba8();
let (w, h) = img.dimensions();
if w == 0 || h == 0 || w % tile_w != 0 || h % tile_h != 0 {
bail!(
"tileset image {w}x{h} is not a multiple of tile size {tile_w}x{tile_h}"
);
}
let rgba: Vec<Rgba> = img
.pixels()
.map(|p| Rgba::new(p.0[0], p.0[1], p.0[2], p.0[3]))
.collect();
Ok(Self::from_rgba(&rgba, w, h, tile_w, tile_h))
}
pub fn load(path: &Path, tile_w: u32, tile_h: u32) -> Result<Self> {
let bytes = std::fs::read(path)
.with_context(|| format!("failed to read tileset {}", path.display()))?;
Self::from_png_bytes(&bytes, tile_w, tile_h)
.with_context(|| format!("invalid tileset {}", path.display()))
}
fn from_rgba(rgba: &[Rgba], w: u32, h: u32, tile_w: u32, tile_h: u32) -> Self {
let cols = (w / tile_w) as usize;
let rows = (h / tile_h) as usize;
let count = cols * rows;
let (tw, th) = (tile_w as usize, tile_h as usize);
let mut pixels = vec![Rgba::TRANSPARENT; count * tw * th];
for ty in 0..rows {
for tx in 0..cols {
let tile_index = ty * cols + tx;
for py in 0..th {
let src_y = ty * th + py;
for px in 0..tw {
let src_x = tx * tw + px;
pixels[tile_index * tw * th + py * tw + px] =
rgba[src_y * w as usize + src_x];
}
}
}
}
Self {
pixels,
tile_w,
tile_h,
count,
}
}
#[inline]
pub fn tile_count(&self) -> usize {
self.count
}
#[inline]
pub fn tile_size(&self) -> (u32, u32) {
(self.tile_w, self.tile_h)
}
#[inline]
pub fn gid_pixel(&self, gid: u16, px: u8, py: u8) -> Rgba {
if gid == 0 {
return Rgba::TRANSPARENT;
}
let idx = (gid - 1) as usize;
if idx >= self.count {
return Rgba::TRANSPARENT;
}
let (tw, th) = (self.tile_w as usize, self.tile_h as usize);
let (px, py) = (px as usize, py as usize);
if px >= tw || py >= th {
return Rgba::TRANSPARENT;
}
self.pixels[idx * tw * th + py * tw + px]
}
}