]> git.bts.cx Git - benzene.git/blobdiff - src/bz/gfx/aseprite.c
Fixed Aseprite rendering system
[benzene.git] / src / bz / gfx / aseprite.c
index 69dc446fbef8b1bbe2b2b178501ed3fde6737130..b2a829ff0bdbf442333ed4bd96186bf1b2f51058 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,18 +349,171 @@ 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;
+}
+
+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[]) {
+       bzResourcesSeek(handle, frameStartPositions[frameIdx]);
+       
+       ASE_Frame frame;
+       ASE_DWORD numChunks;
+       readASEFrame(&frame, &numChunks, handle);
+
+       for (ASE_DWORD chunkIdx = 0; chunkIdx < numChunks; ++chunkIdx) {
+               size_t chunkStartPosition = bzResourcesTell(handle);
+
+               ASE_Chunk chunk;
+               bzResourcesReadBytes(handle, &chunk, sizeof(chunk));
+
+               if (chunk.type == ASE_ChunkType_CelChunk) {
+                       ASE_Chunk_Cel cel;
+                       bzResourcesReadBytes(handle, &cel, sizeof(cel));
+
+                       if (cel.layerIndex == layerIndex) {
+                               if (cel.celType == ASE_Chunk_Cel_CelType_LinkedCel) {
+                                       ASE_Chunk_Cel_LinkedCel linkedCel;
+                                       bzResourcesReadBytes(handle, &linkedCel, sizeof(linkedCel));
+                                       renderChunkCel(outData, zBuffer, arena, handle, layerIndex, header, pixelDataSize, linkedCel.framePosition, frameStartPositions);
+                               } else if (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_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);
+
+                                       size_t imagePixelDataSize = image.pixelWidth * image.pixelHeight * pixelDataSize;
+                                       int8_t *imagePixelData = bzMemoryAlloc(arena, imagePixelDataSize);
+                                       stbi_zlib_decode_buffer(imagePixelData, imagePixelDataSize, compressedData, compressedSize);
+
+                                       for (size_t y = 0; y < image.pixelHeight; ++y) {
+                                               for (size_t x = 0; x < image.pixelWidth; ++x) {
+                                                       size_t outX = cel.positionX + x;
+                                                       size_t outY = cel.positionY + y;
+
+                                                       //ASE_SHORT *currentZ = &zBuffer[outY * header->width + outX];
+                                                       //if (*currentZ > layerIndex) {
+                                                               for (size_t i = 0; i < pixelDataSize; ++i) {
+                                                                       uint8_t pixelData = imagePixelData[(y * image.pixelWidth + x) * pixelDataSize + i];
+                                                                       if (pixelData > 0) { // FIXME, alpha...
+                                                                               outData[outY * header->width + outX * pixelDataSize + i] = pixelData;
+                                                                       }
+                                                               }
+                                                               //*currentZ = layerIndex;//cel.zIndex;
+                                                       //}
+                                               }
+                                       }
+
+                                       bzMemoryArenaPopWatermark(arena);
+                               }
+
+                               // Found what we needed to render...
+                               break;
+                       }
+               }
+
+               bzResourcesSeek(handle, chunkStartPosition + chunk.chunkSize);
+       }                               
+}
+
+static bool renderCelsForLayer(uint8_t *outData, ASE_SHORT *zBuffer, BZMemoryArenaID arena, BZResourceID handle, ASE_WORD layerIndex, const ASE_Header *header, size_t pixelDataSize) {
+       size_t framesStartPosition = bzResourcesTell(handle);
+
+       // Layers are .. complicated, you only really need to read them for the first frame...
+       ASE_Frame firstFrame;
+       ASE_DWORD firstFrameChunks;
+       readASEFrame(&firstFrame, &firstFrameChunks, handle);
+
+       bool layerExists = false;
+       bool layerVisible = false;
+       for (ASE_DWORD chunkIdx = 0, layerCount = 0; chunkIdx < firstFrameChunks; ++chunkIdx) {
+               size_t chunkStartPosition = bzResourcesTell(handle);
+
+               ASE_Chunk chunk;
+               bzResourcesReadBytes(handle, &chunk, sizeof(chunk));
+
+               if (chunk.type == ASE_ChunkType_LayerChunk) {
+                       ASE_Chunk_Layer layer;
+                       bzResourcesReadBytes(handle, &layer, sizeof(layer));
+
+                       if (layerCount == layerIndex) {
+                               layerExists = true;
+                               layerVisible = (layer.flags & 1) > 0; // FIXME, named flag
+                               break;
+                       } else {
+                               layerCount++;
+                       }
+               }// else {
+                       // Skip this chunk...
+                       bzResourcesSeek(handle, chunkStartPosition + chunk.chunkSize);
+               //}
+       }
+
+       if (layerExists && layerVisible) {
+               bzResourcesSeek(handle, framesStartPosition);
+
+               size_t *frameStartPositions = alloca(header->frames * sizeof(size_t)); // FIXME, alloca -> tmp
+
+               for (ASE_WORD frameIdx = 0; frameIdx < header->frames; ++frameIdx) {
+                       frameStartPositions[frameIdx] = bzResourcesTell(handle);
+
+                       ASE_Frame frame;
+                       ASE_DWORD numChunks;
+                       readASEFrame(&frame, &numChunks, handle);
+
+                       for (ASE_DWORD chunkIdx = 0; chunkIdx < numChunks; ++chunkIdx) {
+                               size_t chunkStartPosition = bzResourcesTell(handle);
+
+                               ASE_Chunk chunk;
+                               bzResourcesReadBytes(handle, &chunk, sizeof(chunk));
+
+                               if (chunk.type == ASE_ChunkType_CelChunk) {
+                                       uint8_t *frameOutData = &outData[frameIdx * header->height * header->width * pixelDataSize];
+                                       ASE_SHORT *frameZBuffer = NULL;//&zBuffer[frameIdx * header->height * header->width * sizeof(ASE_SHORT)];
+                                       renderChunkCel(frameOutData, frameZBuffer, arena, handle, layerIndex, header, pixelDataSize, frameIdx, frameStartPositions);
+                               }
+
+                               bzResourcesSeek(handle, chunkStartPosition + chunk.chunkSize);
+                       }
+               }
+       }
+
+       return layerExists;
+}
+
+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);
-       //PHYSFS_sint64 length = PHYSFS_fileLength(handle);
-       //void *data = alloca(length);
-       //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,105 +526,60 @@ 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:
-                       assert(false);//, "Invalid image");
+                       bzAssertMessage(false, "Invalid image");
                        break;
        }
 
