From 1849a35e61d85e899bcc7e3d933f168963c9a44f Mon Sep 17 00:00:00 2001 From: Rangi42 Date: Wed, 23 Jul 2025 21:03:50 -0400 Subject: [PATCH] Rename proto-palettes to color sets (copied from rsgbds) --- Makefile | 6 +- .../gfx/{proto_palette.hpp => color_set.hpp} | 10 +- include/gfx/pal_packing.hpp | 7 +- src/CMakeLists.txt | 2 +- src/gfx/{proto_palette.cpp => color_set.cpp} | 14 +- src/gfx/pal_packing.cpp | 297 +++++++++--------- src/gfx/process.cpp | 110 ++++--- 7 files changed, 218 insertions(+), 228 deletions(-) rename include/gfx/{proto_palette.hpp => color_set.hpp} (80%) rename src/gfx/{proto_palette.cpp => color_set.cpp} (81%) diff --git a/Makefile b/Makefile index 6aa93e31..a0423d4a 100644 --- a/Makefile +++ b/Makefile @@ -101,13 +101,13 @@ rgbfix_obj := \ rgbgfx_obj := \ ${common_obj} \ + src/gfx/color_set.o \ src/gfx/main.o \ src/gfx/pal_packing.o \ src/gfx/pal_sorting.o \ src/gfx/pal_spec.o \ src/gfx/png.o \ src/gfx/process.o \ - src/gfx/proto_palette.o \ src/gfx/reverse.o \ src/gfx/rgba.o \ src/gfx/warning.o @@ -145,6 +145,8 @@ src/link/script.hpp: src/link/script.cpp $Qtouch $@ # Only RGBGFX uses libpng (POSIX make doesn't support pattern rules to cover all these) +src/gfx/color_set.o: src/gfx/color_set.cpp + $Q${CXX} ${REALCXXFLAGS} ${PNGCFLAGS} -c -o $@ $< src/gfx/main.o: src/gfx/main.cpp $Q${CXX} ${REALCXXFLAGS} ${PNGCFLAGS} -c -o $@ $< src/gfx/pal_packing.o: src/gfx/pal_packing.cpp @@ -157,8 +159,6 @@ src/gfx/png.o: src/gfx/png.cpp $Q${CXX} ${REALCXXFLAGS} ${PNGCFLAGS} -c -o $@ $< src/gfx/process.o: src/gfx/process.cpp $Q${CXX} ${REALCXXFLAGS} ${PNGCFLAGS} -c -o $@ $< -src/gfx/proto_palette.o: src/gfx/proto_palette.cpp - $Q${CXX} ${REALCXXFLAGS} ${PNGCFLAGS} -c -o $@ $< src/gfx/reverse.o: src/gfx/reverse.cpp $Q${CXX} ${REALCXXFLAGS} ${PNGCFLAGS} -c -o $@ $< src/gfx/rgba.o: src/gfx/rgba.cpp diff --git a/include/gfx/proto_palette.hpp b/include/gfx/color_set.hpp similarity index 80% rename from include/gfx/proto_palette.hpp rename to include/gfx/color_set.hpp index 7557ad69..3003e09a 100644 --- a/include/gfx/proto_palette.hpp +++ b/include/gfx/color_set.hpp @@ -1,13 +1,13 @@ // SPDX-License-Identifier: MIT -#ifndef RGBDS_GFX_PROTO_PALETTE_HPP -#define RGBDS_GFX_PROTO_PALETTE_HPP +#ifndef RGBDS_GFX_COLOR_SET_HPP +#define RGBDS_GFX_COLOR_SET_HPP #include #include #include -class ProtoPalette { +class ColorSet { public: static constexpr size_t capacity = 4; @@ -26,7 +26,7 @@ public: WE_BIGGER, THEY_BIGGER = -1, }; - ComparisonResult compare(ProtoPalette const &other) const; + ComparisonResult compare(ColorSet const &other) const; size_t size() const; bool empty() const; @@ -35,4 +35,4 @@ public: decltype(_colorIndices)::const_iterator end() const; }; -#endif // RGBDS_GFX_PROTO_PALETTE_HPP +#endif // RGBDS_GFX_COLOR_SET_HPP diff --git a/include/gfx/pal_packing.hpp b/include/gfx/pal_packing.hpp index fa0339fd..2af5de11 100644 --- a/include/gfx/pal_packing.hpp +++ b/include/gfx/pal_packing.hpp @@ -8,10 +8,9 @@ #include struct Palette; -class ProtoPalette; +class ColorSet; -// Returns which palette each proto-palette maps to, and how many palettes are necessary -std::tuple, size_t> - overloadAndRemove(std::vector const &protoPalettes); +// Returns which palette each color set maps to, and how many palettes are necessary +std::tuple, size_t> overloadAndRemove(std::vector const &colorSets); #endif // RGBDS_GFX_PAL_PACKING_HPP diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 22cd030f..e39fa860 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -75,13 +75,13 @@ set(rgbfix_src ) set(rgbgfx_src + "gfx/color_set.cpp" "gfx/main.cpp" "gfx/pal_packing.cpp" "gfx/pal_sorting.cpp" "gfx/pal_spec.cpp" "gfx/png.cpp" "gfx/process.cpp" - "gfx/proto_palette.cpp" "gfx/reverse.cpp" "gfx/rgba.cpp" "gfx/warning.cpp" diff --git a/src/gfx/proto_palette.cpp b/src/gfx/color_set.cpp similarity index 81% rename from src/gfx/proto_palette.cpp rename to src/gfx/color_set.cpp index 7a7a532c..6dc35ec9 100644 --- a/src/gfx/proto_palette.cpp +++ b/src/gfx/color_set.cpp @@ -1,12 +1,12 @@ // SPDX-License-Identifier: MIT -#include "gfx/proto_palette.hpp" +#include "gfx/color_set.hpp" #include #include "helpers.hpp" -void ProtoPalette::add(uint16_t color) { +void ColorSet::add(uint16_t color) { size_t i = 0; // Seek the first slot greater than the new color @@ -37,7 +37,7 @@ void ProtoPalette::add(uint16_t color) { _colorIndices[i] = color; } -ProtoPalette::ComparisonResult ProtoPalette::compare(ProtoPalette const &other) const { +ColorSet::ComparisonResult ColorSet::compare(ColorSet const &other) const { // This works because the sets are sorted numerically assume(std::is_sorted(RANGE(_colorIndices))); assume(std::is_sorted(RANGE(other._colorIndices))); @@ -63,17 +63,17 @@ ProtoPalette::ComparisonResult ProtoPalette::compare(ProtoPalette const &other) return theyBigger ? THEY_BIGGER : (weBigger ? WE_BIGGER : NEITHER); } -size_t ProtoPalette::size() const { +size_t ColorSet::size() const { return std::distance(RANGE(*this)); } -bool ProtoPalette::empty() const { +bool ColorSet::empty() const { return _colorIndices[0] == UINT16_MAX; } -auto ProtoPalette::begin() const -> decltype(_colorIndices)::const_iterator { +auto ColorSet::begin() const -> decltype(_colorIndices)::const_iterator { return _colorIndices.begin(); } -auto ProtoPalette::end() const -> decltype(_colorIndices)::const_iterator { +auto ColorSet::end() const -> decltype(_colorIndices)::const_iterator { return std::find(RANGE(_colorIndices), UINT16_MAX); } diff --git a/src/gfx/pal_packing.cpp b/src/gfx/pal_packing.cpp index 64361de1..eaa0ba26 100644 --- a/src/gfx/pal_packing.cpp +++ b/src/gfx/pal_packing.cpp @@ -15,26 +15,28 @@ #include "helpers.hpp" +#include "gfx/color_set.hpp" #include "gfx/main.hpp" -#include "gfx/proto_palette.hpp" // The solvers here are picked from the paper at https://arxiv.org/abs/1605.00558: // "Algorithms for the Pagination Problem, a Bin Packing with Overlapping Items" // Their formulation of the problem consists in packing "tiles" into "pages". // Here is a correspondence table for our application of it: -// Paper | RGBGFX -// ------+------- -// Tile | Proto-palette -// Page | Palette +// +// Paper | RGBGFX +// -------+---------- +// Symbol | Color +// Tile | Color set +// Page | Palette -// A reference to a proto-palette, and attached attributes for sorting purposes -struct ProtoPalAttrs { - size_t protoPalIndex; +// A reference to a color set, and attached attributes for sorting purposes +struct ColorSetAttrs { + size_t colorSetIndex; // Pages from which we are banned (to prevent infinite loops) // This is dynamic because we wish not to hard-cap the amount of palettes std::vector bannedPages; - explicit ProtoPalAttrs(size_t index) : protoPalIndex(index) {} + explicit ColorSetAttrs(size_t index) : colorSetIndex(index) {} bool isBannedFrom(size_t index) const { return index < bannedPages.size() && bannedPages[index]; } @@ -46,27 +48,27 @@ struct ProtoPalAttrs { } }; -// A collection of proto-palettes assigned to a palette +// A collection of color sets assigned to a palette // Does not contain the actual color indices because we need to be able to remove elements -class AssignedProtos { +class AssignedSets { // We leave room for emptied slots to avoid copying the structs around on removal - std::vector> _assigned; - // For resolving proto-palette indices - std::vector const *_protoPals; + std::vector> _assigned; + // For resolving color set indices + std::vector const *_colorSets; public: template - AssignedProtos(std::vector const &protoPals, Ts &&...elems) - : _assigned{std::forward(elems)...}, _protoPals{&protoPals} {} + AssignedSets(std::vector const &colorSets, Ts &&...elems) + : _assigned{std::forward(elems)...}, _colorSets{&colorSets} {} private: template typename Constness> class Iter { public: - friend class AssignedProtos; + friend class AssignedSets; // For `iterator_traits` using difference_type = typename std::iterator_traits::difference_type; - using value_type = ProtoPalAttrs; + using value_type = ColorSetAttrs; using pointer = Constness *; using reference = Constness &; using iterator_category = std::forward_iterator_tag; @@ -122,12 +124,12 @@ public: } const_iterator end() const { return const_iterator{&_assigned, _assigned.end()}; } - // Assigns a new ProtoPalAttrs in a free slot, assuming there is one - // Args are passed to the `ProtoPalAttrs`'s constructor + // Assigns a new ColorSetAttrs in a free slot, assuming there is one + // Args are passed to the `ColorSetAttrs`'s constructor template void assign(Ts &&...args) { auto freeSlot = - std::find_if_not(RANGE(_assigned), [](std::optional const &slot) { + std::find_if_not(RANGE(_assigned), [](std::optional const &slot) { return slot.has_value(); }); @@ -145,11 +147,11 @@ public: bool empty() const { return std::find_if( RANGE(_assigned), - [](std::optional const &slot) { return slot.has_value(); } + [](std::optional const &slot) { return slot.has_value(); } ) == _assigned.end(); } - size_t nbProtoPals() const { return std::distance(RANGE(*this)); } + size_t nbColorSets() const { return std::distance(RANGE(*this)); } private: template @@ -157,11 +159,11 @@ private: std::unordered_set &colors, Iter iter, Iter const &end, - std::vector const &protoPals + std::vector const &colorSets ) { for (; iter != end; ++iter) { - ProtoPalette const &protoPal = protoPals[iter->protoPalIndex]; - colors.insert(RANGE(protoPal)); + ColorSet const &colorSet = colorSets[iter->colorSetIndex]; + colors.insert(RANGE(colorSet)); } } // This function should stay private because it returns a reference to a unique object @@ -171,20 +173,20 @@ private: static std::unordered_set colors; colors.clear(); - addUniqueColors(colors, RANGE(*this), *_protoPals); + addUniqueColors(colors, RANGE(*this), *_colorSets); return colors; } public: // Returns the number of distinct colors size_t volume() const { return uniqueColors().size(); } - bool canFit(ProtoPalette const &protoPal) const { + bool canFit(ColorSet const &colorSet) const { std::unordered_set &colors = uniqueColors(); - colors.insert(RANGE(protoPal)); + colors.insert(RANGE(colorSet)); return colors.size() <= options.maxOpaqueColors(); } - // The `relSizeOf` method below should compute the sum, for each color in `protoPal`, of - // the reciprocal of the "multiplicity" of the color across "our" proto-palettes. + // The `relSizeOf` method below should compute the sum, for each color in `colorSet`, of + // the reciprocal of the "multiplicity" of the color across "our" color sets. // However, literally computing the reciprocals would involve floating-point division, which // leads to imprecision and even platform-specific differences. // We avoid this by multiplying the reciprocals by a factor such that division always produces @@ -199,22 +201,22 @@ public: return factor; }(); - // Computes the "relative size" of a proto-palette on this palette; - // it's a measure of how much this proto-palette would "cost" to introduce. - uint32_t relSizeOf(ProtoPalette const &protoPal) const { + // Computes the "relative size" of a color set on this palette; + // it's a measure of how much this color set would "cost" to introduce. + uint32_t relSizeOf(ColorSet const &colorSet) const { // NOTE: this function must not call `uniqueColors`, or one of its callers will break! uint32_t relSize = 0; - for (uint16_t color : protoPal) { - // How many of our proto-palettes does this color also belong to? + for (uint16_t color : colorSet) { + // How many of our color sets does this color also belong to? uint32_t multiplicity = - std::count_if(RANGE(*this), [this, &color](ProtoPalAttrs const &attrs) { - ProtoPalette const &pal = (*_protoPals)[attrs.protoPalIndex]; + std::count_if(RANGE(*this), [this, &color](ColorSetAttrs const &attrs) { + ColorSet const &pal = (*_colorSets)[attrs.colorSetIndex]; return std::find(RANGE(pal), color) != pal.end(); }); // We increase the denominator by 1 here; the reference code does this, // but the paper does not. Not adding 1 makes a multiplicity of 0 cause a division by 0 - // (that is, if the color is not found in any proto-palette), and adding 1 still seems + // (that is, if the color is not found in any color set), and adding 1 still seems // to preserve the paper's reasoning. // // The scale factor should ensure integer divisions only. @@ -224,12 +226,12 @@ public: return relSize; } - // Computes the "relative size" of a set of proto-palettes on this palette + // Computes the "relative size" of a set of color sets on this palette template - size_t combinedVolume(Iter &&begin, Iter const &end, std::vector const &protoPals) + size_t combinedVolume(Iter &&begin, Iter const &end, std::vector const &colorSets) const { std::unordered_set &colors = uniqueColors(); - addUniqueColors(colors, std::forward(begin), end, protoPals); + addUniqueColors(colors, std::forward(begin), end, colorSets); return colors.size(); } // Computes the "relative size" of a set of colors on this palette @@ -242,13 +244,13 @@ public: }; static void verboseOutputAssignments( - std::vector const &assignments, std::vector const &protoPalettes + std::vector const &assignments, std::vector const &colorSets ) { - for (AssignedProtos const &assignment : assignments) { + for (AssignedSets const &assignment : assignments) { fputs("{ ", stderr); - for (ProtoPalAttrs const &attrs : assignment) { - fprintf(stderr, "[%zu] ", attrs.protoPalIndex); - for (uint16_t colorIndex : protoPalettes[attrs.protoPalIndex]) { + for (ColorSetAttrs const &attrs : assignment) { + fprintf(stderr, "[%zu] ", attrs.colorSetIndex); + for (uint16_t colorIndex : colorSets[attrs.colorSetIndex]) { fprintf(stderr, "%04" PRIx16 ", ", colorIndex); } } @@ -256,9 +258,7 @@ static void verboseOutputAssignments( } } -static void decant( - std::vector &assignments, std::vector const &protoPalettes -) { +static void decant(std::vector &assignments, std::vector const &colorSets) { // "Decanting" is the process of moving all *things* that can fit in a lower index there auto decantOn = [&assignments](auto const &tryDecanting) { // No need to attempt decanting on palette #0, as there are no palettes to decant to @@ -268,7 +268,7 @@ static void decant( tryDecanting(assignments[to], assignments[from]); } - // If the proto-palette is now empty, remove it + // If the color set is now empty, remove it // Doing this now reduces the number of iterations performed by later steps // NB: order is intentionally preserved so as not to alter the "decantation"'s // properties @@ -287,11 +287,11 @@ static void decant( ); // Decant on palettes - decantOn([&protoPalettes](AssignedProtos &to, AssignedProtos &from) { - // If the entire palettes can be merged, move all of `from`'s proto-palettes - if (to.combinedVolume(RANGE(from), protoPalettes) <= options.maxOpaqueColors()) { - for (ProtoPalAttrs &attrs : from) { - to.assign(attrs.protoPalIndex); + decantOn([&colorSets](AssignedSets &to, AssignedSets &from) { + // If the entire palettes can be merged, move all of `from`'s color sets + if (to.combinedVolume(RANGE(from), colorSets) <= options.maxOpaqueColors()) { + for (ColorSetAttrs &attrs : from) { + to.assign(attrs.colorSetIndex); } from.clear(); } @@ -300,13 +300,13 @@ static void decant( Options::VERB_DEBUG, "%zu palettes after decanting on palettes\n", assignments.size() ); - // Decant on "components" (= proto-pals sharing colors) - decantOn([&protoPalettes](AssignedProtos &to, AssignedProtos &from) { - // We need to iterate on all the "components", which are groups of proto-palettes sharing at - // least one color with another proto-palettes in the group. - // We do this by adding the first available proto-palette, and then looking for palettes - // with common colors. (As an optimization, we know we can skip palettes already scanned.) - std::vector processed(from.nbProtoPals(), false); + // Decant on "components" (= color sets sharing colors) + decantOn([&colorSets](AssignedSets &to, AssignedSets &from) { + // We need to iterate on all the "components", which are groups of color sets sharing at + // least one color with another color sets in the group. + // We do this by adding the first available color set, and then looking for palettes with + // common colors. (As an optimization, we know we can skip palettes already scanned.) + std::vector processed(from.nbColorSets(), false); std::unordered_set colors; std::vector members; while (true) { @@ -322,19 +322,19 @@ static void decant( members.clear(); assume(members.empty()); // Compiler optimization hint do { - ProtoPalette const &protoPal = protoPalettes[attrs->protoPalIndex]; - // If this is the first proto-pal, or if at least one color matches, add it + ColorSet const &colorSet = colorSets[attrs->colorSetIndex]; + // If this is the first color set, or if at least one color matches, add it if (members.empty() - || std::find_first_of(RANGE(colors), RANGE(protoPal)) != colors.end()) { - colors.insert(RANGE(protoPal)); + || std::find_first_of(RANGE(colors), RANGE(colorSet)) != colors.end()) { + colors.insert(RANGE(colorSet)); members.push_back(iter - processed.begin()); - *iter = true; // Mark that proto-pal as processed + *iter = true; // Mark that color set as processed } ++attrs; } while (++iter != processed.end()); if (to.combinedVolume(RANGE(colors)) <= options.maxOpaqueColors()) { - // Iterate through the component's proto-palettes, and transfer them + // Iterate through the component's color sets, and transfer them auto member = from.begin(); size_t curIndex = 0; for (size_t index : members) { @@ -350,56 +350,53 @@ static void decant( Options::VERB_DEBUG, "%zu palettes after decanting on \"components\"\n", assignments.size() ); - // Decant on individual proto-palettes - decantOn([&protoPalettes](AssignedProtos &to, AssignedProtos &from) { + // Decant on individual color sets + decantOn([&colorSets](AssignedSets &to, AssignedSets &from) { for (auto iter = from.begin(); iter != from.end(); ++iter) { - if (to.canFit(protoPalettes[iter->protoPalIndex])) { + if (to.canFit(colorSets[iter->colorSetIndex])) { to.assign(std::move(*iter)); from.remove(iter); } } }); options.verbosePrint( - Options::VERB_DEBUG, "%zu palettes after decanting on proto-palettes\n", assignments.size() + Options::VERB_DEBUG, "%zu palettes after decanting on color sets\n", assignments.size() ); } -std::tuple, size_t> - overloadAndRemove(std::vector const &protoPalettes) { +std::tuple, size_t> overloadAndRemove(std::vector const &colorSets) { options.verbosePrint( Options::VERB_LOG_ACT, "Paginating palettes using \"overload-and-remove\" strategy...\n" ); - // Sort the proto-palettes by size, which improves the packing algorithm's efficiency - auto const indexOfLargestProtoPalFirst = [&protoPalettes](size_t left, size_t right) { - ProtoPalette const &lhs = protoPalettes[left]; - ProtoPalette const &rhs = protoPalettes[right]; - return lhs.size() > rhs.size(); // We want the proto-pals to be sorted *largest first*! + // Sort the color sets by size, which improves the packing algorithm's efficiency + auto const indexOfLargestColorSetFirst = [&colorSets](size_t left, size_t right) { + ColorSet const &lhs = colorSets[left]; + ColorSet const &rhs = colorSets[right]; + return lhs.size() > rhs.size(); // We want the color sets to be sorted *largest first*! }; - std::vector sortedProtoPalIDs; - sortedProtoPalIDs.reserve(protoPalettes.size()); - for (size_t i = 0; i < protoPalettes.size(); ++i) { - sortedProtoPalIDs.insert( - std::lower_bound(RANGE(sortedProtoPalIDs), i, indexOfLargestProtoPalFirst), i + std::vector sortedColorSetIDs; + sortedColorSetIDs.reserve(colorSets.size()); + for (size_t i = 0; i < colorSets.size(); ++i) { + sortedColorSetIDs.insert( + std::lower_bound(RANGE(sortedColorSetIDs), i, indexOfLargestColorSetFirst), i ); } - // Begin with all proto-palettes queued up for insertion - std::queue queue(std::deque(RANGE(sortedProtoPalIDs))); + // Begin with all color sets queued up for insertion + std::queue queue(std::deque(RANGE(sortedColorSetIDs))); // Begin with no pages - std::vector assignments{}; + std::vector assignments{}; for (; !queue.empty(); queue.pop()) { - ProtoPalAttrs const &attrs = queue.front(); // Valid until the `queue.pop()` - options.verbosePrint( - Options::VERB_TRACE, "Handling proto-palette %zu\n", attrs.protoPalIndex - ); + ColorSetAttrs const &attrs = queue.front(); // Valid until the `queue.pop()` + options.verbosePrint(Options::VERB_TRACE, "Handling color set %zu\n", attrs.colorSetIndex); - ProtoPalette const &protoPal = protoPalettes[attrs.protoPalIndex]; + ColorSet const &colorSet = colorSets[attrs.colorSetIndex]; size_t bestPalIndex = assignments.size(); - // We're looking for a palette where the proto-palette's relative size is less than + // We're looking for a palette where the color set's relative size is less than // its actual size; so only overwrite the "not found" index on meeting that criterion - uint32_t bestRelSize = protoPal.size() * AssignedProtos::scaleFactor; + uint32_t bestRelSize = colorSet.size() * AssignedSets::scaleFactor; for (size_t i = 0; i < assignments.size(); ++i) { // Skip the page if this one is banned from it @@ -407,14 +404,14 @@ std::tuple, size_t> continue; } - uint32_t relSize = assignments[i].relSizeOf(protoPal); + uint32_t relSize = assignments[i].relSizeOf(colorSet); options.verbosePrint( Options::VERB_TRACE, " Relative size to palette %zu (of %zu): %" PRIu32 " (size = %zu)\n", i, assignments.size(), relSize, - protoPal.size() + colorSet.size() ); if (relSize < bestRelSize) { bestPalIndex = i; @@ -426,19 +423,19 @@ std::tuple, size_t> // Found nowhere to put it, create a new page containing just that one options.verbosePrint( Options::VERB_TRACE, - "Assigning proto-palette %zu to new palette %zu\n", - attrs.protoPalIndex, + "Assigning color set %zu to new palette %zu\n", + attrs.colorSetIndex, bestPalIndex ); - assignments.emplace_back(protoPalettes, std::move(attrs)); + assignments.emplace_back(colorSets, std::move(attrs)); } else { options.verbosePrint( Options::VERB_TRACE, - "Assigning proto-palette %zu to palette %zu\n", - attrs.protoPalIndex, + "Assigning color set %zu to palette %zu\n", + attrs.colorSetIndex, bestPalIndex ); - AssignedProtos &bestPal = assignments[bestPalIndex]; + AssignedSets &bestPal = assignments[bestPalIndex]; // Add the color to that palette bestPal.assign(std::move(attrs)); @@ -452,23 +449,23 @@ std::tuple, size_t> options.maxOpaqueColors() ); - // Look for a proto-pal minimizing "efficiency" (size / rel_size) + // Look for a color set minimizing "efficiency" (size / rel_size) auto [minEfficiencyIter, maxEfficiencyIter] = std::minmax_element( RANGE(bestPal), - [&bestPal, &protoPalettes](ProtoPalAttrs const &lhs, ProtoPalAttrs const &rhs) { - ProtoPalette const &lhsProtoPal = protoPalettes[lhs.protoPalIndex]; - ProtoPalette const &rhsProtoPal = protoPalettes[rhs.protoPalIndex]; - size_t lhsSize = lhsProtoPal.size(); - size_t rhsSize = rhsProtoPal.size(); - uint32_t lhsRelSize = bestPal.relSizeOf(lhsProtoPal); - uint32_t rhsRelSize = bestPal.relSizeOf(rhsProtoPal); + [&bestPal, &colorSets](ColorSetAttrs const &lhs, ColorSetAttrs const &rhs) { + ColorSet const &lhsColorSet = colorSets[lhs.colorSetIndex]; + ColorSet const &rhsColorSet = colorSets[rhs.colorSetIndex]; + size_t lhsSize = lhsColorSet.size(); + size_t rhsSize = rhsColorSet.size(); + uint32_t lhsRelSize = bestPal.relSizeOf(lhsColorSet); + uint32_t rhsRelSize = bestPal.relSizeOf(rhsColorSet); options.verbosePrint( Options::VERB_TRACE, - " Proto-palettes %zu <=> %zu: Efficiency: %zu / %" PRIu32 " <=> %zu / " + " Color sets %zu <=> %zu: Efficiency: %zu / %" PRIu32 " <=> %zu / " "%" PRIu32 "\n", - lhs.protoPalIndex, - rhs.protoPalIndex, + lhs.colorSetIndex, + rhs.colorSetIndex, lhsSize, lhsRelSize, rhsSize, @@ -482,18 +479,17 @@ std::tuple, size_t> ); // All efficiencies are identical iff min equals max - ProtoPalette const &minProtoPal = protoPalettes[minEfficiencyIter->protoPalIndex]; - ProtoPalette const &maxProtoPal = protoPalettes[maxEfficiencyIter->protoPalIndex]; - size_t minSize = minProtoPal.size(); - size_t maxSize = maxProtoPal.size(); - uint32_t minRelSize = bestPal.relSizeOf(minProtoPal); - uint32_t maxRelSize = bestPal.relSizeOf(maxProtoPal); + ColorSet const &minColorSet = colorSets[minEfficiencyIter->colorSetIndex]; + ColorSet const &maxColorSet = colorSets[maxEfficiencyIter->colorSetIndex]; + size_t minSize = minColorSet.size(); + size_t maxSize = maxColorSet.size(); + uint32_t minRelSize = bestPal.relSizeOf(minColorSet); + uint32_t maxRelSize = bestPal.relSizeOf(maxColorSet); options.verbosePrint( Options::VERB_TRACE, - " Proto-palettes %zu <= %zu: Efficiency: %zu / %" PRIu32 " <= %zu / %" PRIu32 - "\n", - minEfficiencyIter->protoPalIndex, - maxEfficiencyIter->protoPalIndex, + " Color sets %zu <= %zu: Efficiency: %zu / %" PRIu32 " <= %zu / %" PRIu32 "\n", + minEfficiencyIter->colorSetIndex, + maxEfficiencyIter->colorSetIndex, minSize, minRelSize, maxSize, @@ -507,11 +503,11 @@ std::tuple, size_t> break; } - // Remove the proto-pal with minimal efficiency + // Remove the color set with minimal efficiency options.verbosePrint( Options::VERB_TRACE, - " Removing proto-palette %zu\n", - minEfficiencyIter->protoPalIndex + " Removing color set %zu\n", + minEfficiencyIter->colorSetIndex ); queue.emplace(std::move(*minEfficiencyIter)); queue.back().banFrom(bestPalIndex); // Ban it from this palette @@ -521,42 +517,41 @@ std::tuple, size_t> } // Deal with palettes still overloaded, by emptying them - auto const &largestProtoPalFirst = - [&protoPalettes](ProtoPalAttrs const &lhs, ProtoPalAttrs const &rhs) { - return protoPalettes[lhs.protoPalIndex].size() - > protoPalettes[rhs.protoPalIndex].size(); + auto const &largestColorSetFirst = + [&colorSets](ColorSetAttrs const &lhs, ColorSetAttrs const &rhs) { + return colorSets[lhs.colorSetIndex].size() > colorSets[rhs.colorSetIndex].size(); }; - std::vector overloadQueue{}; - for (AssignedProtos &pal : assignments) { + std::vector overloadQueue{}; + for (AssignedSets &pal : assignments) { if (pal.volume() > options.maxOpaqueColors()) { - for (ProtoPalAttrs &attrs : pal) { + for (ColorSetAttrs &attrs : pal) { overloadQueue.emplace( - std::lower_bound(RANGE(overloadQueue), attrs, largestProtoPalFirst), + std::lower_bound(RANGE(overloadQueue), attrs, largestColorSetFirst), std::move(attrs) ); } pal.clear(); } } - // Place back any proto-palettes now in the queue via first-fit - for (ProtoPalAttrs const &attrs : overloadQueue) { - ProtoPalette const &protoPal = protoPalettes[attrs.protoPalIndex]; - auto iter = std::find_if(RANGE(assignments), [&protoPal](AssignedProtos const &pal) { - return pal.canFit(protoPal); + // Place back any color sets now in the queue via first-fit + for (ColorSetAttrs const &attrs : overloadQueue) { + ColorSet const &colorSet = colorSets[attrs.colorSetIndex]; + auto iter = std::find_if(RANGE(assignments), [&colorSet](AssignedSets const &pal) { + return pal.canFit(colorSet); }); if (iter == assignments.end()) { // No such page, create a new one options.verbosePrint( Options::VERB_DEBUG, - "Adding new palette (%zu) for overflowing proto-palette %zu\n", + "Adding new palette (%zu) for overflowing color set %zu\n", assignments.size(), - attrs.protoPalIndex + attrs.colorSetIndex ); - assignments.emplace_back(protoPalettes, std::move(attrs)); + assignments.emplace_back(colorSets, std::move(attrs)); } else { options.verbosePrint( Options::VERB_DEBUG, - "Assigning overflowing proto-palette %zu to palette %zu\n", - attrs.protoPalIndex, + "Assigning overflowing color set %zu to palette %zu\n", + attrs.colorSetIndex, iter - assignments.begin() ); iter->assign(std::move(attrs)); @@ -565,24 +560,24 @@ std::tuple, size_t> // LCOV_EXCL_START if (options.verbosity >= Options::VERB_INTERM) { - verboseOutputAssignments(assignments, protoPalettes); + verboseOutputAssignments(assignments, colorSets); } // LCOV_EXCL_STOP // "Decant" the result - decant(assignments, protoPalettes); + decant(assignments, colorSets); // Note that the result does not contain any empty palettes // LCOV_EXCL_START if (options.verbosity >= Options::VERB_INTERM) { - verboseOutputAssignments(assignments, protoPalettes); + verboseOutputAssignments(assignments, colorSets); } // LCOV_EXCL_STOP - std::vector mappings(protoPalettes.size()); + std::vector mappings(colorSets.size()); for (size_t i = 0; i < assignments.size(); ++i) { - for (ProtoPalAttrs const &attrs : assignments[i]) { - mappings[attrs.protoPalIndex] = i; + for (ColorSetAttrs const &attrs : assignments[i]) { + mappings[attrs.colorSetIndex] = i; } } return {mappings, assignments.size()}; diff --git a/src/gfx/process.cpp b/src/gfx/process.cpp index 00ea9031..7d2bd539 100644 --- a/src/gfx/process.cpp +++ b/src/gfx/process.cpp @@ -20,11 +20,11 @@ #include "helpers.hpp" #include "itertools.hpp" +#include "gfx/color_set.hpp" #include "gfx/main.hpp" #include "gfx/pal_packing.hpp" #include "gfx/pal_sorting.hpp" #include "gfx/png.hpp" -#include "gfx/proto_palette.hpp" #include "gfx/warning.hpp" static bool isBgColorTransparent() { @@ -289,11 +289,11 @@ public: }; struct AttrmapEntry { - // This field can either be a proto-palette ID, or `transparent` to indicate that the + // This field can either be a color set ID, or `transparent` to indicate that the // corresponding tile is fully transparent. If you are looking to get the palette ID for this // attrmap entry while correctly handling the above, use `getPalID`. - size_t protoPaletteID; // Only this field is used when outputting "unoptimized" data - uint8_t tileID; // This is the ID as it will be output to the tilemap + size_t colorSetID; // Only this field is used when outputting "unoptimized" data + uint8_t tileID; // This is the ID as it will be output to the tilemap bool bank; bool yFlip; bool xFlip; @@ -301,9 +301,9 @@ struct AttrmapEntry { static constexpr size_t transparent = static_cast(-1); static constexpr size_t background = static_cast(-2); - bool isBackgroundTile() const { return protoPaletteID == background; } + bool isBackgroundTile() const { return colorSetID == background; } size_t getPalID(std::vector const &mappings) const { - return mappings[isBackgroundTile() || protoPaletteID == transparent ? 0 : protoPaletteID]; + return mappings[isBackgroundTile() || colorSetID == transparent ? 0 : colorSetID]; } }; @@ -330,18 +330,15 @@ static void generatePalSpec(Image const &image) { } static std::tuple, std::vector> - generatePalettes(std::vector const &protoPalettes, Image const &image) { + generatePalettes(std::vector const &colorSets, Image const &image) { // Run a "pagination" problem solver - auto [mappings, nbPalettes] = overloadAndRemove(protoPalettes); - assume(mappings.size() == protoPalettes.size()); + auto [mappings, nbPalettes] = overloadAndRemove(colorSets); + assume(mappings.size() == colorSets.size()); // LCOV_EXCL_START if (options.verbosity >= Options::VERB_INTERM) { fprintf( - stderr, - "Proto-palette mappings: (%zu palette%s)\n", - nbPalettes, - nbPalettes != 1 ? "s" : "" + stderr, "Color set mappings: (%zu palette%s)\n", nbPalettes, nbPalettes != 1 ? "s" : "" ); for (size_t i = 0; i < mappings.size(); ++i) { fprintf(stderr, "%zu -> %zu\n", i, mappings[i]); @@ -358,9 +355,9 @@ static std::tuple, std::vector> } } // Generate the actual palettes from the mappings - for (size_t protoPalID = 0; protoPalID < mappings.size(); ++protoPalID) { - Palette &pal = palettes[mappings[protoPalID]]; - for (uint16_t color : protoPalettes[protoPalID]) { + for (size_t colorSetID = 0; colorSetID < mappings.size(); ++colorSetID) { + Palette &pal = palettes[mappings[colorSetID]]; + for (uint16_t color : colorSets[colorSetID]) { pal.addColor(color); } } @@ -383,7 +380,7 @@ static std::tuple, std::vector> } static std::tuple, std::vector> - makePalsAsSpecified(std::vector const &protoPalettes) { + makePalsAsSpecified(std::vector const &colorSets) { // Convert the palette spec to actual palettes std::vector palettes(options.palSpec.size()); for (auto [spec, pal] : zip(options.palSpec, palettes)) { @@ -404,22 +401,22 @@ static std::tuple, std::vector> return &buf[literal_strlen(", ")]; }; - // Iterate through proto-palettes, and try mapping them to the specified palettes - std::vector mappings(protoPalettes.size()); + // Iterate through color sets, and try mapping them to the specified palettes + std::vector mappings(colorSets.size()); bool bad = false; - for (size_t i = 0; i < protoPalettes.size(); ++i) { - ProtoPalette const &protoPal = protoPalettes[i]; + for (size_t i = 0; i < colorSets.size(); ++i) { + ColorSet const &colorSet = colorSets[i]; // Find the palette... - auto iter = std::find_if(RANGE(palettes), [&protoPal](Palette const &pal) { - // ...which contains all colors in this proto-pal - return std::all_of(RANGE(protoPal), [&pal](uint16_t color) { + auto iter = std::find_if(RANGE(palettes), [&colorSet](Palette const &pal) { + // ...which contains all colors in this color set + return std::all_of(RANGE(colorSet), [&pal](uint16_t color) { return std::find(RANGE(pal), color) != pal.end(); }); }); if (iter == palettes.end()) { - assume(!protoPal.empty()); - error("Failed to fit tile colors [%s] in specified palettes", listColors(protoPal)); + assume(!colorSet.empty()); + error("Failed to fit tile colors [%s] in specified palettes", listColors(colorSet)); bad = true; } mappings[i] = iter - palettes.begin(); // Bogus value, but whatever @@ -808,8 +805,7 @@ static UniqueTiles dedupTiles( attr.bank = 0; attr.tileID = 0; } else { - auto [tileID, matchType] = - tiles.addTile({tile, palettes[mappings[attr.protoPaletteID]]}); + auto [tileID, matchType] = tiles.addTile({tile, palettes[mappings[attr.colorSetID]]}); if (inputWithoutOutput && matchType == TileData::NOPE) { error( @@ -903,9 +899,9 @@ static void void processPalettes() { options.verbosePrint(Options::VERB_CFG, "Using libpng %s\n", png_get_libpng_ver(nullptr)); - std::vector protoPalettes; + std::vector colorSets; std::vector palettes; - std::tie(std::ignore, palettes) = makePalsAsSpecified(protoPalettes); + std::tie(std::ignore, palettes) = makePalsAsSpecified(colorSets); outputPalettes(palettes); } @@ -941,11 +937,11 @@ void process() { } } - // Now, iterate through the tiles, generating proto-palettes as we go + // Now, iterate through the tiles, generating color sets as we go // We do this unconditionally because this performs the image validation (which we want to // perform even if no output is requested), and because it's necessary to generate any // output (with the exception of an un-duplicated tilemap, but that's an acceptable loss.) - std::vector protoPalettes; + std::vector colorSets; std::vector attrmap{}; for (auto tile : image.visitAsTiles()) { @@ -973,22 +969,22 @@ void process() { } if (tileColors.empty()) { - // "Empty" proto-palettes screw with the packing process, so discard those + // "Empty" color sets screw with the packing process, so discard those assume(!isBgColorTransparent()); - attrs.protoPaletteID = AttrmapEntry::transparent; + attrs.colorSetID = AttrmapEntry::transparent; continue; } - ProtoPalette protoPalette; + ColorSet colorSet; for (uint16_t color : tileColors) { - protoPalette.add(color); + colorSet.add(color); } if (options.bgColor.has_value() && std::find(RANGE(tileColors), options.bgColor->cgbColor()) != tileColors.end()) { if (tileColors.size() == 1) { // The tile contains just the background color, skip it. - attrs.protoPaletteID = AttrmapEntry::background; + attrs.colorSetID = AttrmapEntry::background; continue; } fatal( @@ -999,47 +995,47 @@ void process() { ); } - // Insert the proto-palette, making sure to avoid overlaps - for (size_t n = 0; n < protoPalettes.size(); ++n) { - switch (protoPalette.compare(protoPalettes[n])) { - case ProtoPalette::WE_BIGGER: - protoPalettes[n] = protoPalette; // Override them - // Remove any other proto-palettes that we encompass + // Insert the color set, making sure to avoid overlaps + for (size_t n = 0; n < colorSets.size(); ++n) { + switch (colorSet.compare(colorSets[n])) { + case ColorSet::WE_BIGGER: + colorSets[n] = colorSet; // Override them + // Remove any other color sets that we encompass // (Example [(0, 1), (0, 2)], inserting (0, 1, 2)) [[fallthrough]]; - case ProtoPalette::THEY_BIGGER: + case ColorSet::THEY_BIGGER: // Do nothing, they already contain us - attrs.protoPaletteID = n; + attrs.colorSetID = n; goto continue_visiting_tiles; // Can't `continue` from within a nested loop - case ProtoPalette::NEITHER: + case ColorSet::NEITHER: break; // Keep going } } - attrs.protoPaletteID = protoPalettes.size(); - if (protoPalettes.size() == AttrmapEntry::background) { // Check for overflow + attrs.colorSetID = colorSets.size(); + if (colorSets.size() == AttrmapEntry::background) { // Check for overflow fatal( - "Reached %zu proto-palettes... sorry, this image is too much for me to handle :(", + "Reached %zu color sets... sorry, this image is too much for me to handle :(", AttrmapEntry::transparent ); } - protoPalettes.push_back(protoPalette); + colorSets.push_back(colorSet); continue_visiting_tiles:; } options.verbosePrint( Options::VERB_INTERM, - "Image contains %zu proto-palette%s\n", - protoPalettes.size(), - protoPalettes.size() != 1 ? "s" : "" + "Image contains %zu color set%s\n", + colorSets.size(), + colorSets.size() != 1 ? "s" : "" ); // LCOV_EXCL_START if (options.verbosity >= Options::VERB_INTERM) { - for (ProtoPalette const &protoPal : protoPalettes) { + for (ColorSet const &colorSet : colorSets) { fputs("[ ", stderr); - for (uint16_t color : protoPal) { + for (uint16_t color : colorSet) { fprintf(stderr, "$%04x, ", color); } fputs("]\n", stderr); @@ -1052,8 +1048,8 @@ continue_visiting_tiles:; } auto [mappings, palettes] = options.palSpecType == Options::NO_SPEC || options.palSpecType == Options::DMG - ? generatePalettes(protoPalettes, image) - : makePalsAsSpecified(protoPalettes); + ? generatePalettes(colorSets, image) + : makePalsAsSpecified(colorSets); outputPalettes(palettes); // If deduplication is not happening, we just need to output the tile data and/or maps as-is