#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
typedef uint64_t ASE_QWORD;
typedef int64_t ASE_LONG64;
-
-
struct PACKED_STRUCT ASE_STRING {
ASE_WORD length;
//ASE_BYTE characters[];
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;
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;
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;
};
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;
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;
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) {
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));