diff --git a/contrib/bash_compl/_rgbgfx.bash b/contrib/bash_compl/_rgbgfx.bash index 82ee48f56..7729b39b8 100755 --- a/contrib/bash_compl/_rgbgfx.bash +++ b/contrib/bash_compl/_rgbgfx.bash @@ -13,6 +13,8 @@ _rgbgfx_completions() { [O]="group-outputs:normal" [u]="unique-tiles:normal" [v]="verbose:normal" + [X]="mirror-x:normal" + [Y]="mirror-y:normal" [Z]="columns:normal" [a]="attr-map:glob-*.attrmap" [A]="auto-attr-map:normal" diff --git a/contrib/zsh_compl/_rgbgfx b/contrib/zsh_compl/_rgbgfx index 91ed969a7..f009502c8 100644 --- a/contrib/zsh_compl/_rgbgfx +++ b/contrib/zsh_compl/_rgbgfx @@ -22,6 +22,8 @@ local args=( '(-t --tilemap -T --auto-tilemap)'{-T,--auto-tilemap}'[Shortcut for -t .tilemap]' '(-u --unique-tiles)'{-u,--unique-tiles}'[Eliminate redundant tiles]' {-v,--verbose}'[Enable verbose output]' + '(-X --mirror-x)'{-X,--mirror-x}'[Eliminate horizontally mirrored tiles from output]' + '(-Y --mirror-y)'{-Y,--mirror-y}'[Eliminate vertically mirrored tiles from output]' '(-Z --columns)'{-Z,--columns}'[Read the image in column-major order]' '(-a --attr-map -A --auto-attr-map)'{-a,--attr-map}'+[Generate a map of tile attributes (mirroring)]:attrmap file:_files' diff --git a/include/gfx/main.hpp b/include/gfx/main.hpp index a5566f443..9716d4619 100644 --- a/include/gfx/main.hpp +++ b/include/gfx/main.hpp @@ -13,11 +13,12 @@ #include "gfx/rgba.hpp" struct Options { - bool useColorCurve = false; // -C - bool allowMirroring = false; // -m - bool allowDedup = false; // -u - bool columnMajor = false; // -Z, previously -h - uint8_t verbosity = 0; // -v + bool useColorCurve = false; // -C + bool allowDedup = false; // -u + bool allowMirroringX = false; // -X, -m + bool allowMirroringY = false; // -Y, -m + bool columnMajor = false; // -Z + uint8_t verbosity = 0; // -v std::string attrmap{}; // -a, -A std::array baseTileIDs{0, 0}; // -b diff --git a/src/gfx/main.cpp b/src/gfx/main.cpp index 5558497c6..a4464e88a 100644 --- a/src/gfx/main.cpp +++ b/src/gfx/main.cpp @@ -144,14 +144,16 @@ static option const longopts[] = { {"unique-tiles", no_argument, nullptr, 'u'}, {"version", no_argument, nullptr, 'V'}, {"verbose", no_argument, nullptr, 'v'}, + {"mirror-x", no_argument, nullptr, 'X'}, {"trim-end", required_argument, nullptr, 'x'}, + {"mirror-y", no_argument, nullptr, 'Y'}, {"columns", no_argument, nullptr, 'Z'}, {nullptr, no_argument, nullptr, 0 } }; static void printUsage() { fputs( - "Usage: rgbgfx [-r stride] [-CmOuVZ] [-v [-v ...]] [-a | -A]\n" + "Usage: rgbgfx [-r stride] [-CmOuVXYZ] [-v [-v ...]] [-a | -A]\n" " [-b ] [-c ] [-d ] [-L ] [-N ]\n" " [-n ] [-o ] [-p | -P] [-q | -Q]\n" " [-s ] [-t | -T] [-x ] \n" @@ -466,8 +468,9 @@ static char *parseArgv(int argc, char *argv[]) { } break; case 'm': - options.allowMirroring = true; - [[fallthrough]]; // Imply `-u` + options.allowMirroringX = true; // Imply `-X` + options.allowMirroringY = true; // Imply `-Y` + [[fallthrough]]; // Imply `-u` case 'u': options.allowDedup = true; break; @@ -582,6 +585,14 @@ static char *parseArgv(int argc, char *argv[]) { error("Tile trim (-x) argument must be a valid number, not \"%s\"", musl_optarg); } break; + case 'X': + options.allowMirroringX = true; + options.allowDedup = true; // Imply `-u` + break; + case 'Y': + options.allowMirroringY = true; + options.allowDedup = true; // Imply `-u` + break; case 'Z': options.columnMajor = true; break; @@ -757,10 +768,12 @@ int main(int argc, char *argv[]) { fputs("Options:\n", stderr); if (options.columnMajor) fputs("\tVisit image in column-major order\n", stderr); - if (options.allowMirroring) - fputs("\tAllow mirroring tiles\n", stderr); if (options.allowDedup) fputs("\tAllow deduplicating tiles\n", stderr); + if (options.allowMirroringX) + fputs("\tAllow deduplicating horizontally mirrored tiles\n", stderr); + if (options.allowMirroringY) + fputs("\tAllow deduplicating vertically mirrored tiles\n", stderr); if (options.useColorCurve) fputs("\tUse color curve\n", stderr); fprintf(stderr, "\tBit depth: %" PRIu8 "bpp\n", options.bitDepth); diff --git a/src/gfx/process.cpp b/src/gfx/process.cpp index c77975ab3..b8088e98e 100644 --- a/src/gfx/process.cpp +++ b/src/gfx/process.cpp @@ -747,10 +747,10 @@ class TileData { // Update the hash _hash ^= bitplanes; - if (options.allowMirroring) { - // Count the line itself as mirrorred; vertical mirroring is - // already taken care of because the symmetric line will be XOR'd - // the same way. (...which is a problem, but probably benign.) + if (options.allowMirroringX) { + // Count the line itself as mirrorred horizontally; vertical mirroring is already + // taken care of because the symmetric line will be XOR'd the same way. + // (...this reduces the hash's efficiency, but seems benign with most real-world data.) _hash ^= flipTable[bitplanes >> 8] << 8 | flipTable[bitplanes & 0xFF]; } } @@ -773,17 +773,19 @@ class TileData { return MatchType::EXACT; } - if (!options.allowMirroring) { - return MatchType::NOPE; - } - // Check if we have horizontal mirroring, which scans the array forward again - if (std::equal(RANGE(_data), other._data.begin(), [](uint8_t lhs, uint8_t rhs) { - return lhs == flipTable[rhs]; - })) { + if (options.allowMirroringX + && std::equal(RANGE(_data), other._data.begin(), [](uint8_t lhs, uint8_t rhs) { + return lhs == flipTable[rhs]; + })) { return MatchType::HFLIP; } + // The remaining possibilities for matching all require vertical mirroring + if (!options.allowMirroringY) { + return MatchType::NOPE; + } + // Check if we have vertical or vertical+horizontal mirroring, for which we have to read // bitplane *pairs* backwards bool hasVFlip = true, hasVHFlip = true; @@ -803,8 +805,16 @@ class TileData { } // If we have both (i.e. we have symmetry), default to vflip only - assume(hasVFlip || hasVHFlip); - return hasVFlip ? MatchType::VFLIP : MatchType::VHFLIP; + if (hasVFlip) { + return MatchType::VFLIP; + } + + // If we allow both and have both, then use both + if (options.allowMirroringX && hasVHFlip) { + return MatchType::VHFLIP; + } + + return MatchType::NOPE; } friend bool operator==(TileData const &lhs, TileData const &rhs) { return lhs.tryMatching(rhs) != MatchType::NOPE;