Rename proto-palettes to color sets (copied from rsgbds)

This commit is contained in:
Rangi42
2025-07-23 21:03:50 -04:00
parent 18e35053fa
commit 1849a35e61
7 changed files with 218 additions and 228 deletions

View File

@@ -101,13 +101,13 @@ rgbfix_obj := \
rgbgfx_obj := \ rgbgfx_obj := \
${common_obj} \ ${common_obj} \
src/gfx/color_set.o \
src/gfx/main.o \ src/gfx/main.o \
src/gfx/pal_packing.o \ src/gfx/pal_packing.o \
src/gfx/pal_sorting.o \ src/gfx/pal_sorting.o \
src/gfx/pal_spec.o \ src/gfx/pal_spec.o \
src/gfx/png.o \ src/gfx/png.o \
src/gfx/process.o \ src/gfx/process.o \
src/gfx/proto_palette.o \
src/gfx/reverse.o \ src/gfx/reverse.o \
src/gfx/rgba.o \ src/gfx/rgba.o \
src/gfx/warning.o src/gfx/warning.o
@@ -145,6 +145,8 @@ src/link/script.hpp: src/link/script.cpp
$Qtouch $@ $Qtouch $@
# Only RGBGFX uses libpng (POSIX make doesn't support pattern rules to cover all these) # 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 src/gfx/main.o: src/gfx/main.cpp
$Q${CXX} ${REALCXXFLAGS} ${PNGCFLAGS} -c -o $@ $< $Q${CXX} ${REALCXXFLAGS} ${PNGCFLAGS} -c -o $@ $<
src/gfx/pal_packing.o: src/gfx/pal_packing.cpp 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 $@ $< $Q${CXX} ${REALCXXFLAGS} ${PNGCFLAGS} -c -o $@ $<
src/gfx/process.o: src/gfx/process.cpp src/gfx/process.o: src/gfx/process.cpp
$Q${CXX} ${REALCXXFLAGS} ${PNGCFLAGS} -c -o $@ $< $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 src/gfx/reverse.o: src/gfx/reverse.cpp
$Q${CXX} ${REALCXXFLAGS} ${PNGCFLAGS} -c -o $@ $< $Q${CXX} ${REALCXXFLAGS} ${PNGCFLAGS} -c -o $@ $<
src/gfx/rgba.o: src/gfx/rgba.cpp src/gfx/rgba.o: src/gfx/rgba.cpp

View File

@@ -1,13 +1,13 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
#ifndef RGBDS_GFX_PROTO_PALETTE_HPP #ifndef RGBDS_GFX_COLOR_SET_HPP
#define RGBDS_GFX_PROTO_PALETTE_HPP #define RGBDS_GFX_COLOR_SET_HPP
#include <array> #include <array>
#include <stddef.h> #include <stddef.h>
#include <stdint.h> #include <stdint.h>
class ProtoPalette { class ColorSet {
public: public:
static constexpr size_t capacity = 4; static constexpr size_t capacity = 4;
@@ -26,7 +26,7 @@ public:
WE_BIGGER, WE_BIGGER,
THEY_BIGGER = -1, THEY_BIGGER = -1,
}; };
ComparisonResult compare(ProtoPalette const &other) const; ComparisonResult compare(ColorSet const &other) const;
size_t size() const; size_t size() const;
bool empty() const; bool empty() const;
@@ -35,4 +35,4 @@ public:
decltype(_colorIndices)::const_iterator end() const; decltype(_colorIndices)::const_iterator end() const;
}; };
#endif // RGBDS_GFX_PROTO_PALETTE_HPP #endif // RGBDS_GFX_COLOR_SET_HPP

View File

@@ -8,10 +8,9 @@
#include <vector> #include <vector>
struct Palette; struct Palette;
class ProtoPalette; class ColorSet;
// Returns which palette each proto-palette maps to, and how many palettes are necessary // Returns which palette each color set maps to, and how many palettes are necessary
std::tuple<std::vector<size_t>, size_t> std::tuple<std::vector<size_t>, size_t> overloadAndRemove(std::vector<ColorSet> const &colorSets);
overloadAndRemove(std::vector<ProtoPalette> const &protoPalettes);
#endif // RGBDS_GFX_PAL_PACKING_HPP #endif // RGBDS_GFX_PAL_PACKING_HPP

View File

