1// Aseprite
2// Copyright (C) 2018-2022 Igara Studio S.A.
3// Copyright (C) 2001-2018 David Capello
4//
5// This program is distributed under the terms of
6// the End-User License Agreement for Aseprite.
7
8#ifdef HAVE_CONFIG_H
9#include "config.h"
10#endif
11
12#include "app/context.h"
13#include "app/doc.h"
14#include "app/file/file.h"
15#include "app/file/file_format.h"
16#include "app/file/format_options.h"
17#include "app/pref/preferences.h"
18#include "base/cfile.h"
19#include "base/exception.h"
20#include "base/file_handle.h"
21#include "base/fs.h"
22#include "dio/aseprite_common.h"
23#include "dio/aseprite_decoder.h"
24#include "dio/decode_delegate.h"
25#include "dio/file_interface.h"
26#include "doc/doc.h"
27#include "fixmath/fixmath.h"
28#include "fmt/format.h"
29#include "ui/alert.h"
30#include "ver/info.h"
31#include "zlib.h"
32
33#include <cstdio>
34
35namespace app {
36
37using namespace base;
38
39namespace {
40
41class DecodeDelegate : public dio::DecodeDelegate {
42public:
43 DecodeDelegate(FileOp* fop)
44 : m_fop(fop)
45 , m_sprite(nullptr) {
46 }
47 ~DecodeDelegate() { }
48
49 void error(const std::string& msg) override {
50 m_fop->setError(msg.c_str());
51 }
52
53 void progress(double fromZeroToOne) override {
54 m_fop->setProgress(fromZeroToOne);
55 }
56
57 bool isCanceled() override {
58 return m_fop->isStop();
59 }
60
61 bool decodeOneFrame() override {
62 return m_fop->isOneFrame();
63 }
64
65 doc::color_t defaultSliceColor() override {
66 auto color = Preferences::instance().slices.defaultColor();
67 return doc::rgba(color.getRed(),
68 color.getGreen(),
69 color.getBlue(),
70 color.getAlpha());
71 }
72
73 void onSprite(doc::Sprite* sprite) override {
74 m_sprite = sprite;
75 }
76
77 doc::Sprite* sprite() { return m_sprite; }
78
79private:
80 FileOp* m_fop;
81 doc::Sprite* m_sprite;
82};
83
84class ScanlinesGen {
85public:
86 virtual ~ScanlinesGen() { }
87 virtual gfx::Size getImageSize() = 0;
88 virtual int getScanlineSize() = 0;
89 virtual const uint8_t* getScanlineAddress(int y) = 0;
90};
91
92class ImageScanlines : public ScanlinesGen {
93 const Image* m_image;
94public:
95 ImageScanlines(const Image* image) : m_image(image) { }
96 gfx::Size getImageSize() override {
97 return gfx::Size(m_image->width(),
98 m_image->height());
99 }
100 int getScanlineSize() override {
101 return doc::calculate_rowstride_bytes(
102 m_image->pixelFormat(),
103 m_image->width());
104 }
105 const uint8_t* getScanlineAddress(int y) override {
106 return m_image->getPixelAddress(0, y);
107 }
108};
109
110class TilesetScanlines : public ScanlinesGen {
111 const Tileset* m_tileset;
112public:
113 TilesetScanlines(const Tileset* tileset) : m_tileset(tileset) { }
114 gfx::Size getImageSize() override {
115 return gfx::Size(m_tileset->grid().tileSize().w,
116 m_tileset->grid().tileSize().h * m_tileset->size());
117 }
118 int getScanlineSize() override {
119 return doc::calculate_rowstride_bytes(
120 m_tileset->sprite()->pixelFormat(),
121 m_tileset->grid().tileSize().w);
122 }
123 const uint8_t* getScanlineAddress(int y) override {
124 const int h = m_tileset->grid().tileSize().h;
125 const tile_index ti = (y / h);
126 ASSERT(ti >= 0 && ti < m_tileset->size());
127 ImageRef image = m_tileset->get(ti);
128 ASSERT(image);
129 if (image)
130 return image->getPixelAddress(0, y % h);
131 else
132 return nullptr;
133 }
134};
135
136} // anonymous namespace
137
138static void ase_file_prepare_header(FILE* f, dio::AsepriteHeader* header, const Sprite* sprite,
139 const frame_t firstFrame, const frame_t totalFrames);
140static void ase_file_write_header(FILE* f, dio::AsepriteHeader* header);
141static void ase_file_write_header_filesize(FILE* f, dio::AsepriteHeader* header);
142
143static void ase_file_prepare_frame_header(FILE* f, dio::AsepriteFrameHeader* frame_header);
144static void ase_file_write_frame_header(FILE* f, dio::AsepriteFrameHeader* frame_header);
145
146static void ase_file_write_layers(FILE* f, dio::AsepriteFrameHeader* frame_header, const Layer* layer, int child_level);
147static layer_t ase_file_write_cels(FILE* f, dio::AsepriteFrameHeader* frame_header,
148 const Sprite* sprite, const Layer* layer,
149 layer_t layer_index,
150 const frame_t frame,
151 const frame_t firstFrame);
152
153static void ase_file_write_padding(FILE* f, int bytes);
154static void ase_file_write_string(FILE* f, const std::string& string);
155
156static void ase_file_write_start_chunk(FILE* f, dio::AsepriteFrameHeader* frame_header, int type, dio::AsepriteChunk* chunk);
157static void ase_file_write_close_chunk(FILE* f, dio::AsepriteChunk* chunk);
158
159static void ase_file_write_color2_chunk(FILE* f, dio::AsepriteFrameHeader* frame_header, const Palette* pal);
160static void ase_file_write_palette_chunk(FILE* f, dio::AsepriteFrameHeader* frame_header, const Palette* pal, int from, int to);
161static void ase_file_write_layer_chunk(FILE* f, dio::AsepriteFrameHeader* frame_header, const Layer* layer, int child_level);
162static void ase_file_write_cel_chunk(FILE* f, dio::AsepriteFrameHeader* frame_header,
163 const Cel* cel,
164 const LayerImage* layer,
165 const layer_t layer_index,
166 const Sprite* sprite,
167 const frame_t firstFrame);
168static void ase_file_write_cel_extra_chunk(FILE* f, dio::AsepriteFrameHeader* frame_header,
169 const Cel* cel);
170static void ase_file_write_color_profile(FILE* f,
171 dio::AsepriteFrameHeader* frame_header,
172 const doc::Sprite* sprite);
173#if 0
174static void ase_file_write_mask_chunk(FILE* f, dio::AsepriteFrameHeader* frame_header, Mask* mask);
175#endif
176static void ase_file_write_tags_chunk(FILE* f, dio::AsepriteFrameHeader* frame_header, const Tags* tags,
177 const frame_t fromFrame, const frame_t toFrame);
178static void ase_file_write_slice_chunks(FILE* f, dio::AsepriteFrameHeader* frame_header, const Slices& slices,
179 const frame_t fromFrame, const frame_t toFrame);
180static void ase_file_write_slice_chunk(FILE* f, dio::AsepriteFrameHeader* frame_header, Slice* slice,
181 const frame_t fromFrame, const frame_t toFrame);
182static void ase_file_write_user_data_chunk(FILE* f, dio::AsepriteFrameHeader* frame_header, const UserData* userData);
183static void ase_file_write_external_files_chunk(FILE* f,
184 dio::AsepriteFrameHeader* frame_header,
185 dio::AsepriteExternalFiles& ext_files,
186 const Sprite* sprite);
187static void ase_file_write_tileset_chunks(FILE* f, FileOp* fop,
188 dio::AsepriteFrameHeader* frame_header,
189 const dio::AsepriteExternalFiles& ext_files,
190 const Tilesets* tilesets);
191static void ase_file_write_tileset_chunk(FILE* f, FileOp* fop,
192 dio::AsepriteFrameHeader* frame_header,
193 const dio::AsepriteExternalFiles& ext_files,
194 const Tileset* tileset,
195 const tileset_index si);
196static bool ase_has_groups(LayerGroup* group);
197static void ase_ungroup_all(LayerGroup* group);
198
199class ChunkWriter {
200public:
201 ChunkWriter(FILE* f, dio::AsepriteFrameHeader* frame_header, int type) : m_file(f) {
202 ase_file_write_start_chunk(m_file, frame_header, type, &m_chunk);
203 }
204
205 ~ChunkWriter() {
206 ase_file_write_close_chunk(m_file, &m_chunk);
207 }
208
209private:
210 FILE* m_file;
211 dio::AsepriteChunk m_chunk;
212};
213
214class AseFormat : public FileFormat {
215
216 const char* onGetName() const override {
217 return "ase";
218 }
219
220 void onGetExtensions(base::paths& exts) const override {
221 exts.push_back("ase");
222 exts.push_back("aseprite");
223 }
224
225 dio::FileFormat onGetDioFormat() const override {
226 return dio::FileFormat::ASE_ANIMATION;
227 }
228
229 int onGetFlags() const override {
230 return
231 FILE_SUPPORT_LOAD |
232 FILE_SUPPORT_SAVE |
233 FILE_SUPPORT_RGB |
234 FILE_SUPPORT_RGBA |
235 FILE_SUPPORT_GRAY |
236 FILE_SUPPORT_GRAYA |
237 FILE_SUPPORT_INDEXED |
238 FILE_SUPPORT_LAYERS |
239 FILE_SUPPORT_FRAMES |
240 FILE_SUPPORT_PALETTES |
241 FILE_SUPPORT_TAGS |
242 FILE_SUPPORT_BIG_PALETTES |
243 FILE_SUPPORT_PALETTE_WITH_ALPHA;
244 }
245
246 bool onLoad(FileOp* fop) override;
247 bool onPostLoad(FileOp* fop) override;
248#ifdef ENABLE_SAVE
249 bool onSave(FileOp* fop) override;
250#endif
251};
252
253FileFormat* CreateAseFormat()
254{
255 return new AseFormat;
256}
257
258bool AseFormat::onLoad(FileOp* fop)
259{
260 FileHandle handle(open_file_with_exception(fop->filename(), "rb"));
261 dio::StdioFileInterface fileInterface(handle.get());
262
263 DecodeDelegate delegate(fop);
264 dio::AsepriteDecoder decoder;
265 decoder.initialize(&delegate, &fileInterface);
266 if (!decoder.decode())
267 return false;
268
269 Sprite* sprite = delegate.sprite();
270 fop->createDocument(sprite);
271
272 if (sprite->colorSpace() != nullptr &&
273 sprite->colorSpace()->type() != gfx::ColorSpace::None) {
274 fop->setEmbeddedColorProfile();
275 }
276
277 // Sprite grid bounds will be set to empty (instead of
278 // doc::Sprite::DefaultGridBounds()) if the file doesn't contain an
279 // embedded grid bounds.
280 if (!sprite->gridBounds().isEmpty())
281 fop->setEmbeddedGridBounds();
282
283 return true;
284}
285
286bool AseFormat::onPostLoad(FileOp* fop)
287{
288 LayerGroup* group = fop->document()->sprite()->root();
289
290 // Forward Compatibility: In 1.1 we convert a file with layer groups
291 // (saved with 1.2) as top level layers
292 std::string ver = get_app_version();
293 bool flat = (ver[0] == '1' &&
294 ver[1] == '.' &&
295 ver[2] == '1');
296 if (flat && ase_has_groups(group)) {
297 if (fop->context() &&
298 fop->context()->isUIAvailable() &&
299 ui::Alert::show(
300 fmt::format(
301 // This message is not translated because is used only in the old v1.1 only
302 "Warning"
303 "<<The selected file \"{0}\" has layer groups."
304 "<<Do you want to open it with \"{1} {2}\" anyway?"
305 "<<"
306 "<<Note: Layers inside groups will be converted to top level layers."
307 "||&Yes||&No",
308 base::get_file_name(fop->filename()),
309 get_app_name(), ver)) != 1) {
310 return false;
311 }
312 ase_ungroup_all(group);
313 }
314 return true;
315}
316
317#ifdef ENABLE_SAVE
318
319// TODO move the encoder to the dio library
320bool AseFormat::onSave(FileOp* fop)
321{
322 const Sprite* sprite = fop->document()->sprite();
323 FileHandle handle(open_file_with_exception_sync_on_close(fop->filename(), "wb"));
324 FILE* f = handle.get();
325
326 // Write the header
327 dio::AsepriteHeader header;
328 ase_file_prepare_header(f, &header, sprite,
329 fop->roi().fromFrame(),
330 fop->roi().frames());
331 ase_file_write_header(f, &header);
332
333 bool require_new_palette_chunk = false;
334 for (Palette* pal : sprite->getPalettes()) {
335 if (pal->size() != 256 || pal->hasAlpha()) {
336 require_new_palette_chunk = true;
337 break;
338 }
339 }
340
341 // Write frames
342 int outputFrame = 0;
343 dio::AsepriteExternalFiles ext_files;
344 for (frame_t frame : fop->roi().selectedFrames()) {
345 // Prepare the frame header
346 dio::AsepriteFrameHeader frame_header;
347 ase_file_prepare_frame_header(f, &frame_header);
348
349 // Frame duration
350 frame_header.duration = sprite->frameDuration(frame);
351
352 if (outputFrame == 0) {
353 // Check if we need the "external files" chunk
354 ase_file_write_external_files_chunk(f, &frame_header, ext_files, sprite);
355
356 // Save color profile in first frame
357 if (fop->preserveColorProfile())
358 ase_file_write_color_profile(f, &frame_header, sprite);
359 }
360
361 // is the first frame or did the palette change?
362 Palette* pal = sprite->palette(frame);
363 int palFrom = 0, palTo = pal->size()-1;
364 if (// First frame or..
365 (frame == fop->roi().fromFrame() ||
366 // This palette is different from the previous frame palette
367 sprite->palette(frame-1)->countDiff(pal, &palFrom, &palTo) > 0)) {
368 // Write new palette chunk
369 if (require_new_palette_chunk) {
370 ase_file_write_palette_chunk(f, &frame_header,
371 pal, palFrom, palTo);
372 }
373
374 // Write color chunk for backward compatibility only
375 ase_file_write_color2_chunk(f, &frame_header, pal);
376 }
377
378 // Write extra chunks in the first frame
379 if (frame == fop->roi().fromFrame()) {
380 // Write sprite user data only if needed
381 if (!sprite->userData().isEmpty())
382 ase_file_write_user_data_chunk(f, &frame_header, &sprite->userData());
383
384 // Write tilesets
385 ase_file_write_tileset_chunks(f, fop, &frame_header, ext_files,
386 sprite->tilesets());
387
388 // Writer frame tags
389 if (sprite->tags().size() > 0) {
390 ase_file_write_tags_chunk(f, &frame_header, &sprite->tags(),
391 fop->roi().fromFrame(),
392 fop->roi().toFrame());
393 // Write user data for tags
394 for (doc::Tag* tag : sprite->tags()) {
395 ase_file_write_user_data_chunk(f, &frame_header, &(tag->userData()));
396 }
397 }
398
399 // Write layer chunks.
400 // In older versions layers were before tags, but now we put tags
401 // before layers so older version don't get confused by the new
402 // user data chunks for tags.
403 for (Layer* child : sprite->root()->layers())
404 ase_file_write_layers(f, &frame_header, child, 0);
405
406 // Write slice chunks
407 ase_file_write_slice_chunks(f, &frame_header,
408 sprite->slices(),
409 fop->roi().fromFrame(),
410 fop->roi().toFrame());
411 }
412
413 // Write cel chunks
414 ase_file_write_cels(f, &frame_header,
415 sprite, sprite->root(),
416 0, frame, fop->roi().fromFrame());
417
418 // Write the frame header
419 ase_file_write_frame_header(f, &frame_header);
420
421 // Progress
422 if (fop->roi().frames() > 1)
423 fop->setProgress(float(outputFrame+1) / float(fop->roi().frames()));
424 ++outputFrame;
425
426 if (fop->isStop())
427 break;
428 }
429
430 // Write the missing field (filesize) of the header.
431 ase_file_write_header_filesize(f, &header);
432
433 if (ferror(f)) {
434 fop->setError("Error writing file.\n");
435 return false;
436 }
437 else {
438 return true;
439 }
440}
441
442#endif // ENABLE_SAVE
443
444static void ase_file_prepare_header(FILE* f, dio::AsepriteHeader* header, const Sprite* sprite,
445 const frame_t firstFrame, const frame_t totalFrames)
446{
447 header->pos = ftell(f);
448
449 header->size = 0;
450 header->magic = ASE_FILE_MAGIC;
451 header->frames = totalFrames;
452 header->width = sprite->width();
453 header->height = sprite->height();
454 header->depth = (sprite->pixelFormat() == IMAGE_RGB ? 32:
455 sprite->pixelFormat() == IMAGE_GRAYSCALE ? 16:
456 sprite->pixelFormat() == IMAGE_INDEXED ? 8: 0);
457 header->flags = ASE_FILE_FLAG_LAYER_WITH_OPACITY;
458 header->speed = sprite->frameDuration(firstFrame);
459 header->next = 0;
460 header->frit = 0;
461 header->transparent_index = sprite->transparentColor();
462 header->ignore[0] = 0;
463 header->ignore[1] = 0;
464 header->ignore[2] = 0;
465 header->ncolors = sprite->palette(firstFrame)->size();
466 header->pixel_width = sprite->pixelRatio().w;
467 header->pixel_height = sprite->pixelRatio().h;
468 header->grid_x = sprite->gridBounds().x;
469 header->grid_y = sprite->gridBounds().y;
470 header->grid_width = sprite->gridBounds().w;
471 header->grid_height = sprite->gridBounds().h;
472}
473
474static void ase_file_write_header(FILE* f, dio::AsepriteHeader* header)
475{
476 fseek(f, header->pos, SEEK_SET);
477
478 fputl(header->size, f);
479 fputw(header->magic, f);
480 fputw(header->frames, f);
481 fputw(header->width, f);
482 fputw(header->height, f);
483 fputw(header->depth, f);
484 fputl(header->flags, f);
485 fputw(header->speed, f);
486 fputl(header->next, f);
487 fputl(header->frit, f);
488 fputc(header->transparent_index, f);
489 fputc(header->ignore[0], f);
490 fputc(header->ignore[1], f);
491 fputc(header->ignore[2], f);
492 fputw(header->ncolors, f);
493 fputc(header->pixel_width, f);
494 fputc(header->pixel_height, f);
495 fputw(header->grid_x, f);
496 fputw(header->grid_y, f);
497 fputw(header->grid_width, f);
498 fputw(header->grid_height, f);
499
500 fseek(f, header->pos+128, SEEK_SET);
501}
502
503static void ase_file_write_header_filesize(FILE* f, dio::AsepriteHeader* header)
504{
505 header->size = ftell(f)-header->pos;
506
507 fseek(f, header->pos, SEEK_SET);
508 fputl(header->size, f);
509
510 fseek(f, header->pos+header->size, SEEK_SET);
511}
512
513static void ase_file_prepare_frame_header(FILE* f, dio::AsepriteFrameHeader* frame_header)
514{
515 int pos = ftell(f);
516
517 frame_header->size = pos;
518 frame_header->magic = ASE_FILE_FRAME_MAGIC;
519 frame_header->chunks = 0;
520 frame_header->duration = 0;
521
522 fseek(f, pos+16, SEEK_SET);
523}
524
525static void ase_file_write_frame_header(FILE* f, dio::AsepriteFrameHeader* frame_header)
526{
527 int pos = frame_header->size;
528 int end = ftell(f);
529
530 frame_header->size = end-pos;
531
532 fseek(f, pos, SEEK_SET);
533
534 fputl(frame_header->size, f);
535 fputw(frame_header->magic, f);
536 fputw(frame_header->chunks < 0xFFFF ? frame_header->chunks: 0xFFFF, f);
537 fputw(frame_header->duration, f);
538 ase_file_write_padding(f, 2);
539 fputl(frame_header->chunks, f);
540
541 fseek(f, end, SEEK_SET);
542}
543
544static void ase_file_write_layers(FILE* f, dio::AsepriteFrameHeader* frame_header, const Layer* layer, int child_index)
545{
546 ase_file_write_layer_chunk(f, frame_header, layer, child_index);
547 if (!layer->userData().isEmpty())
548 ase_file_write_user_data_chunk(f, frame_header, &layer->userData());
549
550 if (layer->isGroup()) {
551 for (const Layer* child : static_cast<const LayerGroup*>(layer)->layers())
552 ase_file_write_layers(f, frame_header, child, child_index+1);
553 }
554}
555
556static layer_t ase_file_write_cels(FILE* f, dio::AsepriteFrameHeader* frame_header,
557 const Sprite* sprite, const Layer* layer,
558 layer_t layer_index,
559 const frame_t frame,
560 const frame_t firstFrame)
561{
562 if (layer->isImage()) {
563 const Cel* cel = layer->cel(frame);
564 if (cel) {
565 ase_file_write_cel_chunk(f, frame_header, cel,
566 static_cast<const LayerImage*>(layer),
567 layer_index, sprite, firstFrame);
568
569 if (layer->isReference())
570 ase_file_write_cel_extra_chunk(f, frame_header, cel);
571
572 if (!cel->link() &&
573 !cel->data()->userData().isEmpty()) {
574 ase_file_write_user_data_chunk(f, frame_header,
575 &cel->data()->userData());
576 }
577 }
578 }
579
580 if (layer != sprite->root())
581 ++layer_index;
582
583 if (layer->isGroup()) {
584 for (const Layer* child : static_cast<const LayerGroup*>(layer)->layers()) {
585 layer_index =
586 ase_file_write_cels(f, frame_header, sprite, child,
587 layer_index, frame, firstFrame);
588 }
589 }
590
591 return layer_index;
592}
593
594static void ase_file_write_padding(FILE* f, int bytes)
595{
596 for (int c=0; c<bytes; c++)
597 fputc(0, f);
598}
599
600static void ase_file_write_string(FILE* f, const std::string& string)
601{
602 fputw(string.size(), f);
603
604 for (size_t c=0; c<string.size(); ++c)
605 fputc(string[c], f);
606}
607
608static void ase_file_write_start_chunk(FILE* f, dio::AsepriteFrameHeader* frame_header, int type, dio::AsepriteChunk* chunk)
609{
610 frame_header->chunks++;
611
612 chunk->type = type;
613 chunk->start = ftell(f);
614
615 fputl(0, f);
616 fputw(0, f);
617}
618
619static void ase_file_write_close_chunk(FILE* f, dio::AsepriteChunk* chunk)
620{
621 int chunk_end = ftell(f);
622 int chunk_size = chunk_end - chunk->start;
623
624 fseek(f, chunk->start, SEEK_SET);
625 fputl(chunk_size, f);
626 fputw(chunk->type, f);
627 fseek(f, chunk_end, SEEK_SET);
628}
629
630static void ase_file_write_color2_chunk(FILE* f, dio::AsepriteFrameHeader* frame_header, const Palette* pal)
631{
632 ChunkWriter chunk(f, frame_header, ASE_FILE_CHUNK_FLI_COLOR2);
633 int c, color;
634
635 fputw(1, f); // Number of packets
636
637 // First packet
638 fputc(0, f); // skip 0 colors
639 fputc(pal->size() == 256 ? 0: pal->size(), f); // number of colors
640 for (c=0; c<pal->size(); c++) {
641 color = pal->getEntry(c);
642 fputc(rgba_getr(color), f);
643 fputc(rgba_getg(color), f);
644 fputc(rgba_getb(color), f);
645 }
646}
647
648static void ase_file_write_palette_chunk(FILE* f, dio::AsepriteFrameHeader* frame_header, const Palette* pal, int from, int to)
649{
650 ChunkWriter chunk(f, frame_header, ASE_FILE_CHUNK_PALETTE);
651
652 fputl(pal->size(), f);
653 fputl(from, f);
654 fputl(to, f);
655 ase_file_write_padding(f, 8);
656
657 for (int c=from; c<=to; ++c) {
658 color_t color = pal->getEntry(c);
659 // TODO add support to save palette entry name
660 fputw(0, f); // Entry flags (without name)
661 fputc(rgba_getr(color), f);
662 fputc(rgba_getg(color), f);
663 fputc(rgba_getb(color), f);
664 fputc(rgba_geta(color), f);
665 }
666}
667
668static void ase_file_write_layer_chunk(FILE* f, dio::AsepriteFrameHeader* frame_header, const Layer* layer, int child_level)
669{
670 ChunkWriter chunk(f, frame_header, ASE_FILE_CHUNK_LAYER);
671
672 // Flags
673 fputw(static_cast<int>(layer->flags()) &
674 static_cast<int>(doc::LayerFlags::PersistentFlagsMask), f);
675
676 // Layer type
677 int layerType = ASE_FILE_LAYER_IMAGE;
678 if (layer->isImage()) {
679 if (layer->isTilemap())
680 layerType = ASE_FILE_LAYER_TILEMAP;
681 }
682 else if (layer->isGroup()) {
683 layerType = ASE_FILE_LAYER_GROUP;
684 }
685 fputw(layerType, f);
686
687 // Layer child level
688 fputw(child_level, f);
689
690 // Default width & height, and blend mode
691 fputw(0, f);
692 fputw(0, f);
693 fputw(layer->isImage() ? (int)static_cast<const LayerImage*>(layer)->blendMode(): 0, f);
694 fputc(layer->isImage() ? (int)static_cast<const LayerImage*>(layer)->opacity(): 0, f);
695
696 // Padding
697 ase_file_write_padding(f, 3);
698
699 // Layer name
700 ase_file_write_string(f, layer->name());
701
702 // Tileset index
703 if (layer->isTilemap())
704 fputl(static_cast<const LayerTilemap*>(layer)->tilesetIndex(), f);
705}
706
707//////////////////////////////////////////////////////////////////////
708// Pixel I/O
709//////////////////////////////////////////////////////////////////////
710
711template<typename ImageTraits>
712class PixelIO {
713public:
714 void write_pixel(FILE* f, typename ImageTraits::pixel_t c);
715 void write_scanline(typename ImageTraits::address_t address, int w, uint8_t* buffer);
716};
717
718template<>
719class PixelIO<RgbTraits> {
720public:
721 void write_pixel(FILE* f, RgbTraits::pixel_t c) {
722 fputc(rgba_getr(c), f);
723 fputc(rgba_getg(c), f);
724 fputc(rgba_getb(c), f);
725 fputc(rgba_geta(c), f);
726 }
727 void write_scanline(RgbTraits::address_t address, int w, uint8_t* buffer) {
728 for (int x=0; x<w; ++x, ++address) {
729 *(buffer++) = rgba_getr(*address);
730 *(buffer++) = rgba_getg(*address);
731 *(buffer++) = rgba_getb(*address);
732 *(buffer++) = rgba_geta(*address);
733 }
734 }
735};
736
737template<>
738class PixelIO<GrayscaleTraits> {
739public:
740 void write_pixel(FILE* f, GrayscaleTraits::pixel_t c) {
741 fputc(graya_getv(c), f);
742 fputc(graya_geta(c), f);
743 }
744 void write_scanline(GrayscaleTraits::address_t address, int w, uint8_t* buffer) {
745 for (int x=0; x<w; ++x, ++address) {
746 *(buffer++) = graya_getv(*address);
747 *(buffer++) = graya_geta(*address);
748 }
749 }
750};
751
752template<>
753class PixelIO<IndexedTraits> {
754public:
755 void write_pixel(FILE* f, IndexedTraits::pixel_t c) {
756 fputc(c, f);
757 }
758 void write_scanline(IndexedTraits::address_t address, int w, uint8_t* buffer) {
759 memcpy(buffer, address, w);
760 }
761};
762
763template<>
764class PixelIO<TilemapTraits> {
765public:
766 void write_pixel(FILE* f, TilemapTraits::pixel_t c) {
767 fputl(c, f);
768 }
769 void write_scanline(TilemapTraits::address_t address, int w, uint8_t* buffer) {
770 for (int x=0; x<w; ++x, ++address) {
771 *(buffer++) = ((*address) & 0x000000ffl);
772 *(buffer++) = ((*address) & 0x0000ff00l) >> 8;
773 *(buffer++) = ((*address) & 0x00ff0000l) >> 16;
774 *(buffer++) = ((*address) & 0xff000000l) >> 24;
775 }
776 }
777};
778
779//////////////////////////////////////////////////////////////////////
780// Raw Image
781//////////////////////////////////////////////////////////////////////
782
783template<typename ImageTraits>
784static void write_raw_image(FILE* f, const Image* image)
785{
786 PixelIO<ImageTraits> pixel_io;
787 int x, y;
788
789 for (y=0; y<image->height(); y++)
790 for (x=0; x<image->width(); x++)
791 pixel_io.write_pixel(f, get_pixel_fast<ImageTraits>(image, x, y));
792}
793
794//////////////////////////////////////////////////////////////////////
795// Compressed Image
796//////////////////////////////////////////////////////////////////////
797
798template<typename ImageTraits>
799static void write_compressed_image_templ(FILE* f, ScanlinesGen* gen)
800{
801 PixelIO<ImageTraits> pixel_io;
802 z_stream zstream;
803 int y, err;
804
805 zstream.zalloc = (alloc_func)0;
806 zstream.zfree = (free_func)0;
807 zstream.opaque = (voidpf)0;
808 err = deflateInit(&zstream, Z_DEFAULT_COMPRESSION);
809 if (err != Z_OK)
810 throw base::Exception("ZLib error %d in deflateInit().", err);
811
812 std::vector<uint8_t> scanline(gen->getScanlineSize());
813 std::vector<uint8_t> compressed(4096);
814
815 const gfx::Size imgSize = gen->getImageSize();
816 for (y=0; y<imgSize.h; ++y) {
817 typename ImageTraits::address_t address =
818 (typename ImageTraits::address_t)gen->getScanlineAddress(y);
819
820 pixel_io.write_scanline(address, imgSize.w, &scanline[0]);
821
822 zstream.next_in = (Bytef*)&scanline[0];
823 zstream.avail_in = scanline.size();
824 int flush = (y == imgSize.h-1 ? Z_FINISH: Z_NO_FLUSH);
825
826 do {
827 zstream.next_out = (Bytef*)&compressed[0];
828 zstream.avail_out = compressed.size();
829
830 // Compress
831 err = deflate(&zstream, flush);
832 if (err != Z_OK && err != Z_STREAM_END && err != Z_BUF_ERROR)
833 throw base::Exception("ZLib error %d in deflate().", err);
834
835 int output_bytes = compressed.size() - zstream.avail_out;
836 if (output_bytes > 0) {
837 if ((fwrite(&compressed[0], 1, output_bytes, f) != (size_t)output_bytes)
838 || ferror(f))
839 throw base::Exception("Error writing compressed image pixels.\n");
840 }
841 } while (zstream.avail_out == 0);
842 }
843
844 err = deflateEnd(&zstream);
845 if (err != Z_OK)
846 throw base::Exception("ZLib error %d in deflateEnd().", err);
847}
848
849static void write_compressed_image(FILE* f, ScanlinesGen* gen, PixelFormat pixelFormat)
850{
851 switch (pixelFormat) {
852 case IMAGE_RGB:
853 write_compressed_image_templ<RgbTraits>(f, gen);
854 break;
855
856 case IMAGE_GRAYSCALE:
857 write_compressed_image_templ<GrayscaleTraits>(f, gen);
858 break;
859
860 case IMAGE_INDEXED:
861 write_compressed_image_templ<IndexedTraits>(f, gen);
862 break;
863
864 case IMAGE_TILEMAP:
865 write_compressed_image_templ<TilemapTraits>(f, gen);
866 break;
867 }
868}
869
870//////////////////////////////////////////////////////////////////////
871// Cel Chunk
872//////////////////////////////////////////////////////////////////////
873
874static void ase_file_write_cel_chunk(FILE* f, dio::AsepriteFrameHeader* frame_header,
875 const Cel* cel,
876 const LayerImage* layer,
877 const layer_t layer_index,
878 const Sprite* sprite,
879 const frame_t firstFrame)
880{
881 ChunkWriter chunk(f, frame_header, ASE_FILE_CHUNK_CEL);
882
883 const Cel* link = cel->link();
884
885 // In case the original link is outside the ROI, we've to find the
886 // first linked cel that is inside the ROI.
887 if (link && link->frame() < firstFrame) {
888 link = nullptr;
889 for (frame_t i=firstFrame; i<=cel->frame(); ++i) {
890 link = layer->cel(i);
891 if (link && link->image()->id() == cel->image()->id())
892 break;
893 }
894 if (link == cel)
895 link = nullptr;
896 }
897
898 int cel_type = (link ? ASE_FILE_LINK_CEL:
899 cel->layer()->isTilemap() ? ASE_FILE_COMPRESSED_TILEMAP:
900 ASE_FILE_COMPRESSED_CEL);
901
902 fputw(layer_index, f);
903 fputw(cel->x(), f);
904 fputw(cel->y(), f);
905 fputc(cel->opacity(), f);
906 fputw(cel_type, f);
907 ase_file_write_padding(f, 7);
908
909 switch (cel_type) {
910
911 case ASE_FILE_RAW_CEL: {
912 const Image* image = cel->image();
913
914 if (image) {
915 // Width and height
916 fputw(image->width(), f);
917 fputw(image->height(), f);
918
919 // Pixel data
920 switch (image->pixelFormat()) {
921
922 case IMAGE_RGB:
923 write_raw_image<RgbTraits>(f, image);
924 break;
925
926 case IMAGE_GRAYSCALE:
927 write_raw_image<GrayscaleTraits>(f, image);
928 break;
929
930 case IMAGE_INDEXED:
931 write_raw_image<IndexedTraits>(f, image);
932 break;
933
934 }
935 }
936 else {
937 // Width and height
938 fputw(0, f);
939 fputw(0, f);
940 }
941 break;
942 }
943
944 case ASE_FILE_LINK_CEL:
945 // Linked cel to another frame
946 fputw(link->frame()-firstFrame, f);
947 break;
948
949 case ASE_FILE_COMPRESSED_CEL: {
950 const Image* image = cel->image();
951 ASSERT(image);
952 if (image) {
953 // Width and height
954 fputw(image->width(), f);
955 fputw(image->height(), f);
956
957 ImageScanlines scan(image);
958 write_compressed_image(f, &scan, image->pixelFormat());
959 }
960 else {
961 // Width and height
962 fputw(0, f);
963 fputw(0, f);
964 }
965 break;
966 }
967
968 case ASE_FILE_COMPRESSED_TILEMAP: {
969 const Image* image = cel->image();
970 ASSERT(image);
971 ASSERT(image->pixelFormat() == IMAGE_TILEMAP);
972
973 fputw(image->width(), f);
974 fputw(image->height(), f);
975 fputw(32, f); // TODO use different bpp when possible
976 fputl(tile_i_mask, f);
977 fputl(tile_f_flipx, f);
978 fputl(tile_f_flipy, f);
979 fputl(tile_f_90cw, f);
980 ase_file_write_padding(f, 10);
981
982 ImageScanlines scan(image);
983 write_compressed_image(f, &scan, IMAGE_TILEMAP);
984 }
985 }
986}
987
988static void ase_file_write_cel_extra_chunk(FILE* f,
989 dio::AsepriteFrameHeader* frame_header,
990 const Cel* cel)
991{
992 ChunkWriter chunk(f, frame_header, ASE_FILE_CHUNK_CEL_EXTRA);
993
994 ASSERT(cel->layer()->isReference());
995
996 gfx::RectF bounds = cel->boundsF();
997
998 fputl(ASE_CEL_EXTRA_FLAG_PRECISE_BOUNDS, f);
999 fputl(fixmath::ftofix(bounds.x), f);
1000 fputl(fixmath::ftofix(bounds.y), f);
1001 fputl(fixmath::ftofix(bounds.w), f);
1002 fputl(fixmath::ftofix(bounds.h), f);
1003 ase_file_write_padding(f, 16);
1004}
1005
1006static void ase_file_write_color_profile(FILE* f,
1007 dio::AsepriteFrameHeader* frame_header,
1008 const doc::Sprite* sprite)
1009{
1010 const gfx::ColorSpaceRef& cs = sprite->colorSpace();
1011 if (!cs) // No color
1012 return;
1013
1014 int type = ASE_FILE_NO_COLOR_PROFILE;
1015 switch (cs->type()) {
1016
1017 case gfx::ColorSpace::None:
1018 return; // Without color profile, don't write this chunk.
1019
1020 case gfx::ColorSpace::sRGB:
1021 type = ASE_FILE_SRGB_COLOR_PROFILE;
1022 break;
1023 case gfx::ColorSpace::ICC:
1024 type = ASE_FILE_ICC_COLOR_PROFILE;
1025 break;
1026 default:
1027 ASSERT(false); // Unknown color profile
1028 return;
1029 }
1030
1031 ChunkWriter chunk(f, frame_header, ASE_FILE_CHUNK_COLOR_PROFILE);
1032 fputw(type, f);
1033 fputw(cs->hasGamma() ? ASE_COLOR_PROFILE_FLAG_GAMMA: 0, f);
1034
1035 fixmath::fixed gamma = 0;
1036 if (cs->hasGamma())
1037 gamma = fixmath::ftofix(cs->gamma());
1038 fputl(gamma, f);
1039 ase_file_write_padding(f, 8);
1040
1041 if (cs->type() == gfx::ColorSpace::ICC) {
1042 const size_t size = cs->iccSize();
1043 const void* data = cs->iccData();
1044 fputl(size, f);
1045 if (size && data)
1046 fwrite(data, 1, size, f);
1047 }
1048}
1049
1050#if 0
1051static void ase_file_write_mask_chunk(FILE* f, dio::AsepriteFrameHeader* frame_header, Mask* mask)
1052{
1053 ChunkWriter chunk(f, frame_header, ASE_FILE_CHUNK_MASK);
1054
1055 int c, u, v, byte;
1056 const gfx::Rect& bounds(mask->bounds());
1057
1058 fputw(bounds.x, f);
1059 fputw(bounds.y, f);
1060 fputw(bounds.w, f);
1061 fputw(bounds.h, f);
1062 ase_file_write_padding(f, 8);
1063
1064 // Name
1065 ase_file_write_string(f, mask->name());
1066
1067 // Bitmap
1068 for (v=0; v<bounds.h; v++)
1069 for (u=0; u<(bounds.w+7)/8; u++) {
1070 byte = 0;
1071 for (c=0; c<8; c++)
1072 if (get_pixel(mask->bitmap(), u*8+c, v))
1073 byte |= (1<<(7-c));
1074 fputc(byte, f);
1075 }
1076}
1077#endif
1078
1079static void ase_file_write_tags_chunk(FILE* f,
1080 dio::AsepriteFrameHeader* frame_header,
1081 const Tags* tags,
1082 const frame_t fromFrame,
1083 const frame_t toFrame)
1084{
1085 ChunkWriter chunk(f, frame_header, ASE_FILE_CHUNK_TAGS);
1086
1087 int ntags = 0;
1088 for (const Tag* tag : *tags) {
1089 // Skip tags that are outside of the given ROI
1090 if (tag->fromFrame() > toFrame ||
1091 tag->toFrame() < fromFrame)
1092 continue;
1093 ++ntags;
1094 }
1095
1096 fputw(ntags, f);
1097 fputl(0, f); // 8 reserved bytes
1098 fputl(0, f);
1099
1100 for (const Tag* tag : *tags) {
1101 if (tag->fromFrame() > toFrame ||
1102 tag->toFrame() < fromFrame)
1103 continue;
1104
1105 frame_t from = std::clamp(tag->fromFrame()-fromFrame, 0, toFrame-fromFrame);
1106 frame_t to = std::clamp(tag->toFrame()-fromFrame, from, toFrame-fromFrame);
1107
1108 fputw(from, f);
1109 fputw(to, f);
1110 fputc((int)tag->aniDir(), f);
1111
1112 fputl(0, f); // 8 reserved bytes
1113 fputl(0, f);
1114
1115 fputc(doc::rgba_getr(tag->color()), f);
1116 fputc(doc::rgba_getg(tag->color()), f);
1117 fputc(doc::rgba_getb(tag->color()), f);
1118 fputc(0, f);
1119
1120 ase_file_write_string(f, tag->name());
1121 }
1122}
1123
1124static void ase_file_write_user_data_chunk(FILE* f, dio::AsepriteFrameHeader* frame_header, const UserData* userData)
1125{
1126 ChunkWriter chunk(f, frame_header, ASE_FILE_CHUNK_USER_DATA);
1127
1128 int flags = 0;
1129 if (!userData->text().empty())
1130 flags |= ASE_USER_DATA_FLAG_HAS_TEXT;
1131 if (doc::rgba_geta(userData->color()))
1132 flags |= ASE_USER_DATA_FLAG_HAS_COLOR;
1133 fputl(flags, f);
1134
1135 if (flags & ASE_USER_DATA_FLAG_HAS_TEXT)
1136 ase_file_write_string(f, userData->text());
1137
1138 if (flags & ASE_USER_DATA_FLAG_HAS_COLOR) {
1139 fputc(doc::rgba_getr(userData->color()), f);
1140 fputc(doc::rgba_getg(userData->color()), f);
1141 fputc(doc::rgba_getb(userData->color()), f);
1142 fputc(doc::rgba_geta(userData->color()), f);
1143 }
1144}
1145
1146static void ase_file_write_slice_chunks(FILE* f, dio::AsepriteFrameHeader* frame_header,
1147 const Slices& slices,
1148 const frame_t fromFrame,
1149 const frame_t toFrame)
1150{
1151 for (Slice* slice : slices) {
1152 // Skip slices that are outside of the given ROI
1153 if (slice->range(fromFrame, toFrame).empty())
1154 continue;
1155
1156 ase_file_write_slice_chunk(f, frame_header, slice,
1157 fromFrame, toFrame);
1158
1159 if (!slice->userData().isEmpty())
1160 ase_file_write_user_data_chunk(f, frame_header, &slice->userData());
1161 }
1162}
1163
1164static void ase_file_write_slice_chunk(FILE* f, dio::AsepriteFrameHeader* frame_header,
1165 Slice* slice,
1166 const frame_t fromFrame,
1167 const frame_t toFrame)
1168{
1169 ChunkWriter chunk(f, frame_header, ASE_FILE_CHUNK_SLICE);
1170
1171 auto range = slice->range(fromFrame, toFrame);
1172 ASSERT(!range.empty());
1173
1174 int flags = 0;
1175 for (auto key : range) {
1176 if (key) {
1177 if (key->hasCenter()) flags |= ASE_SLICE_FLAG_HAS_CENTER_BOUNDS;
1178 if (key->hasPivot()) flags |= ASE_SLICE_FLAG_HAS_PIVOT_POINT;
1179 }
1180 }
1181
1182 fputl(range.countKeys(), f); // number of keys
1183 fputl(flags, f); // flags
1184 fputl(0, f); // 4 bytes reserved
1185 ase_file_write_string(f, slice->name()); // slice name
1186
1187 frame_t frame = fromFrame;
1188 const SliceKey* oldKey = nullptr;
1189 for (auto key : range) {
1190 if (frame == fromFrame || key != oldKey) {
1191 fputl(frame, f);
1192 fputl((int32_t)(key ? key->bounds().x: 0), f);
1193 fputl((int32_t)(key ? key->bounds().y: 0), f);
1194 fputl(key ? key->bounds().w: 0, f);
1195 fputl(key ? key->bounds().h: 0, f);
1196
1197 if (flags & ASE_SLICE_FLAG_HAS_CENTER_BOUNDS) {
1198 if (key && key->hasCenter()) {
1199 fputl((int32_t)key->center().x, f);
1200 fputl((int32_t)key->center().y, f);
1201 fputl(key->center().w, f);
1202 fputl(key->center().h, f);
1203 }
1204 else {
1205 fputl(0, f);
1206 fputl(0, f);
1207 fputl(0, f);
1208 fputl(0, f);
1209 }
1210 }
1211
1212 if (flags & ASE_SLICE_FLAG_HAS_PIVOT_POINT) {
1213 if (key && key->hasPivot()) {
1214 fputl((int32_t)key->pivot().x, f);
1215 fputl((int32_t)key->pivot().y, f);
1216 }
1217 else {
1218 fputl(0, f);
1219 fputl(0, f);
1220 }
1221 }
1222
1223 oldKey = key;
1224 }
1225 ++frame;
1226 }
1227}
1228
1229static void ase_file_write_external_files_chunk(
1230 FILE* f,
1231 dio::AsepriteFrameHeader* frame_header,
1232 dio::AsepriteExternalFiles& ext_files,
1233 const Sprite* sprite)
1234{
1235 for (const Tileset* tileset : *sprite->tilesets()) {
1236 if (!tileset->externalFilename().empty()) {
1237 auto id = ++ext_files.lastid;
1238 auto fn = tileset->externalFilename();
1239 ext_files.to_fn[id] = fn;
1240 ext_files.to_id[fn] = id;
1241 }
1242 }
1243
1244 // No external files to write
1245 if (ext_files.lastid == 0)
1246 return;
1247
1248 fputl(ext_files.to_fn.size(), f); // Number of entries
1249 ase_file_write_padding(f, 8);
1250 for (auto item : ext_files.to_fn) {
1251 fputl(item.first, f); // ID
1252 ase_file_write_padding(f, 8);
1253 ase_file_write_string(f, item.second); // Filename
1254 }
1255}
1256
1257static void ase_file_write_tileset_chunks(FILE* f, FileOp* fop,
1258 dio::AsepriteFrameHeader* frame_header,
1259 const dio::AsepriteExternalFiles& ext_files,
1260 const Tilesets* tilesets)
1261{
1262 tileset_index si = 0;
1263 for (const Tileset* tileset : *tilesets) {
1264 ase_file_write_tileset_chunk(f, fop, frame_header, ext_files,
1265 tileset, si);
1266 ++si;
1267 }
1268}
1269
1270static void ase_file_write_tileset_chunk(FILE* f, FileOp* fop,
1271 dio::AsepriteFrameHeader* frame_header,
1272 const dio::AsepriteExternalFiles& ext_files,
1273 const Tileset* tileset,
1274 const tileset_index si)
1275{
1276 ChunkWriter chunk(f, frame_header, ASE_FILE_CHUNK_TILESET);
1277
1278 // We always save with the tile zero as the empty tile now
1279 int flags = ASE_TILESET_FLAG_ZERO_IS_NOTILE;
1280 if (!tileset->externalFilename().empty())
1281 flags |= ASE_TILESET_FLAG_EXTERNAL_FILE;
1282 else
1283 flags |= ASE_TILESET_FLAG_EMBEDDED;
1284
1285 fputl(si, f); // Tileset ID
1286 fputl(flags, f); // Tileset Flags
1287 fputl(tileset->size(), f);
1288 fputw(tileset->grid().tileSize().w, f);
1289 fputw(tileset->grid().tileSize().h, f);
1290 fputw(short(tileset->baseIndex()), f);
1291 ase_file_write_padding(f, 14);
1292 ase_file_write_string(f, tileset->name()); // tileset name
1293
1294 // Flag 1 = external tileset
1295 if (flags & ASE_TILESET_FLAG_EXTERNAL_FILE) {
1296 auto it = ext_files.to_id.find(tileset->externalFilename());
1297 if (it != ext_files.to_id.end()) {
1298 auto file_id = it->second;
1299 fputl(file_id, f);
1300 fputl(tileset->externalTileset(), f);
1301 }
1302 else {
1303 ASSERT(false); // Impossible state (corrupted memory or we
1304 // forgot to add the tileset external file to
1305 // "ext_files")
1306
1307 fputl(0, f);
1308 fputl(0, f);
1309 fop->setError("Error writing tileset external reference.\n");
1310 }
1311 }
1312
1313 // Flag 2 = tileset
1314 if (flags & ASE_TILESET_FLAG_EMBEDDED) {
1315 size_t beg = ftell(f);
1316 fputl(0, f); // Field for compressed data length (completed later)
1317 TilesetScanlines gen(tileset);
1318 write_compressed_image(f, &gen, tileset->sprite()->pixelFormat());
1319
1320 size_t end = ftell(f);
1321 fseek(f, beg, SEEK_SET);
1322 fputl(end-beg-4, f); // Save the compressed data length
1323 fseek(f, end, SEEK_SET);
1324 }
1325}
1326
1327static bool ase_has_groups(LayerGroup* group)
1328{
1329 for (Layer* child : group->layers()) {
1330 if (child->isGroup())
1331 return true;
1332 }
1333 return false;
1334}
1335
1336static void ase_ungroup_all(LayerGroup* group)
1337{
1338 LayerGroup* root = group->sprite()->root();
1339 LayerList list = group->layers();
1340
1341 for (Layer* child : list) {
1342 if (child->isGroup()) {
1343 ase_ungroup_all(static_cast<LayerGroup*>(child));
1344 group->removeLayer(child);
1345 }
1346 else if (group != root) {
1347 // Create a new name adding all group layer names
1348 {
1349 std::string name;
1350 for (Layer* layer=child; layer!=root; layer=layer->parent()) {
1351 if (!name.empty())
1352 name.insert(0, "-");
1353 name.insert(0, layer->name());
1354 }
1355 child->setName(name);
1356 }
1357
1358 group->removeLayer(child);
1359 root->addLayer(child);
1360 }
1361 }
1362
1363 if (group != root) {
1364 ASSERT(group->layersCount() == 0);
1365 delete group;
1366 }
1367}
1368
1369} // namespace app
1370