use nom::{
IResult,
bytes::complete::tag,
sequence::{tuple, delimited},
character::complete::char,
bytes::complete::is_not,
multi::count,
};
#[derive(Debug, Clone, PartialEq)]
pub struct PaintLayer {
header: Header,
pub tiles: Vec<Tile>,
}
#[derive(Debug, Clone, PartialEq)]
struct Header {
version: u8,
tile_width: usize,
tile_height: usize,
pixel_size: usize,
num_tiles: usize,
}
#[derive(Debug, Clone, PartialEq)]
pub struct Tile {
pub x: usize,
pub y: usize,
pub width: usize,
pub height: usize,
pub pixel_size: usize,
pub data: Vec<u8>,
}
#[derive(Debug, PartialEq)]
enum Compression {
Lzf,
}
fn parse_usize(i: &[u8]) -> IResult<&[u8], usize> {
match lexical_core::parse_partial(i) {
Ok((value, processed)) => Ok((&i[processed..], value)),
Err(err) => panic!("parse_usize error: {:?}", err),
}
}
fn parse_compression(i: &[u8]) -> IResult<&[u8], Compression> {
tag("LZF")(i).map(|(i, _lzf)| (i, Compression::Lzf))
}
fn parse_header(input: &[u8]) -> IResult<&[u8], Header> {
let (input, (version, tile_width, tile_height, pixel_size, num_tiles)) = tuple((
delimited(tag("VERSION "), is_not("\n"), char('\n')),
delimited(tag("TILEWIDTH "), is_not("\n"), char('\n')),
delimited(tag("TILEHEIGHT "), is_not("\n"), char('\n')),
delimited(tag("PIXELSIZE "), is_not("\n"), char('\n')),
delimited(tag("DATA "), is_not("\n"), char('\n')),
))(input)?;
let version = lexical_core::parse(version).unwrap();
let tile_width = lexical_core::parse(tile_width).unwrap();
let tile_height = lexical_core::parse(tile_height).unwrap();
let pixel_size = lexical_core::parse(pixel_size).unwrap();
let num_tiles = lexical_core::parse(num_tiles).unwrap();
Ok((input, Header {
version,
tile_width,
tile_height,
pixel_size,
num_tiles,
}))
}
fn parse_tile(width: usize, height: usize, pixel_size: usize) -> impl Fn(&[u8]) -> IResult<&[u8], Tile> {
move |i| {
let (i, (x, _, y, _, _compression, _, length, _)) = tuple((
parse_usize,
char(','),
parse_usize,
char(','),
parse_compression,
char(','),
parse_usize,
char('\n'),
))(i)?;
let data = lzf::decompress(&i[1..length], width * height * pixel_size).unwrap();
let tile = Tile {
x,
y,
width,
height,
pixel_size,
data,
};
Ok((&i[length..], tile))
}
}
impl PaintLayer {
pub fn parse(input: &[u8]) -> IResult<&[u8], PaintLayer> {
let i = input;
let (i, header) = parse_header(i)?;
let (i, tiles) = count(parse_tile(header.tile_width, header.tile_height, header.pixel_size), header.num_tiles)(i)?;
let layer = PaintLayer {
header,
tiles,
};
Ok((i, layer))
}
pub fn assemble_tiles(&self, default_pixel: [u8; 4], width: usize, height: usize) -> Vec<u8> {
let mut pixels = Vec::with_capacity(width * height * 4);
unsafe { pixels.set_len(width * height * 4) };
for i in 0..(width * height) {
pixels[i * 4 ] = default_pixel[0];
pixels[i * 4 + 1] = default_pixel[1];
pixels[i * 4 + 2] = default_pixel[2];
pixels[i * 4 + 3] = default_pixel[3];
}
for tile in &self.tiles {
let x = tile.x;
let y = tile.y;
let nb_pixels = tile.width * tile.height;
let data = &tile.data;
for y in y..(y + tile.height) {
if y >= height {
continue;
}
for x in x..(x + tile.width) {
pixels[(y * width + x) * 4 ] = data[2 * nb_pixels + tile.width * (y % tile.height) + (x % tile.width)];
pixels[(y * width + x) * 4 + 1] = data[ nb_pixels + tile.width * (y % tile.height) + (x % tile.width)];
pixels[(y * width + x) * 4 + 2] = data[ tile.width * (y % tile.height) + (x % tile.width)];
pixels[(y * width + x) * 4 + 3] = data[3 * nb_pixels + tile.width * (y % tile.height) + (x % tile.width)];
}
}
}
pixels
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn header() {
let header = parse_header(b"VERSION 2\nTILEWIDTH 64\nTILEHEIGHT 64\nPIXELSIZE 4\nDATA 240\n").unwrap().1;
assert_eq!(header.version, 2);
assert_eq!(header.tile_width, 64);
assert_eq!(header.tile_height, 64);
assert_eq!(header.pixel_size, 4);
assert_eq!(header.num_tiles, 240);
}
}