use super::{Palette, chunk};
use crate::Result;
use crate::io::Reader;
use crate::media::{Image, Rgba};
const FORMAT: &str = "NCGR";
raw_enum! {
pub enum Depth: u32 {
Four = 3,
Eight = 4,
}
}
impl Depth {
pub fn tile_len(self) -> usize {
match self {
Depth::Eight => 64,
_ => 32,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Tiles {
pub size: Option<(u16, u16)>,
pub depth: Depth,
pub step: Option<usize>,
pub tiled: bool,
pub data: Vec<u8>,
}
impl Tiles {
pub fn parse(bytes: &[u8]) -> Result<Self> {
Reader::new(FORMAT, bytes).expect(b"RGCN")?;
let char = chunk(FORMAT, bytes, b"RAHC")?;
let (height, width) = (char.u16_at(0)?, char.u16_at(2)?);
let mapping = char.u32_at(8)?;
let size = char.u32_at(16)? as usize;
let offset = char.u32_at(20)? as usize;
Ok(Self {
size: (width != 0xFFFF && height != 0xFFFF).then_some((width, height)),
depth: Depth::from_raw(char.u32_at(4)?),
step: (mapping & 0x10 != 0).then(|| 32 << (mapping >> 20 & 3)),
tiled: char.u32_at(12)? & 0xFF == 0,
data: char
.slice_at(offset, size.min(char.len().saturating_sub(offset)))?
.to_vec(),
})
}
pub fn indices(&self) -> Vec<u8> {
match self.depth {
Depth::Eight => self.data.clone(),
_ => self
.data
.iter()
.flat_map(|&byte| [byte & 0xF, byte >> 4])
.collect(),
}
}
pub fn tile(&self, number: usize) -> [u8; 64] {
let len = self.depth.tile_len();
let mut tile = [0; 64];
let start = number.saturating_mul(len);
if let Some(bytes) = self.data.get(start..start.saturating_add(len)) {
match self.depth {
Depth::Eight => tile.copy_from_slice(bytes),
_ => {
for (pair, &byte) in tile.as_chunks_mut().0.iter_mut().zip(bytes) {
*pair = [byte & 0xF, byte >> 4];
}
}
}
}
tile
}
pub fn image(&self, palette: &Palette, bank: u8) -> Image {
let tiles = self.data.len() / self.depth.tile_len();
let width = match self.size {
Some((width, _)) => width as usize,
None => 32,
};
let width = width.clamp(1, tiles.max(1));
let height = match self.size {
Some((_, height)) => (height as usize).min(tiles.div_ceil(width)),
None => tiles.div_ceil(width),
};
let (width, height) = (width as u32 * 8, height as u32 * 8);
let colors = self.colors(palette, bank);
if !self.tiled {
return Image::from_indices(width, height, &self.indices(), colors, true);
}
let mut image = Image::new(width, height);
for number in 0..tiles {
let (x, y) = (number as u32 % (width / 8), number as u32 / (width / 8));
draw_tile(
&mut image,
&self.tile(number),
colors,
(x * 8, y * 8),
(false, false),
);
}
image
}
pub(super) fn colors<'p>(&self, palette: &'p Palette, bank: u8) -> &'p [Rgba] {
match self.depth {
Depth::Eight => &palette.colors,
_ => palette.bank(bank),
}
}
}
pub(super) fn draw_tile(
image: &mut Image,
tile: &[u8; 64],
colors: &[Rgba],
(x, y): (u32, u32),
(flip_x, flip_y): (bool, bool),
) {
for row in 0..8 {
for column in 0..8 {
let index = tile[row * 8 + column];
let color = match colors.get(index as usize) {
Some(&color) if index != 0 => color,
_ => continue,
};
let tx = if flip_x { 7 - column } else { column } as u32;
let ty = if flip_y { 7 - row } else { row } as u32;
image.set(x + tx, y + ty, color);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::formats::g2d::testing::ncgr;
const COLORS: [Rgba; 3] = [[9, 9, 9, 255], [255, 0, 0, 255], [0, 255, 0, 255]];
fn palette() -> Palette {
Palette {
colors: COLORS.to_vec(),
}
}
#[test]
fn decodes_tiled_4_bit_graphics() {
let mut data = vec![0; 64];
data[0] = 0x10;
data[32 + 4] = 0x02;
let tiles = Tiles::parse(&ncgr((2, 1), 3, 0, &data)).unwrap();
assert_eq!(
(tiles.size, tiles.depth, tiles.tiled),
(Some((2, 1)), Depth::Four, true)
);
assert_eq!(tiles.step, Some(32));
let image = tiles.image(&palette(), 0);
assert_eq!((image.width, image.height), (16, 8));
assert_eq!(image.get(0, 0), Some([0; 4]));
assert_eq!(image.get(1, 0), Some(COLORS[1]));
assert_eq!(image.get(8, 1), Some(COLORS[2]));
}
#[test]
fn decodes_bitmaps_and_8_bit_graphics() {
let mut data = vec![0; 128];
data[9] = 2; let tiles = Tiles::parse(&ncgr((2, 1), 4, 1, &data)).unwrap();
assert!(!tiles.tiled);
assert_eq!(tiles.image(&palette(), 0).get(9, 0), Some(COLORS[2]));
assert_eq!(tiles.tile(0)[9], 2);
}
#[test]
fn graphics_of_unknown_size_lay_out_as_a_screen() {
let tiles = Tiles::parse(&ncgr((0xFFFF, 0xFFFF), 3, 0, &[0; 32 * 40])).unwrap();
assert_eq!(tiles.size, None);
let image = tiles.image(&palette(), 0);
assert_eq!((image.width, image.height), (256, 16));
assert_eq!(tiles.tile(99), [0; 64]);
}
}