Run clang-format on everything (#1332)

This commit is contained in:
Sylvie
2024-03-04 14:22:49 -05:00
committed by GitHub
parent b004648a13
commit e74073e480
66 changed files with 6091 additions and 4957 deletions

View File

@@ -118,52 +118,54 @@ static char const *optstring = "-Aa:b:Cc:Dd:FfhL:mN:n:Oo:Pp:Qq:r:s:Tt:U:uVvx:Z";
* over short opt matching
*/
static option const longopts[] = {
{"auto-attr-map", no_argument, nullptr, 'A'},
{"output-attr-map", no_argument, nullptr, -'A'}, // Deprecated
{"attr-map", required_argument, nullptr, 'a'},
{"base-tiles", required_argument, nullptr, 'b'},
{"color-curve", no_argument, nullptr, 'C'},
{"colors", required_argument, nullptr, 'c'},
{"depth", required_argument, nullptr, 'd'},
{"slice", required_argument, nullptr, 'L'},
{"mirror-tiles", no_argument, nullptr, 'm'},
{"nb-tiles", required_argument, nullptr, 'N'},
{"nb-palettes", required_argument, nullptr, 'n'},
{"group-outputs", no_argument, nullptr, 'O'},
{"output", required_argument, nullptr, 'o'},
{"auto-palette", no_argument, nullptr, 'P'},
{"output-palette", no_argument, nullptr, -'P'}, // Deprecated
{"palette", required_argument, nullptr, 'p'},
{"auto-palette-map", no_argument, nullptr, 'Q'},
{"output-palette-map", no_argument, nullptr, -'Q'}, // Deprecated
{"palette-map", required_argument, nullptr, 'q'},
{"reverse", required_argument, nullptr, 'r'},
{"auto-tilemap", no_argument, nullptr, 'T'},
{"output-tilemap", no_argument, nullptr, -'T'}, // Deprecated
{"tilemap", required_argument, nullptr, 't'},
{"unit-size", required_argument, nullptr, 'U'},
{"unique-tiles", no_argument, nullptr, 'u'},
{"version", no_argument, nullptr, 'V'},
{"verbose", no_argument, nullptr, 'v'},
{"trim-end", required_argument, nullptr, 'x'},
{"columns", no_argument, nullptr, 'Z'},
{nullptr, no_argument, nullptr, 0 }
{"auto-attr-map", no_argument, nullptr, 'A' },
{"output-attr-map", no_argument, nullptr, -'A'}, // Deprecated
{"attr-map", required_argument, nullptr, 'a' },
{"base-tiles", required_argument, nullptr, 'b' },
{"color-curve", no_argument, nullptr, 'C' },
{"colors", required_argument, nullptr, 'c' },
{"depth", required_argument, nullptr, 'd' },
{"slice", required_argument, nullptr, 'L' },
{"mirror-tiles", no_argument, nullptr, 'm' },
{"nb-tiles", required_argument, nullptr, 'N' },
{"nb-palettes", required_argument, nullptr, 'n' },
{"group-outputs", no_argument, nullptr, 'O' },
{"output", required_argument, nullptr, 'o' },
{"auto-palette", no_argument, nullptr, 'P' },
{"output-palette", no_argument, nullptr, -'P'}, // Deprecated
{"palette", required_argument, nullptr, 'p' },
{"auto-palette-map", no_argument, nullptr, 'Q' },
{"output-palette-map", no_argument, nullptr, -'Q'}, // Deprecated
{"palette-map", required_argument, nullptr, 'q' },
{"reverse", required_argument, nullptr, 'r' },
{"auto-tilemap", no_argument, nullptr, 'T' },
{"output-tilemap", no_argument, nullptr, -'T'}, // Deprecated
{"tilemap", required_argument, nullptr, 't' },
{"unit-size", required_argument, nullptr, 'U' },
{"unique-tiles", no_argument, nullptr, 'u' },
{"version", no_argument, nullptr, 'V' },
{"verbose", no_argument, nullptr, 'v' },
{"trim-end", required_argument, nullptr, 'x' },
{"columns", no_argument, nullptr, 'Z' },
{nullptr, no_argument, nullptr, 0 }
};
static void printUsage() {
fputs("Usage: rgbgfx [-r stride] [-CmOuVZ] [-v [-v ...]] [-a <attr_map> | -A]\n"
" [-b <base_ids>] [-c <colors>] [-d <depth>] [-L <slice>] [-N <nb_tiles>]\n"
" [-n <nb_pals>] [-o <out_file>] [-p <pal_file> | -P] [-q <pal_map> | -Q]\n"
" [-s <nb_colors>] [-t <tile_map> | -T] [-x <nb_tiles>] <file>\n"
"Useful options:\n"
" -m, --mirror-tiles optimize out mirrored tiles\n"
" -o, --output <path> output the tile data to this path\n"
" -t, --tilemap <path> output the tile map to this path\n"
" -u, --unique-tiles optimize out identical tiles\n"
" -V, --version print RGBGFX version and exit\n"
"\n"
"For help, use `man rgbgfx' or go to https://rgbds.gbdev.io/docs/\n",
stderr);
fputs(
"Usage: rgbgfx [-r stride] [-CmOuVZ] [-v [-v ...]] [-a <attr_map> | -A]\n"
" [-b <base_ids>] [-c <colors>] [-d <depth>] [-L <slice>] [-N <nb_tiles>]\n"
" [-n <nb_pals>] [-o <out_file>] [-p <pal_file> | -P] [-q <pal_map> | -Q]\n"
" [-s <nb_colors>] [-t <tile_map> | -T] [-x <nb_tiles>] <file>\n"
"Useful options:\n"
" -m, --mirror-tiles optimize out mirrored tiles\n"
" -o, --output <path> output the tile data to this path\n"
" -t, --tilemap <path> output the tile map to this path\n"
" -u, --unique-tiles optimize out identical tiles\n"
" -V, --version print RGBGFX version and exit\n"
"\n"
"For help, use `man rgbgfx' or go to https://rgbds.gbdev.io/docs/\n",
stderr
);
}
/*
@@ -215,8 +217,9 @@ static uint16_t parseNumber(char *&string, char const *errPrefix, uint16_t errVa
};
if (charIndex(*string) == 255) {
error("%s: expected digit%s, but found nothing", errPrefix,
base != 10 ? " after base" : "");
error(
"%s: expected digit%s, but found nothing", errPrefix, base != 10 ? " after base" : ""
);
return errVal;
}
uint16_t number = 0;
@@ -246,10 +249,13 @@ static void skipWhitespace(char *&arg) {
static void registerInput(char const *arg) {
if (!options.input.empty()) {
fprintf(stderr,
"FATAL: input image specified more than once! (first \"%s\", then "
"\"%s\")\n",
options.input.c_str(), arg);
fprintf(
stderr,
"FATAL: input image specified more than once! (first \"%s\", then "
"\"%s\")\n",
options.input.c_str(),
arg
);
printUsage();
exit(1);
} else if (arg[0] == '\0') { // Empty input path
@@ -272,8 +278,10 @@ static std::vector<size_t> readAtFile(std::string const &path, std::vector<char>
}
// We only filter out `EOF`, but calling `isblank()` on anything else is UB!
static_assert(std::remove_reference_t<decltype(*file)>::traits_type::eof() == EOF,
"isblank(char_traits<...>::eof()) is UB!");
static_assert(
std::remove_reference_t<decltype(*file)>::traits_type::eof() == EOF,
"isblank(char_traits<...>::eof()) is UB!"
);
std::vector<size_t> argvOfs;
for (;;) {
@@ -296,7 +304,7 @@ static std::vector<size_t> readAtFile(std::string const &path, std::vector<char>
}
continue; // Start processing the next line
// If it's an empty line, ignore it
case '\r': // Assuming CRLF here
case '\r': // Assuming CRLF here
file->sbumpc(); // Discard the upcoming '\n'
[[fallthrough]];
case '\n':
@@ -371,8 +379,10 @@ static char *parseArgv(int argc, char *argv[]) {
}
skipWhitespace(arg);
if (*arg != ',') {
error("Base tile IDs must be one or two comma-separated numbers, not \"%s\"",
musl_optarg);
error(
"Base tile IDs must be one or two comma-separated numbers, not \"%s\"",
musl_optarg
);
break;
}
++arg; // Skip comma
@@ -384,8 +394,10 @@ static char *parseArgv(int argc, char *argv[]) {
options.baseTileIDs[1] = number;
}
if (*arg != '\0') {
error("Base tile IDs must be one or two comma-separated numbers, not \"%s\"",
musl_optarg);
error(
"Base tile IDs must be one or two comma-separated numbers, not \"%s\"",
musl_optarg
);
break;
}
break;
@@ -474,8 +486,10 @@ static char *parseArgv(int argc, char *argv[]) {
}
skipWhitespace(arg);
if (*arg != ',') {
error("Bank capacity must be one or two comma-separated numbers, not \"%s\"",
musl_optarg);
error(
"Bank capacity must be one or two comma-separated numbers, not \"%s\"",
musl_optarg
);
break;
}
++arg; // Skip comma
@@ -485,8 +499,10 @@ static char *parseArgv(int argc, char *argv[]) {
error("Bank 1 cannot contain more than 256 tiles");
}
if (*arg != '\0') {
error("Bank capacity must be one or two comma-separated numbers, not \"%s\"",
musl_optarg);
error(
"Bank capacity must be one or two comma-separated numbers, not \"%s\"",
musl_optarg
);
break;
}
break;
@@ -604,7 +620,7 @@ static char *parseArgv(int argc, char *argv[]) {
int main(int argc, char *argv[]) {
struct AtFileStackEntry {
int parentInd; // Saved offset into parent argv
int parentInd; // Saved offset into parent argv
std::vector<char *> argv; // This context's arg pointer vec
std::vector<char> argPool;
@@ -633,7 +649,7 @@ int main(int argc, char *argv[]) {
curArgc = stackEntry.argv.size() - 1;
curArgv = stackEntry.argv.data();
musl_optind = 1; // Don't use 0 because we're not scanning a different argv per se
continue; // Begin scanning that arg vector
continue; // Begin scanning that arg vector
}
if (musl_optind != curArgc) {
@@ -665,16 +681,23 @@ int main(int argc, char *argv[]) {
if (options.nbColorsPerPal == 0) {
options.nbColorsPerPal = 1u << options.bitDepth;
} else if (options.nbColorsPerPal > 1u << options.bitDepth) {
error("%" PRIu8 "bpp palettes can only contain %u colors, not %" PRIu8, options.bitDepth,
1u << options.bitDepth, options.nbColorsPerPal);
error(
"%" PRIu8 "bpp palettes can only contain %u colors, not %" PRIu8,
options.bitDepth,
1u << options.bitDepth,
options.nbColorsPerPal
);
}
auto autoOutPath = [](bool autoOptEnabled, std::string &path, char const *extension) {
if (autoOptEnabled) {
auto &image = localOptions.groupOutputs ? options.output : options.input;
if (image.empty()) {
fprintf(stderr, "FATAL: No %s specified\n", localOptions.groupOutputs
? "output tile data file" : "input image");
fprintf(
stderr,
"FATAL: No %s specified\n",
localOptions.groupOutputs ? "output tile data file" : "input image"
);
printUsage();
exit(1);
}
@@ -723,7 +746,8 @@ int main(int argc, char *argv[]) {
static std::array<char const *, 3> textbox{
" ,----------------------------------------.",
" | Augh, dimensional interference again?! |",
" `----------------------------------------'"};
" `----------------------------------------'",
};
for (size_t i = 0; i < gfx.size(); ++i) {
uint16_t row = gfx[i];
for (uint8_t _ = 0; _ < 10; ++_) {
@@ -781,15 +805,27 @@ int main(int argc, char *argv[]) {
}
fputs("\t]\n", stderr);
}
fprintf(stderr,
"\tInput image slice: %" PRIu32 "x%" PRIu32 " pixels starting at (%" PRIi32
", %" PRIi32 ")\n",
options.inputSlice.width, options.inputSlice.height, options.inputSlice.left,
options.inputSlice.top);
fprintf(stderr, "\tBase tile IDs: [%" PRIu8 ", %" PRIu8 "]\n", options.baseTileIDs[0],
options.baseTileIDs[1]);
fprintf(stderr, "\tMaximum %" PRIu16 " tiles in bank 0, %" PRIu16 " in bank 1\n",
options.maxNbTiles[0], options.maxNbTiles[1]);
fprintf(
stderr,
"\tInput image slice: %" PRIu32 "x%" PRIu32 " pixels starting at (%" PRIi32 ", %" PRIi32
")\n",
options.inputSlice.width,
options.inputSlice.height,
options.inputSlice.left,
options.inputSlice.top
);
fprintf(
stderr,
"\tBase tile IDs: [%" PRIu8 ", %" PRIu8 "]\n",
options.baseTileIDs[0],
options.baseTileIDs[1]
);
fprintf(
stderr,
"\tMaximum %" PRIu16 " tiles in bank 0, %" PRIu16 " in bank 1\n",
options.maxNbTiles[0],
options.maxNbTiles[1]
);
auto printPath = [](char const *name, std::string const &path) {
if (!path.empty()) {
fprintf(stderr, "\t%s: %s\n", name, path.c_str());
@@ -814,8 +850,7 @@ int main(int argc, char *argv[]) {
} else {
process();
}
} else if (!options.palettes.empty() && options.palSpecType == Options::EXPLICIT
&& !options.reverse()) {
} else if (!options.palettes.empty() && options.palSpecType == Options::EXPLICIT && !options.reverse()) {
processPalettes();
} else {
fputs("FATAL: No input image specified\n", stderr);
@@ -832,7 +867,7 @@ int main(int argc, char *argv[]) {
void Palette::addColor(uint16_t color) {
for (size_t i = 0; true; ++i) {
assert(i < colors.size()); // The packing should guarantee this
if (colors[i] == color) { // The color is already present
if (colors[i] == color) { // The color is already present
break;
} else if (colors[i] == UINT16_MAX) { // Empty slot
colors[i] = color;
@@ -858,9 +893,9 @@ auto Palette::begin() -> decltype(colors)::iterator {
auto Palette::end() -> decltype(colors)::iterator {
// Return an iterator pointing past the last non-empty element.
// Since the palette may contain gaps, we must scan from the end.
return std::find_if(colors.rbegin(), colors.rend(),
[](uint16_t c) { return c != UINT16_MAX; })
.base();
return std::find_if(
colors.rbegin(), colors.rend(), [](uint16_t c) { return c != UINT16_MAX; }
).base();
}
auto Palette::begin() const -> decltype(colors)::const_iterator {
@@ -870,9 +905,9 @@ auto Palette::begin() const -> decltype(colors)::const_iterator {
auto Palette::end() const -> decltype(colors)::const_iterator {
// Same as the non-const end().
return std::find_if(colors.rbegin(), colors.rend(),
[](uint16_t c) { return c != UINT16_MAX; })
.base();
return std::find_if(
colors.rbegin(), colors.rend(), [](uint16_t c) { return c != UINT16_MAX; }
).base();
}
uint8_t Palette::size() const {

View File

@@ -140,9 +140,10 @@ public:
*/
template<typename... Ts>
void assign(Ts &&...args) {
auto freeSlot = std::find_if_not(
RANGE(_assigned),
[](std::optional<ProtoPalAttrs> const &slot) { return slot.has_value(); });
auto freeSlot =
std::find_if_not(RANGE(_assigned), [](std::optional<ProtoPalAttrs> const &slot) {
return slot.has_value();
});
if (freeSlot == _assigned.end()) { // We are full, use a new slot
_assigned.emplace_back(std::forward<Ts>(args)...);
@@ -158,15 +159,20 @@ public:
bool empty() const {
return std::find_if(
RANGE(_assigned),
[](std::optional<ProtoPalAttrs> const &slot) { return slot.has_value(); })
[](std::optional<ProtoPalAttrs> const &slot) { return slot.has_value(); }
)
== _assigned.end();
}
size_t nbProtoPals() const { return std::distance(RANGE(*this)); }
private:
template<typename Iter>
static void addUniqueColors(std::unordered_set<uint16_t> &colors, Iter iter, Iter const &end,
std::vector<ProtoPalette> const &protoPals) {
static void addUniqueColors(
std::unordered_set<uint16_t> &colors,
Iter iter,
Iter const &end,
std::vector<ProtoPalette> const &protoPals
) {
for (; iter != end; ++iter) {
ProtoPalette const &protoPal = protoPals[iter->protoPalIndex];
colors.insert(RANGE(protoPal));
@@ -226,8 +232,8 @@ public:
* Computes the "relative size" of a set of proto-palettes on this palette
*/
template<typename Iter>
auto combinedVolume(Iter &&begin, Iter const &end,
std::vector<ProtoPalette> const &protoPals) const {
auto combinedVolume(Iter &&begin, Iter const &end, std::vector<ProtoPalette> const &protoPals)
const {
auto &colors = uniqueColors();
addUniqueColors(colors, std::forward<Iter>(begin), end, protoPals);
return colors.size();
@@ -243,8 +249,9 @@ public:
}
};
static void decant(std::vector<AssignedProtos> &assignments,
std::vector<ProtoPalette> const &protoPalettes) {
static void decant(
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
auto decantOn = [&assignments](auto const &tryDecanting) {
// No need to attempt decanting on palette #0, as there are no palettes to decant to
@@ -268,8 +275,9 @@ static void decant(std::vector<AssignedProtos> &assignments,
}
};
options.verbosePrint(Options::VERB_DEBUG, "%zu palettes before decanting\n",
assignments.size());
options.verbosePrint(
Options::VERB_DEBUG, "%zu palettes before decanting\n", assignments.size()
);
// Decant on palettes
decantOn([&protoPalettes](AssignedProtos &to, AssignedProtos &from) {
@@ -281,8 +289,9 @@ static void decant(std::vector<AssignedProtos> &assignments,
from.clear();
}
});
options.verbosePrint(Options::VERB_DEBUG, "%zu palettes after decanting on palettes\n",
assignments.size());
options.verbosePrint(
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) {
@@ -331,8 +340,9 @@ static void decant(std::vector<AssignedProtos> &assignments,
}
}
});
options.verbosePrint(Options::VERB_DEBUG, "%zu palettes after decanting on \"components\"\n",
assignments.size());
options.verbosePrint(
Options::VERB_DEBUG, "%zu palettes after decanting on \"components\"\n", assignments.size()
);
// Decant on individual proto-palettes
decantOn([&protoPalettes](AssignedProtos &to, AssignedProtos &from) {
@@ -343,14 +353,16 @@ static void decant(std::vector<AssignedProtos> &assignments,
}
}
});
options.verbosePrint(Options::VERB_DEBUG, "%zu palettes after decanting on proto-palettes\n",
assignments.size());
options.verbosePrint(
Options::VERB_DEBUG, "%zu palettes after decanting on proto-palettes\n", assignments.size()
);
}
std::tuple<DefaultInitVec<size_t>, size_t>
overloadAndRemove(std::vector<ProtoPalette> const &protoPalettes) {
options.verbosePrint(Options::VERB_LOG_ACT,
"Paginating palettes using \"overload-and-remove\" strategy...\n");
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
DefaultInitVec<size_t> sortedProtoPalIDs(protoPalettes.size());
@@ -379,9 +391,14 @@ std::tuple<DefaultInitVec<size_t>, size_t>
continue;
}
options.verbosePrint(Options::VERB_DEBUG, "%zu/%zu: Rel size: %f (size = %zu)\n", i + 1,
assignments.size(), assignments[i].relSizeOf(protoPal),
protoPal.size());
options.verbosePrint(
Options::VERB_DEBUG,
"%zu/%zu: Rel size: %f (size = %zu)\n",
i + 1,
assignments.size(),
assignments[i].relSizeOf(protoPal),
protoPal.size()
);
if (assignments[i].relSizeOf(protoPal) < bestRelSize) {
bestPalIndex = i;
}
@@ -397,21 +414,26 @@ std::tuple<DefaultInitVec<size_t>, size_t>
// If this overloads the palette, get it back to normal (if possible)
while (bestPal.volume() > options.maxOpaqueColors()) {
options.verbosePrint(Options::VERB_DEBUG,
"Palette %zu is overloaded! (%zu > %" PRIu8 ")\n",
bestPalIndex, bestPal.volume(), options.maxOpaqueColors());
options.verbosePrint(
Options::VERB_DEBUG,
"Palette %zu is overloaded! (%zu > %" PRIu8 ")\n",
bestPalIndex,
bestPal.volume(),
options.maxOpaqueColors()
);
// Look for a proto-pal minimizing "efficiency" (size / rel_size)
auto efficiency = [&bestPal](ProtoPalette const &pal) {
return pal.size() / bestPal.relSizeOf(pal);
};
auto [minEfficiencyIter, maxEfficiencyIter] =
std::minmax_element(RANGE(bestPal),
[&efficiency, &protoPalettes](ProtoPalAttrs const &lhs,
ProtoPalAttrs const &rhs) {
return efficiency(protoPalettes[lhs.protoPalIndex])
< efficiency(protoPalettes[rhs.protoPalIndex]);
});
auto [minEfficiencyIter, maxEfficiencyIter] = std::minmax_element(
RANGE(bestPal),
[&efficiency,
&protoPalettes](ProtoPalAttrs const &lhs, ProtoPalAttrs const &rhs) {
return efficiency(protoPalettes[lhs.protoPalIndex])
< efficiency(protoPalettes[rhs.protoPalIndex]);
}
);
// All efficiencies are identical iff min equals max
// TODO: maybe not ideal to re-compute these two?
@@ -443,18 +465,24 @@ std::tuple<DefaultInitVec<size_t>, size_t>
while (!queue.empty()) {
ProtoPalAttrs const &attrs = queue.front();
ProtoPalette const &protoPal = protoPalettes[attrs.protoPalIndex];
auto iter =
std::find_if(RANGE(assignments),
[&protoPal](AssignedProtos const &pal) { return pal.canFit(protoPal); });
auto iter = std::find_if(RANGE(assignments), [&protoPal](AssignedProtos const &pal) {
return pal.canFit(protoPal);
});
if (iter == assignments.end()) { // No such page, create a new one
options.verbosePrint(Options::VERB_DEBUG,
"Adding new palette (%zu) for overflowing proto-pal %zu\n",
assignments.size(), attrs.protoPalIndex);
options.verbosePrint(
Options::VERB_DEBUG,
"Adding new palette (%zu) for overflowing proto-pal %zu\n",
assignments.size(),
attrs.protoPalIndex
);
assignments.emplace_back(protoPalettes, std::move(attrs));
} else {
options.verbosePrint(Options::VERB_DEBUG,
"Assigning overflowing proto-pal %zu to palette %zu\n",
attrs.protoPalIndex, iter - assignments.begin());
options.verbosePrint(
Options::VERB_DEBUG,
"Assigning overflowing proto-pal %zu to palette %zu\n",
attrs.protoPalIndex,
iter - assignments.begin()
);
iter->assign(std::move(attrs));
}
queue.pop();

View File

@@ -13,13 +13,20 @@
namespace sorting {
void indexed(std::vector<Palette> &palettes, int palSize, png_color const *palRGB,
int palAlphaSize, png_byte *palAlpha) {
void indexed(
std::vector<Palette> &palettes,
int palSize,
png_color const *palRGB,
int palAlphaSize,
png_byte *palAlpha
) {
options.verbosePrint(Options::VERB_LOG_ACT, "Sorting palettes using embedded palette...\n");
auto pngToRgb = [&palRGB, &palAlphaSize, &palAlpha](int index) {
auto const &c = palRGB[index];
return Rgba(c.red, c.green, c.blue, palAlpha && index < palAlphaSize ? palAlpha[index] : 0xFF);
return Rgba(
c.red, c.green, c.blue, palAlpha && index < palAlphaSize ? palAlpha[index] : 0xFF
);
};
for (Palette &pal : palettes) {
@@ -43,8 +50,9 @@ void indexed(std::vector<Palette> &palettes, int palSize, png_color const *palRG
}
}
void grayscale(std::vector<Palette> &palettes,
std::array<std::optional<Rgba>, 0x8001> const &colors) {
void grayscale(
std::vector<Palette> &palettes, std::array<std::optional<Rgba>, 0x8001> const &colors
) {
options.verbosePrint(Options::VERB_LOG_ACT, "Sorting grayscale-only palette...\n");
// This method is only applicable if there are at most as many colors as colors per palette, so

View File

@@ -66,10 +66,12 @@ void parseInlinePalSpec(char const * const rawArg) {
assert(len <= arg.length());
errorMessage(msg);
fprintf(stderr,
"In inline palette spec: %s\n"
" ",
rawArg);
fprintf(
stderr,
"In inline palette spec: %s\n"
" ",
rawArg
);
for (auto i = ofs; i; --i) {
putc(' ', stderr);
}
@@ -97,12 +99,17 @@ void parseInlinePalSpec(char const * const rawArg) {
auto pos = std::min(arg.find_first_not_of("0123456789ABCDEFabcdef"sv, n), arg.length());
switch (pos - n) {
case 3:
color = Rgba(singleToHex(arg[n + 0]), singleToHex(arg[n + 1]), singleToHex(arg[n + 2]),
0xFF);
color = Rgba(
singleToHex(arg[n + 0]), singleToHex(arg[n + 1]), singleToHex(arg[n + 2]), 0xFF
);
break;
case 6:
color = Rgba(toHex(arg[n + 0], arg[n + 1]), toHex(arg[n + 2], arg[n + 3]),
toHex(arg[n + 4], arg[n + 5]), 0xFF);
color = Rgba(
toHex(arg[n + 0], arg[n + 1]),
toHex(arg[n + 2], arg[n + 3]),
toHex(arg[n + 4], arg[n + 5]),
0xFF
);
break;
case 0:
parseError(n - 1, 1, "Missing color after '#'");
@@ -239,36 +246,31 @@ static std::optional<U> parseDec(std::string const &str, std::string::size_type
return std::optional<U>{value};
}
static std::optional<Rgba> parseColor(std::string const &str, std::string::size_type &n,
uint16_t i) {
static std::optional<Rgba>
parseColor(std::string const &str, std::string::size_type &n, uint16_t i) {
std::optional<uint8_t> r = parseDec<uint8_t>(str, n);
if (!r) {
error("Failed to parse color #%d (\"%s\"): invalid red component", i + 1,
str.c_str());
error("Failed to parse color #%d (\"%s\"): invalid red component", i + 1, str.c_str());
return std::nullopt;
}
skipWhitespace(str, n);
if (n == str.length()) {
error("Failed to parse color #%d (\"%s\"): missing green component", i + 1,
str.c_str());
error("Failed to parse color #%d (\"%s\"): missing green component", i + 1, str.c_str());
return std::nullopt;
}
std::optional<uint8_t> g = parseDec<uint8_t>(str, n);
if (!g) {
error("Failed to parse color #%d (\"%s\"): invalid green component", i + 1,
str.c_str());
error("Failed to parse color #%d (\"%s\"): invalid green component", i + 1, str.c_str());
return std::nullopt;
}
skipWhitespace(str, n);
if (n == str.length()) {
error("Failed to parse color #%d (\"%s\"): missing blue component", i + 1,
str.c_str());
error("Failed to parse color #%d (\"%s\"): missing blue component", i + 1, str.c_str());
return std::nullopt;
}
std::optional<uint8_t> b = parseDec<uint8_t>(str, n);
if (!b) {
error("Failed to parse color #%d (\"%s\"): invalid blue component", i + 1,
str.c_str());
error("Failed to parse color #%d (\"%s\"): invalid blue component", i + 1, str.c_str());
return std::nullopt;
}
@@ -301,10 +303,14 @@ static void parsePSPFile(std::filebuf &file) {
return;
}
if (uint16_t nbPalColors = options.nbColorsPerPal * options.nbPalettes; *nbColors > nbPalColors) {
warning("PSP file contains %" PRIu16 " colors, but there can only be %" PRIu16
"; ignoring extra",
*nbColors, nbPalColors);
if (uint16_t nbPalColors = options.nbColorsPerPal * options.nbPalettes;
*nbColors > nbPalColors) {
warning(
"PSP file contains %" PRIu16 " colors, but there can only be %" PRIu16
"; ignoring extra",
*nbColors,
nbPalColors
);
nbColors = nbPalColors;
}
@@ -320,8 +326,11 @@ static void parsePSPFile(std::filebuf &file) {
return;
}
if (n != line.length()) {
error("Failed to parse color #%d (\"%s\"): trailing characters after blue component",
i + 1, line.c_str());
error(
"Failed to parse color #%d (\"%s\"): trailing characters after blue component",
i + 1,
line.c_str()
);
return;
}
@@ -372,9 +381,12 @@ static void parseGPLFile(std::filebuf &file) {
}
if (nbColors > maxNbColors) {
warning("GPL file contains %" PRIu16 " colors, but there can only be %" PRIu16
"; ignoring extra",
nbColors, maxNbColors);
warning(
"GPL file contains %" PRIu16 " colors, but there can only be %" PRIu16
"; ignoring extra",
nbColors,
maxNbColors
);
}
}
@@ -393,8 +405,11 @@ static void parseHEXFile(std::filebuf &file) {
if (line.length() != 6
|| line.find_first_not_of("0123456789ABCDEFabcdef"sv) != std::string::npos) {
error("Failed to parse color #%d (\"%s\"): invalid \"rrggbb\" line",
nbColors + 1, line.c_str());
error(
"Failed to parse color #%d (\"%s\"): invalid \"rrggbb\" line",
nbColors + 1,
line.c_str()
);
return;
}
@@ -411,9 +426,12 @@ static void parseHEXFile(std::filebuf &file) {
}
if (nbColors > maxNbColors) {
warning("HEX file contains %" PRIu16 " colors, but there can only be %" PRIu16
"; ignoring extra",
nbColors, maxNbColors);
warning(
"HEX file contains %" PRIu16 " colors, but there can only be %" PRIu16
"; ignoring extra",
nbColors,
maxNbColors
);
}
}
@@ -436,10 +454,14 @@ static void parseACTFile(std::filebuf &file) {
return;
}
if (uint16_t nbPalColors = options.nbColorsPerPal * options.nbPalettes; nbColors > nbPalColors) {
warning("ACT file contains %" PRIu16 " colors, but there can only be %" PRIu16
"; ignoring extra",
nbColors, nbPalColors);
if (uint16_t nbPalColors = options.nbColorsPerPal * options.nbPalettes;
nbColors > nbPalColors) {
warning(
"ACT file contains %" PRIu16 " colors, but there can only be %" PRIu16
"; ignoring extra",
nbColors,
nbPalColors
);
nbColors = nbPalColors;
}
@@ -486,10 +508,14 @@ static void parseACOFile(std::filebuf &file) {
}
uint16_t nbColors = readBE<uint16_t>(buf);
if (uint16_t nbPalColors = options.nbColorsPerPal * options.nbPalettes; nbColors > nbPalColors) {
warning("ACO file contains %" PRIu16 " colors, but there can only be %" PRIu16
"; ignoring extra",
nbColors, nbPalColors);
if (uint16_t nbPalColors = options.nbColorsPerPal * options.nbPalettes;
nbColors > nbPalColors) {
warning(
"ACO file contains %" PRIu16 " colors, but there can only be %" PRIu16
"; ignoring extra",
nbColors,
nbPalColors
);
nbColors = nbPalColors;
}
@@ -543,16 +569,22 @@ static void parseGBCFile(std::filebuf &file) {
if (len == 0) {
break;
} else if (len != sizeof(buf)) {
error("GBC palette dump contains %zu 8-byte palette%s, plus %zu byte%s",
options.palSpec.size(), options.palSpec.size() == 1 ? "" : "s", len,
len == 1 ? "" : "s");
error(
"GBC palette dump contains %zu 8-byte palette%s, plus %zu byte%s",
options.palSpec.size(),
options.palSpec.size() == 1 ? "" : "s",
len,
len == 1 ? "" : "s"
);
break;
}
options.palSpec.push_back({Rgba::fromCGBColor(readLE<uint16_t>(&buf[0])),
Rgba::fromCGBColor(readLE<uint16_t>(&buf[2])),
Rgba::fromCGBColor(readLE<uint16_t>(&buf[4])),
Rgba::fromCGBColor(readLE<uint16_t>(&buf[6]))});
options.palSpec.push_back(
{Rgba::fromCGBColor(readLE<uint16_t>(&buf[0])),
Rgba::fromCGBColor(readLE<uint16_t>(&buf[2])),
Rgba::fromCGBColor(readLE<uint16_t>(&buf[4])),
Rgba::fromCGBColor(readLE<uint16_t>(&buf[6]))}
);
}
}
@@ -576,14 +608,16 @@ void parseExternalPalSpec(char const *arg) {
std::tuple{"GBC", &parseGBCFile, std::ios::binary},
};
auto iter = std::find_if(RANGE(parsers),
[&arg, &ptr](decltype(parsers)::value_type const &parser) {
return strncasecmp(arg, std::get<0>(parser), ptr - arg) == 0;
});
auto iter =
std::find_if(RANGE(parsers), [&arg, &ptr](decltype(parsers)::value_type const &parser) {
return strncasecmp(arg, std::get<0>(parser), ptr - arg) == 0;
});
if (iter == parsers.end()) {
error("Unknown external palette format \"%.*s\"",
static_cast<int>(std::min(ptr - arg, static_cast<decltype(ptr - arg)>(INT_MAX))),
arg);
error(
"Unknown external palette format \"%.*s\"",
static_cast<int>(std::min(ptr - arg, static_cast<decltype(ptr - arg)>(INT_MAX))),
arg
);
return;
}

View File

@@ -59,10 +59,9 @@ public:
}
size_t size() const {
return std::count_if(RANGE(_colors),
[](decltype(_colors)::value_type const &slot) {
return slot.has_value() && !slot->isTransparent();
});
return std::count_if(RANGE(_colors), [](decltype(_colors)::value_type const &slot) {
return slot.has_value() && !slot->isTransparent();
});
}
decltype(_colors) const &raw() const { return _colors; }
@@ -105,10 +104,13 @@ class Png {
self->file->sgetn(reinterpret_cast<char *>(data), expectedLen);
if (nbBytesRead != expectedLen) {
fatal("Error reading input image (\"%s\"): file too short (expected at least %zd more "
"bytes after reading %zu)",
self->c_str(), length - nbBytesRead,
(size_t)self->file->pubseekoff(0, std::ios_base::cur));
fatal(
"Error reading input image (\"%s\"): file too short (expected at least %zd more "
"bytes after reading %zu)",
self->c_str(),
length - nbBytesRead,
(size_t)self->file->pubseekoff(0, std::ios_base::cur)
);
}
}
@@ -134,9 +136,12 @@ public:
bool isSuitableForGrayscale() const {
// Check that all of the grays don't fall into the same "bin"
if (colors.size() > options.maxOpaqueColors()) { // Apply the Pigeonhole Principle
options.verbosePrint(Options::VERB_DEBUG,
"Too many colors for grayscale sorting (%zu > %" PRIu8 ")\n",
colors.size(), options.maxOpaqueColors());
options.verbosePrint(
Options::VERB_DEBUG,
"Too many colors for grayscale sorting (%zu > %" PRIu8 ")\n",
colors.size(),
options.maxOpaqueColors()
);
return false;
}
uint8_t bins = 0;
@@ -145,9 +150,11 @@ public:
continue;
}
if (!color->isGray()) {
options.verbosePrint(Options::VERB_DEBUG,
"Found non-gray color #%08x, not using grayscale sorting\n",
color->toCSS());
options.verbosePrint(
Options::VERB_DEBUG,
"Found non-gray color #%08x, not using grayscale sorting\n",
color->toCSS()
);
return false;
}
uint8_t mask = 1 << color->grayIndex();
@@ -155,7 +162,8 @@ public:
options.verbosePrint(
Options::VERB_DEBUG,
"Color #%08x conflicts with another one, not using grayscale sorting\n",
color->toCSS());
color->toCSS()
);
return false;
}
bins |= mask;
@@ -174,8 +182,7 @@ public:
*/
explicit Png(std::string const &filePath) : path(filePath), colors() {
if (file.open(path, std::ios_base::in | std::ios_base::binary) == nullptr) {
fatal("Failed to open input image (\"%s\"): %s", file.c_str(path),
strerror(errno));
fatal("Failed to open input image (\"%s\"): %s", file.c_str(path), strerror(errno));
}
options.verbosePrint(Options::VERB_LOG_ACT, "Opened input file\n");
@@ -190,8 +197,9 @@ public:
options.verbosePrint(Options::VERB_INTERM, "PNG header signature is OK\n");
png = png_create_read_struct(PNG_LIBPNG_VER_STRING, (png_voidp)this, handleError,
handleWarning);
png = png_create_read_struct(
PNG_LIBPNG_VER_STRING, (png_voidp)this, handleError, handleWarning
);
if (!png) {
fatal("Failed to allocate PNG structure: %s", strerror(errno));
}
@@ -215,8 +223,9 @@ public:
int bitDepth, interlaceType; //, compressionType, filterMethod;
png_get_IHDR(png, info, &width, &height, &bitDepth, &colorType, &interlaceType, nullptr,
nullptr);
png_get_IHDR(
png, info, &width, &height, &bitDepth, &colorType, &interlaceType, nullptr, nullptr
);
if (options.inputSlice.width == 0 && width % 8 != 0) {
fatal("Image width (%" PRIu32 " pixels) is not a multiple of 8!", width);
@@ -253,23 +262,35 @@ public:
fatal("Unknown interlace type %d", interlaceType);
}
};
options.verbosePrint(Options::VERB_INTERM,
"Input image: %" PRIu32 "x%" PRIu32 " pixels, %dbpp %s, %s\n", width,
height, bitDepth, colorTypeName(), interlaceTypeName());
options.verbosePrint(
Options::VERB_INTERM,
"Input image: %" PRIu32 "x%" PRIu32 " pixels, %dbpp %s, %s\n",
width,
height,
bitDepth,
colorTypeName(),
interlaceTypeName()
);
if (png_get_PLTE(png, info, &embeddedPal, &nbColors) != 0) {
if (png_get_tRNS(png, info, &transparencyPal, &nbTransparentEntries, nullptr)) {
assert(nbTransparentEntries <= nbColors);
}
options.verbosePrint(Options::VERB_INTERM, "Embedded palette has %d colors: [",
nbColors);
options.verbosePrint(
Options::VERB_INTERM, "Embedded palette has %d colors: [", nbColors
);
for (int i = 0; i < nbColors; ++i) {
auto const &color = embeddedPal[i];
options.verbosePrint(
Options::VERB_INTERM, "#%02x%02x%02x%02x%s", color.red, color.green, color.blue,
Options::VERB_INTERM,
"#%02x%02x%02x%02x%s",
color.red,
color.green,
color.blue,
transparencyPal && i < nbTransparentEntries ? transparencyPal[i] : 0xFF,
i != nbColors - 1 ? ", " : "]\n");
i != nbColors - 1 ? ", " : "]\n"
);
}
} else {
options.verbosePrint(Options::VERB_INTERM, "No embedded palette\n");
@@ -329,40 +350,51 @@ public:
std::vector<uint32_t> indeterminates;
// Assign a color to the given position, and register it in the image palette as well
auto assignColor = [this, &conflicts, &indeterminates](png_uint_32 x, png_uint_32 y,
Rgba &&color) {
if (!color.isTransparent() && !color.isOpaque()) {
uint32_t css = color.toCSS();
if (std::find(RANGE(indeterminates), css)
== indeterminates.end()) {
error("Color #%08x is neither transparent (alpha < %u) nor opaque (alpha >= "
"%u) [first seen at x: %" PRIu32 ", y: %" PRIu32 "]",
css, Rgba::transparency_threshold, Rgba::opacity_threshold, x, y);
indeterminates.push_back(css);
}
} else if (Rgba const *other = colors.registerColor(color); other) {
std::tuple conflicting{color.toCSS(), other->toCSS()};
// Do not report combinations twice
if (std::find(RANGE(conflicts), conflicting) == conflicts.end()) {
warning("Fusing colors #%08x and #%08x into Game Boy color $%04x [first seen "
auto assignColor =
[this, &conflicts, &indeterminates](png_uint_32 x, png_uint_32 y, Rgba &&color) {
if (!color.isTransparent() && !color.isOpaque()) {
uint32_t css = color.toCSS();
if (std::find(RANGE(indeterminates), css) == indeterminates.end()) {
error(
"Color #%08x is neither transparent (alpha < %u) nor opaque (alpha >= "
"%u) [first seen at x: %" PRIu32 ", y: %" PRIu32 "]",
css,
Rgba::transparency_threshold,
Rgba::opacity_threshold,
x,
y
);
indeterminates.push_back(css);
}
} else if (Rgba const *other = colors.registerColor(color); other) {
std::tuple conflicting{color.toCSS(), other->toCSS()};
// Do not report combinations twice
if (std::find(RANGE(conflicts), conflicting) == conflicts.end()) {
warning(
"Fusing colors #%08x and #%08x into Game Boy color $%04x [first seen "
"at x: %" PRIu32 ", y: %" PRIu32 "]",
std::get<0>(conflicting), std::get<1>(conflicting), color.cgbColor(), x,
y);
// Do not report this combination again
conflicts.emplace_back(conflicting);
}
}
std::get<0>(conflicting),
std::get<1>(conflicting),
color.cgbColor(),
x,
y
);
// Do not report this combination again
conflicts.emplace_back(conflicting);
}
}
pixel(x, y) = color;
};
pixel(x, y) = color;
};
if (interlaceType == PNG_INTERLACE_NONE) {
for (png_uint_32 y = 0; y < height; ++y) {
png_read_row(png, row.data(), nullptr);
for (png_uint_32 x = 0; x < width; ++x) {
assignColor(x, y,
Rgba(row[x * 4], row[x * 4 + 1], row[x * 4 + 2], row[x * 4 + 3]));
assignColor(
x, y, Rgba(row[x * 4], row[x * 4 + 1], row[x * 4 + 2], row[x * 4 + 3])
);
}
}
} else {
@@ -454,14 +486,17 @@ public:
iterator begin() const { return {*this, _limit, 0, 0}; }
iterator end() const {
iterator it{*this, _limit, _width - 8, _height - 8}; // Last valid one...
return ++it; // ...now one-past-last!
return ++it; // ...now one-past-last!
}
};
public:
TilesVisitor visitAsTiles() const {
return {*this, options.columnMajor,
options.inputSlice.width ? options.inputSlice.width * 8 : width,
options.inputSlice.height ? options.inputSlice.height * 8 : height};
return {
*this,
options.columnMajor,
options.inputSlice.width ? options.inputSlice.width * 8 : width,
options.inputSlice.height ? options.inputSlice.height * 8 : height,
};
}
};
@@ -497,7 +532,7 @@ struct AttrmapEntry {
* 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
uint8_t tileID; // This is the ID as it will be output to the tilemap
bool bank;
bool yFlip;
bool xFlip;
@@ -523,8 +558,12 @@ static void generatePalSpec(Png const &png) {
embPalSize = options.maxOpaqueColors();
}
for (int i = 0; i < embPalSize; ++i) {
options.palSpec[0][i] = Rgba(embPalRGB[i].red, embPalRGB[i].green, embPalRGB[i].blue,
embPalAlpha && i < embPalAlphaSize ? embPalAlpha[i] : 0xFF);
options.palSpec[0][i] = Rgba(
embPalRGB[i].red,
embPalRGB[i].green,
embPalRGB[i].blue,
embPalAlpha && i < embPalAlphaSize ? embPalAlpha[i] : 0xFF
);
}
}
@@ -536,8 +575,12 @@ static std::tuple<DefaultInitVec<size_t>, std::vector<Palette>>
assert(mappings.size() == protoPalettes.size());
if (options.verbosity >= Options::VERB_INTERM) {
fprintf(stderr, "Proto-palette mappings: (%zu palette%s)\n", nbPalettes,
nbPalettes != 1 ? "s" : "");
fprintf(
stderr,
"Proto-palette 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]);
}
@@ -615,8 +658,11 @@ static std::tuple<DefaultInitVec<size_t>, std::vector<Palette>>
mappings[i] = iter - palettes.begin(); // Bogus value, but whatever
}
if (bad) {
fprintf(stderr, "note: The following palette%s specified:\n",
palettes.size() == 1 ? " was" : "s were");
fprintf(
stderr,
"note: The following palette%s specified:\n",
palettes.size() == 1 ? " was" : "s were"
);
for (Palette const &pal : palettes) {
fprintf(stderr, " [%s]\n", listColors(pal));
}
@@ -640,15 +686,17 @@ static void outputPalettes(std::vector<Palette> const &palettes) {
if (palettes.size() > options.nbPalettes) {
// If the palette generation is wrong, other (dependee) operations are likely to be
// nonsensical, so fatal-error outright
fatal("Generated %zu palettes, over the maximum of %" PRIu8, palettes.size(),
options.nbPalettes);
fatal(
"Generated %zu palettes, over the maximum of %" PRIu8,
palettes.size(),
options.nbPalettes
);
}
if (!options.palettes.empty()) {
File output;
if (!output.open(options.palettes, std::ios_base::out | std::ios_base::binary)) {
fatal("Failed to open \"%s\": %s", output.c_str(options.palettes),
strerror(errno));
fatal("Failed to open \"%s\": %s", output.c_str(options.palettes), strerror(errno));
}
for (Palette const &palette : palettes) {
@@ -675,8 +723,8 @@ public:
// of altering the element's hash, but the tile ID is not part of it.
mutable uint16_t tileID;
static uint16_t rowBitplanes(Png::TilesVisitor::Tile const &tile, Palette const &palette,
uint32_t y) {
static uint16_t
rowBitplanes(Png::TilesVisitor::Tile const &tile, Palette const &palette, uint32_t y) {
uint16_t row = 0;
for (uint32_t x = 0; x < 8; ++x) {
row <<= 1;
@@ -734,8 +782,9 @@ public:
}
// 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 (std::equal(RANGE(_data), other._data.begin(), [](uint8_t lhs, uint8_t rhs) {
return lhs == flipTable[rhs];
})) {
return MatchType::HFLIP;
}
@@ -773,16 +822,20 @@ struct std::hash<TileData> {
namespace unoptimized {
static void outputTileData(Png const &png, DefaultInitVec<AttrmapEntry> const &attrmap,
std::vector<Palette> const &palettes,
DefaultInitVec<size_t> const &mappings) {
static void outputTileData(
Png const &png,
DefaultInitVec<AttrmapEntry> const &attrmap,
std::vector<Palette> const &palettes,
DefaultInitVec<size_t> const &mappings
) {
File output;
if (!output.open(options.output, std::ios_base::out | std::ios_base::binary)) {
fatal("Failed to open \"%s\": %s", output.c_str(options.output), strerror(errno));
}
uint16_t widthTiles = options.inputSlice.width ? options.inputSlice.width : png.getWidth() / 8;
uint16_t heightTiles = options.inputSlice.height ? options.inputSlice.height : png.getHeight() / 8;
uint16_t heightTiles =
options.inputSlice.height ? options.inputSlice.height : png.getHeight() / 8;
uint64_t remainingTiles = widthTiles * heightTiles;
if (remainingTiles <= options.trim) {
return;
@@ -808,15 +861,15 @@ static void outputTileData(Png const &png, DefaultInitVec<AttrmapEntry> const &a
assert(remainingTiles == 0);
}
static void outputMaps(DefaultInitVec<AttrmapEntry> const &attrmap,
DefaultInitVec<size_t> const &mappings) {
static void outputMaps(
DefaultInitVec<AttrmapEntry> const &attrmap, DefaultInitVec<size_t> const &mappings
) {
std::optional<File> tilemapOutput, attrmapOutput, palmapOutput;
auto autoOpenPath = [](std::string const &path, std::optional<File> &file) {
if (!path.empty()) {
file.emplace();
if (!file->open(path, std::ios_base::out | std::ios_base::binary)) {
fatal("Failed to open \"%s\": %s", file->c_str(options.tilemap),
strerror(errno));
fatal("Failed to open \"%s\": %s", file->c_str(options.tilemap), strerror(errno));
}
}
};
@@ -864,8 +917,8 @@ struct UniqueTiles {
/*
* Adds a tile to the collection, and returns its ID
*/
std::tuple<uint16_t, TileData::MatchType> addTile(Png::TilesVisitor::Tile const &tile,
Palette const &palette) {
std::tuple<uint16_t, TileData::MatchType>
addTile(Png::TilesVisitor::Tile const &tile, Palette const &palette) {
TileData newTile(tile, palette);
auto [tileData, inserted] = tileset.insert(newTile);
@@ -893,9 +946,12 @@ struct UniqueTiles {
* 8-bit tile IDs + the bank bit; this will save the work when we output the data later (potentially
* twice)
*/
static UniqueTiles dedupTiles(Png const &png, DefaultInitVec<AttrmapEntry> &attrmap,
std::vector<Palette> const &palettes,
DefaultInitVec<size_t> const &mappings) {
static UniqueTiles dedupTiles(
Png const &png,
DefaultInitVec<AttrmapEntry> &attrmap,
std::vector<Palette> const &palettes,
DefaultInitVec<size_t> const &mappings
) {
// Iterate throughout the image, generating tile data as we go
// (We don't need the full tile data to be able to dedup tiles, but we don't lose anything
// by caching the full tile data anyway, so we might as well.)
@@ -941,8 +997,9 @@ static void outputTilemap(DefaultInitVec<AttrmapEntry> const &attrmap) {
}
}
static void outputAttrmap(DefaultInitVec<AttrmapEntry> const &attrmap,
DefaultInitVec<size_t> const &mappings) {
static void outputAttrmap(
DefaultInitVec<AttrmapEntry> const &attrmap, DefaultInitVec<size_t> const &mappings
) {
File output;
if (!output.open(options.attrmap, std::ios_base::out | std::ios_base::binary)) {
fatal("Failed to create \"%s\": %s", output.c_str(options.attrmap), strerror(errno));
@@ -956,8 +1013,9 @@ static void outputAttrmap(DefaultInitVec<AttrmapEntry> const &attrmap,
}
}
static void outputPalmap(DefaultInitVec<AttrmapEntry> const &attrmap,
DefaultInitVec<size_t> const &mappings) {
static void outputPalmap(
DefaultInitVec<AttrmapEntry> const &attrmap, DefaultInitVec<size_t> const &mappings
) {
File output;
if (!output.open(options.palmap, std::ios_base::out | std::ios_base::binary)) {
fatal("Failed to create \"%s\": %s", output.c_str(options.palmap), strerror(errno));
@@ -1063,21 +1121,33 @@ void process() {
}
if (nbColorsInTile > options.maxOpaqueColors()) {
fatal("Tile at (%" PRIu32 ", %" PRIu32 ") has %" PRIu8 " opaque colors, more than %" PRIu8 "!",
tile.x, tile.y, nbColorsInTile, options.maxOpaqueColors());
fatal(
"Tile at (%" PRIu32 ", %" PRIu32 ") has %" PRIu8 " opaque colors, more than %" PRIu8
"!",
tile.x,
tile.y,
nbColorsInTile,
options.maxOpaqueColors()
);
}
attrs.protoPaletteID = protoPalettes.size();
if (protoPalettes.size() == AttrmapEntry::transparent) { // Check for overflow
fatal("Reached %zu proto-palettes... sorry, this image is too much for me to handle :(",
AttrmapEntry::transparent);
fatal(
"Reached %zu proto-palettes... sorry, this image is too much for me to handle :(",
AttrmapEntry::transparent
);
}
protoPalettes.push_back(tileColors);
contained:;
}
options.verbosePrint(Options::VERB_INTERM, "Image contains %zu proto-palette%s\n",
protoPalettes.size(), protoPalettes.size() != 1 ? "s" : "");
options.verbosePrint(
Options::VERB_INTERM,
"Image contains %zu proto-palette%s\n",
protoPalettes.size(),
protoPalettes.size() != 1 ? "s" : ""
);
if (options.verbosity >= Options::VERB_INTERM) {
for (auto const &protoPal : protoPalettes) {
fputs("[ ", stderr);
@@ -1102,8 +1172,12 @@ contained:;
// Check the tile count
if (nbTilesW * nbTilesH > options.maxNbTiles[0] + options.maxNbTiles[1]) {
fatal("Image contains %" PRIu32 " tiles, exceeding the limit of %" PRIu16 " + %" PRIu16,
nbTilesW * nbTilesH, options.maxNbTiles[0], options.maxNbTiles[1]);
fatal(
"Image contains %" PRIu32 " tiles, exceeding the limit of %" PRIu16 " + %" PRIu16,
nbTilesW * nbTilesH,
options.maxNbTiles[0],
options.maxNbTiles[1]
);
}
if (!options.output.empty()) {
@@ -1114,7 +1188,8 @@ contained:;
if (!options.tilemap.empty() || !options.attrmap.empty() || !options.palmap.empty()) {
options.verbosePrint(
Options::VERB_LOG_ACT,
"Generating unoptimized tilemap and/or attrmap and/or palmap...\n");
"Generating unoptimized tilemap and/or attrmap and/or palmap...\n"
);
unoptimized::outputMaps(attrmap, mappings);
}
} else {
@@ -1123,8 +1198,12 @@ contained:;
optimized::UniqueTiles tiles = optimized::dedupTiles(png, attrmap, palettes, mappings);
if (tiles.size() > options.maxNbTiles[0] + options.maxNbTiles[1]) {
fatal("Image contains %zu tiles, exceeding the limit of %" PRIu16 " + %" PRIu16,
tiles.size(), options.maxNbTiles[0], options.maxNbTiles[1]);
fatal(
"Image contains %zu tiles, exceeding the limit of %" PRIu16 " + %" PRIu16,
tiles.size(),
options.maxNbTiles[0],
options.maxNbTiles[1]
);
}
if (!options.output.empty()) {

View File

@@ -1,7 +1,5 @@
/* SPDX-License-Identifier: MIT */
#include "helpers.hpp"
#include "gfx/proto_palette.hpp"
#include <algorithm>
@@ -10,6 +8,8 @@
#include <stddef.h>
#include <stdint.h>
#include "helpers.hpp"
bool ProtoPalette::add(uint16_t color) {
size_t i = 0;

View File

@@ -53,13 +53,19 @@ static DefaultInitVec<uint8_t> readInto(const std::string &path) {
}
[[noreturn]] static void pngError(png_structp png, char const *msg) {
fatal("Error writing reversed image (\"%s\"): %s",
static_cast<char const *>(png_get_error_ptr(png)), msg);
fatal(
"Error writing reversed image (\"%s\"): %s",
static_cast<char const *>(png_get_error_ptr(png)),
msg
);
}
static void pngWarning(png_structp png, char const *msg) {
warning("While writing reversed image (\"%s\"): %s",
static_cast<char const *>(png_get_error_ptr(png)), msg);
warning(
"While writing reversed image (\"%s\"): %s",
static_cast<char const *>(png_get_error_ptr(png)),
msg
);
}
void writePng(png_structp png, png_bytep data, size_t length) {
@@ -94,17 +100,23 @@ void reverse() {
warning("\"Sliced-off\" pixels are ignored in reverse mode");
}
if (options.inputSlice.width != 0 && options.inputSlice.width != options.reversedWidth * 8) {
warning("Specified input slice width (%" PRIu16
") doesn't match provided reversing width (%" PRIu16 " * 8)",
options.inputSlice.width, options.reversedWidth);
warning(
"Specified input slice width (%" PRIu16
") doesn't match provided reversing width (%" PRIu16 " * 8)",
options.inputSlice.width,
options.reversedWidth
);
}
options.verbosePrint(Options::VERB_LOG_ACT, "Reading tiles...\n");
auto const tiles = readInto(options.output);
uint8_t tileSize = 8 * options.bitDepth;
if (tiles.size() % tileSize != 0) {
fatal("Tile data size (%zu bytes) is not a multiple of %" PRIu8 " bytes",
tiles.size(), tileSize);
fatal(
"Tile data size (%zu bytes) is not a multiple of %" PRIu8 " bytes",
tiles.size(),
tileSize
);
}
// By default, assume tiles are not deduplicated, and add the (allegedly) trimmed tiles
@@ -121,25 +133,33 @@ void reverse() {
fatal("Cannot generate empty image");
}
if (nbTileInstances > options.maxNbTiles[0] + options.maxNbTiles[1]) {
warning("Read %zu tiles, more than the limit of %" PRIu16 " + %" PRIu16,
nbTileInstances, options.maxNbTiles[0], options.maxNbTiles[1]);
warning(
"Read %zu tiles, more than the limit of %" PRIu16 " + %" PRIu16,
nbTileInstances,
options.maxNbTiles[0],
options.maxNbTiles[1]
);
}
size_t width = options.reversedWidth, height; // In tiles
if (nbTileInstances % width != 0) {
fatal("Total number of tiles read (%zu) cannot be divided by image width (%zu tiles)",
nbTileInstances, width);
fatal(
"Total number of tiles read (%zu) cannot be divided by image width (%zu tiles)",
nbTileInstances,
width
);
}
height = nbTileInstances / width;
options.verbosePrint(Options::VERB_INTERM, "Reversed image dimensions: %zux%zu tiles\n", width,
height);
options.verbosePrint(
Options::VERB_INTERM, "Reversed image dimensions: %zux%zu tiles\n", width, height
);
// TODO: `-U` to configure tile size beyond 8x8px ("deduplication units")
std::vector<std::array<std::optional<Rgba>, 4>> palettes{
{Rgba(0xFFFFFFFF), Rgba(0xAAAAAAFF), Rgba(0x555555FF), Rgba(0x000000FF)}
};
};
// If a palette file is used as input, it overrides the default colors.
if (!options.palettes.empty()) {
File file;
@@ -155,27 +175,37 @@ void reverse() {
if (nbRead == buf.size()) {
// Expand the colors
auto &palette = palettes.emplace_back();
std::generate(palette.begin(), palette.begin() + options.nbColorsPerPal,
[&buf, i = 0]() mutable {
i += 2;
return Rgba::fromCGBColor(buf[i - 2] + (buf[i - 1] << 8));
});
std::generate(
palette.begin(),
palette.begin() + options.nbColorsPerPal,
[&buf, i = 0]() mutable {
i += 2;
return Rgba::fromCGBColor(buf[i - 2] + (buf[i - 1] << 8));
}
);
} else if (nbRead != 0) {
fatal("Palette data size (%zu) is not a multiple of %zu bytes!\n",
palettes.size() * buf.size() + nbRead, buf.size());
fatal(
"Palette data size (%zu) is not a multiple of %zu bytes!\n",
palettes.size() * buf.size() + nbRead,
buf.size()
);
}
} while (nbRead != 0);
if (palettes.size() > options.nbPalettes) {
warning("Read %zu palettes, more than the specified limit of %" PRIu8,
palettes.size(), options.nbPalettes);
warning(
"Read %zu palettes, more than the specified limit of %" PRIu8,
palettes.size(),
options.nbPalettes
);
}
if (options.palSpecType == Options::EXPLICIT && palettes != options.palSpec) {
warning("Colors in the palette file do not match those specified with `-c`!");
}
} else if (options.palSpecType == Options::EMBEDDED) {
warning("An embedded palette was requested, but no palette file was specified; ignoring request.");
warning("An embedded palette was requested, but no palette file was specified; ignoring "
"request.");
} else if (options.palSpecType == Options::EXPLICIT) {
palettes = std::move(options.palSpec); // We won't be using it again.
}
@@ -184,8 +214,11 @@ void reverse() {
if (!options.attrmap.empty()) {
attrmap = readInto(options.attrmap);
if (attrmap->size() != nbTileInstances) {
fatal("Attribute map size (%zu tiles) doesn't match image's (%zu)", attrmap->size(),
nbTileInstances);
fatal(
"Attribute map size (%zu tiles) doesn't match image's (%zu)",
attrmap->size(),
nbTileInstances
);
}
// Scan through the attributes for inconsistencies
@@ -195,8 +228,9 @@ void reverse() {
bool bad = false;
for (auto attr : *attrmap) {
if ((attr & 0b111) > palettes.size()) {
error("Referencing palette %u, but there are only %zu!",
attr & 0b111, palettes.size());
error(
"Referencing palette %u, but there are only %zu!", attr & 0b111, palettes.size()
);
bad = true;
}
if (attr & 0x08 && !tilemap) {
@@ -213,16 +247,22 @@ void reverse() {
for (auto [id, attr] : zip(*tilemap, *attrmap)) {
bool bank = attr & 1 << 3;
if (id >= options.maxNbTiles[bank]) {
warning("Tile #%" PRIu8
" was referenced, but the limit for bank %u is %" PRIu16,
id, bank, options.maxNbTiles[bank]);
warning(
"Tile #%" PRIu8 " was referenced, but the limit for bank %u is %" PRIu16,
id,
bank,
options.maxNbTiles[bank]
);
}
}
} else {
for (auto id : *tilemap) {
if (id >= options.maxNbTiles[0]) {
warning("Tile #%" PRIu8 " was referenced, but the limit is %" PRIu16, id,
options.maxNbTiles[0]);
warning(
"Tile #%" PRIu8 " was referenced, but the limit is %" PRIu16,
id,
options.maxNbTiles[0]
);
}
}
}
@@ -232,8 +272,11 @@ void reverse() {
if (!options.palmap.empty()) {
palmap = readInto(options.palmap);
if (palmap->size() != nbTileInstances) {
fatal("Palette map size (%zu tiles) doesn't match image's (%zu)", palmap->size(),
nbTileInstances);
fatal(
"Palette map size (%zu tiles) doesn't match image's (%zu)",
palmap->size(),
nbTileInstances
);
}
}
@@ -244,8 +287,10 @@ void reverse() {
}
png_structp png = png_create_write_struct(
PNG_LIBPNG_VER_STRING,
const_cast<png_voidp>(static_cast<void const *>(pngFile.c_str(options.input))), pngError,
pngWarning);
const_cast<png_voidp>(static_cast<void const *>(pngFile.c_str(options.input))),
pngError,
pngWarning
);
if (!png) {
fatal("Failed to create PNG write struct: %s", strerror(errno));
}
@@ -255,8 +300,17 @@ void reverse() {
}
png_set_write_fn(png, &pngFile, writePng, flushPng);
png_set_IHDR(png, pngInfo, options.reversedWidth * 8, height * 8, 8, PNG_COLOR_TYPE_RGB_ALPHA,
PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
png_set_IHDR(
png,
pngInfo,
options.reversedWidth * 8,
height * 8,
8,
PNG_COLOR_TYPE_RGB_ALPHA,
PNG_INTERLACE_NONE,
PNG_COMPRESSION_TYPE_DEFAULT,
PNG_FILTER_TYPE_DEFAULT
);
png_write_info(png, pngInfo);
png_color_8 sbitChunk;
@@ -270,10 +324,14 @@ void reverse() {
size_t const SIZEOF_ROW = options.reversedWidth * 8 * SIZEOF_PIXEL;
std::vector<uint8_t> tileRow(8 * SIZEOF_ROW, 0xFF); // Data for 8 rows of pixels
uint8_t * const rowPtrs[8] = {
&tileRow.data()[0 * SIZEOF_ROW], &tileRow.data()[1 * SIZEOF_ROW],
&tileRow.data()[2 * SIZEOF_ROW], &tileRow.data()[3 * SIZEOF_ROW],
&tileRow.data()[4 * SIZEOF_ROW], &tileRow.data()[5 * SIZEOF_ROW],
&tileRow.data()[6 * SIZEOF_ROW], &tileRow.data()[7 * SIZEOF_ROW],
&tileRow.data()[0 * SIZEOF_ROW],
&tileRow.data()[1 * SIZEOF_ROW],
&tileRow.data()[2 * SIZEOF_ROW],
&tileRow.data()[3 * SIZEOF_ROW],
&tileRow.data()[4 * SIZEOF_ROW],
&tileRow.data()[5 * SIZEOF_ROW],
&tileRow.data()[6 * SIZEOF_ROW],
&tileRow.data()[7 * SIZEOF_ROW],
};
for (size_t ty = 0; ty < height; ++ty) {
@@ -294,8 +352,22 @@ void reverse() {
// We do not have data for tiles trimmed with `-x`, so assume they are "blank"
static std::array<uint8_t, 16> const trimmedTile{
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
};
uint8_t const *tileData = tileID > nbTileInstances - options.trim
? trimmedTile.data()

View File

@@ -15,11 +15,11 @@
* with gaps in the scale curve filled by polynomial interpolation.
*/
static std::array<uint8_t, 256> reverse_curve{
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, // These
1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, // comments
3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, // prevent
5, 5, 5, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, // clang-format
7, 8, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 10, 10, 10, 10, // from
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, // These
1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, // comments
3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, // prevent
5, 5, 5, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, // clang-format
7, 8, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 10, 10, 10, 10, // from
10, 10, 11, 11, 11, 11, 11, 11, 12, 12, 12, 12, 12, 13, 13, 13, // reflowing
13, 13, 14, 14, 14, 14, 14, 14, 15, 15, 15, 15, 15, 16, 16, 16, // these
16, 16, 16, 17, 17, 17, 17, 17, 18, 18, 18, 18, 18, 18, 19, 19, // sixteen