use crate::{
deserialization::{
deserialize_as_degrees, deserialize_as_milliseconds, deserialize_map_as_vec,
deserialize_multipliers,
},
error::PyxelError,
};
use derivative::Derivative;
use semver::Version;
use serde::Deserialize;
use std::{collections::BTreeMap, time::Duration};
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct Color {
pub r: u8,
pub g: u8,
pub b: u8,
pub a: u8,
}
impl std::str::FromStr for Color {
type Err = hex::FromHexError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
use hex::FromHex;
let decoded = <[u8; 4]>::from_hex(s)?;
Ok(Color {
r: decoded[1],
g: decoded[2],
b: decoded[3],
a: decoded[0],
})
}
}
impl<'de> serde::de::Deserialize<'de> for Color {
fn deserialize<D: serde::de::Deserializer<'de>>(deserializer: D) -> Result<Color, D::Error> {
deserializer.deserialize_str(ColorVisitor)
}
}
struct ColorVisitor;
impl<'de> serde::de::Visitor<'de> for ColorVisitor {
type Value = Color;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("a color in the format AARRGGBB")
}
fn visit_str<E: serde::de::Error>(self, value: &str) -> Result<Self::Value, E> {
use std::str::FromStr;
Self::Value::from_str(value).map_err(serde::de::Error::custom)
}
}
#[derive(Debug, Deserialize)]
pub struct Palette {
#[serde(deserialize_with = "deserialize_map_as_vec")]
colors: Vec<Option<Color>>,
height: u8,
#[serde(rename = "numColors")]
num_colors: usize,
width: u8,
}
impl Palette {
pub fn colors(&self) -> &Vec<Option<Color>> {
&self.colors
}
pub fn height(&self) -> u8 {
self.height
}
pub fn width(&self) -> u8 {
self.width
}
}
#[derive(Clone, Copy, Debug, Deserialize, PartialEq)]
pub struct TileRef {
index: usize,
#[serde(deserialize_with = "deserialize_as_degrees")]
rot: f64,
#[serde(rename = "flipX")]
flip_x: bool,
}
impl TileRef {
pub fn index(&self) -> usize {
self.index
}
pub fn rot(&self) -> f64 {
self.rot
}
pub fn flip_x(&self) -> bool {
self.flip_x
}
}
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq)]
pub enum BlendMode {
#[serde(rename = "normal")]
Normal,
#[serde(rename = "multiply")]
Multiply,
#[serde(rename = "add")]
Add,
#[serde(rename = "difference")]
Difference,
#[serde(rename = "darken")]
Darken,
#[serde(rename = "lighten")]
Lighten,
#[serde(rename = "hardlight")]
Hardlight,
#[serde(rename = "invert")]
Invert,
#[serde(rename = "overlay")]
Overlay,
#[serde(rename = "screen")]
Screen,
#[serde(rename = "subtract")]
Subtract,
}
#[cfg(feature = "images")]
fn default_image() -> image::DynamicImage {
image::DynamicImage::new_rgba8(1, 1)
}
#[derive(Derivative, Deserialize)]
#[derivative(Debug)]
pub struct Layer {
alpha: u8,
#[serde(rename = "blendMode")]
blend_mode: BlendMode,
hidden: bool,
muted: bool,
name: String,
soloed: bool,
#[serde(rename = "tileRefs")]
tile_refs: BTreeMap<usize, TileRef>,
#[cfg(not(feature = "images"))]
#[serde(skip)]
image_data: Vec<u8>,
#[cfg(feature = "images")]
#[derivative(Debug = "ignore")]
#[serde(default = "default_image", skip)]
image: image::DynamicImage,
}
impl Layer {
pub fn alpha(&self) -> u8 {
self.alpha
}
pub fn blend_mode(&self) -> BlendMode {
self.blend_mode
}
pub fn hidden(&self) -> bool {
self.hidden
}
pub fn muted(&self) -> bool {
self.muted
}
pub fn name(&self) -> &String {
&self.name
}
pub fn soloed(&self) -> bool {
self.soloed
}
pub fn tile_refs(&self) -> &BTreeMap<usize, TileRef> {
&self.tile_refs
}
#[cfg(not(feature = "images"))]
pub fn image_data(&self) -> &Vec<u8> {
&self.image_data
}
#[cfg(feature = "images")]
pub fn image(&self) -> &image::DynamicImage {
&self.image
}
}
#[derive(Debug, Deserialize)]
pub struct Canvas {
#[serde(deserialize_with = "deserialize_map_as_vec")]
layers: Vec<Layer>,
height: i32,
#[serde(rename = "numLayers")]
num_layers: usize,
#[serde(rename = "tileHeight")]
tile_height: u16,
#[serde(rename = "tileWidth")]
tile_width: u16,
width: i32,
}
impl Canvas {
pub fn layers(&self) -> &Vec<Layer> {
&self.layers
}
pub fn height(&self) -> i32 {
self.height
}
pub fn tile_height(&self) -> u16 {
self.tile_height
}
pub fn tile_width(&self) -> u16 {
self.tile_width
}
pub fn width(&self) -> i32 {
self.width
}
}
#[derive(Derivative, Deserialize)]
#[derivative(Debug)]
pub struct Tileset {
#[serde(rename = "fixedWidth")]
fixed_width: bool,
#[serde(rename = "numTiles")]
num_tiles: usize,
#[serde(rename = "tileHeight")]
tile_height: u16,
#[serde(rename = "tileWidth")]
tile_width: u16,
#[serde(rename = "tilesWide")]
tiles_wide: u8,
#[cfg(not(feature = "images"))]
#[serde(skip)]
image_data: Vec<Vec<u8>>,
#[cfg(feature = "images")]
#[derivative(Debug = "ignore")]
#[serde(skip)]
images: Vec<image::DynamicImage>,
}
impl Tileset {
pub fn fixed_width(&self) -> bool {
self.fixed_width
}
pub fn tile_height(&self) -> u16 {
self.tile_height
}
pub fn tile_width(&self) -> u16 {
self.tile_width
}
pub fn tiles_wide(&self) -> u8 {
self.tiles_wide
}
#[cfg(not(feature = "images"))]
pub fn image_data(&self) -> &Vec<Vec<u8>> {
&self.image_data
}
#[cfg(feature = "images")]
pub fn images(&self) -> &Vec<image::DynamicImage> {
&self.images
}
}
#[derive(Debug, Deserialize)]
pub struct Animation {
#[serde(rename = "baseTile")]
base_tile: usize,
#[serde(
deserialize_with = "deserialize_as_milliseconds",
rename = "frameDuration"
)]
frame_duration: Duration,
#[serde(
deserialize_with = "deserialize_multipliers",
rename = "frameDurationMultipliers"
)]
frame_duration_multipliers: Vec<f64>,
length: usize,
name: String,
}
impl Animation {
pub fn base_tile(&self) -> usize {
self.base_tile
}
pub fn frame_duration(&self) -> Duration {
self.frame_duration
}
pub fn frame_duration_multipliers(&self) -> &Vec<f64> {
&self.frame_duration_multipliers
}
pub fn length(&self) -> usize {
self.length
}
pub fn name(&self) -> &String {
&self.name
}
}
#[derive(Debug, Deserialize)]
pub struct Pyxel {
#[serde(deserialize_with = "deserialize_map_as_vec")]
animations: Vec<Animation>,
canvas: Canvas,
name: String,
palette: Palette,
tileset: Tileset,
version: Version,
}
impl Pyxel {
pub fn animations(&self) -> &Vec<Animation> {
&self.animations
}
pub fn canvas(&self) -> &Canvas {
&self.canvas
}
pub fn name(&self) -> &String {
&self.name
}
pub fn palette(&self) -> &Palette {
&self.palette
}
pub fn tileset(&self) -> &Tileset {
&self.tileset
}
pub fn version(&self) -> &Version {
&self.version
}
}
#[cfg(not(feature = "images"))]
fn load_image_data_from_zip<R: std::io::Read + std::io::Seek>(
zip: &mut zip::ZipArchive<R>,
path: &str,
) -> Result<Vec<u8>, PyxelError> {
use std::io::Read;
let mut file = zip.by_name(path)?;
let mut buf = Vec::new();
file.read_to_end(&mut buf)?;
Ok(buf)
}
#[cfg(feature = "images")]
fn load_image_from_zip<R: std::io::Read + std::io::Seek>(
zip: &mut zip::ZipArchive<R>,
path: &str,
) -> Result<image::DynamicImage, PyxelError> {
use std::io::Read;
let mut file = zip.by_name(path)?;
let mut buf = Vec::new();
file.read_to_end(&mut buf)?;
let image = image::load_from_memory_with_format(&buf, image::ImageFormat::PNG)?;
Ok(image)
}
pub fn load<R: std::io::Read + std::io::Seek>(r: R) -> Result<Pyxel, PyxelError> {
let mut archive = zip::ZipArchive::new(r)?;
let data = archive.by_name("docData.json")?;
let mut pyxel: Pyxel = serde_json::from_reader(data)?;
for i in 0..pyxel.canvas().num_layers {
#[cfg(not(feature = "images"))]
{
let image_data = load_image_data_from_zip(&mut archive, &format!("layer{}.png", i))?;
pyxel.canvas.layers[i].image_data = image_data;
}
#[cfg(feature = "images")]
{
let image = load_image_from_zip(&mut archive, &format!("layer{}.png", i))?;
pyxel.canvas.layers[i].image = image;
}
}
for i in 0..pyxel.tileset().num_tiles {
#[cfg(not(feature = "images"))]
{
let image_data = load_image_data_from_zip(&mut archive, &format!("layer{}.png", i))?;
pyxel.tileset.image_data.insert(i, image_data);
}
#[cfg(feature = "images")]
{
let image = load_image_from_zip(&mut archive, &format!("tile{}.png", i))?;
pyxel.tileset.images.insert(i, image);
}
}
Ok(pyxel)
}
#[cfg(test)]
mod tests {
use super::*;
use std::{collections::BTreeMap, fs::File, str::FromStr};
#[test]
fn convert_color_from_aarrggbb() {
let c = Color::from_str("ffaabbcc").unwrap();
assert_eq!(170, c.r);
assert_eq!(187, c.g);
assert_eq!(204, c.b);
assert_eq!(255, c.a);
}
const TEST_FILE: &str = "resources/test_v0.4.8.pyxel";
#[test]
fn load_palette_colors() {
let file = File::open(TEST_FILE).unwrap();
let doc = load(file).unwrap();
assert_eq!(
&vec![
Some(Color {
r: 190,
g: 53,
b: 53,
a: 255
}),
Some(Color {
r: 249,
g: 155,
b: 151,
a: 255
}),
Some(Color {
r: 145,
g: 95,
b: 51,
a: 255
}),
Some(Color {
r: 209,
g: 127,
b: 48,
a: 255
}),
Some(Color {
r: 247,
g: 238,
b: 89,
a: 255
}),
Some(Color {
r: 89,
g: 205,
b: 54,
a: 255
}),
Some(Color {
r: 131,
g: 240,
b: 220,
a: 255
}),
Some(Color {
r: 117,
g: 161,
b: 236,
a: 255
}),
Some(Color {
r: 65,
g: 55,
b: 205,
a: 255
}),
Some(Color {
r: 204,
g: 89,
b: 198,
a: 255
}),
Some(Color {
r: 255,
g: 255,
b: 255,
a: 255
}),
Some(Color {
r: 202,
g: 202,
b: 202,
a: 255
}),
Some(Color {
r: 142,
g: 142,
b: 142,
a: 255
}),
Some(Color {
r: 91,
g: 91,
b: 91,
a: 255
}),
Some(Color {
r: 0,
g: 0,
b: 0,
a: 255
})
],
doc.palette().colors()
);
}
#[test]
fn load_canvas_layer_tilerefs() {
let file = File::open(TEST_FILE).unwrap();
let doc = load(file).unwrap();
let mut tile_refs = BTreeMap::new();
tile_refs.insert(
56,
TileRef {
index: 0,
rot: 0.0,
flip_x: false,
},
);
tile_refs.insert(
57,
TileRef {
index: 0,
rot: 90.0,
flip_x: false,
},
);
tile_refs.insert(
58,
TileRef {
index: 0,
rot: 180.0,
flip_x: false,
},
);
tile_refs.insert(
59,
TileRef {
index: 0,
rot: 270.0,
flip_x: false,
},
);
tile_refs.insert(
60,
TileRef {
index: 0,
rot: 0.0,
flip_x: true,
},
);
tile_refs.insert(
61,
TileRef {
index: 0,
rot: 90.0,
flip_x: true,
},
);
tile_refs.insert(
62,
TileRef {
index: 0,
rot: 180.0,
flip_x: true,
},
);
tile_refs.insert(
63,
TileRef {
index: 0,
rot: 270.0,
flip_x: true,
},
);
assert_eq!(&tile_refs, doc.canvas().layers()[1].tile_refs());
}
}