import from github

This commit is contained in:
2022-05-19 17:14:13 +00:00
parent 5247c34f50
commit ab32b30591
12612 changed files with 1905035 additions and 83 deletions

1
tools/gbagfx/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
gbagfx

19
tools/gbagfx/LICENSE Normal file
View File

@ -0,0 +1,19 @@
Copyright (c) 2015 YamaArashi
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

27
tools/gbagfx/Makefile Normal file
View File

@ -0,0 +1,27 @@
CC = gcc
CFLAGS = -Wall -Wextra -Werror -Wno-sign-compare -std=c11 -O2 -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 huff.c
ifeq ($(OS),Windows_NT)
EXE := .exe
else
EXE :=
endif
.PHONY: all clean
all: gbagfx$(EXE)
@:
gbagfx-debug$(EXE): $(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$(EXE): $(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)
clean:
$(RM) gbagfx gbagfx.exe

254
tools/gbagfx/convert_png.c Normal file
View File

@ -0,0 +1,254 @@
// Copyright (c) 2015 YamaArashi
#include <stdio.h>
#include <setjmp.h>
#include <png.h>
#include "global.h"
#include "convert_png.h"
#include "gfx.h"
static FILE *PngReadOpen(char *path, png_structp *pngStruct, png_infop *pngInfo)
{
FILE *fp = fopen(path, "rb");
if (fp == NULL)
FATAL_ERROR("Failed to open \"%s\" for reading.\n", path);
unsigned char sig[8];
if (fread(sig, 8, 1, fp) != 1)
FATAL_ERROR("Failed to read PNG signature from \"%s\".\n", path);
if (png_sig_cmp(sig, 0, 8))
FATAL_ERROR("\"%s\" does not have a valid PNG signature.\n", path);
png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
if (!png_ptr)
FATAL_ERROR("Failed to create PNG read struct.\n");
png_infop info_ptr = png_create_info_struct(png_ptr);
if (!info_ptr)
FATAL_ERROR("Failed to create PNG info struct.\n");
if (setjmp(png_jmpbuf(png_ptr)))
FATAL_ERROR("Failed to init I/O for reading \"%s\".\n", path);
png_init_io(png_ptr, fp);
png_set_sig_bytes(png_ptr, 8);
png_read_info(png_ptr, info_ptr);
*pngStruct = png_ptr;
*pngInfo = info_ptr;
return fp;
}
static unsigned char *ConvertBitDepth(unsigned char *src, int srcBitDepth, int destBitDepth, int numPixels)
{
// Round the number of bits up to the next 8 and divide by 8 to get the number of bytes.
int srcSize = ((numPixels * srcBitDepth + 7) & ~7) / 8;
int destSize = ((numPixels * destBitDepth + 7) & ~7) / 8;
unsigned char *output = calloc(destSize, 1);
unsigned char *dest = output;
int i;
int j;
int destBit = 8 - destBitDepth;
for (i = 0; i < srcSize; i++)
{
unsigned char srcByte = src[i];
for (j = 8 - srcBitDepth; j >= 0; j -= srcBitDepth)
{
unsigned char pixel = (srcByte >> j) % (1 << srcBitDepth);
if (pixel >= (1 << destBitDepth))
FATAL_ERROR("Image exceeds the maximum color value for a %ibpp image.\n", destBitDepth);
*dest |= pixel << destBit;
destBit -= destBitDepth;
if (destBit < 0)
{
dest++;
destBit = 8 - destBitDepth;
}
}
}
return output;
}
void ReadPng(char *path, struct Image *image)
{
png_structp png_ptr;
png_infop info_ptr;
FILE *fp = PngReadOpen(path, &png_ptr, &info_ptr);
int bit_depth = png_get_bit_depth(png_ptr, info_ptr);
int color_type = png_get_color_type(png_ptr, info_ptr);
if (color_type != PNG_COLOR_TYPE_GRAY && color_type != PNG_COLOR_TYPE_PALETTE)
FATAL_ERROR("\"%s\" has an unsupported color type.\n", path);
// Check if the image has a palette so that we can tell if the colors need to be inverted later.
// Don't read the palette because it's not needed for now.
image->hasPalette = (color_type == PNG_COLOR_TYPE_PALETTE);
image->width = png_get_image_width(png_ptr, info_ptr);
image->height = png_get_image_height(png_ptr, info_ptr);
int rowbytes = png_get_rowbytes(png_ptr, info_ptr);
image->pixels = malloc(image->height * rowbytes);
if (image->pixels == NULL)
FATAL_ERROR("Failed to allocate pixel buffer.\n");
png_bytepp row_pointers = malloc(image->height * sizeof(png_bytep));
if (row_pointers == NULL)
FATAL_ERROR("Failed to allocate row pointers.\n");
for (int i = 0; i < image->height; i++)
row_pointers[i] = (png_bytep)(image->pixels + (i * rowbytes));
if (setjmp(png_jmpbuf(png_ptr)))
FATAL_ERROR("Error reading from \"%s\".\n", path);
png_read_image(png_ptr, row_pointers);
png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
free(row_pointers);
fclose(fp);
if (bit_depth != image->bitDepth && image->tilemap.data.affine == NULL)
{
unsigned char *src = image->pixels;
if (bit_depth != 1 && bit_depth != 2 && bit_depth != 4 && bit_depth != 8)
FATAL_ERROR("Bit depth of image must be 1, 2, 4, or 8.\n");
image->pixels = ConvertBitDepth(image->pixels, bit_depth, image->bitDepth, image->width * image->height);
free(src);
image->bitDepth = bit_depth;
}
}
void ReadPngPalette(char *path, struct Palette *palette)
{
png_structp png_ptr;
png_infop info_ptr;
png_colorp colors;
int numColors;
FILE *fp = PngReadOpen(path, &png_ptr, &info_ptr);
if (png_get_color_type(png_ptr, info_ptr) != PNG_COLOR_TYPE_PALETTE)
FATAL_ERROR("The image \"%s\" does not contain a palette.\n", path);
if (png_get_PLTE(png_ptr, info_ptr, &colors, &numColors) != PNG_INFO_PLTE)
FATAL_ERROR("Failed to retrieve palette from \"%s\".\n", path);
if (numColors > 256)
FATAL_ERROR("Images with more than 256 colors are not supported.\n");
palette->numColors = numColors;
for (int i = 0; i < numColors; i++) {
palette->colors[i].red = colors[i].red;
palette->colors[i].green = colors[i].green;
palette->colors[i].blue = colors[i].blue;
}
png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
fclose(fp);
}
void SetPngPalette(png_structp png_ptr, png_infop info_ptr, struct Palette *palette)
{
png_colorp colors = malloc(palette->numColors * sizeof(png_color));
if (colors == NULL)
FATAL_ERROR("Failed to allocate PNG palette.\n");
for (int i = 0; i < palette->numColors; i++) {
colors[i].red = palette->colors[i].red;
colors[i].green = palette->colors[i].green;
colors[i].blue = palette->colors[i].blue;
}
png_set_PLTE(png_ptr, info_ptr, colors, palette->numColors);
free(colors);
}
void WritePng(char *path, struct Image *image)
{
FILE *fp = fopen(path, "wb");
if (fp == NULL)
FATAL_ERROR("Failed to open \"%s\" for writing.\n", path);
png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
if (!png_ptr)
FATAL_ERROR("Failed to create PNG write struct.\n");
png_infop info_ptr = png_create_info_struct(png_ptr);
if (!info_ptr)
FATAL_ERROR("Failed to create PNG info struct.\n");
if (setjmp(png_jmpbuf(png_ptr)))
FATAL_ERROR("Failed to init I/O for writing \"%s\".\n", path);
png_init_io(png_ptr, fp);
if (setjmp(png_jmpbuf(png_ptr)))
FATAL_ERROR("Error writing header for \"%s\".\n", path);
int color_type = image->hasPalette ? PNG_COLOR_TYPE_PALETTE : PNG_COLOR_TYPE_GRAY;
png_set_IHDR(png_ptr, info_ptr, image->width, image->height,
image->bitDepth, color_type, PNG_INTERLACE_NONE,
PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
if (image->hasPalette) {
SetPngPalette(png_ptr, info_ptr, &image->palette);
if (image->hasTransparency) {
png_byte trans = 0;
png_set_tRNS(png_ptr, info_ptr, &trans, 1, 0);
}
}
png_write_info(png_ptr, info_ptr);
png_bytepp row_pointers = malloc(image->height * sizeof(png_bytep));
if (row_pointers == NULL)
FATAL_ERROR("Failed to allocate row pointers.\n");
int rowbytes = png_get_rowbytes(png_ptr, info_ptr);
for (int i = 0; i < image->height; i++)
row_pointers[i] = (png_bytep)(image->pixels + (i * rowbytes));
if (setjmp(png_jmpbuf(png_ptr)))
FATAL_ERROR("Error writing \"%s\".\n", path);
png_write_image(png_ptr, row_pointers);
if (setjmp(png_jmpbuf(png_ptr)))
FATAL_ERROR("Error ending write of \"%s\".\n", path);
png_write_end(png_ptr, NULL);
fclose(fp);
png_destroy_write_struct(&png_ptr, &info_ptr);
free(row_pointers);
}

View File

@ -0,0 +1,12 @@
// Copyright (c) 2015 YamaArashi
#ifndef CONVERT_PNG_H
#define CONVERT_PNG_H
#include "gfx.h"
void ReadPng(char *path, struct Image *image);
void WritePng(char *path, struct Image *image);
void ReadPngPalette(char *path, struct Palette *palette);
#endif // CONVERT_PNG_H

326
tools/gbagfx/font.c Normal file
View File

@ -0,0 +1,326 @@
// Copyright (c) 2015 YamaArashi
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include "global.h"
#include "font.h"
#include "gfx.h"
#include "util.h"
unsigned char gFontPalette[][3] = {
{0x90, 0xC8, 0xFF}, // bg (saturated blue that contrasts well with the shadow color)
{0x38, 0x38, 0x38}, // fg (dark grey)
{0xD8, 0xD8, 0xD8}, // shadow (light grey)
{0xFF, 0xFF, 0xFF} // box (white)
};
static void ConvertFromLatinFont(unsigned char *src, unsigned char *dest, unsigned int numRows)
{
unsigned int srcPixelsOffset = 0;
for (unsigned int row = 0; row < numRows; row++) {
for (unsigned int column = 0; column < 16; column++) {
for (unsigned int glyphTile = 0; glyphTile < 4; glyphTile++) {
unsigned int pixelsX = (column * 16) + ((glyphTile & 1) * 8);
for (unsigned int i = 0; i < 8; i++) {
unsigned int pixelsY = (row * 16) + ((glyphTile >> 1) * 8) + i;
unsigned int destPixelsOffset = (pixelsY * 64) + (pixelsX / 4);
dest[destPixelsOffset] = src[srcPixelsOffset + 1];
dest[destPixelsOffset + 1] = src[srcPixelsOffset];
srcPixelsOffset += 2;
}
}
}
}
}
static void ConvertToLatinFont(unsigned char *src, unsigned char *dest, unsigned int numRows)
{
unsigned int destPixelsOffset = 0;
for (unsigned int row = 0; row < numRows; row++) {
for (unsigned int column = 0; column < 16; column++) {
for (unsigned int glyphTile = 0; glyphTile < 4; glyphTile++) {
unsigned int pixelsX = (column * 16) + ((glyphTile & 1) * 8);
for (unsigned int i = 0; i < 8; i++) {
unsigned int pixelsY = (row * 16) + ((glyphTile >> 1) * 8) + i;
unsigned int srcPixelsOffset = (pixelsY * 64) + (pixelsX / 4);
dest[destPixelsOffset] = src[srcPixelsOffset + 1];
dest[destPixelsOffset + 1] = src[srcPixelsOffset];
destPixelsOffset += 2;
}
}
}
}
}
static void ConvertFromHalfwidthJapaneseFont(unsigned char *src, unsigned char *dest, unsigned int numRows)
{
for (unsigned int row = 0; row < numRows; row++) {
for (unsigned int column = 0; column < 16; column++) {
unsigned int glyphIndex = (row * 16) + column;
for (unsigned int glyphTile = 0; glyphTile < 2; glyphTile++) {
unsigned int pixelsX = column * 8;
unsigned int srcPixelsOffset = 512 * (glyphIndex >> 4) + 16 * (glyphIndex & 0xF) + 256 * glyphTile;
for (unsigned int i = 0; i < 8; i++) {
unsigned int pixelsY = (row * 16) + (glyphTile * 8) + i;
unsigned int destPixelsOffset = (pixelsY * 32) + (pixelsX / 4);
dest[destPixelsOffset] = src[srcPixelsOffset + 1];
dest[destPixelsOffset + 1] = src[srcPixelsOffset];
srcPixelsOffset += 2;
}
}
}
}
}
static void ConvertToHalfwidthJapaneseFont(unsigned char *src, unsigned char *dest, unsigned int numRows)
{
for (unsigned int row = 0; row < numRows; row++) {
for (unsigned int column = 0; column < 16; column++) {
unsigned int glyphIndex = (row * 16) + column;
for (unsigned int glyphTile = 0; glyphTile < 2; glyphTile++) {
unsigned int pixelsX = column * 8;
unsigned int destPixelsOffset = 512 * (glyphIndex >> 4) + 16 * (glyphIndex & 0xF) + 256 * glyphTile;
for (unsigned int i = 0; i < 8; i++) {
unsigned int pixelsY = (row * 16) + (glyphTile * 8) + i;
unsigned int srcPixelsOffset = (pixelsY * 32) + (pixelsX / 4);
dest[destPixelsOffset] = src[srcPixelsOffset + 1];
dest[destPixelsOffset + 1] = src[srcPixelsOffset];
destPixelsOffset += 2;
}
}
}
}
}
static void ConvertFromFullwidthJapaneseFont(unsigned char *src, unsigned char *dest, unsigned int numRows)
{
for (unsigned int row = 0; row < numRows; row++) {
for (unsigned int column = 0; column < 16; column++) {
unsigned int glyphIndex = (row * 16) + column;
for (unsigned int glyphTile = 0; glyphTile < 4; glyphTile++) {
unsigned int pixelsX = (column * 16) + ((glyphTile & 1) * 8);
unsigned int srcPixelsOffset = 512 * (glyphIndex >> 3) + 32 * (glyphIndex & 7) + 256 * (glyphTile >> 1) + 16 * (glyphTile & 1);
for (unsigned int i = 0; i < 8; i++) {
unsigned int pixelsY = (row * 16) + ((glyphTile >> 1) * 8) + i;
unsigned int destPixelsOffset = (pixelsY * 64) + (pixelsX / 4);
dest[destPixelsOffset] = src[srcPixelsOffset + 1];
dest[destPixelsOffset + 1] = src[srcPixelsOffset];
srcPixelsOffset += 2;
}
}
}
}
}
static void ConvertToFullwidthJapaneseFont(unsigned char *src, unsigned char *dest, unsigned int numRows)
{
for (unsigned int row = 0; row < numRows; row++) {
for (unsigned int column = 0; column < 16; column++) {
unsigned int glyphIndex = (row * 16) + column;
for (unsigned int glyphTile = 0; glyphTile < 4; glyphTile++) {
unsigned int pixelsX = (column * 16) + ((glyphTile & 1) * 8);
unsigned int destPixelsOffset = 512 * (glyphIndex >> 3) + 32 * (glyphIndex & 7) + 256 * (glyphTile >> 1) + 16 * (glyphTile & 1);
for (unsigned int i = 0; i < 8; i++) {
unsigned int pixelsY = (row * 16) + ((glyphTile >> 1) * 8) + i;
unsigned int srcPixelsOffset = (pixelsY * 64) + (pixelsX / 4);
dest[destPixelsOffset] = src[srcPixelsOffset + 1];
dest[destPixelsOffset + 1] = src[srcPixelsOffset];
destPixelsOffset += 2;
}
}
}
}
}
static void SetFontPalette(struct Image *image)
{
image->hasPalette = true;
image->palette.numColors = 4;
for (int i = 0; i < image->palette.numColors; i++) {
image->palette.colors[i].red = gFontPalette[i][0];
image->palette.colors[i].green = gFontPalette[i][1];
image->palette.colors[i].blue = gFontPalette[i][2];
}
image->hasTransparency = false;
}
void ReadLatinFont(char *path, struct Image *image)
{
int fileSize;
unsigned char *buffer = ReadWholeFile(path, &fileSize);
int numGlyphs = fileSize / 64;
if (numGlyphs % 16 != 0)
FATAL_ERROR("The number of glyphs (%d) is not a multiple of 16.\n", numGlyphs);
int numRows = numGlyphs / 16;
image->width = 256;
image->height = numRows * 16;
image->bitDepth = 2;
image->pixels = malloc(fileSize);
if (image->pixels == NULL)
FATAL_ERROR("Failed to allocate memory for font.\n");
ConvertFromLatinFont(buffer, image->pixels, numRows);
free(buffer);
SetFontPalette(image);
}
void WriteLatinFont(char *path, struct Image *image)
{
if (image->width != 256)
FATAL_ERROR("The width of the font image (%d) is not 256.\n", image->width);
if (image->height % 16 != 0)
FATAL_ERROR("The height of the font image (%d) is not a multiple of 16.\n", image->height);
int numRows = image->height / 16;
int bufferSize = numRows * 16 * 64;
unsigned char *buffer = malloc(bufferSize);
if (buffer == NULL)
FATAL_ERROR("Failed to allocate memory for font.\n");
ConvertToLatinFont(image->pixels, buffer, numRows);
WriteWholeFile(path, buffer, bufferSize);
free(buffer);
}
void ReadHalfwidthJapaneseFont(char *path, struct Image *image)
{
int fileSize;
unsigned char *buffer = ReadWholeFile(path, &fileSize);
int glyphSize = 32;
if (fileSize % glyphSize != 0)
FATAL_ERROR("The file size (%d) is not a multiple of %d.\n", fileSize, glyphSize);
int numGlyphs = fileSize / glyphSize;
if (numGlyphs % 16 != 0)
FATAL_ERROR("The number of glyphs (%d) is not a multiple of 16.\n", numGlyphs);
int numRows = numGlyphs / 16;
image->width = 128;
image->height = numRows * 16;
image->bitDepth = 2;
image->pixels = malloc(fileSize);
if (image->pixels == NULL)
FATAL_ERROR("Failed to allocate memory for font.\n");
ConvertFromHalfwidthJapaneseFont(buffer, image->pixels, numRows);
free(buffer);
SetFontPalette(image);
}
void WriteHalfwidthJapaneseFont(char *path, struct Image *image)
{
if (image->width != 128)
FATAL_ERROR("The width of the font image (%d) is not 128.\n", image->width);
if (image->height % 16 != 0)
FATAL_ERROR("The height of the font image (%d) is not a multiple of 16.\n", image->height);
int numRows = image->height / 16;
int bufferSize = numRows * 16 * 32;
unsigned char *buffer = malloc(bufferSize);
if (buffer == NULL)
FATAL_ERROR("Failed to allocate memory for font.\n");
ConvertToHalfwidthJapaneseFont(image->pixels, buffer, numRows);
WriteWholeFile(path, buffer, bufferSize);
free(buffer);
}
void ReadFullwidthJapaneseFont(char *path, struct Image *image)
{
int fileSize;
unsigned char *buffer = ReadWholeFile(path, &fileSize);
int numGlyphs = fileSize / 64;
if (numGlyphs % 16 != 0)
FATAL_ERROR("The number of glyphs (%d) is not a multiple of 16.\n", numGlyphs);
int numRows = numGlyphs / 16;
image->width = 256;
image->height = numRows * 16;
image->bitDepth = 2;
image->pixels = malloc(fileSize);
if (image->pixels == NULL)
FATAL_ERROR("Failed to allocate memory for font.\n");
ConvertFromFullwidthJapaneseFont(buffer, image->pixels, numRows);
free(buffer);
SetFontPalette(image);
}
void WriteFullwidthJapaneseFont(char *path, struct Image *image)
{
if (image->width != 256)
FATAL_ERROR("The width of the font image (%d) is not 256.\n", image->width);
if (image->height % 16 != 0)
FATAL_ERROR("The height of the font image (%d) is not a multiple of 16.\n", image->height);
int numRows = image->height / 16;
int bufferSize = numRows * 16 * 64;
unsigned char *buffer = malloc(bufferSize);
if (buffer == NULL)
FATAL_ERROR("Failed to allocate memory for font.\n");
ConvertToFullwidthJapaneseFont(image->pixels, buffer, numRows);
WriteWholeFile(path, buffer, bufferSize);
free(buffer);
}

16
tools/gbagfx/font.h Normal file
View File

@ -0,0 +1,16 @@
// Copyright (c) 2015 YamaArashi
#ifndef FONT_H
#define FONT_H
#include <stdbool.h>
#include "gfx.h"
void ReadLatinFont(char *path, struct Image *image);
void WriteLatinFont(char *path, struct Image *image);
void ReadHalfwidthJapaneseFont(char *path, struct Image *image);
void WriteHalfwidthJapaneseFont(char *path, struct Image *image);
void ReadFullwidthJapaneseFont(char *path, struct Image *image);
void WriteFullwidthJapaneseFont(char *path, struct Image *image);
#endif // FONT_H

507
tools/gbagfx/gfx.c Normal file
View File

@ -0,0 +1,507 @@
// Copyright (c) 2015 YamaArashi
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include "global.h"
#include "gfx.h"
#include "util.h"
#define GET_GBA_PAL_RED(x) (((x) >> 0) & 0x1F)
#define GET_GBA_PAL_GREEN(x) (((x) >> 5) & 0x1F)
#define GET_GBA_PAL_BLUE(x) (((x) >> 10) & 0x1F)
#define SET_GBA_PAL(r, g, b) (((b) << 10) | ((g) << 5) | (r))
#define UPCONVERT_BIT_DEPTH(x) (((x) * 255) / 31)
#define DOWNCONVERT_BIT_DEPTH(x) ((x) / 8)
static void AdvanceMetatilePosition(int *subTileX, int *subTileY, int *metatileX, int *metatileY, int metatilesWide, int metatileWidth, int metatileHeight)
{
(*subTileX)++;
if (*subTileX == metatileWidth) {
*subTileX = 0;
(*subTileY)++;
if (*subTileY == metatileHeight) {
*subTileY = 0;
(*metatileX)++;
if (*metatileX == metatilesWide) {
*metatileX = 0;
(*metatileY)++;
}
}
}
}
static void ConvertFromTiles1Bpp(unsigned char *src, unsigned char *dest, int numTiles, int metatilesWide, int metatileWidth, int metatileHeight, bool invertColors)
{
int subTileX = 0;
int subTileY = 0;
int metatileX = 0;
int metatileY = 0;
int pitch = metatilesWide * metatileWidth;
for (int i = 0; i < numTiles; i++) {
for (int j = 0; j < 8; j++) {
int destY = (metatileY * metatileHeight + subTileY) * 8 + j;
int destX = metatileX * metatileWidth + subTileX;
unsigned char srcPixelOctet = *src++;
unsigned char *destPixelOctet = &dest[destY * pitch + destX];
for (int k = 0; k < 8; k++) {
*destPixelOctet <<= 1;
*destPixelOctet |= (srcPixelOctet & 1) ^ invertColors;
srcPixelOctet >>= 1;
}
}
AdvanceMetatilePosition(&subTileX, &subTileY, &metatileX, &metatileY, metatilesWide, metatileWidth, metatileHeight);
}
}
static void ConvertFromTiles4Bpp(unsigned char *src, unsigned char *dest, int numTiles, int metatilesWide, int metatileWidth, int metatileHeight, bool invertColors)
{
int subTileX = 0;
int subTileY = 0;
int metatileX = 0;
int metatileY = 0;
int pitch = (metatilesWide * metatileWidth) * 4;
for (int i = 0; i < numTiles; i++) {
for (int j = 0; j < 8; j++) {
int destY = (metatileY * metatileHeight + subTileY) * 8 + j;
for (int k = 0; k < 4; k++) {
int destX = (metatileX * metatileWidth + subTileX) * 4 + k;
unsigned char srcPixelPair = *src++;
unsigned char leftPixel = srcPixelPair & 0xF;
unsigned char rightPixel = srcPixelPair >> 4;
if (invertColors) {
leftPixel = 15 - leftPixel;
rightPixel = 15 - rightPixel;
}
dest[destY * pitch + destX] = (leftPixel << 4) | rightPixel;
}
}
AdvanceMetatilePosition(&subTileX, &subTileY, &metatileX, &metatileY, metatilesWide, metatileWidth, metatileHeight);
}
}
static void ConvertFromTiles8Bpp(unsigned char *src, unsigned char *dest, int numTiles, int metatilesWide, int metatileWidth, int metatileHeight, bool invertColors)
{
int subTileX = 0;
int subTileY = 0;
int metatileX = 0;
int metatileY = 0;
int pitch = (metatilesWide * metatileWidth) * 8;
for (int i = 0; i < numTiles; i++) {
for (int j = 0; j < 8; j++) {
int destY = (metatileY * metatileHeight + subTileY) * 8 + j;
for (int k = 0; k < 8; k++) {
int destX = (metatileX * metatileWidth + subTileX) * 8 + k;
unsigned char srcPixel = *src++;
if (invertColors)
srcPixel = 255 - srcPixel;
dest[destY * pitch + destX] = srcPixel;
}
}
AdvanceMetatilePosition(&subTileX, &subTileY, &metatileX, &metatileY, metatilesWide, metatileWidth, metatileHeight);
}
}
static void ConvertToTiles1Bpp(unsigned char *src, unsigned char *dest, int numTiles, int metatilesWide, int metatileWidth, int metatileHeight, bool invertColors)
{
int subTileX = 0;
int subTileY = 0;
int metatileX = 0;
int metatileY = 0;
int pitch = metatilesWide * metatileWidth;
for (int i = 0; i < numTiles; i++) {
for (int j = 0; j < 8; j++) {
int srcY = (metatileY * metatileHeight + subTileY) * 8 + j;
int srcX = metatileX * metatileWidth + subTileX;
unsigned char srcPixelOctet = src[srcY * pitch + srcX];
unsigned char *destPixelOctet = dest++;
for (int k = 0; k < 8; k++) {
*destPixelOctet <<= 1;
*destPixelOctet |= (srcPixelOctet & 1) ^ invertColors;
srcPixelOctet >>= 1;
}
}
AdvanceMetatilePosition(&subTileX, &subTileY, &metatileX, &metatileY, metatilesWide, metatileWidth, metatileHeight);
}
}
static void ConvertToTiles4Bpp(unsigned char *src, unsigned char *dest, int numTiles, int metatilesWide, int metatileWidth, int metatileHeight, bool invertColors)
{
int subTileX = 0;
int subTileY = 0;
int metatileX = 0;
int metatileY = 0;
int pitch = (metatilesWide * metatileWidth) * 4;
for (int i = 0; i < numTiles; i++) {
for (int j = 0; j < 8; j++) {
int srcY = (metatileY * metatileHeight + subTileY) * 8 + j;
for (int k = 0; k < 4; k++) {
int srcX = (metatileX * metatileWidth + subTileX) * 4 + k;
unsigned char srcPixelPair = src[srcY * pitch + srcX];
unsigned char leftPixel = srcPixelPair >> 4;
unsigned char rightPixel = srcPixelPair & 0xF;
if (invertColors) {
leftPixel = 15 - leftPixel;
rightPixel = 15 - rightPixel;
}
*dest++ = (rightPixel << 4) | leftPixel;
}
}
AdvanceMetatilePosition(&subTileX, &subTileY, &metatileX, &metatileY, metatilesWide, metatileWidth, metatileHeight);
}
}
static void ConvertToTiles8Bpp(unsigned char *src, unsigned char *dest, int numTiles, int metatilesWide, int metatileWidth, int metatileHeight, bool invertColors)
{
int subTileX = 0;
int subTileY = 0;
int metatileX = 0;
int metatileY = 0;
int pitch = (metatilesWide * metatileWidth) * 8;
for (int i = 0; i < numTiles; i++) {
for (int j = 0; j < 8; j++) {
int srcY = (metatileY * metatileHeight + subTileY) * 8 + j;
for (int k = 0; k < 8; k++) {
int srcX = (metatileX * metatileWidth + subTileX) * 8 + k;
unsigned char srcPixel = src[srcY * pitch + srcX];
if (invertColors)
srcPixel = 255 - srcPixel;
*dest++ = srcPixel;
}
}
AdvanceMetatilePosition(&subTileX, &subTileY, &metatileX, &metatileY, metatilesWide, metatileWidth, metatileHeight);
}
}
static void DecodeAffineTilemap(unsigned char *input, unsigned char *output, unsigned char *tilemap, int tileSize, int numTiles)
{
for (int i = 0; i < numTiles; i++)
{
memcpy(&output[i * tileSize], &input[tilemap[i] * tileSize], tileSize);
}
}
#define REVERSE_BIT_ORDER(x) ({ \
((((x) >> 7) & 1) << 0) \
| ((((x) >> 6) & 1) << 1) \
| ((((x) >> 5) & 1) << 2) \
| ((((x) >> 4) & 1) << 3) \
| ((((x) >> 3) & 1) << 4) \
| ((((x) >> 2) & 1) << 5) \
| ((((x) >> 1) & 1) << 6) \
| ((((x) >> 0) & 1) << 7); \
})
#define SWAP_BYTES(a, b) ({ \
unsigned char tmp = *(a); \
*(a) = *(b); \
*(b) = tmp; \
})
#define NSWAP(x) ({ (((x) >> 4) & 0xF) | (((x) << 4) & 0xF0); })
#define SWAP_NYBBLES(a, b) ({ \
unsigned char tmp = NSWAP(*(a)); \
*(a) = NSWAP(*(b)); \
*(b) = tmp; \
})
static void VflipTile(unsigned char * tile, int bitDepth)
{
int i;
switch (bitDepth)
{
case 1:
SWAP_BYTES(&tile[0], &tile[7]);
SWAP_BYTES(&tile[1], &tile[6]);
SWAP_BYTES(&tile[2], &tile[5]);
SWAP_BYTES(&tile[3], &tile[4]);
break;
case 4:
for (i = 0; i < 4; i++)
{
SWAP_BYTES(&tile[i + 0], &tile[i + 28]);
SWAP_BYTES(&tile[i + 4], &tile[i + 24]);
SWAP_BYTES(&tile[i + 8], &tile[i + 20]);
SWAP_BYTES(&tile[i + 12], &tile[i + 16]);
}
break;
case 8:
for (i = 0; i < 8; i++)
{
SWAP_BYTES(&tile[i + 0], &tile[i + 56]);
SWAP_BYTES(&tile[i + 8], &tile[i + 48]);
SWAP_BYTES(&tile[i + 16], &tile[i + 40]);
SWAP_BYTES(&tile[i + 24], &tile[i + 32]);
}
break;
}
}
static void HflipTile(unsigned char * tile, int bitDepth)
{
int i;
switch (bitDepth)
{
case 1:
for (i = 0; i < 8; i++)
tile[i] = REVERSE_BIT_ORDER(tile[i]);
break;
case 4:
for (i = 0; i < 8; i++)
{
SWAP_NYBBLES(&tile[4 * i + 0], &tile[4 * i + 3]);
SWAP_NYBBLES(&tile[4 * i + 1], &tile[4 * i + 2]);
}
break;
case 8:
for (i = 0; i < 8; i++)
{
SWAP_BYTES(&tile[8 * i + 0], &tile[8 * i + 7]);
SWAP_BYTES(&tile[8 * i + 1], &tile[8 * i + 6]);
SWAP_BYTES(&tile[8 * i + 2], &tile[8 * i + 5]);
SWAP_BYTES(&tile[8 * i + 3], &tile[8 * i + 4]);
}
break;
}
}
static void DecodeNonAffineTilemap(unsigned char *input, unsigned char *output, struct NonAffineTile *tilemap, int tileSize, int outTileSize, int bitDepth, int numTiles)
{
unsigned char * in_tile;
unsigned char * out_tile = output;
int effectiveBitDepth = tileSize == outTileSize ? bitDepth : 8;
for (int i = 0; i < numTiles; i++)
{
in_tile = &input[tilemap[i].index * tileSize];
if (tileSize == outTileSize)
memcpy(out_tile, in_tile, tileSize);
else
{
for (int j = 0; j < 64; j++)
{
int shift = (j & 1) * 4;
out_tile[j] = (in_tile[j / 2] & (0xF << shift)) >> shift;
}
}
if (tilemap[i].hflip)
HflipTile(out_tile, effectiveBitDepth);
if (tilemap[i].vflip)
VflipTile(out_tile, effectiveBitDepth);
if (bitDepth == 4 && effectiveBitDepth == 8)
{
for (int j = 0; j < 64; j++)
{
out_tile[j] &= 0xF;
out_tile[j] |= (15 - tilemap[i].palno) << 4;
}
}
out_tile += outTileSize;
}
}
static unsigned char *DecodeTilemap(unsigned char *tiles, struct Tilemap *tilemap, int *numTiles_p, bool isAffine, int tileSize, int outTileSize, int bitDepth)
{
int mapTileSize = isAffine ? 1 : 2;
int numTiles = tilemap->size / mapTileSize;
unsigned char *decoded = calloc(numTiles, outTileSize);
if (isAffine)
DecodeAffineTilemap(tiles, decoded, tilemap->data.affine, tileSize, numTiles);
else
DecodeNonAffineTilemap(tiles, decoded, tilemap->data.non_affine, tileSize, outTileSize, bitDepth, numTiles);
free(tiles);
*numTiles_p = numTiles;
return decoded;
}
void ReadImage(char *path, int tilesWidth, int bitDepth, int metatileWidth, int metatileHeight, struct Image *image, bool invertColors)
{
int tileSize = bitDepth * 8;
int fileSize;
unsigned char *buffer = ReadWholeFile(path, &fileSize);
int numTiles = fileSize / tileSize;
if (image->tilemap.data.affine != NULL)
{
int outTileSize = (bitDepth == 4 && image->palette.numColors > 16) ? 64 : tileSize;
buffer = DecodeTilemap(buffer, &image->tilemap, &numTiles, image->isAffine, tileSize, outTileSize, bitDepth);
if (outTileSize == 64)
{
tileSize = 64;
image->bitDepth = bitDepth = 8;
}
}
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)", tilesWidth, metatileWidth);
if (tilesHeight % metatileHeight != 0)
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;
image->bitDepth = bitDepth;
image->pixels = calloc(tilesWidth * tilesHeight, tileSize);
if (image->pixels == NULL)
FATAL_ERROR("Failed to allocate memory for pixels.\n");
int metatilesWide = tilesWidth / metatileWidth;
switch (bitDepth) {
case 1:
ConvertFromTiles1Bpp(buffer, image->pixels, numTiles, metatilesWide, metatileWidth, metatileHeight, invertColors);
break;
case 4:
ConvertFromTiles4Bpp(buffer, image->pixels, numTiles, metatilesWide, metatileWidth, metatileHeight, invertColors);
break;
case 8:
ConvertFromTiles8Bpp(buffer, image->pixels, numTiles, metatilesWide, metatileWidth, metatileHeight, invertColors);
break;
}
free(buffer);
}
void WriteImage(char *path, int numTiles, int bitDepth, int metatileWidth, int metatileHeight, struct Image *image, bool invertColors)
{
int tileSize = bitDepth * 8;
if (image->width % 8 != 0)
FATAL_ERROR("The width in pixels (%d) isn't a multiple of 8.\n", image->width);
if (image->height % 8 != 0)
FATAL_ERROR("The height in pixels (%d) isn't a multiple of 8.\n", image->height);
int tilesWidth = image->width / 8;
int tilesHeight = image->height / 8;
if (tilesWidth % metatileWidth != 0)
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)", tilesHeight, metatileHeight);
int maxNumTiles = tilesWidth * tilesHeight;
if (numTiles == 0)
numTiles = maxNumTiles;
else if (numTiles > maxNumTiles)
FATAL_ERROR("The specified number of tiles (%d) is greater than the maximum possible value (%d).\n", numTiles, maxNumTiles);
int bufferSize = numTiles * tileSize;
unsigned char *buffer = malloc(bufferSize);
if (buffer == NULL)
FATAL_ERROR("Failed to allocate memory for pixels.\n");
int metatilesWide = tilesWidth / metatileWidth;
switch (bitDepth) {
case 1:
ConvertToTiles1Bpp(image->pixels, buffer, numTiles, metatilesWide, metatileWidth, metatileHeight, invertColors);
break;
case 4:
ConvertToTiles4Bpp(image->pixels, buffer, numTiles, metatilesWide, metatileWidth, metatileHeight, invertColors);
break;
case 8:
ConvertToTiles8Bpp(image->pixels, buffer, numTiles, metatilesWide, metatileWidth, metatileHeight, invertColors);
break;
}
WriteWholeFile(path, buffer, bufferSize);
free(buffer);
}
void FreeImage(struct Image *image)
{
if (image->tilemap.data.affine != NULL)
{
free(image->tilemap.data.affine);
image->tilemap.data.affine = NULL;
}
free(image->pixels);
image->pixels = NULL;
}
void ReadGbaPalette(char *path, struct Palette *palette)
{
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);
palette->numColors = fileSize / 2;
for (int i = 0; i < palette->numColors; i++) {
uint16_t paletteEntry = (data[i * 2 + 1] << 8) | data[i * 2];
palette->colors[i].red = UPCONVERT_BIT_DEPTH(GET_GBA_PAL_RED(paletteEntry));
palette->colors[i].green = UPCONVERT_BIT_DEPTH(GET_GBA_PAL_GREEN(paletteEntry));
palette->colors[i].blue = UPCONVERT_BIT_DEPTH(GET_GBA_PAL_BLUE(paletteEntry));
}
// png can only accept 16 or 256 colors, so fill the remainder with black
if (palette->numColors > 16)
{
memset(&palette->colors[palette->numColors], 0, (256 - palette->numColors) * sizeof(struct Color));
palette->numColors = 256;
}
free(data);
}
void WriteGbaPalette(char *path, struct Palette *palette)
{
FILE *fp = fopen(path, "wb");
if (fp == NULL)
FATAL_ERROR("Failed to open \"%s\" for writing.\n", path);
for (int i = 0; i < palette->numColors; i++) {
unsigned char red = DOWNCONVERT_BIT_DEPTH(palette->colors[i].red);
unsigned char green = DOWNCONVERT_BIT_DEPTH(palette->colors[i].green);
unsigned char blue = DOWNCONVERT_BIT_DEPTH(palette->colors[i].blue);
uint16_t paletteEntry = SET_GBA_PAL(red, green, blue);
fputc(paletteEntry & 0xFF, fp);
fputc(paletteEntry >> 8, fp);
}
fclose(fp);
}

