]> git.bts.cx Git - benzene.git/blob - src/bz/gfx/aseprite.c
b2a829ff0bdbf442333ed4bd96186bf1b2f51058
[benzene.git] / src / bz / gfx / aseprite.c
1 #include <bz/gfx/aseprite_internal.h>
2
3 #include <bz/debug/assert.h>
4 #include <bz/debug/log.h>
5 #include <bz/math/math.h>
6 #include <bz/memory/allocator.h>
7 #include <bz/renderer/palette_internal.h>
8 #include <bz/resources/resource.h>
9 #include <bz/types/identifier_internal.h>
10 #include <stdio.h> // Apparently stb_image.h needs this??
11 #include <stb_image.h>
12 #include <string.h> // memset
13
14 #include <assert.h> // Static assertions
15
16 #define PACKED_STRUCT __attribute__((__packed__))
17
18 typedef uint8_t  ASE_BYTE;
19 typedef uint16_t ASE_WORD;
20 typedef int16_t  ASE_SHORT;
21 typedef uint32_t ASE_DWORD;
22 typedef uint32_t ASE_LONG;
23 typedef uint32_t ASE_FIXED;
24 typedef float    ASE_FLOAT;
25 typedef double   ASE_DOUBLE;
26 typedef uint64_t ASE_QWORD;
27 typedef int64_t  ASE_LONG64;
28
29 struct PACKED_STRUCT ASE_STRING {
30         ASE_WORD length;
31         //ASE_BYTE characters[];
32 };
33 typedef struct ASE_STRING ASE_STRING;
34 static_assert(sizeof(ASE_STRING) == 2, "ASE_STRING is wrong size");
35
36 struct PACKED_STRUCT ASE_POINT {
37         ASE_LONG x;
38         ASE_LONG y;
39 };
40 typedef struct ASE_POINT ASE_POINT;
41 static_assert(sizeof(ASE_POINT) == 8, "ASE_POINT is wrong size");
42
43 struct PACKED_STRUCT ASE_SIZE {
44         ASE_LONG width;
45         ASE_LONG height;
46 };
47 typedef struct ASE_SIZE ASE_SIZE;
48 static_assert(sizeof(ASE_SIZE) == 8, "ASE_SIZE is wrong size");
49
50 struct PACKED_STRUCT ASE_RECT {
51         ASE_POINT origin;
52         ASE_SIZE size;
53 };
54 typedef struct ASE_RECT ASE_RECT;
55 static_assert(sizeof(ASE_RECT) == 16, "ASE_RECT is wrong size");
56
57 struct PACKED_STRUCT ASE_PIXEL_RBGA {
58         ASE_BYTE r;
59         ASE_BYTE g;
60         ASE_BYTE b;
61         ASE_BYTE a;
62 };
63 typedef struct ASE_PIXEL_RBGA ASE_PIXEL_RBGA;
64 static_assert(sizeof(ASE_PIXEL_RBGA) == 4, "ASE_PIXEL_RBGA is wrong size");
65
66 struct PACKED_STRUCT ASE_PIXEL_GRAYSCALE {
67         ASE_BYTE v;
68         ASE_BYTE a;
69 };
70 typedef struct ASE_PIXEL_GRAYSCALE ASE_PIXEL_GRAYSCALE;
71 static_assert(sizeof(ASE_PIXEL_GRAYSCALE) == 2, "ASE_PIXEL_GRAYSCALE is wrong size");
72
73 struct PACKED_STRUCT ASE_PIXEL_INDEXED {
74         ASE_BYTE idx;
75 };
76 typedef struct ASE_PIXEL_INDEXED ASE_PIXEL_INDEXED;
77 static_assert(sizeof(ASE_PIXEL_INDEXED) == 1, "ASE_PIXEL_INDEXED is wrong size");
78
79 struct PACKED_STRUCT ASE_Header {
80         ASE_DWORD fileSize;
81         ASE_WORD magicNumber; // 0xA5E0
82         ASE_WORD frames;
83         ASE_WORD width;
84         ASE_WORD height;
85         ASE_WORD depth;
86         ASE_DWORD flags;
87         ASE_WORD ___speed;
88         ASE_DWORD zero1;
89         ASE_DWORD zero2;
90         ASE_BYTE transparentColorIdx;
91         ASE_BYTE ___ignore[3];
92         ASE_WORD numColors;
93         ASE_BYTE pixelWidth;
94         ASE_BYTE pixelHeight;
95         ASE_SHORT gridX;
96         ASE_SHORT gridY;
97         ASE_WORD gridWidth;
98         ASE_WORD gridHeight;
99         ASE_BYTE ___reserved[84];
100 };
101 typedef struct ASE_Header ASE_Header;
102 static_assert(sizeof(ASE_Header) == 128, "ASE_Header is wrong size");
103
104 struct PACKED_STRUCT ASE_Frame {
105         ASE_DWORD frameBytes;
106         ASE_WORD magicNumber; // 0xF1FA
107         ASE_WORD ___numChunks;
108         ASE_WORD duration;
109         ASE_BYTE ___reserved[2];
110         ASE_DWORD numChunks;
111 };
112 typedef struct ASE_Frame ASE_Frame;
113 static_assert(sizeof(ASE_Frame) == 16, "ASE_Frame is wrong size");
114
115 struct PACKED_STRUCT ASE_Chunk {
116         ASE_DWORD chunkSize;
117         ASE_WORD type;
118         //ASE_BYTE data[];
119 };
120 typedef struct ASE_Chunk ASE_Chunk;
121 static_assert(sizeof(ASE_Chunk) == 6, "ASE_Chunk is wrong size");
122
123 enum ASE_ChunkTypes {
124         ASE_ChunkType_LayerChunk = 0x2004,
125         ASE_ChunkType_CelChunk = 0x2005,
126         ASE_ChunkType_CelExtraChunk = 0x2006,
127         ASE_ChunkType_ColorProfileChunk = 0x2007,
128         ASE_ChunkType_ExternalFilesChunk = 0x2008,
129         ASE_ChunkType_TagsChunk = 0x2018,
130         ASE_ChunkType_PaletteChunk = 0x2019,
131         ASE_ChunkType_UserDataChunk = 0x2020,
132         ASE_ChunkType_SliceChunk = 0x2022,
133         ASE_ChunkType_TilesetChunk = 0x2023,
134
135         // Ignore these, deprecated or unused...
136         ASE_ChunkType_OldPaletteChunk1 = 0x0004,
137         ASE_ChunkType_OldPaletteChunk2 = 0x0011,
138         ASE_ChunkType_MaskChunk = 0x2016,
139         ASE_ChunkType_PathChunk = 0x2017,
140 };
141
142 #if 0
143 struct PACKED_STRUCT ASE_Chunk_OldPalette {
144         ASE_WORD numPackets;
145         //BZAsepriteOldPalettePacket packets[];
146 };
147 typedef struct ASE_Chunk_OldPalette ASE_Chunk_OldPalette;
148 static_assert(sizeof(ASE_Chunk_OldPalette) == 2, "ASE_Chunk_OldPalette is wrong size");
149
150 struct PACKED_STRUCT ASE_Chunk_OldPalette_Packet {
151         ASE_BYTE skip;
152         ASE_BYTE numColors;
153         //BZAsepriteOldPaletteColor colors[];
154 };
155 typedef struct ASE_Chunk_OldPalette_Packet ASE_Chunk_OldPalette_Packet;
156 static_assert(sizeof(ASE_Chunk_OldPalette_Packet) == 2, "ASE_Chunk_OldPalette_Packet is wrong size");
157
158 struct PACKED_STRUCT ASE_Chunk_OldPalette_Packet_Color {
159         ASE_BYTE r;
160         ASE_BYTE g;
161         ASE_BYTE b;
162 };
163 typedef struct ASE_Chunk_OldPalette_Packet_Color ASE_Chunk_OldPalette_Packet_Color;
164 static_assert(sizeof(ASE_Chunk_OldPalette_Packet_Color) == 3, "ASE_Chunk_OldPalette_Packet_Color is wrong size");
165 #endif
166
167 struct PACKED_STRUCT ASE_Chunk_Layer {
168         ASE_WORD flags;
169         ASE_WORD layerType;
170         ASE_WORD childLevel;
171         ASE_WORD width;
172         ASE_WORD height;
173         ASE_WORD blendMode;
174         ASE_BYTE opacity;
175         ASE_BYTE ___reserved[3];
176         ASE_STRING name;
177 };
178 typedef struct ASE_Chunk_Layer ASE_Chunk_Layer;
179 static_assert(sizeof(ASE_Chunk_Layer) == 18, "ASE_Chunk_Layer is wrong size");
180
181 enum ASE_Chunk_Layer_BlendModes {
182         ASE_Chunk_Layer_BlendMode_Normal = 0, // Should always be this
183 };
184
185 struct PACKED_STRUCT ASE_Chunk_Layer_Tileset {
186         ASE_DWORD tilesetIndex;
187 };
188 typedef struct ASE_Chunk_Layer_Tileset ASE_Chunk_Layer_Tileset;
189 static_assert(sizeof(ASE_Chunk_Layer_Tileset) == 4, "ASE_Chunk_Layer_Tileset is wrong size");
190
191 struct PACKED_STRUCT ASE_Chunk_Cel {
192         ASE_WORD layerIndex;
193         ASE_SHORT positionX;
194         ASE_SHORT positionY;
195         ASE_BYTE opacity;
196         ASE_WORD celType;
197         ASE_SHORT zIndex;
198         ASE_BYTE ___reserved[5];
199 };
200 typedef struct ASE_Chunk_Cel ASE_Chunk_Cel;
201 static_assert(sizeof(ASE_Chunk_Cel) == 16, "ASE_Chunk_Cel is wrong size");
202
203 enum ASE_Chunk_Cel_CelTypes {
204         ASE_Chunk_Cel_CelType_RawImage = 0,
205         ASE_Chunk_Cel_CelType_LinkedCel = 1,
206         ASE_Chunk_Cel_CelType_CompressedImage = 2,
207         ASE_Chunk_Cel_CelType_CompressedTilemap = 3,
208 };
209
210 struct PACKED_STRUCT ASE_Chunk_Cel_RawImage {
211         ASE_WORD pixelWidth;
212         ASE_WORD pixelHeight;
213         //ASE_BYTE pixelData[];
214 };
215 typedef struct ASE_Chunk_Cel_RawImage ASE_Chunk_Cel_RawImage;
216 static_assert(sizeof(ASE_Chunk_Cel_RawImage) == 4, "ASE_Chunk_Cel_RawImage is wrong size");
217
218 struct PACKED_STRUCT ASE_Chunk_Cel_LinkedCel {
219         ASE_WORD framePosition;
220 };
221 typedef struct ASE_Chunk_Cel_LinkedCel ASE_Chunk_Cel_LinkedCel;
222 static_assert(sizeof(ASE_Chunk_Cel_LinkedCel) == 2, "ASE_Chunk_Cel_LinkedCel is wrong size");
223
224 struct PACKED_STRUCT ASE_Chunk_Cel_CompressedImage {
225         ASE_WORD pixelWidth;
226         ASE_WORD pixelHeight;
227         //ASE_BYTE pixelData[];
228 };
229 typedef struct ASE_Chunk_Cel_CompressedImage ASE_Chunk_Cel_CompressedImage;
230 static_assert(sizeof(ASE_Chunk_Cel_CompressedImage) == 4, "ASE_Chunk_Cel_CompressedImage is wrong size");
231
232 struct PACKED_STRUCT ASE_Chunk_Cel_CompressedTilemap {
233         ASE_WORD tileWidth;
234         ASE_WORD tileHeight;
235         ASE_WORD tileBits;
236         ASE_DWORD tileIDBitmap;
237         ASE_DWORD flipXBitmap;
238         ASE_DWORD flipYBitmap;
239         ASE_DWORD rotationBitmap;
240         ASE_BYTE ___reserved[10];
241         //ASE_DWORD tileData[];
242 };
243 typedef struct ASE_Chunk_Cel_CompressedTilemap ASE_Chunk_Cel_CompressedTilemap;
244 static_assert(sizeof(ASE_Chunk_Cel_CompressedTilemap) == 32, "ASE_Chunk_Cel_CompressedTilemap is wrong size");
245
246 struct PACKED_STRUCT ASE_Chunk_Cel_Extra {
247         ASE_DWORD flags;
248         ASE_FIXED positionX;
249         ASE_FIXED positionY;
250         ASE_FIXED width;
251         ASE_FIXED height;
252         ASE_BYTE ___reserved[16];
253 };
254 typedef struct ASE_Chunk_Cel_Extra ASE_Chunk_Cel_Extra;
255 static_assert(sizeof(ASE_Chunk_Cel_Extra) == 36, "ASE_Chunk_Cel_Extra is wrong size");
256
257 struct PACKED_STRUCT ASE_Chunk_ColorProfile {
258         ASE_WORD type;
259         ASE_WORD flags;
260         ASE_FIXED gamma;
261         ASE_BYTE ___reserved[8];
262 };
263 typedef struct ASE_Chunk_ColorProfile ASE_Chunk_ColorProfile;
264 static_assert(sizeof(ASE_Chunk_ColorProfile) == 16, "ASE_Chunk_ColorProfile is wrong size");
265
266 struct PACKED_STRUCT ASE_Chunk_ColorProfile_ICC {
267         ASE_DWORD length;
268         //ASE_BYTE data[];
269 };
270 typedef struct ASE_Chunk_ColorProfile_ICC ASE_Chunk_ColorProfile_ICC;
271 static_assert(sizeof(ASE_Chunk_ColorProfile_ICC) == 4, "ASE_Chunk_ColorProfile_ICC is wrong size");
272
273 struct PACKED_STRUCT ASE_Chunk_ExternalFiles {
274         ASE_DWORD entries;
275         ASE_BYTE ___reserved[8];
276 };
277 typedef struct ASE_Chunk_ExternalFiles ASE_Chunk_ExternalFiles;
278 static_assert(sizeof(ASE_Chunk_ExternalFiles) == 12, "ASE_Chunk_ExternalFiles is wrong size");
279
280 struct PACKED_STRUCT ASE_Chunk_ExternalFiles_Entry {
281         ASE_DWORD entryID;
282         ASE_BYTE type;
283         ASE_BYTE ___reserved[7];
284         ASE_STRING externalFilename;
285 };
286 typedef struct ASE_Chunk_ExternalFiles_Entry ASE_Chunk_ExternalFiles_Entry;
287 static_assert(sizeof(ASE_Chunk_ExternalFiles_Entry) == 14, "ASE_Chunk_ExternalFiles_Entry is wrong size");
288
289 enum ASE_Chunk_ExternalFiles_Entry_Types {
290         ASE_Chunk_ExternalFiles_EntryType_ExternalPalette = 0,
291         ASE_Chunk_ExternalFiles_EntryType_ExternalTileset = 1,
292         ASE_Chunk_ExternalFiles_EntryType_PropertiesExtension = 2,
293         ASE_Chunk_ExternalFiles_EntryType_TileManagement = 3,
294 };
295
296 struct PACKED_STRUCT ASE_Chunk_Mask {
297         ASE_SHORT positionX;
298         ASE_SHORT positionY;
299         ASE_WORD width;
300         ASE_WORD height;
301         ASE_BYTE ___reserved[8];
302         ASE_STRING maskName;
303 };
304 typedef struct ASE_Chunk_Mask ASE_Chunk_Mask;
305 static_assert(sizeof(ASE_Chunk_Mask) == 18, "ASE_Chunk_Mask is wrong size");
306
307 struct PACKED_STRUCT ASE_Chunk_Tags {
308         ASE_WORD numTags;
309         ASE_BYTE ___reserved[8];
310 };
311 typedef struct ASE_Chunk_Tags ASE_Chunk_Tags;
312 static_assert(sizeof(ASE_Chunk_Tags) == 10, "ASE_Chunk_Tags is wrong size");
313
314 struct PACKED_STRUCT ASE_Chunk_Tags_Tag {
315         ASE_WORD fromFrame;
316         ASE_WORD toFrame;
317         ASE_BYTE loopDirection;
318         ASE_WORD repeat;
319         ASE_BYTE ___reserved[6];
320         ASE_BYTE ___tagRBZ[3];
321         ASE_BYTE ___extra;
322         ASE_STRING tagName;
323 };
324 typedef struct ASE_Chunk_Tags_Tag ASE_Chunk_Tags_Tag;
325 static_assert(sizeof(ASE_Chunk_Tags_Tag) == 19, "ASE_Chunk_Tags_Tag is wrong size");
326
327 struct PACKED_STRUCT ASE_Chunk_Palette {
328         ASE_DWORD paletteSize;
329         ASE_DWORD firstColorIdx;
330         ASE_DWORD lastColorIdx;
331         ASE_BYTE ___reserved[8];
332 };
333 typedef struct ASE_Chunk_Palette ASE_Chunk_Palette;
334 static_assert(sizeof(ASE_Chunk_Palette) == 20, "ASE_Chunk_Palette is wrong size");
335
336 struct PACKED_STRUCT ASE_Chunk_Palette_Entry {
337         ASE_WORD flags;
338         ASE_BYTE r;
339         ASE_BYTE g;
340         ASE_BYTE b;
341         ASE_BYTE a;
342 };
343 typedef struct ASE_Chunk_Palette_Entry ASE_Chunk_Palette_Entry;
344 static_assert(sizeof(ASE_Chunk_Palette_Entry) == 6, "ASE_Chunk_Palette_Entry is wrong size");
345
346 struct PACKED_STRUCT ASE_Chunk_Palette_Entry_Name {
347         ASE_STRING name;
348 };
349 typedef struct ASE_Chunk_Palette_Entry_Name ASE_Chunk_Palette_Entry_Name;
350 static_assert(sizeof(ASE_Chunk_Palette_Entry_Name) == 2, "ASE_Chunk_Palette_Entry_Name is wrong size");
351
352 static bool readASEHeader(ASE_Header *headerOut, BZResourceID handle) {
353         bzAssert(headerOut != NULL);
354         bzResourcesReadBytes(handle, headerOut, sizeof(ASE_Header));
355         bzAssert(headerOut->magicNumber == 0xA5E0);
356         return true;
357 }
358
359 static bool readASEFrame(ASE_Frame *frameOut, ASE_DWORD *numChunksOut, BZResourceID handle) {
360         bzAssert(frameOut != NULL);
361         bzAssert(numChunksOut != NULL);
362
363         bzResourcesReadBytes(handle, frameOut, sizeof(ASE_Frame));
364
365         ASE_DWORD numChunks = frameOut->___numChunks;
366
367         if (numChunks == 0xFFFF) {
368                 numChunks = frameOut->numChunks;
369                 if (numChunks == 0) {
370                         numChunks = frameOut->___numChunks; // See docs.
371                 }
372         }
373
374         *numChunksOut = numChunks;
375
376         return true;
377 }
378
379 static void renderChunkCel(uint8_t *outData, ASE_SHORT *zBuffer, BZMemoryArenaID arena, BZResourceID handle, ASE_WORD layerIndex, const ASE_Header *header, size_t pixelDataSize, size_t frameIdx, size_t frameStartPositions[]) {
380         bzResourcesSeek(handle, frameStartPositions[frameIdx]);
381         
382         ASE_Frame frame;
383         ASE_DWORD numChunks;
384         readASEFrame(&frame, &numChunks, handle);
385
386         for (ASE_DWORD chunkIdx = 0; chunkIdx < numChunks; ++chunkIdx) {
387                 size_t chunkStartPosition = bzResourcesTell(handle);
388
389                 ASE_Chunk chunk;
390                 bzResourcesReadBytes(handle, &chunk, sizeof(chunk));
391
392                 if (chunk.type == ASE_ChunkType_CelChunk) {
393                         ASE_Chunk_Cel cel;
394                         bzResourcesReadBytes(handle, &cel, sizeof(cel));
395
396                         if (cel.layerIndex == layerIndex) {
397                                 if (cel.celType == ASE_Chunk_Cel_CelType_LinkedCel) {
398                                         ASE_Chunk_Cel_LinkedCel linkedCel;
399                                         bzResourcesReadBytes(handle, &linkedCel, sizeof(linkedCel));
400                                         renderChunkCel(outData, zBuffer, arena, handle, layerIndex, header, pixelDataSize, linkedCel.framePosition, frameStartPositions);
401                                 } else if (cel.celType == ASE_Chunk_Cel_CelType_CompressedImage) {
402                                         bzMemoryArenaPushWatermark(arena);
403
404                                         ASE_Chunk_Cel_CompressedImage image;
405                                         bzResourcesReadBytes(handle, &image, sizeof(image));
406
407                                         size_t compressedSize = chunk.chunkSize - sizeof(ASE_Chunk_Cel_CompressedImage) - sizeof(ASE_Chunk_Cel) - sizeof(ASE_Chunk);
408                                         int8_t *compressedData = bzMemoryAlloc(arena, compressedSize); // If we do this on the stack (alloca) the Playdate will explode!
409                                         bzResourcesReadBytes(handle, compressedData, compressedSize);
410
411                                         size_t imagePixelDataSize = image.pixelWidth * image.pixelHeight * pixelDataSize;
412                                         int8_t *imagePixelData = bzMemoryAlloc(arena, imagePixelDataSize);
413                                         stbi_zlib_decode_buffer(imagePixelData, imagePixelDataSize, compressedData, compressedSize);
414
415                                         for (size_t y = 0; y < image.pixelHeight; ++y) {
416                                                 for (size_t x = 0; x < image.pixelWidth; ++x) {
417                                                         size_t outX = cel.positionX + x;
418                                                         size_t outY = cel.positionY + y;
419
420                                                         //ASE_SHORT *currentZ = &zBuffer[outY * header->width + outX];
421                                                         //if (*currentZ > layerIndex) {
422                                                                 for (size_t i = 0; i < pixelDataSize; ++i) {
423                                                                         uint8_t pixelData = imagePixelData[(y * image.pixelWidth + x) * pixelDataSize + i];
424                                                                         if (pixelData > 0) { // FIXME, alpha...
425                                                                                 outData[outY * header->width + outX * pixelDataSize + i] = pixelData;
426                                                                         }
427                                                                 }
428                                                                 //*currentZ = layerIndex;//cel.zIndex;
429                                                         //}
430                                                 }
431                                         }
432
433                                         bzMemoryArenaPopWatermark(arena);
434                                 }
435
436                                 // Found what we needed to render...
437                                 break;
438                         }
439                 }
440
441                 bzResourcesSeek(handle, chunkStartPosition + chunk.chunkSize);
442         }                               
443 }
444
445 static bool renderCelsForLayer(uint8_t *outData, ASE_SHORT *zBuffer, BZMemoryArenaID arena, BZResourceID handle, ASE_WORD layerIndex, const ASE_Header *header, size_t pixelDataSize) {
446         size_t framesStartPosition = bzResourcesTell(handle);
447
448         // Layers are .. complicated, you only really need to read them for the first frame...
449         ASE_Frame firstFrame;
450         ASE_DWORD firstFrameChunks;
451         readASEFrame(&firstFrame, &firstFrameChunks, handle);
452
453         bool layerExists = false;
454         bool layerVisible = false;
455         for (ASE_DWORD chunkIdx = 0, layerCount = 0; chunkIdx < firstFrameChunks; ++chunkIdx) {
456                 size_t chunkStartPosition = bzResourcesTell(handle);
457
458                 ASE_Chunk chunk;
459                 bzResourcesReadBytes(handle, &chunk, sizeof(chunk));
460
461                 if (chunk.type == ASE_ChunkType_LayerChunk) {
462                         ASE_Chunk_Layer layer;
463                         bzResourcesReadBytes(handle, &layer, sizeof(layer));
464
465                         if (layerCount == layerIndex) {
466                                 layerExists = true;
467                                 layerVisible = (layer.flags & 1) > 0; // FIXME, named flag
468                                 break;
469                         } else {
470                                 layerCount++;
471                         }
472                 }// else {
473                         // Skip this chunk...
474                         bzResourcesSeek(handle, chunkStartPosition + chunk.chunkSize);
475                 //}
476         }
477
478         if (layerExists && layerVisible) {
479                 bzResourcesSeek(handle, framesStartPosition);
480
481                 size_t *frameStartPositions = alloca(header->frames * sizeof(size_t)); // FIXME, alloca -> tmp
482
483                 for (ASE_WORD frameIdx = 0; frameIdx < header->frames; ++frameIdx) {
484                         frameStartPositions[frameIdx] = bzResourcesTell(handle);
485
486                         ASE_Frame frame;
487                         ASE_DWORD numChunks;
488                         readASEFrame(&frame, &numChunks, handle);
489
490                         for (ASE_DWORD chunkIdx = 0; chunkIdx < numChunks; ++chunkIdx) {
491                                 size_t chunkStartPosition = bzResourcesTell(handle);
492
493                                 ASE_Chunk chunk;
494                                 bzResourcesReadBytes(handle, &chunk, sizeof(chunk));
495
496                                 if (chunk.type == ASE_ChunkType_CelChunk) {
497                                         uint8_t *frameOutData = &outData[frameIdx * header->height * header->width * pixelDataSize];
498                                         ASE_SHORT *frameZBuffer = NULL;//&zBuffer[frameIdx * header->height * header->width * sizeof(ASE_SHORT)];
499                                         renderChunkCel(frameOutData, frameZBuffer, arena, handle, layerIndex, header, pixelDataSize, frameIdx, frameStartPositions);
500                                 }
501
502                                 bzResourcesSeek(handle, chunkStartPosition + chunk.chunkSize);
503                         }
504                 }
505         }
506
507         return layerExists;
508 }
509
510 void *bzGfxLoadAsepriteImage(BZMemoryArenaID arena, size_t *framesOut, size_t *widthOut, size_t *heightOut, const char *identifierFmt, ...) {
511         bzMakeIdentifier(identifier, identifierFmt);
512
513         BZResourceID handle = bzResourcesOpenResource("sprite", "assets/sprites/%s.aseprite", identifier);
514         
515         ASE_Header header;
516         readASEHeader(&header, handle);
517
518         size_t pixelDataSize;
519         switch (header.depth) {
520                 case 8:
521                         pixelDataSize = sizeof(ASE_PIXEL_INDEXED);
522                         break;
523
524                 case 16:
525                         pixelDataSize = sizeof(ASE_PIXEL_GRAYSCALE);
526                         break;
527
528                 case 32:
529                         pixelDataSize = sizeof(ASE_PIXEL_RBGA);
530                         break;
531         
532                 default:
533                         bzAssertMessage(false, "Invalid image");
534                         break;
535         }
536
537         uint8_t *outData = (uint8_t *)bzMemoryAlloc(arena, header.frames * header.height * header.width * pixelDataSize); // Side by side loading needs to happen...
538         *framesOut = (size_t)header.frames;
539         *widthOut = (size_t)header.width;
540         *heightOut = (size_t)header.height;
541
542         //bzMemoryArenaPushWatermark(arena);
543         ASE_SHORT *zBuffer = NULL;//(ASE_SHORT *)bzMemoryAlloc(arena, header.frames * header.height * header.width * sizeof(ASE_SHORT));
544         //memset(zBuffer, 0, header.frames * header.height * header.width * sizeof(ASE_SHORT)); // FIXME
545
546         size_t startPosition = bzResourcesTell(handle);
547         for (ASE_WORD layerIndex = 0; ; ++layerIndex) {
548                 // Reset...
549                 bzResourcesSeek(handle, startPosition);
550
551                 bool rendered = renderCelsForLayer(outData, zBuffer, arena, handle, layerIndex, &header, pixelDataSize);
552                 if (rendered == false) {
553                         break;
554                 }
555         }
556
557         //bzMemoryArenaPopWatermark(arena);
558
559         return outData;
560 }
561
562 size_t bzGfxLoadAsepritePalette(uint32_t *colorsOut, size_t maxColors, const char *filename) {
563         BZResourceID handle = bzResourcesOpenResource("palette", "assets/palettes/%s.aseprite", filename);
564         
565         ASE_Header header;
566         readASEHeader(&header, handle);
567
568         size_t colorCount = 0;
569
570         for (ASE_WORD frame = 0; frame < header.frames; ++frame) {
571                 ASE_Frame frame;
572                 ASE_DWORD numChunks;
573                 readASEFrame(&frame, &numChunks, handle);
574
575                 for (ASE_DWORD chunk = 0; chunk < numChunks; ++chunk) {
576                         size_t chunkStartPosition = bzResourcesTell(handle);
577
578                         ASE_Chunk chunk;
579                         bzResourcesReadBytes(handle, &chunk, sizeof(chunk));
580
581                         switch (chunk.type) {
582                                 case ASE_ChunkType_PaletteChunk: {
583                                         ASE_Chunk_Palette palette;
584                                         bzResourcesReadBytes(handle, &palette, sizeof(palette));
585
586                                         if (palette.firstColorIdx < maxColors) {
587                                                 size_t lastIdx = bzMin(palette.lastColorIdx + 1, maxColors);
588                                                 for (size_t i = palette.firstColorIdx; i < lastIdx; ++i) {
589                                                         ASE_Chunk_Palette_Entry paletteEntry;
590                                                         bzResourcesReadBytes(handle, &paletteEntry, sizeof(paletteEntry));
591                                                         colorsOut[i] = bzPaletteMakeColor(paletteEntry.r, paletteEntry.g, paletteEntry.b);
592                                                 }
593                                                 colorCount = bzMax(colorCount, lastIdx);
594                                         }
595                                         
596                                         // FIXME
597                                         bzResourcesSeek(handle, chunkStartPosition + chunk.chunkSize);
598
599                                         break;
600                                 }
601                                 
602                                 default:
603                                         // Skip this chunk...
604                                         bzResourcesSeek(handle, chunkStartPosition + chunk.chunkSize);
605                                         break;
606                         }
607                 }
608         }
609         
610         bzResourcesCloseResource(handle);
611
612         return colorCount;
613 }