X-Git-Url: https://git.bts.cx/benzene.git/blobdiff_plain/88fe7f92d3fd592960bc2534fcdc534021c870bc..a2fade1d5540d1017c26fd2d2f7733915088ff94:/src/bz/gfx/aseprite.c diff --git a/src/bz/gfx/aseprite.c b/src/bz/gfx/aseprite.c index 69dc446..b2a829f 100644 --- a/src/bz/gfx/aseprite.c +++ b/src/bz/gfx/aseprite.c @@ -1,12 +1,15 @@ #include +#include +#include #include #include #include #include #include -#include // Apparently stb_image.h need this?? +#include // Apparently stb_image.h needs this?? #include +#include // memset #include // 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));