53
tools/gbagfx/gfx.h Normal file
View File

@ -0,0 +1,53 @@
// Copyright (c) 2015 YamaArashi
#ifndef GFX_H
#define GFX_H
#include <stdint.h>
#include <stdbool.h>
struct Color {
unsigned char red;
unsigned char green;
unsigned char blue;
};
struct Palette {
struct Color colors[256];
int numColors;
};
struct NonAffineTile {
unsigned short index:10;
unsigned short hflip:1;
unsigned short vflip:1;
unsigned short palno:4;
} __attribute__((packed));
struct Tilemap {
union {
struct NonAffineTile *non_affine;
unsigned char *affine;
} data;
int size;
};
struct Image {
int width;
int height;
int bitDepth;
unsigned char *pixels;
bool hasPalette;
struct Palette palette;
bool hasTransparency;
struct Tilemap tilemap;
bool isAffine;
};
void ReadImage(char *path, int tilesWidth, int bitDepth, int metatileWidth, int metatileHeight, struct Image *image, bool invertColors);
void WriteImage(char *path, int numTiles, int bitDepth, int metatileWidth, int metatileHeight, struct Image *image, bool invertColors);
void FreeImage(struct Image *image);
void ReadGbaPalette(char *path, struct Palette *palette);
void WriteGbaPalette(char *path, struct Palette *palette);
#endif // GFX_H

