]> git.bts.cx Git - benzene.git/blob - src/bz/gfx/aseprite.c
046ca246488f378ba191537b92f8ed693c254d37
[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 void *bzGfxLoadAsepriteImage(BZMemoryArenaID arena, size_t *framesOut, size_t *widthOut, size_t *heightOut, const char *identifierFmt, ...) {
380         bzMakeIdentifier(identifier, identifierFmt);
381
382         BZResourceID handle = bzResourcesOpenResource("sprite", "assets/sprites/%s.aseprite", identifier);
383         //PHYSFS_sint64 length = PHYSFS_fileLength(handle);
384         //void *data = alloca(length);
385         //PHYSFS_readBytes(handle, data, length);
386         
387         ASE_Header header;
388         readASEHeader(&header, handle);
389
390         size_t pixelDataSize;
391         switch (header.depth) {
392                 case 8:
393                         pixelDataSize = sizeof(ASE_PIXEL_INDEXED);
394                         break;
395
396                 case 16:
397                         pixelDataSize = sizeof(ASE_PIXEL_GRAYSCALE);
398                         break;
399
400                 case 32:
401                         pixelDataSize = sizeof(ASE_PIXEL_RBGA);
402                         break;
403         
404                 default:
405                         assert(false);//, "Invalid image");
406                         break;
407         }
408
409         uint8_t *outData = (uint8_t *)bzMemoryAlloc(arena, header.frames * header.width * header.height * pixelDataSize); // Side by side loading needs to happen...
410         *framesOut = (size_t)header.frames;
411         *widthOut = (size_t)header.width;
412         *heightOut = (size_t)header.height;
413
414         bzMemoryArenaPushWatermark(arena);
415         size_t zBufferSize = header.width * header.height * sizeof(ASE_SHORT);
416         ASE_SHORT *zBuffer = (ASE_SHORT *)bzMemoryAlloc(arena, zBufferSize);
417
418         size_t frameImageSize = header.width * header.height * pixelDataSize;
419
420         for (ASE_WORD frameIdx = 0; frameIdx < header.frames; ++frameIdx) {
421                 ASE_Frame frame;
422                 ASE_DWORD numChunks = 0;
423                 readASEFrame(&frame, &numChunks, handle);
424                 bzLog("FRAME %d: %d chunks", frameIdx, numChunks);
425
426                 memset(zBuffer, 0, zBufferSize);
427
428                 bool layerVisible[16];
429                 size_t nextLayer = 0;
430
431                 size_t layerStartOffset[16];
432
433                 for (ASE_DWORD chunk = 0; chunk < numChunks; ++chunk) {
434                         size_t chunkStartPosition = bzResourcesTell(handle);
435
436                         ASE_Chunk chunk;
437                         bzResourcesReadBytes(handle, &chunk, sizeof(chunk));
438
439                         switch (chunk.type) {
440                                 case ASE_ChunkType_LayerChunk: {
441                                         bzLog("LAYER %d: %d chunks", frameIdx, numChunks);
442
443
444                                         ASE_Chunk_Layer layer;
445                                         bzResourcesReadBytes(handle, &layer, sizeof(layer));
446
447                                         layerVisible[nextLayer] = (layer.flags & 1) > 0;
448                                         ++nextLayer;
449
450                                         bzResourcesSeek(handle, chunkStartPosition + chunk.chunkSize);
451                                         break;
452                                 }
453
454                                 case ASE_ChunkType_CelChunk: {
455                                         bzLog("CELL %d: %d chunks", frameIdx, numChunks);
456                                         ASE_Chunk_Cel cel;
457                                         bzResourcesReadBytes(handle, &cel, sizeof(cel));
458
459 /////                                   ASE_Chunk_Cel_CelType_LinkedCel aaaahhh
460
461                                         if (layerVisible[cel.layerIndex] && cel.celType == ASE_Chunk_Cel_CelType_CompressedImage) {
462                                                 bzMemoryArenaPushWatermark(arena);
463
464                                                 ASE_Chunk_Cel_CompressedImage image;
465                                                 bzResourcesReadBytes(handle, &image, sizeof(image));
466
467                                                 size_t compressedSize = chunk.chunkSize - sizeof(ASE_Chunk_Cel_CompressedImage) - sizeof(ASE_Chunk_Cel) - sizeof(ASE_Chunk);
468                                                 int8_t *compressedData = bzMemoryAlloc(arena, compressedSize); // If we do this on the stack (alloca) the Playdate will explode!
469                                                 bzResourcesReadBytes(handle, compressedData, compressedSize);
470
471                                                 size_t imagePixelDataSize = image.pixelWidth * image.pixelHeight * pixelDataSize;
472                                                 int8_t *imagePixelData = bzMemoryAlloc(arena, imagePixelDataSize);
473                                                 stbi_zlib_decode_buffer(imagePixelData, imagePixelDataSize, compressedData, compressedSize);
474
475                                                 for (size_t y = 0; y < image.pixelHeight; ++y) {
476                                                         for (size_t x = 0; x < image.pixelWidth; ++x) {
477                                                                 size_t outX = cel.positionX + x;
478                                                                 size_t outY = cel.positionY + y;
479
480                                                                 ASE_SHORT *currentZ = &zBuffer[outY * header.width + outX];
481
482                                                                 if (*currentZ <= cel.zIndex) {
483                                                                         for (size_t i = 0; i < pixelDataSize; ++i) {
484                                                                                 uint8_t pixelData = imagePixelData[(y * image.pixelWidth + x) * pixelDataSize + i];
485                                                                                 if (pixelData > 0) { // FIXME, alpha...
486                                                                                         outData[frameIdx * frameImageSize + (outY * header.width + outX) * pixelDataSize + i] = pixelData;
487                                                                                 }
488                                                                         }
489                                                                         *currentZ = cel.zIndex;
490                                                                 }
491                                                         }
492                                                 }
493
494                                                 bzMemoryArenaPopWatermark(arena);
495                                         } else {
496                                                 // Skip this chunk...
497                                                 bzResourcesSeek(handle, chunkStartPosition + chunk.chunkSize);
498                                         }
499
500                                         break;
501                                 }
502                                 
503                                 default:
504                                         // Skip this chunk...
505                                         bzResourcesSeek(handle, chunkStartPosition + chunk.chunkSize);
506                                         break;
507                         }
508                 }
509         }
510         
511         bzMemoryArenaPopWatermark(arena);
512
513         bzResourcesCloseResource(handle);
514
515         return outData;
516 }
517
518 size_t bzGfxLoadAsepritePalette(uint32_t *colorsOut, size_t maxColors, const char *filename) {
519         BZResourceID handle = bzResourcesOpenResource("palette", "assets/palettes/%s", filename);
520         
521         ASE_Header header;
522         readASEHeader(&header, handle);
523
524         size_t colorCount = 0;
525
526         for (ASE_WORD frame = 0; frame < header.frames; ++frame) {
527                 ASE_Frame frame;
528                 bzResourcesReadBytes(handle, &frame, sizeof(frame));
529
530                 ASE_DWORD numChunks = frame.___numChunks;
531
532                 if (numChunks == 0xFFFF) {
533                         numChunks = frame.numChunks;
534                         if (numChunks == 0) {
535                                 numChunks = frame.___numChunks; // See docs.
536                         }
537                 }
538
539                 for (ASE_DWORD chunk = 0; chunk < numChunks; ++chunk) {
540                         size_t chunkStartPosition = bzResourcesTell(handle);
541
542                         ASE_Chunk chunk;
543                         bzResourcesReadBytes(handle, &chunk, sizeof(chunk));
544
545                         switch (chunk.type) {
546                                 case ASE_ChunkType_PaletteChunk: {
547                                         ASE_Chunk_Palette palette;
548                                         bzResourcesReadBytes(handle, &palette, sizeof(palette));
549
550                                         if (palette.firstColorIdx < maxColors) {
551                                                 size_t lastIdx = bzMin(palette.lastColorIdx + 1, maxColors);
552                                                 for (size_t i = palette.firstColorIdx; i < lastIdx; ++i) {
553                                                         ASE_Chunk_Palette_Entry paletteEntry;
554                                                         bzResourcesReadBytes(handle, &paletteEntry, sizeof(paletteEntry));
555                                                         colorsOut[i] = bzPaletteMakeColor(paletteEntry.r, paletteEntry.g, paletteEntry.b);
556                                                 }
557                                                 colorCount = bzMax(colorCount, lastIdx);
558                                         }
559                                         
560                                         // FIXME
561                                         bzResourcesSeek(handle, chunkStartPosition + chunk.chunkSize);
562
563                                         break;
564                                 }
565                                 
566                                 default:
567                                         // Skip this chunk...
568                                         bzResourcesSeek(handle, chunkStartPosition + chunk.chunkSize);
569                                         break;
570                         }
571                 }
572         }
573         
574         bzResourcesCloseResource(handle);
575
576         return colorCount;
577 }