@@ -75,13 +75,13 @@ set(rgbfix_src
) )
set(rgbgfx_src set(rgbgfx_src
"gfx/color_set.cpp"
"gfx/main.cpp" "gfx/main.cpp"
"gfx/pal_packing.cpp" "gfx/pal_packing.cpp"
"gfx/pal_sorting.cpp" "gfx/pal_sorting.cpp"
"gfx/pal_spec.cpp" "gfx/pal_spec.cpp"
"gfx/png.cpp" "gfx/png.cpp"
"gfx/process.cpp" "gfx/process.cpp"
"gfx/proto_palette.cpp"
"gfx/reverse.cpp" "gfx/reverse.cpp"
"gfx/rgba.cpp" "gfx/rgba.cpp"
"gfx/warning.cpp" "gfx/warning.cpp"

View File

@@ -1,12 +1,12 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
#include "gfx/proto_palette.hpp" #include "gfx/color_set.hpp"
#include <algorithm> #include <algorithm>
#include "helpers.hpp" #include "helpers.hpp"
void ProtoPalette::add(uint16_t color) { void ColorSet::add(uint16_t color) {
size_t i = 0; size_t i = 0;
// Seek the first slot greater than the new color // Seek the first slot greater than the new color
@@ -37,7 +37,7 @@ void ProtoPalette::add(uint16_t color) {
_colorIndices[i] = 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 // This works because the sets are sorted numerically
assume(std::is_sorted(RANGE(_colorIndices))); assume(std::is_sorted(RANGE(_colorIndices)));
assume(std::is_sorted(RANGE(other._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); return theyBigger ? THEY_BIGGER : (weBigger ? WE_BIGGER : NEITHER);
} }
size_t ProtoPalette::size() const { size_t ColorSet::size() const {
return std::distance(RANGE(*this)); return std::distance(RANGE(*this));
} }
bool ProtoPalette::empty() const { bool ColorSet::empty() const {
return _colorIndices[0] == UINT16_MAX; return _colorIndices[0] == UINT16_MAX;
} }
auto ProtoPalette::begin() const -> decltype(_colorIndices)::const_iterator { auto ColorSet::begin() const -> decltype(_colorIndices)::const_iterator {
return _colorIndices.begin(); 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); return std::find(RANGE(_colorIndices), UINT16_MAX);
} }

View File

@@ -15,26 +15,28 @@
#include "helpers.hpp" #include "helpers.hpp"
#include "gfx/color_set.hpp"
#include "gfx/main.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: // 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" // "Algorithms for the Pagination Problem, a Bin Packing with Overlapping Items"
// Their formulation of the problem consists in packing "tiles" into "pages". // Their formulation of the problem consists in packing "tiles" into "pages".
// Here is a correspondence table for our application of it: // Here is a correspondence table for our application of it:
// Paper | RGBGFX //
// ------+------- // Paper | RGBGFX
// Tile | Proto-palette // -------+----------
// Page | Palette // Symbol | Color
// Tile | Color set
// Page | Palette
// A reference to a proto-palette, and attached attributes for sorting purposes // A reference to a color set, and attached attributes for sorting purposes
struct ProtoPalAttrs { struct ColorSetAttrs {
size_t protoPalIndex; size_t colorSetIndex;
// Pages from which we are banned (to prevent infinite loops) // Pages from which we are banned (to prevent infinite loops)
// This is dynamic because we wish not to hard-cap the amount of palettes // This is dynamic because we wish not to hard-cap the amount of palettes
std::vector<bool> bannedPages; std::vector<bool> bannedPages;
explicit ProtoPalAttrs(size_t index) : protoPalIndex(index) {} explicit ColorSetAttrs(size_t index) : colorSetIndex(index) {}
bool isBannedFrom(size_t index) const { bool isBannedFrom(size_t index) const {
return index < bannedPages.size() && bannedPages[index]; 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 // 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 // We leave room for emptied slots to avoid copying the structs around on removal
std::vector<std::optional<ProtoPalAttrs>> _assigned; std::vector<std::optional<ColorSetAttrs>> _assigned;
// For resolving proto-palette indices // For resolving color set indices
std::vector<ProtoPalette> const *_protoPals; std::vector<ColorSet> const *_colorSets;
public: public:
template<typename... Ts> template<typename... Ts>
AssignedProtos(std::vector<ProtoPalette> const &protoPals, Ts &&...elems) AssignedSets(std::vector<ColorSet> const &colorSets, Ts &&...elems)
: _assigned{std::forward<Ts>(elems)...}, _protoPals{&protoPals} {} : _assigned{std::forward<Ts>(elems)...}, _colorSets{&colorSets} {}
private: private:
template<typename Inner, template<typename> typename Constness> template<typename Inner, template<typename> typename Constness>
class Iter { class Iter {
public: public:
friend class AssignedProtos; friend class AssignedSets;
// For `iterator_traits` // For `iterator_traits`
using difference_type = typename std::iterator_traits<Inner>::difference_type; using difference_type = typename std::iterator_traits<Inner>::difference_type;
using value_type = ProtoPalAttrs; using value_type = ColorSetAttrs;
using pointer = Constness<value_type> *; using pointer = Constness<value_type> *;
using reference = Constness<value_type> &; using reference = Constness<value_type> &;
using iterator_category = std::forward_iterator_tag; using iterator_category = std::forward_iterator_tag;
@@ -122,12 +124,12 @@ public:
} }
const_iterator end() const { return const_iterator{&_assigned, _assigned.end()}; } const_iterator end() const { return const_iterator{&_assigned, _assigned.end()}; }
// Assigns a new ProtoPalAttrs in a free slot, assuming there is one // Assigns a new ColorSetAttrs in a free slot, assuming there is one
// Args are passed to the `ProtoPalAttrs`'s constructor // Args are passed to the `ColorSetAttrs`'s constructor
template<typename... Ts> template<typename... Ts>
void assign(Ts &&...args) { void assign(Ts &&...args) {
auto freeSlot = auto freeSlot =
std::find_if_not(RANGE(_assigned), [](std::optional<ProtoPalAttrs> const &slot) { std::find_if_not(RANGE(_assigned), [](std::optional<ColorSetAttrs> const &slot) {
return slot.has_value(); return slot.has_value();
}); });
@@ -145,11 +147,11 @@ public:
bool empty() const { bool empty() const {
return std::find_if( return std::find_if(
RANGE(_assigned), RANGE(_assigned),
[](std::optional<ProtoPalAttrs> const &slot) { return slot.has_value(); } [](std::optional<ColorSetAttrs> const &slot) { return slot.has_value(); }
) )
== _assigned.end(); == _assigned.end();
} }
size_t nbProtoPals() const { return std::distance(RANGE(*this)); } size_t nbColorSets() const { return std::distance(RANGE(*this)); }
private: private:
template<typename Iter> template<typename Iter>
@@ -157,11 +159,11 @@ private:
std::unordered_set<uint16_t> &colors, std::unordered_set<uint16_t> &colors,
Iter iter, Iter iter,
Iter const &end, Iter const &end,
std::vector<ProtoPalette> const &protoPals std::vector<ColorSet> const &colorSets
) { ) {
for (; iter != end; ++iter) { for (; iter != end; ++iter) {
ProtoPalette const &protoPal = protoPals[iter->protoPalIndex]; ColorSet const &colorSet = colorSets[iter->colorSetIndex];
colors.insert(RANGE(protoPal)); colors.insert(RANGE(colorSet));
} }
} }
// This function should stay private because it returns a reference to a unique object // This function should stay private because it returns a reference to a unique object
@@ -171,20 +173,20 @@ private:
static std::unordered_set<uint16_t> colors; static std::unordered_set<uint16_t> colors;
colors.clear(); colors.clear();
addUniqueColors(colors, RANGE(*this), *_protoPals); addUniqueColors(colors, RANGE(*this), *_colorSets);
return colors; return colors;
} }
public: public:
// Returns the number of distinct colors // Returns the number of distinct colors
size_t volume() const { return uniqueColors().size(); } size_t volume() const { return uniqueColors().size(); }
bool canFit(ProtoPalette const &protoPal) const { bool canFit(ColorSet const &colorSet) const {
std::unordered_set<uint16_t> &colors = uniqueColors(); std::unordered_set<uint16_t> &colors = uniqueColors();
colors.insert(RANGE(protoPal)); colors.insert(RANGE(colorSet));
return colors.size() <= options.maxOpaqueColors(); return colors.size() <= options.maxOpaqueColors();
} }
// The `relSizeOf` method below should compute the sum, for each color in `protoPal`, of // The `relSizeOf` method below should compute the sum, for each color in `colorSet`, of
// the reciprocal of the "multiplicity" of the color across "our" proto-palettes. // the reciprocal of the "multiplicity" of the color across "our" color sets.
// However, literally computing the reciprocals would involve floating-point division, which // However, literally computing the reciprocals would involve floating-point division, which
// leads to imprecision and even platform-specific differences. // leads to imprecision and even platform-specific differences.
// We avoid this by multiplying the reciprocals by a factor such that division always produces // We avoid this by multiplying the reciprocals by a factor such that division always produces
@@ -199,22 +201,22 @@ public:
return factor; return factor;
}(); }();
// Computes the "relative size" of a proto-palette on this palette; // Computes the "relative size" of a color set on this palette;
// it's a measure of how much this proto-palette would "cost" to introduce. // it's a measure of how much this color set would "cost" to introduce.
uint32_t relSizeOf(ProtoPalette const &protoPal) const { uint32_t relSizeOf(ColorSet const &colorSet) const {
// NOTE: this function must not call `uniqueColors`, or one of its callers will break! // NOTE: this function must not call `uniqueColors`, or one of its callers will break!
uint32_t relSize = 0; uint32_t relSize = 0;
for (uint16_t color : protoPal) { for (uint16_t color : colorSet) {
// How many of our proto-palettes does this color also belong to? // How many of our color sets does this color also belong to?
uint32_t multiplicity = uint32_t multiplicity =
std::count_if(RANGE(*this), [this, &color](ProtoPalAttrs const &attrs) { std::count_if(RANGE(*this), [this, &color](ColorSetAttrs const &attrs) {
ProtoPalette const &pal = (*_protoPals)[attrs.protoPalIndex]; ColorSet const &pal = (*_colorSets)[attrs.colorSetIndex];
return std::find(RANGE(pal), color) != pal.end(); return std::find(RANGE(pal), color) != pal.end();
}); });
// We increase the denominator by 1 here; the reference code does this, // 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 // 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. // to preserve the paper's reasoning.
// //
// The scale factor should ensure integer divisions only. // The scale factor should ensure integer divisions only.
@@ -224,12 +226,12 @@ public:
return relSize; 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<typename Iter> template<typename Iter>
size_t combinedVolume(Iter &&begin, Iter const &end, std::vector<ProtoPalette> const &protoPals) size_t combinedVolume(Iter &&begin, Iter const &end, std::vector<ColorSet> const &colorSets)
const { const {
std::unordered_set<uint16_t> &colors = uniqueColors(); std::unordered_set<uint16_t> &colors = uniqueColors();
addUniqueColors(colors, std::forward<Iter>(begin), end, protoPals); addUniqueColors(colors, std::forward<Iter>(begin), end, colorSets);
return colors.size(); return colors.size();
} }
// Computes the "relative size" of a set of colors on this palette // Computes the "relative size" of a set of colors on this palette
@@ -242,13 +244,13 @@ public:
}; };
static void verboseOutputAssignments( static void verboseOutputAssignments(
std::vector<AssignedProtos> const &assignments, std::vector<ProtoPalette> const &protoPalettes std::vector<AssignedSets> const &assignments, std::vector<ColorSet> const &colorSets
) { ) {
for (AssignedProtos const &assignment : assignments) { for (AssignedSets const &assignment : assignments) {
fputs("{ ", stderr); fputs("{ ", stderr);
for (ProtoPalAttrs const &attrs : assignment) { for (ColorSetAttrs const &attrs : assignment) {
fprintf(stderr, "[%zu] ", attrs.protoPalIndex); fprintf(stderr, "[%zu] ", attrs.colorSetIndex);
for (uint16_t colorIndex : protoPalettes[attrs.protoPalIndex]) { for (uint16_t colorIndex : colorSets[attrs.colorSetIndex]) {
fprintf(stderr, "%04" PRIx16 ", ", colorIndex); fprintf(stderr, "%04" PRIx16 ", ", colorIndex);
} }
} }
@@ -256,9 +258,7 @@ static void verboseOutputAssignments(
} }
} }
static void decant( static void decant(std::vector<AssignedSets> &assignments, std::vector<ColorSet> const &colorSets) {
std::vector<AssignedProtos> &assignments, std::vector<ProtoPalette> const &protoPalettes
) {
// "Decanting" is the process of moving all *things* that can fit in a lower index there // "Decanting" is the process of moving all *things* that can fit in a lower index there
auto decantOn = [&assignments](auto const &tryDecanting) { auto decantOn = [&assignments](auto const &tryDecanting) {
// No need to attempt decanting on palette #0, as there are no palettes to decant to // 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]); 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 // 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 // NB: order is intentionally preserved so as not to alter the "decantation"'s
// properties // properties
@@ -287,11 +287,11 @@ static void decant(
); );
// Decant on palettes // Decant on palettes
decantOn([&protoPalettes](AssignedProtos &to, AssignedProtos &from) { decantOn([&colorSets](AssignedSets &to, AssignedSets &from) {
// If the entire palettes can be merged, move all of `from`'s proto-palettes // If the entire palettes can be merged, move all of `from`'s color sets
if (to.combinedVolume(RANGE(from), protoPalettes) <= options.maxOpaqueColors()) { if (to.combinedVolume(RANGE(from), colorSets) <= options.maxOpaqueColors()) {
for (ProtoPalAttrs &attrs : from) { for (ColorSetAttrs &attrs : from) {
to.assign(attrs.protoPalIndex); to.assign(attrs.colorSetIndex);
} }
from.clear(); from.clear();
} }
@@ -300,13 +300,13 @@ static void decant(
Options::VERB_DEBUG, "%zu palettes after decanting on palettes\n", assignments.size() Options::VERB_DEBUG, "%zu palettes after decanting on palettes\n", assignments.size()
); );
// Decant on "components" (= proto-pals sharing colors) // Decant on "components" (= color sets sharing colors)
decantOn([&protoPalettes](AssignedProtos &to, AssignedProtos &from) { decantOn([&colorSets](AssignedSets &to, AssignedSets &from) {
// We need to iterate on all the "components", which are groups of proto-palettes sharing at // We need to iterate on all the "components", which are groups of color sets sharing at
// least one color with another proto-palettes in the group. // least one color with another color sets in the group.
// We do this by adding the first available proto-palette, and then looking for palettes // We do this by adding the first available color set, and then looking for palettes with
// with common colors. (As an optimization, we know we can skip palettes already scanned.) // common colors. (As an optimization, we know we can skip palettes already scanned.)
std::vector<bool> processed(from.nbProtoPals(), false); std::vector<bool> processed(from.nbColorSets(), false);
std::unordered_set<uint16_t> colors; std::unordered_set<uint16_t> colors;
std::vector<size_t> members; std::vector<size_t> members;
while (true) { while (true) {
@@ -322,19 +322,19 @@ static void decant(
members.clear(); members.clear();
assume(members.empty()); // Compiler optimization hint assume(members.empty()); // Compiler optimization hint
do { do {
ProtoPalette const &protoPal = protoPalettes[attrs->protoPalIndex]; ColorSet const &colorSet = colorSets[attrs->colorSetIndex];
// If this is the first proto-pal, or if at least one color matches, add it // If this is the first color set, or if at least one color matches, add it
if (members.empty() if (members.empty()
|| std::find_first_of(RANGE(colors), RANGE(protoPal)) != colors.end()) { || std::find_first_of(RANGE(colors), RANGE(colorSet)) != colors.end()) {
colors.insert(RANGE(protoPal)); colors.insert(RANGE(colorSet));
members.push_back(iter - processed.begin()); members.push_back(iter - processed.begin());
*iter = true; // Mark that proto-pal as processed *iter = true; // Mark that color set as processed
} }
++attrs; ++attrs;
} while (++iter != processed.end()); } while (++iter != processed.end());
if (to.combinedVolume(RANGE(colors)) <= options.maxOpaqueColors()) { 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(); auto member = from.begin();
size_t curIndex = 0; size_t curIndex = 0;
for (size_t index : members) { for (size_t index : members) {
@@ -350,56 +350,53 @@ static void decant(
Options::VERB_DEBUG, "%zu palettes after decanting on \"components\"\n", assignments.size() Options::VERB_DEBUG, "%zu palettes after decanting on \"components\"\n", assignments.size()
); );
// Decant on individual proto-palettes // Decant on individual color sets
decantOn([&protoPalettes](AssignedProtos &to, AssignedProtos &from) { decantOn([&colorSets](AssignedSets &to, AssignedSets &from) {
for (auto iter = from.begin(); iter != from.end(); ++iter) { 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)); to.assign(std::move(*iter));
from.remove(iter); from.remove(iter);
} }
} }
}); });
options.verbosePrint( 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<std::vector<size_t>, size_t> std::tuple<std::vector<size_t>, size_t> overloadAndRemove(std::vector<ColorSet> const &colorSets) {
overloadAndRemove(std::vector<ProtoPalette> const &protoPalettes) {
options.verbosePrint( options.verbosePrint(
Options::VERB_LOG_ACT, "Paginating palettes using \"overload-and-remove\" strategy...\n" 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 // Sort the color sets by size, which improves the packing algorithm's efficiency
auto const indexOfLargestProtoPalFirst = [&protoPalettes](size_t left, size_t right) { auto const indexOfLargestColorSetFirst = [&colorSets](size_t left, size_t right) {
ProtoPalette const &lhs = protoPalettes[left]; ColorSet const &lhs = colorSets[left];
ProtoPalette const &rhs = protoPalettes[right]; ColorSet const &rhs = colorSets[right];
return lhs.size() > rhs.size(); // We want the proto-pals to be sorted *largest first*! return lhs.size() > rhs.size(); // We want the color sets to be sorted *largest first*!
}; };
std::vector<size_t> sortedProtoPalIDs; std::vector<size_t> sortedColorSetIDs;
sortedProtoPalIDs.reserve(protoPalettes.size()); sortedColorSetIDs.reserve(colorSets.size());
for (size_t i = 0; i < protoPalettes.size(); ++i) { for (size_t i = 0; i < colorSets.size(); ++i) {
sortedProtoPalIDs.insert( sortedColorSetIDs.insert(
std::lower_bound(RANGE(sortedProtoPalIDs), i, indexOfLargestProtoPalFirst), i std::lower_bound(RANGE(sortedColorSetIDs), i, indexOfLargestColorSetFirst), i
); );
} }
// Begin with all proto-palettes queued up for insertion // Begin with all color sets queued up for insertion
std::queue<ProtoPalAttrs> queue(std::deque<ProtoPalAttrs>(RANGE(sortedProtoPalIDs))); std::queue<ColorSetAttrs> queue(std::deque<ColorSetAttrs>(RANGE(sortedColorSetIDs)));
// Begin with no pages // Begin with no pages
std::vector<AssignedProtos> assignments{}; std::vector<AssignedSets> assignments{};
for (; !queue.empty(); queue.pop()) { for (; !queue.empty(); queue.pop()) {
ProtoPalAttrs const &attrs = queue.front(); // Valid until the `queue.pop()` ColorSetAttrs const &attrs = queue.front(); // Valid until the `queue.pop()`
options.verbosePrint( options.verbosePrint(Options::VERB_TRACE, "Handling color set %zu\n", attrs.colorSetIndex);
Options::VERB_TRACE, "Handling proto-palette %zu\n", attrs.protoPalIndex
);
ProtoPalette const &protoPal = protoPalettes[attrs.protoPalIndex]; ColorSet const &colorSet = colorSets[attrs.colorSetIndex];
size_t bestPalIndex = assignments.size(); 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 // 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) { for (size_t i = 0; i < assignments.size(); ++i) {
// Skip the page if this one is banned from it // Skip the page if this one is banned from it
@@ -407,14 +404,14 @@ std::tuple<std::vector<size_t>, size_t>
continue; continue;
} }
uint32_t relSize = assignments[i].relSizeOf(protoPal); uint32_t relSize = assignments[i].relSizeOf(colorSet);
options.verbosePrint( options.verbosePrint(
Options::VERB_TRACE, Options::VERB_TRACE,
" Relative size to palette %zu (of %zu): %" PRIu32 " (size = %zu)\n", " Relative size to palette %zu (of %zu): %" PRIu32 " (size = %zu)\n",
i, i,
assignments.size(), assignments.size(),
relSize, relSize,
protoPal.size() colorSet.size()
); );
if (relSize < bestRelSize) { if (relSize < bestRelSize) {
bestPalIndex = i; bestPalIndex = i;
@@ -426,19 +423,19 @@ std::tuple<std::vector<size_t>, size_t>
// Found nowhere to put it, create a new page containing just that one // Found nowhere to put it, create a new page containing just that one
options.verbosePrint( options.verbosePrint(
Options::VERB_TRACE, Options::VERB_TRACE,
"Assigning proto-palette %zu to new palette %zu\n", "Assigning color set %zu to new palette %zu\n",
attrs.protoPalIndex, attrs.colorSetIndex,
bestPalIndex bestPalIndex
); );
assignments.emplace_back(protoPalettes, std::move(attrs)); assignments.emplace_back(colorSets, std::move(attrs));
} else { } else {
options.verbosePrint( options.verbosePrint(
Options::VERB_TRACE, Options::VERB_TRACE,
"Assigning proto-palette %zu to palette %zu\n", "Assigning color set %zu to palette %zu\n",
attrs.protoPalIndex, attrs.colorSetIndex,
bestPalIndex bestPalIndex
); );
AssignedProtos &bestPal = assignments[bestPalIndex]; AssignedSets &bestPal = assignments[bestPalIndex];
// Add the color to that palette // Add the color to that palette
bestPal.assign(std::move(attrs)); bestPal.assign(std::move(attrs));
@@ -452,23 +449,23 @@ std::tuple<std::vector<size_t>, size_t>
options.maxOpaqueColors() 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( auto [minEfficiencyIter, maxEfficiencyIter] = std::minmax_element(
RANGE(bestPal), RANGE(bestPal),
[&bestPal, &protoPalettes](ProtoPalAttrs const &lhs, ProtoPalAttrs const &rhs) { [&bestPal, &colorSets](ColorSetAttrs const &lhs, ColorSetAttrs const &rhs) {
ProtoPalette const &lhsProtoPal = protoPalettes[lhs.protoPalIndex]; ColorSet const &lhsColorSet = colorSets[lhs.colorSetIndex];
ProtoPalette const &rhsProtoPal = protoPalettes[rhs.protoPalIndex]; ColorSet const &rhsColorSet = colorSets[rhs.colorSetIndex];
size_t lhsSize = lhsProtoPal.size(); size_t lhsSize = lhsColorSet.size();
size_t rhsSize = rhsProtoPal.size(); size_t rhsSize = rhsColorSet.size();
uint32_t lhsRelSize = bestPal.relSizeOf(lhsProtoPal); uint32_t lhsRelSize = bestPal.relSizeOf(lhsColorSet);
uint32_t rhsRelSize = bestPal.relSizeOf(rhsProtoPal); uint32_t rhsRelSize = bestPal.relSizeOf(rhsColorSet);
options.verbosePrint( options.verbosePrint(
Options::VERB_TRACE, Options::VERB_TRACE,
" Proto-palettes %zu <=> %zu: Efficiency: %zu / %" PRIu32 " <=> %zu / " " Color sets %zu <=> %zu: Efficiency: %zu / %" PRIu32 " <=> %zu / "
"%" PRIu32 "\n", "%" PRIu32 "\n",
lhs.protoPalIndex, lhs.colorSetIndex,
rhs.protoPalIndex, rhs.colorSetIndex,
lhsSize, lhsSize,
lhsRelSize, lhsRelSize,
rhsSize, rhsSize,
@@ -482,18 +479,17 @@ std::tuple<std::vector<size_t>, size_t>
); );
// All efficiencies are identical iff min equals max // All efficiencies are identical iff min equals max
ProtoPalette const &minProtoPal = protoPalettes[minEfficiencyIter->protoPalIndex]; ColorSet const &minColorSet = colorSets[minEfficiencyIter->colorSetIndex];
ProtoPalette const &maxProtoPal = protoPalettes[maxEfficiencyIter->protoPalIndex]; ColorSet const &maxColorSet = colorSets[maxEfficiencyIter->colorSetIndex];
size_t minSize = minProtoPal.size(); size_t minSize = minColorSet.size();
size_t maxSize = maxProtoPal.size(); size_t maxSize = maxColorSet.size();
uint32_t minRelSize = bestPal.relSizeOf(minProtoPal); uint32_t minRelSize = bestPal.relSizeOf(minColorSet);
uint32_t maxRelSize = bestPal.relSizeOf(maxProtoPal); uint32_t maxRelSize = bestPal.relSizeOf(maxColorSet);
options.verbosePrint( options.verbosePrint(
Options::VERB_TRACE, Options::VERB_TRACE,
" Proto-palettes %zu <= %zu: Efficiency: %zu / %" PRIu32 " <= %zu / %" PRIu32 " Color sets %zu <= %zu: Efficiency: %zu / %" PRIu32 " <= %zu / %" PRIu32 "\n",
"\n", minEfficiencyIter->colorSetIndex,
minEfficiencyIter->protoPalIndex, maxEfficiencyIter->colorSetIndex,
maxEfficiencyIter->protoPalIndex,
minSize, minSize,
minRelSize, minRelSize,
maxSize, maxSize,
@@ -507,11 +503,11 @@ std::tuple<std::vector<size_t>, size_t>
break; break;
} }
// Remove the proto-pal with minimal efficiency // Remove the color set with minimal efficiency
options.verbosePrint( options.verbosePrint(
Options::VERB_TRACE, Options::VERB_TRACE,
" Removing proto-palette %zu\n", " Removing color set %zu\n",
minEfficiencyIter->protoPalIndex minEfficiencyIter->colorSetIndex
); );
queue.emplace(std::move(*minEfficiencyIter)); queue.emplace(std::move(*minEfficiencyIter));
queue.back().banFrom(bestPalIndex); // Ban it from this palette queue.back().banFrom(bestPalIndex); // Ban it from this palette
@@ -521,42 +517,41 @@ std::tuple<std::vector<size_t>, size_t>
} }
// Deal with palettes still overloaded, by emptying them // Deal with palettes still overloaded, by emptying them
auto const &largestProtoPalFirst = auto const &largestColorSetFirst =
[&protoPalettes](ProtoPalAttrs const &lhs, ProtoPalAttrs const &rhs) { [&colorSets](ColorSetAttrs const &lhs, ColorSetAttrs const &rhs) {
return protoPalettes[lhs.protoPalIndex].size() return colorSets[lhs.colorSetIndex].size() > colorSets[rhs.colorSetIndex].size();
> protoPalettes[rhs.protoPalIndex].size();
}; };
std::vector<ProtoPalAttrs> overloadQueue{}; std::vector<ColorSetAttrs> overloadQueue{};
for (AssignedProtos &pal : assignments) { for (AssignedSets &pal : assignments) {
if (pal.volume() > options.maxOpaqueColors()) { if (pal.volume() > options.maxOpaqueColors()) {
for (ProtoPalAttrs &attrs : pal) { for (ColorSetAttrs &attrs : pal) {
overloadQueue.emplace( overloadQueue.emplace(
std::lower_bound(RANGE(overloadQueue), attrs, largestProtoPalFirst), std::lower_bound(RANGE(overloadQueue), attrs, largestColorSetFirst),
std::move(attrs) std::move(attrs)
); );
} }
pal.clear(); pal.clear();
} }
} }
// Place back any proto-palettes now in the queue via first-fit // Place back any color sets now in the queue via first-fit
for (ProtoPalAttrs const &attrs : overloadQueue) { for (ColorSetAttrs const &attrs : overloadQueue) {
ProtoPalette const &protoPal = protoPalettes[attrs.protoPalIndex]; ColorSet const &colorSet = colorSets[attrs.colorSetIndex];
auto iter = std::find_if(RANGE(assignments), [&protoPal](AssignedProtos const &pal) { auto iter = std::find_if(RANGE(assignments), [&colorSet](AssignedSets const &pal) {
return pal.canFit(protoPal); return pal.canFit(colorSet);
}); });
if (iter == assignments.end()) { // No such page, create a new one if (iter == assignments.end()) { // No such page, create a new one
options.verbosePrint( options.verbosePrint(
Options::VERB_DEBUG, 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(), assignments.size(),
attrs.protoPalIndex attrs.colorSetIndex
); );
assignments.emplace_back(protoPalettes, std::move(attrs)); assignments.emplace_back(colorSets, std::move(attrs));
} else { } else {
options.verbosePrint( options.verbosePrint(
Options::VERB_DEBUG, Options::VERB_DEBUG,
"Assigning overflowing proto-palette %zu to palette %zu\n", "Assigning overflowing color set %zu to palette %zu\n",
attrs.protoPalIndex, attrs.colorSetIndex,
iter - assignments.begin() iter - assignments.begin()
); );
iter->assign(std::move(attrs)); iter->assign(std::move(attrs));
@@ -565,24 +560,24 @@ std::tuple<std::vector<size_t>, size_t>
// LCOV_EXCL_START // LCOV_EXCL_START
if (options.verbosity >= Options::VERB_INTERM) { if (options.verbosity >= Options::VERB_INTERM) {
verboseOutputAssignments(assignments, protoPalettes); verboseOutputAssignments(assignments, colorSets);
} }
// LCOV_EXCL_STOP // LCOV_EXCL_STOP
// "Decant" the result // "Decant" the result
decant(assignments, protoPalettes); decant(assignments, colorSets);
// Note that the result does not contain any empty palettes // Note that the result does not contain any empty palettes
// LCOV_EXCL_START // LCOV_EXCL_START
if (options.verbosity >= Options::VERB_INTERM) { if (options.verbosity >= Options::VERB_INTERM) {
verboseOutputAssignments(assignments, protoPalettes); verboseOutputAssignments(assignments, colorSets);
} }
// LCOV_EXCL_STOP // LCOV_EXCL_STOP
std::vector<size_t> mappings(protoPalettes.size()); std::vector<size_t> mappings(colorSets.size());
for (size_t i = 0; i < assignments.size(); ++i) { for (size_t i = 0; i < assignments.size(); ++i) {
for (ProtoPalAttrs const &attrs : assignments[i]) { for (ColorSetAttrs const &attrs : assignments[i]) {
mappings[attrs.protoPalIndex] = i; mappings[attrs.colorSetIndex] = i;
} }
} }
return {mappings, assignments.size()}; return {mappings, assignments.size()};

View File

@@ -20,11 +20,11 @@
#include "helpers.hpp" #include "helpers.hpp"
#include "itertools.hpp" #include "itertools.hpp"
#include "gfx/color_set.hpp"
#include "gfx/main.hpp" #include "gfx/main.hpp"
#include "gfx/pal_packing.hpp" #include "gfx/pal_packing.hpp"
#include "gfx/pal_sorting.hpp" #include "gfx/pal_sorting.hpp"
#include "gfx/png.hpp" #include "gfx/png.hpp"
#include "gfx/proto_palette.hpp"
#include "gfx/warning.hpp" #include "gfx/warning.hpp"
static bool isBgColorTransparent() { static bool isBgColorTransparent() {
@@ -289,11 +289,11 @@ public:
}; };
struct AttrmapEntry { 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 // 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`. // attrmap entry while correctly handling the above, use `getPalID`.
size_t protoPaletteID; // Only this field is used when outputting "unoptimized" data 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 uint8_t tileID; // This is the ID as it will be output to the tilemap
bool bank; bool bank;
bool yFlip; bool yFlip;
bool xFlip; bool xFlip;
@@ -301,9 +301,9 @@ struct AttrmapEntry {
static constexpr size_t transparent = static_cast<size_t>(-1); static constexpr size_t transparent = static_cast<size_t>(-1);
static constexpr size_t background = static_cast<size_t>(-2); static constexpr size_t background = static_cast<size_t>(-2);
bool isBackgroundTile() const { return protoPaletteID == background; } bool isBackgroundTile() const { return colorSetID == background; }
size_t getPalID(std::vector<size_t> const &mappings) const { size_t getPalID(std::vector<size_t> 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<size_t>, std::vector<Palette>> static std::tuple<std::vector<size_t>, std::vector<Palette>>
generatePalettes(std::vector<ProtoPalette> const &protoPalettes, Image const &image) { generatePalettes(std::vector<ColorSet> const &colorSets, Image const &image) {
// Run a "pagination" problem solver // Run a "pagination" problem solver
auto [mappings, nbPalettes] = overloadAndRemove(protoPalettes); auto [mappings, nbPalettes] = overloadAndRemove(colorSets);
assume(mappings.size() == protoPalettes.size()); assume(mappings.size() == colorSets.size());
// LCOV_EXCL_START // LCOV_EXCL_START
if (options.verbosity >= Options::VERB_INTERM) { if (options.verbosity >= Options::VERB_INTERM) {
fprintf( fprintf(
stderr, stderr, "Color set mappings: (%zu palette%s)\n", nbPalettes, nbPalettes != 1 ? "s" : ""
"Proto-palette mappings: (%zu palette%s)\n",
nbPalettes,
nbPalettes != 1 ? "s" : ""
); );
for (size_t i = 0; i < mappings.size(); ++i) { for (size_t i = 0; i < mappings.size(); ++i) {
fprintf(stderr, "%zu -> %zu\n", i, mappings[i]); fprintf(stderr, "%zu -> %zu\n", i, mappings[i]);
@@ -358,9 +355,9 @@ static std::tuple<std::vector<size_t>, std::vector<Palette>>
} }
} }
// Generate the actual palettes from the mappings // Generate the actual palettes from the mappings
for (size_t protoPalID = 0; protoPalID < mappings.size(); ++protoPalID) { for (size_t colorSetID = 0; colorSetID < mappings.size(); ++colorSetID) {
Palette &pal = palettes[mappings[protoPalID]]; Palette &pal = palettes[mappings[colorSetID]];
for (uint16_t color : protoPalettes[protoPalID]) { for (uint16_t color : colorSets[colorSetID]) {
pal.addColor(color); pal.addColor(color);
} }
} }
@@ -383,7 +380,7 @@ static std::tuple<std::vector<size_t>, std::vector<Palette>>
} }
static std::tuple<std::vector<size_t>, std::vector<Palette>> static std::tuple<std::vector<size_t>, std::vector<Palette>>
makePalsAsSpecified(std::vector<ProtoPalette> const &protoPalettes) { makePalsAsSpecified(std::vector<ColorSet> const &colorSets) {
// Convert the palette spec to actual palettes // Convert the palette spec to actual palettes
std::vector<Palette> palettes(options.palSpec.size()); std::vector<Palette> palettes(options.palSpec.size());
for (auto [spec, pal] : zip(options.palSpec, palettes)) { for (auto [spec, pal] : zip(options.palSpec, palettes)) {
@@ -404,22 +401,22 @@ static std::tuple<std::vector<size_t>, std::vector<Palette>>
return &buf[literal_strlen(", ")]; return &buf[literal_strlen(", ")];
}; };
// Iterate through proto-palettes, and try mapping them to the specified palettes // Iterate through color sets, and try mapping them to the specified palettes
std::vector<size_t> mappings(protoPalettes.size()); std::vector<size_t> mappings(colorSets.size());
bool bad = false; bool bad = false;
for (size_t i = 0; i < protoPalettes.size(); ++i) { for (size_t i = 0; i < colorSets.size(); ++i) {
ProtoPalette const &protoPal = protoPalettes[i]; ColorSet const &colorSet = colorSets[i];
// Find the palette... // Find the palette...
auto iter = std::find_if(RANGE(palettes), [&protoPal](Palette const &pal) { auto iter = std::find_if(RANGE(palettes), [&colorSet](Palette const &pal) {
// ...which contains all colors in this proto-pal // ...which contains all colors in this color set
return std::all_of(RANGE(protoPal), [&pal](uint16_t color) { return std::all_of(RANGE(colorSet), [&pal](uint16_t color) {
return std::find(RANGE(pal), color) != pal.end(); return std::find(RANGE(pal), color) != pal.end();
}); });
}); });
if (iter == palettes.end()) { if (iter == palettes.end()) {
assume(!protoPal.empty()); assume(!colorSet.empty());
error("Failed to fit tile colors [%s] in specified palettes", listColors(protoPal)); error("Failed to fit tile colors [%s] in specified palettes", listColors(colorSet));
bad = true; bad = true;
} }
mappings[i] = iter - palettes.begin(); // Bogus value, but whatever mappings[i] = iter - palettes.begin(); // Bogus value, but whatever
@@ -808,8 +805,7 @@ static UniqueTiles dedupTiles(
attr.bank = 0; attr.bank = 0;
attr.tileID = 0; attr.tileID = 0;
} else { } else {
auto [tileID, matchType] = auto [tileID, matchType] = tiles.addTile({tile, palettes[mappings[attr.colorSetID]]});
tiles.addTile({tile, palettes[mappings[attr.protoPaletteID]]});
if (inputWithoutOutput && matchType == TileData::NOPE) { if (inputWithoutOutput && matchType == TileData::NOPE) {
error( error(
@@ -903,9 +899,9 @@ static void
void processPalettes() { void processPalettes() {
options.verbosePrint(Options::VERB_CFG, "Using libpng %s\n", png_get_libpng_ver(nullptr)); options.verbosePrint(Options::VERB_CFG, "Using libpng %s\n", png_get_libpng_ver(nullptr));
std::vector<ProtoPalette> protoPalettes; std::vector<ColorSet> colorSets;
std::vector<Palette> palettes; std::vector<Palette> palettes;
std::tie(std::ignore, palettes) = makePalsAsSpecified(protoPalettes); std::tie(std::ignore, palettes) = makePalsAsSpecified(colorSets);
outputPalettes(palettes); 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 // 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 // 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.) // output (with the exception of an un-duplicated tilemap, but that's an acceptable loss.)
std::vector<ProtoPalette> protoPalettes; std::vector<ColorSet> colorSets;
std::vector<AttrmapEntry> attrmap{}; std::vector<AttrmapEntry> attrmap{};
for (auto tile : image.visitAsTiles()) { for (auto tile : image.visitAsTiles()) {
@@ -973,22 +969,22 @@ void process() {
} }
if (tileColors.empty()) { 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()); assume(!isBgColorTransparent());
attrs.protoPaletteID = AttrmapEntry::transparent; attrs.colorSetID = AttrmapEntry::transparent;
continue; continue;
} }
ProtoPalette protoPalette; ColorSet colorSet;
for (uint16_t color : tileColors) { for (uint16_t color : tileColors) {
protoPalette.add(color); colorSet.add(color);
} }
if (options.bgColor.has_value() if (options.bgColor.has_value()
&& std::find(RANGE(tileColors), options.bgColor->cgbColor()) != tileColors.end()) { && std::find(RANGE(tileColors), options.bgColor->cgbColor()) != tileColors.end()) {
if (tileColors.size() == 1) { if (tileColors.size() == 1) {
// The tile contains just the background color, skip it. // The tile contains just the background color, skip it.
attrs.protoPaletteID = AttrmapEntry::background; attrs.colorSetID = AttrmapEntry::background;
continue; continue;
} }
fatal( fatal(
@@ -999,47 +995,47 @@ void process() {
); );
} }
// Insert the proto-palette, making sure to avoid overlaps // Insert the color set, making sure to avoid overlaps
for (size_t n = 0; n < protoPalettes.size(); ++n) { for (size_t n = 0; n < colorSets.size(); ++n) {
switch (protoPalette.compare(protoPalettes[n])) { switch (colorSet.compare(colorSets[n])) {
case ProtoPalette::WE_BIGGER: case ColorSet::WE_BIGGER:
protoPalettes[n] = protoPalette; // Override them colorSets[n] = colorSet; // Override them
// Remove any other proto-palettes that we encompass // Remove any other color sets that we encompass
// (Example [(0, 1), (0, 2)], inserting (0, 1, 2)) // (Example [(0, 1), (0, 2)], inserting (0, 1, 2))
[[fallthrough]]; [[fallthrough]];
case ProtoPalette::THEY_BIGGER: case ColorSet::THEY_BIGGER:
// Do nothing, they already contain us // Do nothing, they already contain us
attrs.protoPaletteID = n; attrs.colorSetID = n;
goto continue_visiting_tiles; // Can't `continue` from within a nested loop goto continue_visiting_tiles; // Can't `continue` from within a nested loop
case ProtoPalette::NEITHER: case ColorSet::NEITHER:
break; // Keep going break; // Keep going
} }
} }
attrs.protoPaletteID = protoPalettes.size(); attrs.colorSetID = colorSets.size();
if (protoPalettes.size() == AttrmapEntry::background) { // Check for overflow if (colorSets.size() == AttrmapEntry::background) { // Check for overflow
fatal( 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 AttrmapEntry::transparent
); );
} }
protoPalettes.push_back(protoPalette); colorSets.push_back(colorSet);
continue_visiting_tiles:; continue_visiting_tiles:;
} }
options.verbosePrint( options.verbosePrint(
Options::VERB_INTERM, Options::VERB_INTERM,
"Image contains %zu proto-palette%s\n", "Image contains %zu color set%s\n",
protoPalettes.size(), colorSets.size(),
protoPalettes.size() != 1 ? "s" : "" colorSets.size() != 1 ? "s" : ""
); );
// LCOV_EXCL_START // LCOV_EXCL_START
if (options.verbosity >= Options::VERB_INTERM) { if (options.verbosity >= Options::VERB_INTERM) {
for (ProtoPalette const &protoPal : protoPalettes) { for (ColorSet const &colorSet : colorSets) {
fputs("[ ", stderr); fputs("[ ", stderr);
for (uint16_t color : protoPal) { for (uint16_t color : colorSet) {
fprintf(stderr, "$%04x, ", color); fprintf(stderr, "$%04x, ", color);
} }
fputs("]\n", stderr); fputs("]\n", stderr);
@@ -1052,8 +1048,8 @@ continue_visiting_tiles:;
} }
auto [mappings, palettes] = auto [mappings, palettes] =
options.palSpecType == Options::NO_SPEC || options.palSpecType == Options::DMG options.palSpecType == Options::NO_SPEC || options.palSpecType == Options::DMG
? generatePalettes(protoPalettes, image) ? generatePalettes(colorSets, image)
: makePalsAsSpecified(protoPalettes); : makePalsAsSpecified(colorSets);
outputPalettes(palettes); outputPalettes(palettes);
// If deduplication is not happening, we just need to output the tile data and/or maps as-is // If deduplication is not happening, we just need to output the tile data and/or maps as-is