31
tools/gbagfx/global.h Normal file
View File

@ -0,0 +1,31 @@
// Copyright (c) 2015 YamaArashi
#ifndef GLOBAL_H
#define GLOBAL_H
#include <stdio.h>
#include <stdlib.h>
#ifdef _MSC_VER
#define FATAL_ERROR(format, ...) \
do { \
fprintf(stderr, format, __VA_ARGS__); \
exit(1); \
} while (0)
#define UNUSED
#else
#define FATAL_ERROR(format, ...) \
do { \
fprintf(stderr, format, ##__VA_ARGS__); \
exit(1); \
} while (0)
#define UNUSED __attribute__((__unused__))
#endif // _MSC_VER
#endif // GLOBAL_H

398
tools/gbagfx/huff.c Normal file
View File

@ -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");
}

38
tools/gbagfx/huff.h Normal file
View File

@ -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

172
tools/gbagfx/jasc_pal.c Normal file
View File

@ -0,0 +1,172 @@
// Copyright (c) 2015 YamaArashi
#include <stdio.h>
#include <string.h>
#include "global.h"
#include "gfx.h"
#include "util.h"
// Read/write Paint Shop Pro palette files.
// Format of a Paint Shop Pro palette file, line by line:
// "JASC-PAL\r\n" (signature)
// "0100\r\n" (version; seems to always be "0100")
// "<NUMBER_OF_COLORS>\r\n" (number of colors in decimal)
//
// <NUMBER_OF_COLORS> times:
// "<RED> <GREEN> <BLUE>\r\n" (color entry)
//
// Each color component is a decimal number from 0 to 255.
// Examples:
// Black - "0 0 0\r\n"
// Blue - "0 0 255\r\n"
// Brown - "150 75 0\r\n"
#define MAX_LINE_LENGTH 11
void ReadJascPaletteLine(FILE *fp, char *line)
{
int c;
int length = 0;
for (;;)
{
c = fgetc(fp);
if (c == '\r')
{
c = fgetc(fp);
if (c != '\n')
FATAL_ERROR("CR line endings aren't supported.\n");
line[length] = 0;
return;
}
if (c == '\n')
FATAL_ERROR("LF line endings aren't supported.\n");
if (c == EOF)
FATAL_ERROR("Unexpected EOF. No CRLF at end of file.\n");
if (c == 0)
FATAL_ERROR("NUL character in file.\n");
if (length == MAX_LINE_LENGTH)
{
line[length] = 0;
FATAL_ERROR("The line \"%s\" is too long.\n", line);
}
line[length++] = c;
}
}
void ReadJascPalette(char *path, struct Palette *palette)
{
char line[MAX_LINE_LENGTH + 1];
FILE *fp = fopen(path, "rb");
if (fp == NULL)
FATAL_ERROR("Failed to open JASC-PAL file \"%s\" for reading.\n", path);
ReadJascPaletteLine(fp, line);
if (strcmp(line, "JASC-PAL") != 0)
FATAL_ERROR("Invalid JASC-PAL signature.\n");
ReadJascPaletteLine(fp, line);
if (strcmp(line, "0100") != 0)
FATAL_ERROR("Unsuported JASC-PAL version.\n");
ReadJascPaletteLine(fp, line);
if (!ParseNumber(line, NULL, 10, &palette->numColors))
FATAL_ERROR("Failed to parse number of colors.\n");
if (palette->numColors < 1 || palette->numColors > 256)
FATAL_ERROR("%d is an invalid number of colors. The number of colors must be in the range [1, 256].\n", palette->numColors);
for (int i = 0; i < palette->numColors; i++)
{
ReadJascPaletteLine(fp, line);
char *s = line;
char *end;
int red;
int green;
int blue;
if (!ParseNumber(s, &end, 10, &red))
FATAL_ERROR("Failed to parse red color component.\n");
s = end;
if (*s != ' ')
FATAL_ERROR("Expected a space after red color component.\n");
s++;
if (*s < '0' || *s > '9')
FATAL_ERROR("Expected only a space between red and green color components.\n");
if (!ParseNumber(s, &end, 10, &green))
FATAL_ERROR("Failed to parse green color component.\n");
s = end;
if (*s != ' ')
FATAL_ERROR("Expected a space after green color component.\n");
s++;
if (*s < '0' || *s > '9')
FATAL_ERROR("Expected only a space between green and blue color components.\n");
if (!ParseNumber(s, &end, 10, &blue))
FATAL_ERROR("Failed to parse blue color component.\n");
if (*end != 0)
FATAL_ERROR("Garbage after blue color component.\n");
if (red < 0 || red > 255)
FATAL_ERROR("Red color component (%d) is outside the range [0, 255].\n", red);
if (green < 0 || green > 255)
FATAL_ERROR("Green color component (%d) is outside the range [0, 255].\n", green);
if (blue < 0 || blue > 255)
FATAL_ERROR("Blue color component (%d) is outside the range [0, 255].\n", blue);
palette->colors[i].red = red;
palette->colors[i].green = green;
palette->colors[i].blue = blue;
}
if (fgetc(fp) != EOF)
FATAL_ERROR("Garbage after color data.\n");
fclose(fp);
}
void WriteJascPalette(char *path, struct Palette *palette)
{
FILE *fp = fopen(path, "wb");
fputs("JASC-PAL\r\n", fp);
fputs("0100\r\n", fp);
fprintf(fp, "%d\r\n", palette->numColors);
for (int i = 0; i < palette->numColors; i++)
{
struct Color *color = &palette->colors[i];
fprintf(fp, "%d %d %d\r\n", color->red, color->green, color->blue);
}
fclose(fp);
}

