aseprite_loader/loader/
mod.rs

1//! This module contains the actual loader API. This API is based on the
2//! `binary`-module of this crate and does provide a convenience API for
3//! accessing layers, frames, tags and fully blended images.
4
5use flate2::Decompress;
6use std::{
7    collections::hash_map::DefaultHasher,
8    collections::HashMap,
9    hash::{Hash, Hasher},
10    ops::RangeInclusive,
11};
12
13mod blend;
14
15use crate::{
16    binary::{
17        blend_mode::BlendMode,
18        chunks::{
19            cel::CelContent,
20            layer::{LayerFlags, LayerType},
21            slice::SliceChunk,
22            tags::AnimationDirection,
23        },
24        color_depth::ColorDepth,
25        file::{parse_file, File},
26        image::Image,
27        palette::Palette,
28    },
29    loader::blend::{blend_mode_to_blend_fn, Color},
30};
31
32/// This can be used to load an Aseprite file.
33#[derive(Debug)]
34pub struct AsepriteFile<'a> {
35    pub file: File<'a>,
36    /// All layers in the file in order
37    pub layers: Vec<Layer>,
38    /// All frames in the file in order
39    pub frames: Vec<Frame>,
40    /// All tags in the file
41    pub tags: Vec<Tag>,
42    /// All images in the file
43    pub images: Vec<Image<'a>>,
44}
45
46/// A cel in a frame
47///
48/// This is a reference to an image cel
49#[derive(Debug, Copy, Clone)]
50pub struct FrameCel {
51    pub origin: (i16, i16),
52    pub size: (u16, u16),
53    pub layer_index: usize,
54    pub image_index: usize,
55}
56
57/// A frame in the file
58///
59/// This is a collection of cels for each layer
60#[derive(Debug, Clone)]
61pub struct Frame {
62    pub duration: u16,
63    pub origin: (i16, i16),
64    pub cels: Vec<FrameCel>,
65}
66
67/// A tag in the file
68///
69/// This is a range of frames over the frames in the file, ordered by frame index
70#[derive(Debug, Clone)]
71pub struct Tag {
72    pub name: String,
73    pub range: RangeInclusive<u16>,
74    pub direction: AnimationDirection,
75    pub repeat: Option<u16>,
76}
77
78/// A layer in the file
79#[derive(Debug, Clone)]
80pub struct Layer {
81    pub name: String,
82    pub opacity: u8,
83    pub blend_mode: BlendMode,
84    pub visible: bool,
85    pub layer_type: LayerType,
86}
87
88impl AsepriteFile<'_> {
89    /// Load a aseprite file from a byte slice
90    pub fn load(data: &[u8]) -> Result<AsepriteFile<'_>, LoadSpriteError> {
91        let file = parse_file(data).map_err(|e| LoadSpriteError::Parse {
92            message: e.to_string(),
93        })?;
94        let layers: Vec<_> = file
95            .layers
96            .iter()
97            .filter_map(|layer| {
98                if layer.layer_type == LayerType::Normal || layer.layer_type == LayerType::Group {
99                    Some(Layer {
100                        name: layer.name.to_string(),
101                        opacity: layer.opacity,
102                        blend_mode: layer.blend_mode,
103                        visible: layer.flags.contains(LayerFlags::VISIBLE),
104                        layer_type: layer.layer_type,
105                    })
106                } else {
107                    None
108                }
109            })
110            .collect();
111
112        let mut image_vec: Vec<Image<'_>> = Vec::new();
113        let mut image_map: HashMap<(usize, usize), usize> = HashMap::new();
114
115        for (frame_index, frame) in file.frames.iter().enumerate() {
116            for cel in frame.cels.iter().filter_map(|x| x.as_ref()) {
117                if let CelContent::Image(image) = &cel.content {
118                    let image_index = image_vec.len();
119                    image_vec.push(image.clone());
120                    let _ = image_map.insert((frame_index, cel.layer_index.into()), image_index);
121                }
122            }
123        }
124
125        let mut frames: Vec<Frame> = Vec::new();
126        let mut tags: Vec<Tag> = Vec::new();
127
128        for tag in file.tags.iter() {
129            tags.push(Tag {
130                name: tag.name.to_string(),
131                range: tag.frames.clone(),
132                direction: tag.animation_direction,
133                repeat: if tag.animation_repeat > 0 {
134                    Some(tag.animation_repeat)
135                } else {
136                    None
137                },
138            });
139        }
140
141        for (index, frame) in file.frames.iter().enumerate() {
142            let mut cels: Vec<FrameCel> = Vec::new();
143            for cel in frame.cels.iter().filter_map(|x| x.as_ref()) {
144                let image_index = match cel.content {
145                    CelContent::Image(_) => image_map[&(index, cel.layer_index.into())],
146                    CelContent::LinkedCel { frame_position } => *image_map
147                        .get(&(frame_position.into(), cel.layer_index.into()))
148                        .ok_or_else(|| LoadSpriteError::Parse {
149                            message: format!(
150                                "invalid linked cel at frame {} layer {}",
151                                index, cel.layer_index
152                            ),
153                        })?,
154                    _ => {
155                        return Err(LoadSpriteError::Parse {
156                            message: "invalid cel".to_owned(),
157                        })
158                    }
159                };
160                let width = image_vec[image_index].width;
161                let height = image_vec[image_index].height;
162                cels.push(FrameCel {
163                    origin: (cel.x, cel.y),
164                    size: (width, height),
165                    layer_index: cel.layer_index.into(),
166                    image_index,
167                });
168            }
169
170            frames.push(Frame {
171                duration: frame.duration,
172                origin: (0, 0),
173                cels,
174            });
175        }
176
177        Ok(AsepriteFile {
178            file,
179            tags,
180            layers,
181            frames,
182            images: image_vec,
183        })
184    }
185    /// Get size of the sprite (width, height)
186    pub fn size(&self) -> (u16, u16) {
187        (self.file.header.width, self.file.header.height)
188    }
189    /// Get tag names
190    pub fn tags(&self) -> &[Tag] {
191        &self.tags
192    }
193    /// Get layer names
194    pub fn layers(&self) -> &[Layer] {
195        &self.layers
196    }
197    /// Get the image indices for a given tag and layer
198    pub fn frames(&self) -> &[Frame] {
199        &self.frames
200    }
201    /// Get image count
202    pub fn image_count(&self) -> usize {
203        self.images.len()
204    }
205
206    /// Get image loader for a given frame index
207    /// This will combine all layers into a single image
208    /// returns a hash describing the image, since cels can be reused in multiple frames
209    pub fn combined_frame_image(
210        &self,
211        frame_index: usize,
212        target: &mut [u8],
213    ) -> Result<u64, LoadImageError> {
214        let mut hasher = DefaultHasher::new();
215
216        let target_size =
217            usize::from(self.file.header.width) * usize::from(self.file.header.height) * 4;
218
219        if target.len() < target_size {
220            return Err(LoadImageError::TargetBufferTooSmall);
221        }
222
223        let frame = &self.frames[frame_index];
224
225        for cel in frame.cels.iter() {
226            let Some(layer) = self.layers.get(cel.layer_index) else {
227                continue;
228            };
229            if !layer.visible || layer.layer_type == LayerType::Group {
230                continue;
231            }
232
233            let mut cel_target = vec![0; usize::from(cel.size.0) * usize::from(cel.size.1) * 4];
234            self.load_image(cel.image_index, &mut cel_target).unwrap();
235            let layer = &self.layers[cel.layer_index];
236
237            (cel.image_index, cel.layer_index, cel.origin, cel.size).hash(&mut hasher);
238
239            let blend_fn = blend_mode_to_blend_fn(layer.blend_mode);
240
241            for y in 0..cel.size.1 {
242                for x in 0..cel.size.0 {
243                    let Some(target_x) = x.checked_add_signed(cel.origin.0) else {
244                        continue;
245                    };
246                    if target_x >= self.file.header.width {
247                        continue;
248                    }
249                    let Some(target_y) = y.checked_add_signed(cel.origin.1) else {
250                        continue;
251                    };
252                    if target_y >= self.file.header.height {
253                        continue;
254                    }
255
256                    let target_index = usize::from(target_y) * usize::from(self.file.header.width)
257                        + usize::from(target_x);
258                    let cel_index = usize::from(y) * usize::from(cel.size.0) + usize::from(x);
259
260                    let cel_pixel: &[u8] = &cel_target[cel_index * 4..cel_index * 4 + 4];
261                    let target_pixel: &mut [u8] =
262                        &mut target[target_index * 4..target_index * 4 + 4];
263
264                    let back = Color::from(&*target_pixel);
265                    let front = Color::from(cel_pixel);
266                    let out = blend_fn(back, front, layer.opacity);
267
268                    target_pixel[0] = out.r;
269                    target_pixel[1] = out.g;
270                    target_pixel[2] = out.b;
271                    target_pixel[3] = out.a;
272                }
273            }
274        }
275
276        Ok(hasher.finish())
277    }
278
279    /// Get image loader for a given image index
280    pub fn load_image(&self, index: usize, target: &mut [u8]) -> Result<(), LoadImageError> {
281        let image = &self.images[index];
282        let target_size = usize::from(image.width) * usize::from(image.height) * 4;
283        if target.len() < target_size {
284            return Err(LoadImageError::TargetBufferTooSmall);
285        }
286        let target = &mut target[..target_size];
287        match (self.file.header.color_depth, image.compressed) {
288            (ColorDepth::Rgba, false) => target.copy_from_slice(image.data),
289            (ColorDepth::Rgba, true) => decompress(image.data, target)?,
290            (ColorDepth::Grayscale, false) => {
291                grayscale_to_rgba(image.data, target)?;
292            }
293            (ColorDepth::Grayscale, true) => {
294                let mut buf = vec![0u8; usize::from(image.width) * usize::from(image.height) * 2];
295                decompress(image.data, &mut buf)?;
296                grayscale_to_rgba(&buf, target)?;
297            }
298            (ColorDepth::Indexed, false) => {
299                indexed_to_rgba(
300                    image.data,
301                    self.file
302                        .palette
303                        .as_ref()
304                        .ok_or(LoadImageError::MissingPalette)?,
305                    target,
306                )?;
307            }
308            (ColorDepth::Indexed, true) => {
309                let mut buf = vec![0u8; usize::from(image.width) * usize::from(image.height)];
310                decompress(image.data, &mut buf)?;
311                indexed_to_rgba(
312                    &buf,
313                    self.file
314                        .palette
315                        .as_ref()
316                        .ok_or(LoadImageError::MissingPalette)?,
317                    target,
318                )?;
319            }
320            (ColorDepth::Unknown(_), _) => return Err(LoadImageError::UnsupportedColorDepth),
321        }
322        Ok(())
323    }
324    pub fn slices(&self) -> &[SliceChunk<'_>] {
325        &self.file.slices
326    }
327}
328
329use thiserror::Error;
330
331#[derive(Error, Debug)]
332pub enum LoadSpriteError {
333    #[error("parsing failed {message}")]
334    Parse { message: String },
335    #[error("missing tag: {0}")]
336    MissingTag(String),
337    #[error("missing layer: {0}")]
338    MissingLayer(String),
339    #[error("frame index out of range: {0}")]
340    FrameIndexOutOfRange(usize),
341}
342
343#[allow(missing_copy_implementations)]
344#[derive(Error, Debug)]
345pub enum LoadImageError {
346    #[error("target buffer too small")]
347    TargetBufferTooSmall,
348    #[error("missing palette")]
349    MissingPalette,
350    #[error("unsupported color depth")]
351    UnsupportedColorDepth,
352    #[error("decompression failed")]
353    DecompressError,
354    #[error("invalid image data")]
355    InvalidImageData,
356}
357
358pub fn decompress(data: &[u8], target: &mut [u8]) -> Result<(), LoadImageError> {
359    let mut decompressor = Decompress::new(true);
360    match decompressor.decompress(data, target, flate2::FlushDecompress::Finish) {
361        Ok(flate2::Status::Ok | flate2::Status::BufError) => Err(LoadImageError::DecompressError),
362        Ok(flate2::Status::StreamEnd) => Ok(()),
363        Err(_) => Err(LoadImageError::DecompressError),
364    }
365}
366
367fn grayscale_to_rgba(source: &[u8], target: &mut [u8]) -> Result<(), LoadImageError> {
368    if target.len() != source.len() * 2 {
369        return Err(LoadImageError::InvalidImageData);
370    }
371    for (i, chunk) in source.chunks(2).enumerate() {
372        target[i * 4] = chunk[0];
373        target[i * 4 + 1] = chunk[0];
374        target[i * 4 + 2] = chunk[0];
375        target[i * 4 + 3] = chunk[1];
376    }
377    Ok(())
378}
379
380fn indexed_to_rgba(
381    source: &[u8],
382    palette: &Palette,
383    target: &mut [u8],
384) -> Result<(), LoadImageError> {
385    if target.len() != source.len() * 4 {
386        return Err(LoadImageError::InvalidImageData);
387    }
388    for (i, px) in source.iter().enumerate() {
389        let color = palette.colors[usize::from(*px)];
390        target[i * 4] = color.red;
391        target[i * 4 + 1] = color.green;
392        target[i * 4 + 2] = color.blue;
393        target[i * 4 + 3] = color.alpha;
394    }
395    Ok(())
396}
397
398#[test]
399fn test_cel() {
400    use image::RgbaImage;
401    use tempfile::TempDir;
402
403    let path = "./tests/combine.aseprite";
404    let file = std::fs::read(path).unwrap();
405    let file = AsepriteFile::load(&file).unwrap();
406
407    for frame in file.frames().iter() {
408        for (i, cel) in frame.cels.iter().enumerate() {
409            let (width, height) = cel.size;
410
411            let mut target = vec![0; usize::from(width * height) * 4];
412            file.load_image(cel.image_index, &mut target).unwrap();
413
414            let image = RgbaImage::from_raw(u32::from(width), u32::from(height), target).unwrap();
415
416            let tmp = TempDir::with_prefix("aseprite-loader").unwrap();
417            let path = tmp.path().join(format!("cel_{}.png", i));
418            image.save(path).unwrap();
419        }
420    }
421}
422
423#[test]
424fn test_combine() {
425    use image::RgbaImage;
426    use tempfile::TempDir;
427
428    let path = "./tests/combine.aseprite";
429    let file = std::fs::read(path).unwrap();
430    let file = AsepriteFile::load(&file).unwrap();
431
432    let (width, height) = file.size();
433    for (index, _) in file.frames().iter().enumerate() {
434        let mut target = vec![0; usize::from(width * height) * 4];
435        let _ = file.combined_frame_image(index, &mut target).unwrap();
436        let image = RgbaImage::from_raw(u32::from(width), u32::from(height), target).unwrap();
437
438        let tmp = TempDir::with_prefix("aseprite-loader").unwrap();
439        println!("{:?}", tmp);
440        let path = tmp.path().join(format!("combined_{}.png", index));
441        image.save(path).unwrap();
442    }
443}
444
445/// https://github.com/bikeshedder/aseprite-loader/issues/4
446#[test]
447fn test_issue_4_1() {
448    let path = "./tests/issue_4_1.aseprite";
449    let file = std::fs::read(path).unwrap();
450    let file = AsepriteFile::load(&file).unwrap();
451    let (width, height) = file.size();
452    let mut buf = vec![0; usize::from(width * height) * 4];
453    for idx in 0..file.frames().len() {
454        let _ = file.combined_frame_image(idx, &mut buf).unwrap();
455    }
456}
457
458#[test]
459fn test_issue_4_2() {
460    let path = "./tests/issue_4_2.aseprite";
461    let file = std::fs::read(path).unwrap();
462    let file = AsepriteFile::load(&file).unwrap();
463    let (width, height) = file.size();
464    let mut buf = vec![0; usize::from(width * height) * 4];
465    for idx in 0..file.frames().len() {
466        let _ = file.combined_frame_image(idx, &mut buf).unwrap();
467    }
468}