1use 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#[derive(Debug)]
34pub struct AsepriteFile<'a> {
35 pub file: File<'a>,
37 pub layers: Vec<Layer>,
39 pub frames: Vec<Frame>,
41 pub tags: Vec<Tag>,
43 pub images: Vec<Image<'a>>,
45}
46
47#[derive(Debug, Copy, Clone)]
51pub struct FrameCel {
52 pub origin: (i16, i16),
54 pub size: (u16, u16),
56 pub layer_index: usize,
58 pub image_index: usize,
60}
61
62#[derive(Debug, Clone)]
66pub struct Frame {
67 pub duration: u16,
69 pub origin: (i16, i16),
71 pub cels: Vec<FrameCel>,
73}
74
75#[derive(Debug, Clone)]
79pub struct Tag {
80 pub name: String,
82 pub range: RangeInclusive<u16>,
84 pub direction: AnimationDirection,
86 pub repeat: Option<u16>,
88}
89
90#[derive(Debug, Clone)]
92pub struct Layer {
93 pub name: String,
95 pub opacity: u8,
97 pub blend_mode: BlendMode,
99 pub visible: bool,
101 pub layer_type: LayerType,
103}
104
105#[derive(Debug, Clone, PartialEq, Eq)]
109pub enum LayerSelection {
110 Visible,
112 All,
114 Mask(Vec<bool>),
116}
117
118impl LayerSelection {
119 fn is_selected(&self, layer_index: usize, layer: &Layer) -> bool {
120 match self {
121 Self::Visible => layer.visible,
122 Self::All => true,
123 Self::Mask(mask) => mask.get(layer_index).copied().unwrap_or(false),
124 }
125 }
126}
127
128impl AsepriteFile<'_> {
129 pub fn load(data: &[u8]) -> Result<AsepriteFile<'_>, LoadSpriteError> {
131 let file = parse_file(data).map_err(|e| LoadSpriteError::Parse {
132 message: e.to_string(),
133 })?;
134 let layers: Vec<_> = file
135 .layers
136 .iter()
137 .filter_map(|layer| {
138 if layer.layer_type == LayerType::Normal || layer.layer_type == LayerType::Group {
139 Some(Layer {
140 name: layer.name.to_string(),
141 opacity: layer.opacity,
142 blend_mode: layer.blend_mode,
143 visible: layer.flags.contains(LayerFlags::VISIBLE),
144 layer_type: layer.layer_type,
145 })
146 } else {
147 None
148 }
149 })
150 .collect();
151
152 let mut image_vec: Vec<Image<'_>> = Vec::new();
153 let mut image_map: HashMap<(usize, usize), usize> = HashMap::new();
154
155 for (frame_index, frame) in file.frames.iter().enumerate() {
156 for cel in frame.cels.iter().filter_map(|x| x.as_ref()) {
157 if let CelContent::Image(image) = &cel.content {
158 let image_index = image_vec.len();
159 image_vec.push(image.clone());
160 let _ = image_map.insert((frame_index, cel.layer_index.into()), image_index);
161 }
162 }
163 }
164
165 let mut frames: Vec<Frame> = Vec::new();
166 let mut tags: Vec<Tag> = Vec::new();
167
168 for tag in file.tags.iter() {
169 tags.push(Tag {
170 name: tag.name.to_string(),
171 range: tag.frames.clone(),
172 direction: tag.animation_direction,
173 repeat: if tag.animation_repeat > 0 {
174 Some(tag.animation_repeat)
175 } else {
176 None
177 },
178 });
179 }
180
181 for (index, frame) in file.frames.iter().enumerate() {
182 let mut cels: Vec<FrameCel> = Vec::new();
183 for cel in frame.cels.iter().filter_map(|x| x.as_ref()) {
184 let image_index = match cel.content {
185 CelContent::Image(_) => image_map[&(index, cel.layer_index.into())],
186 CelContent::LinkedCel { frame_position } => *image_map
187 .get(&(frame_position.into(), cel.layer_index.into()))
188 .ok_or_else(|| LoadSpriteError::Parse {
189 message: format!(
190 "invalid linked cel at frame {} layer {}",
191 index, cel.layer_index
192 ),
193 })?,
194 _ => {
195 return Err(LoadSpriteError::Parse {
196 message: "invalid cel".to_owned(),
197 })
198 }
199 };
200 let width = image_vec[image_index].width;
201 let height = image_vec[image_index].height;
202 cels.push(FrameCel {
203 origin: (cel.x, cel.y),
204 size: (width, height),
205 layer_index: cel.layer_index.into(),
206 image_index,
207 });
208 }
209
210 frames.push(Frame {
211 duration: frame.duration,
212 origin: (0, 0),
213 cels,
214 });
215 }
216
217 Ok(AsepriteFile {
218 file,
219 tags,
220 layers,
221 frames,
222 images: image_vec,
223 })
224 }
225 pub fn size(&self) -> (u16, u16) {
227 (self.file.header.width, self.file.header.height)
228 }
229 pub fn tags(&self) -> &[Tag] {
231 &self.tags
232 }
233 pub fn layers(&self) -> &[Layer] {
235 &self.layers
236 }
237 pub fn frames(&self) -> &[Frame] {
239 &self.frames
240 }
241 pub fn image_count(&self) -> usize {
243 self.images.len()
244 }
245
246 pub fn select_layers(&self, predicate: impl Fn(&Layer) -> bool) -> LayerSelection {
251 LayerSelection::Mask(self.layers.iter().map(predicate).collect())
252 }
253
254 pub fn select_layers_by_name(&self, names: &[&str]) -> LayerSelection {
260 self.select_layers(|l| names.contains(&l.name.as_str()))
261 }
262
263 #[deprecated(note = "Use `render_frame` instead.")]
268 pub fn combined_frame_image(
269 &self,
270 frame_index: usize,
271 target: &mut [u8],
272 ) -> Result<u64, LoadImageError> {
273 self.render_frame(frame_index, target, &LayerSelection::Visible)?;
274 let mut hasher = DefaultHasher::new();
275 let frame = &self.frames[frame_index];
276 for cel in frame.cels.iter() {
277 (cel.image_index, cel.layer_index, cel.origin, cel.size).hash(&mut hasher);
278 }
279 Ok(hasher.finish())
280 }
281
282 pub fn render_frame(
288 &self,
289 frame_index: usize,
290 target: &mut [u8],
291 layers: &LayerSelection,
292 ) -> Result<(), LoadImageError> {
293 let target_size =
294 usize::from(self.file.header.width) * usize::from(self.file.header.height) * 4;
295
296 if target.len() < target_size {
297 return Err(LoadImageError::TargetBufferTooSmall);
298 }
299
300 let frame = &self.frames[frame_index];
301
302 for cel in frame.cels.iter() {
303 let Some(layer) = self.layers.get(cel.layer_index) else {
304 continue;
305 };
306 if layer.layer_type == LayerType::Group || !layers.is_selected(cel.layer_index, layer) {
307 continue;
308 }
309
310 let mut cel_target = vec![0; usize::from(cel.size.0) * usize::from(cel.size.1) * 4];
311 self.load_image(cel.image_index, &mut cel_target).unwrap();
312 let layer = &self.layers[cel.layer_index];
313
314 let blend_fn = blend_mode_to_blend_fn(layer.blend_mode);
315
316 for y in 0..cel.size.1 {
317 for x in 0..cel.size.0 {
318 let Some(target_x) = x.checked_add_signed(cel.origin.0) else {
319 continue;
320 };
321 if target_x >= self.file.header.width {
322 continue;
323 }
324 let Some(target_y) = y.checked_add_signed(cel.origin.1) else {
325 continue;
326 };
327 if target_y >= self.file.header.height {
328 continue;
329 }
330
331 let target_index = usize::from(target_y) * usize::from(self.file.header.width)
332 + usize::from(target_x);
333 let cel_index = usize::from(y) * usize::from(cel.size.0) + usize::from(x);
334
335 let cel_pixel: &[u8] = &cel_target[cel_index * 4..cel_index * 4 + 4];
336 let target_pixel: &mut [u8] =
337 &mut target[target_index * 4..target_index * 4 + 4];
338
339 let back = Color::from(&*target_pixel);
340 let front = Color::from(cel_pixel);
341 let out = blend_fn(back, front, layer.opacity);
342
343 target_pixel[0] = out.r;
344 target_pixel[1] = out.g;
345 target_pixel[2] = out.b;
346 target_pixel[3] = out.a;
347 }
348 }
349 }
350 Ok(())
351 }
352
353 pub fn load_image(&self, index: usize, target: &mut [u8]) -> Result<(), LoadImageError> {
355 let image = &self.images[index];
356 let target_size = usize::from(image.width) * usize::from(image.height) * 4;
357 if target.len() < target_size {
358 return Err(LoadImageError::TargetBufferTooSmall);
359 }
360 let target = &mut target[..target_size];
361 match (self.file.header.color_depth, image.compressed) {
362 (ColorDepth::Rgba, false) => target.copy_from_slice(image.data),
363 (ColorDepth::Rgba, true) => decompress(image.data, target)?,
364 (ColorDepth::Grayscale, false) => {
365 grayscale_to_rgba(image.data, target)?;
366 }
367 (ColorDepth::Grayscale, true) => {
368 let mut buf = vec![0u8; usize::from(image.width) * usize::from(image.height) * 2];
369 decompress(image.data, &mut buf)?;
370 grayscale_to_rgba(&buf, target)?;
371 }
372 (ColorDepth::Indexed, false) => {
373 indexed_to_rgba(
374 image.data,
375 self.file
376 .palette
377 .as_ref()
378 .ok_or(LoadImageError::MissingPalette)?,
379 target,
380 )?;
381 }
382 (ColorDepth::Indexed, true) => {
383 let mut buf = vec![0u8; usize::from(image.width) * usize::from(image.height)];
384 decompress(image.data, &mut buf)?;
385 indexed_to_rgba(
386 &buf,
387 self.file
388 .palette
389 .as_ref()
390 .ok_or(LoadImageError::MissingPalette)?,
391 target,
392 )?;
393 }
394 (ColorDepth::Unknown(_), _) => return Err(LoadImageError::UnsupportedColorDepth),
395 }
396 Ok(())
397 }
398 pub fn slices(&self) -> &[SliceChunk<'_>] {
400 &self.file.slices
401 }
402}
403
404use thiserror::Error;
405
406#[derive(Error, Debug)]
408pub enum LoadSpriteError {
409 #[error("parsing failed {message}")]
411 Parse {
412 message: String,
414 },
415 #[error("missing tag: {0}")]
417 MissingTag(String),
418 #[error("missing layer: {0}")]
420 MissingLayer(String),
421 #[error("frame index out of range: {0}")]
423 FrameIndexOutOfRange(usize),
424}
425
426#[allow(missing_copy_implementations)]
428#[derive(Error, Debug)]
429pub enum LoadImageError {
430 #[error("target buffer too small")]
432 TargetBufferTooSmall,
433 #[error("missing palette")]
435 MissingPalette,
436 #[error("unsupported color depth")]
438 UnsupportedColorDepth,
439 #[error("decompression failed")]
441 DecompressError,
442 #[error("invalid image data")]
444 InvalidImageData,
445}
446
447pub fn decompress(data: &[u8], target: &mut [u8]) -> Result<(), LoadImageError> {
451 let mut decompressor = Decompress::new(true);
452 match decompressor.decompress(data, target, flate2::FlushDecompress::Finish) {
453 Ok(flate2::Status::Ok | flate2::Status::BufError) => Err(LoadImageError::DecompressError),
454 Ok(flate2::Status::StreamEnd) => Ok(()),
455 Err(_) => Err(LoadImageError::DecompressError),
456 }
457}
458
459fn grayscale_to_rgba(source: &[u8], target: &mut [u8]) -> Result<(), LoadImageError> {
460 if target.len() != source.len() * 2 {
461 return Err(LoadImageError::InvalidImageData);
462 }
463 for (i, chunk) in source.chunks(2).enumerate() {
464 target[i * 4] = chunk[0];
465 target[i * 4 + 1] = chunk[0];
466 target[i * 4 + 2] = chunk[0];
467 target[i * 4 + 3] = chunk[1];
468 }
469 Ok(())
470}
471
472fn indexed_to_rgba(
473 source: &[u8],
474 palette: &Palette,
475 target: &mut [u8],
476) -> Result<(), LoadImageError> {
477 if target.len() != source.len() * 4 {
478 return Err(LoadImageError::InvalidImageData);
479 }
480 for (i, px) in source.iter().enumerate() {
481 let color = palette.colors[usize::from(*px)];
482 target[i * 4] = color.red;
483 target[i * 4 + 1] = color.green;
484 target[i * 4 + 2] = color.blue;
485 target[i * 4 + 3] = color.alpha;
486 }
487 Ok(())
488}
489
490#[test]
491fn test_cel() {
492 use image::RgbaImage;
493 use tempfile::TempDir;
494
495 let path = "./tests/combine.aseprite";
496 let file = std::fs::read(path).unwrap();
497 let file = AsepriteFile::load(&file).unwrap();
498
499 for frame in file.frames().iter() {
500 for (i, cel) in frame.cels.iter().enumerate() {
501 let (width, height) = cel.size;
502
503 let mut target = vec![0; usize::from(width * height) * 4];
504 file.load_image(cel.image_index, &mut target).unwrap();
505
506 let image = RgbaImage::from_raw(u32::from(width), u32::from(height), target).unwrap();
507
508 let tmp = TempDir::with_prefix("aseprite-loader").unwrap();
509 let path = tmp.path().join(format!("cel_{}.png", i));
510 image.save(path).unwrap();
511 }
512 }
513}
514
515#[test]
516fn test_combine() {
517 use image::RgbaImage;
518 use tempfile::TempDir;
519
520 let path = "./tests/combine.aseprite";
521 let file = std::fs::read(path).unwrap();
522 let file = AsepriteFile::load(&file).unwrap();
523
524 let (width, height) = file.size();
525 for (index, _) in file.frames().iter().enumerate() {
526 let mut target = vec![0; usize::from(width * height) * 4];
527 file.render_frame(index, &mut target, &LayerSelection::Visible)
528 .unwrap();
529 let image = RgbaImage::from_raw(u32::from(width), u32::from(height), target).unwrap();
530
531 let tmp = TempDir::with_prefix("aseprite-loader").unwrap();
532 println!("{:?}", tmp);
533 let path = tmp.path().join(format!("combined_{}.png", index));
534 image.save(path).unwrap();
535 }
536}
537
538#[test]
539fn test_visible_layers_empty_produces_blank() {
540 let data = std::fs::read("./tests/layers.aseprite").unwrap();
541 let file = AsepriteFile::load(&data).unwrap();
542 let (width, height) = file.size();
543 let mut buf = vec![0u8; usize::from(width) * usize::from(height) * 4];
544 let sel = file.select_layers_by_name(&[]);
545 file.render_frame(0, &mut buf, &sel).unwrap();
546 assert!(
547 buf.iter().all(|&b| b == 0),
548 "empty layer list should produce a blank image"
549 );
550}
551
552#[test]
553fn test_visible_layers_single_differs_from_all() {
554 let data = std::fs::read("./tests/layers.aseprite").unwrap();
555 let file = AsepriteFile::load(&data).unwrap();
556
557 let visible_normal: Vec<&str> = file
558 .layers()
559 .iter()
560 .filter(|l| l.visible && l.layer_type != LayerType::Group)
561 .map(|l| l.name.as_str())
562 .collect();
563
564 assert!(
565 visible_normal.len() >= 2,
566 "layers.aseprite needs at least 2 visible layers for this test"
567 );
568
569 let (width, height) = file.size();
570 let buf_size = usize::from(width) * usize::from(height) * 4;
571
572 let mut buf_all = vec![0u8; buf_size];
573 file.render_frame(0, &mut buf_all, &LayerSelection::Visible)
574 .unwrap();
575
576 let sel = file.select_layers_by_name(&visible_normal[..1]);
577 let mut buf_one = vec![0u8; buf_size];
578 file.render_frame(0, &mut buf_one, &sel).unwrap();
579
580 assert_ne!(
581 buf_all, buf_one,
582 "single layer composite should differ from all-layers composite"
583 );
584}
585
586#[test]
588fn test_issue_4_1() {
589 let path = "./tests/issue_4_1.aseprite";
590 let file = std::fs::read(path).unwrap();
591 let file = AsepriteFile::load(&file).unwrap();
592 let (width, height) = file.size();
593 let mut buf = vec![0; usize::from(width * height) * 4];
594 for idx in 0..file.frames().len() {
595 file.render_frame(idx, &mut buf, &LayerSelection::Visible)
596 .unwrap();
597 }
598}
599
600#[test]
601fn test_issue_4_2() {
602 let path = "./tests/issue_4_2.aseprite";
603 let file = std::fs::read(path).unwrap();
604 let file = AsepriteFile::load(&file).unwrap();
605 let (width, height) = file.size();
606 let mut buf = vec![0; usize::from(width * height) * 4];
607 for idx in 0..file.frames().len() {
608 file.render_frame(idx, &mut buf, &LayerSelection::Visible)
609 .unwrap();
610 }
611}