9
tools/gbagfx/jasc_pal.h Normal file
View File

@ -0,0 +1,9 @@
// Copyright (c) 2015 YamaArashi
#ifndef JASC_PAL_H
#define JASC_PAL_H
void ReadJascPalette(char *path, struct Palette *palette);
void WriteJascPalette(char *path, struct Palette *palette);
#endif // JASC_PAL_H

153
tools/gbagfx/lz.c Normal file
View File

@ -0,0 +1,153 @@
// Copyright (c) 2015 YamaArashi
#include <stdlib.h>
#include <stdbool.h>
#include "global.h"
#include "lz.h"
unsigned char *LZDecompress(unsigned char *src, int srcSize, int *uncompressedSize)
{
if (srcSize < 4)
goto fail;
int destSize = (src[3] << 16) | (src[2] << 8) | src[1];
unsigned char *dest = malloc(destSize);
if (dest == NULL)
goto fail;
int srcPos = 4;
int destPos = 0;
for (;;) {
if (srcPos >= srcSize)
goto fail;
unsigned char flags = src[srcPos++];
for (int i = 0; i < 8; i++) {
if (flags & 0x80) {
if (srcPos + 1 >= srcSize)
goto fail;
int blockSize = (src[srcPos] >> 4) + 3;
int blockDistance = (((src[srcPos] & 0xF) << 8) | src[srcPos + 1]) + 1;
srcPos += 2;
int blockPos = destPos - blockDistance;
// Some Ruby/Sapphire tilesets overflow.
if (destPos + blockSize > destSize) {
blockSize = destSize - destPos;
fprintf(stderr, "Destination buffer overflow.\n");
}
if (blockPos < 0)
goto fail;
for (int j = 0; j < blockSize; j++)
dest[destPos++] = dest[blockPos + j];
} else {
if (srcPos >= srcSize || destPos >= destSize)
goto fail;
dest[destPos++] = src[srcPos++];
}
if (destPos == destSize) {
*uncompressedSize = destSize;
return dest;
}
flags <<= 1;
}
}
fail:
FATAL_ERROR("Fatal error while decompressing LZ file.\n");
}
unsigned char *LZCompress(unsigned char *src, int srcSize, int *compressedSize, const int minDistance)
{
if (srcSize <= 0)
goto fail;
int worstCaseDestSize = 4 + srcSize + ((srcSize + 7) / 8);
// Round up to the next multiple of four.
worstCaseDestSize = (worstCaseDestSize + 3) & ~3;
unsigned char *dest = malloc(worstCaseDestSize);
if (dest == NULL)
goto fail;
// header
dest[0] = 0x10; // LZ compression type
dest[1] = (unsigned char)srcSize;
dest[2] = (unsigned char)(srcSize >> 8);
dest[3] = (unsigned char)(srcSize >> 16);
int srcPos = 0;
int destPos = 4;
for (;;) {
unsigned char *flags = &dest[destPos++];
*flags = 0;
for (int i = 0; i < 8; i++) {
int bestBlockDistance = 0;
int bestBlockSize = 0;
int blockDistance = minDistance;
while (blockDistance <= srcPos && blockDistance <= 0x1000) {
int blockStart = srcPos - blockDistance;
int blockSize = 0;
while (blockSize < 18
&& srcPos + blockSize < srcSize
&& src[blockStart + blockSize] == src[srcPos + blockSize])
blockSize++;
if (blockSize > bestBlockSize) {
bestBlockDistance = blockDistance;
bestBlockSize = blockSize;
if (blockSize == 18)
break;
}
blockDistance++;
}
if (bestBlockSize >= 3) {
*flags |= (0x80 >> i);
srcPos += bestBlockSize;
bestBlockSize -= 3;
bestBlockDistance--;
dest[destPos++] = (bestBlockSize << 4) | ((unsigned int)bestBlockDistance >> 8);
dest[destPos++] = (unsigned char)bestBlockDistance;
} else {
dest[destPos++] = src[srcPos++];
}
if (srcPos == srcSize) {
// Pad to multiple of 4 bytes.
int remainder = destPos % 4;
if (remainder != 0) {
for (int i = 0; i < 4 - remainder; i++)
dest[destPos++] = 0;
}
*compressedSize = destPos;
return dest;
}
}
}
fail:
FATAL_ERROR("Fatal error while compressing LZ file.\n");
}

