summaryrefslogtreecommitdiff
path: root/tools/gbagfx
diff options
context:
space:
mode:
Diffstat (limited to 'tools/gbagfx')
-rw-r--r--tools/gbagfx/Makefile7
-rw-r--r--tools/gbagfx/gfx.c197
-rw-r--r--tools/gbagfx/gfx.h17
-rw-r--r--tools/gbagfx/huff.c398
-rw-r--r--tools/gbagfx/huff.h38
-rw-r--r--tools/gbagfx/main.c118
-rw-r--r--tools/gbagfx/options.h2
7 files changed, 502 insertions, 275 deletions
diff --git a/tools/gbagfx/Makefile b/tools/gbagfx/Makefile
index 339585b92..93bea4bdc 100644
--- a/tools/gbagfx/Makefile
+++ b/tools/gbagfx/Makefile
@@ -1,16 +1,19 @@
CC = gcc
-CFLAGS = -Wall -Wextra -Werror -std=c11 -O2 -DPNG_SKIP_SETJMP_CHECK
+CFLAGS = -Wall -Wextra -Werror -Wno-sign-compare -std=c11 -O2 -s -DPNG_SKIP_SETJMP_CHECK
LIBS = -lpng -lz
-SRCS = main.c convert_png.c gfx.c jasc_pal.c lz.c rl.c util.c font.c
+SRCS = main.c convert_png.c gfx.c jasc_pal.c lz.c rl.c util.c font.c huff.c
.PHONY: all clean
all: gbagfx
@:
+gbagfx-debug: $(SRCS) convert_png.h gfx.h global.h jasc_pal.h lz.h rl.h util.h font.h
+ $(CC) $(CFLAGS) -DDEBUG $(SRCS) -o $@ $(LDFLAGS) $(LIBS)
+
gbagfx: $(SRCS) convert_png.h gfx.h global.h jasc_pal.h lz.h rl.h util.h font.h
$(CC) $(CFLAGS) $(SRCS) -o $@ $(LDFLAGS) $(LIBS)
diff --git a/tools/gbagfx/gfx.c b/tools/gbagfx/gfx.c
index da92771b9..f927deed9 100644
--- a/tools/gbagfx/gfx.c
+++ b/tools/gbagfx/gfx.c
@@ -4,7 +4,6 @@
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
-#include <string.h>
#include "global.h"
#include "gfx.h"
#include "util.h"
@@ -19,140 +18,6 @@
#define DOWNCONVERT_BIT_DEPTH(x) ((x) / 8)
-static inline void swap_bytes(unsigned char * orig, unsigned char * dest) {
- unsigned char tmp = *orig;
- *orig = *dest;
- *dest = tmp;
-}
-
-static inline unsigned char swap_nybbles(unsigned char orig)
-{
- return (orig >> 4) | (orig << 4);
-}
-
-static inline void swap_bytes_nybswap(unsigned char * orig, unsigned char * dest) {
- unsigned char tmp = swap_nybbles(*orig);
- *orig = swap_nybbles(*dest);
- *dest = tmp;
-}
-
-static inline unsigned char reverse_bits(unsigned char orig) {
- unsigned char dest = 0;
- for (int i = 0; i < 8; i++)
- {
- dest <<= 1;
- dest |= orig & 1;
- orig >>= 1;
- }
- return dest;
-}
-
-static void vflip(unsigned char * tile, int bitDepth) {
- for (int x = 0; x < bitDepth; x++)
- {
- unsigned char * col = tile + x;
- swap_bytes(col + 0 * bitDepth, col + 7 * bitDepth);
- swap_bytes(col + 1 * bitDepth, col + 6 * bitDepth);
- swap_bytes(col + 2 * bitDepth, col + 5 * bitDepth);
- swap_bytes(col + 3 * bitDepth, col + 4 * bitDepth);
- }
-}
-
-static void hflip(unsigned char * tile, int bitDepth) {
- for (int y = 0; y < 8; y++)
- {
- unsigned char * row = tile + y * bitDepth;
- switch (bitDepth)
- {
- case 1:
- *row = reverse_bits(*row);
- break;
- case 4:
- swap_bytes_nybswap(row + 0, row + 3);
- swap_bytes_nybswap(row + 1, row + 2);
- break;
- case 8:
- swap_bytes(row + 0, row + 56);
- swap_bytes(row + 8, row + 48);
- swap_bytes(row + 16, row + 40);
- swap_bytes(row + 24, row + 32);
- break;
- }
- }
-}
-
-static unsigned char * ApplyTilemap(struct Image *image, unsigned char * buffer, int bitDepth)
-{
- int tileSize = bitDepth * 8;
- unsigned char * tiles = calloc(image->tileMap.numTiles, tileSize);
- int i;
- struct Tile tileInfo;
-
- for (i = 0; i < image->tileMap.numTiles; i++) {
- tileInfo = image->tileMap.data[i];
- unsigned char * tile = tiles + i * tileSize;
- memcpy(tile, buffer + tileInfo.index * tileSize, tileSize);
- if (tileInfo.xflip)
- hflip(tile, bitDepth);
- if (tileInfo.yflip)
- vflip(tile, bitDepth);
- }
- free(buffer);
- return tiles;
-}
-
-static unsigned char * BuildTilemap(struct Image *image, unsigned char * buffer, int * bufferSize)
-{
- int tileSize = image->bitDepth * 8;
- unsigned char * outputPixels = calloc(1024, tileSize);
- int nTilesIn = image->height * image->width / 64;
- image->tileMap.data = calloc(nTilesIn, sizeof(struct Tilemap));
- image->tileMap.numTiles = nTilesIn;
- int nTilesOut = 0;
- unsigned char curTile1[tileSize];
- unsigned char curTile2[tileSize];
-
- for (int i = 0; i < nTilesIn; i++) {
- bool xflip = false;
- bool yflip = false;
- int j;
- memcpy(curTile1, buffer + i * tileSize, tileSize);
-
- for (j = 0; j < nTilesOut; j++) {
- memcpy(curTile2, outputPixels + j * tileSize, tileSize);
- if (memcmp(curTile1, curTile2, tileSize) == 0)
- break;
- xflip = true;
- hflip(curTile2, image->bitDepth);
- if (memcmp(curTile1, curTile2, tileSize) == 0)
- break;
- yflip = true;
- vflip(curTile2, image->bitDepth);
- if (memcmp(curTile1, curTile2, tileSize) == 0)
- break;
- xflip = false;
- hflip(curTile2, image->bitDepth);
- if (memcmp(curTile1, curTile2, tileSize) == 0)
- break;
- yflip = false;
- }
- image->tileMap.data[i].index = j;
- image->tileMap.data[i].xflip = xflip;
- image->tileMap.data[i].yflip = yflip;
- image->tileMap.data[i].palno = 0;
- if (j >= nTilesOut) {
- if (nTilesOut >= 1024)
- FATAL_ERROR("Cannot reduce image to 1024 or fewer tiles.\n");
- memcpy(outputPixels + nTilesOut * tileSize, curTile1, tileSize);
- nTilesOut++;
- }
- }
-
- free(buffer);
- *bufferSize = nTilesOut * tileSize;
- return outputPixels;
-}
-
static void AdvanceMetatilePosition(int *subTileX, int *subTileY, int *metatileX, int *metatileY, int metatilesWide, int metatileWidth, int metatileHeight)
{
(*subTileX)++;
@@ -345,21 +210,15 @@ void ReadImage(char *path, int tilesWidth, int bitDepth, int metatileWidth, int
int fileSize;
unsigned char *buffer = ReadWholeFile(path, &fileSize);
- int numTiles;
- if (image->hasTilemap) {
- buffer = ApplyTilemap(image, buffer, bitDepth);
- numTiles = image->tileMap.numTiles;
- }
- else
- numTiles = fileSize / tileSize;
+ int numTiles = fileSize / tileSize;
int tilesHeight = (numTiles + tilesWidth - 1) / tilesWidth;
if (tilesWidth % metatileWidth != 0)
- FATAL_ERROR("The width in tiles (%d) isn't a multiple of the specified metatile width (%d)\n", tilesWidth, metatileWidth);
+ FATAL_ERROR("The width in tiles (%d) isn't a multiple of the specified metatile width (%d)", tilesWidth, metatileWidth);
if (tilesHeight % metatileHeight != 0)
- FATAL_ERROR("The height in tiles (%d) isn't a multiple of the specified metatile height (%d)\n", tilesHeight, metatileHeight);
+ FATAL_ERROR("The height in tiles (%d) isn't a multiple of the specified metatile height (%d)", tilesHeight, metatileHeight);
image->width = tilesWidth * 8;
image->height = tilesHeight * 8;
@@ -432,9 +291,6 @@ void WriteImage(char *path, int numTiles, int bitDepth, int metatileWidth, int m
break;
}
- if (image->hasTilemap)
- buffer = BuildTilemap(image, buffer, &bufferSize);
-
WriteWholeFile(path, buffer, bufferSize);
free(buffer);
@@ -444,11 +300,6 @@ void FreeImage(struct Image *image)
{
free(image->pixels);
image->pixels = NULL;
- if (image->hasTilemap && image->tileMap.data != NULL) {
- free(image->tileMap.data);
- image->tileMap.data = NULL;
- image->tileMap.numTiles = 0;
- }
}
void ReadGbaPalette(char *path, struct Palette *palette)
@@ -491,45 +342,3 @@ void WriteGbaPalette(char *path, struct Palette *palette)
fclose(fp);
}
-
-void ReadGbaTilemap(char *path, struct Tilemap *tileMap)
-{
- int fileSize;
- unsigned char *data = ReadWholeFile(path, &fileSize);
-
- if (fileSize % 2 != 0)
- FATAL_ERROR("The file size (%d) is not a multiple of 2.\n", fileSize);
-
- tileMap->numTiles = fileSize / 2;
- tileMap->data = malloc(tileMap->numTiles * sizeof(struct Tile));
-
- for (int i = 0; i < tileMap->numTiles; i++)
- {
- uint16_t raw = data[2 * i + 0] | (data[2 * i + 1] << 8);
- tileMap->data[i].index = raw & 0x3FF;
- tileMap->data[i].xflip = raw & 0x400 ? 1 : 0;
- tileMap->data[i].yflip = raw & 0x800 ? 1 : 0;
- tileMap->data[i].palno = raw >> 12;
- }
-
- free(data);
-}
-
-void WriteGbaTilemap(char *path, struct Tilemap *tileMap)
-{
- FILE *fp = fopen(path, "wb");
-
- if (fp == NULL)
- FATAL_ERROR("Failed to open \"%s\" for writing.\n", path);
-
- for (int i = 0; i < tileMap->numTiles; i++) {
- uint16_t raw = tileMap->data[i].index
- | (tileMap->data[i].xflip << 10)
- | (tileMap->data[i].yflip << 11)
- | (tileMap->data[i].palno << 12);
- fputc(raw & 0xFF, fp);
- fputc(raw >> 8, fp);
- }
-
- fclose(fp);
-}
diff --git a/tools/gbagfx/gfx.h b/tools/gbagfx/gfx.h
index 15a3c6a6c..5355ced85 100644
--- a/tools/gbagfx/gfx.h
+++ b/tools/gbagfx/gfx.h
@@ -17,18 +17,6 @@ struct Palette {
int numColors;
};
-struct __attribute__((packed)) Tile {
- unsigned short index:10;
- unsigned short xflip:1;
- unsigned short yflip:1;
- unsigned short palno:4;
-};
-
-struct Tilemap {
- struct Tile *data;
- int numTiles;
-};
-
struct Image {
int width;
int height;
@@ -37,9 +25,6 @@ struct Image {
bool hasPalette;
struct Palette palette;
bool hasTransparency;
- struct Tilemap tileMap;
- bool hasTilemap;
-
};
void ReadImage(char *path, int tilesWidth, int bitDepth, int metatileWidth, int metatileHeight, struct Image *image, bool invertColors);
@@ -47,7 +32,5 @@ void WriteImage(char *path, int numTiles, int bitDepth, int metatileWidth, int m
void FreeImage(struct Image *image);
void ReadGbaPalette(char *path, struct Palette *palette);
void WriteGbaPalette(char *path, struct Palette *palette);
-void ReadGbaTilemap(char *path, struct Tilemap *tileMap);
-void WriteGbaTilemap(char *path, struct Tilemap *tileMap);
#endif // GFX_H
diff --git a/tools/gbagfx/huff.c b/tools/gbagfx/huff.c
new file mode 100644
index 000000000..143ed79be
--- /dev/null
+++ b/tools/gbagfx/huff.c
@@ -0,0 +1,398 @@
+#include <stdbool.h>
+#include <string.h>
+#include <assert.h>
+#include <stdio.h>
+#include <stdint.h>
+#include "global.h"
+#include "huff.h"
+
+static int cmp_tree(const void * a0, const void * b0) {
+ return ((struct HuffData *)a0)->value - ((struct HuffData *)b0)->value;
+}
+
+typedef int (*cmpfun)(const void *, const void *);
+
+int msort_r(void * data, size_t count, size_t size, cmpfun cmp, void * buffer) {
+ /*
+ * Out-of-place mergesort (stable sort)
+ * Returns 1 on success, 0 on failure
+ */
+ void * leftPtr;
+ void * rightPtr;
+ void * leftEnd;
+ void * rightEnd;
+ int i;
+
+ switch (count) {
+ case 0:
+ // Should never be here
+ return 0;
+
+ case 1:
+ // Nothing to do here
+ break;
+
+ case 2:
+ // Swap the two entries if the right one compares higher.
+ if (cmp(data, data + size) > 0) {
+ memcpy(buffer, data, size);
+ memcpy(data, data + size, size);
+ memcpy(data + size, buffer, size);
+ }
+ break;
+ default:
+ // Merge sort out-of-place.
+ leftPtr = data;
+ leftEnd = rightPtr = data + count / 2 * size;
+ rightEnd = data + count * size;
+
+ // Sort the left half
+ if (!msort_r(leftPtr, count / 2, size, cmp, buffer))
+ return 0;
+
+ // Sort the right half
+ if (!msort_r(rightPtr, count / 2 + (count & 1), size, cmp, buffer))
+ return 0;
+
+ // Merge the sorted halves out of place
+ i = 0;
+ do {
+ if (cmp(leftPtr, rightPtr) <= 0) {
+ memcpy(buffer + i * size, leftPtr, size);
+ leftPtr += size;
+ } else {
+ memcpy(buffer + i * size, rightPtr, size);
+ rightPtr += size;
+ }
+
+ } while (++i < count && leftPtr < leftEnd && rightPtr < rightEnd);
+
+ // Copy the remainder
+ if (i < count) {
+ if (leftPtr < leftEnd) {
+ memcpy(buffer + i * size, leftPtr, leftEnd - leftPtr);
+ }
+ else {
+ memcpy(buffer + i * size, rightPtr, rightEnd - rightPtr);
+ }
+ }
+
+ // Copy the merged data back
+ memcpy(data, buffer, count * size);
+ break;
+ }
+
+ return 1;
+}
+
+int msort(void * data, size_t count, size_t size, cmpfun cmp) {
+ void * buffer = malloc(count * size);
+ if (buffer == NULL) return 0;
+ int result = msort_r(data, count, size, cmp, buffer);
+ free(buffer);
+ return result;
+}
+
+static void write_tree(unsigned char * dest, HuffNode_t * tree, int nitems, struct BitEncoding * encoding) {
+ /*
+ * The example used to guide this function encodes the tree in a
+ * breadth-first manner. We attempt to emulate that here.
+ */
+
+ int i, j, k;
+
+ // There are (2 * nitems - 1) nodes in the binary tree. Allocate that.
+ HuffNode_t * traversal = calloc(2 * nitems - 1, sizeof(HuffNode_t));
+ if (traversal == NULL)
+ FATAL_ERROR("Fatal error while compressing Huff file.\n");
+
+ // The first node is the root of the tree.
+ traversal[0] = *tree;
+ i = 1;
+
+ // Copy the tree into a breadth-first ordering using brute force.
+ for (int depth = 1; i < 2 * nitems - 1; depth++) {
+ // Consider every possible path up to the current depth.
+ for (j = 0; i < 2 * nitems - 1 && j < 1 << depth; j++) {
+ // The index of the path is used to encode the path itself.
+ // Start from the most significant relevant bit and work our way down.
+ // Keep track of the current and previous nodes.
+ HuffNode_t * currNode = traversal;
+ HuffNode_t * parent = NULL;
+ for (k = 0; k < depth; k++) {
+ if (currNode->header.isLeaf)
+ break;
+ parent = currNode;
+ if ((j >> (depth - k - 1)) & 1)
+ currNode = currNode->branch.right;
+ else
+ currNode = currNode->branch.left;
+ }
+ // Check that the length of the current path equals the current depth.
+ if (k == depth) {
+ // Make sure we can encode the current branch.
+ // Bail here if we cannot.
+ // This is only applicable for 8-bit encodings.
+ if (traversal + i - parent > 128)
+ FATAL_ERROR("Fatal error while compressing Huff file: unable to encode binary tree.\n");
+ // Copy the current node, and update its parent.
+ traversal[i] = *currNode;
+ if (parent != NULL) {
+ if ((j & 1) == 1)
+ parent->branch.right = traversal + i;
+ else
+ parent->branch.left = traversal + i;
+ }
+ // Encode the path through the tree in the lookup table
+ if (traversal[i].header.isLeaf) {
+ encoding[traversal[i].leaf.key].nbits = depth;
+ encoding[traversal[i].leaf.key].bitstring = j;
+ }
+ i++;
+ }
+ }
+ }
+
+ // Encode the size of the tree.
+ // This is used by the decompressor to skip the tree.
+ dest[4] = nitems - 1;
+
+ // Encode each node in the tree.
+ for (i = 0; i < 2 * nitems - 1; i++) {
+ HuffNode_t * currNode = traversal + i;
+ if (currNode->header.isLeaf) {
+ dest[5 + i] = traversal[i].leaf.key;
+ } else {
+ dest[5 + i] = (((currNode->branch.right - traversal - i) / 2) - 1);
+ if (currNode->branch.left->header.isLeaf)
+ dest[5 + i] |= 0x80;
+ if (currNode->branch.right->header.isLeaf)
+ dest[5 + i] |= 0x40;
+ }
+ }
+
+ free(traversal);
+}
+
+static inline void write_32_le(unsigned char * dest, int * destPos, uint32_t * buff, int * buffPos) {
+ dest[*destPos] = *buff;
+ dest[*destPos + 1] = *buff >> 8;
+ dest[*destPos + 2] = *buff >> 16;
+ dest[*destPos + 3] = *buff >> 24;
+ *destPos += 4;
+ *buff = 0;
+ *buffPos = 0;
+}
+
+static inline void read_32_le(unsigned char * src, int * srcPos, uint32_t * buff) {
+ uint32_t tmp = src[*srcPos];
+ tmp |= src[*srcPos + 1] << 8;
+ tmp |= src[*srcPos + 2] << 16;
+ tmp |= src[*srcPos + 3] << 24;
+ *srcPos += 4;
+ *buff = tmp;
+}
+
+static void write_bits(unsigned char * dest, int * destPos, struct BitEncoding * encoding, int value, uint32_t * buff, int * buffBits) {
+ int nbits = encoding[value].nbits;
+ uint32_t bitstring = encoding[value].bitstring;
+
+ if (*buffBits + nbits >= 32) {
+ int diff = *buffBits + nbits - 32;
+ *buff <<= nbits - diff;
+ *buff |= bitstring >> diff;
+ bitstring &= ~(1 << diff);
+ nbits = diff;
+ write_32_le(dest, destPos, buff, buffBits);
+ }
+ if (nbits != 0) {
+ *buff <<= nbits;
+ *buff |= bitstring;
+ *buffBits += nbits;
+ }
+}
+
+/*
+=======================================
+MAIN COMPRESSION/DECOMPRESSION ROUTINES
+=======================================
+ */
+
+unsigned char * HuffCompress(unsigned char * src, int srcSize, int * compressedSize_p, int bitDepth) {
+ if (srcSize <= 0)
+ goto fail;
+
+ int worstCaseDestSize = 4 + (2 << bitDepth) + srcSize * 3;
+
+ unsigned char *dest = malloc(worstCaseDestSize);
+ if (dest == NULL)
+ goto fail;
+
+ int nitems = 1 << bitDepth;
+
+ HuffNode_t * freqs = calloc(nitems, sizeof(HuffNode_t));
+ if (freqs == NULL)
+ goto fail;
+
+ struct BitEncoding * encoding = calloc(nitems, sizeof(struct BitEncoding));
+ if (encoding == NULL)
+ goto fail;
+
+ // Set up the frequencies table. This will inform the tree.
+ for (int i = 0; i < nitems; i++) {
+ freqs[i].header.isLeaf = 1;
+ freqs[i].header.value = 0;
+ freqs[i].leaf.key = i;
+ }
+
+ // Count each nybble or byte.
+ for (int i = 0; i < srcSize; i++) {
+ if (bitDepth == 8) {
+ freqs[src[i]].header.value++;
+ } else {
+ freqs[src[i] >> 4].header.value++;
+ freqs[src[i] & 0xF].header.value++;
+ }
+ }
+
+#ifdef DEBUG
+ for (int i = 0; i < nitems; i++) {
+ fprintf(stderr, "%d: %d\n", i, freqs[i].header.value);
+ }
+#endif // DEBUG
+
+ // Sort the frequency table.
+ if (!msort(freqs, nitems, sizeof(HuffNode_t), cmp_tree))
+ goto fail;
+
+ // Prune zero-frequency values.
+ for (int i = 0; i < nitems; i++) {
+ if (freqs[i].header.value != 0) {
+ if (i > 0) {
+ for (int j = i; j < nitems; j++) {
+ freqs[j - i] = freqs[j];
+ }
+ nitems -= i;
+ }
+ break;
+ }
+ // This should never happen:
+ if (i == nitems - 1)
+ goto fail;
+ }
+
+ HuffNode_t * tree = calloc(nitems * 2 - 1, sizeof(HuffNode_t));
+ if (tree == NULL)
+ goto fail;
+
+ // Iteratively collapse the two least frequent nodes.
+ HuffNode_t * endptr = freqs + nitems - 2;
+
+ for (int i = 0; i < nitems - 1; i++) {
+ HuffNode_t * left = freqs;
+ HuffNode_t * right = freqs + 1;
+ tree[i * 2] = *right;
+ tree[i * 2 + 1] = *left;
+ for (int j = 0; j < nitems - i - 2; j++)
+ freqs[j] = freqs[j + 2];
+ endptr->header.isLeaf = 0;
+ endptr->header.value = tree[i * 2].header.value + tree[i * 2 + 1].header.value;
+ endptr->branch.left = tree + i * 2;
+ endptr->branch.right = tree + i * 2 + 1;
+ endptr--;
+ if (i < nitems - 2 && !msort(freqs, nitems - i - 1, sizeof(HuffNode_t), cmp_tree))
+ goto fail;
+ }
+
+ // Write the tree breadth-first, and create the path lookup table.
+ write_tree(dest, freqs, nitems, encoding);
+
+ free(tree);
+ free(freqs);
+
+ // Encode the data itself.
+ int destPos = 4 + nitems * 2;
+ uint32_t destBuf = 0;
+ uint32_t srcBuf = 0;
+ int destBitPos = 0;
+
+ for (int srcPos = 0; srcPos < srcSize;) {
+ read_32_le(src, &srcPos, &srcBuf);
+ for (int i = 0; i < 32 / bitDepth; i++) {
+ write_bits(dest, &destPos, encoding, srcBuf & (0xFF >> (8 - bitDepth)), &destBuf, &destBitPos);
+ srcBuf >>= bitDepth;
+ }
+ }
+
+ if (destBitPos != 0) {
+ write_32_le(dest, &destPos, &destBuf, &destBitPos);
+ }
+
+ free(encoding);
+
+ // Write the header.
+ dest[0] = bitDepth | 0x20;
+ dest[1] = srcSize;
+ dest[2] = srcSize >> 8;
+ dest[3] = srcSize >> 16;
+ *compressedSize_p = (destPos + 3) & ~3;
+ return dest;
+
+fail:
+ FATAL_ERROR("Fatal error while compressing Huff file.\n");
+}
+
+unsigned char * HuffDecompress(unsigned char * src, int srcSize, int * uncompressedSize_p) {
+ if (srcSize < 4)
+ goto fail;
+
+ int bitDepth = *src & 15;
+ if (bitDepth != 4 && bitDepth != 8)
+ goto fail;
+
+ int destSize = (src[3] << 16) | (src[2] << 8) | src[1];
+
+ unsigned char *dest = malloc(destSize);
+
+ if (dest == NULL)
+ goto fail;
+
+ int treePos = 5;
+ int treeSize = (src[4] + 1) * 2;
+ int srcPos = 4 + treeSize;
+ int destPos = 0;
+ int curValPos = 0;
+ uint32_t destTmp = 0;
+ uint32_t window;
+
+ for (;;)
+ {
+ if (srcPos >= srcSize)
+ goto fail;
+ read_32_le(src, &srcPos, &window);
+ for (int i = 0; i < 32; i++) {
+ int curBit = (window >> 31) & 1;
+ unsigned char treeView = src[treePos];
+ bool isLeaf = ((treeView << curBit) & 0x80) != 0;
+ treePos &= ~1; // align
+ treePos += ((treeView & 0x3F) + 1) * 2 + curBit;
+ if (isLeaf) {
+ destTmp >>= bitDepth;
+ destTmp |= (src[treePos] << (32 - bitDepth));
+ curValPos++;
+ if (curValPos == 32 / bitDepth) {
+ write_32_le(dest, &destPos, &destTmp, &curValPos);
+ if (destPos == destSize) {
+ *uncompressedSize_p = destSize;
+ return dest;
+ }
+ }
+ treePos = 5;
+ }
+ window <<= 1;
+ }
+ }
+
+fail:
+ FATAL_ERROR("Fatal error while decompressing Huff file.\n");
+}
diff --git a/tools/gbagfx/huff.h b/tools/gbagfx/huff.h
new file mode 100644
index 000000000..6002fe954
--- /dev/null
+++ b/tools/gbagfx/huff.h
@@ -0,0 +1,38 @@
+#ifndef HUFF_H
+#define HUFF_H
+
+union HuffNode;
+
+struct HuffData {
+ unsigned value:31;
+ unsigned isLeaf:1;
+};
+
+struct HuffLeaf {
+ struct HuffData header;
+ unsigned char key;
+};
+
+struct HuffBranch {
+ struct HuffData header;
+ union HuffNode * left;
+ union HuffNode * right;
+};
+
+union HuffNode {
+ struct HuffData header;
+ struct HuffLeaf leaf;
+ struct HuffBranch branch;
+};
+
+typedef union HuffNode HuffNode_t;
+
+struct BitEncoding {
+ unsigned long long nbits:6;
+ unsigned long long bitstring:58;
+};
+
+unsigned char * HuffCompress(unsigned char * buffer, int srcSize, int * compressedSize_p, int bitDepth);
+unsigned char * HuffDecompress(unsigned char * buffer, int srcSize, int * uncompressedSize_p);
+
+#endif //HUFF_H
diff --git a/tools/gbagfx/main.c b/tools/gbagfx/main.c
index 2174e37a5..aa0681fb6 100644
--- a/tools/gbagfx/main.c
+++ b/tools/gbagfx/main.c
@@ -12,6 +12,7 @@
#include "lz.h"
#include "rl.h"
#include "font.h"
+#include "huff.h"
struct CommandHandler
{
@@ -34,17 +35,6 @@ void ConvertGbaToPng(char *inputPath, char *outputPath, struct GbaToPngOptions *
image.hasPalette = false;
}
- if (options->tilemapFilePath != NULL)
- {
- ReadGbaTilemap(options->tilemapFilePath, &image.tileMap);
- image.hasTilemap = true;
- }
- else
- {
- image.tileMap.data = NULL;
- image.hasTilemap = false;
- }
-
ReadImage(inputPath, options->width, options->bitDepth, options->metatileWidth, options->metatileHeight, &image, !image.hasPalette);
image.hasTransparency = options->hasTransparency;
@@ -59,17 +49,11 @@ void ConvertPngToGba(char *inputPath, char *outputPath, struct PngToGbaOptions *
struct Image image;
image.bitDepth = options->bitDepth;
- image.hasTilemap = options->tilemapFilePath == NULL ? false : true;
- image.tileMap.data = NULL;
- image.tileMap.numTiles = 0;
ReadPng(inputPath, &image);
WriteImage(outputPath, options->numTiles, options->bitDepth, options->metatileWidth, options->metatileHeight, &image, !image.hasPalette);
- if (image.hasTilemap)
- WriteGbaTilemap(options->tilemapFilePath, &image.tileMap);
-
FreeImage(&image);
}
@@ -78,7 +62,6 @@ void HandleGbaToPngCommand(char *inputPath, char *outputPath, int argc, char **a
char *inputFileExtension = GetFileExtension(inputPath);
struct GbaToPngOptions options;
options.paletteFilePath = NULL;
- options.tilemapFilePath = NULL;
options.bitDepth = inputFileExtension[0] - '0';
options.hasTransparency = false;
options.width = 1;
@@ -98,15 +81,6 @@ void HandleGbaToPngCommand(char *inputPath, char *outputPath, int argc, char **a
options.paletteFilePath = argv[i];
}
- else if (strcmp(option, "-tilemap") == 0)
- {
- if (i + 1 >= argc)
- FATAL_ERROR("No tilemap file path following \"-tilemap\".\n");
-
- i++;
-
- options.tilemapFilePath = argv[i];
- }
else if (strcmp(option, "-object") == 0)
{
options.hasTransparency = true;
@@ -171,7 +145,6 @@ void HandlePngToGbaCommand(char *inputPath, char *outputPath, int argc, char **a
options.bitDepth = bitDepth;
options.metatileWidth = 1;
options.metatileHeight = 1;
- options.tilemapFilePath = NULL;
for (int i = 3; i < argc; i++)
{
@@ -190,14 +163,6 @@ void HandlePngToGbaCommand(char *inputPath, char *outputPath, int argc, char **a
if (options.numTiles < 1)
FATAL_ERROR("Number of tiles must be positive.\n");
}
- else if (strcmp(option, "-tilemap") == 0)
- {
- if (i + 1 >= argc)
- FATAL_ERROR("No tilemap path following \"-tilemap\".\n");
-
- i++;
- options.tilemapFilePath = argv[i];
- }
else if (strcmp(option, "-mwidth") == 0)
{
if (i + 1 >= argc)
@@ -290,10 +255,6 @@ void HandleLatinFontToPngCommand(char *inputPath, char *outputPath, int argc UNU
{
struct Image image;
- image.hasTilemap = false;
- image.tileMap.data = NULL;
- image.tileMap.numTiles = 0;
-
ReadLatinFont(inputPath, &image);
WritePng(outputPath, &image);
@@ -304,10 +265,6 @@ void HandlePngToLatinFontCommand(char *inputPath, char *outputPath, int argc UNU
{
struct Image image;
- image.hasTilemap = false;
- image.tileMap.data = NULL;
- image.tileMap.numTiles = 0;
-
image.bitDepth = 2;
ReadPng(inputPath, &image);
@@ -320,10 +277,6 @@ void HandleHalfwidthJapaneseFontToPngCommand(char *inputPath, char *outputPath,
{
struct Image image;
- image.hasTilemap = false;
- image.tileMap.data = NULL;
- image.tileMap.numTiles = 0;
-
ReadHalfwidthJapaneseFont(inputPath, &image);
WritePng(outputPath, &image);
@@ -334,10 +287,6 @@ void HandlePngToHalfwidthJapaneseFontCommand(char *inputPath, char *outputPath,
{
struct Image image;
- image.hasTilemap = false;
- image.tileMap.data = NULL;
- image.tileMap.numTiles = 0;
-
image.bitDepth = 2;
ReadPng(inputPath, &image);
@@ -350,10 +299,6 @@ void HandleFullwidthJapaneseFontToPngCommand(char *inputPath, char *outputPath,
{
struct Image image;
- image.hasTilemap = false;
- image.tileMap.data = NULL;
- image.tileMap.numTiles = 0;
-
ReadFullwidthJapaneseFont(inputPath, &image);
WritePng(outputPath, &image);
@@ -364,10 +309,6 @@ void HandlePngToFullwidthJapaneseFontCommand(char *inputPath, char *outputPath,
{
struct Image image;
- image.hasTilemap = false;
- image.tileMap.data = NULL;
- image.tileMap.numTiles = 0;
-
image.bitDepth = 2;
ReadPng(inputPath, &image);
@@ -485,6 +426,61 @@ void HandleRLDecompressCommand(char *inputPath, char *outputPath, int argc UNUSE
free(uncompressedData);
}
+void HandleHuffCompressCommand(char *inputPath, char *outputPath, int argc, char **argv)
+{
+ int fileSize;
+ int bitDepth = 4;
+
+ for (int i = 3; i < argc; i++)
+ {
+ char *option = argv[i];
+
+ if (strcmp(option, "-depth") == 0)
+ {
+ if (i + 1 >= argc)
+ FATAL_ERROR("No size following \"-depth\".\n");
+
+ i++;
+
+ if (!ParseNumber(argv[i], NULL, 10, &bitDepth))
+ FATAL_ERROR("Failed to parse bit depth.\n");
+
+ if (bitDepth != 4 && bitDepth != 8)
+ FATAL_ERROR("GBA only supports bit depth of 4 or 8.\n");
+ }
+ else
+ {
+ FATAL_ERROR("Unrecognized option \"%s\".\n", option);
+ }
+ }
+
+ unsigned char *buffer = ReadWholeFile(inputPath, &fileSize);
+
+ int compressedSize;
+ unsigned char *compressedData = HuffCompress(buffer, fileSize, &compressedSize, bitDepth);
+
+ free(buffer);
+
+ WriteWholeFile(outputPath, compressedData, compressedSize);
+
+ free(compressedData);
+}
+
+void HandleHuffDecompressCommand(char *inputPath, char *outputPath, int argc UNUSED, char **argv UNUSED)
+{
+ int fileSize;
+ unsigned char *buffer = ReadWholeFile(inputPath, &fileSize);
+
+ int uncompressedSize;
+ unsigned char *uncompressedData = HuffDecompress(buffer, fileSize, &uncompressedSize);
+
+ free(buffer);
+
+ WriteWholeFile(outputPath, uncompressedData, uncompressedSize);
+
+ free(uncompressedData);
+}
+
int main(int argc, char **argv)
{
if (argc < 3)
@@ -507,7 +503,9 @@ int main(int argc, char **argv)
{ "png", "hwjpnfont", HandlePngToHalfwidthJapaneseFontCommand },
{ "fwjpnfont", "png", HandleFullwidthJapaneseFontToPngCommand },
{ "png", "fwjpnfont", HandlePngToFullwidthJapaneseFontCommand },
+ { NULL, "huff", HandleHuffCompressCommand },
{ NULL, "lz", HandleLZCompressCommand },
+ { "huff", NULL, HandleHuffDecompressCommand },
{ "lz", NULL, HandleLZDecompressCommand },
{ NULL, "rl", HandleRLCompressCommand },
{ "rl", NULL, HandleRLDecompressCommand },
diff --git a/tools/gbagfx/options.h b/tools/gbagfx/options.h
index 463ee2455..2ff3967a4 100644
--- a/tools/gbagfx/options.h
+++ b/tools/gbagfx/options.h
@@ -7,7 +7,6 @@
struct GbaToPngOptions {
char *paletteFilePath;
- char *tilemapFilePath;
int bitDepth;
bool hasTransparency;
int width;
@@ -16,7 +15,6 @@ struct GbaToPngOptions {
};
struct PngToGbaOptions {
- char *tilemapFilePath;
int numTiles;
int bitDepth;
int metatileWidth;