mabel_aseprite/
file.rs

1use std::{
2    fs::File,
3    io::{BufReader, Read},
4    path::Path,
5    sync::Arc,
6};
7
8use crate::{
9    blend::{self, mul_un8, Color8},
10    cel::{CelCommon, CelId, CelsData, ImageContent, ImageSize},
11    external_file::{ExternalFile, ExternalFileId, ExternalFilesById},
12    layer::{Layer, LayerType, LayersData},
13    pixel::Pixels,
14    slice::Slice,
15    tile::TileId,
16    tilemap::{Tilemap, TilemapData},
17    tileset::{TileSize, Tileset, TilesetsById},
18    user_data::UserData,
19};
20use crate::{cel::Cel, *};
21use cel::{CelContent, RawCel};
22use image::{Rgba, RgbaImage};
23
24/// A parsed Aseprite file.
25#[derive(Debug)]
26pub struct AsepriteFile {
27    pub(crate) width: u16,
28    pub(crate) height: u16,
29    pub(crate) num_frames: u16,
30    pub(crate) pixel_format: PixelFormat,
31    // palette is an Arc because every chunk of pixel data will reference it (read-only).
32    pub(crate) palette: Option<Arc<ColorPalette>>,
33    pub(crate) layers: LayersData,
34    // pub(crate) color_profile: Option<ColorProfile>,
35    pub(crate) frame_times: Vec<u16>,
36    pub(crate) tags: Vec<Tag>,
37    pub(crate) framedata: CelsData<Pixels>, // Vec<Vec<cel::RawCel>>,
38    pub(crate) external_files: ExternalFilesById,
39    pub(crate) tilesets: TilesetsById,
40    pub(crate) sprite_user_data: Option<UserData>,
41    pub(crate) slices: Vec<Slice>,
42}
43
44/// A reference to a single frame.
45#[derive(Debug)]
46pub struct Frame<'a> {
47    file: &'a AsepriteFile,
48    index: u32,
49}
50
51/// Pixel format of the source Aseprite file.
52#[derive(Debug, Clone, Copy, PartialEq, Eq)]
53pub enum PixelFormat {
54    /// Red, green, blue, and alpha with 8 bits each.
55    Rgba,
56    /// 8 bit grayscale and 8 bit alpha,
57    Grayscale,
58    /// Indexed color. Color is determined by palette.
59    /// The `transparent_color_index` is used to indicate a
60    /// transparent pixel in any non-background layer.
61    #[allow(missing_docs)]
62    Indexed { transparent_color_index: u8 },
63}
64
65impl PixelFormat {
66    /// Number of bytes to store one pixel.
67    pub fn bytes_per_pixel(&self) -> usize {
68        match self {
69            PixelFormat::Rgba => 4,
70            PixelFormat::Grayscale => 2,
71            PixelFormat::Indexed { .. } => 1,
72        }
73    }
74    /// When Indexed, returns the index of the transparent color.
75    pub fn transparent_color_index(&self) -> Option<u8> {
76        match self {
77            PixelFormat::Indexed {
78                transparent_color_index,
79            } => Some(*transparent_color_index),
80            _ => None,
81        }
82    }
83}
84
85impl AsepriteFile {
86    /// Load Aseprite file. Loads full file into memory.
87    pub fn read_file(path: &Path) -> Result<Self> {
88        let file = File::open(path)?;
89        let reader = BufReader::new(file);
90        parse::read_aseprite(reader)
91    }
92
93    /// Load Aseprite file from any input that implements `std::io::Read`.
94    ///
95    /// You can use this to read from an in-memory file.
96    pub fn read<R: Read>(input: R) -> Result<AsepriteFile> {
97        parse::read_aseprite(input)
98    }
99
100    /// Width in pixels.
101    pub fn width(&self) -> usize {
102        self.width as usize
103    }
104
105    /// Height in pixels.
106    pub fn height(&self) -> usize {
107        self.height as usize
108    }
109
110    /// Width and height in pixels.
111    pub fn size(&self) -> (usize, usize) {
112        (self.width(), self.height())
113    }
114
115    /// Number of animation frames.
116    pub fn num_frames(&self) -> u32 {
117        self.num_frames as u32
118    }
119
120    /// Number of layers.
121    pub fn num_layers(&self) -> u32 {
122        self.layers.layers.len() as u32
123    }
124
125    /// The pixel format used by the origal file. This library internally
126    /// represents all images as RGBA.
127    pub fn pixel_format(&self) -> PixelFormat {
128        self.pixel_format
129    }
130
131    /// The color palette in the image.
132    ///
133    /// For indexed color images, this includes all colors used by individual
134    /// cels. However, the final image after layer blending may contain colors
135    /// outside of this palette (or with different transparency levels).
136    pub fn palette(&self) -> Option<&ColorPalette> {
137        self.palette.as_deref()
138    }
139
140    /// Does this file use indexed color format.
141    pub fn is_indexed_color(&self) -> bool {
142        match self.pixel_format() {
143            PixelFormat::Indexed { .. } => true,
144            PixelFormat::Rgba | PixelFormat::Grayscale => false,
145        }
146    }
147
148    /// The color index of the transparent pixel.
149    pub fn transparent_color_index(&self) -> Option<u8> {
150        match self.pixel_format() {
151            PixelFormat::Indexed {
152                transparent_color_index,
153            } => Some(transparent_color_index),
154            PixelFormat::Rgba | PixelFormat::Grayscale => None,
155        }
156    }
157
158    /// Access a layer by ID.
159    ///
160    /// # Panics
161    ///
162    /// Panics if the ID is not valid. ID must be less than number of layers.
163    pub fn layer(&self, id: u32) -> Layer {
164        assert!(id < self.num_layers());
165        Layer {
166            file: self,
167            layer_id: id,
168        }
169    }
170
171    /// Access a layer by name.
172    ///
173    /// If multiple layers with the same name exist returns the layer with
174    /// the lower ID.
175    pub fn layer_by_name(&self, name: &str) -> Option<Layer> {
176        for layer_id in 0..self.num_layers() {
177            let l = self.layer(layer_id);
178            if l.name() == name {
179                return Some(l);
180            }
181        }
182        None
183    }
184
185    /// An iterator over all layers.
186    pub fn layers(&self) -> LayersIter {
187        LayersIter {
188            file: self,
189            next: 0,
190        }
191    }
192
193    /// A reference to a single frame.
194    ///
195    /// # Panics
196    ///
197    /// Panics if `index` is not less than `num_frames`.
198    pub fn frame(&self, index: u32) -> Frame {
199        assert!(index < self.num_frames as u32);
200        Frame { file: self, index }
201    }
202
203    /// Get a direct reference to a [Cel].
204    ///
205    /// Argument order is `x, y` if you think of the timeline panel in the GUI.
206    ///
207    /// # Panics
208    ///
209    /// Panics if `frame` is not less than `num_frames` or if `layer` is not
210    /// less than `num_layers`.
211    pub fn cel(&self, frame: u32, layer: u32) -> Cel {
212        assert!(frame < self.num_frames as u32 && layer < self.num_layers());
213        Cel {
214            file: self,
215            cel_id: CelId {
216                frame: frame as u16,
217                layer: layer as u16,
218            },
219        }
220    }
221
222    /// A mapping from external file ids to external files.
223    pub fn external_files(&self) -> &ExternalFilesById {
224        &self.external_files
225    }
226
227    /// Get a reference to an external file by ID, if the file exists.
228    pub fn external_file_by_id(&self, id: &ExternalFileId) -> Option<&ExternalFile> {
229        self.external_files.get(id)
230    }
231
232    /// Total number of tags.
233    pub fn num_tags(&self) -> u32 {
234        self.tags.len() as u32
235    }
236
237    /// Get a reference to the tag by ID.
238    pub fn get_tag(&self, tag_id: u32) -> Option<&Tag> {
239        self.tags.get(tag_id as usize)
240    }
241
242    /// Get a reference to the tag by ID.
243    ///
244    /// # Panics
245    ///
246    /// Panics if `tag_id` is not less than `num_tags`.
247    pub fn tag(&self, tag_id: u32) -> &Tag {
248        &self.tags[tag_id as usize]
249    }
250
251    /// Lookup tag by name.
252    ///
253    /// If multiple tags with the same name exist, returns the one with the
254    /// lower ID.
255    pub fn tag_by_name(&self, name: &str) -> Option<&Tag> {
256        self.tags.iter().find(|&tag| tag.name() == name)
257    }
258
259    /// Access the file's [Tileset]s.
260    pub fn tilesets(&self) -> &TilesetsById {
261        &self.tilesets
262    }
263
264    /// Get the [Tilemap] at the given cel.
265    ///
266    /// Returns `None` if the cel is empty or if it is not a tilemap.
267    pub fn tilemap(&self, layer_id: u32, frame: u32) -> Option<Tilemap> {
268        if layer_id >= self.num_layers() || frame >= self.num_frames() {
269            return None;
270        }
271        match self.layer(layer_id).layer_type() {
272            LayerType::Tilemap(tileset_id) => {
273                let tileset = self.tilesets().get(tileset_id)?;
274                let cel = self.cel(frame, layer_id);
275                if !cel.is_tilemap() {
276                    return None;
277                }
278                let pixel_width = self.width() as u32;
279                let pixel_height = self.height() as u32;
280                let (tile_width, tile_height) = tileset.tile_size().into();
281                let w = (pixel_width + tile_width - 1) / tile_width;
282                let h = (pixel_height + tile_height - 1) / tile_height;
283                assert!(w < (1u32 << 16) && h < (1u32 << 16));
284                Some(Tilemap {
285                    cel,
286                    tileset,
287                    logical_size: (w as u16, h as u16),
288                })
289            }
290            LayerType::Image | LayerType::Group => None,
291        }
292    }
293
294    /// The user data for the entire sprite, if any exists.
295    pub fn sprite_user_data(&self) -> Option<&UserData> {
296        self.sprite_user_data.as_ref()
297    }
298
299    /// All [Slice]s in the file.
300    pub fn slices(&self) -> &[Slice] {
301        &self.slices
302    }
303
304    // pub fn color_profile(&self) -> Option<&ColorProfile> {
305    //     self.color_profile.as_ref()
306    // }
307
308    /// Construct the image belonging to the specific animation frame. Combines
309    /// layers according to their blend mode. Skips invisible layers (i.e.,
310    /// layers with a deactivated eye icon).
311    ///
312    /// Can fail if the `frame` does not exist, an unsupported feature is
313    /// used, or the file is malformed.
314    fn frame_image(&self, frame: u16) -> RgbaImage {
315        let mut image = RgbaImage::new(self.width as u32, self.height as u32);
316
317        for (layer_id, cel) in self.framedata.frame_cels(frame) {
318            // TODO: Ensure this is always done in layer order (pre-sort Cels?)
319            if !self.layer(layer_id).is_visible() {
320                continue;
321            }
322            self.write_cel(&mut image, cel);
323        }
324
325        image
326    }
327
328    fn write_cel(&self, image: &mut RgbaImage, cel: &RawCel<Pixels>) {
329        let RawCel { data, content, .. } = cel;
330        let layer = self.layer(data.layer_index as u32);
331        let blend_mode = layer.blend_mode();
332        // let resolver_data = pixel::IndexResolverData {
333        //     palette: self.palette.as_ref(),
334        //     transparent_color_index: self.pixel_format.transparent_color_index(),
335        //     layer_is_background: self.layers[layer.id()].is_background(),
336        // };
337        match &content {
338            CelContent::Raw(image_content) => {
339                let ImageContent { size, pixels } = image_content;
340                let image_pixels = pixels.clone_as_image_rgba();
341
342                write_raw_cel_to_image(
343                    image,
344                    data,
345                    size,
346                    image_pixels.as_ref(),
347                    &blend_mode,
348                    layer.opacity(),
349                );
350            }
351            CelContent::Tilemap(tilemap_data) => {
352                let layer_type = layer.layer_type();
353                let tileset_id = if let LayerType::Tilemap(tileset_id) = layer_type {
354                    tileset_id
355                } else {
356                    panic!(
357                        "Tilemap cel not in tilemap layer. Should have been caught by CelsData::validate"
358                    );
359                };
360                let tileset = self
361                    .tilesets()
362                    .get(tileset_id)
363                    .expect("Tilemap layer references a missing tileset. Should have been caught by LayersData::validate()");
364                let tileset_pixels = tileset
365                    .pixels
366                    .as_ref()
367                    .expect("Expected Tileset data to contain pixels. Should have been caught by TilesetsById::validate()");
368                let rgba_pixels = tileset_pixels.clone_as_image_rgba();
369
370                write_tilemap_cel_to_image(
371                    image,
372                    data,
373                    tilemap_data,
374                    tileset,
375                    rgba_pixels.as_ref(),
376                    &blend_mode,
377                    layer.opacity(),
378                );
379            }
380            CelContent::Linked(frame) => {
381                if let Some(cel) = self.framedata.cel(CelId {
382                    frame: *frame,
383                    layer: data.layer_index,
384                }) {
385                    if let CelContent::Linked(_) = cel.content {
386                        panic!(
387                            "Cel links to empty cel. Should have been caught by CelsData::validate"
388                        );
389                    } else {
390                        // Recurse once with the source non-Linked cel
391                        self.write_cel(image, cel);
392                    }
393                }
394            }
395        }
396    }
397
398    pub(crate) fn layer_image(&self, cel_id: CelId) -> RgbaImage {
399        let mut image = RgbaImage::new(self.width as u32, self.height as u32);
400        if let Some(cel) = self.framedata.cel(cel_id) {
401            self.write_cel(&mut image, cel);
402        }
403        image
404    }
405
406    // fn frame_cels(&self, frame: u16, layer: u16) -> Vec<&RawCel> {
407    //     self.framedata[frame as usize]
408    //         .iter()
409    //         .filter(|c| c.layer_index == layer)
410    //         .collect()
411    // }
412}
413
414/// An iterator over layers. See [AsepriteFile::layers].
415#[derive(Debug)]
416pub struct LayersIter<'a> {
417    file: &'a AsepriteFile,
418    next: u32,
419}
420
421impl<'a> Iterator for LayersIter<'a> {
422    type Item = Layer<'a>;
423
424    fn next(&mut self) -> Option<Self::Item> {
425        if self.next < self.file.num_layers() {
426            let item = self.file.layer(self.next);
427            self.next += 1;
428            Some(item)
429        } else {
430            None
431        }
432    }
433}
434
435impl<'a> Frame<'a> {
436    /// Construct the image belonging to the specific animation frame. Combines
437    /// layers according to their blend mode. Skips invisible layers (i.e.,
438    /// layers with a deactivated eye icon).
439    ///
440    pub fn image(&self) -> RgbaImage {
441        self.file.frame_image(self.index as u16)
442    }
443
444    /// Frame ID, i.e., the frame number.
445    pub fn id(&self) -> u32 {
446        self.index
447    }
448
449    /// Get cel corresponding to the given layer in this frame.
450    pub fn layer(&self, layer_id: u32) -> Cel {
451        assert!(layer_id < self.file.num_layers());
452        let cel_id = CelId {
453            frame: self.index as u16,
454            layer: layer_id as u16,
455        };
456        Cel {
457            file: self.file,
458            cel_id,
459        }
460    }
461
462    /// Frame duration in milliseconds.
463    pub fn duration(&self) -> u32 {
464        self.file.frame_times[self.index as usize] as u32
465    }
466}
467
468type BlendFn = Box<dyn Fn(Color8, Color8, u8) -> Color8>;
469
470fn blend_mode_to_blend_fn(mode: BlendMode) -> BlendFn {
471    // TODO: Make these statically allocated
472    match mode {
473        BlendMode::Normal => Box::new(blend::normal),
474        BlendMode::Multiply => Box::new(blend::multiply),
475        BlendMode::Screen => Box::new(blend::screen),
476        BlendMode::Overlay => Box::new(blend::overlay),
477        BlendMode::Darken => Box::new(blend::darken),
478        BlendMode::Lighten => Box::new(blend::lighten),
479        BlendMode::ColorDodge => Box::new(blend::color_dodge),
480        BlendMode::ColorBurn => Box::new(blend::color_burn),
481        BlendMode::HardLight => Box::new(blend::hard_light),
482        BlendMode::SoftLight => Box::new(blend::soft_light),
483        BlendMode::Difference => Box::new(blend::difference),
484        BlendMode::Exclusion => Box::new(blend::exclusion),
485        BlendMode::Hue => Box::new(blend::hsl_hue),
486        BlendMode::Saturation => Box::new(blend::hsl_saturation),
487        BlendMode::Color => Box::new(blend::hsl_color),
488        BlendMode::Luminosity => Box::new(blend::hsl_luminosity),
489        BlendMode::Addition => Box::new(blend::addition),
490        BlendMode::Subtract => Box::new(blend::subtract),
491        BlendMode::Divide => Box::new(blend::divide),
492    }
493}
494
495fn tile_slice<'a, T>(pixels: &'a [T], tile_size: &TileSize, tile_id: &TileId) -> &'a [T] {
496    let pixels_per_tile = tile_size.pixels_per_tile() as usize;
497    let start = pixels_per_tile * (tile_id.0 as usize);
498    let end = start + pixels_per_tile;
499    &pixels[start..end]
500}
501
502fn write_tilemap_cel_to_image(
503    image: &mut RgbaImage,
504    cel_data: &CelCommon,
505    tilemap_data: &TilemapData,
506    tileset: &Tileset,
507    pixels: &[Rgba<u8>],
508    blend_mode: &BlendMode,
509    outer_opacity: u8,
510) {
511    let CelCommon {
512        x,
513        y,
514        opacity: cel_opacity,
515        ..
516    } = cel_data;
517    let cel_x = *x as i32;
518    let cel_y = *y as i32;
519    let opacity = mul_un8(outer_opacity as i32, *cel_opacity as i32);
520    // tilemap dimensions
521    let tilemap_width = tilemap_data.width() as i32;
522    let tilemap_height = tilemap_data.height() as i32;
523    //let tiles = &tilemap_data.tiles;
524    // tile dimensions
525    let tile_size = tileset.tile_size();
526    let tile_width = tile_size.width() as i32;
527    let tile_height = tile_size.height() as i32;
528    // pixels
529    let blend_fn = blend_mode_to_blend_fn(*blend_mode);
530
531    for tile_y in 0..tilemap_height {
532        for tile_x in 0..tilemap_width {
533            // TODO: support tile transform flags
534            let tile = tilemap_data
535                .tile(tile_x as u16, tile_y as u16)
536                .expect("Invalid tile index");
537            let tile_id = &tile.id;
538            let tile_pixels = tile_slice(pixels, &tile_size, tile_id);
539            for pixel_y in 0..tile_height {
540                for pixel_x in 0..tile_width {
541                    let pixel_idx = ((pixel_y * tile_width) + pixel_x) as usize;
542                    let image_pixel = tile_pixels[pixel_idx];
543                    let image_x = (tile_x * tile_width) + pixel_x + cel_x;
544                    let image_y = (tile_y * tile_height) + pixel_y + cel_y;
545                    // Skip pixels off of the canvas.
546                    let x_in_bounds = (0..(image.width() as i32)).contains(&image_x);
547                    let y_in_bounds = (0..(image.height() as i32)).contains(&image_y);
548                    if x_in_bounds && y_in_bounds {
549                        let image_x = image_x as u32;
550                        let image_y = image_y as u32;
551                        let src = *image.get_pixel(image_x, image_y);
552                        let new = blend_fn(src, image_pixel, opacity);
553                        image.put_pixel(image_x, image_y, new);
554                    }
555                }
556            }
557        }
558    }
559}
560
561fn write_raw_cel_to_image(
562    image: &mut RgbaImage,
563    cel_data: &CelCommon,
564    image_size: &ImageSize,
565    pixels: &[Rgba<u8>],
566    blend_mode: &BlendMode,
567    outer_opacity: u8,
568) {
569    let ImageSize { width, height } = image_size;
570    let CelCommon {
571        x,
572        y,
573        opacity: cel_opacity,
574        ..
575    } = cel_data;
576    let opacity = mul_un8(outer_opacity as i32, *cel_opacity as i32);
577    let blend_fn = blend_mode_to_blend_fn(*blend_mode);
578    let x0 = *x as i32;
579    let y0 = *y as i32;
580    let x_end = x0 + (*width as i32);
581    let y_end = y0 + (*height as i32);
582    let (img_width, img_height) = image.dimensions();
583
584    for y in y0..y_end {
585        if y < 0 || y >= img_height as i32 {
586            continue;
587        }
588        for x in x0..x_end {
589            if x < 0 || x >= img_width as i32 {
590                continue;
591            }
592            let idx = (y - y0) as usize * *width as usize + (x - x0) as usize;
593            let image_pixel = pixels[idx];
594            let src = *image.get_pixel(x as u32, y as u32);
595            let new = blend_fn(src, image_pixel, opacity);
596            image.put_pixel(x as u32, y as u32, new);
597        }
598    }
599}