aseprite/
lib.rs

1//! A crate for loading data from the aseprite sprite editor. Should
2//! go along well with the tiled crate, I hope!
3//!
4//! It does not load any actual images, just the metadata. Currently
5//! it only loads aseprite's JSON export format, and only when
6//! exported in a particular format that has all the options just
7//! right. I've yet to find a use case that won't cover though.
8//!
9//! Automatically exporting a sprite to a given format is documented
10//! here: https://www.aseprite.org/docs/cli/ The easy way to export in
11//! the right format is to use a command such as `aseprite -b
12//! boonga.ase --sheet boonga.png --format json-array --data
13//! boonga.json`
14//!
15//! Otherwise you have to go to `file->export sprite sheet` and select
16//! "array" rather than "hash".  Every.  Single.  Time.
17//!
18//! This has been tested to work with aseprite 1.1.6; newer or older
19//! versions have not been tested.
20
21#[macro_use]
22extern crate serde_derive;
23
24#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone, Copy)]
25pub struct Rect {
26    pub x: u32,
27    pub y: u32,
28    pub w: u32,
29    pub h: u32,
30}
31
32
33#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone, Copy)]
34pub struct Dimensions {
35    pub w: u32,
36    pub h: u32,
37}
38
39
40#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
41pub struct Frame {
42    pub filename: String,
43    pub frame: Rect,
44    pub rotated: bool,
45    pub trimmed: bool,
46    #[serde(rename = "spriteSourceSize")]
47    pub sprite_source_size: Rect,
48    #[serde(rename = "sourceSize")]
49    pub source_size: Dimensions,
50    pub duration: u32,
51}
52
53#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone, Copy)]
54pub enum Direction {
55    #[serde(rename="forward")]
56    Forward,
57    #[serde(rename="reverse")]
58    Reverse,
59    #[serde(rename="pingpong")]
60    Pingpong,
61}
62
63#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
64pub struct Frametag {
65    pub name: String,
66    pub from: u32,
67    pub to: u32,
68    pub direction: Direction,
69}
70
71// These are listed at:
72// https://github.com/aseprite/aseprite/blob/2e3bbe2968da65fa8852ebb94464942bf9cb8870/src/doc/blend_mode.cpp#L17
73
74#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone, Copy)]
75pub enum BlendMode {
76    #[serde(rename="normal")]
77    Normal,
78    #[serde(rename="multiply")]
79    Multiply,
80    #[serde(rename="screen")]
81    Screen,
82    #[serde(rename="overlay")]
83    Overlay,
84    #[serde(rename="darken")]
85    Darken,
86    #[serde(rename="lighten")]
87    Lighten,
88    #[serde(rename="color_dodge")]
89    ColorDodge,
90    #[serde(rename="color_burn")]
91    ColorBurn,
92    #[serde(rename="hard_light")]
93    HardLight,
94    #[serde(rename="soft_light")]
95    SoftLight,
96    #[serde(rename="difference")]
97    Difference,
98    #[serde(rename="exclusion")]
99    Exclusion,
100    #[serde(rename="hsl_hue")]
101    HslHue,
102    #[serde(rename="hsl_saturation")]
103    HslSaturation,
104    #[serde(rename="hsl_color")]
105    HslColor,
106    #[serde(rename="hsl_luminosity")]
107    HslLuminosity,
108}
109
110#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
111pub struct Layer {
112    pub name: String,
113    pub opacity: u32,
114    #[serde(rename = "blendMode")]
115    pub blend_mode: BlendMode,
116}
117
118
119#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
120pub struct Metadata {
121    pub app: String,
122    pub version: String,
123    pub format: String,
124    pub size: Dimensions,
125    pub scale: String, // Surely this should be a number?
126    #[serde(rename = "frameTags")]
127    pub frame_tags: Option<Vec<Frametag>>,
128    pub layers: Option<Vec<Layer>>,
129    pub image: Option<String>,
130}
131
132
133#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
134pub struct SpritesheetData {
135    pub frames: Vec<Frame>,
136    pub meta: Metadata,
137}
138
139
140#[cfg(test)]
141mod tests {
142    extern crate serde_json;
143
144    const S: &'static str = r##"{ "frames": [
145   {
146    "filename": "boonga 0.ase",
147    "frame": { "x": 1, "y": 1, "w": 18, "h": 18 },
148    "rotated": false,
149    "trimmed": false,
150    "spriteSourceSize": { "x": 0, "y": 0, "w": 16, "h": 16 },
151    "sourceSize": { "w": 16, "h": 16 },
152    "duration": 250
153   },
154   {
155    "filename": "boonga 1.ase",
156    "frame": { "x": 20, "y": 1, "w": 18, "h": 18 },
157    "rotated": false,
158    "trimmed": false,
159    "spriteSourceSize": { "x": 0, "y": 0, "w": 16, "h": 16 },
160    "sourceSize": { "w": 16, "h": 16 },
161    "duration": 250
162   }
163 ],
164 "meta": {
165  "app": "http://www.aseprite.org/",
166  "version": "1.1.6-dev",
167  "image": "boonga.png",
168  "format": "RGBA8888",
169  "size": { "w": 39, "h": 20 },
170  "scale": "1",
171  "frameTags": [
172   { "name": "testtag", "from": 0, "to": 1, "direction": "forward" }
173  ],
174  "layers": [
175   { "name": "Layer 1", "opacity": 255, "blendMode": "normal" }
176  ]
177 }
178}
179"##;
180
181
182    const S_NO_META: &'static str = r##"{ "frames": [
183   {
184    "filename": "boonga 0.ase",
185    "frame": { "x": 1, "y": 1, "w": 18, "h": 18 },
186    "rotated": false,
187    "trimmed": false,
188    "spriteSourceSize": { "x": 0, "y": 0, "w": 16, "h": 16 },
189    "sourceSize": { "w": 16, "h": 16 },
190    "duration": 250
191   },
192   {
193    "filename": "boonga 1.ase",
194    "frame": { "x": 20, "y": 1, "w": 18, "h": 18 },
195    "rotated": false,
196    "trimmed": false,
197    "spriteSourceSize": { "x": 0, "y": 0, "w": 16, "h": 16 },
198    "sourceSize": { "w": 16, "h": 16 },
199    "duration": 250
200   }
201 ],
202 "meta": {
203  "app": "http://www.aseprite.org/",
204  "version": "1.1.6-dev",
205  "image": "boonga.png",
206  "format": "RGBA8888",
207  "size": { "w": 39, "h": 20 },
208  "scale": "1"
209 }
210}
211"##;
212
213
214    #[test]
215    fn test_sprite_load_save() {
216        let deserialized: super::SpritesheetData = serde_json::from_str(S).unwrap();
217
218        let serialized = serde_json::to_string(&deserialized).unwrap();
219        let deserialized_again: super::SpritesheetData = serde_json::from_str(&serialized).unwrap();
220
221        assert_eq!(deserialized, deserialized_again);
222    }
223
224
225    #[test]
226    fn test_less_metadata() {
227        let deserialized: super::SpritesheetData = serde_json::from_str(S_NO_META).unwrap();
228
229        let serialized = serde_json::to_string(&deserialized).unwrap();
230        let deserialized_again: super::SpritesheetData = serde_json::from_str(&serialized).unwrap();
231
232        assert_eq!(deserialized, deserialized_again);
233    }
234
235}