From 65045c0e2c972d35c4049d3c23ed76c07769c19f Mon Sep 17 00:00:00 2001 From: David Capello Date: Tue, 7 Jul 2015 12:58:05 -0300 Subject: [PATCH] Fix interoperability problem with layer opacity between v1.0 and v1.1 Saving a file with v1.1, loading it with v1.0, and then loading it back with v1.1 was generating a layer with flag 32 enabled ("layer has opacity bit") and opacity = 0 (the opacity was overwritten by v1.0, but the flag was persisted). Now the "layers have opacity" bit is saved in the file header. This flag can be used because v1.0 saves it with value = 0. --- docs/files/ase.txt | 12 +++++-- src/app/file/ase_format.cpp | 66 ++++++++++++++++++++----------------- 2 files changed, 44 insertions(+), 34 deletions(-) diff --git a/docs/files/ase.txt b/docs/files/ase.txt index 9447b4209..2c925fc9d 100644 --- a/docs/files/ase.txt +++ b/docs/files/ase.txt @@ -43,7 +43,7 @@ images with zlib. Color palettes are in FLI color chunks (it could be type=11 or type=4). For color depths more than 8bpp, palettes are optional. See fli.txt for details. -To read the sprite, just do this: +To read the sprite: * Read the ASE header (section 3) * For each frame do (how many frames? the ASE header has that information): @@ -70,7 +70,8 @@ WORD Color depth (bits per pixel) 32 bpp = RGBA 16 bpp = Grayscale 8 bpp = Indexed -DWORD Flags (must be 0) +DWORD Flags: + 1 = Layer opacity has valid value WORD Speed (milliseconds between frame, like in FLC files) DEPRECATED: You should use the frame duration field from each frame header @@ -111,6 +112,10 @@ Then each chunk format is: Old palette chunk (0x0004) ---------------------------------------- +Ignore this chunk if you find the new palette chunk (0x2019) Aseprite +v1.1 saves both chunks 0x0004 and 0x2019 just for backward +compatibility. + WORD Number of packets + For each packet BYTE Number of palette entries to skip from the last packet (start from 0) @@ -124,6 +129,8 @@ Old palette chunk (0x0004) Old palette chunk (0x0011) ---------------------------------------- +Ignore this chunk if you find the new palette chunk (0x2019) + WORD Number of packets + For each packet BYTE Number of palette entries to skip from the last packet (start from 0) @@ -146,7 +153,6 @@ Layer Chunk (0x2004) 4 = Lock movement 8 = Background 16 = Prefer linked cels - 32 = Has a valid opacity value WORD Layer type (0=normal (image) layer, 1=layer set) WORD Layer child level (see NOTE.1) WORD Default layer width in pixels (ignored) diff --git a/src/app/file/ase_format.cpp b/src/app/file/ase_format.cpp index 7c17df136..cf7fbd10c 100644 --- a/src/app/file/ase_format.cpp +++ b/src/app/file/ase_format.cpp @@ -21,28 +21,31 @@ #include -#define ASE_FILE_MAGIC 0xA5E0 -#define ASE_FILE_FRAME_MAGIC 0xF1FA +#define ASE_FILE_MAGIC 0xA5E0 +#define ASE_FILE_FRAME_MAGIC 0xF1FA -#define ASE_FILE_CHUNK_FLI_COLOR2 4 -#define ASE_FILE_CHUNK_FLI_COLOR 11 -#define ASE_FILE_CHUNK_LAYER 0x2004 -#define ASE_FILE_CHUNK_CEL 0x2005 -#define ASE_FILE_CHUNK_MASK 0x2016 -#define ASE_FILE_CHUNK_PATH 0x2017 -#define ASE_FILE_CHUNK_FRAME_TAGS 0x2018 -#define ASE_FILE_CHUNK_PALETTE 0x2019 +#define ASE_FILE_FLAG_LAYER_WITH_OPACITY 1 -#define ASE_FILE_RAW_CEL 0 -#define ASE_FILE_LINK_CEL 1 -#define ASE_FILE_COMPRESSED_CEL 2 +#define ASE_FILE_CHUNK_FLI_COLOR2 4 +#define ASE_FILE_CHUNK_FLI_COLOR 11 +#define ASE_FILE_CHUNK_LAYER 0x2004 +#define ASE_FILE_CHUNK_CEL 0x2005 +#define ASE_FILE_CHUNK_MASK 0x2016 +#define ASE_FILE_CHUNK_PATH 0x2017 +#define ASE_FILE_CHUNK_FRAME_TAGS 0x2018 +#define ASE_FILE_CHUNK_PALETTE 0x2019 + +#define ASE_FILE_RAW_CEL 0 +#define ASE_FILE_LINK_CEL 1 +#define ASE_FILE_COMPRESSED_CEL 2 #define ASE_LAYER_FLAG_VISIBLE 1 #define ASE_LAYER_FLAG_EDITABLE 2 #define ASE_LAYER_FLAG_LOCK_MOVEMENT 4 #define ASE_LAYER_FLAG_BACKGROUND 8 #define ASE_LAYER_FLAG_PREFER_LINKED_CELS 16 -#define ASE_LAYER_FLAG_HAS_OPACITY 32 + +#define ASE_PALETTE_FLAG_HAS_NAME 1 namespace app { @@ -103,7 +106,7 @@ static Palette* ase_file_read_color2_chunk(FILE* f, Palette* prevPal, frame_t fr static Palette* ase_file_read_palette_chunk(FILE* f, Palette* prevPal, frame_t frame); static void ase_file_write_color2_chunk(FILE* f, ASE_FrameHeader* frame_header, Palette* pal); static void ase_file_write_palette_chunk(FILE* f, ASE_FrameHeader* frame_header, Palette* pal, int from, int to); -static Layer* ase_file_read_layer_chunk(FILE* f, Sprite* sprite, Layer** previous_layer, int* current_level); +static Layer* ase_file_read_layer_chunk(FILE* f, ASE_Header* header, Sprite* sprite, Layer** previous_layer, int* current_level); static void ase_file_write_layer_chunk(FILE* f, ASE_FrameHeader* frame_header, Layer* layer); static Cel* ase_file_read_cel_chunk(FILE* f, Sprite* sprite, frame_t frame, PixelFormat pixelFormat, FileOp* fop, ASE_Header* header, size_t chunk_end); static void ase_file_write_cel_chunk(FILE* f, ASE_FrameHeader* frame_header, Cel* cel, LayerImage* layer, Sprite* sprite); @@ -188,13 +191,13 @@ bool AseFormat::onLoad(FileOp* fop) Layer* last_layer = sprite->folder(); int current_level = -1; - /* read frame by frame to end-of-file */ + // Read frame by frame to end-of-file for (frame_t frame(0); frametotalFrames(); ++frame) { - /* start frame position */ + // Start frame position int frame_pos = ftell(f); fop_progress(fop, (float)frame_pos / (float)header.size); - /* read frame header */ + // Read frame header ASE_FrameHeader frame_header; ase_file_read_frame_header(f, &frame_header); @@ -243,7 +246,7 @@ bool AseFormat::onLoad(FileOp* fop) case ASE_FILE_CHUNK_LAYER: { /* fop_error(fop, "Layer chunk\n"); */ - ase_file_read_layer_chunk(f, sprite, + ase_file_read_layer_chunk(f, &header, sprite, &last_layer, ¤t_level); break; @@ -285,15 +288,15 @@ bool AseFormat::onLoad(FileOp* fop) break; } - /* skip chunk size */ + // Skip chunk size fseek(f, chunk_pos+chunk_size, SEEK_SET); } } - /* skip frame size */ + // Skip frame size fseek(f, frame_pos+frame_header.size, SEEK_SET); - /* just one frame? */ + // Just one frame? if (fop->oneframe) break; @@ -439,7 +442,7 @@ static void ase_file_prepare_header(FILE* f, ASE_Header* header, const Sprite* s header->depth = (sprite->pixelFormat() == IMAGE_RGB ? 32: sprite->pixelFormat() == IMAGE_GRAYSCALE ? 16: sprite->pixelFormat() == IMAGE_INDEXED ? 8: 0); - header->flags = 0; + header->flags = ASE_FILE_FLAG_LAYER_WITH_OPACITY; header->speed = sprite->frameDuration(frame_t(0)); header->next = 0; header->frit = 0; @@ -688,7 +691,7 @@ static Palette* ase_file_read_palette_chunk(FILE* f, Palette* prevPal, frame_t f pal->setEntry(c, rgba(r, g, b, a)); // Skip name - if (flags & 1) { + if (flags & ASE_PALETTE_FLAG_HAS_NAME) { std::string name = ase_file_read_string(f); // Ignore color entry name } @@ -734,7 +737,7 @@ static void ase_file_write_palette_chunk(FILE* f, ASE_FrameHeader* frame_header, } } -static Layer* ase_file_read_layer_chunk(FILE* f, Sprite* sprite, Layer** previous_layer, int* current_level) +static Layer* ase_file_read_layer_chunk(FILE* f, ASE_Header* header, Sprite* sprite, Layer** previous_layer, int* current_level) { std::string name; Layer* layer = NULL; @@ -757,10 +760,12 @@ static Layer* ase_file_read_layer_chunk(FILE* f, Sprite* sprite, Layer** previou // Image layer if (layer_type == 0) { layer = new LayerImage(sprite); - static_cast(layer)->setBlendMode((BlendMode)blendmode); - if (flags & ASE_LAYER_FLAG_HAS_OPACITY) { - flags ^= ASE_LAYER_FLAG_HAS_OPACITY; - static_cast(layer)->setOpacity(opacity); + + // Only transparent layers can have blend mode and opacity + if (!(flags & ASE_LAYER_FLAG_BACKGROUND)) { + static_cast(layer)->setBlendMode((BlendMode)blendmode); + if (header->flags & ASE_FILE_FLAG_LAYER_WITH_OPACITY) + static_cast(layer)->setOpacity(opacity); } } // Layer set @@ -795,8 +800,7 @@ static void ase_file_write_layer_chunk(FILE* f, ASE_FrameHeader* frame_header, L ChunkWriter chunk(f, frame_header, ASE_FILE_CHUNK_LAYER); // Flags - fputw(static_cast(layer->flags()) | - (layer->isImage() ? ASE_LAYER_FLAG_HAS_OPACITY: 0), f); + fputw(static_cast(layer->flags()), f); // Layer type fputw(layer->isImage() ? 0: (layer->isFolder() ? 1: -1), f);