1 #include <bz/gfx/aseprite_internal.h>
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
14 #include <assert.h> // Static assertions
16 #define PACKED_STRUCT __attribute__((__packed__))
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;
29 struct PACKED_STRUCT ASE_STRING {
31 //ASE_BYTE characters[];
33 typedef struct ASE_STRING ASE_STRING;
34 static_assert(sizeof(ASE_STRING) == 2, "ASE_STRING is wrong size");
36 struct PACKED_STRUCT ASE_POINT {
40 typedef struct ASE_POINT ASE_POINT;
41 static_assert(sizeof(ASE_POINT) == 8, "ASE_POINT is wrong size");
43 struct PACKED_STRUCT ASE_SIZE {
47 typedef struct ASE_SIZE ASE_SIZE;
48 static_assert(sizeof(ASE_SIZE) == 8, "ASE_SIZE is wrong size");
50 struct PACKED_STRUCT ASE_RECT {
54 typedef struct ASE_RECT ASE_RECT;
55 static_assert(sizeof(ASE_RECT) == 16, "ASE_RECT is wrong size");
57 struct PACKED_STRUCT ASE_PIXEL_RBGA {
63 typedef struct ASE_PIXEL_RBGA ASE_PIXEL_RBGA;
64 static_assert(sizeof(ASE_PIXEL_RBGA) == 4, "ASE_PIXEL_RBGA is wrong size");
66 struct PACKED_STRUCT ASE_PIXEL_GRAYSCALE {
70 typedef struct ASE_PIXEL_GRAYSCALE ASE_PIXEL_GRAYSCALE;
71 static_assert(sizeof(ASE_PIXEL_GRAYSCALE) == 2, "ASE_PIXEL_GRAYSCALE is wrong size");
73 struct PACKED_STRUCT ASE_PIXEL_INDEXED {
76 typedef struct ASE_PIXEL_INDEXED ASE_PIXEL_INDEXED;
77 static_assert(sizeof(ASE_PIXEL_INDEXED) == 1, "ASE_PIXEL_INDEXED is wrong size");
79 struct PACKED_STRUCT ASE_Header {
81 ASE_WORD magicNumber; // 0xA5E0
90 ASE_BYTE transparentColorIdx;
91 ASE_BYTE ___ignore[3];
99 ASE_BYTE ___reserved[84];
101 typedef struct ASE_Header ASE_Header;
102 static_assert(sizeof(ASE_Header) == 128, "ASE_Header is wrong size");
104 struct PACKED_STRUCT ASE_Frame {
105 ASE_DWORD frameBytes;
106 ASE_WORD magicNumber; // 0xF1FA
107 ASE_WORD ___numChunks;
109 ASE_BYTE ___reserved[2];
112 typedef struct ASE_Frame ASE_Frame;
113 static_assert(sizeof(ASE_Frame) == 16, "ASE_Frame is wrong size");
115 struct PACKED_STRUCT ASE_Chunk {
120 typedef struct ASE_Chunk ASE_Chunk;
121 static_assert(sizeof(ASE_Chunk) == 6, "ASE_Chunk is wrong size");
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,
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,
143 struct PACKED_STRUCT ASE_Chunk_OldPalette {
145 //BZAsepriteOldPalettePacket packets[];
147 typedef struct ASE_Chunk_OldPalette ASE_Chunk_OldPalette;
148 static_assert(sizeof(ASE_Chunk_OldPalette) == 2, "ASE_Chunk_OldPalette is wrong size");
150 struct PACKED_STRUCT ASE_Chunk_OldPalette_Packet {
153 //BZAsepriteOldPaletteColor colors[];
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");
158 struct PACKED_STRUCT ASE_Chunk_OldPalette_Packet_Color {
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");
167 struct PACKED_STRUCT ASE_Chunk_Layer {
175 ASE_BYTE ___reserved[3];
178 typedef struct ASE_Chunk_Layer ASE_Chunk_Layer;
179 static_assert(sizeof(ASE_Chunk_Layer) == 18, "ASE_Chunk_Layer is wrong size");
181 enum ASE_Chunk_Layer_BlendModes {
182 ASE_Chunk_Layer_BlendMode_Normal = 0, // Should always be this
185 struct PACKED_STRUCT ASE_Chunk_Layer_Tileset {
186 ASE_DWORD tilesetIndex;
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");
191 struct PACKED_STRUCT ASE_Chunk_Cel {
198 ASE_BYTE ___reserved[5];
200 typedef struct ASE_Chunk_Cel ASE_Chunk_Cel;
201 static_assert(sizeof(ASE_Chunk_Cel) == 16, "ASE_Chunk_Cel is wrong size");
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,
210 struct PACKED_STRUCT ASE_Chunk_Cel_RawImage {
212 ASE_WORD pixelHeight;
213 //ASE_BYTE pixelData[];
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");
218 struct PACKED_STRUCT ASE_Chunk_Cel_LinkedCel {
219 ASE_WORD framePosition;
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");
224 struct PACKED_STRUCT ASE_Chunk_Cel_CompressedImage {
226 ASE_WORD pixelHeight;
227 //ASE_BYTE pixelData[];
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");
232 struct PACKED_STRUCT ASE_Chunk_Cel_CompressedTilemap {
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[];
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");
246 struct PACKED_STRUCT ASE_Chunk_Cel_Extra {
252 ASE_BYTE ___reserved[16];
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");
257 struct PACKED_STRUCT ASE_Chunk_ColorProfile {
261 ASE_BYTE ___reserved[8];
263 typedef struct ASE_Chunk_ColorProfile ASE_Chunk_ColorProfile;
264 static_assert(sizeof(ASE_Chunk_ColorProfile) == 16, "ASE_Chunk_ColorProfile is wrong size");
266 struct PACKED_STRUCT ASE_Chunk_ColorProfile_ICC {
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");
273 struct PACKED_STRUCT ASE_Chunk_ExternalFiles {
275 ASE_BYTE ___reserved[8];
277 typedef struct ASE_Chunk_ExternalFiles ASE_Chunk_ExternalFiles;
278 static_assert(sizeof(ASE_Chunk_ExternalFiles) == 12, "ASE_Chunk_ExternalFiles is wrong size");
280 struct PACKED_STRUCT ASE_Chunk_ExternalFiles_Entry {
283 ASE_BYTE ___reserved[7];
284 ASE_STRING externalFilename;
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");
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,
296 struct PACKED_STRUCT ASE_Chunk_Mask {
301 ASE_BYTE ___reserved[8];
304 typedef struct ASE_Chunk_Mask ASE_Chunk_Mask;
305 static_assert(sizeof(ASE_Chunk_Mask) == 18, "ASE_Chunk_Mask is wrong size");
307 struct PACKED_STRUCT ASE_Chunk_Tags {
309 ASE_BYTE ___reserved[8];
311 typedef struct ASE_Chunk_Tags ASE_Chunk_Tags;
312 static_assert(sizeof(ASE_Chunk_Tags) == 10, "ASE_Chunk_Tags is wrong size");
314 struct PACKED_STRUCT ASE_Chunk_Tags_Tag {
317 ASE_BYTE loopDirection;
319 ASE_BYTE ___reserved[6];
320 ASE_BYTE ___tagRBZ[3];
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");
327 struct PACKED_STRUCT ASE_Chunk_Palette {
328 ASE_DWORD paletteSize;
329 ASE_DWORD firstColorIdx;
330 ASE_DWORD lastColorIdx;
331 ASE_BYTE ___reserved[8];
333 typedef struct ASE_Chunk_Palette ASE_Chunk_Palette;
334 static_assert(sizeof(ASE_Chunk_Palette) == 20, "ASE_Chunk_Palette is wrong size");
336 struct PACKED_STRUCT ASE_Chunk_Palette_Entry {
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");
346 struct PACKED_STRUCT ASE_Chunk_Palette_Entry_Name {
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");
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);
359 static bool readASEFrame(ASE_Frame *frameOut, ASE_DWORD *numChunksOut, BZResourceID handle) {
360 bzAssert(frameOut != NULL);
361 bzAssert(numChunksOut != NULL);
363 bzResourcesReadBytes(handle, frameOut, sizeof(ASE_Frame));
365 ASE_DWORD numChunks = frameOut->___numChunks;
367 if (numChunks == 0xFFFF) {
368 numChunks = frameOut->numChunks;
369 if (numChunks == 0) {
370 numChunks = frameOut->___numChunks; // See docs.
374 *numChunksOut = numChunks;
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]);
384 readASEFrame(&frame, &numChunks, handle);
386 for (ASE_DWORD chunkIdx = 0; chunkIdx < numChunks; ++chunkIdx) {
387 size_t chunkStartPosition = bzResourcesTell(handle);
390 bzResourcesReadBytes(handle, &chunk, sizeof(chunk));
392 if (chunk.type == ASE_ChunkType_CelChunk) {
394 bzResourcesReadBytes(handle, &cel, sizeof(cel));
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);
404 ASE_Chunk_Cel_CompressedImage image;
405 bzResourcesReadBytes(handle, &image, sizeof(image));
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);
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);
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;
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;
428 //*currentZ = layerIndex;//cel.zIndex;
433 bzMemoryArenaPopWatermark(arena);
436 // Found what we needed to render...
441 bzResourcesSeek(handle, chunkStartPosition + chunk.chunkSize);
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);
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);
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);
459 bzResourcesReadBytes(handle, &chunk, sizeof(chunk));
461 if (chunk.type == ASE_ChunkType_LayerChunk) {
462 ASE_Chunk_Layer layer;
463 bzResourcesReadBytes(handle, &layer, sizeof(layer));
465 if (layerCount == layerIndex) {
467 layerVisible = (layer.flags & 1) > 0; // FIXME, named flag
473 // Skip this chunk...
474 bzResourcesSeek(handle, chunkStartPosition + chunk.chunkSize);
478 if (layerExists && layerVisible) {
479 bzResourcesSeek(handle, framesStartPosition);
481 size_t *frameStartPositions = alloca(header->frames * sizeof(size_t)); // FIXME, alloca -> tmp
483 for (ASE_WORD frameIdx = 0; frameIdx < header->frames; ++frameIdx) {
484 frameStartPositions[frameIdx] = bzResourcesTell(handle);
488 readASEFrame(&frame, &numChunks, handle);
490 for (ASE_DWORD chunkIdx = 0; chunkIdx < numChunks; ++chunkIdx) {
491 size_t chunkStartPosition = bzResourcesTell(handle);
494 bzResourcesReadBytes(handle, &chunk, sizeof(chunk));
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);
502 bzResourcesSeek(handle, chunkStartPosition + chunk.chunkSize);
510 void *bzGfxLoadAsepriteImage(BZMemoryArenaID arena, size_t *framesOut, size_t *widthOut, size_t *heightOut, const char *identifierFmt, ...) {
511 bzMakeIdentifier(identifier, identifierFmt);
513 BZResourceID handle = bzResourcesOpenResource("sprite", "assets/sprites/%s.aseprite", identifier);
516 readASEHeader(&header, handle);
518 size_t pixelDataSize;
519 switch (header.depth) {
521 pixelDataSize = sizeof(ASE_PIXEL_INDEXED);
525 pixelDataSize = sizeof(ASE_PIXEL_GRAYSCALE);
529 pixelDataSize = sizeof(ASE_PIXEL_RBGA);
533 bzAssertMessage(false, "Invalid image");
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;
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
546 size_t startPosition = bzResourcesTell(handle);
547 for (ASE_WORD layerIndex = 0; ; ++layerIndex) {
549 bzResourcesSeek(handle, startPosition);
551 bool rendered = renderCelsForLayer(outData, zBuffer, arena, handle, layerIndex, &header, pixelDataSize);
552 if (rendered == false) {
557 //bzMemoryArenaPopWatermark(arena);
562 size_t bzGfxLoadAsepritePalette(uint32_t *colorsOut, size_t maxColors, const char *filename) {
563 BZResourceID handle = bzResourcesOpenResource("palette", "assets/palettes/%s.aseprite", filename);
566 readASEHeader(&header, handle);
568 size_t colorCount = 0;
570 for (ASE_WORD frame = 0; frame < header.frames; ++frame) {
573 readASEFrame(&frame, &numChunks, handle);
575 for (ASE_DWORD chunk = 0; chunk < numChunks; ++chunk) {
576 size_t chunkStartPosition = bzResourcesTell(handle);
579 bzResourcesReadBytes(handle, &chunk, sizeof(chunk));
581 switch (chunk.type) {
582 case ASE_ChunkType_PaletteChunk: {
583 ASE_Chunk_Palette palette;
584 bzResourcesReadBytes(handle, &palette, sizeof(palette));
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);
593 colorCount = bzMax(colorCount, lastIdx);
597 bzResourcesSeek(handle, chunkStartPosition + chunk.chunkSize);
603 // Skip this chunk...
604 bzResourcesSeek(handle, chunkStartPosition + chunk.chunkSize);
610 bzResourcesCloseResource(handle);