diff --git a/CONTRIBUTORS.rst b/CONTRIBUTORS.rst index ae8a923c..75e29dd0 100644 --- a/CONTRIBUTORS.rst +++ b/CONTRIBUTORS.rst @@ -36,6 +36,8 @@ Other contributors - The OpenBSD Project +- Quint Guvernator + - Sanqui - YamaArashi diff --git a/include/gfx/gb.h b/include/gfx/gb.h index 21a2376c..cdb895f8 100644 --- a/include/gfx/gb.h +++ b/include/gfx/gb.h @@ -12,14 +12,24 @@ #include #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); diff --git a/include/gfx/main.h b/include/gfx/main.h index d6f30e87..a5919b99 100644 --- a/include/gfx/main.h +++ b/include/gfx/main.h @@ -21,10 +21,13 @@ struct Options { 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 RGBColor { 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 @@ struct GBImage { int trim; }; -struct Tilemap { +struct Mapfile { uint8_t *data; int size; }; diff --git a/src/gfx/gb.c b/src/gfx/gb.c index d9b4d085..0e92d576 100644 --- a/src/gfx/gb.c +++ b/src/gfx/gb.c @@ -85,8 +85,87 @@ int get_tile_index(uint8_t *tile, uint8_t **tiles, int num_tiles, int tile_size) 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; @@ -94,6 +173,7 @@ void create_tilemap(const struct Options *opts, struct GBImage *gb, int max_tiles; int num_tiles; int index; + int flags; int gb_size; uint8_t *tile; uint8_t **tiles; @@ -109,19 +189,33 @@ void create_tilemap(const struct Options *opts, struct GBImage *gb, 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]; 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 @@ void create_tilemap(const struct Options *opts, struct GBImage *gb, 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 create_tilemap(const struct Options *opts, struct GBImage *gb, } 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, diff --git a/src/gfx/main.c b/src/gfx/main.c index b58e39f8..7ca526dc 100644 --- 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,21 +30,29 @@ int main(int argc, char *argv[]) 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."; 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 @@ int main(int argc, char *argv[]) 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 @@ int main(int argc, char *argv[]) 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 @@ int main(int argc, char *argv[]) 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,25 +161,45 @@ int main(int argc, char *argv[]) (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) @@ -189,19 +222,35 @@ int main(int argc, char *argv[]) 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"); } } @@ -226,17 +275,20 @@ int main(int argc, char *argv[]) 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); diff --git a/src/gfx/makepng.c b/src/gfx/makepng.c index 38f54d4c..543c4507 100644 --- a/src/gfx/makepng.c +++ b/src/gfx/makepng.c @@ -649,10 +649,16 @@ static void get_text(const struct PNGImage *img, 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; @@ -699,18 +705,30 @@ static void set_text(const struct PNGImage *img, 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); + } if (*png_options->palfile) { text[0].key = "p"; text[0].text = ""; diff --git a/src/gfx/rgbgfx.1 b/src/gfx/rgbgfx.1 index 57a0e100..635206c1 100644 --- 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 @@ The input image may not contain more colors than the selected bit depth 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 @@ The bit depth of the output image (either 1 or 2). 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 @@ Same as 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 @@ -105,6 +132,14 @@ The following creates a planar 2bpp file with only unique tiles, and its 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 .D1 $ rgbgfx in.png