- restaurades les paletes amb la ordenacio original

- afegida opció de reordenar les paletes automaticament per luminositat o paregut a la paleta d'spectrum
This commit is contained in:
2026-04-02 07:59:30 +02:00
parent 3bd13b72cd
commit 015a9cc4e1
32 changed files with 595 additions and 354 deletions

View File

@@ -80,7 +80,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|<name>]" usage: "PALETTE [NEXT|PREV|SORT [ORIGINAL|LUMINANCE|SPECTRUM]|DEFAULT|<name>]"
completions:
PALETTE SORT: [ORIGINAL, LUMINANCE, SPECTRUM]
dynamic_completions: true dynamic_completions: true
- name: AUDIO - name: AUDIO

View File

@@ -116,6 +116,7 @@ ui:
supersampling_enabled: "SUPERMOSTREIG ACTIVAT" supersampling_enabled: "SUPERMOSTREIG ACTIVAT"
supersampling_disabled: "SUPERMOSTREIG DESACTIVAT" supersampling_disabled: "SUPERMOSTREIG DESACTIVAT"
palette: "PALETA" palette: "PALETA"
palette_sort: "ORDENACIÓ PALETA"
integer_scale_enabled: "ESCALAT SENCER ACTIVAT" integer_scale_enabled: "ESCALAT SENCER ACTIVAT"
integer_scale_disabled: "ESCALAT SENCER DESACTIVAT" integer_scale_disabled: "ESCALAT SENCER DESACTIVAT"
vsync_enabled: "V-SYNC ACTIVAT" vsync_enabled: "V-SYNC ACTIVAT"

View File

@@ -116,6 +116,7 @@ ui:
supersampling_enabled: "SUPERSAMPLING ON" supersampling_enabled: "SUPERSAMPLING ON"
supersampling_disabled: "SUPERSAMPLING OFF" supersampling_disabled: "SUPERSAMPLING OFF"
palette: "PALETTE" palette: "PALETTE"
palette_sort: "PALETTE SORT"
integer_scale_enabled: "INTEGER SCALE ENABLED" integer_scale_enabled: "INTEGER SCALE ENABLED"
integer_scale_disabled: "INTEGER SCALE DISABLED" integer_scale_disabled: "INTEGER SCALE DISABLED"
vsync_enabled: "V-SYNC ENABLED" vsync_enabled: "V-SYNC ENABLED"

View File

@@ -1,19 +1,19 @@
JASC-PAL JASC-PAL
0100 0100
16 16
67 0 103
22 23 26 22 23 26
127 6 34 127 6 34
0 40 89
148 33 106
35 73 117
214 36 17 214 36 17
255 38 116
0 120 153
255 132 38 255 132 38
255 128 164
104 174 212
16 210 117
255 209 0 255 209 0
191 255 60
250 253 255 250 253 255
255 128 164
255 38 116
148 33 106
67 0 103
35 73 117
104 174 212
191 255 60
16 210 117
0 120 153
0 40 89

View File

@@ -1,19 +1,19 @@
JASC-PAL JASC-PAL
0100 0100
16 16
31 32 37
46 51 77
76 82 116
124 143 178
128 77 83
191 125 133
114 51 76
192 165 169
82 103 93
108 154 154
108 154 154
226 217 228 226 217 228
108 154 154
82 103 93
55 64 59
243 200 147 243 200 147
229 152 125 229 152 125
203 94 92
114 51 76
192 165 169 192 165 169
226 217 228 191 125 133
128 77 83
64 48 56
124 143 178
76 82 116
46 51 77
31 32 37

View File

@@ -1,19 +1,19 @@
JASC-PAL JASC-PAL
0100 0100
16 16
255 255 255
109 247 193
17 173 193
96 108 129
57 52 87 57 52 87
30 136 117
91 179 97
161 229 90
247 228 118
249 146 82
203 77 104
106 55 113 106 55 113
201 36 100 201 36 100
203 77 104
96 108 129
30 136 117
17 173 193
155 156 130
91 179 97
249 146 82
244 140 182 244 140 182
247 182 158 247 182 158
161 229 90 155 156 130
109 247 193
247 228 118
255 255 255

View File

@@ -1,19 +1,19 @@
JASC-PAL JASC-PAL
0100 0100
16 16
75 61 68
77 69 57
87 72 82
121 68 74
75 114 110
174 93 64
119 116 59
146 116 65
132 120 117
199 123 88
186 145 88
171 155 142
179 165 85
140 171 161
209 177 135 209 177 135
199 123 88
174 93 64
121 68 74
75 61 68
186 145 88
146 116 65
77 69 57
119 116 59
179 165 85
210 201 165 210 201 165
140 171 161
75 114 110
87 72 82
132 120 117
171 155 142

View File

