Commit 4319b9696 for imagemagick.org
commit 4319b969621774bfda4510692b8b04a267ee5091
Author: Dirk Lemstra <dirk@lemstra.org>
Date: Sat Aug 8 12:11:41 2026 +0200
Added initial support for the aseprite format (#8783)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
diff --git a/MagickCore/static.h b/MagickCore/static.h
index 7e386cf99..cf94243e8 100644
--- a/MagickCore/static.h
+++ b/MagickCore/static.h
@@ -35,6 +35,7 @@ extern MagickExport void
extern ModuleExport size_t
RegisterAAIImage(void),
RegisterARTImage(void),
+ RegisterASEImage(void),
RegisterASHLARImage(void),
RegisterAVSImage(void),
RegisterBAYERImage(void),
@@ -177,6 +178,7 @@ extern ModuleExport size_t
extern ModuleExport void
UnregisterAAIImage(void),
UnregisterARTImage(void),
+ UnregisterASEImage(void),
UnregisterASHLARImage(void),
UnregisterAVSImage(void),
UnregisterBAYERImage(void),
diff --git a/coders/Makefile.am b/coders/Makefile.am
index d239633cc..0a994c2d9 100644
--- a/coders/Makefile.am
+++ b/coders/Makefile.am
@@ -116,6 +116,8 @@ MAGICKCORE_CODER_SRCS = \
coders/aai.h \
coders/art.c \
coders/art.h \
+ coders/ase.c \
+ coders/ase.h \
coders/ashlar.c \
coders/ashlar.h \
coders/avs.c \
@@ -405,6 +407,7 @@ MAGICKCORE_CODER_SRCS = \
CODERS_NOINST_HDRS = \
coders/aai.h \
coders/art.h \
+ coders/ase.h \
coders/ashlar.h \
coders/avs.h \
coders/bayer.h \
@@ -556,6 +559,7 @@ if WITH_MODULES
coders_LTLIBRARIES = \
coders/aai.la \
coders/art.la \
+ coders/ase.la \
coders/ashlar.la \
coders/avs.la \
coders/bayer.la \
@@ -709,6 +713,12 @@ coders_art_la_CPPFLAGS = $(MAGICK_CODER_CPPFLAGS)
coders_art_la_LDFLAGS = $(MODULECOMMONFLAGS)
coders_art_la_LIBADD = $(MAGICKCORE_LIBS)
+# ASE coder module
+coders_ase_la_SOURCES = coders/ase.c
+coders_ase_la_CPPFLAGS = $(MAGICK_CODER_CPPFLAGS)
+coders_ase_la_LDFLAGS = $(MODULECOMMONFLAGS)
+coders_ase_la_LIBADD = $(MAGICKCORE_LIBS) $(ZLIB_LIBS)
+
# ASHLAR coder module
coders_ashlar_la_SOURCES = coders/ashlar.c
coders_ashlar_la_CPPFLAGS = $(MAGICK_CODER_CPPFLAGS)
diff --git a/coders/ase.c b/coders/ase.c
new file mode 100644
index 000000000..42c27026e
--- /dev/null
+++ b/coders/ase.c
@@ -0,0 +1,1434 @@
+/*
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+% %
+% %
+% %
+% AAA SSSSS EEEEE PPPP RRRR IIIII TTTTT EEEEE %
+% A A S E P P R R I T E %
+% AAAAA SSSSS EEEEE PPPP RRRR I T EEEEE %
+% A A S E P R R I T E %
+% A A SSSSS EEEEE P R R IIIII T EEEEE %
+% %
+% %
+% Read/Write Aseprite Sprite Format %
+% %
+% Software Design %
+% Dirk Lemstra & Copilot %
+% August 2026 %
+% %
+% %
+% Copyright @ 1999 ImageMagick Studio LLC, a non-profit organization %
+% dedicated to making software imaging solutions freely available. %
+% %
+% You may not use this file except in compliance with the License. You may %
+% obtain a copy of the License at %
+% %
+% https://imagemagick.org/license/ %
+% %
+% Unless required by applicable law or agreed to in writing, software %
+% distributed under the License is distributed on an "AS IS" BASIS, %
+% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %
+% See the License for the specific language governing permissions and %
+% limitations under the License. %
+% %
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+%
+*/
+
+/*
+ Include declarations.
+*/
+#include "MagickCore/studio.h"
+#include "MagickCore/attribute.h"
+#include "MagickCore/blob.h"
+#include "MagickCore/blob-private.h"
+#include "MagickCore/cache.h"
+#include "MagickCore/colormap.h"
+#include "MagickCore/colorspace.h"
+#include "MagickCore/colorspace-private.h"
+#include "MagickCore/exception.h"
+#include "MagickCore/exception-private.h"
+#include "MagickCore/image.h"
+#include "MagickCore/image-private.h"
+#include "MagickCore/list.h"
+#include "MagickCore/magick.h"
+#include "MagickCore/memory_.h"
+#include "MagickCore/monitor.h"
+#include "MagickCore/monitor-private.h"
+#include "MagickCore/pixel-accessor.h"
+#include "MagickCore/quantum-private.h"
+#include "MagickCore/resource_.h"
+#include "MagickCore/static.h"
+#include "MagickCore/string_.h"
+#include "MagickCore/module.h"
+#if defined(MAGICKCORE_ZLIB_DELEGATE)
+#include <zlib.h>
+#endif
+
+/*
+ Define declarations.
+*/
+#define ASE_MAGIC 0xA5E0
+#define ASE_FRAME_MAGIC 0xF1FA
+
+/*
+ Typedef declarations.
+*/
+typedef struct _AsepriteHeader
+{
+ uint32_t file_size;
+ uint16_t frames;
+ uint16_t width;
+ uint16_t height;
+ uint16_t color_depth;
+ uint32_t flags;
+ uint16_t speed;
+ uint32_t padding1;
+ uint32_t padding2;
+ uint8_t palette_index;
+ uint8_t ignore1;
+ uint8_t ignore2;
+ uint8_t ignore3;
+ uint16_t pixel_width;
+ uint16_t pixel_height;
+} AsepriteHeader;
+
+typedef struct _AsepriteFrame
+{
+ uint32_t frame_size;
+ uint16_t frame_magic;
+ uint16_t chunk_count;
+ uint16_t duration;
+ uint8_t padding1;
+ uint8_t padding2;
+ uint32_t next_frame;
+} AsepriteFrame;
+
+typedef struct _AsepriteChunk
+{
+ uint32_t chunk_size;
+ uint16_t chunk_type;
+} AsepriteChunk;
+
+typedef struct _AsepriteLayer
+{
+ MagickBooleanType visible;
+ uint16_t blend_mode;
+ uint8_t opacity;
+} AsepriteLayer;
+
+/*
+ AsepriteLayerList is a growable array of AsepriteLayer entries indexed by
+ layer index, used to resolve a cel's referencing layer's visibility,
+ blend mode and opacity.
+*/
+typedef struct _AsepriteLayerList
+{
+ AsepriteLayer *layers;
+ size_t count;
+ size_t capacity;
+} AsepriteLayerList;
+
+/*
+ AsepriteCelCache caches the decompressed pixels (and placement/opacity) of
+ every Raw/Compressed cel keyed by (frame_index,layer_index), so a later
+ Linked Cel (cel_type==1) referencing an earlier frame's cel for the same
+ layer can be resolved and composited identically to a direct cel.
+*/
+typedef struct _AsepriteCelCacheEntry
+{
+ size_t frame_index;
+ uint16_t layer_index;
+ int16_t cel_x;
+ int16_t cel_y;
+ uint16_t cel_width;
+ uint16_t cel_height;
+ uint8_t cel_opacity;
+ uint8_t *pixels;
+} AsepriteCelCacheEntry;
+
+typedef struct _AsepriteCelCache
+{
+ AsepriteCelCacheEntry *entries;
+ size_t count;
+ size_t capacity;
+} AsepriteCelCache;
+
+/*
+ AsepriteCanvas bundles the frame's decoded pixel buffer with its
+ dimensions, color depth and transparent-index, so compositing helpers
+ only need a single parameter instead of five.
+*/
+typedef struct _AsepriteCanvas
+{
+ uint8_t *pixels;
+ size_t width;
+ size_t height;
+ size_t color_depth;
+ uint8_t transparent_index;
+} AsepriteCanvas;
+
+/*
+ Forward declarations.
+*/
+static MagickBooleanType
+ WriteASEImage(const ImageInfo *,Image *,ExceptionInfo *);
+
+/*
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+% %
+% %
+% %
+% R e a d A S E I m a g e %
+% %
+% %
+% %
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% ReadASEImage() reads an Aseprite sprite file and returns it.
+%
+% The format of the ReadASEImage method is:
+%
+% Image *ReadASEImage(const ImageInfo *image_info,
+% ExceptionInfo *exception)
+%
+% A description of each parameter follows:
+%
+% o image_info: the image info.
+%
+% o exception: return any errors or warnings in this structure.
+%
+*/
+static void DestroyASELayerList(AsepriteLayerList *layers)
+{
+ layers->layers=(AsepriteLayer *) RelinquishMagickMemory(layers->layers);
+ layers->count=0;
+ layers->capacity=0;
+}
+
+static void DestroyASECelCache(AsepriteCelCache *cel_cache)
+{
+ size_t
+ i;
+
+ if (cel_cache->entries == (AsepriteCelCacheEntry *) NULL)
+ return;
+ for (i=0; i < cel_cache->count; i++)
+ cel_cache->entries[i].pixels=(uint8_t *) RelinquishMagickMemory(
+ cel_cache->entries[i].pixels);
+ cel_cache->entries=(AsepriteCelCacheEntry *) RelinquishMagickMemory(
+ cel_cache->entries);
+ cel_cache->count=0;
+ cel_cache->capacity=0;
+}
+
+static void ReadASEPaletteChunk(Image *image,const uint8_t *chunk_data,
+ size_t payload_size)
+{
+ size_t
+ first_index,
+ i,
+ last_index,
+ new_size,
+ offset;
+
+ if (payload_size < 20)
+ return;
+ new_size=(size_t) (chunk_data[0] | (chunk_data[1] << 8) |
+ (chunk_data[2] << 16) | (chunk_data[3] << 24));
+ first_index=(size_t) (chunk_data[4] | (chunk_data[5] << 8) |
+ (chunk_data[6] << 16) | (chunk_data[7] << 24));
+ last_index=(size_t) (chunk_data[8] | (chunk_data[9] << 8) |
+ (chunk_data[10] << 16) | (chunk_data[11] << 24));
+ if ((last_index < 256) && (last_index+1 > new_size))
+ new_size=last_index+1;
+ if ((new_size > 0) && (new_size <= 256))
+ image->colors=new_size;
+ offset=20;
+ for (i=first_index; (i <= last_index) && (i < 256); i++)
+ {
+ uint16_t
+ flags;
+
+ if ((offset+6) > payload_size)
+ break;
+ flags=(uint16_t) (chunk_data[offset] | (chunk_data[offset+1] << 8));
+ image->colormap[i].red=(MagickRealType) ScaleCharToQuantum(
+ chunk_data[offset+2]);
+ image->colormap[i].green=(MagickRealType) ScaleCharToQuantum(
+ chunk_data[offset+3]);
+ image->colormap[i].blue=(MagickRealType) ScaleCharToQuantum(
+ chunk_data[offset+4]);
+ image->colormap[i].alpha=(MagickRealType) ScaleCharToQuantum(
+ chunk_data[offset+5]);
+ offset+=6;
+ if ((flags & 0x01) != 0)
+ {
+ uint16_t
+ name_length;
+
+ if ((offset+2) > payload_size)
+ break;
+ name_length=(uint16_t) (chunk_data[offset] |
+ (chunk_data[offset+1] << 8));
+ offset+=2+name_length;
+ }
+ }
+}
+
+/*
+ ReadASELayerChunk() reads a layer chunk (0x2004) and appends its
+ visibility, blend mode and opacity to the growable layers array, so cels
+ can later be composited according to their referencing layer's index.
+ Per the header's flags field, the opacity value is only meaningful if
+ bit 0 ("Layer opacity has valid value") is set; otherwise the layer is
+ treated as fully opaque.
+*/
+static MagickBooleanType ReadASELayerChunk(const Image *image,
+ const uint8_t *chunk_data,size_t payload_size,
+ MagickBooleanType opacity_is_valid,AsepriteLayerList *layers,
+ ExceptionInfo *exception)
+{
+ AsepriteLayer
+ *layer;
+
+ if (payload_size < 18)
+ return(MagickTrue);
+ if (layers->count == layers->capacity)
+ {
+ size_t
+ new_capacity;
+
+ AsepriteLayer
+ *new_layers;
+
+ new_capacity=(layers->capacity == 0) ? 16 : (layers->capacity*2);
+ new_layers=(AsepriteLayer *) ResizeQuantumMemory(layers->layers,
+ new_capacity,sizeof(*new_layers));
+ if (new_layers == (AsepriteLayer *) NULL)
+ {
+ (void) ThrowMagickException(exception,GetMagickModule(),
+ ResourceLimitError,"MemoryAllocationFailed","`%s'",
+ image->filename);
+ return(MagickFalse);
+ }
+ layers->layers=new_layers;
+ layers->capacity=new_capacity;
+ }
+ layer=layers->layers+layers->count;
+ layer->visible=((chunk_data[0] & 0x01) != 0) ? MagickTrue : MagickFalse;
+ layer->blend_mode=(uint16_t) (chunk_data[10] | (chunk_data[11] << 8));
+ layer->opacity=(opacity_is_valid != MagickFalse) ? chunk_data[12] : 255;
+ layers->count++;
+ return(MagickTrue);
+}
+
+/*
+ CompositeASECel() alpha-blends a single decompressed cel's pixel buffer
+ onto the frame canvas at the given position/opacity ("over" compositing).
+ Used for both direct cels (Raw/Compressed) and cels resolved via a Linked
+ Cel reference, so both paths render identically.
+*/
+static void CompositeASECel(const uint8_t *cel_pixels,uint16_t cel_width,
+ uint16_t cel_height,int16_t cel_x,int16_t cel_y,double combined_opacity,
+ AsepriteCanvas *canvas)
+{
+ size_t
+ bytes_per_pixel,
+ x,
+ y;
+
+ bytes_per_pixel=canvas->color_depth/8;
+ for (y=0; y < (size_t) cel_height; y++)
+ {
+ ssize_t
+ canvas_y;
+
+ canvas_y=(ssize_t) cel_y+(ssize_t) y;
+ if ((canvas_y < 0) || (canvas_y >= (ssize_t) canvas->height))
+ continue;
+ for (x=0; x < (size_t) cel_width; x++)
+ {
+ ssize_t
+ canvas_x;
+
+ const uint8_t
+ *src;
+
+ uint8_t
+ *dst;
+
+ canvas_x=(ssize_t) cel_x+(ssize_t) x;
+ if ((canvas_x < 0) || (canvas_x >= (ssize_t) canvas->width))
+ continue;
+ src=cel_pixels+(y*cel_width+x)*bytes_per_pixel;
+ dst=canvas->pixels+((size_t) canvas_y*canvas->width+(size_t) canvas_x)*
+ bytes_per_pixel;
+ switch (canvas->color_depth)
+ {
+ /* RGBA: alpha blend the cel over the canvas ("over" compositing). */
+ case 32:
+ {
+ double
+ dst_alpha,
+ out_alpha,
+ src_alpha;
+
+ size_t
+ channel;
+
+ src_alpha=((double) src[3]/255.0)*combined_opacity;
+ dst_alpha=(double) dst[3]/255.0;
+ out_alpha=src_alpha+dst_alpha*(1.0-src_alpha);
+ for (channel=0; channel < 3; channel++)
+ {
+ double
+ value;
+
+ value=out_alpha > 0.0 ? (src[channel]*src_alpha+dst[channel]*
+ dst_alpha*(1.0-src_alpha))/out_alpha : 0.0;
+ dst[channel]=(uint8_t) (value+0.5);
+ }
+ dst[3]=(uint8_t) (out_alpha*255.0+0.5);
+ break;
+ }
+ /* Grayscale+alpha: same "over" compositing on a single channel. */
+ case 16:
+ {
+ double
+ dst_alpha,
+ out_alpha,
+ src_alpha;
+
+ src_alpha=((double) src[1]/255.0)*combined_opacity;
+ dst_alpha=(double) dst[1]/255.0;
+ out_alpha=src_alpha+dst_alpha*(1.0-src_alpha);
+ if (out_alpha > 0.0)
+ dst[0]=(uint8_t) ((src[0]*src_alpha+dst[0]*dst_alpha*
+ (1.0-src_alpha))/out_alpha+0.5);
+ dst[1]=(uint8_t) (out_alpha*255.0+0.5);
+ break;
+ }
+ /* Indexed: no partial blending is possible; visible, non-transparent pixels
+ replace the destination. */
+ default:
+ {
+ if ((src[0] != canvas->transparent_index) &&
+ (combined_opacity >= 0.5))
+ dst[0]=src[0];
+ break;
+ }
+ }
+ }
+ }
+}
+
+/*
+ CacheASECel() stores a copy of a decoded direct cel's pixels/placement so
+ a later frame's Linked Cel (cel_type==1) referencing this (frame,layer)
+ can be resolved. Returns MagickFalse on allocation failure.
+*/
+static MagickBooleanType CacheASECel(const Image *image,
+ const uint8_t *cel_pixels,size_t cel_pixels_size,uint16_t cel_width,
+ uint16_t cel_height,int16_t cel_x,int16_t cel_y,uint8_t cel_opacity,
+ size_t frame_index,uint16_t layer_index,AsepriteCelCache *cel_cache,
+ ExceptionInfo *exception)
+{
+ AsepriteCelCacheEntry
+ *entry;
+
+ if (cel_cache->count == cel_cache->capacity)
+ {
+ AsepriteCelCacheEntry
+ *new_entries;
+
+ size_t
+ new_capacity;
+
+ new_capacity=(cel_cache->capacity == 0) ? 16 : (cel_cache->capacity*2);
+ new_entries=(AsepriteCelCacheEntry *) ResizeQuantumMemory(
+ cel_cache->entries,new_capacity,sizeof(*new_entries));
+ if (new_entries == (AsepriteCelCacheEntry *) NULL)
+ {
+ (void) ThrowMagickException(exception,GetMagickModule(),
+ ResourceLimitError,"MemoryAllocationFailed","`%s'",
+ image->filename);
+ return(MagickFalse);
+ }
+ cel_cache->entries=new_entries;
+ cel_cache->capacity=new_capacity;
+ }
+ entry=cel_cache->entries+cel_cache->count;
+ entry->pixels=(uint8_t *) AcquireQuantumMemory(cel_pixels_size,
+ sizeof(uint8_t));
+ if (entry->pixels == (uint8_t *) NULL)
+ {
+ (void) ThrowMagickException(exception,GetMagickModule(),
+ ResourceLimitError,"MemoryAllocationFailed","`%s'",image->filename);
+ return(MagickFalse);
+ }
+ (void) memcpy(entry->pixels,cel_pixels,cel_pixels_size);
+ entry->frame_index=frame_index;
+ entry->layer_index=layer_index;
+ entry->cel_x=cel_x;
+ entry->cel_y=cel_y;
+ entry->cel_width=cel_width;
+ entry->cel_height=cel_height;
+ entry->cel_opacity=cel_opacity;
+ cel_cache->count++;
+ return(MagickTrue);
+}
+
+static MagickBooleanType ReadASECelChunk(const Image *image,
+ const uint8_t *chunk_data,size_t payload_size,AsepriteCanvas *canvas,
+ const AsepriteLayerList *layers,size_t frame_index,
+ AsepriteCelCache *cel_cache,ExceptionInfo *exception)
+{
+ double
+ combined_opacity;
+
+ int16_t
+ cel_x,
+ cel_y;
+
+ MagickBooleanType
+ visible;
+
+ size_t
+ bytes_per_pixel,
+ cel_pixels_size,
+ i;
+
+ uint16_t
+ cel_height,
+ cel_type,
+ cel_width,
+ layer_index;
+
+ uint8_t
+ cel_opacity,
+ *cel_pixels,
+ layer_opacity;
+
+ if (payload_size < 16)
+ {
+ (void) ThrowMagickException(exception,GetMagickModule(),
+ CorruptImageError,"InvalidChunkSize","`%s'",image->filename);
+ return(MagickFalse);
+ }
+ layer_index=(uint16_t) (chunk_data[0] | (chunk_data[1] << 8));
+ cel_x=(int16_t) (chunk_data[2] | (chunk_data[3] << 8));
+ cel_y=(int16_t) (chunk_data[4] | (chunk_data[5] << 8));
+ cel_opacity=chunk_data[6];
+ cel_type=(uint16_t) (chunk_data[7] | (chunk_data[8] << 8));
+ visible=MagickTrue;
+ layer_opacity=255;
+ if (layer_index < layers->count)
+ {
+ visible=layers->layers[layer_index].visible;
+ layer_opacity=layers->layers[layer_index].opacity;
+ }
+ if ((visible != MagickFalse) && (layer_index < layers->count) &&
+ (layers->layers[layer_index].blend_mode != 0))
+ {
+ (void) ThrowMagickException(exception,GetMagickModule(),
+ CoderError,"UnsupportedBlendMode","`%s'",image->filename);
+ return(MagickFalse);
+ }
+ if (cel_type == 1)
+ {
+ /* Linked Cel: resolve the referenced frame's cel for this layer from
+ the cache and composite it exactly like a direct cel. */
+ size_t
+ link_frame;
+
+ if (payload_size < 18)
+ {
+ (void) ThrowMagickException(exception,GetMagickModule(),
+ CorruptImageError,"InvalidChunkSize","`%s'",image->filename);
+ return(MagickFalse);
+ }
+ if (visible == MagickFalse)
+ return(MagickTrue);
+ link_frame=(size_t) (chunk_data[16] | (chunk_data[17] << 8));
+ for (i=0; i < cel_cache->count; i++)
+ {
+ AsepriteCelCacheEntry
+ *entry;
+
+ entry=cel_cache->entries+i;
+ if ((entry->frame_index == link_frame) &&
+ (entry->layer_index == layer_index))
+ {
+ combined_opacity=((double) layer_opacity/255.0)*
+ ((double) entry->cel_opacity/255.0);
+ CompositeASECel(entry->pixels,entry->cel_width,
+ entry->cel_height,entry->cel_x,entry->cel_y,combined_opacity,
+ canvas);
+ break;
+ }
+ }
+ return(MagickTrue);
+ }
+ if (cel_type != 0 && cel_type != 2)
+ return(MagickTrue); /* Tilemap cels are not supported. */
+ if (payload_size < 20)
+ {
+ (void) ThrowMagickException(exception,GetMagickModule(),
+ CorruptImageError,"InvalidChunkSize","`%s'",image->filename);
+ return(MagickFalse);
+ }
+ cel_width=(uint16_t) (chunk_data[16] | (chunk_data[17] << 8));
+ cel_height=(uint16_t) (chunk_data[18] | (chunk_data[19] << 8));
+ bytes_per_pixel=canvas->color_depth/8;
+ cel_pixels_size=(size_t) cel_width*cel_height*bytes_per_pixel;
+ if (cel_pixels_size == 0)
+ {
+ (void) ThrowMagickException(exception,GetMagickModule(),
+ CorruptImageError,"InvalidChunkSize","`%s'",image->filename);
+ return(MagickFalse);
+ }
+ cel_pixels=(uint8_t *) AcquireQuantumMemory(cel_pixels_size,
+ sizeof(uint8_t));
+ if (cel_pixels == (uint8_t *) NULL)
+ {
+ (void) ThrowMagickException(exception,GetMagickModule(),
+ ResourceLimitError,"MemoryAllocationFailed","`%s'",image->filename);
+ return(MagickFalse);
+ }
+ if (cel_type == 0)
+ {
+ if ((size_t) (payload_size-20) < cel_pixels_size)
+ {
+ cel_pixels=(uint8_t *) RelinquishMagickMemory(cel_pixels);
+ (void) ThrowMagickException(exception,GetMagickModule(),
+ CorruptImageError,"InvalidChunkSize","`%s'",image->filename);
+ return(MagickFalse);
+ }
+ (void) memcpy(cel_pixels,chunk_data+20,cel_pixels_size);
+ }
+ else
+ {
+#if defined(MAGICKCORE_ZLIB_DELEGATE)
+ int
+ zlib_status;
+
+ uLongf
+ uncompressed_size;
+
+ uncompressed_size=(uLongf) cel_pixels_size;
+ zlib_status=uncompress(cel_pixels,&uncompressed_size,chunk_data+20,
+ (uLongf) (payload_size-20));
+ if ((zlib_status != Z_OK) ||
+ (uncompressed_size != (uLongf) cel_pixels_size))
+ {
+ cel_pixels=(uint8_t *) RelinquishMagickMemory(cel_pixels);
+ (void) ThrowMagickException(exception,GetMagickModule(),
+ CorruptImageError,"UnableToDecompressImage","`%s'",
+ image->filename);
+ return(MagickFalse);
+ }
+#else
+ cel_pixels=(uint8_t *) RelinquishMagickMemory(cel_pixels);
+ (void) ThrowMagickException(exception,GetMagickModule(),
+ MissingDelegateError,"DelegateLibrarySupportNotBuiltIn","`%s' (ZLIB)",
+ image->filename);
+ return(MagickFalse);
+#endif
+ }
+ /* Cache this direct cel's pixels so a later frame can link to it, even
+ if the current layer is invisible or fully transparent. */
+ if (CacheASECel(image,cel_pixels,cel_pixels_size,cel_width,cel_height,
+ cel_x,cel_y,cel_opacity,frame_index,layer_index,cel_cache,
+ exception) == MagickFalse)
+ {
+ cel_pixels=(uint8_t *) RelinquishMagickMemory(cel_pixels);
+ return(MagickFalse);
+ }
+ if (visible == MagickFalse)
+ {
+ cel_pixels=(uint8_t *) RelinquishMagickMemory(cel_pixels);
+ return(MagickTrue);
+ }
+ combined_opacity=((double) layer_opacity/255.0)*((double) cel_opacity/
+ 255.0);
+ CompositeASECel(cel_pixels,cel_width,cel_height,cel_x,cel_y,
+ combined_opacity,canvas);
+ cel_pixels=(uint8_t *) RelinquishMagickMemory(cel_pixels);
+ return(MagickTrue);
+}
+
+static Image *ReadASEImage(const ImageInfo *image_info,ExceptionInfo *exception)
+{
+ AsepriteCanvas
+ canvas;
+
+ AsepriteCelCache
+ cel_cache;
+
+ AsepriteChunk
+ ase_chunk;
+
+ AsepriteFrame
+ ase_frame;
+
+ AsepriteHeader
+ ase_header;
+
+ AsepriteLayerList
+ layers;
+
+ Image
+ *image,
+ *frame_image;
+
+ MagickBooleanType
+ status;
+
+ Quantum
+ *q;
+
+ size_t
+ chunk_idx,
+ count,
+ frame_idx,
+ pixels_size;
+
+ ssize_t
+ x;
+
+ ssize_t
+ y;
+
+ uint16_t
+ magic;
+
+ uint8_t
+ *chunk_data,
+ *pixel_data,
+ *pixels;
+
+ assert(image_info != (const ImageInfo *) NULL);
+ assert(image_info->signature == MagickCoreSignature);
+ assert(exception != (ExceptionInfo *) NULL);
+ assert(exception->signature == MagickCoreSignature);
+ if (IsEventLogging() != MagickFalse)
+ (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",
+ image_info->filename);
+ layers.layers=(AsepriteLayer *) NULL;
+ layers.count=0;
+ layers.capacity=0;
+ cel_cache.entries=(AsepriteCelCacheEntry *) NULL;
+ cel_cache.count=0;
+ cel_cache.capacity=0;
+ image=AcquireImage(image_info,exception);
+ status=OpenBlob(image_info,image,ReadBinaryBlobMode,exception);
+ if (status == MagickFalse)
+ return(DestroyImageList(image));
+ ase_header.file_size=ReadBlobLSBLong(image);
+ magic=ReadBlobLSBShort(image);
+ if (magic != ASE_MAGIC)
+ ThrowReaderException(CorruptImageError,"ImproperImageHeader");
+ ase_header.frames=ReadBlobLSBShort(image);
+ ase_header.width=ReadBlobLSBShort(image);
+ ase_header.height=ReadBlobLSBShort(image);
+ ase_header.color_depth=ReadBlobLSBShort(image);
+ ase_header.flags=ReadBlobLSBLong(image);
+ ase_header.speed=ReadBlobLSBShort(image);
+ ase_header.padding1=ReadBlobLSBLong(image);
+ ase_header.padding2=ReadBlobLSBLong(image);
+ ase_header.palette_index=(uint8_t) ReadBlobByte(image);
+ ase_header.ignore1=(uint8_t) ReadBlobByte(image);
+ ase_header.ignore2=(uint8_t) ReadBlobByte(image);
+ ase_header.ignore3=(uint8_t) ReadBlobByte(image);
+ ase_header.pixel_width=ReadBlobLSBShort(image);
+ ase_header.pixel_height=ReadBlobLSBShort(image);
+ (void) SeekBlob(image,92,SEEK_CUR);
+ if ((ase_header.width == 0) || (ase_header.height == 0))
+ ThrowReaderException(CorruptImageError,"InvalidImageDimensions");
+ if ((ase_header.width > 16384) || (ase_header.height > 16384))
+ ThrowReaderException(CorruptImageError,"InvalidImageDimensions");
+ if ((ase_header.color_depth != 32) && (ase_header.color_depth != 8) &&
+ (ase_header.color_depth != 16))
+ ThrowReaderException(CorruptImageError,"UnsupportedColorDepth");
+ if ((ase_header.frames == 0) || (ase_header.frames > 4096))
+ ThrowReaderException(CorruptImageError,"InvalidNumberOfFrames");
+ if (ase_header.file_size > 512*1024*1024)
+ ThrowReaderException(CorruptImageError,"FileSizeTooLarge");
+ image->columns=ase_header.width;
+ image->rows=ase_header.height;
+ image->alpha_trait=BlendPixelTrait;
+ image->ticks_per_second=1000;
+ image->dispose=BackgroundDispose;
+ status=SetImageExtent(image,image->columns,image->rows,exception);
+ if (status == MagickFalse)
+ return(DestroyImageList(image));
+ if (ase_header.color_depth == 8)
+ {
+ if (AcquireImageColormap(image,256,exception) == MagickFalse)
+ {
+ DestroyASELayerList(&layers);
+ DestroyASECelCache(&cel_cache);
+ ThrowReaderException(ResourceLimitError,"MemoryAllocationFailed");
+ }
+ }
+ if (AcquireMagickResource(ListLengthResource,ase_header.frames) == MagickFalse)
+ ThrowFileException(exception,ResourceLimitError,"ListLengthExceedsLimit",
+ image->filename);
+ frame_image=image;
+ for (frame_idx=0; frame_idx < (size_t) ase_header.frames; frame_idx++)
+ {
+ if (frame_idx > 0)
+ {
+ AcquireNextImage(image_info,frame_image,exception);
+ if (GetNextImageInList(frame_image) == (Image *) NULL)
+ {
+ DestroyASELayerList(&layers);
+ DestroyASECelCache(&cel_cache);
+ return(DestroyImageList(image));
+ }
+ frame_image=SyncNextImageInList(frame_image);
+ frame_image->columns=ase_header.width;
+ frame_image->rows=ase_header.height;
+ frame_image->alpha_trait=BlendPixelTrait;
+ frame_image->ticks_per_second=1000;
+ frame_image->dispose=BackgroundDispose;
+ status=SetImageExtent(frame_image,frame_image->columns,
+ frame_image->rows,exception);
+ if (status == MagickFalse)
+ {
+ DestroyASELayerList(&layers);
+ DestroyASECelCache(&cel_cache);
+ return(DestroyImageList(image));
+ }
+ if (ase_header.color_depth == 8)
+ {
+ if (AcquireImageColormap(frame_image,256,exception) ==
+ MagickFalse)
+ {
+ DestroyASELayerList(&layers);
+ DestroyASECelCache(&cel_cache);
+ ThrowReaderException(ResourceLimitError,
+ "MemoryAllocationFailed");
+ }
+ (void) memcpy(frame_image->colormap,
+ frame_image->previous->colormap,256*
+ sizeof(*frame_image->colormap));
+ }
+ }
+ ase_frame.frame_size=ReadBlobLSBLong(image);
+ ase_frame.frame_magic=ReadBlobLSBShort(image);
+ if (ase_frame.frame_magic != ASE_FRAME_MAGIC)
+ {
+ DestroyASELayerList(&layers);
+ DestroyASECelCache(&cel_cache);
+ ThrowReaderException(CorruptImageError,"InvalidFrameHeader");
+ }
+ ase_frame.chunk_count=ReadBlobLSBShort(image);
+ ase_frame.duration=ReadBlobLSBShort(image);
+ ase_frame.padding1=(uint8_t) ReadBlobByte(image);
+ ase_frame.padding2=(uint8_t) ReadBlobByte(image);
+ ase_frame.next_frame=ReadBlobLSBLong(image);
+ if (ase_frame.duration > 0)
+ frame_image->delay=ase_frame.duration;
+ if (image_info->ping != MagickFalse)
+ {
+ (void) SeekBlob(image,(MagickOffsetType) ase_frame.frame_size-16,
+ SEEK_CUR);
+ continue;
+ }
+ switch (ase_header.color_depth)
+ {
+ case 32:
+ pixels_size=ase_header.width*ase_header.height*4;
+ break;
+ case 16:
+ pixels_size=ase_header.width*ase_header.height*2;
+ break;
+ case 8:
+ pixels_size=ase_header.width*ase_header.height;
+ break;
+ default:
+ pixels_size=0;
+ break;
+ }
+ pixel_data=(uint8_t *) AcquireQuantumMemory(pixels_size,sizeof(uint8_t));
+ if (pixel_data == (uint8_t *) NULL)
+ {
+ DestroyASELayerList(&layers);
+ DestroyASECelCache(&cel_cache);
+ ThrowReaderException(ResourceLimitError,"MemoryAllocationFailed");
+ }
+ if (ase_header.color_depth == 8)
+ (void) memset(pixel_data,ase_header.palette_index,pixels_size);
+ else
+ (void) memset(pixel_data,0,pixels_size);
+ canvas.pixels=pixel_data;
+ canvas.width=ase_header.width;
+ canvas.height=ase_header.height;
+ canvas.color_depth=ase_header.color_depth;
+ canvas.transparent_index=ase_header.palette_index;
+ for (chunk_idx=0; chunk_idx < (size_t) ase_frame.chunk_count; chunk_idx++)
+ {
+ size_t
+ payload_size;
+
+ ase_chunk.chunk_size=ReadBlobLSBLong(image);
+ ase_chunk.chunk_type=ReadBlobLSBShort(image);
+ if ((ase_chunk.chunk_size < 6) || (ase_chunk.chunk_size > 100*1024*1024))
+ {
+ pixel_data=(uint8_t *) RelinquishMagickMemory(pixel_data);
+ DestroyASELayerList(&layers);
+ DestroyASECelCache(&cel_cache);
+ ThrowReaderException(CorruptImageError,"InvalidChunkSize");
+ }
+ payload_size=ase_chunk.chunk_size-6;
+ chunk_data=(uint8_t *) AcquireQuantumMemory(payload_size,
+ sizeof(uint8_t));
+ if (chunk_data == (uint8_t *) NULL)
+ {
+ pixel_data=(uint8_t *) RelinquishMagickMemory(pixel_data);
+ DestroyASELayerList(&layers);
+ DestroyASECelCache(&cel_cache);
+ ThrowReaderException(ResourceLimitError,"MemoryAllocationFailed");
+ }
+ count=ReadBlob(image,payload_size,chunk_data);
+ if (count != payload_size)
+ {
+ chunk_data=(uint8_t *) RelinquishMagickMemory(chunk_data);
+ pixel_data=(uint8_t *) RelinquishMagickMemory(pixel_data);
+ DestroyASELayerList(&layers);
+ DestroyASECelCache(&cel_cache);
+ ThrowReaderException(CorruptImageError,"UnableToReadImageData");
+ }
+ switch (ase_chunk.chunk_type)
+ {
+ /* CEL chunk (0x2005): composite the raw or compressed cel image
+ payload onto the frame canvas. */
+ case 0x2005:
+ {
+ MagickBooleanType
+ cel_status;
+
+ cel_status=ReadASECelChunk(image,chunk_data,payload_size,&canvas,
+ &layers,frame_idx,&cel_cache,exception);
+ chunk_data=(uint8_t *) RelinquishMagickMemory(chunk_data);
+ if (cel_status == MagickFalse)
+ {
+ pixel_data=(uint8_t *) RelinquishMagickMemory(pixel_data);
+ DestroyASELayerList(&layers);
+ DestroyASECelCache(&cel_cache);
+ return(DestroyImageList(image));
+ }
+ continue;
+ }
+ /* New palette chunk: populate the RGBA color table. */
+ case 0x2019:
+ {
+ if (frame_image->storage_class == PseudoClass)
+ ReadASEPaletteChunk(frame_image,chunk_data,payload_size);
+ break;
+ }
+ /* Layer chunk: track visibility/blend mode/opacity for later cel
+ compositing. */
+ case 0x2004:
+ {
+ if (ReadASELayerChunk(image,chunk_data,payload_size,
+ (ase_header.flags & 0x01) != 0 ? MagickTrue : MagickFalse,
+ &layers,exception) == MagickFalse)
+ {
+ chunk_data=(uint8_t *) RelinquishMagickMemory(chunk_data);
+ pixel_data=(uint8_t *) RelinquishMagickMemory(pixel_data);
+ DestroyASELayerList(&layers);
+ DestroyASECelCache(&cel_cache);
+ return(DestroyImageList(image));
+ }
+ break;
+ }
+ default:
+ break;
+ }
+ chunk_data=(uint8_t *) RelinquishMagickMemory(chunk_data);
+ }
+ pixels=pixel_data;
+ switch (ase_header.color_depth)
+ {
+ case 32:
+ {
+ unsigned char
+ *p;
+
+ p=pixels;
+ for (y=0; y < (ssize_t) frame_image->rows; y++)
+ {
+ q=QueueAuthenticPixels(frame_image,0,y,frame_image->columns,1,exception);
+ if (q == (Quantum *) NULL)
+ break;
+ for (x=0; x < (ssize_t) frame_image->columns; x++)
+ {
+ SetPixelRed(frame_image,ScaleCharToQuantum(*p++),q);
+ SetPixelGreen(frame_image,ScaleCharToQuantum(*p++),q);
+ SetPixelBlue(frame_image,ScaleCharToQuantum(*p++),q);
+ SetPixelAlpha(frame_image,ScaleCharToQuantum(*p++),q);
+ q+=GetPixelChannels(frame_image);
+ }
+ if (SyncAuthenticPixels(frame_image,exception) == MagickFalse)
+ break;
+ if (SetImageProgress(frame_image,LoadImageTag,y,frame_image->rows) == MagickFalse)
+ break;
+ }
+ break;
+ }
+ case 16:
+ {
+ unsigned char
+ *p;
+
+ p=pixels;
+ for (y=0; y < (ssize_t) frame_image->rows; y++)
+ {
+ q=QueueAuthenticPixels(frame_image,0,y,frame_image->columns,1,exception);
+ if (q == (Quantum *) NULL)
+ break;
+ for (x=0; x < (ssize_t) frame_image->columns; x++)
+ {
+ Quantum
+ gray;
+
+ gray=ScaleCharToQuantum(*p++);
+ SetPixelRed(frame_image,gray,q);
+ SetPixelGreen(frame_image,gray,q);
+ SetPixelBlue(frame_image,gray,q);
+ SetPixelAlpha(frame_image,ScaleCharToQuantum(*p++),q);
+ q+=GetPixelChannels(frame_image);
+ }
+ if (SyncAuthenticPixels(frame_image,exception) == MagickFalse)
+ break;
+ if (SetImageProgress(frame_image,LoadImageTag,y,frame_image->rows) == MagickFalse)
+ break;
+ }
+ break;
+ }
+ case 8:
+ {
+ unsigned char
+ *p;
+
+ p=pixels;
+ for (y=0; y < (ssize_t) frame_image->rows; y++)
+ {
+ q=QueueAuthenticPixels(frame_image,0,y,frame_image->columns,1,exception);
+ if (q == (Quantum *) NULL)
+ break;
+ for (x=0; x < (ssize_t) frame_image->columns; x++)
+ {
+ SetPixelIndex(frame_image,*p,q);
+ p++;
+ q+=GetPixelChannels(frame_image);
+ }
+ if (SyncAuthenticPixels(frame_image,exception) == MagickFalse)
+ break;
+ if (SetImageProgress(frame_image,LoadImageTag,y,frame_image->rows) == MagickFalse)
+ break;
+ }
+ break;
+ }
+ }
+ pixel_data=(uint8_t *) RelinquishMagickMemory(pixel_data);
+ }
+ DestroyASELayerList(&layers);
+ DestroyASECelCache(&cel_cache);
+ if (EOFBlob(image) != MagickFalse)
+ ThrowFileException(exception,CorruptImageError,"UnexpectedEndOfFile",
+ image->filename);
+ if (CloseBlob(image) == MagickFalse)
+ status=MagickFalse;
+ if (status == MagickFalse)
+ return(DestroyImageList(image));
+ return(GetFirstImageInList(image));
+}
+
+/*
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+% %
+% %
+% %
+% R e g i s t e r A S E I m a g e %
+% %
+% %
+% %
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% RegisterASEImage() adds properties for the ASE image format to
+% the list of supported formats. The properties include the image format
+% tag, a method to read and/or write the format, whether the format
+% supports the saving of more than one frame to the same file or blob,
+% whether the format supports native in-memory I/O, and a brief
+% description of the format.
+%
+% The format of the RegisterASEImage method is:
+%
+% size_t RegisterASEImage(void)
+%
+*/
+ModuleExport size_t RegisterASEImage(void)
+{
+ MagickInfo
+ *entry;
+
+ entry=AcquireMagickInfo("ASE","ASE","Aseprite Sprite Format");
+ entry->decoder=(DecodeImageHandler *) ReadASEImage;
+ entry->encoder=(EncodeImageHandler *) WriteASEImage;
+ entry->flags|=CoderDecoderSeekableStreamFlag;
+ entry->flags|=CoderEncoderSeekableStreamFlag;
+ entry->mime_type=ConstantString("image/x-aseprite");
+ (void) RegisterMagickInfo(entry);
+ entry=AcquireMagickInfo("ASE","ASEPRITE","Aseprite Sprite Format");
+ entry->decoder=(DecodeImageHandler *) ReadASEImage;
+ entry->encoder=(EncodeImageHandler *) WriteASEImage;
+ entry->flags|=CoderDecoderSeekableStreamFlag;
+ entry->flags|=CoderEncoderSeekableStreamFlag;
+ entry->mime_type=ConstantString("image/x-aseprite");
+ (void) RegisterMagickInfo(entry);
+ return(MagickImageCoderSignature);
+}
+
+/*
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+% %
+% %
+% %
+% U n r e g i s t e r A S E I m a g e %
+% %
+% %
+% %
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% UnregisterASEImage() removes format registrations made by the
+% ASE module from the list of supported formats.
+%
+% The format of the UnregisterASEImage method is:
+%
+% UnregisterASEImage(void)
+%
+*/
+ModuleExport void UnregisterASEImage(void)
+{
+ (void) UnregisterMagickInfo("ASE");
+ (void) UnregisterMagickInfo("ASEPRITE");
+}
+
+/*
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+% %
+% %
+% %
+% W r i t e A S E I m a g e %
+% %
+% %
+% %
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% WriteASEImage() writes an image to an Aseprite sprite file.
+%
+% The format of the WriteASEImage method is:
+%
+% MagickBooleanType WriteASEImage(const ImageInfo *image_info,
+% Image *image,ExceptionInfo *exception)
+%
+% A description of each parameter follows.
+%
+% o image_info: the image info.
+%
+% o image: The image.
+%
+% o exception: return any errors or warnings in this structure.
+%
+*/
+static MagickBooleanType WriteASEImage(const ImageInfo *image_info,
+ Image *image,ExceptionInfo *exception)
+{
+ const Quantum
+ *q;
+
+ double
+ delay_ms;
+
+ Image
+ *p;
+
+ MagickBooleanType
+ is_indexed,
+ status;
+
+ size_t
+ bytes_per_pixel,
+ chunk_count,
+ chunk_count_offset,
+ color_depth,
+ count,
+ end_offset,
+ file_size_offset,
+ frame_count,
+ frame_size_offset,
+ frame_x,
+ frame_y,
+ pixel_count,
+ ticks_per_second;
+
+ ssize_t
+ i;
+
+ uint8_t
+ *pixels,
+ transparent_index;
+
+ assert(image_info != (const ImageInfo *) NULL);
+ assert(image_info->signature == MagickCoreSignature);
+ assert(image != (Image *) NULL);
+ assert(image->signature == MagickCoreSignature);
+ assert(exception != (ExceptionInfo *) NULL);
+ assert(exception->signature == MagickCoreSignature);
+ if (IsEventLogging() != MagickFalse)
+ (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",
+ image_info->filename);
+ status=OpenBlob(image_info,image,WriteBinaryBlobMode,exception);
+ if (status == MagickFalse)
+ return(status);
+ frame_count=0;
+ for (p=image; p != (Image *) NULL; p=p->next)
+ frame_count++;
+ /*
+ Indexed (PseudoClass) images with 256 colors or fewer are written as
+ 8bpp Indexed sprites with a real Palette Chunk; everything else is
+ written as 32bpp RGBA, as before.
+ */
+ is_indexed=((image->storage_class == PseudoClass) &&
+ (image->colors > 0) && (image->colors <= 256)) ? MagickTrue :
+ MagickFalse;
+ color_depth=(is_indexed != MagickFalse) ? 8 : 32;
+ bytes_per_pixel=color_depth/8;
+ transparent_index=0;
+ if (is_indexed != MagickFalse)
+ for (i=0; i < (ssize_t) image->colors; i++)
+ if (image->colormap[i].alpha == (MagickRealType) TransparentAlpha)
+ {
+ transparent_index=(uint8_t) i;
+ break;
+ }
+ file_size_offset=TellBlob(image);
+ (void) WriteBlobLSBLong(image,0);
+ (void) WriteBlobLSBShort(image,ASE_MAGIC);
+ (void) WriteBlobLSBShort(image,(unsigned short) frame_count);
+ (void) WriteBlobLSBShort(image,(unsigned short) image->columns);
+ (void) WriteBlobLSBShort(image,(unsigned short) image->rows);
+ (void) WriteBlobLSBShort(image,(unsigned short) color_depth);
+ (void) WriteBlobLSBLong(image,1); /* flags: bit 0 = layer opacity has valid value */
+ ticks_per_second=(image->ticks_per_second > 0) ? image->ticks_per_second :
+ 100;
+ (void) WriteBlobLSBShort(image,(unsigned short) (((double) image->delay*1000.0)/
+ (double) ticks_per_second+0.5));
+ (void) WriteBlobLSBLong(image,0);
+ (void) WriteBlobLSBLong(image,0);
+ (void) WriteBlobByte(image,transparent_index);
+ (void) WriteBlobByte(image,0);
+ (void) WriteBlobByte(image,0);
+ (void) WriteBlobByte(image,0);
+ (void) WriteBlobLSBShort(image,1);
+ (void) WriteBlobLSBShort(image,1);
+ for (i=0; i < 46; i++)
+ (void) WriteBlobLSBShort(image,0); /* grid x/y/width/height + reserved[84] */
+ pixel_count=image->columns*image->rows;
+ pixels=(uint8_t *) AcquireQuantumMemory(pixel_count,bytes_per_pixel);
+ if (pixels == (uint8_t *) NULL)
+ ThrowWriterException(ResourceLimitError,"MemoryAllocationFailed");
+ for (p=image; p != (Image *) NULL; p=p->next)
+ {
+ chunk_count=0;
+ frame_size_offset=TellBlob(image);
+ (void) WriteBlobLSBLong(image,0);
+ (void) WriteBlobLSBShort(image,ASE_FRAME_MAGIC);
+ chunk_count_offset=TellBlob(image);
+ (void) WriteBlobLSBShort(image,0);
+ ticks_per_second=(p->ticks_per_second > 0) ? p->ticks_per_second : 100;
+ delay_ms=((double) p->delay*1000.0)/(double) ticks_per_second;
+ (void) WriteBlobLSBShort(image,(unsigned short) (delay_ms+0.5));
+ (void) WriteBlobByte(image,0);
+ (void) WriteBlobByte(image,0);
+ (void) WriteBlobLSBLong(image,0);
+ if (p == image)
+ {
+ /*
+ Write a single Layer Chunk (0x2004) describing the one layer all
+ cels reference, so the file is well-formed for any Aseprite-
+ compatible reader (not just our own).
+ */
+ (void) WriteBlobLSBLong(image,24);
+ (void) WriteBlobLSBShort(image,0x2004);
+ (void) WriteBlobLSBShort(image,1); /* flags: visible */
+ (void) WriteBlobLSBShort(image,0); /* layer type: normal */
+ (void) WriteBlobLSBShort(image,0); /* child level */
+ (void) WriteBlobLSBShort(image,0); /* default width (ignored) */
+ (void) WriteBlobLSBShort(image,0); /* default height (ignored) */
+ (void) WriteBlobLSBShort(image,0); /* blend mode: normal */
+ (void) WriteBlobByte(image,255); /* opacity */
+ (void) WriteBlobByte(image,0);
+ (void) WriteBlobByte(image,0);
+ (void) WriteBlobByte(image,0);
+ (void) WriteBlobLSBShort(image,0); /* layer name length */
+ chunk_count++;
+ if (is_indexed != MagickFalse)
+ {
+ /*
+ Write the New Palette Chunk (0x2019) with the real colormap
+ (including per-entry alpha), so the sprite's colors and
+ transparency survive the round trip.
+ */
+ (void) WriteBlobLSBLong(image,(unsigned int) (20+image->colors*6+6));
+ (void) WriteBlobLSBShort(image,0x2019);
+ (void) WriteBlobLSBLong(image,(unsigned int) image->colors);
+ (void) WriteBlobLSBLong(image,0);
+ (void) WriteBlobLSBLong(image,(unsigned int) (image->colors-1));
+ (void) WriteBlobLSBLong(image,0);
+ (void) WriteBlobLSBLong(image,0);
+ for (i=0; i < (ssize_t) image->colors; i++)
+ {
+ (void) WriteBlobLSBShort(image,0);
+ (void) WriteBlobByte(image,ScaleQuantumToChar(
+ ClampToQuantum(image->colormap[i].red)));
+ (void) WriteBlobByte(image,ScaleQuantumToChar(
+ ClampToQuantum(image->colormap[i].green)));
+ (void) WriteBlobByte(image,ScaleQuantumToChar(
+ ClampToQuantum(image->colormap[i].blue)));
+ (void) WriteBlobByte(image,ScaleQuantumToChar(
+ ClampToQuantum(image->colormap[i].alpha)));
+ }
+ chunk_count++;
+ }
+ }
+ (void) memset(pixels,0,pixel_count*bytes_per_pixel);
+ for (frame_y=0; frame_y < p->rows; frame_y++)
+ {
+ q=GetVirtualPixels(p,0,(ssize_t)frame_y,p->columns,1,exception);
+ if (q == (const Quantum *) NULL)
+ break;
+ for (frame_x=0; frame_x < p->columns; frame_x++)
+ {
+ size_t
+ offset;
+ offset=(frame_y*p->columns+frame_x)*bytes_per_pixel;
+ if (offset+bytes_per_pixel <= pixel_count*bytes_per_pixel)
+ {
+ if (is_indexed != MagickFalse)
+ pixels[offset]=(uint8_t) GetPixelIndex(p,q);
+ else
+ {
+ pixels[offset]=ScaleQuantumToChar(GetPixelRed(p,q));
+ pixels[offset+1]=ScaleQuantumToChar(GetPixelGreen(p,q));
+ pixels[offset+2]=ScaleQuantumToChar(GetPixelBlue(p,q));
+ pixels[offset+3]=ScaleQuantumToChar(GetPixelAlpha(p,q));
+ }
+ }
+ q+=GetPixelChannels(p);
+ }
+ }
+ if (image_info->compression == NoCompression)
+ {
+ (void) WriteBlobLSBLong(image,(unsigned int) (pixel_count*bytes_per_pixel+26));
+ (void) WriteBlobLSBShort(image,0x2005);
+ (void) WriteBlobLSBShort(image,0); /* layer index */
+ (void) WriteBlobLSBShort(image,0); /* x */
+ (void) WriteBlobLSBShort(image,0); /* y */
+ (void) WriteBlobByte(image,255); /* opacity */
+ (void) WriteBlobLSBShort(image,0); /* raw image */
+ (void) WriteBlobLSBShort(image,0); /* z-index */
+ (void) WriteBlobByte(image,0);
+ (void) WriteBlobByte(image,0);
+ (void) WriteBlobByte(image,0);
+ (void) WriteBlobByte(image,0);
+ (void) WriteBlobByte(image,0);
+ (void) WriteBlobLSBShort(image,(unsigned short) p->columns);
+ (void) WriteBlobLSBShort(image,(unsigned short) p->rows);
+ count=WriteBlob(image,pixel_count*bytes_per_pixel,pixels);
+ chunk_count++;
+ if (count != pixel_count*bytes_per_pixel)
+ {
+ pixels=(uint8_t *) RelinquishMagickMemory(pixels);
+ ThrowWriterException(FileOpenError,"UnableToWriteBlob");
+ }
+ }
+ else
+#if defined(MAGICKCORE_ZLIB_DELEGATE)
+ {
+ uLongf
+ compressed_size,
+ payload_size;
+ uint8_t
+ *compressed;
+
+ payload_size=(uLongf) (pixel_count*bytes_per_pixel);
+ compressed_size=compressBound((uLong) payload_size);
+ compressed=(uint8_t *) AcquireQuantumMemory((size_t) compressed_size,
+ sizeof(uint8_t));
+ if (compressed == (uint8_t *) NULL)
+ {
+ pixels=(uint8_t *) RelinquishMagickMemory(pixels);
+ ThrowWriterException(ResourceLimitError,"MemoryAllocationFailed");
+ }
+ if (compress2(compressed,&compressed_size,pixels,payload_size,
+ Z_BEST_COMPRESSION) != Z_OK)
+ {
+ compressed=(uint8_t *) RelinquishMagickMemory(compressed);
+ pixels=(uint8_t *) RelinquishMagickMemory(pixels);
+ ThrowWriterException(CorruptImageError,"UnableToCompressImage");
+ }
+ (void) WriteBlobLSBLong(image,(unsigned int) (compressed_size+26));
+ (void) WriteBlobLSBShort(image,0x2005);
+ (void) WriteBlobLSBShort(image,0); /* layer index */
+ (void) WriteBlobLSBShort(image,0); /* x */
+ (void) WriteBlobLSBShort(image,0); /* y */
+ (void) WriteBlobByte(image,255); /* opacity */
+ (void) WriteBlobLSBShort(image,2); /* compressed image */
+ (void) WriteBlobLSBShort(image,0); /* z-index */
+ (void) WriteBlobByte(image,0);
+ (void) WriteBlobByte(image,0);
+ (void) WriteBlobByte(image,0);
+ (void) WriteBlobByte(image,0);
+ (void) WriteBlobByte(image,0);
+ (void) WriteBlobLSBShort(image,(unsigned short) p->columns);
+ (void) WriteBlobLSBShort(image,(unsigned short) p->rows);
+ count=WriteBlob(image,(size_t) compressed_size,compressed);
+ compressed=(uint8_t *) RelinquishMagickMemory(compressed);
+ chunk_count++;
+ if (count != (size_t) compressed_size)
+ {
+ pixels=(uint8_t *) RelinquishMagickMemory(pixels);
+ ThrowWriterException(FileOpenError,"UnableToWriteBlob");
+ }
+ }
+#else
+ {
+ pixels=(uint8_t *) RelinquishMagickMemory(pixels);
+ (void) ThrowMagickException(exception,GetMagickModule(),
+ MissingDelegateError,"DelegateLibrarySupportNotBuiltIn","`%s' (ZLIB)",
+ image->filename);
+ return(MagickFalse);
+ }
+#endif
+ end_offset=TellBlob(image);
+ SeekBlob(image,(MagickOffsetType)chunk_count_offset,SEEK_SET);
+ (void) WriteBlobLSBShort(image,(unsigned short) chunk_count);
+ SeekBlob(image,(MagickOffsetType)frame_size_offset,SEEK_SET);
+ (void) WriteBlobLSBLong(image,(unsigned int) (end_offset-frame_size_offset));
+ SeekBlob(image,0,SEEK_END);
+ }
+ pixels=(uint8_t *) RelinquishMagickMemory(pixels);
+ end_offset=TellBlob(image);
+ SeekBlob(image,(MagickOffsetType)file_size_offset,SEEK_SET);
+ (void) WriteBlobLSBLong(image,(unsigned int) end_offset);
+ SeekBlob(image,0,SEEK_END);
+ if (CloseBlob(image) == MagickFalse)
+ status=MagickFalse;
+ return(status);
+}
diff --git a/coders/ase.h b/coders/ase.h
new file mode 100644
index 000000000..b43ad9222
--- /dev/null
+++ b/coders/ase.h
@@ -0,0 +1,33 @@
+/*
+ Copyright @ 1999 ImageMagick Studio LLC, a non-profit organization
+ dedicated to making software imaging solutions freely available.
+
+ You may not use this file except in compliance with the License. You may
+ obtain a copy of the License at
+
+ https://imagemagick.org/license/
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+*/
+
+#include "coders/coders-private.h"
+
+#define MagickASEHeaders \
+ MagickCoderHeader("ASE", 4, "\xa5\xe0")
+
+#define MagickASEAliases \
+ MagickCoderAlias("ASE", "ASEPRITE")
+
+#if defined(__cplusplus) || defined(c_plusplus)
+extern "C" {
+#endif
+
+MagickCoderExports(ASE)
+
+#if defined(__cplusplus) || defined(c_plusplus)
+}
+#endif
diff --git a/coders/coders-list.h b/coders/coders-list.h
index cdf06c68a..2691d78bd 100644
--- a/coders/coders-list.h
+++ b/coders/coders-list.h
@@ -18,6 +18,7 @@
AddMagickCoder(AAI)
AddMagickCoder(ART)
+AddMagickCoder(ASE)
AddMagickCoder(ASHLAR)
AddMagickCoder(AVS)
AddMagickCoder(BAYER)
diff --git a/coders/coders.h b/coders/coders.h
index 7f3c70c8b..1bf290ecf 100644
--- a/coders/coders.h
+++ b/coders/coders.h
@@ -21,6 +21,7 @@
*/
#include "coders/aai.h"
#include "coders/art.h"
+#include "coders/ase.h"
#include "coders/ashlar.h"
#include "coders/avs.h"
#include "coders/bayer.h"