From 05e36767b0d3dcf64e9a37bbd0a2cc93771dfbfd Mon Sep 17 00:00:00 2001 From: ISSOtm Date: Fri, 20 May 2022 09:30:35 +0200 Subject: [PATCH] Implement "palette map" output --- include/gfx/main.hpp | 1 + man/rgbgfx.1 | 18 ++++++++-- src/gfx/main.cpp | 83 +++++++++++++++++++++++++------------------- src/gfx/process.cpp | 35 +++++++++++++++---- src/gfx/reverse.cpp | 15 ++++++-- 5 files changed, 104 insertions(+), 48 deletions(-) diff --git a/include/gfx/main.hpp b/include/gfx/main.hpp index b8e8b62d..dfed320d 100644 --- a/include/gfx/main.hpp +++ b/include/gfx/main.hpp @@ -43,6 +43,7 @@ struct Options { uint8_t nbPalettes = 8; // -n std::string output{}; // -o std::string palettes{}; // -p, -P + std::string palmap{}; // -q, -Q uint8_t nbColorsPerPal = 0; // -s; 0 means "auto" = 1 << bitDepth; std::string tilemap{}; // -t, -T std::array unitSize{1, 1}; // -U (in tiles) diff --git a/man/rgbgfx.1 b/man/rgbgfx.1 index b18d27e7..7aeaf5a9 100644 --- a/man/rgbgfx.1 +++ b/man/rgbgfx.1 @@ -26,6 +26,7 @@ .Op Fl n Ar nb_pals .Op Fl o Ar out_file .Op Fl p Ar pal_file | Fl P +.Op Fl q Ar pal_map | Fl Q .Op Fl s Ar nb_colors .Op Fl t Ar tilemap | Fl T .Op Fl U Ar unit_size @@ -72,8 +73,6 @@ All of these are equivalent: .Ql 0X2A , .Ql 0x2a . .Pp -TODO: add "palette map" output. -.Pp The following options are accepted: .Bl -tag -width Ds .It Fl a Ar attrmap , Fl Fl attr-map Ar attrmap @@ -182,7 +181,10 @@ is not specified, no limit will be set on the amount of tiles placed in bank 0, Abort if more than .Ar nb_pals palettes are generated. -Note that attribute map output only has 3 bits for the palette ID, so a limit higher than 8 may yield incomplete data. +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 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 @@ -196,6 +198,16 @@ where .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 +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 . .It Fl s Ar nb_colors , Fl Fl palette-size Ar nb_colors Specify how many colors each palette contains, including the transparent one if any. .Ar nb_colors diff --git a/src/gfx/main.cpp b/src/gfx/main.cpp index 9e38cdb7..909285b2 100644 --- a/src/gfx/main.cpp +++ b/src/gfx/main.cpp @@ -87,7 +87,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:r:s:Tt:U:uVvx:Z"; +static char const *optstring = "-Aa:b:Cc:Dd:FfhL:mN:n:o:Pp:Qq:r:s:Tt:U:uVvx:Z"; /* * Equivalent long options @@ -100,40 +100,42 @@ static char const *optstring = "-Aa:b:Cc:Dd:FfhL:mN:n:o:Pp:r:s:Tt:U:uVvx:Z"; * over short opt matching */ static struct option const longopts[] = { - {"output-attr-map", no_argument, NULL, 'A'}, - {"attr-map", required_argument, NULL, 'a'}, - {"base-tiles", required_argument, NULL, 'b'}, - {"color-curve", no_argument, NULL, 'C'}, - {"colors", required_argument, NULL, 'c'}, - {"debug", no_argument, NULL, 'D'}, // Ignored - {"depth", required_argument, NULL, 'd'}, - {"fix", no_argument, NULL, 'f'}, - {"fix-and-save", no_argument, NULL, 'F'}, // Deprecated - {"horizontal", no_argument, NULL, 'h'}, // Deprecated - {"slice", required_argument, NULL, 'L'}, - {"mirror-tiles", no_argument, NULL, 'm'}, - {"nb-tiles", required_argument, NULL, 'N'}, - {"nb-palettes", required_argument, NULL, 'n'}, - {"output", required_argument, NULL, 'o'}, - {"output-palette", no_argument, NULL, 'P'}, - {"palette", required_argument, NULL, 'p'}, - {"reverse", required_argument, NULL, 'r'}, - {"output-tilemap", no_argument, NULL, 'T'}, - {"tilemap", required_argument, NULL, 't'}, - {"unit-size", required_argument, NULL, 'U'}, - {"unique-tiles", no_argument, NULL, 'u'}, - {"version", no_argument, NULL, 'V'}, - {"verbose", no_argument, NULL, 'v'}, - {"trim-end", required_argument, NULL, 'x'}, - {"columns", no_argument, NULL, 'Z'}, - {NULL, no_argument, NULL, 0 } + {"output-attr-map", no_argument, NULL, 'A'}, + {"attr-map", required_argument, NULL, 'a'}, + {"base-tiles", required_argument, NULL, 'b'}, + {"color-curve", no_argument, NULL, 'C'}, + {"colors", required_argument, NULL, 'c'}, + {"debug", no_argument, NULL, 'D'}, // Ignored + {"depth", required_argument, NULL, 'd'}, + {"fix", no_argument, NULL, 'f'}, + {"fix-and-save", no_argument, NULL, 'F'}, // Deprecated + {"horizontal", no_argument, NULL, 'h'}, // Deprecated + {"slice", required_argument, NULL, 'L'}, + {"mirror-tiles", no_argument, NULL, 'm'}, + {"nb-tiles", required_argument, NULL, 'N'}, + {"nb-palettes", required_argument, NULL, 'n'}, + {"output", required_argument, NULL, 'o'}, + {"output-palette", no_argument, NULL, 'P'}, + {"palette", required_argument, NULL, 'p'}, + {"output-palette-map", no_argument, NULL, 'Q'}, + {"palette-map", required_argument, NULL, 'q'}, + {"reverse", required_argument, NULL, 'r'}, + {"output-tilemap", no_argument, NULL, 'T'}, + {"tilemap", required_argument, NULL, 't'}, + {"unit-size", required_argument, NULL, 'U'}, + {"unique-tiles", no_argument, NULL, 'u'}, + {"version", no_argument, NULL, 'V'}, + {"verbose", no_argument, NULL, 'v'}, + {"trim-end", required_argument, NULL, 'x'}, + {"columns", no_argument, NULL, 'Z'}, + {NULL, no_argument, NULL, 0 } }; static void printUsage(void) { fputs("Usage: rgbgfx [-r stride] [-CmuVZ] [-v [-v ...]] [-a | -A]\n" " [-b base_ids] [-c color_spec] [-d ] [-L slice] [-N nb_tiles]\n" - " [-n nb_pals] [-o ] [-p | -P] [-s nb_colors]\n" - " [-t | -T] [-U unit_size] [-x ] \n" + " [-n nb_pals] [-o ] [-p | -P] [-q | -Q ]\n" + " [-s nb_colors] [-t | -T] [-U unit_size] [-x ] \n" "Useful options:\n" " -m, --mirror-tiles optimize out mirrored tiles\n" " -o, --output set the output binary file\n" @@ -319,7 +321,7 @@ static std::vector readAtFile(std::string const &path, std::vector * "at-file" path if one is encountered. */ static char *parseArgv(int argc, char **argv, bool &autoAttrmap, bool &autoTilemap, - bool &autoPalettes) { + bool &autoPalettes, bool &autoPalmap) { int opt; while ((opt = musl_getopt_long_only(argc, argv, optstring, longopts, nullptr)) != -1) { @@ -422,12 +424,12 @@ static char *parseArgv(int argc, char **argv, bool &autoAttrmap, bool &autoTilem } break; case 'n': - options.nbPalettes = parseNumber(arg, "Number of palettes", 8); + options.nbPalettes = parseNumber(arg, "Number of palettes", 256); if (*arg != '\0') { error("Number of palettes (-n) must be a valid number, not \"%s\"", musl_optarg); } - if (options.nbPalettes > 8) { - error("Number of palettes (-n) must not exceed 8!"); + if (options.nbPalettes > 256) { + error("Number of palettes (-n) must not exceed 256!"); } else if (options.nbPalettes == 0) { error("Number of palettes (-n) may not be 0!"); } @@ -442,6 +444,13 @@ static char *parseArgv(int argc, char **argv, bool &autoAttrmap, bool &autoTilem autoPalettes = false; options.palettes = musl_optarg; break; + case 'Q': + autoPalmap = true; + break; + case 'q': + autoPalmap = false; + options.palmap = musl_optarg; + break; case 'r': options.reversedWidth = parseNumber(arg, "Reversed image stride"); if (*arg != '\0') { @@ -512,7 +521,7 @@ static char *parseArgv(int argc, char **argv, bool &autoAttrmap, bool &autoTilem } int main(int argc, char *argv[]) { - bool autoAttrmap = false, autoTilemap = false, autoPalettes = false; + bool autoAttrmap = false, autoTilemap = false, autoPalettes = false, autoPalmap = false; struct AtFileStackEntry { int parentInd; // Saved offset into parent argv @@ -527,7 +536,8 @@ int main(int argc, char *argv[]) { int curArgc = argc; char **curArgv = argv; for (;;) { - char *atFileName = parseArgv(curArgc, curArgv, autoAttrmap, autoTilemap, autoPalettes); + char *atFileName = + parseArgv(curArgc, curArgv, autoAttrmap, autoTilemap, autoPalettes, autoPalmap); if (atFileName) { // Copy `argv[0]` for error reporting, and because option parsing skips it AtFileStackEntry &stackEntry = @@ -606,6 +616,7 @@ int main(int argc, char *argv[]) { autoOutPath(autoAttrmap, options.attrmap, ".attrmap"); autoOutPath(autoTilemap, options.tilemap, ".tilemap"); autoOutPath(autoPalettes, options.palettes, ".pal"); + autoOutPath(autoPalmap, options.palmap, ".palmap"); // Execute deferred external pal spec parsing, now that all other params are known if (externalPalSpec) { diff --git a/src/gfx/process.cpp b/src/gfx/process.cpp index 683d4349..f1f75688 100644 --- a/src/gfx/process.cpp +++ b/src/gfx/process.cpp @@ -739,7 +739,7 @@ static void outputTileData(Png const &png, DefaultInitVec const &a static void outputMaps(Png const &png, DefaultInitVec const &attrmap, DefaultInitVec const &mappings) { - std::optional tilemapOutput, attrmapOutput; + std::optional tilemapOutput, attrmapOutput, palmapOutput; if (!options.tilemap.empty()) { tilemapOutput.emplace(); tilemapOutput->open(options.tilemap, std::ios_base::out | std::ios_base::binary); @@ -748,6 +748,10 @@ static void outputMaps(Png const &png, DefaultInitVec const &attrm attrmapOutput.emplace(); attrmapOutput->open(options.attrmap, std::ios_base::out | std::ios_base::binary); } + if (!options.palmap.empty()) { + palmapOutput.emplace(); + palmapOutput->open(options.palmap, std::ios_base::out | std::ios_base::binary); + } uint8_t tileID = 0; uint8_t bank = 0; @@ -765,9 +769,12 @@ static void outputMaps(Png const &png, DefaultInitVec const &attrm if (attrmapOutput.has_value()) { uint8_t palID = iter->getPalID(mappings) & 7; attrmapOutput->sputc(palID | bank << 3); // The other flags are all 0 - ++iter; + } + if (palmapOutput.has_value()) { + palmapOutput->sputc(iter->getPalID(mappings)); } ++tileID; + ++iter; } assert(iter == attrmap.end()); } @@ -874,11 +881,21 @@ static void outputAttrmap(DefaultInitVec const &attrmap, for (AttrmapEntry const &entry : attrmap) { uint8_t attr = entry.xFlip << 5 | entry.yFlip << 6; attr |= entry.bank << 3; - attr |= mappings[entry.protoPaletteID] & 7; + attr |= entry.getPalID(mappings) & 7; output.sputc(attr); } } +static void outputPalmap(DefaultInitVec const &attrmap, + DefaultInitVec const &mappings) { + std::filebuf output; + output.open(options.attrmap, std::ios_base::out | std::ios_base::binary); + + for (AttrmapEntry const &entry : attrmap) { + output.sputc(entry.getPalID(mappings)); + } +} + } // namespace optimized void process() { @@ -1023,9 +1040,10 @@ contained:; unoptimized::outputTileData(png, attrmap, palettes, mappings); } - if (!options.tilemap.empty() || !options.attrmap.empty()) { - options.verbosePrint(Options::VERB_LOG_ACT, - "Generating unoptimized tilemap and/or attrmap...\n"); + if (!options.tilemap.empty() || !options.attrmap.empty() || !options.palmap.empty()) { + options.verbosePrint( + Options::VERB_LOG_ACT, + "Generating unoptimized tilemap and/or attrmap and/or palmap...\n"); unoptimized::outputMaps(png, attrmap, mappings); } } else { @@ -1052,5 +1070,10 @@ contained:; options.verbosePrint(Options::VERB_LOG_ACT, "Generating optimized attrmap...\n"); optimized::outputAttrmap(attrmap, mappings); } + + if (!options.palmap.empty()) { + options.verbosePrint(Options::VERB_LOG_ACT, "Generating optimized palmap...\n"); + optimized::outputPalmap(attrmap, mappings); + } } } diff --git a/src/gfx/reverse.cpp b/src/gfx/reverse.cpp index dc4c26a7..81f693c6 100644 --- a/src/gfx/reverse.cpp +++ b/src/gfx/reverse.cpp @@ -178,9 +178,17 @@ void reverse() { // We do this now for two reasons: // 1. Checking those during the main loop is harmful to optimization, and // 2. It clutters the code more, and it's not in great shape to begin with + // TODO } - // TODO: palette map (overrides attributes) + std::optional> palmap; + if (!options.palmap.empty()) { + palmap = readInto(options.palmap); + if (palmap->size() != nbTileInstances) { + fatal("Palette map size (%zu tiles) doesn't match image's (%zu)", palmap->size(), + nbTileInstances); + } + } options.verbosePrint(Options::VERB_LOG_ACT, "Writing image...\n"); std::filebuf pngFile; @@ -246,6 +254,8 @@ void reverse() { (*tilemap)[index] - options.baseTileIDs[bank] + bank * options.maxNbTiles[0]; } assert(tileID < nbTileInstances); // Should have been checked earlier + size_t palID = palmap ? (*palmap)[index] : attribute & 0b111; + assert(palID < palettes.size()); // Should be ensured on data read // We do not have data for tiles trimmed with `-x`, so assume they are "blank" static std::array const trimmedTile{ @@ -255,8 +265,7 @@ void reverse() { uint8_t const *tileData = tileID > nbTileInstances - options.trim ? trimmedTile.data() : &tiles[tileID * tileSize]; - assert((attribute & 0b111) < palettes.size()); // Should be ensured on data read - auto const &palette = palettes[attribute & 0b111]; + auto const &palette = palettes[palID]; for (uint8_t y = 0; y < 8; ++y) { // If vertically mirrored, fetch the bytes from the other end uint8_t realY = attribute & 0x40 ? 7 - y : y;