@@ -1,19 +1,19 @@
JASC-PAL JASC-PAL
0100 0100
16 16
31 14 28
62 33 55
23 67 75
157 48 59
112 55 127
88 69 99
154 99 72
100 125 52
52 133 157
210 100 113
140 143 174 140 143 174
228 148 58 88 69 99
62 33 55
154 99 72
215 155 125 215 155 125
126 196 193
192 199 65
245 237 186 245 237 186
192 199 65
100 125 52
228 148 58
157 48 59
210 100 113
112 55 127
126 196 193
52 133 157
23 67 75
31 14 28

View File

@@ -3,17 +3,17 @@ JASC-PAL
16 16
0 0 0 0 0 0
29 43 83 29 43 83
126 37 83
0 135 81
171 82 54
95 87 79
194 195 199
255 241 232
255 0 77
255 163 0
255 236 39
0 228 54
41 173 255 41 173 255
131 118 156 131 118 156
255 0 77
171 82 54
255 119 168 255 119 168
194 195 199
0 228 54
0 135 81
95 87 79
255 241 232
255 236 39
255 163 0
255 204 170 255 204 170
126 37 83

View File

@@ -2,18 +2,18 @@ JASC-PAL
0100 0100
16 16
17 17 37 17 17 37
82 75 109
176 201 196
255 252 241
36 34 114 36 34 114
52 112 190 52 112 190
150 58 191
255 94 57
159 32 98 159 32 98
255 94 57
150 58 191
255 105 246 255 105 246
176 201 196
44 126 75 44 126 75
160 195 95 160 195 95
67 152 196 67 152 196
147 255 229 147 255 229
210 133 55 210 133 55
254 245 107 254 245 107
255 252 241
82 75 109

View File

@@ -2,18 +2,18 @@ JASC-PAL
0100 0100
16 16
15 11 56 15 11 56
122 87 22 97 106 130
173 180 183
249 255 236
40 19 160 40 19 160
74 107 255 74 107 255
160 35 17 160 35 17
237 23 95 237 23 95
238 20 181
115 16 147 115 16 147
238 20 181
39 139 97 39 139 97
157 255 38 157 255 38
71 233 223
27 105 167 27 105 167
71 233 223
122 87 22
247 229 77 247 229 77
173 180 183
249 255 236
97 106 130

View File

@@ -3,17 +3,17 @@ JASC-PAL
16 16
0 3 60 0 3 60
0 82 96 0 82 96
0 56 132
42 46 121
255 0 78
177 5 133
172 41 206
255 92 255
10 255 82
0 157 74 0 157 74
0 247 255 10 255 82
0 56 132
0 138 197 0 138 197
78 110 168 0 247 255
255 255 255 255 92 255
173 212 250 172 41 206
96 0 136 96 0 136
177 5 133
255 0 78
42 46 121
78 110 168
173 212 250
255 255 255

View File

@@ -1,19 +1,19 @@
JASC-PAL JASC-PAL
0100 0100
16 16
33 59 37
58 96 74
79 119 84
161 159 124
119 116 79
119 92 79
96 59 58
59 33 55
23 14 25 23 14 25
47 33 59 47 33 59
59 33 55
33 59 37
67 58 96 67 58 96
96 59 58
79 82 119 79 82 119
58 96 74
119 92 79
79 119 84
101 115 140 101 115 140
119 116 79
124 148 161 124 148 161
161 159 124
160 185 186 160 185 186
192 209 204 192 209 204

View File

@@ -2,18 +2,18 @@ JASC-PAL
0100 0100
16 16
26 28 44 26 28 44
51 60 87 93 39 93
59 93 201
41 54 111
177 62 83 177 62 83
239 125 87 239 125 87
93 39 93
148 176 194
56 183 100
37 113 121
65 166 246
115 239 247
255 205 117 255 205 117
167 240 112 167 240 112
56 183 100
37 113 121
41 54 111
59 93 201
65 166 246
115 239 247
244 244 244 244 244 244
148 176 194
86 108 134 86 108 134
51 60 87

View File

@@ -3,17 +3,17 @@ JASC-PAL
16 16
0 0 0 0 0 0
50 1 50 50 1 50
84 1 103
118 50 118
50 50 171 50 50 171
35 103 239 35 103 239
254 1 69
119 70 2
118 50 118
239 152 152
1 137 84
1 186 152
152 186 220 152 186 220
253 253 253 253 253 253
1 186 152
1 137 84
254 239 69 254 239 69
119 70 2
186 118 84 186 118 84
254 1 69
239 152 152
254 205 205 254 205 205
84 1 103

View File

