From 6b559e99b21c7c524dc799e17c1d61d7f7ec20cc Mon Sep 17 00:00:00 2001 From: ISSOtm Date: Sat, 30 Dec 2023 20:31:13 +0100 Subject: [PATCH] Revert "Switch to using `std::filesystem` (#1235)" This reverts commit cf62ff772fcf2446e836cf6baab1c8f9ccdadad4. Some functions used by this break on macOS before 10.15, which we want to keep supporting. --- include/file.hpp | 11 +++---- include/gfx/main.hpp | 14 ++++----- src/gfx/main.cpp | 73 ++++++++++++++++++++++++++------------------ src/gfx/process.cpp | 71 +++++++++++++++++++----------------------- src/gfx/reverse.cpp | 39 +++++++++++------------ 5 files changed, 105 insertions(+), 103 deletions(-) diff --git a/include/file.hpp b/include/file.hpp index 9a5e06bb..eef3ad52 100644 --- a/include/file.hpp +++ b/include/file.hpp @@ -7,7 +7,6 @@ #include #include #include -#include #include #include #include @@ -42,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::filesystem::path const &path, std::ios_base::openmode mode) { + File *open(std::string const &path, std::ios_base::openmode mode) { if (path != "-") { return _file.emplace().open(path, mode) ? this : nullptr; } else if (mode & std::ios_base::in) { @@ -86,11 +85,11 @@ public: : nullptr; } - std::string string(std::filesystem::path const &path) const { - return std::visit(Visitor{[&path](std::filebuf const &) { return path.string(); }, + char const *c_str(std::string const &path) const { + return std::visit(Visitor{[&path](std::filebuf const &) { return path.c_str(); }, [](std::streambuf const *buf) { - return std::string{buf == std::cin.rdbuf() - ? "" : ""}; + return buf == std::cin.rdbuf() + ? "" : ""; }}, _file); } diff --git a/include/gfx/main.hpp b/include/gfx/main.hpp index 52ddc686..55189fbf 100644 --- a/include/gfx/main.hpp +++ b/include/gfx/main.hpp @@ -4,9 +4,7 @@ #define RGBDS_GFX_MAIN_HPP #include -#include #include -#include #include #include #include @@ -26,7 +24,7 @@ struct Options { bool columnMajor = false; // -Z, previously -h uint8_t verbosity = 0; // -v - std::optional attrmap{}; // -a, -A + std::string attrmap{}; // -a, -A std::array baseTileIDs{0, 0}; // -b enum { NO_SPEC, @@ -43,14 +41,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::optional output{}; // -o - std::optional palettes{}; // -p, -P - std::optional palmap{}; // -q, -Q + 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::optional tilemap{}; // -t, -T + std::string tilemap{}; // -t, -T uint64_t trim = 0; // -x - std::optional input{}; // positional arg + std::string 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 963a0fcb..33e8f0e7 100644 --- a/src/gfx/main.cpp +++ b/src/gfx/main.cpp @@ -7,12 +7,10 @@ #include #include #include -#include #include #include #include #include -#include #include #include #include @@ -240,11 +238,11 @@ static void skipWhitespace(char *&arg) { } static void registerInput(char const *arg) { - if (options.input.has_value()) { + if (!options.input.empty()) { 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 @@ -252,7 +250,7 @@ static void registerInput(char const *arg) { printUsage(); exit(1); } else { - options.input.emplace(arg); + options.input = arg; } } @@ -260,11 +258,10 @@ 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::filesystem::path const &path, - std::vector &argPool) { +static std::vector readAtFile(std::string const &path, std::vector &argPool) { File file; if (!file.open(path, std::ios_base::in)) { - fatal("Error reading @%s: %s", file.string(path).c_str(), strerror(errno)); + fatal("Error reading @%s: %s", file.c_str(path), strerror(errno)); } // We only filter out `EOF`, but calling `isblank()` on anything else is UB! @@ -349,8 +346,8 @@ static char *parseArgv(int argc, char **argv) { break; case 'a': localOptions.autoAttrmap = false; - if (options.attrmap.has_value()) - warning("Overriding attrmap file %s", options.attrmap->c_str()); + if (!options.attrmap.empty()) + warning("Overriding attrmap file %s", options.attrmap.c_str()); options.attrmap = musl_optarg; break; case 'b': @@ -496,8 +493,8 @@ static char *parseArgv(int argc, char **argv) { localOptions.groupOutputs = true; break; case 'o': - if (options.output.has_value()) - warning("Overriding tile data file %s", options.output->c_str()); + if (!options.output.empty()) + warning("Overriding tile data file %s", options.output.c_str()); options.output = musl_optarg; break; case -'P': @@ -508,8 +505,8 @@ static char *parseArgv(int argc, char **argv) { break; case 'p': localOptions.autoPalettes = false; - if (options.palettes.has_value()) - warning("Overriding palettes file %s", options.palettes->c_str()); + if (!options.palettes.empty()) + warning("Overriding palettes file %s", options.palettes.c_str()); options.palettes = musl_optarg; break; case -'Q': @@ -520,8 +517,8 @@ static char *parseArgv(int argc, char **argv) { break; case 'q': localOptions.autoPalmap = false; - if (options.palmap.has_value()) - warning("Overriding palette map file %s", options.palmap->c_str()); + if (!options.palmap.empty()) + warning("Overriding palette map file %s", options.palmap.c_str()); options.palmap = musl_optarg; break; case 'r': @@ -552,8 +549,8 @@ static char *parseArgv(int argc, char **argv) { break; case 't': localOptions.autoTilemap = false; - if (options.tilemap.has_value()) - warning("Overriding tilemap file %s", options.tilemap->c_str()); + if (!options.tilemap.empty()) + warning("Overriding tilemap file %s", options.tilemap.c_str()); options.tilemap = musl_optarg; break; case 'V': @@ -658,17 +655,36 @@ int main(int argc, char *argv[]) { 1u << options.bitDepth, options.nbColorsPerPal); } - auto autoOutPath = [](bool autoOptEnabled, std::optional &path, - char const *extension) { + auto autoOutPath = [](bool autoOptEnabled, std::string &path, char const *extension) { if (autoOptEnabled) { - auto image = localOptions.groupOutputs ? options.output : options.input; - if (!image.has_value()) { + auto &image = localOptions.groupOutputs ? options.output : options.input; + if (image.empty()) { fprintf(stderr, "FATAL: No %s specified\n", localOptions.groupOutputs ? "output tile data file" : "input image"); printUsage(); exit(1); } - path.emplace(*image).replace_extension(extension); + + // Manual implementation of std::filesystem::path.replace_extension(). + constexpr std::string_view chars = +// Both must start with a dot! +#if defined(_MSC_VER) || defined(__MINGW32__) + "./\\"sv; +#else + "./"sv; +#endif + size_t len = image.npos; + size_t i = image.find_last_of(chars); + if (i != image.npos && image[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(image[i - 1], 1) == chars.npos) { + // We can replace the extension + len = i; + } + } + path.assign(image, 0, len); + path.append(extension); } }; autoOutPath(localOptions.autoAttrmap, options.attrmap, ".attrmap"); @@ -753,10 +769,9 @@ 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::optional const &path) { - if (path.has_value()) { - fprintf(stderr, "\t%s: %s\n", name, path->c_str()); + auto printPath = [](char const *name, std::string const &path) { + if (!path.empty()) { + fprintf(stderr, "\t%s: %s\n", name, path.c_str()); } }; printPath("Input image", options.input); @@ -772,13 +787,13 @@ int main(int argc, char *argv[]) { giveUp(); } - if (options.input.has_value()) { + if (!options.input.empty()) { if (options.reverse()) { reverse(); } else { process(); } - } else if (options.palettes.has_value() && options.palSpecType == Options::EXPLICIT + } else if (!options.palettes.empty() && options.palSpecType == Options::EXPLICIT && !options.reverse()) { processPalettes(); } else { diff --git a/src/gfx/process.cpp b/src/gfx/process.cpp index 775a43c0..c3929156 100644 --- a/src/gfx/process.cpp +++ b/src/gfx/process.cpp @@ -8,7 +8,6 @@ #include #include #include -#include #include #include #include @@ -72,7 +71,7 @@ public: }; class Png { - std::filesystem::path const &path; + std::string const &path; File file{}; png_structp png = nullptr; png_infop info = nullptr; @@ -89,13 +88,13 @@ class Png { [[noreturn]] static void handleError(png_structp png, char const *msg) { Png *self = reinterpret_cast(png_get_error_ptr(png)); - fatal("Error reading input image (\"%s\"): %s", self->string().c_str(), msg); + fatal("Error reading input image (\"%s\"): %s", self->c_str(), msg); } static void handleWarning(png_structp png, char const *msg) { Png *self = reinterpret_cast(png_get_error_ptr(png)); - warning("In input image (\"%s\"): %s", self->string().c_str(), msg); + warning("In input image (\"%s\"): %s", self->c_str(), msg); } static void readData(png_structp png, png_bytep data, size_t length) { @@ -107,7 +106,7 @@ class Png { if (nbBytesRead != expectedLen) { fatal("Error reading input image (\"%s\"): file too short (expected at least %zd more " "bytes after reading %lld)", - self->string().c_str(), length - nbBytesRead, + self->c_str(), length - nbBytesRead, self->file->pubseekoff(0, std::ios_base::cur)); } } @@ -129,7 +128,7 @@ public: Rgba const &pixel(uint32_t x, uint32_t y) const { return pixels[y * width + x]; } - std::string string() const { return file.string(path); } + char const *c_str() const { return file.c_str(path); } bool isSuitableForGrayscale() const { // Check that all of the grays don't fall into the same "bin" @@ -172,9 +171,9 @@ 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::filesystem::path const &filePath) : path(filePath), colors() { + explicit Png(std::string 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.string(path).c_str(), + fatal("Failed to open input image (\"%s\"): %s", file.c_str(path), strerror(errno)); } @@ -185,7 +184,7 @@ public: if (file->sgetn(reinterpret_cast(pngHeader.data()), pngHeader.size()) != static_cast(pngHeader.size()) // Not enough bytes? || png_sig_cmp(pngHeader.data(), 0, pngHeader.size()) != 0) { - fatal("Input file (\"%s\") is not a PNG image!", file.string(path).c_str()); + fatal("Input file (\"%s\") is not a PNG image!", file.c_str(path)); } options.verbosePrint(Options::VERB_INTERM, "PNG header signature is OK\n"); @@ -641,10 +640,10 @@ static void outputPalettes(std::vector const &palettes) { options.nbPalettes); } - if (options.palettes.has_value()) { + if (!options.palettes.empty()) { File output; - if (!output.open(*options.palettes, std::ios_base::out | std::ios_base::binary)) { - fatal("Failed to open \"%s\": %s", output.string(*options.palettes).c_str(), + 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)); } @@ -774,9 +773,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.string(*options.output).c_str(), - 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; @@ -809,11 +807,11 @@ static void outputTileData(Png const &png, DefaultInitVec const &a static void outputMaps(DefaultInitVec const &attrmap, DefaultInitVec const &mappings) { std::optional tilemapOutput, attrmapOutput, palmapOutput; - auto autoOpenPath = [](std::optional &path, std::optional &file) { - if (path.has_value()) { + auto autoOpenPath = [](std::string const &path, std::optional &file) { + if (!path.empty()) { file.emplace(); - if (!file->open(*path, std::ios_base::out | std::ios_base::binary)) { - fatal("Failed to open \"%s\": %s", file->string(*path).c_str(), + if (!file->open(path, std::ios_base::out | std::ios_base::binary)) { + fatal("Failed to open \"%s\": %s", file->c_str(options.tilemap), strerror(errno)); } } @@ -915,9 +913,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.string(*options.output).c_str(), - 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; @@ -931,9 +928,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.string(*options.tilemap).c_str(), - 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) { @@ -944,9 +940,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.string(*options.attrmap).c_str(), - 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) { @@ -960,9 +955,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.string(*options.palmap).c_str(), - 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 +980,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,13 +1102,12 @@ contained:; nbTilesW * nbTilesH, options.maxNbTiles[0], options.maxNbTiles[1]); } - if (options.output.has_value()) { + if (!options.output.empty()) { options.verbosePrint(Options::VERB_LOG_ACT, "Generating unoptimized tile data...\n"); unoptimized::outputTileData(png, attrmap, palettes, mappings); } - if (options.tilemap.has_value() || options.attrmap.has_value() - || options.palmap.has_value()) { + 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"); @@ -1130,22 +1123,22 @@ contained:; tiles.size(), options.maxNbTiles[0], options.maxNbTiles[1]); } - if (options.output.has_value()) { + if (!options.output.empty()) { options.verbosePrint(Options::VERB_LOG_ACT, "Generating optimized tile data...\n"); optimized::outputTileData(tiles); } - if (options.tilemap.has_value()) { + if (!options.tilemap.empty()) { options.verbosePrint(Options::VERB_LOG_ACT, "Generating optimized tilemap...\n"); optimized::outputTilemap(attrmap); } - if (options.attrmap.has_value()) { + if (!options.attrmap.empty()) { options.verbosePrint(Options::VERB_LOG_ACT, "Generating optimized attrmap...\n"); optimized::outputAttrmap(attrmap, mappings); } - if (options.palmap.has_value()) { + 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 3f09f75a..ea481ba5 100644 --- a/src/gfx/reverse.cpp +++ b/src/gfx/reverse.cpp @@ -7,7 +7,6 @@ #include #include #include -#include #include #include #include @@ -22,10 +21,10 @@ #include "gfx/main.hpp" -static DefaultInitVec readInto(std::filesystem::path path) { +static DefaultInitVec readInto(std::string path) { File file; if (!file.open(path, std::ios::in | std::ios::binary)) { - fatal("Failed to open \"%s\": %s", file.string(path).c_str(), strerror(errno)); + fatal("Failed to open \"%s\": %s", file.c_str(path), strerror(errno)); } DefaultInitVec data(128 * 16); // Begin with some room pre-allocated @@ -78,11 +77,11 @@ void reverse() { // Check for weird flag combinations - if (!options.output.has_value()) { + if (options.output.empty()) { fatal("Tile data must be provided when reversing an image!"); } - if (options.allowDedup && !options.tilemap.has_value()) { + if (options.allowDedup && options.tilemap.empty()) { warning("Tile deduplication is enabled, but no tilemap is provided?"); } @@ -101,7 +100,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", @@ -112,8 +111,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.has_value()) { - tilemap = readInto(*options.tilemap); + if (!options.tilemap.empty()) { + tilemap = readInto(options.tilemap); nbTileInstances = tilemap->size(); options.verbosePrint(Options::VERB_INTERM, "Read %zu tilemap entries.\n", nbTileInstances); } @@ -142,11 +141,10 @@ void reverse() { {Rgba(0xFFFFFFFF), Rgba(0xAAAAAAFF), Rgba(0x555555FF), Rgba(0x000000FF)} }; // If a palette file is used as input, it overrides the default colors. - if (options.palettes.has_value()) { + if (!options.palettes.empty()) { File file; - if (!file.open(*options.palettes, std::ios::in | std::ios::binary)) { - fatal("Failed to open \"%s\": %s", file.string(*options.palettes).c_str(), - 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(); @@ -183,8 +181,8 @@ void reverse() { } std::optional> attrmap; - if (options.attrmap.has_value()) { - attrmap = readInto(*options.attrmap); + if (!options.attrmap.empty()) { + attrmap = readInto(options.attrmap); if (attrmap->size() != nbTileInstances) { fatal("Attribute map size (%zu tiles) doesn't match image's (%zu)", attrmap->size(), nbTileInstances); @@ -230,8 +228,8 @@ void reverse() { } std::optional> palmap; - if (options.palmap.has_value()) { - palmap = readInto(*options.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); @@ -240,14 +238,13 @@ 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.string(*options.input).c_str(), - 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.string(*options.input).c_str())), - pngError, pngWarning); + const_cast(static_cast(pngFile.c_str(options.input))), pngError, + pngWarning); if (!png) { fatal("Couldn't create PNG write struct: %s", strerror(errno)); }