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 pub layer_type: LayerType,
86}
87
88impl AsepriteFile<'_> {
89 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 pub fn size(&self) -> (u16, u16) {
187 (self.file.header.width, self.file.header.height)
188 }
189 pub fn tags(&self) -> &[Tag] {
191 &self.tags
192 }
193 pub fn layers(&self) -> &[Layer] {
195 &self.layers
196 }
197 pub fn frames(&self) -> &[Frame] {
199 &self.frames
200 }
201 pub fn image_count(&self) -> usize {
203 self.images.len()
204 }
205
206 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 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#[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}