use std::io::BufRead;
use std::path::PathBuf;
use flate2::bufread::ZlibDecoder;
use serde::{Serialize, Deserialize};
use libre_pvz_resources::animation as packed;
use libre_pvz_resources::animation::Element;
use libre_pvz_resources::cached::{Cached, SortedSlice};
use packed::Action;
use crate::stream::{Decode, DecodeExt, Stream, Result};
#[derive(Debug, Serialize, Deserialize)]
pub struct Animation {
pub fps: f32,
pub tracks: Box<[Track]>,
}
impl Animation {
pub fn decompress_and_decode<R: Stream + BufRead + ?Sized>(s: &mut R) -> Result<Animation> {
if let Ok([0xD4, 0xFE, 0xAD, 0xDE, ..]) = s.fill_buf() {
s.consume(8);
Animation::decode(&mut ZlibDecoder::new(s))
} else {
Animation::decode(s)
}
}
}
declare_no_args!(Animation);
impl Decode<()> for Animation {
fn decode_with<S: Stream + ?Sized>(s: &mut S, _args: ()) -> Result<Animation> {
tracing::debug!("decoding Animation (XML root node)");
s.check_magic(0xB3_93_B4_C0)?;
s.drop_padding("after-magic", 4)?;
let track_count = s.read_data::<u32>()? as usize;
let fps = s.read_data::<f32>()?;
s.drop_padding("prop", 4)?;
s.check_magic(0x0C)?;
let mut tracks = Vec::with_capacity(track_count);
let frame_counts = std::iter::repeat_with(|| {
s.drop_padding("frame", 8)?;
s.read_data::<u32>()
}).take(track_count).collect::<Result<Vec<_>>>()?;
for frame_count in frame_counts {
tracks.push(Track::decode_with(s, frame_count as usize)?);
}
Ok(Animation { fps, tracks: tracks.into_boxed_slice() })
}
}
macro_rules! narrow {
($n:expr, $on_err:expr, $or_else:expr) => {
match $n.try_into() {
Ok(n) => n,
Err(_) => {
$on_err($n);
return Err($or_else);
}
}
}
}
fn track_to_meta(track: packed::Track) -> Result<packed::Meta, packed::Track> {
let mut ranges = Vec::new();
let mut ignored_count = 0_usize;
let mut current_visible = true;
let mut last_key_frame = 0;
for (k, frame) in track.frames
.iter().enumerate()
.filter(|(_, frame)| !frame.0.is_empty()) {
for trans in frame.0.iter() {
let visible = match trans {
&Action::Show(visible) => visible,
Action::LoadElement(_) => return Err(track),
Action::Alpha(_)
| Action::Translation(_)
| Action::Scale(_)
| Action::Rotation(_) => {
ignored_count += 1;
continue;
}
};
if current_visible && !visible && last_key_frame != k {
ranges.push((last_key_frame, k));
}
if current_visible != visible {
last_key_frame = k;
current_visible = visible;
} else {
tracing::warn!(target: "pack", "redundant 'show' in track '{}' frame {k}", track.name);
}
}
}
if current_visible {
ranges.push((last_key_frame, track.frames.len()));
}
if let [(start_frame, end_frame)] = ranges[..] {
let on_err = |n: usize| tracing::error!(target: "pack", "frame index ({n}) overflow in a meta track");
let start_frame = narrow!(start_frame, on_err, track);
let end_frame = narrow!(end_frame - 1, on_err, track);
if ignored_count > 0 {
tracing::warn!(target: "pack", "ignored {ignored_count} transform/alpha in meta track {}", track.name);
}
Ok(packed::Meta { name: track.name, start_frame, end_frame })
} else {
tracing::warn!(target: "pack", "discontinuous meta track {}: found ranges {ranges:?}", track.name);
Err(track)
}
}
impl From<Animation> for packed::AnimDesc {
fn from(anim: Animation) -> packed::AnimDesc {
let mut metas = Vec::new();
let mut tracks = Vec::new();
for track in anim.tracks.into_vec().into_iter().map(packed::Track::from) {
match track_to_meta(track) {
Ok(meta) => metas.push(meta),
Err(track) => tracks.push(track),
}
}
packed::AnimDesc {
fps: anim.fps,
meta: SortedSlice::from(metas),
tracks: tracks.into_boxed_slice(),
}
}
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Track {
pub name: String,
pub frames: Box<[Frame]>,
}
declare_no_args!(Track);
impl Decode<usize> for Track {
fn decode_with<S: Stream + ?Sized>(s: &mut S, n: usize) -> Result<Self> {
let name = s.read_string()?;
tracing::debug!("decoding Track '{name}' of length {n} (XML tag <track>)");
s.check_magic(0x2C)?;
let transforms = s.read_n::<Transform>(n)?;
let elements = s.read_n::<Elements>(n)?;
let frames = std::iter::zip(transforms, elements)
.map(|(transform, elements)| Frame { transform, elements })
.collect();
Ok(Track { name, frames })
}
}
impl From<Track> for packed::Track {
fn from(track: Track) -> packed::Track {
let mut frames = Vec::with_capacity(track.frames.len());
#[derive(Copy, Clone)]
struct RawTrans {
x: f32,
y: f32,
kx: f32,
ky: f32,
sx: f32,
sy: f32,
}
let mut last_frame = RawTrans { x: 0.0, y: 0.0, kx: 0.0, ky: 0.0, sx: 1.0, sy: 1.0 };
for frame in track.frames.into_vec() {
let mut packed = Vec::new();
let Transform { x, y, kx, ky, sx, sy, f, a } = frame.transform;
if x.is_some() || y.is_some() {
last_frame.x = x.unwrap_or(last_frame.x);
last_frame.y = y.unwrap_or(last_frame.y);
packed.push(Action::Translation([last_frame.x, -last_frame.y]));
}
if sx.is_some() || sy.is_some() {
last_frame.sx = sx.unwrap_or(last_frame.sx);
last_frame.sy = sy.unwrap_or(last_frame.sy);
packed.push(Action::Scale([last_frame.sx, last_frame.sy]));
}
if kx.is_some() || ky.is_some() {
last_frame.kx = kx.map(f32::to_radians).unwrap_or(last_frame.kx);
last_frame.ky = ky.map(f32::to_radians).unwrap_or(last_frame.ky);
packed.push(Action::Rotation([-last_frame.kx, last_frame.ky]));
}
if let Some(a) = a {
packed.push(Action::Alpha(a));
}
if let Some(f) = f {
packed.push(Action::Show(f >= 0.0));
if ![0.0, -1.0].contains(&f) {
tracing::warn!(target: "pack", "non-standard <f> node with value {f}");
}
}
let Elements { text, font, image } = frame.elements;
let mut has_image = false;
if let Some(mut image) = image {
let mut image_name_valid = false;
if let Some(s) = image.strip_prefix("IMAGE_REANIM_") {
image = s.to_string();
if let Some(tail) = image.get_mut(1..) {
tail.make_ascii_lowercase();
image.push_str(".png");
image_name_valid = true;
}
}
if !image_name_valid {
tracing::error!(target: "pack", "exotic file name: {image}");
}
let image = Cached::from(PathBuf::from(image));
packed.push(Action::LoadElement(Element::Image { image }));
has_image = true;
}
match (text, font) {
(Some(text), Some(font)) => if has_image {
tracing::warn!(target: "pack", "dropped <text>{text}</text> in favour of <i>");
} else {
let font = Cached::from(PathBuf::from(font));
packed.push(Action::LoadElement(Element::Text { text, font }));
},
(Some(text), None) => tracing::warn!(target: "pack", "dropped <text>{text}</text> without <font>"),
(None, Some(font)) => tracing::warn!(target: "pack", "dropped <font>{font}</font> without <text>"),
_ => {}
}
frames.push(packed::Frame(packed.into_boxed_slice()))
}
packed::Track { name: track.name, frames: frames.into_boxed_slice() }
}
}
#[derive(Debug, Serialize, Deserialize)]
#[allow(missing_docs)]
pub struct Transform {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub x: Option<f32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub y: Option<f32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub kx: Option<f32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub ky: Option<f32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub sx: Option<f32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub sy: Option<f32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub f: Option<f32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub a: Option<f32>,
}
declare_no_args!(Transform);
impl Decode<()> for Transform {
fn decode_with<S: Stream + ?Sized>(s: &mut S, _args: ()) -> Result<Transform> {
tracing::debug!("decoding Transform (XML tag <t>)");
let x = s.read_optional::<f32>()?;
let y = s.read_optional::<f32>()?;
let kx = s.read_optional::<f32>()?;
let ky = s.read_optional::<f32>()?;
let sx = s.read_optional::<f32>()?;
let sy = s.read_optional::<f32>()?;
let f = s.read_optional::<f32>()?;
let a = s.read_optional::<f32>()?;
s.drop_padding("transform", 12)?;
Ok(Transform { x, y, kx, ky, sx, sy, f, a })
}
}
#[derive(Debug, Serialize, Deserialize)]
#[allow(missing_docs)]
pub struct Elements {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub image: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub font: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub text: Option<String>,
}
declare_no_args!(Elements);
impl Decode<()> for Elements {
fn decode_with<S: Stream + ?Sized>(s: &mut S, _args: ()) -> Result<Elements> {
fn opt(s: String) -> Option<String> {
if s.is_empty() { None } else { Some(s) }
}
let image_name = opt(s.read_string()?);
let font_name = opt(s.read_string()?);
let text = opt(s.read_string()?);
Ok(Elements { image: image_name, font: font_name, text })
}
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Frame {
#[serde(flatten)]
pub transform: Transform,
#[serde(flatten)]
pub elements: Elements,
}