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