ref: 6b0cab32a67247be33fcfa8d509603fb52020920
parent: cc27169ecdd4e29944e207acc4a39f25e78b0a8f
author: ISSOtm <[email protected]>
date: Sat Apr 9 09:47:38 EDT 2022
Implement inline palette spec parsing
--- a/Makefile
+++ b/Makefile
@@ -108,6 +108,7 @@
src/gfx/main.o \
src/gfx/pal_packing.o \
src/gfx/pal_sorting.o \
+ src/gfx/pal_spec.o \
src/gfx/process.o \
src/gfx/proto_palette.o \
src/gfx/reverse.o \
--- /dev/null
+++ b/include/gfx/pal_spec.hpp
@@ -1,0 +1,16 @@
+/*
+ * This file is part of RGBDS.
+ *
+ * Copyright (c) 2022, Eldred Habert and RGBDS contributors.
+ *
+ * SPDX-License-Identifier: MIT
+ */
+
+#ifndef RGBDS_GFX_PAL_SPEC_HPP
+#define RGBDS_GFX_PAL_SPEC_HPP
+
+#include <string_view>
+
+void parseInlinePalSpec(char const * const arg);
+
+#endif /* RGBDS_GFX_PAL_SPEC_HPP */
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -73,6 +73,7 @@
"gfx/main.cpp"
"gfx/pal_packing.cpp"
"gfx/pal_sorting.cpp"
+ "gfx/pal_spec.cpp"
"gfx/process.cpp"
"gfx/proto_palette.cpp"
"gfx/reverse.cpp"
--- a/src/gfx/main.cpp
+++ b/src/gfx/main.cpp
@@ -26,6 +26,7 @@
#include "platform.h"
#include "version.h"
+#include "gfx/pal_spec.hpp"
#include "gfx/process.hpp"
#include "gfx/reverse.hpp"
@@ -223,9 +224,8 @@
static void parsePaletteSpec(char const *arg) {
if (arg[0] == '#') {
- // List of #rrggbb/#rgb colors, comma-separated, palettes are separated by colons
options.palSpecType = Options::EXPLICIT;
- // TODO
+ parseInlinePalSpec(arg);
} else if (strcasecmp(arg, "embedded") == 0) {
// Use PLTE, error out if missing
options.palSpecType = Options::EMBEDDED;
@@ -668,6 +668,14 @@
}
return "???";
}());
+ if (options.palSpecType == Options::EXPLICIT) {
+ fputs("\t[\n", stderr);
+ for (std::array<Rgba, 4> const &pal : options.palSpec) {
+ fprintf(stderr, "\t\t#%06x, #%06x, #%06x, #%06x,\n", pal[0].toCSS() >> 8,
+ pal[1].toCSS() >> 8, pal[2].toCSS() >> 8, pal[3].toCSS() >> 8);
+ }
+ fputs("\t]\n", stderr);
+ }
fprintf(stderr, "\tDedup unit: %" PRIu16 "x%" PRIu16 " tiles\n", options.unitSize[0],
options.unitSize[1]);
fprintf(stderr,
--- /dev/null
+++ b/src/gfx/pal_spec.cpp
@@ -1,0 +1,151 @@
+/*
+ * This file is part of RGBDS.
+ *
+ * Copyright (c) 2022, Eldred Habert and RGBDS contributors.
+ *
+ * SPDX-License-Identifier: MIT
+ */
+
+#include "gfx/pal_spec.hpp"
+
+#include <algorithm>
+#include <cassert>
+#include <cstdio>
+
+#include "gfx/main.hpp"
+
+using namespace std::string_view_literals;
+
+constexpr uint8_t nibble(char c) {
+ if (c >= 'a') {
+ assert(c <= 'f');
+ return c - 'a' + 10;
+ } else if (c >= 'A') {
+ assert(c <= 'F');
+ return c - 'A' + 10;
+ } else {
+ assert(c >= '0' && c <= '9');
+ return c - '0';
+ }
+}
+
+constexpr uint8_t toHex(char c1, char c2) {
+ return nibble(c1) * 16 + nibble(c2);
+}
+
+constexpr uint8_t singleToHex(char c) {
+ return toHex(c, c);
+}
+
+void parseInlinePalSpec(char const * const rawArg) {
+ // List of #rrggbb/#rgb colors, comma-separated, palettes are separated by colons
+
+ std::string_view arg(rawArg);
+ using size_type = decltype(arg)::size_type;
+
+ auto parseError = [&rawArg, &arg](size_type ofs, size_type len, char const *fmt,
+ auto &&...args) {
+ (void)arg; // With NDEBUG, `arg` is otherwise not used
+ assert(ofs <= arg.length());
+ assert(len <= arg.length());
+
+ error(fmt, args...);
+ fprintf(stderr,
+ "In inline palette spec: %s\n"
+ " ",
+ rawArg);
+ for (auto i = ofs; i; --i) {
+ putc(' ', stderr);
+ }
+ for (auto i = len; i; --i) {
+ putc('^', stderr);
+ }
+ putc('\n', stderr);
+ };
+
+ auto skipWhitespace = [&arg](size_type &pos) {
+ pos = std::min(arg.find_first_not_of(" \t", pos), arg.length());
+ };
+
+ options.palSpec.clear();
+ options.palSpec
+ .emplace_back(); // Not default-initialized, but value-initialized, so we get zeros
+
+ size_type n = 0; // Index into the argument
+ // TODO: store max `nbColors` ever reached, and compare against palette size later
+ size_t nbColors = 0; // Number of colors in the current palette
+ for (;;) {
+ ++n; // Ignore the '#' (checked either by caller or previous loop iteration)
+
+ Rgba &color = options.palSpec.back()[nbColors];
+ 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);
+ 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);
+ break;
+ case 0:
+ parseError(n - 1, 1, "Missing color after '#'");
+ return;
+ default:
+ parseError(n, pos - n, "Unknown color specification");
+ return;
+ }
+ n = pos;
+
+ // Skip whitespace, if any
+ skipWhitespace(n);
+
+ // Skip comma/colon, or end
+ if (n == arg.length()) {
+ break;
+ }
+ switch (arg[n]) {
+ case ',':
+ ++n; // Skip it
+
+ ++nbColors;
+
+ // A trailing comma may be followed by a colon
+ skipWhitespace(n);
+ if (n == arg.length()) {
+ break;
+ } else if (arg[n] != ':') {
+ if (nbColors == 4) {
+ parseError(n, 1, "Each palette can only contain up to 4 colors");
+ return;
+ }
+ break;
+ }
+ [[fallthrough]];
+
+ case ':':
+ ++n;
+ skipWhitespace(n);
+
+ nbColors = 0; // Start a new palette
+ // Avoid creating a spurious empty palette
+ if (n != arg.length()) {
+ options.palSpec.emplace_back();
+ }
+ break;
+
+ default:
+ parseError(n, 1, "Unexpected character, expected ',', ':', or end of argument");
+ return;
+ }
+
+ // Check again to allow trailing a comma/colon
+ if (n == arg.length()) {
+ break;
+ }
+ if (arg[n] != '#') {
+ parseError(n, 1, "Unexpected character, expected '#'");
+ return;
+ }
+ }
+}