1use crate::{
2 deserialization::{
3 deserialize_as_degrees, deserialize_as_milliseconds, deserialize_map_as_vec,
4 deserialize_multipliers,
5 },
6 error::PyxelError,
7};
8
9use derivative::Derivative;
10use semver::Version;
11use serde::Deserialize;
12use std::{collections::BTreeMap, time::Duration};
13
14#[derive(Clone, Copy, Debug, Eq, PartialEq)]
16pub struct Color {
17 pub r: u8,
19 pub g: u8,
21 pub b: u8,
23 pub a: u8,
25}
26
27impl std::str::FromStr for Color {
28 type Err = hex::FromHexError;
29
30 fn from_str(s: &str) -> Result<Self, Self::Err> {
31 use hex::FromHex;
32
33 let decoded = <[u8; 4]>::from_hex(s)?;
34
35 Ok(Color {
36 r: decoded[1],
37 g: decoded[2],
38 b: decoded[3],
39 a: decoded[0],
40 })
41 }
42}
43
44impl<'de> serde::de::Deserialize<'de> for Color {
45 fn deserialize<D: serde::de::Deserializer<'de>>(deserializer: D) -> Result<Color, D::Error> {
46 deserializer.deserialize_str(ColorVisitor)
47 }
48}
49
50struct ColorVisitor;
51
52impl<'de> serde::de::Visitor<'de> for ColorVisitor {
53 type Value = Color;
54
55 fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
56 formatter.write_str("a color in the format AARRGGBB")
57 }
58
59 fn visit_str<E: serde::de::Error>(self, value: &str) -> Result<Self::Value, E> {
60 use std::str::FromStr;
61 Self::Value::from_str(value).map_err(serde::de::Error::custom)
62 }
63}
64
65#[derive(Debug, Deserialize)]
67pub struct Palette {
68 #[serde(deserialize_with = "deserialize_map_as_vec")]
69 colors: Vec<Option<Color>>,
70
71 height: u8,
72
73 #[serde(rename = "numColors")]
74 num_colors: usize,
75
76 width: u8,
77}
78
79impl Palette {
80 pub fn colors(&self) -> &Vec<Option<Color>> {
82 &self.colors
83 }
84
85 pub fn height(&self) -> u8 {
87 self.height
88 }
89
90 pub fn width(&self) -> u8 {
92 self.width
93 }
94}
95
96#[derive(Clone, Copy, Debug, Deserialize, PartialEq)]
98pub struct TileRef {
99 index: usize,
100 #[serde(deserialize_with = "deserialize_as_degrees")]
101 rot: f64,
102
103 #[serde(rename = "flipX")]
104 flip_x: bool,
105}
106
107impl TileRef {
108 pub fn index(&self) -> usize {
110 self.index
111 }
112
113 pub fn rot(&self) -> f64 {
115 self.rot
116 }
117
118 pub fn flip_x(&self) -> bool {
120 self.flip_x
121 }
122}
123
124#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq)]
126pub enum BlendMode {
127 #[serde(rename = "normal")]
129 Normal,
130
131 #[serde(rename = "multiply")]
133 Multiply,
134
135 #[serde(rename = "add")]
137 Add,
138
139 #[serde(rename = "difference")]
141 Difference,
142
143 #[serde(rename = "darken")]
145 Darken,
146
147 #[serde(rename = "lighten")]
149 Lighten,
150
151 #[serde(rename = "hardlight")]
153 Hardlight,
154
155 #[serde(rename = "invert")]
157 Invert,
158
159 #[serde(rename = "overlay")]
161 Overlay,
162
163 #[serde(rename = "screen")]
165 Screen,
166
167 #[serde(rename = "subtract")]
169 Subtract,
170}
171
172#[cfg(feature = "images")]
173fn default_image() -> image::DynamicImage {
174 image::DynamicImage::new_rgba8(1, 1)
175}
176
177#[derive(Derivative, Deserialize)]
179#[derivative(Debug)]
180pub struct Layer {
181 alpha: u8,
182
183 #[serde(rename = "blendMode")]
184 blend_mode: BlendMode,
185
186 hidden: bool,
187 muted: bool,
188 name: String,
189 soloed: bool,
190
191 #[serde(rename = "tileRefs")]
192 tile_refs: BTreeMap<usize, TileRef>,
193
194 #[cfg(not(feature = "images"))]
195 #[serde(skip)]
196 image_data: Vec<u8>,
197
198 #[cfg(feature = "images")]
199 #[derivative(Debug = "ignore")]
200 #[serde(default = "default_image", skip)]
201 image: image::DynamicImage,
202}
203
204impl Layer {
205 pub fn alpha(&self) -> u8 {
207 self.alpha
208 }
209
210 pub fn blend_mode(&self) -> BlendMode {
212 self.blend_mode
213 }
214
215 pub fn hidden(&self) -> bool {
217 self.hidden
218 }
219
220 pub fn muted(&self) -> bool {
222 self.muted
223 }
224
225 pub fn name(&self) -> &String {
227 &self.name
228 }
229
230 pub fn soloed(&self) -> bool {
232 self.soloed
233 }
234
235 pub fn tile_refs(&self) -> &BTreeMap<usize, TileRef> {
237 &self.tile_refs
238 }
239
240 #[cfg(not(feature = "images"))]
242 pub fn image_data(&self) -> &Vec<u8> {
243 &self.image_data
244 }
245
246 #[cfg(feature = "images")]
248 pub fn image(&self) -> &image::DynamicImage {
249 &self.image
250 }
251}
252
253#[derive(Debug, Deserialize)]
255pub struct Canvas {
256 #[serde(deserialize_with = "deserialize_map_as_vec")]
257 layers: Vec<Layer>,
258 height: i32,
259
260 #[serde(rename = "numLayers")]
261 num_layers: usize,
262
263 #[serde(rename = "tileHeight")]
264 tile_height: u16,
265
266 #[serde(rename = "tileWidth")]
267 tile_width: u16,
268
269 width: i32,
270}
271
272impl Canvas {
273 pub fn layers(&self) -> &Vec<Layer> {
275 &self.layers
276 }
277
278 pub fn height(&self) -> i32 {
280 self.height
281 }
282
283 pub fn tile_height(&self) -> u16 {
285 self.tile_height
286 }
287
288 pub fn tile_width(&self) -> u16 {
290 self.tile_width
291 }
292
293 pub fn width(&self) -> i32 {
295 self.width
296 }
297}
298
299#[derive(Derivative, Deserialize)]
301#[derivative(Debug)]
302pub struct Tileset {
303 #[serde(rename = "fixedWidth")]
304 fixed_width: bool,
305
306 #[serde(rename = "numTiles")]
307 num_tiles: usize,
308
309 #[serde(rename = "tileHeight")]
310 tile_height: u16,
311
312 #[serde(rename = "tileWidth")]
313 tile_width: u16,
314
315 #[serde(rename = "tilesWide")]
316 tiles_wide: u8,
317
318 #[cfg(not(feature = "images"))]
319 #[serde(skip)]
320 image_data: Vec<Vec<u8>>,
321
322 #[cfg(feature = "images")]
323 #[derivative(Debug = "ignore")]
324 #[serde(skip)]
325 images: Vec<image::DynamicImage>,
326}
327
328impl Tileset {
329 pub fn fixed_width(&self) -> bool {
331 self.fixed_width
332 }
333
334 pub fn tile_height(&self) -> u16 {
336 self.tile_height
337 }
338
339 pub fn tile_width(&self) -> u16 {
341 self.tile_width
342 }
343
344 pub fn tiles_wide(&self) -> u8 {
346 self.tiles_wide
347 }
348
349 #[cfg(not(feature = "images"))]
351 pub fn image_data(&self) -> &Vec<Vec<u8>> {
352 &self.image_data
353 }
354
355 #[cfg(feature = "images")]
357 pub fn images(&self) -> &Vec<image::DynamicImage> {
358 &self.images
359 }
360}
361
362#[derive(Debug, Deserialize)]
364pub struct Animation {
365 #[serde(rename = "baseTile")]
366 base_tile: usize,
367
368 #[serde(
369 deserialize_with = "deserialize_as_milliseconds",
370 rename = "frameDuration"
371 )]
372 frame_duration: Duration,
373
374 #[serde(
375 deserialize_with = "deserialize_multipliers",
376 rename = "frameDurationMultipliers"
377 )]
378 frame_duration_multipliers: Vec<f64>,
379
380 length: usize,
381 name: String,
382}
383
384impl Animation {
385 pub fn base_tile(&self) -> usize {
387 self.base_tile
388 }
389
390 pub fn frame_duration(&self) -> Duration {
392 self.frame_duration
393 }
394
395 pub fn frame_duration_multipliers(&self) -> &Vec<f64> {
397 &self.frame_duration_multipliers
398 }
399
400 pub fn length(&self) -> usize {
402 self.length
403 }
404
405 pub fn name(&self) -> &String {
407 &self.name
408 }
409}
410
411#[derive(Debug, Deserialize)]
413pub struct Pyxel {
414 #[serde(deserialize_with = "deserialize_map_as_vec")]
415 animations: Vec<Animation>,
416 canvas: Canvas,
417 name: String,
418 palette: Palette,
419 tileset: Tileset,
420 version: Version,
421}
422
423impl Pyxel {
424 pub fn animations(&self) -> &Vec<Animation> {
426 &self.animations
427 }
428
429 pub fn canvas(&self) -> &Canvas {
431 &self.canvas
432 }
433
434 pub fn name(&self) -> &String {
436 &self.name
437 }
438
439 pub fn palette(&self) -> &Palette {
441 &self.palette
442 }
443
444 pub fn tileset(&self) -> &Tileset {
446 &self.tileset
447 }
448
449 pub fn version(&self) -> &Version {
451 &self.version
452 }
453}
454
455#[cfg(not(feature = "images"))]
456fn load_image_data_from_zip<R: std::io::Read + std::io::Seek>(
457 zip: &mut zip::ZipArchive<R>,
458 path: &str,
459) -> Result<Vec<u8>, PyxelError> {
460 use std::io::Read;
461
462 let mut file = zip.by_name(path)?;
463
464 let mut buf = Vec::new();
465 file.read_to_end(&mut buf)?;
466
467 Ok(buf)
468}
469
470#[cfg(feature = "images")]
471fn load_image_from_zip<R: std::io::Read + std::io::Seek>(
472 zip: &mut zip::ZipArchive<R>,
473 path: &str,
474) -> Result<image::DynamicImage, PyxelError> {
475 use std::io::Read;
476
477 let mut file = zip.by_name(path)?;
478
479 let mut buf = Vec::new();
480 file.read_to_end(&mut buf)?;
481
482 let image = image::load_from_memory_with_format(&buf, image::ImageFormat::PNG)?;
483 Ok(image)
484}
485
486pub fn load<R: std::io::Read + std::io::Seek>(r: R) -> Result<Pyxel, PyxelError> {
499 let mut archive = zip::ZipArchive::new(r)?;
500 let data = archive.by_name("docData.json")?;
501
502 let mut pyxel: Pyxel = serde_json::from_reader(data)?;
503
504 for i in 0..pyxel.canvas().num_layers {
505 #[cfg(not(feature = "images"))]
506 {
507 let image_data = load_image_data_from_zip(&mut archive, &format!("layer{}.png", i))?;
508 pyxel.canvas.layers[i].image_data = image_data;
509 }
510 #[cfg(feature = "images")]
511 {
512 let image = load_image_from_zip(&mut archive, &format!("layer{}.png", i))?;
513 pyxel.canvas.layers[i].image = image;
514 }
515 }
516
517 for i in 0..pyxel.tileset().num_tiles {
518 #[cfg(not(feature = "images"))]
519 {
520 let image_data = load_image_data_from_zip(&mut archive, &format!("layer{}.png", i))?;
521 pyxel.tileset.image_data.insert(i, image_data);
522 }
523 #[cfg(feature = "images")]
524 {
525 let image = load_image_from_zip(&mut archive, &format!("tile{}.png", i))?;
526 pyxel.tileset.images.insert(i, image);
527 }
528 }
529
530 Ok(pyxel)
531}
532
533#[cfg(test)]
534mod tests {
535 use super::*;
536 use std::{collections::BTreeMap, fs::File, str::FromStr};
537
538 #[test]
539 fn convert_color_from_aarrggbb() {
540 let c = Color::from_str("ffaabbcc").unwrap();
541 assert_eq!(170, c.r);
542 assert_eq!(187, c.g);
543 assert_eq!(204, c.b);
544 assert_eq!(255, c.a);
545 }
546
547 const TEST_FILE: &str = "resources/test_v0.4.8.pyxel";
548
549 #[test]
550 fn load_palette_colors() {
551 let file = File::open(TEST_FILE).unwrap();
552 let doc = load(file).unwrap();
553
554 assert_eq!(
555 &vec![
556 Some(Color {
557 r: 190,
558 g: 53,
559 b: 53,
560 a: 255
561 }),
562 Some(Color {
563 r: 249,
564 g: 155,
565 b: 151,
566 a: 255
567 }),
568 Some(Color {
569 r: 145,
570 g: 95,
571 b: 51,
572 a: 255
573 }),
574 Some(Color {
575 r: 209,
576 g: 127,
577 b: 48,
578 a: 255
579 }),
580 Some(Color {
581 r: 247,
582 g: 238,
583 b: 89,
584 a: 255
585 }),
586 Some(Color {
587 r: 89,
588 g: 205,
589 b: 54,
590 a: 255
591 }),
592 Some(Color {
593 r: 131,
594 g: 240,
595 b: 220,
596 a: 255
597 }),
598 Some(Color {
599 r: 117,
600 g: 161,
601 b: 236,
602 a: 255
603 }),
604 Some(Color {
605 r: 65,
606 g: 55,
607 b: 205,
608 a: 255
609 }),
610 Some(Color {
611 r: 204,
612 g: 89,
613 b: 198,
614 a: 255
615 }),
616 Some(Color {
617 r: 255,
618 g: 255,
619 b: 255,
620 a: 255
621 }),
622 Some(Color {
623 r: 202,
624 g: 202,
625 b: 202,
626 a: 255
627 }),
628 Some(Color {
629 r: 142,
630 g: 142,
631 b: 142,
632 a: 255
633 }),
634 Some(Color {
635 r: 91,
636 g: 91,
637 b: 91,
638 a: 255
639 }),
640 Some(Color {
641 r: 0,
642 g: 0,
643 b: 0,
644 a: 255
645 })
646 ],
647 doc.palette().colors()
648 );
649 }
650
651 #[test]
652 fn load_canvas_layer_tilerefs() {
653 let file = File::open(TEST_FILE).unwrap();
654 let doc = load(file).unwrap();
655
656 let mut tile_refs = BTreeMap::new();
657 tile_refs.insert(
658 56,
659 TileRef {
660 index: 0,
661 rot: 0.0,
662 flip_x: false,
663 },
664 );
665 tile_refs.insert(
666 57,
667 TileRef {
668 index: 0,
669 rot: 90.0,
670 flip_x: false,
671 },
672 );
673 tile_refs.insert(
674 58,
675 TileRef {
676 index: 0,
677 rot: 180.0,
678 flip_x: false,
679 },
680 );
681 tile_refs.insert(
682 59,
683 TileRef {
684 index: 0,
685 rot: 270.0,
686 flip_x: false,
687 },
688 );
689 tile_refs.insert(
690 60,
691 TileRef {
692 index: 0,
693 rot: 0.0,
694 flip_x: true,
695 },
696 );
697 tile_refs.insert(
698 61,
699 TileRef {
700 index: 0,
701 rot: 90.0,
702 flip_x: true,
703 },
704 );
705 tile_refs.insert(
706 62,
707 TileRef {
708 index: 0,
709 rot: 180.0,
710 flip_x: true,
711 },
712 );
713 tile_refs.insert(
714 63,
715 TileRef {
716 index: 0,
717 rot: 270.0,
718 flip_x: true,
719 },
720 );
721
722 assert_eq!(&tile_refs, doc.canvas().layers()[1].tile_refs());
723 }
724}