diff --git a/include/gfx/main.hpp b/include/gfx/main.hpp index 5fb0e94f..b58039bd 100644 --- a/include/gfx/main.hpp +++ b/include/gfx/main.hpp @@ -24,8 +24,8 @@ struct Options { bool fixInput = false; // -f bool allowMirroring = false; // -m bool allowDedup = false; // -u - bool beVerbose = false; // -v bool columnMajor = false; // -Z, previously -h + uint8_t verbosity = 0; // -v std::string attrmap{}; // -a, -A std::array baseTileIDs{0, 0}; // -b @@ -48,7 +48,14 @@ struct Options { std::string input{}; // positional arg - format_(printf, 2, 3) void verbosePrint(char const *fmt, ...) const; + static constexpr uint8_t VERB_NONE = 0; // Normal, no extra output + static constexpr uint8_t VERB_CFG = 1; // Print configuration after parsing options + static constexpr uint8_t VERB_LOG_ACT = 2; // Log actions before doing them + static constexpr uint8_t VERB_INTERM = 3; // Print some intermediate results + static constexpr uint8_t VERB_DEBUG = 4; // Internals are logged + static constexpr uint8_t VERB_UNMAPPED = 5; // Unused so far + static constexpr uint8_t VERB_VVVVVV = 6; // What, can't I have a little fun? + format_(printf, 3, 4) void verbosePrint(uint8_t level, char const *fmt, ...) const; uint8_t maxPalSize() const { return nbColorsPerPal; // TODO: minus 1 when transparency is active } diff --git a/src/gfx/convert.cpp b/src/gfx/convert.cpp index b9aff05a..300b9bb8 100644 --- a/src/gfx/convert.cpp +++ b/src/gfx/convert.cpp @@ -121,7 +121,8 @@ public: bool isSuitableForGrayscale() const { // Check that all of the grays don't fall into the same "bin" if (colors.size() > options.maxPalSize()) { // Apply the Pigeonhole Principle - options.verbosePrint("Too many colors for grayscale sorting (%zu > %" PRIu8 ")\n", + options.verbosePrint(Options::VERB_DEBUG, + "Too many colors for grayscale sorting (%zu > %" PRIu8 ")\n", colors.size(), options.maxPalSize()); return false; } @@ -131,13 +132,15 @@ public: continue; } if (!color->isGray()) { - options.verbosePrint("Found non-gray color #%08x, not using grayscale sorting\n", + options.verbosePrint(Options::VERB_DEBUG, + "Found non-gray color #%08x, not using grayscale sorting\n", color->toCSS()); return false; } uint8_t mask = 1 << color->grayIndex(); if (bins & mask) { // Two in the same bin! options.verbosePrint( + Options::VERB_DEBUG, "Color #%08x conflicts with another one, not using grayscale sorting\n", color->toCSS()); return false; @@ -161,7 +164,7 @@ public: fatal("Failed to open input image (\"%s\"): %s", path.c_str(), strerror(errno)); } - options.verbosePrint("Opened input file\n"); + options.verbosePrint(Options::VERB_LOG_ACT, "Opened input file\n"); std::array pngHeader; @@ -171,7 +174,7 @@ public: fatal("Input file (\"%s\") is not a PNG image!", path.c_str()); } - options.verbosePrint("PNG header signature is OK\n"); + options.verbosePrint(Options::VERB_INTERM, "PNG header signature is OK\n"); png = png_create_read_struct(PNG_LIBPNG_VER_STRING, (png_voidp)this, handleError, handleWarning); @@ -234,7 +237,8 @@ public: fatal("Unknown interlace type %d", interlaceType); } }; - options.verbosePrint("Input image: %" PRIu32 "x%" PRIu32 " pixels, %dbpp %s, %s\n", height, + options.verbosePrint(Options::VERB_INTERM, + "Input image: %" PRIu32 "x%" PRIu32 " pixels, %dbpp %s, %s\n", height, width, bitDepth, colorTypeName(), interlaceTypeName()); if (png_get_PLTE(png, info, &embeddedPal, &nbColors) != 0) { @@ -243,15 +247,16 @@ public: assert(nbTransparentEntries == nbColors); } - options.verbosePrint("Embedded palette has %d colors: [", nbColors); + options.verbosePrint(Options::VERB_INTERM, "Embedded palette has %d colors: [", + nbColors); for (int i = 0; i < nbColors; ++i) { auto const &color = embeddedPal[i]; - options.verbosePrint("#%02x%02x%02x%02x%s", color.red, color.green, color.blue, - transparencyPal ? transparencyPal[i] : 0xFF, - i != nbColors - 1 ? ", " : "]\n"); + options.verbosePrint( + Options::VERB_INTERM, "#%02x%02x%02x%02x%s", color.red, color.green, color.blue, + transparencyPal ? transparencyPal[i] : 0xFF, i != nbColors - 1 ? ", " : "]\n"); } } else { - options.verbosePrint("No embedded palette\n"); + options.verbosePrint(Options::VERB_INTERM, "No embedded palette\n"); } // Set up transformations; to turn everything into RGBA888 @@ -442,11 +447,11 @@ static std::tuple, std::vector> auto [mappings, nbPalettes] = packing::overloadAndRemove(protoPalettes); assert(mappings.size() == protoPalettes.size()); - if (options.beVerbose) { - options.verbosePrint("Proto-palette mappings: (%zu palette%s)\n", nbPalettes, - nbPalettes != 1 ? "s" : ""); + if (options.verbosity >= Options::VERB_INTERM) { + fprintf(stderr, "Proto-palette mappings: (%zu palette%s)\n", nbPalettes, + nbPalettes != 1 ? "s" : ""); for (size_t i = 0; i < mappings.size(); ++i) { - options.verbosePrint("%zu -> %zu\n", i, mappings[i]); + fprintf(stderr, "%zu -> %zu\n", i, mappings[i]); } } @@ -835,27 +840,27 @@ static void outputAttrmap(DefaultInitVec const &attrmap, } // namespace optimized void process() { - options.verbosePrint("Using libpng %s\n", png_get_libpng_ver(nullptr)); + options.verbosePrint(Options::VERB_CFG, "Using libpng %s\n", png_get_libpng_ver(nullptr)); - options.verbosePrint("Reading tiles...\n"); + options.verbosePrint(Options::VERB_LOG_ACT, "Reading tiles...\n"); Png png(options.input); ImagePalette const &colors = png.getColors(); // Now, we have all the image's colors in `colors` // The next step is to order the palette - if (options.beVerbose) { - options.verbosePrint("Image colors: [ "); + if (options.verbosity >= Options::VERB_INTERM) { + fputs("Image colors: [ ", stderr); size_t i = 0; for (auto const &slot : colors) { if (!slot.has_value()) { continue; } - options.verbosePrint("#%02x%02x%02x%02x%s", slot->red, slot->green, slot->blue, - slot->alpha, i != colors.size() - 1 ? ", " : ""); + fprintf(stderr, "#%02x%02x%02x%02x%s", slot->red, slot->green, slot->blue, slot->alpha, + i != colors.size() - 1 ? ", " : ""); ++i; } - options.verbosePrint("]\n"); + fputs("]\n", stderr); } // Now, iterate through the tiles, generating proto-palettes as we go @@ -909,8 +914,8 @@ void process() { contained:; } - options.verbosePrint("Image contains %zu proto-palette%s\n", protoPalettes.size(), - protoPalettes.size() != 1 ? "s" : ""); + options.verbosePrint(Options::VERB_INTERM, "Image contains %zu proto-palette%s\n", + protoPalettes.size(), protoPalettes.size() != 1 ? "s" : ""); // Sort the proto-palettes by size, which improves the packing algorithm's efficiency // We sort after all insertions to avoid moving items: https://stackoverflow.com/a/2710332 @@ -922,13 +927,13 @@ contained:; ? generatePalettes(protoPalettes, png) : makePalsAsSpecified(protoPalettes, png); - if (options.beVerbose) { + if (options.verbosity >= Options::VERB_INTERM) { for (auto &&palette : palettes) { - options.verbosePrint("{ "); + fputs("{ ", stderr); for (uint16_t colorIndex : palette) { - options.verbosePrint("%04" PRIx16 ", ", colorIndex); + fprintf(stderr, "%04" PRIx16 ", ", colorIndex); } - options.verbosePrint("}\n"); + fputs("}\n", stderr); } } @@ -947,17 +952,18 @@ contained:; } if (!options.output.empty()) { - options.verbosePrint("Generating unoptimized tile data...\n"); + options.verbosePrint(Options::VERB_LOG_ACT, "Generating unoptimized tile data...\n"); unoptimized::outputTileData(png, attrmap, palettes, mappings); } if (!options.tilemap.empty() || !options.attrmap.empty()) { - options.verbosePrint("Generating unoptimized tilemap and/or attrmap...\n"); + options.verbosePrint(Options::VERB_LOG_ACT, + "Generating unoptimized tilemap and/or attrmap...\n"); unoptimized::outputMaps(png, attrmap, mappings); } } else { // All of these require the deduplication process to be performed to be output - options.verbosePrint("Deduplicating tiles...\n"); + options.verbosePrint(Options::VERB_LOG_ACT, "Deduplicating tiles...\n"); optimized::UniqueTiles tiles = optimized::dedupTiles(png, attrmap, palettes, mappings); if (tiles.size() > options.maxNbTiles[0] + options.maxNbTiles[1]) { @@ -966,17 +972,17 @@ contained:; } if (!options.output.empty()) { - options.verbosePrint("Generating optimized tile data...\n"); + options.verbosePrint(Options::VERB_LOG_ACT, "Generating optimized tile data...\n"); optimized::outputTileData(tiles); } if (!options.tilemap.empty()) { - options.verbosePrint("Generating optimized tilemap...\n"); + options.verbosePrint(Options::VERB_LOG_ACT, "Generating optimized tilemap...\n"); optimized::outputTilemap(attrmap); } if (!options.attrmap.empty()) { - options.verbosePrint("Generating optimized attrmap...\n"); + options.verbosePrint(Options::VERB_LOG_ACT, "Generating optimized attrmap...\n"); optimized::outputAttrmap(attrmap, mappings); } } diff --git a/src/gfx/main.cpp b/src/gfx/main.cpp index f9adc936..154cad77 100644 --- a/src/gfx/main.cpp +++ b/src/gfx/main.cpp @@ -70,8 +70,8 @@ void error(char const *fmt, ...) { exit(1); } -void Options::verbosePrint(char const *fmt, ...) const { - if (beVerbose) { +void Options::verbosePrint(uint8_t level, char const *fmt, ...) const { + if (verbosity >= level) { va_list ap; va_start(ap, fmt); @@ -308,7 +308,9 @@ int main(int argc, char *argv[]) { printf("rgbgfx %s\n", get_package_version_string()); exit(0); case 'v': - options.beVerbose = true; + if (options.verbosity < Options::VERB_VVVVVV) { + ++options.verbosity; + } break; case 'x': options.trim = parseNumber(arg, "Number of tiles to trim"); @@ -372,8 +374,36 @@ int main(int argc, char *argv[]) { autoOutPath(autoTilemap, options.tilemap, ".tilemap"); autoOutPath(autoPalettes, options.palettes, ".pal"); - if (options.beVerbose) { + if (options.verbosity >= Options::VERB_CFG) { fprintf(stderr, "rgbgfx %s\n", get_package_version_string()); + + if (options.verbosity >= Options::VERB_VVVVVV) { + fputc('\n', stderr); + static std::array gfx{ + 0x1FE, 0x3FF, 0x399, 0x399, 0x3FF, 0x3FF, 0x381, 0x3C3, 0x1FE, 0x078, 0x1FE, + 0x3FF, 0x3FF, 0x3FF, 0x37B, 0x37B, 0x0FC, 0x0CC, 0x1CE, 0x1CE, 0x1CE, + }; + static std::array textbox{ + " ,----------------------------------------.", + " | Augh, dimensional interference again?! |", + " `----------------------------------------'"}; + for (size_t i = 0; i < gfx.size(); ++i) { + uint16_t row = gfx[i]; + for (uint8_t _ = 0; _ < 10; ++_) { + unsigned char c = row & 1 ? '0' : ' '; + fputc(c, stderr); + // Double the pixel horizontally, otherwise the aspect ratio looks wrong + fputc(c, stderr); + row >>= 1; + } + if (i < textbox.size()) { + fputs(textbox[i], stderr); + } + fputc('\n', stderr); + } + fputc('\n', stderr); + } + fputs("Options:\n", stderr); if (options.fixInput) fputs("\tConvert input to indexed\n", stderr); diff --git a/src/gfx/pal_packing.cpp b/src/gfx/pal_packing.cpp index 7bacd2c0..7e90b5e3 100644 --- a/src/gfx/pal_packing.cpp +++ b/src/gfx/pal_packing.cpp @@ -342,7 +342,8 @@ static void decant(std::vector &assignments, std::tuple, size_t> overloadAndRemove(std::vector const &protoPalettes) { - options.verbosePrint("Paginating palettes using \"overload-and-remove\" strategy...\n"); + options.verbosePrint(Options::VERB_LOG_ACT, + "Paginating palettes using \"overload-and-remove\" strategy...\n"); struct Iota { using value_type = size_t; @@ -388,8 +389,9 @@ std::tuple, size_t> continue; } - options.verbosePrint("%zu/%zu: Rel size: %f (size = %zu)\n", i, assignments.size(), - assignments[i].relSizeOf(protoPal), protoPal.size()); + options.verbosePrint(Options::VERB_DEBUG, "%zu/%zu: Rel size: %f (size = %zu)\n", i, + assignments.size(), assignments[i].relSizeOf(protoPal), + protoPal.size()); if (assignments[i].relSizeOf(protoPal) < bestRelSize) { bestPalIndex = i; } @@ -405,7 +407,8 @@ std::tuple, size_t> // If this overloads the palette, get it back to normal (if possible) while (bestPal.volume() > options.maxPalSize()) { - options.verbosePrint("Palette %zu is overloaded! (%zu > %" PRIu8 ")\n", + options.verbosePrint(Options::VERB_DEBUG, + "Palette %zu is overloaded! (%zu > %" PRIu8 ")\n", bestPalIndex, bestPal.volume(), options.maxPalSize()); // Look for a proto-pal minimizing "efficiency" (size / rel_size) @@ -454,10 +457,11 @@ std::tuple, size_t> std::find_if(assignments.begin(), assignments.end(), [&protoPal](AssignedProtos const &pal) { return pal.canFit(protoPal); }); if (iter == assignments.end()) { // No such page, create a new one - options.verbosePrint("Adding new palette for overflow\n"); + options.verbosePrint(Options::VERB_DEBUG, "Adding new palette for overflow\n"); assignments.emplace_back(protoPalettes, std::move(attrs)); } else { - options.verbosePrint("Assigning overflow to palette %zu\n", iter - assignments.begin()); + options.verbosePrint(Options::VERB_DEBUG, "Assigning overflow to palette %zu\n", + iter - assignments.begin()); iter->assign(std::move(attrs)); } queue.pop(); @@ -467,15 +471,15 @@ std::tuple, size_t> decant(assignments, protoPalettes); // Note that the result does not contain any empty palettes - if (options.beVerbose) { + if (options.verbosity >= Options::VERB_INTERM) { for (auto &&assignment : assignments) { - options.verbosePrint("{ "); + fprintf(stderr, "{ "); for (auto &&attrs : assignment) { for (auto &&colorIndex : protoPalettes[attrs.palIndex]) { - options.verbosePrint("%04" PRIx16 ", ", colorIndex); + fprintf(stderr, "%04" PRIx16 ", ", colorIndex); } } - options.verbosePrint("} (volume = %zu)\n", assignment.volume()); + fprintf(stderr, "} (volume = %zu)\n", assignment.volume()); } } diff --git a/src/gfx/pal_sorting.cpp b/src/gfx/pal_sorting.cpp index 658593fa..ac5fa15d 100644 --- a/src/gfx/pal_sorting.cpp +++ b/src/gfx/pal_sorting.cpp @@ -16,7 +16,7 @@ namespace sorting { void indexed(std::vector &palettes, int palSize, png_color const *palRGB, png_byte *palAlpha) { - options.verbosePrint("Sorting palettes using embedded palette...\n"); + options.verbosePrint(Options::VERB_LOG_ACT, "Sorting palettes using embedded palette...\n"); auto pngToRgb = [&palRGB, &palAlpha](int index) { auto const &c = palRGB[index]; @@ -71,7 +71,7 @@ void indexed(std::vector &palettes, int palSize, png_color const *palRG void grayscale(std::vector &palettes, std::array, 0x8001> const &colors) { - options.verbosePrint("Sorting grayscale-only palette...\n"); + options.verbosePrint(Options::VERB_LOG_ACT, "Sorting grayscale-only palette...\n"); // This method is only applicable if there are at most as many colors as colors per palette, so // we should only have a single palette. @@ -95,7 +95,7 @@ static unsigned int legacyLuminance(uint16_t color) { } void rgb(std::vector &palettes) { - options.verbosePrint("Sorting palettes by \"\"\"luminance\"\"\"...\n"); + options.verbosePrint(Options::VERB_LOG_ACT, "Sorting palettes by \"\"\"luminance\"\"\"...\n"); for (Palette &pal : palettes) { std::sort(pal.begin(), pal.end(), [](uint16_t lhs, uint16_t rhs) {