shithub: rgbds

Download patch

ref: 21aea281bdd1a5b8aa0cb4cc73c58b3b9e86b9e7
parent: b2c1f6122eb5ed5a9070c8cf0450f0cf826f6edd
author: Quint Guvernator <quint@guvernator.net>
date: Sun May 19 16:16:47 EDT 2019

gfx: Add mirrored tile check when generating tilemap

--- a/CONTRIBUTORS.rst
+++ b/CONTRIBUTORS.rst
@@ -36,6 +36,8 @@
 
 - The OpenBSD Project <http://www.openbsd.org>
 
+- Quint Guvernator <quint@guvernator.net>
+
 - Sanqui <gsanky@gmail.com>
 
 - YamaArashi <shadow962@live.com>
--- a/include/gfx/gb.h
+++ b/include/gfx/gb.h
@@ -12,14 +12,24 @@
 #include <stdint.h>
 #include "gfx/main.h"
 
+#define XFLIP 0x40
+#define YFLIP 0x20
+
 void raw_to_gb(const struct RawIndexedImage *raw_image, struct GBImage *gb);
 void output_file(const struct Options *opts, const struct GBImage *gb);
 int get_tile_index(uint8_t *tile, uint8_t **tiles, int num_tiles,
 		   int tile_size);
-void create_tilemap(const struct Options *opts, struct GBImage *gb,
-		    struct Tilemap *tilemap);
+uint8_t reverse_bits(uint8_t b);
+void xflip(uint8_t *tile, uint8_t *tile_xflip, int tile_size);
+void yflip(uint8_t *tile, uint8_t *tile_yflip, int tile_size);
+int get_mirrored_tile_index(uint8_t *tile, uint8_t **tiles, int num_tiles,
+			    int tile_size, int *flags);
+void create_mapfiles(const struct Options *opts, struct GBImage *gb,
+		     struct Mapfile *tilemap, struct Mapfile *attrmap);
 void output_tilemap_file(const struct Options *opts,
-			 const struct Tilemap *tilemap);
+			 const struct Mapfile *tilemap);
+void output_attrmap_file(const struct Options *opts,
+			 const struct Mapfile *attrmap);
 void output_palette_file(const struct Options *opts,
 			 const struct RawIndexedImage *raw_image);
 
--- a/include/gfx/main.h
+++ b/include/gfx/main.h
@@ -21,10 +21,13 @@
 	bool hardfix;
 	bool fix;
 	bool horizontal;
+	bool mirror;
 	bool unique;
 	int trim;
-	char *mapfile;
-	bool mapout;
+	char *tilemapfile;
+	bool tilemapout;
+	char *attrmapfile;
+	bool attrmapout;
 	char *palfile;
 	bool palout;
 	char *outfile;
@@ -40,8 +43,10 @@
 struct ImageOptions {
 	bool horizontal;
 	int trim;
-	char *mapfile;
-	bool mapout;
+	char *tilemapfile;
+	bool tilemapout;
+	char *attrmapfile;
+	bool attrmapout;
 	char *palfile;
 	bool palout;
 };
@@ -71,7 +76,7 @@
 	int trim;
 };
 
-struct Tilemap {
+struct Mapfile {
 	uint8_t *data;
 	int size;
 };
--- a/src/gfx/gb.c
+++ b/src/gfx/gb.c
@@ -85,9 +85,88 @@
 	return -1;
 }
 
