#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;
+}
+
+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_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:
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);
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);
}
}
+ bzMemoryArenaPopWatermark(arena);
+
bzResourcesCloseResource(handle);
return outData;
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;
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));