]> git.bts.cx Git - benzene.git/blobdiff - src/bz/gfx/aseprite.c
Sprites
[benzene.git] / src / bz / gfx / aseprite.c
index 69dc446fbef8b1bbe2b2b178501ed3fde6737130..046ca246488f378ba191537b92f8ed693c254d37 100644 (file)
@@ -1,12 +1,15 @@
 #include <bz/gfx/aseprite_internal.h>
 
+#include <bz/debug/assert.h>
+#include <bz/debug/log.h>
 #include <bz/math/math.h>
 #include <bz/memory/allocator.h>
 #include <bz/renderer/palette_internal.h>
 #include <bz/resources/resource.h>
 #include <bz/types/identifier_internal.h>
-#include <stdio.h> // Apparently stb_image.h need this??
+#include <stdio.h> // Apparently stb_image.h needs this??
 #include <stb_image.h>
+#include <string.h> // memset
 
 #include <assert.h> // Static assertions
 
@@ -23,8 +26,6 @@ typedef double   ASE_DOUBLE;
 typedef uint64_t ASE_QWORD;
 typedef int64_t  ASE_LONG64;
 
-
-
 struct PACKED_STRUCT ASE_STRING {
        ASE_WORD length;
        //ASE_BYTE characters[];
@@ -53,14 +54,14 @@ struct PACKED_STRUCT ASE_RECT {
 typedef struct ASE_RECT ASE_RECT;
 static_assert(sizeof(ASE_RECT) == 16, "ASE_RECT is wrong size");
 
-struct PACKED_STRUCT ASE_PIXEL_RBZA {
+struct PACKED_STRUCT ASE_PIXEL_RBGA {
        ASE_BYTE r;
        ASE_BYTE g;
        ASE_BYTE b;
        ASE_BYTE a;
 };
-typedef struct ASE_PIXEL_RBZA ASE_PIXEL_RBZA;
-static_assert(sizeof(ASE_PIXEL_RBZA) == 4, "ASE_PIXEL_RBZA is wrong size");
+typedef struct ASE_PIXEL_RBGA ASE_PIXEL_RBGA;
+static_assert(sizeof(ASE_PIXEL_RBGA) == 4, "ASE_PIXEL_RBGA is wrong size");
 
 struct PACKED_STRUCT ASE_PIXEL_GRAYSCALE {
        ASE_BYTE v;
@@ -119,6 +120,25 @@ struct PACKED_STRUCT ASE_Chunk {
 typedef struct ASE_Chunk ASE_Chunk;
 static_assert(sizeof(ASE_Chunk) == 6, "ASE_Chunk is wrong size");
 
+enum ASE_ChunkTypes {
+       ASE_ChunkType_LayerChunk = 0x2004,
+       ASE_ChunkType_CelChunk = 0x2005,
+       ASE_ChunkType_CelExtraChunk = 0x2006,
+       ASE_ChunkType_ColorProfileChunk = 0x2007,
+       ASE_ChunkType_ExternalFilesChunk = 0x2008,
+       ASE_ChunkType_TagsChunk = 0x2018,
+       ASE_ChunkType_PaletteChunk = 0x2019,
+       ASE_ChunkType_UserDataChunk = 0x2020,
+       ASE_ChunkType_SliceChunk = 0x2022,
+       ASE_ChunkType_TilesetChunk = 0x2023,
+
+       // Ignore these, deprecated or unused...
+       ASE_ChunkType_OldPaletteChunk1 = 0x0004,
+       ASE_ChunkType_OldPaletteChunk2 = 0x0011,
+       ASE_ChunkType_MaskChunk = 0x2016,
+       ASE_ChunkType_PathChunk = 0x2017,
+};
+
 #if 0
 struct PACKED_STRUCT ASE_Chunk_OldPalette {
        ASE_WORD numPackets;
@@ -158,6 +178,10 @@ struct PACKED_STRUCT ASE_Chunk_Layer {
 typedef struct ASE_Chunk_Layer ASE_Chunk_Layer;
 static_assert(sizeof(ASE_Chunk_Layer) == 18, "ASE_Chunk_Layer is wrong size");
 
+enum ASE_Chunk_Layer_BlendModes {
+       ASE_Chunk_Layer_BlendMode_Normal = 0, // Should always be this
+};
+
 struct PACKED_STRUCT ASE_Chunk_Layer_Tileset {
        ASE_DWORD tilesetIndex;
 };
@@ -176,6 +200,13 @@ struct PACKED_STRUCT ASE_Chunk_Cel {
 typedef struct ASE_Chunk_Cel ASE_Chunk_Cel;
 static_assert(sizeof(ASE_Chunk_Cel) == 16, "ASE_Chunk_Cel is wrong size");
 
+enum ASE_Chunk_Cel_CelTypes {
+       ASE_Chunk_Cel_CelType_RawImage = 0,
+       ASE_Chunk_Cel_CelType_LinkedCel = 1,
+       ASE_Chunk_Cel_CelType_CompressedImage = 2,
+       ASE_Chunk_Cel_CelType_CompressedTilemap = 3,
+};
+
 struct PACKED_STRUCT ASE_Chunk_Cel_RawImage {
        ASE_WORD pixelWidth;
        ASE_WORD pixelHeight;
@@ -255,6 +286,13 @@ struct PACKED_STRUCT ASE_Chunk_ExternalFiles_Entry {
 typedef struct ASE_Chunk_ExternalFiles_Entry ASE_Chunk_ExternalFiles_Entry;
 static_assert(sizeof(ASE_Chunk_ExternalFiles_Entry) == 14, "ASE_Chunk_ExternalFiles_Entry is wrong size");
 
+enum ASE_Chunk_ExternalFiles_Entry_Types {
+       ASE_Chunk_ExternalFiles_EntryType_ExternalPalette = 0,
+       ASE_Chunk_ExternalFiles_EntryType_ExternalTileset = 1,
+       ASE_Chunk_ExternalFiles_EntryType_PropertiesExtension = 2,
+       ASE_Chunk_ExternalFiles_EntryType_TileManagement = 3,
+};
+
 struct PACKED_STRUCT ASE_Chunk_Mask {
        ASE_SHORT positionX;
        ASE_SHORT positionY;
@@ -311,7 +349,34 @@ struct PACKED_STRUCT ASE_Chunk_Palette_Entry_Name {
 typedef struct ASE_Chunk_Palette_Entry_Name ASE_Chunk_Palette_Entry_Name;
 static_assert(sizeof(ASE_Chunk_Palette_Entry_Name) == 2, "ASE_Chunk_Palette_Entry_Name is wrong size");
 
-void *bzGfxLoadAsepriteImage(BZMemoryArenaID arena, size_t *widthOut, size_t *heightOut, const char *identifierFmt, ...) {
+static bool readASEHeader(ASE_Header *headerOut, BZResourceID handle) {
+       bzAssert(headerOut != NULL);
+       bzResourcesReadBytes(handle, headerOut, sizeof(ASE_Header));
+       bzAssert(headerOut->magicNumber == 0xA5E0);
+       return true;
+}
+
+static bool readASEFrame(ASE_Frame *frameOut, ASE_DWORD *numChunksOut, BZResourceID handle) {
+       bzAssert(frameOut != NULL);
+       bzAssert(numChunksOut != NULL);
+
+       bzResourcesReadBytes(handle, frameOut, sizeof(ASE_Frame));
+
+       ASE_DWORD numChunks = frameOut->___numChunks;
+
+       if (numChunks == 0xFFFF) {
+               numChunks = frameOut->numChunks;
+               if (numChunks == 0) {
+                       numChunks = frameOut->___numChunks; // See docs.
+               }
+       }
+
+       *numChunksOut = numChunks;
+
+       return true;
+}
+
+void *bzGfxLoadAsepriteImage(BZMemoryArenaID arena, size_t *framesOut, size_t *widthOut, size_t *heightOut, const char *identifierFmt, ...) {
        bzMakeIdentifier(identifier, identifierFmt);
 
        BZResourceID handle = bzResourcesOpenResource("sprite", "assets/sprites/%s.aseprite", identifier);
@@ -320,9 +385,7 @@ void *bzGfxLoadAsepriteImage(BZMemoryArenaID arena, size_t *widthOut, size_t *he
        //PHYSFS_readBytes(handle, data, length);
        
        ASE_Header header;
-       bzResourcesReadBytes(handle, &header, sizeof(header));
-       
-       bzAssert(header.magicNumber == 0xA5E0);
+       readASEHeader(&header, handle);
 
        size_t pixelDataSize;
        switch (header.depth) {
@@ -335,7 +398,7 @@ void *bzGfxLoadAsepriteImage(BZMemoryArenaID arena, size_t *widthOut, size_t *he
                        break;
 
                case 32:
-                       pixelDataSize = sizeof(ASE_PIXEL_RBZA);
+                       pixelDataSize = sizeof(ASE_PIXEL_RBGA);
                        break;
        
                default:
@@ -343,32 +406,65 @@ void *bzGfxLoadAsepriteImage(BZMemoryArenaID arena, size_t *widthOut, size_t *he
                        break;
        }
 
-       uint8_t *outData = (uint8_t *)bzMemoryAlloc(arena, header.width * header.height * pixelDataSize);
+       uint8_t *outData = (uint8_t *)bzMemoryAlloc(arena, header.frames * header.width * header.height * pixelDataSize); // Side by side loading needs to happen...
+       *framesOut = (size_t)header.frames;
        *widthOut = (size_t)header.width;
        *heightOut = (size_t)header.height;
 
-       for (ASE_WORD frame = 0; frame < header.frames; ++frame) {
+       bzMemoryArenaPushWatermark(arena);
+       size_t zBufferSize = header.width * header.height * sizeof(ASE_SHORT);
+       ASE_SHORT *zBuffer = (ASE_SHORT *)bzMemoryAlloc(arena, zBufferSize);
+
+       size_t frameImageSize = header.width * header.height * pixelDataSize;
+
+       for (ASE_WORD frameIdx = 0; frameIdx < header.frames; ++frameIdx) {
                ASE_Frame frame;
-               bzResourcesReadBytes(handle, &frame, sizeof(frame));
+               ASE_DWORD numChunks = 0;
+               readASEFrame(&frame, &numChunks, handle);
+               bzLog("FRAME %d: %d chunks", frameIdx, numChunks);
+
+               memset(zBuffer, 0, zBufferSize);
+
+               bool layerVisible[16];
+               size_t nextLayer = 0;
+
+               size_t layerStartOffset[16];
 
-               for (ASE_DWORD chunk = 0; chunk < frame.numChunks; ++chunk) {
+               for (ASE_DWORD chunk = 0; chunk < numChunks; ++chunk) {
                        size_t chunkStartPosition = bzResourcesTell(handle);
 
                        ASE_Chunk chunk;
                        bzResourcesReadBytes(handle, &chunk, sizeof(chunk));
 
                        switch (chunk.type) {
-                               case 0x2005: {
+                               case ASE_ChunkType_LayerChunk: {
+                                       bzLog("LAYER %d: %d chunks", frameIdx, numChunks);
+
+
+                                       ASE_Chunk_Layer layer;
+                                       bzResourcesReadBytes(handle, &layer, sizeof(layer));
+
+                                       layerVisible[nextLayer] = (layer.flags & 1) > 0;
+                                       ++nextLayer;
+
+                                       bzResourcesSeek(handle, chunkStartPosition + chunk.chunkSize);
+                                       break;
+                               }
+
+                               case ASE_ChunkType_CelChunk: {
+                                       bzLog("CELL %d: %d chunks", frameIdx, numChunks);
                                        ASE_Chunk_Cel cel;
                                        bzResourcesReadBytes(handle, &cel, sizeof(cel));
 
-                                       if (cel.celType == 2) {
+/////                                  ASE_Chunk_Cel_CelType_LinkedCel aaaahhh
+
+                                       if (layerVisible[cel.layerIndex] && cel.celType == ASE_Chunk_Cel_CelType_CompressedImage) {
                                                bzMemoryArenaPushWatermark(arena);
 
                                                ASE_Chunk_Cel_CompressedImage image;
                                                bzResourcesReadBytes(handle, &image, sizeof(image));
 
-                                               size_t compressedSize = chunk.chunkSize - sizeof(ASE_Chunk_Cel_CompressedImage) - sizeof(ASE_Chunk);
+                                               size_t compressedSize = chunk.chunkSize - sizeof(ASE_Chunk_Cel_CompressedImage) - sizeof(ASE_Chunk_Cel) - sizeof(ASE_Chunk);
                                                int8_t *compressedData = bzMemoryAlloc(arena, compressedSize); // If we do this on the stack (alloca) the Playdate will explode!
                                                bzResourcesReadBytes(handle, compressedData, compressedSize);
 
@@ -381,16 +477,21 @@ void *bzGfxLoadAsepriteImage(BZMemoryArenaID arena, size_t *widthOut, size_t *he
                                                                size_t outX = cel.positionX + x;
                                                                size_t outY = cel.positionY + y;
 
-                                                               for (size_t i = 0; i < pixelDataSize; ++i) {
-                                                                       outData[(outY * header.width + outX) * pixelDataSize + i] = imagePixelData[(y * image.pixelWidth + x) * pixelDataSize + i];
+                                                               ASE_SHORT *currentZ = &zBuffer[outY * header.width + outX];
+
+                                                               if (*currentZ <= cel.zIndex) {
+                                                                       for (size_t i = 0; i < pixelDataSize; ++i) {
+                                                                               uint8_t pixelData = imagePixelData[(y * image.pixelWidth + x) * pixelDataSize + i];
+                                                                               if (pixelData > 0) { // FIXME, alpha...
+                                                                                       outData[frameIdx * frameImageSize + (outY * header.width + outX) * pixelDataSize + i] = pixelData;
+                                                                               }
+                                                                       }
+                                                                       *currentZ = cel.zIndex;
                                                                }
                                                        }
                                                }
 
                                                bzMemoryArenaPopWatermark(arena);
-
-                                               // FIXME
-                                               //bzResourcesSeek(handle, chunkStartPosition + chunk.chunkSize);
                                        } else {
                                                // Skip this chunk...
                                                bzResourcesSeek(handle, chunkStartPosition + chunk.chunkSize);
@@ -407,6 +508,8 @@ void *bzGfxLoadAsepriteImage(BZMemoryArenaID arena, size_t *widthOut, size_t *he
                }
        }
        
+       bzMemoryArenaPopWatermark(arena);
+
        bzResourcesCloseResource(handle);
 
        return outData;
@@ -416,9 +519,7 @@ size_t bzGfxLoadAsepritePalette(uint32_t *colorsOut, size_t maxColors, const cha
        BZResourceID handle = bzResourcesOpenResource("palette", "assets/palettes/%s", filename);
        
        ASE_Header header;
-       bzResourcesReadBytes(handle, &header, sizeof(header));
-       
-       bzAssert(header.magicNumber == 0xA5E0);
+       readASEHeader(&header, handle);
 
        size_t colorCount = 0;
 
@@ -426,14 +527,23 @@ size_t bzGfxLoadAsepritePalette(uint32_t *colorsOut, size_t maxColors, const cha
                ASE_Frame frame;
                bzResourcesReadBytes(handle, &frame, sizeof(frame));
 
-               for (ASE_DWORD chunk = 0; chunk < frame.numChunks; ++chunk) {
+               ASE_DWORD numChunks = frame.___numChunks;
+
+               if (numChunks == 0xFFFF) {
+                       numChunks = frame.numChunks;
+                       if (numChunks == 0) {
+                               numChunks = frame.___numChunks; // See docs.
+                       }
+               }
+
+               for (ASE_DWORD chunk = 0; chunk < numChunks; ++chunk) {
                        size_t chunkStartPosition = bzResourcesTell(handle);
 
                        ASE_Chunk chunk;
                        bzResourcesReadBytes(handle, &chunk, sizeof(chunk));
 
                        switch (chunk.type) {
-                               case 0x2019: {
+                               case ASE_ChunkType_PaletteChunk: {
                                        ASE_Chunk_Palette palette;
                                        bzResourcesReadBytes(handle, &palette, sizeof(palette));