yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
palette_editor_widget.cc
Go to the documentation of this file.
2
3#include <algorithm>
4#include <map>
5
6#include "absl/strings/str_format.h"
12#include "util/log.h"
13
14namespace yaze {
15namespace gui {
16
17// Merged implementation from PaletteWidget and PaletteEditorWidget
18
20 game_data_ = game_data;
21 rom_ = nullptr;
23 if (game_data_) {
25 }
28}
29
31 rom_ = rom;
32 game_data_ = nullptr;
34 // Legacy mode - no palette loading without game_data
37}
38
39// --- Embedded Draw Method (from simple editor) ---
41 if (!game_data_) {
42 ImGui::TextColored(ImVec4(1, 0, 0, 1), "GameData not loaded");
43 return;
44 }
45
46 ImGui::BeginGroup();
47
49 ImGui::Separator();
50
51 auto& dungeon_pal_group = game_data_->palette_groups.dungeon_main;
52 if (current_palette_id_ >= 0 &&
53 current_palette_id_ < (int)dungeon_pal_group.size()) {
54 auto palette = dungeon_pal_group[current_palette_id_];
55 DrawPaletteGrid(palette, 15);
56 dungeon_pal_group[current_palette_id_] = palette;
57 }
58
59 ImGui::Separator();
60
61 if (selected_color_index_ >= 0) {
63 } else {
64 ImGui::TextDisabled("Select a color to edit");
65 }
66
67 ImGui::EndGroup();
68}
69
71 if (!game_data_) return;
72 auto& dungeon_pal_group = game_data_->palette_groups.dungeon_main;
73 int num_palettes = dungeon_pal_group.size();
74
75 ImGui::Text("Dungeon Palette:");
76 ImGui::SameLine();
77
78 if (ImGui::BeginCombo(
79 "##PaletteSelect",
80 absl::StrFormat("Palette %d", current_palette_id_).c_str())) {
81 for (int i = 0; i < num_palettes; i++) {
82 bool is_selected = (current_palette_id_ == i);
83 if (ImGui::Selectable(absl::StrFormat("Palette %d", i).c_str(),
84 is_selected)) {
87 }
88 if (is_selected) {
89 ImGui::SetItemDefaultFocus();
90 }
91 }
92 ImGui::EndCombo();
93 }
94}
95
97 if (!game_data_) return;
98 ImGui::SeparatorText(
99 absl::StrFormat("Edit Color %d", selected_color_index_).c_str());
100
101 auto& dungeon_pal_group = game_data_->palette_groups.dungeon_main;
102 auto palette = dungeon_pal_group[current_palette_id_];
103 auto original_color = palette[selected_color_index_];
104
105 // Use standardized SnesColorEdit4 for consistency
107 "Color", &palette[selected_color_index_],
108 ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_PickerHueWheel)) {
109 // Update the palette group with the modified palette
110 dungeon_pal_group[current_palette_id_] = palette;
111
112 // Update editing_color_ to match (for consistency with other parts of the
113 // widget)
116
119 }
120 }
121
122 ImGui::Text("RGB (0-255): (%d, %d, %d)",
123 static_cast<int>(editing_color_.x * 255),
124 static_cast<int>(editing_color_.y * 255),
125 static_cast<int>(editing_color_.z * 255));
126 ImGui::Text("SNES BGR555: 0x%04X", original_color.snes());
127
128 if (ImGui::Button("Reset to Original")) {
129 editing_color_ = ImVec4(original_color.rgb().x / 255.0f,
130 original_color.rgb().y / 255.0f,
131 original_color.rgb().z / 255.0f, 1.0f);
132 // Also reset the actual palette color
133 palette[selected_color_index_] = original_color;
134 dungeon_pal_group[current_palette_id_] = palette;
137 }
138 }
139}
140
141// --- Modal/Popup Methods (from feature-rich widget) ---
142
144 const std::string& title) {
145 if (ImGui::BeginPopupModal(title.c_str(), nullptr,
146 ImGuiWindowFlags_AlwaysAutoResize)) {
147 ImGui::Text("Enhanced Palette Editor");
148 ImGui::Separator();
149
150 DrawPaletteGrid(palette);
151 ImGui::Separator();
152
153 if (ImGui::CollapsingHeader("Palette Analysis")) {
154 DrawPaletteAnalysis(palette);
155 }
156
157 if (ImGui::CollapsingHeader("ROM Palette Manager") && rom_) {
159
160 if (ImGui::Button("Apply ROM Palette") && !rom_palette_groups_.empty()) {
162 static_cast<int>(rom_palette_groups_.size())) {
164 }
165 }
166 }
167
168 ImGui::Separator();
169
170 if (ImGui::Button("Save Backup")) {
171 SavePaletteBackup(palette);
172 }
173 ImGui::SameLine();
174 if (ImGui::Button("Restore Backup")) {
175 RestorePaletteBackup(palette);
176 }
177 ImGui::SameLine();
178 if (ImGui::Button("Close")) {
179 ImGui::CloseCurrentPopup();
180 }
181
182 ImGui::EndPopup();
183 }
184}
185
187 if (!show_rom_manager_) return;
188
189 if (ImGui::Begin("ROM Palette Manager", &show_rom_manager_)) {
190 if (!rom_) {
191 ImGui::Text("No ROM loaded");
192 ImGui::End();
193 return;
194 }
195
198 }
199
201
202 if (current_group_index_ < static_cast<int>(rom_palette_groups_.size())) {
203 ImGui::Separator();
204 ImGui::Text("Preview of %s:",
206
207 const auto& preview_palette = rom_palette_groups_[current_group_index_];
208 DrawPaletteGrid(const_cast<gfx::SnesPalette&>(preview_palette));
209 DrawPaletteAnalysis(preview_palette);
210 }
211 }
212 ImGui::End();
213}
214
216 const std::string& title) {
217 if (!show_color_analysis_) return;
218
219 if (ImGui::Begin(title.c_str(), &show_color_analysis_)) {
220 ImGui::Text("Bitmap Color Analysis");
221 ImGui::Separator();
222
223 if (!bitmap.is_active()) {
224 ImGui::Text("Bitmap is not active");
225 ImGui::End();
226 return;
227 }
228
229 std::map<uint8_t, int> pixel_counts;
230 const auto& data = bitmap.vector();
231
232 for (uint8_t pixel : data) {
233 uint8_t palette_index = pixel & 0x0F;
234 pixel_counts[palette_index]++;
235 }
236
237 ImGui::Text("Bitmap Size: %d x %d (%zu pixels)", bitmap.width(),
238 bitmap.height(), data.size());
239
240 ImGui::Separator();
241 ImGui::Text("Pixel Distribution:");
242
243 int total_pixels = static_cast<int>(data.size());
244 plotting::PlotStyleScope plot_style(gui::ThemeManager::Get().GetCurrentTheme());
245 plotting::PlotConfig plot_cfg{
246 .id = "Pixel Distribution",
247 .x_label = "Palette Index",
248 .y_label = "Count",
249 .flags = ImPlotFlags_NoBoxSelect,
250 .x_axis_flags = ImPlotAxisFlags_AutoFit,
251 .y_axis_flags = ImPlotAxisFlags_AutoFit};
252 std::vector<double> x;
253 std::vector<double> y;
254 x.reserve(pixel_counts.size());
255 y.reserve(pixel_counts.size());
256 for (const auto& [index, count] : pixel_counts) {
257 x.push_back(static_cast<double>(index));
258 y.push_back(static_cast<double>(count));
259 }
260 plotting::PlotGuard plot(plot_cfg);
261 if (plot && !x.empty()) {
262 ImPlot::PlotBars("Usage", x.data(), y.data(), static_cast<int>(x.size()),
263 0.67, 0.0, ImPlotBarsFlags_None);
264 }
265 }
266 ImGui::End();
267}
268
270 int palette_index) {
271 if (!bitmap || !rom_palettes_loaded_ || group_index < 0 ||
272 group_index >= static_cast<int>(rom_palette_groups_.size())) {
273 return false;
274 }
275
276 try {
277 const auto& selected_palette = rom_palette_groups_[group_index];
278 SavePaletteBackup(bitmap->palette());
279
280 if (palette_index >= 0 && palette_index < 8) {
281 bitmap->SetPaletteWithTransparent(selected_palette, palette_index);
282 } else {
283 bitmap->SetPalette(selected_palette);
284 }
285
288
289 current_group_index_ = group_index;
290 current_palette_index_ = palette_index;
291 return true;
292 } catch (const std::exception& e) {
293 return false;
294 }
295}
296
299 current_group_index_ >= static_cast<int>(rom_palette_groups_.size())) {
300 return nullptr;
301 }
303}
304
308
310 if (backup_palette_.size() == 0) {
311 return false;
312 }
313 palette = backup_palette_;
314 return true;
315}
316
317// Unified grid drawing function
319 for (int i = 0; i < static_cast<int>(palette.size()); i++) {
320 if (i % cols != 0) ImGui::SameLine();
321
322 auto color = palette[i];
323 ImVec4 display_color = color.rgb();
324
325 ImGui::PushID(i);
326 if (ImGui::ColorButton("##color", display_color,
327 ImGuiColorEditFlags_NoTooltip, ImVec2(30, 30))) {
330 temp_color_ = display_color;
331 editing_color_ = display_color;
332 }
333
334 if (ImGui::BeginPopupContextItem()) {
335 ImGui::Text("Color %d (0x%04X)", i, color.snes());
336 ImGui::Separator();
337 if (ImGui::MenuItem("Edit Color")) {
340 temp_color_ = display_color;
341 editing_color_ = display_color;
342 }
343 if (ImGui::MenuItem("Reset to Black")) {
344 palette[i] = gfx::SnesColor(0);
345 }
346 ImGui::EndPopup();
347 }
348
349 if (ImGui::IsItemHovered()) {
350 ImGui::SetTooltip("Color %d\nSNES: 0x%04X\nRGB: (%d, %d, %d)", i,
351 color.snes(), static_cast<int>(display_color.x * 255),
352 static_cast<int>(display_color.y * 255),
353 static_cast<int>(display_color.z * 255));
354 }
355
356 ImGui::PopID();
357 }
358
359 if (editing_color_index_ >= 0) {
360 // Use a unique ID for the popup to prevent conflicts
361 std::string popup_id =
362 gui::MakePopupIdWithInstance("PaletteEditorWidget", "EditColor", this);
363
364 ImGui::OpenPopup(popup_id.c_str());
365 if (ImGui::BeginPopupModal(popup_id.c_str(), nullptr,
366 ImGuiWindowFlags_AlwaysAutoResize)) {
367 ImGui::Text("Editing Color %d", editing_color_index_);
368 if (ImGui::ColorEdit4(
369 "Color", &temp_color_.x,
370 ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_DisplayRGB)) {
371 auto new_snes_color = gfx::SnesColor(temp_color_);
372 palette[editing_color_index_] = new_snes_color;
373 }
374 if (ImGui::Button("Apply")) {
376 ImGui::CloseCurrentPopup();
377 }
378 ImGui::SameLine();
379 if (ImGui::Button("Cancel")) {
381 ImGui::CloseCurrentPopup();
382 }
383 ImGui::EndPopup();
384 }
385 }
386}
387
391 }
392
393 if (rom_palette_groups_.empty()) {
394 ImGui::Text("No ROM palettes available");
395 return;
396 }
397
398 ImGui::Text("Palette Group:");
399 if (ImGui::Combo(
400 "##PaletteGroup", &current_group_index_,
401 [](void* data, int idx, const char** out_text) -> bool {
402 auto* names = static_cast<std::vector<std::string>*>(data);
403 if (idx < 0 || idx >= static_cast<int>(names->size()))
404 return false;
405 *out_text = (*names)[idx].c_str();
406 return true;
407 },
409 static_cast<int>(palette_group_names_.size()))) {}
410
411 ImGui::Text("Palette Index:");
412 ImGui::SliderInt("##PaletteIndex", &current_palette_index_, 0, 7, "%d");
413
414 if (current_group_index_ < static_cast<int>(rom_palette_groups_.size())) {
415 ImGui::Text("Preview:");
416 const auto& preview_palette = rom_palette_groups_[current_group_index_];
417 for (int i = 0; i < 8 && i < static_cast<int>(preview_palette.size());
418 i++) {
419 if (i > 0)
420 ImGui::SameLine();
421 auto color = preview_palette[i];
422 ImVec4 display_color = color.rgb();
423 ImGui::ColorButton(("##preview" + std::to_string(i)).c_str(),
424 display_color, ImGuiColorEditFlags_NoTooltip,
425 ImVec2(20, 20));
426 }
427 }
428}
429
431 int color_index) {
432 ImVec4 rgba = color.rgb();
433
434 ImGui::PushID(color_index);
435
436 if (ImGui::ColorEdit4(
437 "##color_edit", &rgba.x,
438 ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_DisplayRGB)) {
439 color = gfx::SnesColor(rgba);
440 }
441
442 ImGui::Text("SNES Color: 0x%04X", color.snes());
443
444 int r = (color.snes() & 0x1F);
445 int g = (color.snes() >> 5) & 0x1F;
446 int b = (color.snes() >> 10) & 0x1F;
447
448 if (ImGui::SliderInt("Red", &r, 0, 31)) {
449 uint16_t new_color = (color.snes() & 0xFFE0) | (r & 0x1F);
450 color = gfx::SnesColor(new_color);
451 }
452 if (ImGui::SliderInt("Green", &g, 0, 31)) {
453 uint16_t new_color = (color.snes() & 0xFC1F) | ((g & 0x1F) << 5);
454 color = gfx::SnesColor(new_color);
455 }
456 if (ImGui::SliderInt("Blue", &b, 0, 31)) {
457 uint16_t new_color = (color.snes() & 0x83FF) | ((b & 0x1F) << 10);
458 color = gfx::SnesColor(new_color);
459 }
460
461 ImGui::PopID();
462}
463
465 ImGui::Text("Palette Information:");
466 ImGui::Text("Size: %zu colors", palette.size());
467
468 std::map<uint16_t, int> color_frequency;
469 for (int i = 0; i < static_cast<int>(palette.size()); i++) {
470 color_frequency[palette[i].snes()]++;
471 }
472
473 ImGui::Text("Unique Colors: %zu", color_frequency.size());
474
475 if (color_frequency.size() < palette.size()) {
476 ImGui::TextColored(ImVec4(1, 1, 0, 1),
477 "Warning: Duplicate colors detected!");
478 if (ImGui::TreeNode("Duplicate Colors")) {
479 for (const auto& [snes_color, count] : color_frequency) {
480 if (count > 1) {
481 ImVec4 display_color = gfx::SnesColor(snes_color).rgb();
482 ImGui::ColorButton(("##dup" + std::to_string(snes_color)).c_str(),
483 display_color, ImGuiColorEditFlags_NoTooltip,
484 ImVec2(16, 16));
485 ImGui::SameLine();
486 ImGui::Text("0x%04X appears %d times", snes_color, count);
487 }
488 }
489 ImGui::TreePop();
490 }
491 }
492
493 // Visual histogram of color reuse
494 {
495 plotting::PlotStyleScope plot_style(gui::ThemeManager::Get().GetCurrentTheme());
496 plotting::PlotConfig plot_cfg{
497 .id = "Palette Color Frequency",
498 .x_label = "Color Index",
499 .y_label = "Count",
500 .flags = ImPlotFlags_NoBoxSelect,
501 .x_axis_flags = ImPlotAxisFlags_AutoFit,
502 .y_axis_flags = ImPlotAxisFlags_AutoFit};
503 std::vector<double> x;
504 std::vector<double> y;
505 x.reserve(color_frequency.size());
506 y.reserve(color_frequency.size());
507 for (const auto& [snes_color, count] : color_frequency) {
508 x.push_back(static_cast<double>(snes_color));
509 y.push_back(static_cast<double>(count));
510 }
511 plotting::PlotGuard plot(plot_cfg);
512 if (plot && !x.empty()) {
513 ImPlot::PlotBars("Count", x.data(), y.data(), static_cast<int>(x.size()),
514 0.5, 0.0, ImPlotBarsFlags_None);
515 }
516 }
517
518 float total_brightness = 0.0f;
519 float min_brightness = 1.0f;
520 float max_brightness = 0.0f;
521
522 for (int i = 0; i < static_cast<int>(palette.size()); i++) {
523 ImVec4 color = palette[i].rgb();
524 float brightness = (color.x + color.y + color.z) / 3.0f;
525 total_brightness += brightness;
526 min_brightness = std::min(min_brightness, brightness);
527 max_brightness = std::max(max_brightness, brightness);
528 }
529
530 float avg_brightness = total_brightness / palette.size();
531
532 ImGui::Separator();
533 ImGui::Text("Brightness Analysis:");
534 ImGui::Text("Average: %.2f", avg_brightness);
535 ImGui::Text("Range: %.2f - %.2f", min_brightness, max_brightness);
536
537 ImGui::Text("Brightness Distribution:");
538 ImGui::ProgressBar(avg_brightness, ImVec2(-1, 0), "Avg");
539}
540
543 return;
544
545 try {
546 const auto& palette_groups = game_data_->palette_groups;
548 palette_group_names_.clear();
549
550 if (palette_groups.overworld_main.size() > 0) {
551 rom_palette_groups_.push_back(palette_groups.overworld_main[0]);
552 palette_group_names_.push_back("Overworld Main");
553 }
554 if (palette_groups.overworld_aux.size() > 0) {
555 rom_palette_groups_.push_back(palette_groups.overworld_aux[0]);
556 palette_group_names_.push_back("Overworld Aux");
557 }
558 if (palette_groups.overworld_animated.size() > 0) {
559 rom_palette_groups_.push_back(palette_groups.overworld_animated[0]);
560 palette_group_names_.push_back("Overworld Animated");
561 }
562 if (palette_groups.dungeon_main.size() > 0) {
563 rom_palette_groups_.push_back(palette_groups.dungeon_main[0]);
564 palette_group_names_.push_back("Dungeon Main");
565 }
566 if (palette_groups.global_sprites.size() > 0) {
567 rom_palette_groups_.push_back(palette_groups.global_sprites[0]);
568 palette_group_names_.push_back("Global Sprites");
569 }
570 if (palette_groups.armors.size() > 0) {
571 rom_palette_groups_.push_back(palette_groups.armors[0]);
572 palette_group_names_.push_back("Armor");
573 }
574 if (palette_groups.swords.size() > 0) {
575 rom_palette_groups_.push_back(palette_groups.swords[0]);
576 palette_group_names_.push_back("Swords");
577 }
578
580 } catch (const std::exception& e) {
581 LOG_ERROR("Enhanced Palette Editor", "Failed to load ROM palettes");
582 }
583}
584
585} // namespace gui
586} // namespace yaze
The Rom class is used to load, save, and modify Rom data. This is a generic SNES ROM container and do...
Definition rom.h:24
void QueueTextureCommand(TextureCommandType type, Bitmap *bitmap)
Definition arena.cc:34
static Arena & Get()
Definition arena.cc:19
Represents a bitmap image optimized for SNES ROM hacking.
Definition bitmap.h:67
const SnesPalette & palette() const
Definition bitmap.h:368
const std::vector< uint8_t > & vector() const
Definition bitmap.h:381
bool is_active() const
Definition bitmap.h:384
int height() const
Definition bitmap.h:374
void SetPalette(const SnesPalette &palette)
Set the palette for the bitmap using SNES palette format.
Definition bitmap.cc:382
int width() const
Definition bitmap.h:373
void SetPaletteWithTransparent(const SnesPalette &palette, size_t index, int length=7)
Set the palette with a transparent color.
Definition bitmap.cc:454
SNES Color container.
Definition snes_color.h:110
constexpr ImVec4 rgb() const
Get RGB values (WARNING: stored as 0-255 in ImVec4)
Definition snes_color.h:183
constexpr uint16_t snes() const
Get SNES 15-bit color.
Definition snes_color.h:193
Represents a palette of colors for the Super Nintendo Entertainment System (SNES).
std::vector< gfx::SnesPalette > rom_palette_groups_
std::function< void(int palette_id)> on_palette_changed_
bool RestorePaletteBackup(gfx::SnesPalette &palette)
void ShowPaletteEditor(gfx::SnesPalette &palette, const std::string &title="Palette Editor")
bool ApplyROMPalette(gfx::Bitmap *bitmap, int group_index, int palette_index)
const gfx::SnesPalette * GetSelectedROMPalette() const
void SavePaletteBackup(const gfx::SnesPalette &palette)
void ShowColorAnalysis(const gfx::Bitmap &bitmap, const std::string &title="Color Analysis")
void Initialize(zelda3::GameData *game_data)
void DrawColorEditControls(gfx::SnesColor &color, int color_index)
void DrawPaletteAnalysis(const gfx::SnesPalette &palette)
std::vector< std::string > palette_group_names_
void DrawPaletteGrid(gfx::SnesPalette &palette, int cols=15)
static ThemeManager & Get()
#define LOG_ERROR(category, format,...)
Definition log.h:109
std::string MakePopupIdWithInstance(const std::string &editor_name, const std::string &popup_name, const void *instance)
Generate popup ID with instance pointer for guaranteed uniqueness.
Definition popup_id.h:45
ImVec4 ConvertSnesColorToImVec4(const gfx::SnesColor &color)
Convert SnesColor to standard ImVec4 for display.
Definition color.cc:19
IMGUI_API bool SnesColorEdit4(absl::string_view label, gfx::SnesColor *color, ImGuiColorEditFlags flags)
Definition color.cc:54
SNES color in 15-bit RGB format (BGR555)
Definition yaze.h:218
gfx::PaletteGroupMap palette_groups
Definition game_data.h:89