Implement rgbgfx -O (#1240)

This commit is contained in:
Rangi
2023-11-21 10:19:44 -05:00
committed by GitHub
parent 99671b8eb5
commit 083a82f6d1
4 changed files with 80 additions and 51 deletions

View File

@@ -10,6 +10,7 @@ _rgbgfx_completions() {
[V]="version:normal" [V]="version:normal"
[C]="color-curve:normal" [C]="color-curve:normal"
[m]="mirror-tiles:normal" [m]="mirror-tiles:normal"
[O]="group-outputs:normal"
[u]="unique-tiles:normal" [u]="unique-tiles:normal"
[v]="verbose:normal" [v]="verbose:normal"
[Z]="columns:normal" [Z]="columns:normal"

View File

@@ -16,6 +16,7 @@ local args=(
'(-a --attr-map -A --output-attr-map)'{-A,--output-attr-map}'[Shortcut for -a <file>.attrmap]' '(-a --attr-map -A --output-attr-map)'{-A,--output-attr-map}'[Shortcut for -a <file>.attrmap]'
'(-C --color-curve)'{-C,--color-curve}'[Generate palettes using GBC color curve]' '(-C --color-curve)'{-C,--color-curve}'[Generate palettes using GBC color curve]'
'(-m --mirror-tiles)'{-m,--mirror-tiles}'[Eliminate mirrored tiles from output]' '(-m --mirror-tiles)'{-m,--mirror-tiles}'[Eliminate mirrored tiles from output]'
'(-O --group-outputs)'{-O,--group-outputs}'[Base "shortcut" options on the output path, not input]'
'(-p --palette -P --output-palette)'{-P,--output-palette}'[Shortcut for -p <file>.pal]' '(-p --palette -P --output-palette)'{-P,--output-palette}'[Shortcut for -p <file>.pal]'
'(-q --palette-map -Q --output-palette-map)'{-Q,--output-palette-map}'[Shortcut for -p <file>.palmap]' '(-q --palette-map -Q --output-palette-map)'{-Q,--output-palette-map}'[Shortcut for -p <file>.palmap]'
'(-t --tilemap -T --output-tilemap)'{-T,--output-tilemap}'[Shortcut for -t <file>.tilemap]' '(-t --tilemap -T --output-tilemap)'{-T,--output-tilemap}'[Shortcut for -t <file>.tilemap]'

View File

@@ -10,7 +10,7 @@
.Nd Game Boy graphics converter .Nd Game Boy graphics converter
.Sh SYNOPSIS .Sh SYNOPSIS
.Nm .Nm
.Op Fl CmuVZ .Op Fl CmOuVZ
.Op Fl v Op Fl v No ... .Op Fl v Op Fl v No ...
.Op Fl a Ar attrmap | Fl A .Op Fl a Ar attrmap | Fl A
.Op Fl b Ar base_ids .Op Fl b Ar base_ids
@@ -100,11 +100,8 @@ follows the same order
and has the same size. and has the same size.
.It Fl A , Fl Fl output-attr-map .It Fl A , Fl Fl output-attr-map
Same as Same as
.Fl a Ar path , .Fl a Ar base_path Ns .attrmap
where .Pq see Sx Automatic output paths .
.Ar path
is the input image's path with the extension set to
.Pa .attrmap .
.It Fl b Ar base_ids , Fl Fl base-tiles Ar base_ids .It Fl b Ar base_ids , Fl Fl base-tiles Ar base_ids
Set the base IDs for tile map output. Set the base IDs for tile map output.
.Ar base_ids .Ar base_ids
@@ -207,6 +204,13 @@ This may not be more than 256.
.Pp .Pp
Note that attribute map output only has 3 bits for the palette ID, so a limit higher than 8 may yield incomplete data unless relying on a palette map Note that attribute map output only has 3 bits for the palette ID, so a limit higher than 8 may yield incomplete data unless relying on a palette map
.Pq see Fl q . .Pq see Fl q .
.It Fl O , Fl Fl group-outputs
Sets the
.Sq base path
to be the output tile data path from
.Fl o
instead of the input image path
.Pq see Sx Automatic output paths .
.It Fl o Ar out_file , Fl Fl output Ar out_file .It Fl o Ar out_file , Fl Fl output Ar out_file
Output the tile data in native 2bpp format or in 1bpp Output the tile data in native 2bpp format or in 1bpp
.Pq depending on Fl d .Pq depending on Fl d
@@ -215,21 +219,15 @@ to this file.
Output the image's palette set to this file. Output the image's palette set to this file.
.It Fl P , Fl Fl output-palette .It Fl P , Fl Fl output-palette
Same as Same as
.Fl p Ar path , .Fl p Ar base_path Ns .pal
where .Pq see Sx Automatic output paths .
.Ar path
is the input image's path with the extension set to
.Pa .pal .
.It Fl q Ar pal_file , Fl Fl palette-map Ar pal_file .It Fl q Ar pal_file , Fl Fl palette-map Ar pal_file
Output the image's palette map to this file. Output the image's palette map to this file.
This is useful if the input image contains more than 8 palettes, as the attribute map only contains the lower 3 bits of the palette indices. This is useful if the input image contains more than 8 palettes, as the attribute map only contains the lower 3 bits of the palette indices.
.It Fl Q , Fl Fl output-palette-map .It Fl Q , Fl Fl output-palette-map
Same as Same as
.Fl q Ar path , .Fl q Ar base_path Ns .palmap
where .Pq see Sx Automatic output paths .
.Ar path
is the input image's path with the extension set to
.Pa .palmap .
.It Fl r Ar width , Fl Fl reverse Ar width .It Fl r Ar width , Fl Fl reverse Ar width
Switches Switches
.Nm .Nm
@@ -264,11 +262,8 @@ and/or
to keep track of duplicate tiles. to keep track of duplicate tiles.
.It Fl T , Fl Fl output-tilemap .It Fl T , Fl Fl output-tilemap
Same as Same as
.Fl t Ar path , .Fl t Ar base_path Ns .tilemap
where .Pq see Sx Automatic output paths .
.Ar path
is the input image's path with the extension set to
.Pa .tilemap .
.It Fl u , Fl Fl unique-tiles .It Fl u , Fl Fl unique-tiles
Deduplicate identical tiles. Only one of each unique tile will be saved in the tile data file. Deduplicate identical tiles. Only one of each unique tile will be saved in the tile data file.
Useful with a tile map Useful with a tile map
@@ -533,6 +528,28 @@ The contents of individual bytes follows the GBC's native format:
.El .El
.Pp .Pp
Note that if more than 8 palettes are used, only the lowest 3 bits of the palette ID are output. Note that if more than 8 palettes are used, only the lowest 3 bits of the palette ID are output.
.Ss Automatic output paths
For convenience,
.Nm
provides shortcuts to generate all files in the same directory.
This is done by using the uppercase version of a flag
.Pq for example, Fl A No instead of Fl a .
The
.Ar base_path
is the input image path
.Pq or the output tile data path from Fl o , No if Fl O No was given
with its extension, if any, removed.
.Pp
For example, these two are equivalent:
.Bd -literal -indent Ds
$ rgbgfx img/player.png -o build/player.2bpp -P
$ rgbgfx img/player.png -o build/player.2bpp -p img/player.pal
.Pp
And so are these two:
.Bd -literal -indent Ds
$ rgbgfx img/player.png -o build/player.2bpp -O -P
$ rgbgfx img/player.png -o build/player.2bpp -p build/player.pal
.El
.Sh REVERSE MODE .Sh REVERSE MODE
.Nm .Nm
can produce a PNG image from valid data. can produce a PNG image from valid data.

View File

@@ -34,7 +34,16 @@
using namespace std::literals::string_view_literals; using namespace std::literals::string_view_literals;
Options options; Options options;
char const *externalPalSpec = nullptr;
static struct LocalOptions {
char const *externalPalSpec;
bool autoAttrmap;
bool autoTilemap;
bool autoPalettes;
bool autoPalmap;
bool groupOutputs;
} localOptions;
static uintmax_t nbErrors; static uintmax_t nbErrors;
[[noreturn]] void giveUp() { [[noreturn]] void giveUp() {
@@ -91,7 +100,7 @@ void Options::verbosePrint(uint8_t level, char const *fmt, ...) const {
} }
// Short options // Short options
static char const *optstring = "-Aa:b:Cc:Dd:FfhL:mN:n:o:Pp:Qq:r:s:Tt:U:uVvx:Z"; static char const *optstring = "-Aa:b:Cc:Dd:FfhL:mN:n:Oo:Pp:Qq:r:s:Tt:U:uVvx:Z";
/* /*
* Equivalent long options * Equivalent long options
@@ -118,6 +127,7 @@ static struct option const longopts[] = {
{"mirror-tiles", no_argument, NULL, 'm'}, {"mirror-tiles", no_argument, NULL, 'm'},
{"nb-tiles", required_argument, NULL, 'N'}, {"nb-tiles", required_argument, NULL, 'N'},
{"nb-palettes", required_argument, NULL, 'n'}, {"nb-palettes", required_argument, NULL, 'n'},
{"group-outputs", no_argument, NULL, 'O'},
{"output", required_argument, NULL, 'o'}, {"output", required_argument, NULL, 'o'},
{"output-palette", no_argument, NULL, 'P'}, {"output-palette", no_argument, NULL, 'P'},
{"palette", required_argument, NULL, 'p'}, {"palette", required_argument, NULL, 'p'},
@@ -136,7 +146,7 @@ static struct option const longopts[] = {
}; };
static void printUsage(void) { static void printUsage(void) {
fputs("Usage: rgbgfx [-r stride] [-CmuVZ] [-v [-v ...]] [-a <attr_map> | -A]\n" fputs("Usage: rgbgfx [-r stride] [-CmOuVZ] [-v [-v ...]] [-a <attr_map> | -A]\n"
" [-b <base_ids>] [-c <colors>] [-d <depth>] [-L <slice>] [-N <nb_tiles>]\n" " [-b <base_ids>] [-c <colors>] [-d <depth>] [-L <slice>] [-N <nb_tiles>]\n"
" [-n <nb_pals>] [-o <out_file>] [-p <pal_file> | -P] [-q <pal_map> | -Q]\n" " [-n <nb_pals>] [-o <out_file>] [-p <pal_file> | -P] [-q <pal_map> | -Q]\n"
" [-s <nb_colors>] [-t <tile_map> | -T] [-x <nb_tiles>] <file>\n" " [-s <nb_colors>] [-t <tile_map> | -T] [-x <nb_tiles>] <file>\n"
@@ -321,23 +331,21 @@ static std::vector<size_t> readAtFile(std::filesystem::path const &path,
} }
/* /*
* Parses an arg vector, modifying `options` as options are read. * Parses an arg vector, modifying `options` and `localOptions` as options are read.
* The three booleans are for the "auto path" flags, since their processing must be deferred to the * The `localOptions` struct is for flags which must be processed after the option parsing finishes.
* end of option parsing.
* *
* Returns NULL if the vector was fully parsed, or a pointer (which is part of the arg vector) to an * Returns NULL if the vector was fully parsed, or a pointer (which is part of the arg vector) to an
* "at-file" path if one is encountered. * "at-file" path if one is encountered.
*/ */
static char *parseArgv(int argc, char **argv, bool &autoAttrmap, bool &autoTilemap, static char *parseArgv(int argc, char **argv) {
bool &autoPalettes, bool &autoPalmap) {
for (int ch; (ch = musl_getopt_long_only(argc, argv, optstring, longopts, nullptr)) != -1;) { for (int ch; (ch = musl_getopt_long_only(argc, argv, optstring, longopts, nullptr)) != -1;) {
char *arg = musl_optarg; // Make a copy for scanning char *arg = musl_optarg; // Make a copy for scanning
switch (ch) { switch (ch) {
case 'A': case 'A':
autoAttrmap = true; localOptions.autoAttrmap = true;
break; break;
case 'a': case 'a':
autoAttrmap = false; localOptions.autoAttrmap = false;
if (options.attrmap.has_value()) if (options.attrmap.has_value())
warning("Overriding attrmap file %s", options.attrmap->c_str()); warning("Overriding attrmap file %s", options.attrmap->c_str());
options.attrmap = musl_optarg; options.attrmap = musl_optarg;
@@ -385,7 +393,7 @@ static char *parseArgv(int argc, char **argv, bool &autoAttrmap, bool &autoTilem
// size to be split; thus, we defer that // size to be split; thus, we defer that
// TODO: this does not validate the `fmt` part of any external spec but the last // TODO: this does not validate the `fmt` part of any external spec but the last
// one, but I guess that's okay // one, but I guess that's okay
externalPalSpec = musl_optarg; localOptions.externalPalSpec = musl_optarg;
} }
break; break;
case 'd': case 'd':
@@ -481,25 +489,28 @@ static char *parseArgv(int argc, char **argv, bool &autoAttrmap, bool &autoTilem
error("Number of palettes (-n) may not be 0!"); error("Number of palettes (-n) may not be 0!");
} }
break; break;
case 'O':
localOptions.groupOutputs = true;
break;
case 'o': case 'o':
if (options.output.has_value()) if (options.output.has_value())
warning("Overriding tile data file %s", options.output->c_str()); warning("Overriding tile data file %s", options.output->c_str());
options.output = musl_optarg; options.output = musl_optarg;
break; break;
case 'P': case 'P':
autoPalettes = true; localOptions.autoPalettes = true;
break; break;
case 'p': case 'p':
autoPalettes = false; localOptions.autoPalettes = false;
if (options.palettes.has_value()) if (options.palettes.has_value())
warning("Overriding palettes file %s", options.palettes->c_str()); warning("Overriding palettes file %s", options.palettes->c_str());
options.palettes = musl_optarg; options.palettes = musl_optarg;
break; break;
case 'Q': case 'Q':
autoPalmap = true; localOptions.autoPalmap = true;
break; break;
case 'q': case 'q':
autoPalmap = false; localOptions.autoPalmap = false;
if (options.palmap.has_value()) if (options.palmap.has_value())
warning("Overriding palette map file %s", options.palmap->c_str()); warning("Overriding palette map file %s", options.palmap->c_str());
options.palmap = musl_optarg; options.palmap = musl_optarg;
@@ -525,10 +536,10 @@ static char *parseArgv(int argc, char **argv, bool &autoAttrmap, bool &autoTilem
} }
break; break;
case 'T': case 'T':
autoTilemap = true; localOptions.autoTilemap = true;
break; break;
case 't': case 't':
autoTilemap = false; localOptions.autoTilemap = false;
if (options.tilemap.has_value()) if (options.tilemap.has_value())
warning("Overriding tilemap file %s", options.tilemap->c_str()); warning("Overriding tilemap file %s", options.tilemap->c_str());
options.tilemap = musl_optarg; options.tilemap = musl_optarg;
@@ -577,8 +588,6 @@ static char *parseArgv(int argc, char **argv, bool &autoAttrmap, bool &autoTilem
} }
int main(int argc, char *argv[]) { int main(int argc, char *argv[]) {
bool autoAttrmap = false, autoTilemap = false, autoPalettes = false, autoPalmap = false;
struct AtFileStackEntry { struct AtFileStackEntry {
int parentInd; // Saved offset into parent argv int parentInd; // Saved offset into parent argv
std::vector<char *> argv; // This context's arg pointer vec std::vector<char *> argv; // This context's arg pointer vec
@@ -592,8 +601,7 @@ int main(int argc, char *argv[]) {
int curArgc = argc; int curArgc = argc;
char **curArgv = argv; char **curArgv = argv;
for (;;) { for (;;) {
char *atFileName = char *atFileName = parseArgv(curArgc, curArgv);
parseArgv(curArgc, curArgv, autoAttrmap, autoTilemap, autoPalettes, autoPalmap);
if (atFileName) { if (atFileName) {
// Copy `argv[0]` for error reporting, and because option parsing skips it // Copy `argv[0]` for error reporting, and because option parsing skips it
AtFileStackEntry &stackEntry = AtFileStackEntry &stackEntry =
@@ -649,22 +657,24 @@ int main(int argc, char *argv[]) {
auto autoOutPath = [](bool autoOptEnabled, std::optional<std::filesystem::path> &path, auto autoOutPath = [](bool autoOptEnabled, std::optional<std::filesystem::path> &path,
char const *extension) { char const *extension) {
if (autoOptEnabled) { if (autoOptEnabled) {
if (!options.input.has_value()) { auto image = localOptions.groupOutputs ? options.output : options.input;
fputs("FATAL: No input image specified\n", stderr); if (!image.has_value()) {
fprintf(stderr, "FATAL: No %s specified\n", localOptions.groupOutputs
? "output tile data file" : "input image");
printUsage(); printUsage();
exit(1); exit(1);
} }
path.emplace(*options.input).replace_extension(extension); path.emplace(*image).replace_extension(extension);
} }
}; };
autoOutPath(autoAttrmap, options.attrmap, ".attrmap"); autoOutPath(localOptions.autoAttrmap, options.attrmap, ".attrmap");
autoOutPath(autoTilemap, options.tilemap, ".tilemap"); autoOutPath(localOptions.autoTilemap, options.tilemap, ".tilemap");
autoOutPath(autoPalettes, options.palettes, ".pal"); autoOutPath(localOptions.autoPalettes, options.palettes, ".pal");
autoOutPath(autoPalmap, options.palmap, ".palmap"); autoOutPath(localOptions.autoPalmap, options.palmap, ".palmap");
// Execute deferred external pal spec parsing, now that all other params are known // Execute deferred external pal spec parsing, now that all other params are known
if (externalPalSpec) { if (localOptions.externalPalSpec) {
parseExternalPalSpec(externalPalSpec); parseExternalPalSpec(localOptions.externalPalSpec);
} }
if (options.verbosity >= Options::VERB_CFG) { if (options.verbosity >= Options::VERB_CFG) {