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