-void create_tilemap(const struct Options *opts, struct GBImage *gb,
-		    struct Tilemap *tilemap)
+uint8_t reverse_bits(uint8_t b)
 {
+	uint8_t rev = 0;
+
+	rev |= (b & 0x80) >> 7;
+	rev |= (b & 0x40) >> 5;
+	rev |= (b & 0x20) >> 3;
+	rev |= (b & 0x10) >> 1;
+	rev |= (b & 0x08) << 1;
+	rev |= (b & 0x04) << 3;
+	rev |= (b & 0x02) << 5;
+	rev |= (b & 0x01) << 7;
+	return rev;
+}
+
+void xflip(uint8_t *tile, uint8_t *tile_xflip, int tile_size)
+{
+	int i;
+
+	for (i = 0; i < tile_size; i++)
+		tile_xflip[i] = reverse_bits(tile[i]);
+}
+
+void yflip(uint8_t *tile, uint8_t *tile_yflip, int tile_size)
+{
+	int i;
+
+	for (i = 0; i < tile_size; i++)
+		tile_yflip[i] = tile[(tile_size - i - 1) ^ (depth - 1)];
+}
+
+/*
+ * get_mirrored_tile_index looks for `tile` in tile array `tiles`, also
+ * checking x-, y-, and xy-mirrored versions of `tile`. If one is found,
+ * `*flags` is set according to the type of mirroring and the index of the
+ * matched tile is returned. If no match is found, -1 is returned.
+ */
+int get_mirrored_tile_index(uint8_t *tile, uint8_t **tiles, int num_tiles,
+			    int tile_size, int *flags)
+{
+	int index;
+	uint8_t *tile_xflip;
+	uint8_t *tile_yflip;
+
+	index = get_tile_index(tile, tiles, num_tiles, tile_size);
+	if (index >= 0) {
+		*flags = 0;
+		return index;
+	}
+
+	tile_yflip = malloc(tile_size);
+	yflip(tile, tile_yflip, tile_size);
+	index = get_tile_index(tile_yflip, tiles, num_tiles, tile_size);
+	if (index >= 0) {
+		*flags = YFLIP;
+		free(tile_yflip);
+		return index;
+	}
+
+	tile_xflip = malloc(tile_size);
+	xflip(tile, tile_xflip, tile_size);
+	index = get_tile_index(tile_xflip, tiles, num_tiles, tile_size);
+	if (index >= 0) {
+		*flags = XFLIP;
+		free(tile_yflip);
+		free(tile_xflip);
+		return index;
+	}
+
+	yflip(tile_xflip, tile_yflip, tile_size);
+	index = get_tile_index(tile_yflip, tiles, num_tiles, tile_size);
+	if (index >= 0)
+		*flags = XFLIP | YFLIP;
+
+	free(tile_yflip);
+	free(tile_xflip);
+	return index;
+}
+
+void create_mapfiles(const struct Options *opts, struct GBImage *gb,
+		     struct Mapfile *tilemap, struct Mapfile *attrmap)
+{
 	int i, j;
 	int gb_i;
 	int tile_size;
@@ -94,6 +173,7 @@
 	int max_tiles;
 	int num_tiles;
 	int index;
+	int flags;
 	int gb_size;
 	uint8_t *tile;
 	uint8_t **tiles;
@@ -109,11 +189,20 @@
 	tiles = calloc(max_tiles, sizeof(uint8_t *));
 	num_tiles = 0;
 
-	tilemap->data = calloc(max_tiles, sizeof(uint8_t));
-	tilemap->size = 0;
+	if (*opts->tilemapfile) {
+		tilemap->data = calloc(max_tiles, sizeof(uint8_t));
+		tilemap->size = 0;
+	}
 
+	if (*opts->attrmapfile) {
+		attrmap->data = calloc(max_tiles, sizeof(uint8_t));
+		attrmap->size = 0;
+	}
+
+
 	gb_i = 0;
 	while (gb_i < gb_size) {
+		flags = 0;
 		tile = malloc(tile_size);
 		for (i = 0; i < tile_size; i++) {
 			tile[i] = gb->data[gb_i];
@@ -120,8 +209,13 @@
 			gb_i++;
 		}
 		if (opts->unique) {
-			index = get_tile_index(tile, tiles, num_tiles,
-					       tile_size);
+			if (opts->mirror) {
+				index = get_mirrored_tile_index(tile, tiles, num_tiles,
+								tile_size, &flags);
+			} else {
+				index = get_tile_index(tile, tiles, num_tiles,
+						       tile_size);
+			}
 			if (index < 0) {
 				index = num_tiles;
 				tiles[num_tiles] = tile;
@@ -132,8 +226,14 @@
 			tiles[num_tiles] = tile;
 			num_tiles++;
 		}
-		tilemap->data[tilemap->size] = index;
-		tilemap->size++;
+		if (*opts->tilemapfile) {
+			tilemap->data[tilemap->size] = index;
+			tilemap->size++;
+		}
+		if (*opts->attrmapfile) {
+			attrmap->data[attrmap->size] = flags;
+			attrmap->size++;
+		}
 	}
 
 	if (opts->unique) {
@@ -154,19 +254,35 @@
 }
 
 void output_tilemap_file(const struct Options *opts,
-			 const struct Tilemap *tilemap)
+			 const struct Mapfile *tilemap)
 {
 	FILE *f;
 
-	f = fopen(opts->mapfile, "wb");
+	f = fopen(opts->tilemapfile, "wb");
 	if (!f)
-		err(1, "Opening tilemap file '%s' failed", opts->mapfile);
+		err(1, "Opening tilemap file '%s' failed", opts->tilemapfile);
 
 	fwrite(tilemap->data, 1, tilemap->size, f);
 	fclose(f);
 
-	if (opts->mapout)
-		free(opts->mapfile);
+	if (opts->tilemapout)
+		free(opts->tilemapfile);
+}
+
+void output_attrmap_file(const struct Options *opts,
+			 const struct Mapfile *attrmap)
+{
+	FILE *f;
+
+	f = fopen(opts->attrmapfile, "wb");
+	if (!f)
+		err(1, "Opening attrmap file '%s' failed", opts->attrmapfile);
+
+	fwrite(attrmap->data, 1, attrmap->size, f);
+	fclose(f);
+
+	if (opts->attrmapout)
+		free(opts->attrmapfile);
 }
 
 void output_palette_file(const struct Options *opts,
--- a/src/gfx/main.c
+++ b/src/gfx/main.c
@@ -18,8 +18,8 @@
 static void print_usage(void)
 {
 	printf(
-"usage: rgbgfx [-DFfhPTuVv] [-d #] [-o outfile] [-p palfile] [-t mapfile]\n"
-"              [-x #] infile\n");
+"usage: rgbgfx [-ADFfhmPTuVv] [-o outfile] [-a attrmap] [-d #] [-p palfile]\n"
+"              [-t tilemap] [-x #] infile\n");
 	exit(1);
 }
 
@@ -30,7 +30,8 @@
 	struct ImageOptions png_options = {0};
 	struct RawIndexedImage *raw_image;
 	struct GBImage gb = {0};
-	struct Tilemap tilemap = {0};
+	struct Mapfile tilemap = {0};
+	struct Mapfile attrmap = {0};
 	char *ext;
 	const char *errmsg = "Warning: The PNG's %s setting is not the same as the setting defined on the command line.";
 
@@ -37,14 +38,21 @@
 	if (argc == 1)
 		print_usage();
 
-	opts.mapfile = "";
+	opts.tilemapfile = "";
+	opts.attrmapfile = "";
 	opts.palfile = "";
 	opts.outfile = "";
 
 	depth = 2;
 
-	while ((ch = getopt(argc, argv, "Dd:Ffho:Tt:uPp:Vvx:")) != -1) {
+	while ((ch = getopt(argc, argv, "Aa:Dd:Ffhmo:Tt:uPp:Vvx:")) != -1) {
 		switch (ch) {
+		case 'A':
+			opts.attrmapout = true;
+			break;
+		case 'a':
+			opts.attrmapfile = optarg;
+			break;
 		case 'D':
 			opts.debug = true;
 			break;
@@ -60,6 +68,10 @@
 		case 'h':
 			opts.horizontal = true;
 			break;
+		case 'm':
+			opts.mirror = true;
+			opts.unique = true;
+			break;
 		case 'o':
 			opts.outfile = optarg;
 			break;
@@ -70,10 +82,10 @@
 			opts.palfile = optarg;
 			break;
 		case 'T':
-			opts.mapout = true;
+			opts.tilemapout = true;
 			break;
 		case 't':
-			opts.mapfile = optarg;
+			opts.tilemapfile = optarg;
 			break;
 		case 'u':
 			opts.unique = true;
@@ -107,7 +119,8 @@
 
 	raw_image = input_png_file(&opts, &png_options);
 
-	png_options.mapfile = "";
+	png_options.tilemapfile = "";
+	png_options.attrmapfile = "";
 	png_options.palfile = "";
 
 	if (png_options.horizontal != opts.horizontal) {
@@ -148,26 +161,46 @@
 		     (raw_image->width / 8) * (raw_image->height / 8) - 1);
 	}
 
-	if (strcmp(png_options.mapfile, opts.mapfile) != 0) {
+	if (strcmp(png_options.tilemapfile, opts.tilemapfile) != 0) {
 		if (opts.verbose)
 			warnx(errmsg, "tilemap file");
 
 		if (opts.hardfix)
-			png_options.mapfile = opts.mapfile;
+			png_options.tilemapfile = opts.tilemapfile;
 	}
-	if (!*opts.mapfile)
-		opts.mapfile = png_options.mapfile;
+	if (!*opts.tilemapfile)
+		opts.tilemapfile = png_options.tilemapfile;
 
-	if (png_options.mapout != opts.mapout) {
+	if (png_options.tilemapout != opts.tilemapout) {
 		if (opts.verbose)
 			warnx(errmsg, "tilemap file");
 
 		if (opts.hardfix)
-			png_options.mapout = opts.mapout;
+			png_options.tilemapout = opts.tilemapout;
 	}
-	if (png_options.mapout)
-		opts.mapout = png_options.mapout;
+	if (png_options.tilemapout)
+		opts.tilemapout = png_options.tilemapout;
 
+	if (strcmp(png_options.attrmapfile, opts.attrmapfile) != 0) {
+		if (opts.verbose)
+			warnx(errmsg, "attrmap file");
+
+		if (opts.hardfix)
+			png_options.attrmapfile = opts.attrmapfile;
+	}
+	if (!*opts.attrmapfile)
+		opts.attrmapfile = png_options.attrmapfile;
+
+	if (png_options.attrmapout != opts.attrmapout) {
+		if (opts.verbose)
+			warnx(errmsg, "attrmap file");
+
+		if (opts.hardfix)
+			png_options.attrmapout = opts.attrmapout;
+	}
+	if (png_options.attrmapout)
+		opts.attrmapout = png_options.attrmapout;
+
 	if (strcmp(png_options.palfile, opts.palfile) != 0) {
 		if (opts.verbose)
 			warnx(errmsg, "palette file");
@@ -189,22 +222,38 @@
 	if (png_options.palout)
 		opts.palout = png_options.palout;
 
-	if (!*opts.mapfile && opts.mapout) {
+	if (!*opts.tilemapfile && opts.tilemapout) {
 		ext = strrchr(opts.infile, '.');
 
 		if (ext != NULL) {
 			size = ext - opts.infile + 9;
-			opts.mapfile = malloc(size);
-			strncpy(opts.mapfile, opts.infile, size);
-			*strrchr(opts.mapfile, '.') = '\0';
-			strcat(opts.mapfile, ".tilemap");
+			opts.tilemapfile = malloc(size);
+			strncpy(opts.tilemapfile, opts.infile, size);
+			*strrchr(opts.tilemapfile, '.') = '\0';
+			strcat(opts.tilemapfile, ".tilemap");
 		} else {
-			opts.mapfile = malloc(strlen(opts.infile) + 9);
-			strcpy(opts.mapfile, opts.infile);
-			strcat(opts.mapfile, ".tilemap");
+			opts.tilemapfile = malloc(strlen(opts.infile) + 9);
+			strcpy(opts.tilemapfile, opts.infile);
+			strcat(opts.tilemapfile, ".tilemap");
 		}
 	}
 
+	if (!*opts.attrmapfile && opts.attrmapout) {
+		ext = strrchr(opts.infile, '.');
+
+		if (ext != NULL) {
+			size = ext - opts.infile + 9;
+			opts.attrmapfile = malloc(size);
+			strncpy(opts.attrmapfile, opts.infile, size);
+			*strrchr(opts.attrmapfile, '.') = '\0';
+			strcat(opts.attrmapfile, ".attrmap");
+		} else {
+			opts.attrmapfile = malloc(strlen(opts.infile) + 9);
+			strcpy(opts.attrmapfile, opts.infile);
+			strcat(opts.attrmapfile, ".attrmap");
+		}
+	}
+
 	if (!*opts.palfile && opts.palout) {
 		ext = strrchr(opts.infile, '.');
 
@@ -226,16 +275,19 @@
 	gb.trim = opts.trim;
 	gb.horizontal = opts.horizontal;
 
-	if (*opts.outfile || *opts.mapfile) {
+	if (*opts.outfile || *opts.tilemapfile || *opts.attrmapfile) {
 		raw_to_gb(raw_image, &gb);
-		create_tilemap(&opts, &gb, &tilemap);
+		create_mapfiles(&opts, &gb, &tilemap, &attrmap);
 	}
 
 	if (*opts.outfile)
 		output_file(&opts, &gb);
 
-	if (*opts.mapfile)
+	if (*opts.tilemapfile)
 		output_tilemap_file(&opts, &tilemap);
+
+	if (*opts.attrmapfile)
+		output_attrmap_file(&opts, &attrmap);
 
 	if (*opts.palfile)
 		output_palette_file(&opts, raw_image);
--- a/src/gfx/makepng.c
+++ b/src/gfx/makepng.c
@@ -649,11 +649,17 @@
 			png_options->trim = strtoul(text[i].text, NULL, 0);
 			png_free_data(img->png, img->info, PNG_FREE_TEXT, i);
 		} else if (strcmp(text[i].key, "t") == 0) {
-			png_options->mapfile = text[i].text;
+			png_options->tilemapfile = text[i].text;
 			png_free_data(img->png, img->info, PNG_FREE_TEXT, i);
 		} else if (strcmp(text[i].key, "T") == 0 && !*text[i].text) {
-			png_options->mapout = true;
+			png_options->tilemapout = true;
 			png_free_data(img->png, img->info, PNG_FREE_TEXT, i);
+		} else if (strcmp(text[i].key, "a") == 0) {
+			png_options->attrmapfile = text[i].text;
+			png_free_data(img->png, img->info, PNG_FREE_TEXT, i);
+		} else if (strcmp(text[i].key, "A") == 0 && !*text[i].text) {
+			png_options->attrmapout = true;
+			png_free_data(img->png, img->info, PNG_FREE_TEXT, i);
 		} else if (strcmp(text[i].key, "p") == 0) {
 			png_options->palfile = text[i].text;
 			png_free_data(img->png, img->info, PNG_FREE_TEXT, i);
@@ -699,14 +705,26 @@
 		text[0].compression = PNG_TEXT_COMPRESSION_NONE;
 		png_set_text(img->png, img->info, text, 1);
 	}
-	if (*png_options->mapfile) {
+	if (*png_options->tilemapfile) {
 		text[0].key = "t";
 		text[0].text = "";
 		text[0].compression = PNG_TEXT_COMPRESSION_NONE;
 		png_set_text(img->png, img->info, text, 1);
 	}
-	if (png_options->mapout) {
+	if (png_options->tilemapout) {
 		text[0].key = "T";
+		text[0].text = "";
+		text[0].compression = PNG_TEXT_COMPRESSION_NONE;
+		png_set_text(img->png, img->info, text, 1);
+	}
+	if (*png_options->attrmapfile) {
+		text[0].key = "a";
+		text[0].text = "";
+		text[0].compression = PNG_TEXT_COMPRESSION_NONE;
+		png_set_text(img->png, img->info, text, 1);
+	}
+	if (png_options->attrmapout) {
+		text[0].key = "A";
 		text[0].text = "";
 		text[0].compression = PNG_TEXT_COMPRESSION_NONE;
 		png_set_text(img->png, img->info, text, 1);
--- a/src/gfx/rgbgfx.1
+++ b/src/gfx/rgbgfx.1
@@ -13,11 +13,12 @@
 .Nd Game Boy graphics converter
 .Sh SYNOPSIS
 .Nm rgbgfx
-.Op Fl DfFhPTVv
+.Op Fl ADfFhmPTuVv
 .Op Fl o Ar outfile
+.Op Fl a Ar attrmap
 .Op Fl d Ar depth
 .Op Fl p Ar palfile
-.Op Fl t Ar mapfile
+.Op Fl t Ar tilemap
 .Op Fl x Ar tiles
 .Ar file
 .Sh DESCRIPTION
@@ -47,6 +48,19 @@
 allows. Transparent pixels are set to palette index 0.
 .Sh ARGUMENTS
 .Bl -tag -width Ds
+.It Fl a Ar attrmap
+Generate a file of tile mirroring attributes for OAM or (CGB-only) background
+tiles. For each tile in the input file, a byte is written representing the
+dimensions that the associated tile in the output file should be mirrored.
+Useful in combination with
+.Fl m
+to keep track the mirror direction of mirrored duplicate tiles.
+.It Fl A
+Same as
+.Fl a ,
+but the attrmap file output name is made by taking the input filename, removing
+the file extension, and appending
+.Pa .attrmap .
 .It Fl D
 Debug features are enabled.
 .It Fl f
@@ -61,6 +75,12 @@
 By default, the bit depth is 2 (two bits per pixel).
 .It Fl h
 Lay out tiles horizontally rather than vertically.
+.It Fl m
+Truncate tiles by checking for tiles that are mirrored versions of others and
+omitting these from the output file. Useful with tilemaps and attrmaps together
+to keep track of the duplicated tiles and the dimension mirrored. Tiles are
+checked for horizontal, vertical, and horizontal-vertical mirroring. Implies
+.Fl u .
 .It Fl o Ar outfile
 The name of the output file.
 .It Fl p Ar palfile
@@ -74,17 +94,24 @@
 but the palette file output name is made by taking the input PNG file's
 filename, removing the file extension, and appending
 .Pa .pal .
-.It Fl t Ar mapfile
-If any tiles are the same, don't place the repeat tiles in the output file, and
-make a tilemap file.
+.It Fl t Ar tilemap
+Generate a file of tile indices. For each tile in the input file, a byte is
+written representing the index of the associated tile in the output file.
+Useful in combination with
+.Fl u
+or
+.Fl m
+to keep track of duplicate tiles.
 .It Fl T
 Same as
 .Fl t ,
-but the tilemap file output name is made by taking the input filename,
-removing the file extension, and appending
+but the tilemap file output name is made by taking the input filename, removing
+the file extension, and appending
 .Pa .tilemap .
 .It Fl u
-Truncate repeated tiles. Useful with tilemaps.
+Truncate tiles by checking for tiles that are exact duplicates of others and
+omitting these from the output file. Useful with tilemaps to keep track of the
+duplicated tiles.
 .It Fl V
 Print the version of the program and exit.
 .It Fl v
@@ -104,6 +131,14 @@
 .Pa out.tilemap :
 .Pp
 .D1 $ rgbgfx -T -u -o out.2bpp in.png
+.Pp
+The following creates a planar 2bpp file with only unique tiles (accounting for
+tile mirroring) and its associated tilemap
+.Pa out.tilemap
+and attrmap
+.Pa out.attrmap :
+.Pp
+.D1 $ rgbgfx -A -T -m -o out.2bpp in.png
 .Pp
 The following will do nothing:
 .Pp