@@ -127,6 +127,11 @@ namespace GlobalInputs {
Notifier::get()->show({Locale::get()->get("ui.palette") + " " + toUpper(Screen::get()->getPalettePrettyName())}); // NOLINT(readability-static-accessed-through-instance) Notifier::get()->show({Locale::get()->get("ui.palette") + " " + toUpper(Screen::get()->getPalettePrettyName())}); // NOLINT(readability-static-accessed-through-instance)
} }
void handleNextPaletteSortMode() {
Screen::get()->nextPaletteSortMode();
Notifier::get()->show({Locale::get()->get("ui.palette_sort") + " " + toUpper(Screen::get()->getPaletteSortModeName())}); // NOLINT(readability-static-accessed-through-instance)
}
void handleToggleIntegerScale() { void handleToggleIntegerScale() {
Screen::get()->toggleIntegerScale(); Screen::get()->toggleIntegerScale();
Screen::get()->setVideoMode(Options::video.fullscreen); Screen::get()->setVideoMode(Options::video.fullscreen);
@@ -172,10 +177,13 @@ namespace GlobalInputs {
} }
} }
if (Input::get()->checkAction(InputAction::NEXT_PALETTE, Input::DO_NOT_ALLOW_REPEAT)) { if (Input::get()->checkAction(InputAction::NEXT_PALETTE, Input::DO_NOT_ALLOW_REPEAT)) {
return InputAction::NEXT_PALETTE; if ((SDL_GetModState() & SDL_KMOD_CTRL) != 0U) {
return InputAction::PREVIOUS_PALETTE; // Ctrl+F5
}
return InputAction::NEXT_PALETTE; // F5
} }
if (Input::get()->checkAction(InputAction::PREVIOUS_PALETTE, Input::DO_NOT_ALLOW_REPEAT)) { if (Input::get()->checkAction(InputAction::NEXT_PALETTE_SORT, Input::DO_NOT_ALLOW_REPEAT)) {
return InputAction::PREVIOUS_PALETTE; return InputAction::NEXT_PALETTE_SORT; // F6
} }
if (Input::get()->checkAction(InputAction::TOGGLE_INTEGER_SCALE, Input::DO_NOT_ALLOW_REPEAT)) { if (Input::get()->checkAction(InputAction::TOGGLE_INTEGER_SCALE, Input::DO_NOT_ALLOW_REPEAT)) {
return InputAction::TOGGLE_INTEGER_SCALE; return InputAction::TOGGLE_INTEGER_SCALE;
@@ -273,6 +281,10 @@ namespace GlobalInputs {
handlePreviousPalette(); handlePreviousPalette();
break; break;
case InputAction::NEXT_PALETTE_SORT:
handleNextPaletteSortMode();
break;
case InputAction::TOGGLE_INTEGER_SCALE: case InputAction::TOGGLE_INTEGER_SCALE:
handleToggleIntegerScale(); handleToggleIntegerScale();
break; break;

View File

@@ -45,7 +45,7 @@ Input::Input(std::string game_controller_db_path)
{Action::TOGGLE_FULLSCREEN, KeyState{.scancode = SDL_SCANCODE_F3}}, {Action::TOGGLE_FULLSCREEN, KeyState{.scancode = SDL_SCANCODE_F3}},
{Action::TOGGLE_SHADER, KeyState{.scancode = SDL_SCANCODE_F4}}, {Action::TOGGLE_SHADER, KeyState{.scancode = SDL_SCANCODE_F4}},
{Action::NEXT_PALETTE, KeyState{.scancode = SDL_SCANCODE_F5}}, {Action::NEXT_PALETTE, KeyState{.scancode = SDL_SCANCODE_F5}},
{Action::PREVIOUS_PALETTE, KeyState{.scancode = SDL_SCANCODE_F6}}, {Action::NEXT_PALETTE_SORT, KeyState{.scancode = SDL_SCANCODE_F6}},
{Action::TOGGLE_INTEGER_SCALE, KeyState{.scancode = SDL_SCANCODE_F7}}, {Action::TOGGLE_INTEGER_SCALE, KeyState{.scancode = SDL_SCANCODE_F7}},
{Action::TOGGLE_IN_GAME_MUSIC, KeyState{.scancode = SDL_SCANCODE_F8}}, {Action::TOGGLE_IN_GAME_MUSIC, KeyState{.scancode = SDL_SCANCODE_F8}},
{Action::TOGGLE_BORDER, KeyState{.scancode = SDL_SCANCODE_F9}}, {Action::TOGGLE_BORDER, KeyState{.scancode = SDL_SCANCODE_F9}},

View File

@@ -20,6 +20,7 @@ const std::unordered_map<InputAction, std::string> ACTION_TO_STRING = {
{InputAction::TOGGLE_IN_GAME_MUSIC, "TOGGLE_MUSIC"}, {InputAction::TOGGLE_IN_GAME_MUSIC, "TOGGLE_MUSIC"},
{InputAction::NEXT_PALETTE, "NEXT_PALETTE"}, {InputAction::NEXT_PALETTE, "NEXT_PALETTE"},
{InputAction::PREVIOUS_PALETTE, "PREVIOUS_PALETTE"}, {InputAction::PREVIOUS_PALETTE, "PREVIOUS_PALETTE"},
{InputAction::NEXT_PALETTE_SORT, "NEXT_PALETTE_SORT"},
{InputAction::TOGGLE_SHADER, "TOGGLE_POSTFX"}, {InputAction::TOGGLE_SHADER, "TOGGLE_POSTFX"},
{InputAction::NEXT_SHADER_PRESET, "NEXT_POSTFX_PRESET"}, {InputAction::NEXT_SHADER_PRESET, "NEXT_POSTFX_PRESET"},
{InputAction::TOGGLE_INFO, "TOGGLE_DEBUG"}, {InputAction::TOGGLE_INFO, "TOGGLE_DEBUG"},
@@ -42,6 +43,7 @@ const std::unordered_map<std::string, InputAction> STRING_TO_ACTION = {
{"TOGGLE_MUSIC", InputAction::TOGGLE_IN_GAME_MUSIC}, {"TOGGLE_MUSIC", InputAction::TOGGLE_IN_GAME_MUSIC},
{"NEXT_PALETTE", InputAction::NEXT_PALETTE}, {"NEXT_PALETTE", InputAction::NEXT_PALETTE},
{"PREVIOUS_PALETTE", InputAction::PREVIOUS_PALETTE}, {"PREVIOUS_PALETTE", InputAction::PREVIOUS_PALETTE},
{"NEXT_PALETTE_SORT", InputAction::NEXT_PALETTE_SORT},
{"TOGGLE_POSTFX", InputAction::TOGGLE_SHADER}, {"TOGGLE_POSTFX", InputAction::TOGGLE_SHADER},
{"NEXT_POSTFX_PRESET", InputAction::NEXT_SHADER_PRESET}, {"NEXT_POSTFX_PRESET", InputAction::NEXT_SHADER_PRESET},
{"TOGGLE_DEBUG", InputAction::TOGGLE_INFO}, {"TOGGLE_DEBUG", InputAction::TOGGLE_INFO},

View File

@@ -31,6 +31,7 @@ enum class InputAction : int { // Acciones de entrada posibles en el juego
TOGGLE_IN_GAME_MUSIC, TOGGLE_IN_GAME_MUSIC,
NEXT_PALETTE, NEXT_PALETTE,
PREVIOUS_PALETTE, PREVIOUS_PALETTE,
NEXT_PALETTE_SORT,
TOGGLE_INFO, TOGGLE_INFO,
TOGGLE_CONSOLE, TOGGLE_CONSOLE,

View File

@@ -2,7 +2,9 @@
#include <algorithm> #include <algorithm>
#include <cctype> #include <cctype>
#include <cmath>
#include <string> #include <string>
#include <vector>
#include "core/rendering/surface.hpp" #include "core/rendering/surface.hpp"
#include "core/resources/resource_cache.hpp" #include "core/resources/resource_cache.hpp"
@@ -10,13 +12,136 @@
#include "game/options.hpp" #include "game/options.hpp"
#include "utils/utils.hpp" #include "utils/utils.hpp"
// ── Conversión string ↔ PaletteSortMode ──────────────────────────────────────
auto sortModeFromString(const std::string& str) -> PaletteSortMode {
const std::string lower = toLower(str);
if (lower == "luminance") { return PaletteSortMode::LUMINANCE; }
if (lower == "spectrum") { return PaletteSortMode::SPECTRUM; }
return PaletteSortMode::ORIGINAL;
}
auto sortModeToString(PaletteSortMode mode) -> std::string {
switch (mode) {
case PaletteSortMode::LUMINANCE:
return "luminance";
case PaletteSortMode::SPECTRUM:
return "spectrum";
default:
return "original";
}
}
// ── Paleta de referencia ZX Spectrum (16 colores ARGB) ───────────────────────
namespace {
// Helpers para extraer componentes RGB de un color ARGB (0xAARRGGBB)
constexpr auto redOf(Uint32 c) -> int { return static_cast<int>((c >> 16) & 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 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)
auto rgbDistanceSq(Uint32 a, Uint32 b) -> int {
const int dr = redOf(a) - redOf(b);
const int dg = greenOf(a) - greenOf(b);
const int db = blueOf(a) - blueOf(b);
return dr * dr + dg * dg + db * db;
}
// Cuenta los colores activos en la paleta (los que tienen alpha != 0)
auto countActiveColors(const Palette& palette) -> size_t {
size_t count = 0;
for (const auto& c : palette) {
if (c == 0) { break; }
++count;
}
return count;
}
// Ordenar por luminancia
auto sortByLuminance(const Palette& palette) -> Palette {
const size_t n = countActiveColors(palette);
std::vector<Uint32> colors(palette.begin(), palette.begin() + static_cast<ptrdiff_t>(n));
std::sort(colors.begin(), colors.end(), [](Uint32 a, Uint32 b) {
return luminance(a) < luminance(b);
});
Palette result{};
result.fill(0);
std::copy(colors.begin(), colors.end(), result.begin());
return result;
}
// Ordenar por similitud con la paleta ZX Spectrum (greedy matching)
auto sortBySpectrum(const Palette& palette) -> Palette {
const size_t n = countActiveColors(palette);
std::vector<Uint32> available(palette.begin(), palette.begin() + static_cast<ptrdiff_t>(n));
std::vector<Uint32> result;
result.reserve(n);
// Para cada color de referencia del Spectrum, buscar el más cercano disponible
const size_t refs = std::min(n, SPECTRUM_REFERENCE.size());
for (size_t i = 0; i < refs && !available.empty(); ++i) {
const Uint32 ref = SPECTRUM_REFERENCE[i];
auto best = std::min_element(available.begin(), available.end(), [ref](Uint32 a, Uint32 b) {
return rgbDistanceSq(a, ref) < rgbDistanceSq(b, ref);
});
result.push_back(*best);
available.erase(best);
}
// Si quedan colores sin asignar, añadirlos al final
for (const auto& c : available) {
result.push_back(c);
}
Palette out{};
out.fill(0);
std::copy(result.begin(), result.end(), out.begin());
return out;
}
} // namespace
// ── PaletteManager ───────────────────────────────────────────────────────────
PaletteManager::PaletteManager( PaletteManager::PaletteManager(
std::vector<std::string> raw_paths, std::vector<std::string> raw_paths,
const std::string& initial_name, const std::string& initial_name,
PaletteSortMode initial_sort_mode,
std::shared_ptr<Surface> game_surface, std::shared_ptr<Surface> game_surface,
std::shared_ptr<Surface> border_surface, std::shared_ptr<Surface> border_surface,
OnChangeCallback on_change) OnChangeCallback on_change)
: palettes_(std::move(raw_paths)), : palettes_(std::move(raw_paths)),
sort_mode_(initial_sort_mode),
game_surface_(std::move(game_surface)), game_surface_(std::move(game_surface)),
border_surface_(std::move(border_surface)), border_surface_(std::move(border_surface)),
on_change_(std::move(on_change)) { on_change_(std::move(on_change)) {
@@ -24,7 +149,7 @@ PaletteManager::PaletteManager(
// Leer y aplicar paleta inicial directamente desde el archivo // Leer y aplicar paleta inicial 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 = readPalFile(palettes_.at(current_)); const auto initial_palette = sortPalette(readPalFile(palettes_.at(current_)), sort_mode_);
game_surface_->setPalette(initial_palette); game_surface_->setPalette(initial_palette);
border_surface_->setPalette(initial_palette); border_surface_->setPalette(initial_palette);
@@ -83,9 +208,31 @@ auto PaletteManager::getPrettyName() const -> std::string {
return name; return name;
} }
void PaletteManager::nextSortMode() {
sort_mode_ = static_cast<PaletteSortMode>((static_cast<int>(sort_mode_) + 1) % static_cast<int>(PaletteSortMode::COUNT));
Options::video.palette_sort = sortModeToString(sort_mode_);
apply();
}
void PaletteManager::setSortMode(PaletteSortMode mode) {
sort_mode_ = mode;
Options::video.palette_sort = sortModeToString(sort_mode_);
apply();
}
auto PaletteManager::getSortMode() const -> PaletteSortMode {
return sort_mode_;
}
auto PaletteManager::getSortModeName() const -> std::string {
return sortModeToString(sort_mode_);
}
void PaletteManager::apply() { void PaletteManager::apply() {
game_surface_->loadPalette(Resource::Cache::get()->getPalette(palettes_.at(current_))); Palette raw = Resource::Cache::get()->getPalette(palettes_.at(current_));
border_surface_->loadPalette(Resource::Cache::get()->getPalette(palettes_.at(current_))); Palette sorted = sortPalette(raw, sort_mode_);
game_surface_->loadPalette(sorted);
border_surface_->loadPalette(sorted);
Options::video.palette = getCurrentName(); Options::video.palette = getCurrentName();
@@ -116,3 +263,14 @@ void PaletteManager::processPathList() {
palette = getFileName(palette); palette = getFileName(palette);
} }
} }
auto PaletteManager::sortPalette(const Palette& palette, PaletteSortMode mode) -> Palette {
switch (mode) {
case PaletteSortMode::LUMINANCE:
return sortByLuminance(palette);
case PaletteSortMode::SPECTRUM:
return sortBySpectrum(palette);
default:
return palette;
}
}

View File

@@ -1,12 +1,30 @@
#pragma once #pragma once
#include <SDL3/SDL.h>
#include <array>
#include <functional> #include <functional>
#include <memory> #include <memory>
#include <string> #include <string>
#include <vector> #include <vector>
// Alias de paleta (igual que en surface.hpp; evita incluir todo el header)
using Palette = std::array<Uint32, 256>;
class Surface; class Surface;
// Modo de ordenación de paletas
enum class PaletteSortMode : int {
ORIGINAL = 0, // Paleta tal cual viene del fichero
LUMINANCE = 1, // Ordenada por luminancia percibida
SPECTRUM = 2, // Reordenada para imitar la paleta ZX Spectrum
COUNT = 3
};
// Conversión string ↔ PaletteSortMode
auto sortModeFromString(const std::string& str) -> PaletteSortMode;
auto sortModeToString(PaletteSortMode mode) -> std::string;
class PaletteManager { class PaletteManager {
public: public:
using OnChangeCallback = std::function<void()>; using OnChangeCallback = std::function<void()>;
@@ -14,6 +32,7 @@ class PaletteManager {
PaletteManager( PaletteManager(
std::vector<std::string> raw_paths, std::vector<std::string> raw_paths,
const std::string& initial_name, const std::string& initial_name,
PaletteSortMode initial_sort_mode,
std::shared_ptr<Surface> game_surface, std::shared_ptr<Surface> game_surface,
std::shared_ptr<Surface> border_surface, std::shared_ptr<Surface> border_surface,
OnChangeCallback on_change = nullptr); OnChangeCallback on_change = nullptr);
@@ -25,13 +44,20 @@ class PaletteManager {
[[nodiscard]] auto getCurrentName() const -> std::string; // Nombre de la paleta actual (minúsculas, sin .pal) [[nodiscard]] auto getCurrentName() const -> std::string; // Nombre de la paleta actual (minúsculas, sin .pal)
[[nodiscard]] auto getPrettyName() const -> std::string; // Nombre actual con guiones sustituidos por espacios [[nodiscard]] auto getPrettyName() const -> std::string; // Nombre actual con guiones sustituidos por espacios
void nextSortMode(); // Cicla al siguiente modo de ordenación
void setSortMode(PaletteSortMode mode); // Establece un modo de ordenación concreto
[[nodiscard]] auto getSortMode() const -> PaletteSortMode; // Devuelve el modo de ordenación actual
[[nodiscard]] auto getSortModeName() const -> std::string; // Nombre del modo actual ("ORIGINAL", etc.)
private: private:
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
std::vector<std::string> palettes_; std::vector<std::string> palettes_;
size_t current_{0}; size_t current_{0};
PaletteSortMode sort_mode_{PaletteSortMode::ORIGINAL};
std::shared_ptr<Surface> game_surface_; std::shared_ptr<Surface> game_surface_;
std::shared_ptr<Surface> border_surface_; std::shared_ptr<Surface> border_surface_;
OnChangeCallback on_change_; OnChangeCallback on_change_;

View File

@@ -85,6 +85,7 @@ Screen::Screen() {
palette_manager_ = std::make_unique<PaletteManager>( palette_manager_ = std::make_unique<PaletteManager>(
Resource::List::get()->getListByType(Resource::List::Type::PALETTE), Resource::List::get()->getListByType(Resource::List::Type::PALETTE),
Options::video.palette, Options::video.palette,
sortModeFromString(Options::video.palette_sort),
game_surface_, game_surface_,
border_surface_, border_surface_,
[this]() { [this]() {
@@ -449,6 +450,9 @@ auto Screen::setPaletteByName(const std::string& name) -> bool { return palette_
// Devuelve los nombres de paletas disponibles (minúsculas, sin extensión .pal) // Devuelve los nombres de paletas disponibles (minúsculas, sin extensión .pal)
auto Screen::getPaletteNames() const -> std::vector<std::string> { return palette_manager_->getNames(); } auto Screen::getPaletteNames() const -> std::vector<std::string> { return palette_manager_->getNames(); }
auto Screen::getPalettePrettyName() const -> std::string { return palette_manager_->getPrettyName(); } auto Screen::getPalettePrettyName() const -> std::string { return palette_manager_->getPrettyName(); }
void Screen::nextPaletteSortMode() { palette_manager_->nextSortMode(); }
void Screen::setPaletteSortMode(PaletteSortMode mode) { palette_manager_->setSortMode(mode); }
auto Screen::getPaletteSortModeName() const -> std::string { return palette_manager_->getSortModeName(); }
// Limpia la game_surface_ // Limpia la game_surface_
void Screen::clearSurface(Uint8 index) { game_surface_->clear(index); } void Screen::clearSurface(Uint8 index) { game_surface_->clear(index); }

View File

@@ -59,6 +59,9 @@ class Screen {
auto setPaletteByName(const std::string& name) -> bool; // Cambia a paleta por nombre; false si no existe auto setPaletteByName(const std::string& name) -> bool; // Cambia a paleta por nombre; false si no existe
[[nodiscard]] auto getPaletteNames() const -> std::vector<std::string>; // Nombres disponibles (minúsculas, sin .pal) [[nodiscard]] auto getPaletteNames() const -> std::vector<std::string>; // Nombres disponibles (minúsculas, sin .pal)
[[nodiscard]] auto getPalettePrettyName() const -> std::string; // Nombre actual con guiones sustituidos por espacios [[nodiscard]] auto getPalettePrettyName() const -> std::string; // Nombre actual con guiones sustituidos por espacios
void nextPaletteSortMode(); // Cicla al siguiente modo de ordenación de paleta
void setPaletteSortMode(PaletteSortMode mode); // Establece modo de ordenación concreto
[[nodiscard]] auto getPaletteSortModeName() const -> std::string; // Nombre del modo de ordenación actual
void toggleShaders(); // Activa/desactiva todos los shaders respetando current_shader void toggleShaders(); // Activa/desactiva todos los shaders respetando current_shader
void toggleSupersampling(); // Activa/desactiva el supersampling global void toggleSupersampling(); // Activa/desactiva el supersampling global
void reloadPostFX(); // Recarga el shader del preset actual sin toggle void reloadPostFX(); // Recarga el shader del preset actual sin toggle

View File

@@ -30,6 +30,7 @@ namespace Defaults::Video {
constexpr bool INTEGER_SCALE = true; // Escalado entero activado por defecto constexpr bool INTEGER_SCALE = true; // Escalado entero activado por defecto
constexpr bool KEEP_ASPECT = true; // Mantener aspecto activado por defecto constexpr bool KEEP_ASPECT = true; // Mantener aspecto activado por defecto
constexpr const char* PALETTE_NAME = "zx-spectrum"; // Paleta por defecto constexpr const char* PALETTE_NAME = "zx-spectrum"; // Paleta por defecto
constexpr const char* PALETTE_SORT = "original"; // Modo de ordenación de paleta por defecto
constexpr bool LINEAR_UPSCALE = false; // Upscale NEAREST por defecto constexpr bool LINEAR_UPSCALE = false; // Upscale NEAREST por defecto
constexpr int DOWNSCALE_ALGO = 1; // Downscale Lanczos2 por defecto constexpr int DOWNSCALE_ALGO = 1; // Downscale Lanczos2 por defecto
constexpr bool GPU_ACCELERATION = true; // Aceleración GPU activada por defecto constexpr bool GPU_ACCELERATION = true; // Aceleración GPU activada por defecto

View File

@@ -288,12 +288,20 @@ namespace Options {
// Helper: carga el campo palette; PaletteManager hará el fallback si el nombre no existe // Helper: carga el campo palette; PaletteManager hará el fallback si el nombre no existe
void loadPaletteFromYaml(const fkyaml::node& vid) { void loadPaletteFromYaml(const fkyaml::node& vid) {
if (!vid.contains("palette")) { return; } if (vid.contains("palette")) {
try { try {
auto palette_str = vid["palette"].get_value<std::string>(); auto palette_str = vid["palette"].get_value<std::string>();
video.palette = toLower(palette_str); video.palette = toLower(palette_str);
} catch (...) { } catch (...) {
video.palette = Defaults::Video::PALETTE_NAME; video.palette = Defaults::Video::PALETTE_NAME;
}
}
if (vid.contains("palette_sort")) {
try {
video.palette_sort = toLower(vid["palette_sort"].get_value<std::string>());
} catch (...) {
video.palette_sort = Defaults::Video::PALETTE_SORT;
}
} }
} }
@@ -748,6 +756,7 @@ namespace Options {
file << " keep_aspect: " << (video.keep_aspect ? "true" : "false") << "\n"; file << " keep_aspect: " << (video.keep_aspect ? "true" : "false") << "\n";
file << " filter: " << filterToString(video.filter) << " # filter: nearest (pixel perfect) | linear (smooth)\n"; file << " filter: " << filterToString(video.filter) << " # filter: nearest (pixel perfect) | linear (smooth)\n";
file << " palette: " << video.palette << "\n"; file << " palette: " << video.palette << "\n";
file << " palette_sort: " << video.palette_sort << "\n";
file << " border:\n"; file << " border:\n";
file << " enabled: " << (video.border.enabled ? "true" : "false") << "\n"; file << " enabled: " << (video.border.enabled ? "true" : "false") << "\n";
file << " width: " << video.border.width << "\n"; file << " width: " << video.border.width << "\n";

View File

@@ -100,17 +100,18 @@ namespace Options {
// Estructura para las opciones de video // Estructura para las opciones de video
struct Video { struct Video {
bool fullscreen{Defaults::Video::FULLSCREEN}; // Contiene el valor del modo de pantalla completa bool fullscreen{Defaults::Video::FULLSCREEN}; // Contiene el valor del modo de pantalla completa
Screen::Filter filter{Defaults::Video::FILTER}; // Filtro usado para el escalado de la imagen Screen::Filter filter{Defaults::Video::FILTER}; // Filtro usado para el escalado de la imagen
bool vertical_sync{Defaults::Video::VERTICAL_SYNC}; // Indica si se quiere usar vsync o no bool vertical_sync{Defaults::Video::VERTICAL_SYNC}; // Indica si se quiere usar vsync o no
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 info; // Información sobre el modo de vídeo std::string palette_sort{Defaults::Video::PALETTE_SORT}; // Modo de ordenación de la paleta (original/luminance/spectrum)
Border border{}; // Borde de la pantalla std::string info; // Información sobre el modo de vídeo
GPU gpu{}; // Opciones de aceleración GPU Border border{}; // Borde de la pantalla
Supersampling supersampling{}; // Opciones de supersampling GPU gpu{}; // Opciones de aceleración GPU
ShaderConfig shader{}; // Opciones de shader post-procesado Supersampling supersampling{}; // Opciones de supersampling
ShaderConfig shader{}; // Opciones de shader post-procesado
}; };
// Estructura para las opciones de musica // Estructura para las opciones de musica

View File

@@ -333,12 +333,12 @@ static auto cmd_driver(const std::vector<std::string>& args) -> std::string {
return "Driver: " + driver_lower + " (restart)"; return "Driver: " + driver_lower + " (restart)";
} }
// PALETTE NEXT/PREV/<name> // PALETTE NEXT/PREV/SORT/DEFAULT/<name>
static auto cmd_palette(const std::vector<std::string>& args) -> std::string { static auto cmd_palette(const std::vector<std::string>& args) -> std::string {
const auto palName = []() -> std::string { const auto palName = []() -> std::string {
return Screen::get()->getPalettePrettyName(); return Screen::get()->getPalettePrettyName();
}; };
if (args.empty()) { return "usage: palette [next|prev|<name>]"; } if (args.empty()) { return "usage: palette [next|prev|sort [original|luminance|spectrum]|default|<name>]"; }
if (args[0] == "NEXT") { if (args[0] == "NEXT") {
Screen::get()->nextPalette(); Screen::get()->nextPalette();
return "Palette: " + palName(); return "Palette: " + palName();
@@ -347,6 +347,26 @@ static auto cmd_palette(const std::vector<std::string>& args) -> std::string {
Screen::get()->previousPalette(); Screen::get()->previousPalette();
return "Palette: " + palName(); return "Palette: " + palName();
} }
if (args[0] == "DEFAULT") {
Screen::get()->setPaletteByName(Defaults::Video::PALETTE_NAME);
return "Palette: " + palName();
}
if (args[0] == "SORT") {
if (args.size() == 1) {
Screen::get()->nextPaletteSortMode();
return "Palette sort: " + Screen::get()->getPaletteSortModeName();
}
if (args[1] == "ORIGINAL") {
Screen::get()->setPaletteSortMode(PaletteSortMode::ORIGINAL);
} else if (args[1] == "LUMINANCE") {
Screen::get()->setPaletteSortMode(PaletteSortMode::LUMINANCE);
} else if (args[1] == "SPECTRUM") {
Screen::get()->setPaletteSortMode(PaletteSortMode::SPECTRUM);
} else {
return "Unknown sort mode. Use: original, luminance, spectrum";
}
return "Palette sort: " + Screen::get()->getPaletteSortModeName();
}
if (!Screen::get()->setPaletteByName(args[0])) { if (!Screen::get()->setPaletteByName(args[0])) {
std::string arg_lower = args[0]; std::string arg_lower = args[0];
std::ranges::transform(arg_lower, arg_lower.begin(), ::tolower); std::ranges::transform(arg_lower, arg_lower.begin(), ::tolower);
@@ -815,7 +835,7 @@ void CommandRegistry::registerHandlers() {
// Proveedores de completions dinámicas // Proveedores de completions dinámicas
// PALETTE: NEXT, PREV + nombres de paletas disponibles (UPPERCASE) // PALETTE: NEXT, PREV + nombres de paletas disponibles (UPPERCASE)
dynamic_providers_["PALETTE"] = []() -> std::vector<std::string> { dynamic_providers_["PALETTE"] = []() -> std::vector<std::string> {
std::vector<std::string> result = {"NEXT", "PREV"}; std::vector<std::string> result = {"NEXT", "PREV", "SORT", "DEFAULT"};
if (Screen::get() != nullptr) { if (Screen::get() != nullptr) {
for (const auto& name : Screen::get()->getPaletteNames()) { for (const auto& name : Screen::get()->getPaletteNames()) {
result.push_back(toUpper(name)); result.push_back(toUpper(name));