#![allow(clippy::cast_sign_loss)]
use std::{
collections::HashMap,
fs,
io::{Cursor, Read},
ops::Generator,
};
use byteorder::{LittleEndian, ReadBytesExt};
use chrono::{DateTime, NaiveDateTime, Utc};
lazy_static::lazy_static! {
static ref FRAMERATES: HashMap<u8, f64> = {
let mut hashmap = HashMap::new();
hashmap.insert(1, 0.5);
hashmap.insert(2, 1.0);
hashmap.insert(3, 2.0);
hashmap.insert(4, 4.0);
hashmap.insert(5, 6.0);
hashmap.insert(6, 12.0);
hashmap.insert(7, 20.0);
hashmap.insert(8, 30.0);
hashmap
};
static ref THUMBNAIL_PALETTE: &'static [(u64, u64, u64)] = &[
(0xFF, 0xFF, 0xFF),
(0x52, 0x52, 0x52),
(0xFF, 0xFF, 0xFF),
(0x9C, 0x9C, 0x9C),
(0xFF, 0x48, 0x44),
(0xC8, 0x51, 0x4F),
(0xFF, 0xAD, 0xAC),
(0x00, 0xFF, 0x00),
(0x48, 0x40, 0xFF),
(0x51, 0x4F, 0xB8),
(0xAD, 0xAB, 0xFF),
(0x00, 0xFF, 0x00),
(0xB6, 0x57, 0xB7),
(0x00, 0xFF, 0x00),
(0x00, 0xFF, 0x00),
(0x00, 0xFF, 0x00),
];
static ref BLACK: (u8, u8, u8) = (0x0E, 0x0E, 0x0E);
static ref WHITE: (u8, u8, u8) = (0xFF, 0xFF, 0xFF);
static ref BLUE: (u8, u8, u8) = (0x0A, 0x39, 0xFF);
static ref RED: (u8, u8, u8) = (0xFF, 0x2A, 0x2A);
}
macro read_n_to_as_utf8_from_stream($n:expr, $from:ident) {
String::from_utf8({
let mut buffer = vec![0; $n];
$from.stream.read_exact(&mut buffer).unwrap();
buffer
})
.unwrap()
}
macro read_n_of_size_from_to_vec($n:expr, $from:tt, $size:ty) {{
let mut buffer = vec![0 as $size; $n];
$from.stream.read_exact(&mut buffer).unwrap();
buffer
}}
fn strip_null(string: &str) -> String { string.replace(char::from(0), "") }
fn read_n_to_vec(stream: &mut Cursor<Vec<u8>>, n: usize) -> Vec<u8> {
let mut buffer = vec![0; n];
stream.read_exact(&mut buffer).unwrap();
buffer
}
fn vec_u8_to_string(vec: &[u8]) -> String {
vec.iter().rev().map(|m| format!("{:02X}", m)).collect()
}
pub struct PPMParser {
stream: Cursor<Vec<u8>>,
layers: Vec<Vec<Vec<u8>>>,
prev_layers: Vec<Vec<Vec<u8>>>,
prev_frame_index: usize,
animation_data_size: u32,
sound_data_size: u32,
frame_count: u16,
lock: u16,
thumb_index: u16,
root_author_name: String,
parent_author_name: String,
current_author_name: String,
parent_author_id: String,
current_author_id: String,
parent_filename: String,
current_filename: String,
root_author_id: String,
partial_filename: String,
timestamp: DateTime<Utc>,
layer_1_visible: bool,
layer_2_visible: bool,
loop_: bool,
frame_speed: u8,
bgm_speed: u8,
framerate: f64,
bgm_framerate: f64,
offset_table: Vec<u32>,
}
impl PPMParser {
#[allow(unused)]
pub fn new(stream: Vec<u8>) -> Self {
Self {
stream: Cursor::new(stream),
..Self::default()
}
}
pub fn new_from_file(file: &str) -> Self {
Self {
stream: Cursor::new(std::fs::read(file).unwrap()),
..Self::default()
}
}
pub fn load(&mut self) {
self.read_header();
self.read_meta();
self.read_animation_header();
self.read_sound_header();
self.layers = vec![vec![vec![0; 256]; 192]; 2];
self.prev_layers = vec![vec![vec![0; 256]; 192]; 2];
self.prev_frame_index = isize::MAX as usize; }
fn read_header(&mut self) {
self.stream.set_position(0);
let _magic = read_n_to_as_utf8_from_stream!(4, self);
let animation_data_size = self.stream.read_u32::<LittleEndian>().unwrap();
let sound_data_size = self.stream.read_u32::<LittleEndian>().unwrap();
let frame_count = self.stream.read_u16::<LittleEndian>().unwrap();
let _version = self.stream.read_u16::<LittleEndian>().unwrap();
self.animation_data_size = animation_data_size;
self.sound_data_size = sound_data_size;
self.frame_count = frame_count + 1;
}
fn read_filename(&mut self) -> String {
let mac = read_n_to_vec(&mut self.stream, 3);
let ident = read_n_to_vec(&mut self.stream, 13)
.into_iter()
.map(|c| c as char)
.collect::<String>();
let edits = self.stream.read_u16::<LittleEndian>().unwrap();
format!(
"{}_{}_{:#03}",
mac
.into_iter()
.map(|m| format!("{:02X}", m))
.collect::<String>(),
String::from_utf8(ident.as_bytes().to_vec()).unwrap(),
edits,
)
}
fn read_meta(&mut self) {
self.stream.set_position(0x10);
self.lock = self.stream.read_u16::<LittleEndian>().unwrap();
self.thumb_index = self.stream.read_u16::<LittleEndian>().unwrap();
self.root_author_name = strip_null(&read_n_to_as_utf8_from_stream!(22, self));
self.parent_author_name = strip_null(&read_n_to_as_utf8_from_stream!(22, self));
self.current_author_name = strip_null(&read_n_to_as_utf8_from_stream!(22, self));
self.parent_author_id = vec_u8_to_string(read_n_to_vec(&mut self.stream, 8).as_mut_slice());
self.current_author_id = vec_u8_to_string(read_n_to_vec(&mut self.stream, 8).as_mut_slice());
self.parent_filename = self.read_filename();
self.current_filename = self.read_filename();
self.root_author_id = vec_u8_to_string(read_n_to_vec(&mut self.stream, 8).as_mut_slice());
self.partial_filename = vec_u8_to_string(read_n_to_vec(&mut self.stream, 8).as_slice());
let timestamp = self.stream.read_u32::<LittleEndian>().unwrap();
self.timestamp = DateTime::from_utc(
NaiveDateTime::from_timestamp(i64::from(timestamp) + 946_684_800, 0),
Utc,
);
}
#[allow(unused)]
fn read_thumbnail(&mut self) -> Vec<Vec<u64>> {
self.stream.set_position(0xA0);
let mut bitmap = vec![vec![0; 64]; 48];
for tile_index in 0..48 {
let tile_x = tile_index % 8 * 8;
let tile_y = tile_index / 8 * 8;
for line in 0..8 {
for pixel in (0..8).step_by(2) {
let byte = self.stream.read_uint::<LittleEndian>(1).unwrap();
let x = tile_x + pixel;
let y = tile_y + line;
bitmap[y][x] = byte & 0x0F;
bitmap[y][x + 1] = (byte >> 4) & 0x0F;
}
}
}
bitmap
}
fn read_animation_header(&mut self) {
self.stream.set_position(0x06A0);
let table_size = self.stream.read_u16::<LittleEndian>().unwrap();
let _unknown = self.stream.read_u16::<LittleEndian>().unwrap();
let flags = self.stream.read_u32::<LittleEndian>().unwrap();
self.layer_1_visible = (flags >> 11) & 0x01 != 0;
self.layer_2_visible = (flags >> 10) & 0x01 != 0;
self.loop_ = (flags >> 1) & 0x01 != 0;
let offset_table = {
let from_buffer = read_n_to_vec(&mut self.stream, table_size.into());
let mut buffer = Vec::with_capacity((table_size / 4).into());
for index in (0..usize::from(table_size)).step_by(4) {
buffer.push(
(u32::from(from_buffer[index]))
| (u32::from(from_buffer[index + 1]) << 8)
| (u32::from(from_buffer[index + 2]) << 16)
| (u32::from(from_buffer[index + 3]) << 24),
);
}
buffer
};
self.offset_table = offset_table
.into_iter()
.map(|m| m + 0x06A0 + 8 + u32::from(table_size))
.collect();
}
fn read_sound_header(&mut self) {
let mut offset = 0x06A0 + self.animation_data_size + u32::from(self.frame_count);
if offset % 2 != 0 {
offset += 4 - (offset % 4);
}
self.stream.set_position(u64::from(offset));
let _bgm_size = self.stream.read_u32::<LittleEndian>().unwrap();
let _se1_size = self.stream.read_u32::<LittleEndian>().unwrap();
let _se2_size = self.stream.read_u32::<LittleEndian>().unwrap();
let _se3_size = self.stream.read_u32::<LittleEndian>().unwrap();
let frame_speed = self.stream.read_u8().unwrap();
let bgm_speed = self.stream.read_u8().unwrap();
self.frame_speed = 8 - frame_speed;
self.bgm_speed = 8 - bgm_speed;
self.framerate = *FRAMERATES.get(&self.frame_speed).unwrap();
self.bgm_framerate = *FRAMERATES.get(&self.bgm_speed).unwrap();
}
fn frame_is_new(&mut self, index: usize) -> bool {
self
.stream
.set_position(u64::from(*self.offset_table.get(index).unwrap()));
self.stream.read_uint::<LittleEndian>(1).unwrap() >> 7 & 0x1 != 0
}
fn read_line_types(line_types: &[u8]) -> impl Generator<Yield = (usize, u8), Return = ()> + '_ {
move || {
for index in 0..192 {
let line_type = line_types.get(index / 4).unwrap() >> ((index % 4) * 2) & 0x03;
yield (index, line_type);
}
}
}
fn read_frame(&mut self, index: usize) -> &Vec<Vec<Vec<u8>>> {
if index != 0 && self.prev_frame_index != index - 1 && !self.frame_is_new(index) {
self.read_frame(index - 1);
}
self.prev_layers.clone_from_slice(&self.layers);
self.prev_frame_index = index;
self.layers.fill(vec![vec![0u8; 256]; 192]);
self
.stream
.set_position(u64::from(*self.offset_table.get(index).unwrap()));
let header = self.stream.read_uint::<LittleEndian>(1).unwrap();
let is_new_frame = (header >> 7) & 0x01 != 0;
let is_translated = (header >> 5) & 0x03 != 0;
let translation_x = if is_translated {
self.stream.read_i8().unwrap()
} else {
0
};
let translation_y = if is_translated {
self.stream.read_i8().unwrap()
} else {
0
};
let line_types = vec![
read_n_of_size_from_to_vec!(48, self, u8),
read_n_of_size_from_to_vec!(48, self, u8),
];
#[allow(clippy::needless_range_loop)]
for layer in 0..2 {
let bitmap = &mut self.layers[layer];
{
let mut generator = Self::read_line_types(&line_types[layer]);
while let std::ops::GeneratorState::Yielded((line, line_type)) =
std::pin::Pin::new(&mut generator).resume(())
{
let mut pixel = 0;
if line_type == 0 {
} else if line_type == 1 || line_type == 2 {
if line_type == 2 {
for i in 0..256 {
bitmap[line][i] = 1;
}
}
let mut chunk_usage = self.stream.read_u32::<byteorder::BigEndian>().unwrap();
while pixel < 256 {
if chunk_usage & 0x8000_0000 == 0 {
pixel += 8;
} else {
let chunk = self.stream.read_uint::<LittleEndian>(1).unwrap();
for bit in 0..8 {
bitmap[line][pixel] = (chunk >> bit & 0x1) as u8;
pixel += 1;
}
}
chunk_usage <<= 1;
}
} else if line_type == 3 {
while pixel < 256 {
let chunk = self.stream.read_uint::<LittleEndian>(1).unwrap();
for bit in 0..8 {
bitmap[line][pixel] = (chunk >> bit & 0x1) as u8;
pixel += 1;
}
}
}
}
}
}
if !is_new_frame {
for y in 0..192 {
if y - translation_y as usize >= 192 {
break;
}
for x in 0..256 {
if x - translation_x as usize >= 256 {
break;
}
self.layers[0][y][x] ^=
self.prev_layers[0][y - translation_y as usize][x - translation_x as usize];
self.layers[1][y][x] ^=
self.prev_layers[1][y - translation_y as usize][x - translation_x as usize];
}
}
}
&self.layers
}
pub fn get_frame_palette(&mut self, index: usize) -> Vec<(u8, u8, u8)> {
self.stream.set_position(self.offset_table[index].into());
let header = self.stream.read_uint::<LittleEndian>(1).unwrap();
let paper_colour = header & 0x1;
let pen = vec![
None,
Some(if paper_colour == 1 { *BLACK } else { *WHITE }),
Some(*RED),
Some(*BLUE),
];
vec![
if paper_colour == 1 { *WHITE } else { *BLACK },
pen.get(((header >> 1) & 0x3) as usize).unwrap().unwrap(), pen.get(((header >> 3) & 0x3) as usize).unwrap().unwrap(), ]
}
pub fn get_frame_pixels(&mut self, index: usize) -> Vec<Vec<u8>> {
let layers = self.read_frame(index);
let mut pixels = vec![vec![0u8; 256]; 192];
#[allow(clippy::needless_range_loop)]
for y in 0..192 {
for x in 0..256 {
if layers[0][y][x] > 0 {
pixels[y][x] = 1;
} else if layers[1][y][x] > 0 {
pixels[y][x] = 2;
}
}
}
pixels
}
pub const fn get_frame_count(&self) -> u16 { self.frame_count }
pub const fn get_thumb_index(&self) -> u16 { self.thumb_index }
pub const fn get_framerate(&self) -> f64 { self.framerate }
pub fn dump_to_json(&self, filename: &str) {
let writer = std::io::BufWriter::new(fs::File::create(filename).unwrap());
serde_json::to_writer_pretty(
writer,
&serde_json::json!({
"animation_data_size": self.animation_data_size,
"sound_data_size": self.sound_data_size,
"frame_count": self.frame_count,
"lock": self.lock,
"thumb_index": self.thumb_index,
"root_author_name": self.root_author_name,
"parent_author_name": self.parent_author_name,
"current_author_name": self.current_author_name,
"root_author_id": self.root_author_id,
"parent_author_id": self.parent_author_id,
"current_author_id": self.current_author_id,
"parent_filename": self.parent_filename,
"current_filename": self.current_filename,
"partial_filename": self.partial_filename,
"timestamp": self.timestamp.to_string(),
"layer_1_visible": self.layer_1_visible,
"layer_2_visible": self.layer_2_visible,
"loop": self.loop_,
"frame_speed": self.frame_speed,
"bgm_speed": self.bgm_speed,
"framerate": self.framerate,
"bgm_framerate": self.bgm_framerate,
}),
)
.unwrap();
}
}
impl Default for PPMParser {
fn default() -> Self {
Self {
stream: Cursor::default(),
layers: Vec::new(),
prev_layers: Vec::new(),
prev_frame_index: Default::default(),
animation_data_size: Default::default(),
sound_data_size: Default::default(),
frame_count: Default::default(),
lock: Default::default(),
thumb_index: Default::default(),
root_author_name: String::default(),
parent_author_name: String::default(),
current_author_name: String::default(),
parent_author_id: String::default(),
current_author_id: String::default(),
parent_filename: String::default(),
current_filename: String::default(),
root_author_id: String::default(),
partial_filename: String::default(),
timestamp: DateTime::from_utc(NaiveDateTime::from_timestamp(0, 0), Utc),
layer_1_visible: Default::default(),
layer_2_visible: Default::default(),
loop_: Default::default(),
frame_speed: Default::default(),
bgm_speed: Default::default(),
framerate: Default::default(),
bgm_framerate: Default::default(),
offset_table: Vec::default(),
}
}
}