shithub: rgbds

Download patch

ref: 05e36767b0d3dcf64e9a37bbd0a2cc93771dfbfd
parent: 531092f5bdc790c52827dce30dfc29c073ccaa15
author: ISSOtm <[email protected]>
date: Fri May 20 05:30:35 EDT 2022

Implement "palette map" output

--- a/include/gfx/main.hpp
+++ b/include/gfx/main.hpp
@@ -43,6 +43,7 @@
 	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<uint16_t, 2> unitSize{1, 1}; // -U (in tiles)
--- 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 @@
 .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 @@
 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 @@
 .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
--- a/src/gfx/main.cpp
+++ b/src/gfx/main.cpp
@@ -87,7 +87,7 @@
 }
 
 // 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 @@
  * 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 <attr_map> | -A]\n"
 	      "       [-b base_ids] [-c color_spec] [-d <depth>] [-L slice] [-N nb_tiles]\n"
-	      "       [-n nb_pals] [-o <out_file>] [-p <pal_file> | -P] [-s nb_colors]\n"
-	      "       [-t <tile_map> | -T] [-U unit_size] [-x <tiles>] <file>\n"
+	      "       [-n nb_pals] [-o <out_file>] [-p <pal_file> | -P] [-q <pal_mal> | -Q ]\n"
+	      "       [-s nb_colors] [-t <tile_map> | -T] [-U unit_size] [-x <tiles>] <file>\n"
 	      "Useful options:\n"
 	      "    -m, --mirror-tiles    optimize out mirrored tiles\n"
 	      "    -o, --output <path>   set the output binary file\n"
@@ -319,7 +321,7 @@
  * "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 @@
 			}
 			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 @@
 			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 @@
 }
 
 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 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 @@
 	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) {
--- a/src/gfx/process.cpp
+++ b/src/gfx/process.cpp
@@ -739,7 +739,7 @@
 
 static void outputMaps(Png const &png, DefaultInitVec<AttrmapEntry> const &attrmap,
                        DefaultInitVec<size_t> const &mappings) {
-	std::optional<std::filebuf> tilemapOutput, attrmapOutput;
+	std::optional<std::filebuf> 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 @@
 		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 @@
 		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 @@
 	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<AttrmapEntry> const &attrmap,
+                         DefaultInitVec<size_t> 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 @@
 			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 {
@@ -1051,6 +1069,11 @@
 		if (!options.attrmap.empty()) {
 			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);
 		}
 	}
 }
--- a/src/gfx/reverse.cpp
+++ b/src/gfx/reverse.cpp
@@ -178,9 +178,17 @@
 		// 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<DefaultInitVec<uint8_t>> 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 @@
 				    (*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<uint8_t, 16> const trimmedTile{
@@ -255,8 +265,7 @@
 			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;