-       uint8_t *outData = (uint8_t *)bzMemoryAlloc(arena, header.width * header.height * pixelDataSize);
+       uint8_t *outData = (uint8_t *)bzMemoryAlloc(arena, header.frames * header.height * header.width * 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) {
-               ASE_Frame frame;
-               bzResourcesReadBytes(handle, &frame, sizeof(frame));
-
-               for (ASE_DWORD chunk = 0; chunk < frame.numChunks; ++chunk) {
-                       size_t chunkStartPosition = bzResourcesTell(handle);
-
-                       ASE_Chunk chunk;
-                       bzResourcesReadBytes(handle, &chunk, sizeof(chunk));
-
-                       switch (chunk.type) {
-                               case 0x2005: {
-                                       ASE_Chunk_Cel cel;
-                                       bzResourcesReadBytes(handle, &cel, sizeof(cel));
-
-                                       if (cel.celType == 2) {
-                                               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);
-                                               int8_t *compressedData = bzMemoryAlloc(arena, compressedSize); // If we do this on the stack (alloca) the Playdate will explode!
-                                               bzResourcesReadBytes(handle, compressedData, compressedSize);
+       //bzMemoryArenaPushWatermark(arena);
+       ASE_SHORT *zBuffer = NULL;//(ASE_SHORT *)bzMemoryAlloc(arena, header.frames * header.height * header.width * sizeof(ASE_SHORT));
+       //memset(zBuffer, 0, header.frames * header.height * header.width * sizeof(ASE_SHORT)); // FIXME
 
-                                               size_t imagePixelDataSize = image.pixelWidth * image.pixelHeight * pixelDataSize;
-                                               int8_t *imagePixelData = bzMemoryAlloc(arena, imagePixelDataSize);
-                                               stbi_zlib_decode_buffer(imagePixelData, imagePixelDataSize, compressedData, compressedSize);
+       size_t startPosition = bzResourcesTell(handle);
+       for (ASE_WORD layerIndex = 0; ; ++layerIndex) {
+               // Reset...
+               bzResourcesSeek(handle, startPosition);
 
-                                               for (size_t y = 0; y < image.pixelHeight; ++y) {
-                                                       for (size_t x = 0; x < image.pixelWidth; ++x) {
-                                                               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];
-                                                               }
-                                                       }
-                                               }
-
-                                               bzMemoryArenaPopWatermark(arena);
-
-                                               // FIXME
-                                               //bzResourcesSeek(handle, chunkStartPosition + chunk.chunkSize);
-                                       } else {
-                                               // Skip this chunk...
-                                               bzResourcesSeek(handle, chunkStartPosition + chunk.chunkSize);
-                                       }
-
-                                       break;
-                               }
-                               
-                               default:
-                                       // Skip this chunk...
-                                       bzResourcesSeek(handle, chunkStartPosition + chunk.chunkSize);
-                                       break;
-                       }
+               bool rendered = renderCelsForLayer(outData, zBuffer, arena, handle, layerIndex, &header, pixelDataSize);
+               if (rendered == false) {
+                       break;
                }
        }
-       
-       bzResourcesCloseResource(handle);
+
+       //bzMemoryArenaPopWatermark(arena);
 
        return outData;
 }
 
 size_t bzGfxLoadAsepritePalette(uint32_t *colorsOut, size_t maxColors, const char *filename) {
-       BZResourceID handle = bzResourcesOpenResource("palette", "assets/palettes/%s", filename);
+       BZResourceID handle = bzResourcesOpenResource("palette", "assets/palettes/%s.aseprite", filename);
        
        ASE_Header header;
-       bzResourcesReadBytes(handle, &header, sizeof(header));
-       
-       bzAssert(header.magicNumber == 0xA5E0);
+       readASEHeader(&header, handle);
 
        size_t colorCount = 0;
 
        for (ASE_WORD frame = 0; frame < header.frames; ++frame) {
                ASE_Frame frame;
-               bzResourcesReadBytes(handle, &frame, sizeof(frame));
+               ASE_DWORD numChunks;
+               readASEFrame(&frame, &numChunks, handle);
 
-               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 0x2019: {
+                               case ASE_ChunkType_PaletteChunk: {
                                        ASE_Chunk_Palette palette;
                                        bzResourcesReadBytes(handle, &palette, sizeof(palette));