+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, ...) {