diff --git a/contrib/bash_compl/_rgbgfx.bash b/contrib/bash_compl/_rgbgfx.bash index 0a5b87ed..8b7a9248 100755 --- a/contrib/bash_compl/_rgbgfx.bash +++ b/contrib/bash_compl/_rgbgfx.bash @@ -10,6 +10,7 @@ _rgbgfx_completions() { [V]="version:normal" [C]="color-curve:normal" [m]="mirror-tiles:normal" + [O]="group-outputs:normal" [u]="unique-tiles:normal" [v]="verbose:normal" [Z]="columns:normal" diff --git a/contrib/zsh_compl/_rgbgfx b/contrib/zsh_compl/_rgbgfx index 0238f17a..5d179794 100644 --- a/contrib/zsh_compl/_rgbgfx +++ b/contrib/zsh_compl/_rgbgfx @@ -16,6 +16,7 @@ local args=( '(-a --attr-map -A --output-attr-map)'{-A,--output-attr-map}'[Shortcut for -a .attrmap]' '(-C --color-curve)'{-C,--color-curve}'[Generate palettes using GBC color curve]' '(-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 .pal]' '(-q --palette-map -Q --output-palette-map)'{-Q,--output-palette-map}'[Shortcut for -p .palmap]' '(-t --tilemap -T --output-tilemap)'{-T,--output-tilemap}'[Shortcut for -t .tilemap]' diff --git a/man/rgbgfx.1 b/man/rgbgfx.1 index af9a83de..f5514e25 100644 --- a/man/rgbgfx.1 +++ b/man/rgbgfx.1 @@ -10,7 +10,7 @@ .Nd Game Boy graphics converter .Sh SYNOPSIS .Nm -.Op Fl CmuVZ +.Op Fl CmOuVZ .Op Fl v Op Fl v No ... .Op Fl a Ar attrmap | Fl A .Op Fl b Ar base_ids @@ -100,11 +100,8 @@ follows the same order and has the same size. .It Fl A , Fl Fl output-attr-map Same as -.Fl a Ar path , -where -.Ar path -is the input image's path with the extension set to -.Pa .attrmap . +.Fl a Ar base_path Ns .attrmap +.Pq see Sx Automatic output paths . .It Fl b Ar base_ids , Fl Fl base-tiles Ar base_ids Set the base IDs for tile map output. .Ar base_ids @@ -207,6 +204,13 @@ This may not be more than 256. .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 .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 Output the tile data in native 2bpp format or in 1bpp .Pq depending on Fl d @@ -215,21 +219,15 @@ to this file. Output the image's palette set to this file. .It Fl P , Fl Fl output-palette Same as -.Fl p Ar path , -where -.Ar path -is the input image's path with the extension set to -.Pa .pal . +.Fl p Ar base_path Ns .pal +.Pq see Sx Automatic output paths . .It Fl q Ar pal_file , Fl Fl palette-map Ar pal_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. .It Fl Q , Fl Fl output-palette-map Same as -.Fl q Ar path , -where -.Ar path -is the input image's path with the extension set to -.Pa .palmap . +.Fl q Ar base_path Ns .palmap +.Pq see Sx Automatic output paths . .It Fl r Ar width , Fl Fl reverse Ar width Switches .Nm @@ -264,11 +262,8 @@ and/or to keep track of duplicate tiles. .It Fl T , Fl Fl output-tilemap Same as -.Fl t Ar path , -where -.Ar path -is the input image's path with the extension set to -.Pa .tilemap . +.Fl t Ar base_path Ns .tilemap +.Pq see Sx Automatic output paths . .It Fl u , Fl Fl unique-tiles Deduplicate identical tiles. Only one of each unique tile will be saved in the tile data file. Useful with a tile map @@ -533,6 +528,28 @@ The contents of individual bytes follows the GBC's native format: .El .Pp 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 .Nm can produce a PNG image from valid data. diff --git a/src/gfx/main.cpp b/src/gfx/main.cpp index b7aac36c..b7beb7b9 100644 --- a/src/gfx/main.cpp +++ b/src/gfx/main.cpp @@ -34,7 +34,16 @@ using namespace std::literals::string_view_literals; 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; [[noreturn]] void giveUp() { @@ -91,7 +100,7 @@ void Options::verbosePrint(uint8_t level, char const *fmt, ...) const { } // 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 @@ -118,6 +127,7 @@ static struct option const longopts[] = { {"mirror-tiles", no_argument, NULL, 'm'}, {"nb-tiles", required_argument, NULL, 'N'}, {"nb-palettes", required_argument, NULL, 'n'}, + {"group-outputs", no_argument, NULL, 'O'}, {"output", required_argument, NULL, 'o'}, {"output-palette", no_argument, NULL, 'P'}, {"palette", required_argument, NULL, 'p'}, @@ -136,7 +146,7 @@ static struct option const longopts[] = { }; static void printUsage(void) { - fputs("Usage: rgbgfx [-r stride] [-CmuVZ] [-v [-v ...]] [-a | -A]\n" + fputs("Usage: rgbgfx [-r stride] [-CmOuVZ] [-v [-v ...]] [-a | -A]\n" " [-b ] [-c ] [-d ] [-L ] [-N ]\n" " [-n ] [-o ] [-p | -P] [-q | -Q]\n" " [-s ] [-t | -T] [-x ] \n" @@ -321,23 +331,21 @@ static std::vector readAtFile(std::filesystem::path const &path, } /* - * Parses an arg vector, modifying `options` as options are read. - * The three booleans are for the "auto path" flags, since their processing must be deferred to the - * end of option parsing. + * Parses an arg vector, modifying `options` and `localOptions` as options are read. + * The `localOptions` struct is for flags which must be processed after the option parsing finishes. * * 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. */ -static char *parseArgv(int argc, char **argv, bool &autoAttrmap, bool &autoTilemap, - bool &autoPalettes, bool &autoPalmap) { +static char *parseArgv(int argc, char **argv) { for (int ch; (ch = musl_getopt_long_only(argc, argv, optstring, longopts, nullptr)) != -1;) { char *arg = musl_optarg; // Make a copy for scanning switch (ch) { case 'A': - autoAttrmap = true; + localOptions.autoAttrmap = true; break; case 'a': - autoAttrmap = false; + localOptions.autoAttrmap = false; if (options.attrmap.has_value()) warning("Overriding attrmap file %s", options.attrmap->c_str()); 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 // TODO: this does not validate the `fmt` part of any external spec but the last // one, but I guess that's okay - externalPalSpec = musl_optarg; + localOptions.externalPalSpec = musl_optarg; } break; 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!"); } break; + case 'O': + localOptions.groupOutputs = true; + break; case 'o': if (options.output.has_value()) warning("Overriding tile data file %s", options.output->c_str()); options.output = musl_optarg; break; case 'P': - autoPalettes = true; + localOptions.autoPalettes = true; break; case 'p': - autoPalettes = false; + localOptions.autoPalettes = false; if (options.palettes.has_value()) warning("Overriding palettes file %s", options.palettes->c_str()); options.palettes = musl_optarg; break; case 'Q': - autoPalmap = true; + localOptions.autoPalmap = true; break; case 'q': - autoPalmap = false; + localOptions.autoPalmap = false; if (options.palmap.has_value()) warning("Overriding palette map file %s", options.palmap->c_str()); options.palmap = musl_optarg; @@ -525,10 +536,10 @@ static char *parseArgv(int argc, char **argv, bool &autoAttrmap, bool &autoTilem } break; case 'T': - autoTilemap = true; + localOptions.autoTilemap = true; break; case 't': - autoTilemap = false; + localOptions.autoTilemap = false; if (options.tilemap.has_value()) warning("Overriding tilemap file %s", options.tilemap->c_str()); 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[]) { - bool autoAttrmap = false, autoTilemap = false, autoPalettes = false, autoPalmap = false; - struct AtFileStackEntry { int parentInd; // Saved offset into parent argv std::vector argv; // This context's arg pointer vec @@ -592,8 +601,7 @@ int main(int argc, char *argv[]) { int curArgc = argc; char **curArgv = argv; for (;;) { - char *atFileName = - parseArgv(curArgc, curArgv, autoAttrmap, autoTilemap, autoPalettes, autoPalmap); + char *atFileName = parseArgv(curArgc, curArgv); if (atFileName) { // Copy `argv[0]` for error reporting, and because option parsing skips it AtFileStackEntry &stackEntry = @@ -649,22 +657,24 @@ int main(int argc, char *argv[]) { auto autoOutPath = [](bool autoOptEnabled, std::optional &path, char const *extension) { if (autoOptEnabled) { - if (!options.input.has_value()) { - fputs("FATAL: No input image specified\n", stderr); + auto image = localOptions.groupOutputs ? options.output : options.input; + if (!image.has_value()) { + fprintf(stderr, "FATAL: No %s specified\n", localOptions.groupOutputs + ? "output tile data file" : "input image"); printUsage(); exit(1); } - path.emplace(*options.input).replace_extension(extension); + path.emplace(*image).replace_extension(extension); } }; - autoOutPath(autoAttrmap, options.attrmap, ".attrmap"); - autoOutPath(autoTilemap, options.tilemap, ".tilemap"); - autoOutPath(autoPalettes, options.palettes, ".pal"); - autoOutPath(autoPalmap, options.palmap, ".palmap"); + autoOutPath(localOptions.autoAttrmap, options.attrmap, ".attrmap"); + autoOutPath(localOptions.autoTilemap, options.tilemap, ".tilemap"); + autoOutPath(localOptions.autoPalettes, options.palettes, ".pal"); + autoOutPath(localOptions.autoPalmap, options.palmap, ".palmap"); // Execute deferred external pal spec parsing, now that all other params are known - if (externalPalSpec) { - parseExternalPalSpec(externalPalSpec); + if (localOptions.externalPalSpec) { + parseExternalPalSpec(localOptions.externalPalSpec); } if (options.verbosity >= Options::VERB_CFG) {