modificats els metodes de ordenació de paleta: luminosidad per optimal i spectrum per referece
This commit is contained in:
@@ -83,9 +83,9 @@ categories:
|
|||||||
- keyword: PALETTE
|
- keyword: PALETTE
|
||||||
handler: cmd_palette
|
handler: cmd_palette
|
||||||
description: "Color palette (F5/F6)"
|
description: "Color palette (F5/F6)"
|
||||||
usage: "PALETTE [NEXT|PREV|SORT [ORIGINAL|LUMINANCE|SPECTRUM]|DEFAULT|<name>]"
|
usage: "PALETTE [NEXT|PREV|SORT [ORIGINAL|OPTIMAL|REFERENCE]|DEFAULT|<name>]"
|
||||||
completions:
|
completions:
|
||||||
PALETTE SORT: [ORIGINAL, LUMINANCE, SPECTRUM]
|
PALETTE SORT: [ORIGINAL, OPTIMAL, REFERENCE]
|
||||||
dynamic_completions: true
|
dynamic_completions: true
|
||||||
|
|
||||||
- name: AUDIO
|
- name: AUDIO
|
||||||
|
|||||||
@@ -96,8 +96,8 @@ platforms:
|
|||||||
easing: cubicInOut
|
easing: cubicInOut
|
||||||
frame: 0
|
frame: 0
|
||||||
path:
|
path:
|
||||||
- {x: 5, y: 18, wait: 2}
|
- {x: 5, y: 18, wait: 1}
|
||||||
- {x: 20, y: 18, wait: 2}
|
- {x: 20, y: 18, wait: 1}
|
||||||
- {x: 20, y: 13, wait: 2}
|
- {x: 20, y: 13, wait: 1}
|
||||||
- {x: 5, y: 13, wait: 2}
|
- {x: 5, y: 13, wait: 1}
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cctype>
|
#include <cctype>
|
||||||
#include <cmath>
|
#include <climits>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
@@ -16,23 +16,23 @@
|
|||||||
|
|
||||||
auto sortModeFromString(const std::string& str) -> PaletteSortMode {
|
auto sortModeFromString(const std::string& str) -> PaletteSortMode {
|
||||||
const std::string LOWER = toLower(str);
|
const std::string LOWER = toLower(str);
|
||||||
if (LOWER == "luminance") { return PaletteSortMode::LUMINANCE; }
|
if (LOWER == "optimal") { return PaletteSortMode::OPTIMAL; }
|
||||||
if (LOWER == "spectrum") { return PaletteSortMode::SPECTRUM; }
|
if (LOWER == "reference") { return PaletteSortMode::REFERENCE; }
|
||||||
return PaletteSortMode::ORIGINAL;
|
return PaletteSortMode::ORIGINAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto sortModeToString(PaletteSortMode mode) -> std::string {
|
auto sortModeToString(PaletteSortMode mode) -> std::string {
|
||||||
switch (mode) {
|
switch (mode) {
|
||||||
case PaletteSortMode::LUMINANCE:
|
case PaletteSortMode::OPTIMAL:
|
||||||
return "luminance";
|
return "optimal";
|
||||||
case PaletteSortMode::SPECTRUM:
|
case PaletteSortMode::REFERENCE:
|
||||||
return "spectrum";
|
return "reference";
|
||||||
default:
|
default:
|
||||||
return "original";
|
return "original";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Paleta de referencia ZX Spectrum (16 colores ARGB) ───────────────────────
|
// ── Helpers de color y ordenación de paletas ─────────────────────────────────
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
// Helpers para extraer componentes RGB de un color ARGB (0xAARRGGBB)
|
// Helpers para extraer componentes RGB de un color ARGB (0xAARRGGBB)
|
||||||
@@ -40,35 +40,6 @@ namespace {
|
|||||||
constexpr auto greenOf(Uint32 c) -> int { return static_cast<int>((c >> 8) & 0xFF); }
|
constexpr auto greenOf(Uint32 c) -> int { return static_cast<int>((c >> 8) & 0xFF); }
|
||||||
constexpr auto blueOf(Uint32 c) -> int { return static_cast<int>(c & 0xFF); }
|
constexpr auto blueOf(Uint32 c) -> int { return static_cast<int>(c & 0xFF); }
|
||||||
|
|
||||||
constexpr auto makeARGB(int r, int g, int b) -> Uint32 {
|
|
||||||
return (0xFFU << 24) | (static_cast<Uint32>(r) << 16) | (static_cast<Uint32>(g) << 8) | static_cast<Uint32>(b);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Paleta ZX Spectrum de referencia (misma que en tools/sort_palette/sort_palette.py)
|
|
||||||
constexpr std::array<Uint32, 16> SPECTRUM_REFERENCE = {
|
|
||||||
makeARGB(0, 0, 0),
|
|
||||||
makeARGB(0, 0, 0),
|
|
||||||
makeARGB(0, 0, 216),
|
|
||||||
makeARGB(0, 0, 255),
|
|
||||||
makeARGB(216, 0, 0),
|
|
||||||
makeARGB(255, 0, 0),
|
|
||||||
makeARGB(216, 0, 216),
|
|
||||||
makeARGB(255, 0, 255),
|
|
||||||
makeARGB(0, 216, 0),
|
|
||||||
makeARGB(0, 255, 0),
|
|
||||||
makeARGB(0, 216, 216),
|
|
||||||
makeARGB(0, 255, 255),
|
|
||||||
makeARGB(216, 216, 0),
|
|
||||||
makeARGB(255, 255, 0),
|
|
||||||
makeARGB(216, 216, 216),
|
|
||||||
makeARGB(255, 255, 255),
|
|
||||||
};
|
|
||||||
|
|
||||||
// Luminancia percibida (ITU-R BT.709)
|
|
||||||
auto luminance(Uint32 color) -> double {
|
|
||||||
return (0.2126 * redOf(color)) + (0.7152 * greenOf(color)) + (0.0722 * blueOf(color));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Distancia euclídea al cuadrado en espacio RGB (no necesita sqrt para comparar)
|
// Distancia euclídea al cuadrado en espacio RGB (no necesita sqrt para comparar)
|
||||||
auto rgbDistanceSq(Uint32 a, Uint32 b) -> int {
|
auto rgbDistanceSq(Uint32 a, Uint32 b) -> int {
|
||||||
const int DR = redOf(a) - redOf(b);
|
const int DR = redOf(a) - redOf(b);
|
||||||
@@ -87,31 +58,118 @@ namespace {
|
|||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ordenar por luminancia
|
// Asignación óptima de colores mediante el algoritmo húngaro (Kuhn-Munkres).
|
||||||
auto sortByLuminance(const Palette& palette) -> Palette {
|
// Minimiza la distancia RGB total entre la paleta y la referencia.
|
||||||
const size_t N = countActiveColors(palette);
|
// O(N³) con N = número de colores activos — para N ≤ 256 es instantáneo.
|
||||||
std::vector<Uint32> colors(palette.begin(), palette.begin() + static_cast<ptrdiff_t>(N));
|
// NOLINTNEXTLINE(readability-function-cognitive-complexity)
|
||||||
std::ranges::sort(colors, [](Uint32 a, Uint32 b) {
|
auto sortByOptimal(const Palette& palette, const Palette& reference) -> Palette {
|
||||||
return luminance(a) < luminance(b);
|
const auto N = static_cast<int>(countActiveColors(palette));
|
||||||
});
|
const auto M = static_cast<int>(countActiveColors(reference));
|
||||||
|
const int SZ = std::max(N, M);
|
||||||
|
if (SZ == 0) { return palette; }
|
||||||
|
|
||||||
Palette result{};
|
// Matriz de coste NxM (ampliada a SZxSZ con ceros para hacerla cuadrada)
|
||||||
result.fill(0);
|
std::vector<int> cost(SZ * SZ, 0);
|
||||||
std::ranges::copy(colors, result.begin());
|
for (int i = 0; i < N; ++i) {
|
||||||
return result;
|
for (int j = 0; j < M; ++j) {
|
||||||
|
cost[(i * SZ) + j] = rgbDistanceSq(palette[i], reference[j]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hungarian algorithm (Kuhn-Munkres) — versión basada en potenciales
|
||||||
|
constexpr int INF = INT_MAX / 2;
|
||||||
|
std::vector<int> u(SZ + 1, 0); // Potenciales de filas
|
||||||
|
std::vector<int> v(SZ + 1, 0); // Potenciales de columnas
|
||||||
|
std::vector<int> p(SZ + 1, 0); // p[j] = fila asignada a columna j
|
||||||
|
std::vector<int> way(SZ + 1, 0);
|
||||||
|
|
||||||
|
for (int i = 1; i <= SZ; ++i) {
|
||||||
|
p[0] = i;
|
||||||
|
int j0 = 0;
|
||||||
|
std::vector<int> minv(SZ + 1, INF);
|
||||||
|
std::vector<bool> used(SZ + 1, false);
|
||||||
|
|
||||||
|
do {
|
||||||
|
used[j0] = true;
|
||||||
|
int i0 = p[j0];
|
||||||
|
int delta = INF;
|
||||||
|
int j1 = 0;
|
||||||
|
|
||||||
|
for (int j = 1; j <= SZ; ++j) {
|
||||||
|
if (!used[j]) {
|
||||||
|
int cur = cost[((i0 - 1) * SZ) + (j - 1)] - u[i0] - v[j];
|
||||||
|
if (cur < minv[j]) {
|
||||||
|
minv[j] = cur;
|
||||||
|
way[j] = j0;
|
||||||
|
}
|
||||||
|
if (minv[j] < delta) {
|
||||||
|
delta = minv[j];
|
||||||
|
j1 = j;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int j = 0; j <= SZ; ++j) {
|
||||||
|
if (used[j]) {
|
||||||
|
u[p[j]] += delta;
|
||||||
|
v[j] -= delta;
|
||||||
|
} else {
|
||||||
|
minv[j] -= delta;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
j0 = j1;
|
||||||
|
} while (p[j0] != 0);
|
||||||
|
|
||||||
|
do {
|
||||||
|
int j1 = way[j0];
|
||||||
|
p[j0] = p[j1];
|
||||||
|
j0 = j1;
|
||||||
|
} while (j0 != 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Construir la paleta resultante: assignment[j] = fila asignada a columna j
|
||||||
|
// Queremos result[j] = palette[fila asignada a j]
|
||||||
|
Palette out{};
|
||||||
|
out.fill(0);
|
||||||
|
for (int j = 1; j <= M && j <= N; ++j) {
|
||||||
|
int row = p[j] - 1; // Índice 0-based en palette
|
||||||
|
if (row >= 0 && row < N) {
|
||||||
|
out[j - 1] = palette[row];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Colores extra de palette que no tienen pareja en reference (N > M)
|
||||||
|
if (N > M) {
|
||||||
|
std::vector<bool> used_rows(N, false);
|
||||||
|
for (int j = 1; j <= M; ++j) {
|
||||||
|
int row = p[j] - 1;
|
||||||
|
if (row >= 0 && row < N) { used_rows[row] = true; }
|
||||||
|
}
|
||||||
|
int out_idx = M;
|
||||||
|
for (int i = 0; i < N; ++i) {
|
||||||
|
if (!used_rows[i]) {
|
||||||
|
out[out_idx++] = palette[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ordenar por similitud con la paleta ZX Spectrum (greedy matching)
|
// Asignación greedy de colores a la paleta de referencia.
|
||||||
auto sortBySpectrum(const Palette& palette) -> Palette {
|
// Más rápida pero puede asignar colores subóptimamente.
|
||||||
|
auto sortByReference(const Palette& palette, const Palette& reference) -> Palette {
|
||||||
|
const size_t REF_COUNT = countActiveColors(reference);
|
||||||
const size_t N = countActiveColors(palette);
|
const size_t N = countActiveColors(palette);
|
||||||
std::vector<Uint32> available(palette.begin(), palette.begin() + static_cast<ptrdiff_t>(N));
|
std::vector<Uint32> available(palette.begin(), palette.begin() + static_cast<ptrdiff_t>(N));
|
||||||
std::vector<Uint32> result;
|
std::vector<Uint32> result;
|
||||||
result.reserve(N);
|
result.reserve(N);
|
||||||
|
|
||||||
// Para cada color de referencia del Spectrum, buscar el más cercano disponible
|
// Para cada color de referencia, buscar el más cercano disponible
|
||||||
const size_t REFS = std::min(N, SPECTRUM_REFERENCE.size());
|
const size_t REFS = std::min(N, REF_COUNT);
|
||||||
for (size_t i = 0; i < REFS && !available.empty(); ++i) {
|
for (size_t i = 0; i < REFS && !available.empty(); ++i) {
|
||||||
const Uint32 REF = SPECTRUM_REFERENCE[i];
|
const Uint32 REF = reference[i];
|
||||||
auto best = std::ranges::min_element(available, [REF](Uint32 a, Uint32 b) {
|
auto best = std::ranges::min_element(available, [REF](Uint32 a, Uint32 b) {
|
||||||
return rgbDistanceSq(a, REF) < rgbDistanceSq(b, REF);
|
return rgbDistanceSq(a, REF) < rgbDistanceSq(b, REF);
|
||||||
});
|
});
|
||||||
@@ -147,9 +205,18 @@ PaletteManager::PaletteManager(
|
|||||||
on_change_(std::move(on_change)) {
|
on_change_(std::move(on_change)) {
|
||||||
current_ = findIndex(initial_name);
|
current_ = findIndex(initial_name);
|
||||||
|
|
||||||
// Leer y aplicar paleta inicial directamente desde el archivo
|
// Leer la paleta de referencia directamente desde el archivo
|
||||||
// (Resource::Cache aún no está disponible en este punto del ciclo de vida)
|
// (Resource::Cache aún no está disponible en este punto del ciclo de vida)
|
||||||
const auto INITIAL_PALETTE = sortPalette(readPalFile(palettes_.at(current_)), sort_mode_);
|
const std::string REF_NAME = std::string(Defaults::Video::PALETTE_NAME) + ".pal";
|
||||||
|
for (const auto& p : palettes_) {
|
||||||
|
if (getFileName(p) == REF_NAME) {
|
||||||
|
reference_palette_ = readPalFile(p);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Leer y aplicar paleta inicial
|
||||||
|
const auto INITIAL_PALETTE = sortPalette(readPalFile(palettes_.at(current_)), sort_mode_, reference_palette_);
|
||||||
game_surface_->setPalette(INITIAL_PALETTE);
|
game_surface_->setPalette(INITIAL_PALETTE);
|
||||||
border_surface_->setPalette(INITIAL_PALETTE);
|
border_surface_->setPalette(INITIAL_PALETTE);
|
||||||
|
|
||||||
@@ -230,7 +297,7 @@ auto PaletteManager::getSortModeName() const -> std::string {
|
|||||||
|
|
||||||
void PaletteManager::apply() {
|
void PaletteManager::apply() {
|
||||||
Palette raw = Resource::Cache::get()->getPalette(palettes_.at(current_));
|
Palette raw = Resource::Cache::get()->getPalette(palettes_.at(current_));
|
||||||
Palette sorted = sortPalette(raw, sort_mode_);
|
Palette sorted = sortPalette(raw, sort_mode_, reference_palette_);
|
||||||
game_surface_->loadPalette(sorted);
|
game_surface_->loadPalette(sorted);
|
||||||
border_surface_->loadPalette(sorted);
|
border_surface_->loadPalette(sorted);
|
||||||
|
|
||||||
@@ -264,12 +331,12 @@ void PaletteManager::processPathList() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto PaletteManager::sortPalette(const Palette& palette, PaletteSortMode mode) -> Palette {
|
auto PaletteManager::sortPalette(const Palette& palette, PaletteSortMode mode, const Palette& reference) -> Palette {
|
||||||
switch (mode) {
|
switch (mode) {
|
||||||
case PaletteSortMode::LUMINANCE:
|
case PaletteSortMode::OPTIMAL:
|
||||||
return sortByLuminance(palette);
|
return sortByOptimal(palette, reference);
|
||||||
case PaletteSortMode::SPECTRUM:
|
case PaletteSortMode::REFERENCE:
|
||||||
return sortBySpectrum(palette);
|
return sortByReference(palette, reference);
|
||||||
default:
|
default:
|
||||||
return palette;
|
return palette;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,9 +15,9 @@ class Surface;
|
|||||||
|
|
||||||
// Modo de ordenación de paletas
|
// Modo de ordenación de paletas
|
||||||
enum class PaletteSortMode : int {
|
enum class PaletteSortMode : int {
|
||||||
ORIGINAL = 0, // Paleta tal cual viene del fichero
|
ORIGINAL = 0, // Paleta tal cual viene del fichero
|
||||||
LUMINANCE = 1, // Ordenada por luminancia percibida
|
OPTIMAL = 1, // Asignación óptima a la paleta por defecto (Hungarian algorithm)
|
||||||
SPECTRUM = 2, // Reordenada para imitar la paleta ZX Spectrum
|
REFERENCE = 2, // Asignación greedy a la paleta por defecto
|
||||||
COUNT = 3
|
COUNT = 3
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -53,9 +53,10 @@ class PaletteManager {
|
|||||||
void apply(); // Aplica la paleta actual a ambas surfaces
|
void apply(); // Aplica la paleta actual a ambas surfaces
|
||||||
[[nodiscard]] auto findIndex(const std::string& name) const -> size_t; // Localiza paleta por nombre en el vector
|
[[nodiscard]] auto findIndex(const std::string& name) const -> size_t; // Localiza paleta por nombre en el vector
|
||||||
void processPathList(); // Extrae nombres de archivo de las rutas completas
|
void processPathList(); // Extrae nombres de archivo de las rutas completas
|
||||||
static auto sortPalette(const Palette& palette, PaletteSortMode mode) -> Palette; // Reordena una paleta según el modo
|
static auto sortPalette(const Palette& palette, PaletteSortMode mode, const Palette& reference) -> Palette;
|
||||||
|
|
||||||
std::vector<std::string> palettes_;
|
std::vector<std::string> palettes_;
|
||||||
|
Palette reference_palette_{}; // Paleta de referencia para el modo REFERENCE
|
||||||
size_t current_{0};
|
size_t current_{0};
|
||||||
PaletteSortMode sort_mode_{PaletteSortMode::ORIGINAL};
|
PaletteSortMode sort_mode_{PaletteSortMode::ORIGINAL};
|
||||||
std::shared_ptr<Surface> game_surface_;
|
std::shared_ptr<Surface> game_surface_;
|
||||||
|
|||||||
@@ -104,7 +104,7 @@ namespace Options {
|
|||||||
bool integer_scale{Defaults::Video::INTEGER_SCALE}; // Indica si el escalado de la imagen ha de ser entero en el modo a pantalla completa
|
bool integer_scale{Defaults::Video::INTEGER_SCALE}; // Indica si el escalado de la imagen ha de ser entero en el modo a pantalla completa
|
||||||
bool keep_aspect{Defaults::Video::KEEP_ASPECT}; // Indica si se ha de mantener la relación de aspecto al poner el modo a pantalla completa
|
bool keep_aspect{Defaults::Video::KEEP_ASPECT}; // Indica si se ha de mantener la relación de aspecto al poner el modo a pantalla completa
|
||||||
std::string palette{Defaults::Video::PALETTE_NAME}; // Paleta de colores a usar en el juego
|
std::string palette{Defaults::Video::PALETTE_NAME}; // Paleta de colores a usar en el juego
|
||||||
std::string palette_sort{Defaults::Video::PALETTE_SORT}; // Modo de ordenación de la paleta (original/luminance/spectrum)
|
std::string palette_sort{Defaults::Video::PALETTE_SORT}; // Modo de ordenación de la paleta (original/optimal/reference)
|
||||||
std::string info; // Información sobre el modo de vídeo
|
std::string info; // Información sobre el modo de vídeo
|
||||||
Border border{}; // Borde de la pantalla
|
Border border{}; // Borde de la pantalla
|
||||||
GPU gpu{}; // Opciones de aceleración GPU
|
GPU gpu{}; // Opciones de aceleración GPU
|
||||||
|
|||||||
@@ -340,7 +340,7 @@ static auto cmdPalette(const std::vector<std::string>& args) -> std::string {
|
|||||||
const auto PAL_NAME = []() -> std::string {
|
const auto PAL_NAME = []() -> std::string {
|
||||||
return Screen::get()->getPalettePrettyName();
|
return Screen::get()->getPalettePrettyName();
|
||||||
};
|
};
|
||||||
if (args.empty()) { return "usage: palette [next|prev|sort [original|luminance|spectrum]|default|<name>]"; }
|
if (args.empty()) { return "usage: palette [next|prev|sort [original|optimal|reference]|default|<name>]"; }
|
||||||
if (args[0] == "NEXT") {
|
if (args[0] == "NEXT") {
|
||||||
Screen::get()->nextPalette();
|
Screen::get()->nextPalette();
|
||||||
return "Palette: " + PAL_NAME();
|
return "Palette: " + PAL_NAME();
|
||||||
@@ -360,12 +360,12 @@ static auto cmdPalette(const std::vector<std::string>& args) -> std::string {
|
|||||||
}
|
}
|
||||||
if (args[1] == "ORIGINAL") {
|
if (args[1] == "ORIGINAL") {
|
||||||
Screen::get()->setPaletteSortMode(PaletteSortMode::ORIGINAL);
|
Screen::get()->setPaletteSortMode(PaletteSortMode::ORIGINAL);
|
||||||
} else if (args[1] == "LUMINANCE") {
|
} else if (args[1] == "OPTIMAL") {
|
||||||
Screen::get()->setPaletteSortMode(PaletteSortMode::LUMINANCE);
|
Screen::get()->setPaletteSortMode(PaletteSortMode::OPTIMAL);
|
||||||
} else if (args[1] == "SPECTRUM") {
|
} else if (args[1] == "REFERENCE") {
|
||||||
Screen::get()->setPaletteSortMode(PaletteSortMode::SPECTRUM);
|
Screen::get()->setPaletteSortMode(PaletteSortMode::REFERENCE);
|
||||||
} else {
|
} else {
|
||||||
return "Unknown sort mode. Use: original, luminance, spectrum";
|
return "Unknown sort mode. Use: original, optimal, reference";
|
||||||
}
|
}
|
||||||
return "Palette sort: " + Screen::get()->getPaletteSortModeName();
|
return "Palette sort: " + Screen::get()->getPaletteSortModeName();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user