9
tools/gbagfx/lz.h Normal file
View File

@ -0,0 +1,9 @@
// Copyright (c) 2015 YamaArashi
#ifndef LZ_H
#define LZ_H
unsigned char *LZDecompress(unsigned char *src, int srcSize, int *uncompressedSize);
unsigned char *LZCompress(unsigned char *src, int srcSize, int *compressedSize, const int minDistance);
#endif // LZ_H

626
tools/gbagfx/main.c Normal file
View File

@ -0,0 +1,626 @@
// Copyright (c) 2015 YamaArashi
#include <stdio.h>
#include <string.h>
#include <stdbool.h>
#include "global.h"
#include "util.h"
#include "options.h"
#include "gfx.h"
#include "convert_png.h"
#include "jasc_pal.h"
#include "lz.h"
#include "rl.h"
#include "font.h"
#include "huff.h"
struct CommandHandler
{
const char *inputFileExtension;
const char *outputFileExtension;
void(*function)(char *inputPath, char *outputPath, int argc, char **argv);
};
void ConvertGbaToPng(char *inputPath, char *outputPath, struct GbaToPngOptions *options)
{
struct Image image;
if (options->paletteFilePath != NULL)
{
char *paletteFileExtension = GetFileExtensionAfterDot(options->paletteFilePath);
if (strcmp(paletteFileExtension, "gbapal") == 0)
{
ReadGbaPalette(options->paletteFilePath, &image.palette);
}
else
{
ReadJascPalette(options->paletteFilePath, &image.palette);
}
image.hasPalette = true;
}
else
{
image.hasPalette = false;
}
if (options->tilemapFilePath != NULL)
{
int fileSize;
image.tilemap.data.affine = ReadWholeFile(options->tilemapFilePath, &fileSize);
if (options->isAffineMap && options->bitDepth != 8)
FATAL_ERROR("affine maps are necessarily 8bpp\n");
image.isAffine = options->isAffineMap;
image.tilemap.size = fileSize;
}
else
{
image.tilemap.data.affine = NULL;
}
ReadImage(inputPath, options->width, options->bitDepth, options->metatileWidth, options->metatileHeight, &image, !image.hasPalette);
image.hasTransparency = options->hasTransparency;
WritePng(outputPath, &image);
FreeImage(&image);
}
void ConvertPngToGba(char *inputPath, char *outputPath, struct PngToGbaOptions *options)
{
struct Image image;
image.bitDepth = options->bitDepth;
image.tilemap.data.affine = NULL; // initialize to NULL to avoid issues in FreeImage
ReadPng(inputPath, &image);
WriteImage(outputPath, options->numTiles, options->bitDepth, options->metatileWidth, options->metatileHeight, &image, !image.hasPalette);
FreeImage(&image);
}
void HandleGbaToPngCommand(char *inputPath, char *outputPath, int argc, char **argv)
{
char *inputFileExtension = GetFileExtensionAfterDot(inputPath);
struct GbaToPngOptions options;
options.paletteFilePath = NULL;
options.bitDepth = inputFileExtension[0] - '0';
options.hasTransparency = false;
options.width = 1;
options.metatileWidth = 1;
options.metatileHeight = 1;
options.tilemapFilePath = NULL;
options.isAffineMap = false;
for (int i = 3; i < argc; i++)
{
char *option = argv[i];
if (strcmp(option, "-palette") == 0)
{
if (i + 1 >= argc)
FATAL_ERROR("No palette file path following \"-palette\".\n");
i++;
options.paletteFilePath = argv[i];
}
else if (strcmp(option, "-object") == 0)
{
options.hasTransparency = true;
}
else if (strcmp(option, "-width") == 0)
{
if (i + 1 >= argc)
FATAL_ERROR("No width following \"-width\".\n");
i++;
if (!ParseNumber(argv[i], NULL, 10, &options.width))
FATAL_ERROR("Failed to parse width.\n");
if (options.width < 1)
FATAL_ERROR("Width must be positive.\n");
}
else if (strcmp(option, "-mwidth") == 0)
{
if (i + 1 >= argc)
FATAL_ERROR("No metatile width value following \"-mwidth\".\n");
i++;
if (!ParseNumber(argv[i], NULL, 10, &options.metatileWidth))
FATAL_ERROR("Failed to parse metatile width.\n");
if (options.metatileWidth < 1)
FATAL_ERROR("metatile width must be positive.\n");
}
else if (strcmp(option, "-mheight") == 0)
{
if (i + 1 >= argc)
FATAL_ERROR("No metatile height value following \"-mheight\".\n");
i++;
if (!ParseNumber(argv[i], NULL, 10, &options.metatileHeight))
FATAL_ERROR("Failed to parse metatile height.\n");
if (options.metatileHeight < 1)
FATAL_ERROR("metatile height must be positive.\n");
}
else if (strcmp(option, "-tilemap") == 0)
{
if (i + 1 >= argc)
FATAL_ERROR("No tilemap value following \"-tilemap\".\n");
i++;
options.tilemapFilePath = argv[i];
}
else if (strcmp(option, "-affine") == 0)
{
options.isAffineMap = true;
}
else
{
FATAL_ERROR("Unrecognized option \"%s\".\n", option);
}
}
if (options.metatileWidth > options.width)
options.width = options.metatileWidth;
ConvertGbaToPng(inputPath, outputPath, &options);
}
void HandlePngToGbaCommand(char *inputPath, char *outputPath, int argc, char **argv)
{
char *outputFileExtension = GetFileExtensionAfterDot(outputPath);
int bitDepth = outputFileExtension[0] - '0';
struct PngToGbaOptions options;
options.numTiles = 0;
options.bitDepth = bitDepth;
options.metatileWidth = 1;
options.metatileHeight = 1;
options.tilemapFilePath = NULL;
options.isAffineMap = false;
for (int i = 3; i < argc; i++)
{
char *option = argv[i];
if (strcmp(option, "-num_tiles") == 0)
{
if (i + 1 >= argc)
FATAL_ERROR("No number of tiles following \"-num_tiles\".\n");
i++;
if (!ParseNumber(argv[i], NULL, 10, &options.numTiles))
FATAL_ERROR("Failed to parse number of tiles.\n");
if (options.numTiles < 1)
FATAL_ERROR("Number of tiles must be positive.\n");
}
else if (strcmp(option, "-mwidth") == 0)
{
if (i + 1 >= argc)
FATAL_ERROR("No metatile width value following \"-mwidth\".\n");
i++;
if (!ParseNumber(argv[i], NULL, 10, &options.metatileWidth))
FATAL_ERROR("Failed to parse metatile width.\n");
if (options.metatileWidth < 1)
FATAL_ERROR("metatile width must be positive.\n");
}
else if (strcmp(option, "-mheight") == 0)
{
if (i + 1 >= argc)
FATAL_ERROR("No metatile height value following \"-mheight\".\n");
i++;
if (!ParseNumber(argv[i], NULL, 10, &options.metatileHeight))
FATAL_ERROR("Failed to parse metatile height.\n");
if (options.metatileHeight < 1)
FATAL_ERROR("metatile height must be positive.\n");
}
else
{
FATAL_ERROR("Unrecognized option \"%s\".\n", option);
}
}
ConvertPngToGba(inputPath, outputPath, &options);
}
void HandlePngToJascPaletteCommand(char *inputPath, char *outputPath, int argc UNUSED, char **argv UNUSED)
{
struct Palette palette = {};
ReadPngPalette(inputPath, &palette);
WriteJascPalette(outputPath, &palette);
}
void HandlePngToGbaPaletteCommand(char *inputPath, char *outputPath, int argc UNUSED, char **argv UNUSED)
{
struct Palette palette = {};
ReadPngPalette(inputPath, &palette);
WriteGbaPalette(outputPath, &palette);
}
void HandleGbaToJascPaletteCommand(char *inputPath, char *outputPath, int argc UNUSED, char **argv UNUSED)
{
struct Palette palette = {};
ReadGbaPalette(inputPath, &palette);
WriteJascPalette(outputPath, &palette);
}
void HandleJascToGbaPaletteCommand(char *inputPath, char *outputPath, int argc, char **argv)
{
int numColors = 0;
for (int i = 3; i < argc; i++)
{
char *option = argv[i];
if (strcmp(option, "-num_colors") == 0)
{
if (i + 1 >= argc)
FATAL_ERROR("No number of colors following \"-num_colors\".\n");
i++;
if (!ParseNumber(argv[i], NULL, 10, &numColors))
FATAL_ERROR("Failed to parse number of colors.\n");
if (numColors < 1)
FATAL_ERROR("Number of colors must be positive.\n");
}
else
{
FATAL_ERROR("Unrecognized option \"%s\".\n", option);
}
}
struct Palette palette = {};
ReadJascPalette(inputPath, &palette);
if (numColors != 0)
palette.numColors = numColors;
WriteGbaPalette(outputPath, &palette);
}
void HandleLatinFontToPngCommand(char *inputPath, char *outputPath, int argc UNUSED, char **argv UNUSED)
{
struct Image image;
image.tilemap.data.affine = NULL; // initialize to NULL to avoid issues in FreeImage
ReadLatinFont(inputPath, &image);
WritePng(outputPath, &image);
FreeImage(&image);
}
void HandlePngToLatinFontCommand(char *inputPath, char *outputPath, int argc UNUSED, char **argv UNUSED)
{
struct Image image;
image.tilemap.data.affine = NULL; // initialize to NULL to avoid issues in FreeImage
image.bitDepth = 2;
ReadPng(inputPath, &image);
WriteLatinFont(outputPath, &image);
FreeImage(&image);
}
void HandleHalfwidthJapaneseFontToPngCommand(char *inputPath, char *outputPath, int argc UNUSED, char **argv UNUSED)
{
struct Image image;
image.tilemap.data.affine = NULL; // initialize to NULL to avoid issues in FreeImage
ReadHalfwidthJapaneseFont(inputPath, &image);
WritePng(outputPath, &image);
FreeImage(&image);
}
void HandlePngToHalfwidthJapaneseFontCommand(char *inputPath, char *outputPath, int argc UNUSED, char **argv UNUSED)
{
struct Image image;
image.tilemap.data.affine = NULL; // initialize to NULL to avoid issues in FreeImage
image.bitDepth = 2;
ReadPng(inputPath, &image);
WriteHalfwidthJapaneseFont(outputPath, &image);
FreeImage(&image);
}
void HandleFullwidthJapaneseFontToPngCommand(char *inputPath, char *outputPath, int argc UNUSED, char **argv UNUSED)
{
struct Image image;
image.tilemap.data.affine = NULL; // initialize to NULL to avoid issues in FreeImage
ReadFullwidthJapaneseFont(inputPath, &image);
WritePng(outputPath, &image);
FreeImage(&image);
}
void HandlePngToFullwidthJapaneseFontCommand(char *inputPath, char *outputPath, int argc UNUSED, char **argv UNUSED)
{
struct Image image;
image.tilemap.data.affine = NULL; // initialize to NULL to avoid issues in FreeImage
image.bitDepth = 2;
ReadPng(inputPath, &image);
WriteFullwidthJapaneseFont(outputPath, &image);
FreeImage(&image);
}
void HandleLZCompressCommand(char *inputPath, char *outputPath, int argc, char **argv)
{
int overflowSize = 0;
int minDistance = 2; // default, for compatibility with LZ77UnCompVram()
for (int i = 3; i < argc; i++)
{
char *option = argv[i];
if (strcmp(option, "-overflow") == 0)
{
if (i + 1 >= argc)
FATAL_ERROR("No size following \"-overflow\".\n");
i++;
if (!ParseNumber(argv[i], NULL, 10, &overflowSize))
FATAL_ERROR("Failed to parse overflow size.\n");
if (overflowSize < 1)
FATAL_ERROR("Overflow size must be positive.\n");
}
else if (strcmp(option, "-search") == 0)
{
if (i + 1 >= argc)
FATAL_ERROR("No size following \"-overflow\".\n");
i++;
if (!ParseNumber(argv[i], NULL, 10, &minDistance))
FATAL_ERROR("Failed to parse LZ min search distance.\n");
if (minDistance < 1)
FATAL_ERROR("LZ min search distance must be positive.\n");
}
else
{
FATAL_ERROR("Unrecognized option \"%s\".\n", option);
}
}
// The overflow option allows a quirk in some of Ruby/Sapphire's tilesets
// to be reproduced. It works by appending a number of zeros to the data
// before compressing it and then amending the LZ header's size field to
// reflect the expected size. This will cause an overflow when decompressing
// the data.
int fileSize;
unsigned char *buffer = ReadWholeFileZeroPadded(inputPath, &fileSize, overflowSize);
int compressedSize;
unsigned char *compressedData = LZCompress(buffer, fileSize + overflowSize, &compressedSize, minDistance);
compressedData[1] = (unsigned char)fileSize;
compressedData[2] = (unsigned char)(fileSize >> 8);
compressedData[3] = (unsigned char)(fileSize >> 16);
free(buffer);
WriteWholeFile(outputPath, compressedData, compressedSize);
free(compressedData);
}
void HandleLZDecompressCommand(char *inputPath, char *outputPath, int argc UNUSED, char **argv UNUSED)
{
int fileSize;
unsigned char *buffer = ReadWholeFile(inputPath, &fileSize);
int uncompressedSize;
unsigned char *uncompressedData = LZDecompress(buffer, fileSize, &uncompressedSize);
free(buffer);
WriteWholeFile(outputPath, uncompressedData, uncompressedSize);
free(uncompressedData);
}
void HandleRLCompressCommand(char *inputPath, char *outputPath, int argc UNUSED, char **argv UNUSED)
{
int fileSize;
unsigned char *buffer = ReadWholeFile(inputPath, &fileSize);
int compressedSize;
unsigned char *compressedData = RLCompress(buffer, fileSize, &compressedSize);
free(buffer);
WriteWholeFile(outputPath, compressedData, compressedSize);
free(compressedData);
}
void HandleRLDecompressCommand(char *inputPath, char *outputPath, int argc UNUSED, char **argv UNUSED)
{
int fileSize;
unsigned char *buffer = ReadWholeFile(inputPath, &fileSize);
int uncompressedSize;
unsigned char *uncompressedData = RLDecompress(buffer, fileSize, &uncompressedSize);
free(buffer);
WriteWholeFile(outputPath, uncompressedData, uncompressedSize);
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)
{
char converted = 0;
if (argc < 3)
FATAL_ERROR("Usage: gbagfx INPUT_PATH OUTPUT_PATH [options...]\n");
struct CommandHandler handlers[] =
{
{ "1bpp", "png", HandleGbaToPngCommand },
{ "4bpp", "png", HandleGbaToPngCommand },
{ "8bpp", "png", HandleGbaToPngCommand },
{ "png", "1bpp", HandlePngToGbaCommand },
{ "png", "4bpp", HandlePngToGbaCommand },
{ "png", "8bpp", HandlePngToGbaCommand },
{ "png", "gbapal", HandlePngToGbaPaletteCommand },
{ "png", "pal", HandlePngToJascPaletteCommand },
{ "gbapal", "pal", HandleGbaToJascPaletteCommand },
{ "pal", "gbapal", HandleJascToGbaPaletteCommand },
{ "latfont", "png", HandleLatinFontToPngCommand },
{ "png", "latfont", HandlePngToLatinFontCommand },
{ "hwjpnfont", "png", HandleHalfwidthJapaneseFontToPngCommand },
{ "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 },
{ NULL, NULL, NULL }
};
char *inputPath = argv[1];
char *outputPath = argv[2];
char *inputFileExtension = GetFileExtensionAfterDot(inputPath);
char *outputFileExtension = GetFileExtensionAfterDot(outputPath);
if (inputFileExtension == NULL)
FATAL_ERROR("Input file \"%s\" has no extension.\n", inputPath);
if (outputFileExtension == NULL)
{
outputFileExtension = GetFileExtension(outputPath);
if (*outputFileExtension == '.')
outputFileExtension++;
if (*outputFileExtension == 0)
FATAL_ERROR("Output file \"%s\" has no extension.\n", outputPath);
size_t newOutputPathSize = strlen(inputPath) - strlen(inputFileExtension) + strlen(outputFileExtension);
outputPath = malloc(newOutputPathSize);
if (outputPath == NULL)
FATAL_ERROR("Failed to allocate memory for new output path.\n");
for (int i = 0; i < newOutputPathSize; i++)
{
outputPath[i] = inputPath[i];
if (outputPath[i] == '.')
{
strcpy(&outputPath[i + 1], outputFileExtension);
break;
}
}
}
for (int i = 0; handlers[i].function != NULL; i++)
{
if ((handlers[i].inputFileExtension == NULL || strcmp(handlers[i].inputFileExtension, inputFileExtension) == 0)
&& (handlers[i].outputFileExtension == NULL || strcmp(handlers[i].outputFileExtension, outputFileExtension) == 0))
{
handlers[i].function(inputPath, outputPath, argc, argv);
converted = 1;
break;
}
}
if (outputPath != argv[2])
free(outputPath);
if (!converted)
FATAL_ERROR("Don't know how to convert \"%s\" to \"%s\".\n", argv[1], argv[2]);
return 0;
}

28
tools/gbagfx/options.h Normal file
View File

@ -0,0 +1,28 @@
// Copyright (c) 2018 huderlem
#ifndef OPTIONS_H
#define OPTIONS_H
#include <stdbool.h>
struct GbaToPngOptions {
char *paletteFilePath;
int bitDepth;
bool hasTransparency;
int width;
int metatileWidth;
int metatileHeight;
char *tilemapFilePath;
bool isAffineMap;
};
struct PngToGbaOptions {
int numTiles;
int bitDepth;
int metatileWidth;
int metatileHeight;
char *tilemapFilePath;
bool isAffineMap;
};
#endif // OPTIONS_H

149
tools/gbagfx/rl.c Normal file
View File

@ -0,0 +1,149 @@
// Copyright (c) 2016 YamaArashi
#include <stdlib.h>
#include <stdbool.h>
#include "global.h"
#include "rl.h"
unsigned char *RLDecompress(unsigned char *src, int srcSize, int *uncompressedSize)
{
if (srcSize < 4)
goto fail;
int destSize = (src[3] << 16) | (src[2] << 8) | src[1];
unsigned char *dest = malloc(destSize);
if (dest == NULL)
goto fail;
int srcPos = 4;
int destPos = 0;
for (;;)
{
if (srcPos >= srcSize)
goto fail;
unsigned char flags = src[srcPos++];
bool compressed = ((flags & 0x80) != 0);
if (compressed)
{
int length = (flags & 0x7F) + 3;
unsigned char data = src[srcPos++];
if (destPos + length > destSize)
goto fail;
for (int i = 0; i < length; i++)
dest[destPos++] = data;
}
else
{
int length = (flags & 0x7F) + 1;
if (destPos + length > destSize)
goto fail;
for (int i = 0; i < length; i++)
dest[destPos++] = src[srcPos++];
}
if (destPos == destSize)
{
*uncompressedSize = destSize;
return dest;
}
}
fail:
FATAL_ERROR("Fatal error while decompressing RL file.\n");
}
unsigned char *RLCompress(unsigned char *src, int srcSize, int *compressedSize)
{
if (srcSize <= 0)
goto fail;
int worstCaseDestSize = 4 + srcSize * 2;
// Round up to the next multiple of four.
worstCaseDestSize = (worstCaseDestSize + 3) & ~3;
unsigned char *dest = malloc(worstCaseDestSize);
if (dest == NULL)
goto fail;
// header
dest[0] = 0x30; // RL compression type
dest[1] = (unsigned char)srcSize;
dest[2] = (unsigned char)(srcSize >> 8);
dest[3] = (unsigned char)(srcSize >> 16);
int srcPos = 0;
int destPos = 4;
for (;;)
{
bool compress = false;
int uncompressedStart = srcPos;
int uncompressedLength = 0;
while (srcPos < srcSize && uncompressedLength < (0x7F + 1))
{
compress = (srcPos + 2 < srcSize && src[srcPos] == src[srcPos + 1] && src[srcPos] == src[srcPos + 2]);
if (compress)
break;
srcPos++;
uncompressedLength++;
}
if (uncompressedLength > 0)
{
dest[destPos++] = uncompressedLength - 1;
for (int i = 0; i < uncompressedLength; i++)
dest[destPos++] = src[uncompressedStart + i];
}
if (compress)
{
unsigned char data = src[srcPos];
int compressedLength = 0;
while (compressedLength < (0x7F + 3)
&& srcPos + compressedLength < srcSize
&& src[srcPos + compressedLength] == data)
{
compressedLength++;
}
dest[destPos++] = 0x80 | (compressedLength - 3);
dest[destPos++] = data;
srcPos += compressedLength;
}
if (srcPos == srcSize)
{
// Pad to multiple of 4 bytes.
int remainder = destPos % 4;
if (remainder != 0)
{
for (int i = 0; i < 4 - remainder; i++)
dest[destPos++] = 0;
}
*compressedSize = destPos;
return dest;
}
}
fail:
FATAL_ERROR("Fatal error while compressing RL file.\n");
}

9
tools/gbagfx/rl.h Normal file
View File

@ -0,0 +1,9 @@
// Copyright (c) 2016 YamaArashi
#ifndef RL_H
#define RL_H
unsigned char *RLDecompress(unsigned char *src, int srcSize, int *uncompressedSize);
unsigned char *RLCompress(unsigned char *src, int srcSize, int *compressedSize);
#endif // RL_H

131
tools/gbagfx/util.c Normal file
View File

@ -0,0 +1,131 @@
// Copyright (c) 2015 YamaArashi
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <errno.h>
#include <limits.h>
#include "global.h"
#include "util.h"
bool ParseNumber(char *s, char **end, int radix, int *intValue)
{
char *localEnd;
if (end == NULL)
end = &localEnd;
errno = 0;
const long longValue = strtol(s, end, radix);
if (*end == s)
return false; // not a number
if ((longValue == LONG_MIN || longValue == LONG_MAX) && errno == ERANGE)
return false;
if (longValue > INT_MAX)
return false;
if (longValue < INT_MIN)
return false;
*intValue = (int)longValue;
return true;
}
char *GetFileExtension(char *path)
{
char *extension = path;
while (*extension != 0)
extension++;
while (extension > path && *extension != '.')
extension--;
return extension;
}
char *GetFileExtensionAfterDot(char *path)
{
char *extension = GetFileExtension(path);
if (extension == path)
return NULL;
extension++;
if (*extension == 0)
return NULL;
return extension;
}
unsigned char *ReadWholeFile(char *path, int *size)
{
FILE *fp = fopen(path, "rb");
if (fp == NULL)
FATAL_ERROR("Failed to open \"%s\" for reading.\n", path);
fseek(fp, 0, SEEK_END);
*size = ftell(fp);
unsigned char *buffer = malloc(*size);
if (buffer == NULL)
FATAL_ERROR("Failed to allocate memory for reading \"%s\".\n", path);
rewind(fp);
if (fread(buffer, *size, 1, fp) != 1)
FATAL_ERROR("Failed to read \"%s\".\n", path);
fclose(fp);
return buffer;
}
unsigned char *ReadWholeFileZeroPadded(char *path, int *size, int padAmount)
{
FILE *fp = fopen(path, "rb");
if (fp == NULL)
FATAL_ERROR("Failed to open \"%s\" for reading.\n", path);
fseek(fp, 0, SEEK_END);
*size = ftell(fp);
unsigned char *buffer = calloc(*size + padAmount, 1);
if (buffer == NULL)
FATAL_ERROR("Failed to allocate memory for reading \"%s\".\n", path);
rewind(fp);
if (fread(buffer, *size, 1, fp) != 1)
FATAL_ERROR("Failed to read \"%s\".\n", path);
fclose(fp);
return buffer;
}
void WriteWholeFile(char *path, void *buffer, int bufferSize)
{
FILE *fp = fopen(path, "wb");
if (fp == NULL)
FATAL_ERROR("Failed to open \"%s\" for writing.\n", path);
if (fwrite(buffer, bufferSize, 1, fp) != 1)
FATAL_ERROR("Failed to write to \"%s\".\n", path);
fclose(fp);
}

15
tools/gbagfx/util.h Normal file
View File

@ -0,0 +1,15 @@
// Copyright (c) 2015 YamaArashi
#ifndef UTIL_H
#define UTIL_H
#include <stdbool.h>
bool ParseNumber(char *s, char **end, int radix, int *intValue);
char *GetFileExtension(char *path);
char *GetFileExtensionAfterDot(char *path);
unsigned char *ReadWholeFile(char *path, int *size);
unsigned char *ReadWholeFileZeroPadded(char *path, int *size, int padAmount);
void WriteWholeFile(char *path, void *buffer, int bufferSize);
#endif // UTIL_H