diff --git a/include/file.hpp b/include/file.hpp index 73079e95..aba7a84f 100644 --- a/include/file.hpp +++ b/include/file.hpp @@ -7,11 +7,11 @@ #include #include #include +#include #include #include #include #include -#include #include #include #include @@ -41,7 +41,7 @@ public: * This should only be called once, and before doing any `->` operations. * Returns `nullptr` on error, and a non-null pointer otherwise. */ - File *open(std::string const &path, std::ios_base::openmode mode) { + File *open(std::filesystem::path const &path, std::ios_base::openmode mode) { if (path != "-") { return _file.emplace().open(path, mode) ? this : nullptr; } else if (mode & std::ios_base::in) { @@ -85,8 +85,15 @@ public: : nullptr; } - char const *c_str(std::string const &path) const { - return std::visit(Visitor{[&path](std::filebuf const &) { return path.c_str(); }, + char const *c_str(std::filesystem::path const &path) const { + // FIXME: This is a hack to prevent the path string from being destroyed until + // `.c_str(path)` is called again. It's necessary because just `return path.c_str()` + // fails on Windows, where paths use `wchar_t`. + static std::string path_string; + return std::visit(Visitor{[&path](std::filebuf const &) { + path_string = path.string(); + return path_string.c_str(); + }, [](std::streambuf const *buf) { return buf == std::cin.rdbuf() ? "" : ""; }}, diff --git a/include/gfx/main.hpp b/include/gfx/main.hpp index 55189fbf..52ddc686 100644 --- a/include/gfx/main.hpp +++ b/include/gfx/main.hpp @@ -4,7 +4,9 @@ #define RGBDS_GFX_MAIN_HPP #include +#include #include +#include #include #include #include @@ -24,7 +26,7 @@ struct Options { bool columnMajor = false; // -Z, previously -h uint8_t verbosity = 0; // -v - std::string attrmap{}; // -a, -A + std::optional attrmap{}; // -a, -A std::array baseTileIDs{0, 0}; // -b enum { NO_SPEC, @@ -41,14 +43,14 @@ struct Options { } inputSlice{0, 0, 0, 0}; // -L (margins in clockwise order, like CSS) std::array maxNbTiles{UINT16_MAX, 0}; // -N uint8_t nbPalettes = 8; // -n - std::string output{}; // -o - std::string palettes{}; // -p, -P - std::string palmap{}; // -q, -Q + std::optional output{}; // -o + std::optional palettes{}; // -p, -P + std::optional palmap{}; // -q, -Q uint8_t nbColorsPerPal = 0; // -s; 0 means "auto" = 1 << bitDepth; - std::string tilemap{}; // -t, -T + std::optional tilemap{}; // -t, -T uint64_t trim = 0; // -x - std::string input{}; // positional arg + std::optional input{}; // positional arg static constexpr uint8_t VERB_NONE = 0; // Normal, no extra output static constexpr uint8_t VERB_CFG = 1; // Print configuration after parsing options diff --git a/src/gfx/main.cpp b/src/gfx/main.cpp index 11ad77e2..7a4298ed 100644 --- a/src/gfx/main.cpp +++ b/src/gfx/main.cpp @@ -7,10 +7,12 @@ #include #include #include +#include #include #include #include #include +#include #include #include #include @@ -228,11 +230,11 @@ static void skipWhitespace(char *&arg) { } static void registerInput(char const *arg) { - if (!options.input.empty()) { + if (options.input.has_value()) { fprintf(stderr, "FATAL: input image specified more than once! (first \"%s\", then " "\"%s\")\n", - options.input.c_str(), arg); + options.input->c_str(), arg); printUsage(); exit(1); } else if (arg[0] == '\0') { // Empty input path @@ -240,7 +242,7 @@ static void registerInput(char const *arg) { printUsage(); exit(1); } else { - options.input = arg; + options.input.emplace(arg); } } @@ -248,7 +250,8 @@ static void registerInput(char const *arg) { * Turn an "at-file"'s contents into an argv that `getopt` can handle * @param argPool Argument characters will be appended to this vector, for storage purposes. */ -static std::vector readAtFile(std::string const &path, std::vector &argPool) { +static std::vector readAtFile(std::filesystem::path const &path, + std::vector &argPool) { File file; if (!file.open(path, std::ios_base::in)) { fatal("Error reading @%s: %s", file.c_str(path), strerror(errno)); @@ -316,6 +319,7 @@ static std::vector readAtFile(std::string const &path, std::vector } while (c != '\n' && c != EOF); // End if we reached EOL } } + /* * 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 @@ -334,8 +338,8 @@ static char *parseArgv(int argc, char **argv, bool &autoAttrmap, bool &autoTilem break; case 'a': autoAttrmap = false; - if (!options.attrmap.empty()) - warning("Overriding attrmap file %s", options.attrmap.c_str()); + if (options.attrmap.has_value()) + warning("Overriding attrmap file %s", options.attrmap->c_str()); options.attrmap = musl_optarg; break; case 'b': @@ -478,8 +482,8 @@ static char *parseArgv(int argc, char **argv, bool &autoAttrmap, bool &autoTilem } break; case 'o': - if (!options.output.empty()) - warning("Overriding tile data file %s", options.output.c_str()); + if (options.output.has_value()) + warning("Overriding tile data file %s", options.output->c_str()); options.output = musl_optarg; break; case 'P': @@ -487,8 +491,8 @@ static char *parseArgv(int argc, char **argv, bool &autoAttrmap, bool &autoTilem break; case 'p': autoPalettes = false; - if (!options.palettes.empty()) - warning("Overriding palettes file %s", options.palettes.c_str()); + if (options.palettes.has_value()) + warning("Overriding palettes file %s", options.palettes->c_str()); options.palettes = musl_optarg; break; case 'Q': @@ -496,8 +500,8 @@ static char *parseArgv(int argc, char **argv, bool &autoAttrmap, bool &autoTilem break; case 'q': autoPalmap = false; - if (!options.palmap.empty()) - warning("Overriding palette map file %s", options.palmap.c_str()); + if (options.palmap.has_value()) + warning("Overriding palette map file %s", options.palmap->c_str()); options.palmap = musl_optarg; break; case 'r': @@ -525,8 +529,8 @@ static char *parseArgv(int argc, char **argv, bool &autoAttrmap, bool &autoTilem break; case 't': autoTilemap = false; - if (!options.tilemap.empty()) - warning("Overriding tilemap file %s", options.tilemap.c_str()); + if (options.tilemap.has_value()) + warning("Overriding tilemap file %s", options.tilemap->c_str()); options.tilemap = musl_optarg; break; case 'V': @@ -642,27 +646,15 @@ int main(int argc, char *argv[]) { 1u << options.bitDepth, options.nbColorsPerPal); } - auto autoOutPath = [](bool autoOptEnabled, std::string &path, char const *extension) { + auto autoOutPath = [](bool autoOptEnabled, std::optional &path, + char const *extension) { if (autoOptEnabled) { - constexpr std::string_view chars = -// Both must start with a dot! -#if defined(_MSC_VER) || defined(__MINGW32__) - "./\\"sv; -#else - "./"sv; -#endif - size_t len = options.input.npos; - size_t i = options.input.find_last_of(chars); - if (i != options.input.npos && options.input[i] == '.') { - // We found the last dot, but check if it's part of a stem - // (There must be a non-path separator character before it) - if (i != 0 && chars.find(options.input[i - 1], 1) == chars.npos) { - // We can replace the extension - len = i; - } + if (!options.input.has_value()) { + fputs("FATAL: No input image specified\n", stderr); + printUsage(); + exit(1); } - path.assign(options.input, 0, len); - path.append(extension); + path.emplace(*options.input).replace_extension(extension); } }; autoOutPath(autoAttrmap, options.attrmap, ".attrmap"); @@ -747,9 +739,10 @@ int main(int argc, char *argv[]) { options.baseTileIDs[1]); fprintf(stderr, "\tMaximum %" PRIu16 " tiles in bank 0, %" PRIu16 " in bank 1\n", options.maxNbTiles[0], options.maxNbTiles[1]); - auto printPath = [](char const *name, std::string const &path) { - if (!path.empty()) { - fprintf(stderr, "\t%s: %s\n", name, path.c_str()); + auto printPath = [](char const *name, + std::optional const &path) { + if (path.has_value()) { + fprintf(stderr, "\t%s: %s\n", name, path->c_str()); } }; printPath("Input image", options.input); @@ -765,13 +758,13 @@ int main(int argc, char *argv[]) { giveUp(); } - if (!options.input.empty()) { + if (options.input.has_value()) { if (options.reverse()) { reverse(); } else { process(); } - } else if (!options.palettes.empty() && options.palSpecType == Options::EXPLICIT + } else if (options.palettes.has_value() && options.palSpecType == Options::EXPLICIT && !options.reverse()) { processPalettes(); } else { diff --git a/src/gfx/process.cpp b/src/gfx/process.cpp index ccb8e493..cbe5f60f 100644 --- a/src/gfx/process.cpp +++ b/src/gfx/process.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -71,7 +72,7 @@ public: }; class Png { - std::string const &path; + std::filesystem::path const &path; File file{}; png_structp png = nullptr; png_infop info = nullptr; @@ -169,7 +170,7 @@ public: * We also use that occasion to only read the PNG one line at a time, since we store all of * the pixel data in `pixels`, which saves on memory allocations. */ - explicit Png(std::string const &filePath) : path(filePath), colors() { + explicit Png(std::filesystem::path const &filePath) : path(filePath), colors() { if (file.open(path, std::ios_base::in | std::ios_base::binary) == nullptr) { fatal("Failed to open input image (\"%s\"): %s", file.c_str(path), strerror(errno)); } @@ -637,10 +638,10 @@ static void outputPalettes(std::vector const &palettes) { options.nbPalettes); } - if (!options.palettes.empty()) { + if (options.palettes.has_value()) { File output; - if (!output.open(options.palettes, std::ios_base::out | std::ios_base::binary)) { - fatal("Failed to open \"%s\": %s", output.c_str(options.palettes), + if (!output.open(*options.palettes, std::ios_base::out | std::ios_base::binary)) { + fatal("Failed to open \"%s\": %s", output.c_str(*options.palettes), strerror(errno)); } @@ -770,8 +771,8 @@ static void outputTileData(Png const &png, DefaultInitVec const &a std::vector const &palettes, DefaultInitVec const &mappings) { File output; - if (!output.open(options.output, std::ios_base::out | std::ios_base::binary)) { - fatal("Failed to open \"%s\": %s", output.c_str(options.output), strerror(errno)); + if (!output.open(*options.output, std::ios_base::out | std::ios_base::binary)) { + fatal("Failed to open \"%s\": %s", output.c_str(*options.output), strerror(errno)); } uint16_t widthTiles = options.inputSlice.width ? options.inputSlice.width : png.getWidth() / 8; @@ -804,27 +805,18 @@ static void outputTileData(Png const &png, DefaultInitVec const &a static void outputMaps(DefaultInitVec const &attrmap, DefaultInitVec const &mappings) { std::optional tilemapOutput, attrmapOutput, palmapOutput; - if (!options.tilemap.empty()) { - tilemapOutput.emplace(); - if (!tilemapOutput->open(options.tilemap, std::ios_base::out | std::ios_base::binary)) { - fatal("Failed to open \"%s\": %s", tilemapOutput->c_str(options.tilemap), - strerror(errno)); + auto autoOpenPath = [](std::optional &path, std::optional &file) { + if (path.has_value()) { + file.emplace(); + if (!file->open(*path, std::ios_base::out | std::ios_base::binary)) { + fatal("Failed to open \"%s\": %s", file->c_str(*path), + strerror(errno)); + } } - } - if (!options.attrmap.empty()) { - attrmapOutput.emplace(); - if (!attrmapOutput->open(options.attrmap, std::ios_base::out | std::ios_base::binary)) { - fatal("Failed to open \"%s\": %s", attrmapOutput->c_str(options.attrmap), - strerror(errno)); - } - } - if (!options.palmap.empty()) { - palmapOutput.emplace(); - if (!palmapOutput->open(options.palmap, std::ios_base::out | std::ios_base::binary)) { - fatal("Failed to open \"%s\": %s", palmapOutput->c_str(options.palmap), - strerror(errno)); - } - } + }; + autoOpenPath(options.tilemap, tilemapOutput); + autoOpenPath(options.attrmap, attrmapOutput); + autoOpenPath(options.palmap, palmapOutput); uint8_t tileID = 0; uint8_t bank = 0; @@ -919,8 +911,8 @@ static UniqueTiles dedupTiles(Png const &png, DefaultInitVec &attr static void outputTileData(UniqueTiles const &tiles) { File output; - if (!output.open(options.output, std::ios_base::out | std::ios_base::binary)) { - fatal("Failed to create \"%s\": %s", output.c_str(options.output), strerror(errno)); + if (!output.open(*options.output, std::ios_base::out | std::ios_base::binary)) { + fatal("Failed to create \"%s\": %s", output.c_str(*options.output), strerror(errno)); } uint16_t tileID = 0; @@ -934,8 +926,8 @@ static void outputTileData(UniqueTiles const &tiles) { static void outputTilemap(DefaultInitVec const &attrmap) { File output; - if (!output.open(options.tilemap, std::ios_base::out | std::ios_base::binary)) { - fatal("Failed to create \"%s\": %s", output.c_str(options.tilemap), strerror(errno)); + if (!output.open(*options.tilemap, std::ios_base::out | std::ios_base::binary)) { + fatal("Failed to create \"%s\": %s", output.c_str(*options.tilemap), strerror(errno)); } for (AttrmapEntry const &entry : attrmap) { @@ -946,8 +938,8 @@ static void outputTilemap(DefaultInitVec const &attrmap) { static void outputAttrmap(DefaultInitVec const &attrmap, DefaultInitVec const &mappings) { File output; - if (!output.open(options.attrmap, std::ios_base::out | std::ios_base::binary)) { - fatal("Failed to create \"%s\": %s", output.c_str(options.attrmap), strerror(errno)); + if (!output.open(*options.attrmap, std::ios_base::out | std::ios_base::binary)) { + fatal("Failed to create \"%s\": %s", output.c_str(*options.attrmap), strerror(errno)); } for (AttrmapEntry const &entry : attrmap) { @@ -961,8 +953,8 @@ static void outputAttrmap(DefaultInitVec const &attrmap, static void outputPalmap(DefaultInitVec const &attrmap, DefaultInitVec const &mappings) { File output; - if (!output.open(options.palmap, std::ios_base::out | std::ios_base::binary)) { - fatal("Failed to create \"%s\": %s", output.c_str(options.palmap), strerror(errno)); + if (!output.open(*options.palmap, std::ios_base::out | std::ios_base::binary)) { + fatal("Failed to create \"%s\": %s", output.c_str(*options.palmap), strerror(errno)); } for (AttrmapEntry const &entry : attrmap) { @@ -986,7 +978,7 @@ void process() { options.verbosePrint(Options::VERB_CFG, "Using libpng %s\n", png_get_libpng_ver(nullptr)); options.verbosePrint(Options::VERB_LOG_ACT, "Reading tiles...\n"); - Png png(options.input); // This also sets `hasTransparentPixels` as a side effect + Png png(*options.input); // This also sets `hasTransparentPixels` as a side effect ImagePalette const &colors = png.getColors(); // Now, we have all the image's colors in `colors` @@ -1108,12 +1100,13 @@ contained:; nbTilesW * nbTilesH, options.maxNbTiles[0], options.maxNbTiles[1]); } - if (!options.output.empty()) { + if (options.output.has_value()) { 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.palmap.empty()) { + if (options.tilemap.has_value() || options.attrmap.has_value() + || options.palmap.has_value()) { options.verbosePrint( Options::VERB_LOG_ACT, "Generating unoptimized tilemap and/or attrmap and/or palmap...\n"); @@ -1129,22 +1122,22 @@ contained:; tiles.size(), options.maxNbTiles[0], options.maxNbTiles[1]); } - if (!options.output.empty()) { + if (options.output.has_value()) { options.verbosePrint(Options::VERB_LOG_ACT, "Generating optimized tile data...\n"); optimized::outputTileData(tiles); } - if (!options.tilemap.empty()) { + if (options.tilemap.has_value()) { options.verbosePrint(Options::VERB_LOG_ACT, "Generating optimized tilemap...\n"); optimized::outputTilemap(attrmap); } - if (!options.attrmap.empty()) { + if (options.attrmap.has_value()) { options.verbosePrint(Options::VERB_LOG_ACT, "Generating optimized attrmap...\n"); optimized::outputAttrmap(attrmap, mappings); } - if (!options.palmap.empty()) { + if (options.palmap.has_value()) { 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 cd9e3f7e..f6f317a2 100644 --- a/src/gfx/reverse.cpp +++ b/src/gfx/reverse.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -21,7 +22,7 @@ #include "gfx/main.hpp" -static DefaultInitVec readInto(std::string path) { +static DefaultInitVec readInto(std::filesystem::path path) { File file; if (!file.open(path, std::ios::in | std::ios::binary)) { fatal("Failed to open \"%s\": %s", file.c_str(path), strerror(errno)); @@ -77,11 +78,11 @@ void reverse() { // Check for weird flag combinations - if (options.output.empty()) { + if (!options.output.has_value()) { fatal("Tile data must be provided when reversing an image!"); } - if (options.allowDedup && options.tilemap.empty()) { + if (options.allowDedup && !options.tilemap.has_value()) { warning("Tile deduplication is enabled, but no tilemap is provided?"); } @@ -100,7 +101,7 @@ void reverse() { } options.verbosePrint(Options::VERB_LOG_ACT, "Reading tiles...\n"); - auto const tiles = readInto(options.output); + auto const tiles = readInto(*options.output); uint8_t tileSize = 8 * options.bitDepth; if (tiles.size() % tileSize != 0) { fatal("Tile data size (%zu bytes) is not a multiple of %" PRIu8 " bytes", @@ -111,8 +112,8 @@ void reverse() { size_t nbTileInstances = tiles.size() / tileSize + options.trim; // Image size in tiles options.verbosePrint(Options::VERB_INTERM, "Read %zu tiles.\n", nbTileInstances); std::optional> tilemap; - if (!options.tilemap.empty()) { - tilemap = readInto(options.tilemap); + if (options.tilemap.has_value()) { + tilemap = readInto(*options.tilemap); nbTileInstances = tilemap->size(); options.verbosePrint(Options::VERB_INTERM, "Read %zu tilemap entries.\n", nbTileInstances); } @@ -140,10 +141,10 @@ void reverse() { std::vector> palettes{ {Rgba(0xFFFFFFFF), Rgba(0xAAAAAAFF), Rgba(0x555555FF), Rgba(0x000000FF)} }; - if (!options.palettes.empty()) { + if (options.palettes.has_value()) { File file; - if (!file.open(options.palettes, std::ios::in | std::ios::binary)) { - fatal("Failed to open \"%s\": %s", file.c_str(options.palettes), strerror(errno)); + if (!file.open(*options.palettes, std::ios::in | std::ios::binary)) { + fatal("Failed to open \"%s\": %s", file.c_str(*options.palettes), strerror(errno)); } palettes.clear(); @@ -172,8 +173,8 @@ void reverse() { } std::optional> attrmap; - if (!options.attrmap.empty()) { - attrmap = readInto(options.attrmap); + if (options.attrmap.has_value()) { + attrmap = readInto(*options.attrmap); if (attrmap->size() != nbTileInstances) { fatal("Attribute map size (%zu tiles) doesn't match image's (%zu)", attrmap->size(), nbTileInstances); @@ -219,8 +220,8 @@ void reverse() { } std::optional> palmap; - if (!options.palmap.empty()) { - palmap = readInto(options.palmap); + if (options.palmap.has_value()) { + palmap = readInto(*options.palmap); if (palmap->size() != nbTileInstances) { fatal("Palette map size (%zu tiles) doesn't match image's (%zu)", palmap->size(), nbTileInstances); @@ -229,12 +230,12 @@ void reverse() { options.verbosePrint(Options::VERB_LOG_ACT, "Writing image...\n"); File pngFile; - if (!pngFile.open(options.input, std::ios::out | std::ios::binary)) { - fatal("Failed to create \"%s\": %s", pngFile.c_str(options.input), strerror(errno)); + if (!pngFile.open(*options.input, std::ios::out | std::ios::binary)) { + fatal("Failed to create \"%s\": %s", pngFile.c_str(*options.input), strerror(errno)); } png_structp png = png_create_write_struct( PNG_LIBPNG_VER_STRING, - const_cast(static_cast(pngFile.c_str(options.input))), pngError, + const_cast(static_cast(pngFile.c_str(*options.input))), pngError, pngWarning); if (!png) { fatal("Couldn't create PNG write struct: %s", strerror(errno));