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#include "util/i18n/tr.h"
3
4#include <algorithm>
5#include <map>
6
7#include "absl/strings/str_format.h"
10#include "app/gui/core/color.h"
15#include "util/log.h"
16
17#include <cstring>
18#include <string>
19#include <vector>
20
21namespace yaze {
22namespace gui {
23
24namespace {
25
31
32std::optional<DungeonRenderColorTarget> ResolveDungeonRenderColorTarget(
33 int display_index, int dungeon_palette_id) {
34 if (display_index < 0 || display_index >= 128) {
35 return std::nullopt;
36 }
37 if (display_index < 32) {
39 display_index};
40 }
41
42 const int local_index = display_index - 32;
43 const int row = local_index / 16;
44 const int col = local_index % 16;
45 if (row >= 6 || col == 0) {
46 return std::nullopt;
47 }
48
50 dungeon_palette_id, row * 15 + (col - 1)};
51}
52
54 return source == DungeonRenderPaletteSource::kHud ? "hud" : "dungeon_main";
55}
56
58 const zelda3::GameData& game_data, const DungeonRenderColorTarget& target) {
60 ? game_data.palette_groups.hud
61 : game_data.palette_groups.dungeon_main;
62}
63
65 const DungeonRenderColorTarget& target) {
67 return "HUD / floor-ceiling";
68 }
69 return absl::StrFormat("Dungeon row %d", target.color_index / 15 + 2);
70}
71
72const char* RomPaletteGroupNameGetter(void* user_data, int idx) {
73 auto* names = static_cast<std::vector<std::string>*>(user_data);
74 if (!names || idx < 0 || idx >= static_cast<int>(names->size())) {
75 return nullptr;
76 }
77 return (*names)[idx].c_str();
78}
79
80void DrawColorDropTargetOutline(ImVec2 min, ImVec2 max) {
81 const auto& theme = ThemeManager::Get().GetCurrentTheme();
82 ImGui::GetForegroundDrawList()->AddRect(
83 min, max, ImGui::GetColorU32(ConvertColorToImVec4(theme.accent)), 0.0f, 0,
84 2.0f);
85}
86
87bool AcceptImGuiColorDrop(gfx::SnesColor* color, ImVec2 min, ImVec2 max) {
88 if (!color || !ImGui::BeginDragDropTarget()) {
89 return false;
90 }
91
92 bool changed = false;
93 constexpr ImGuiDragDropFlags kFlags =
94 ImGuiDragDropFlags_AcceptBeforeDelivery |
95 ImGuiDragDropFlags_AcceptNoDrawDefaultRect |
96 ImGuiDragDropFlags_AcceptNoPreviewTooltip;
97 auto accept = [&](const char* type, size_t components) {
98 if (changed) {
99 return;
100 }
101 if (const ImGuiPayload* payload =
102 ImGui::AcceptDragDropPayload(type, kFlags)) {
103 DrawColorDropTargetOutline(min, max);
104 if (payload->IsDelivery()) {
105 ImVec4 dropped(0, 0, 0, 1);
106 std::memcpy(&dropped.x, payload->Data, sizeof(float) * components);
107 if (components == 3) {
108 dropped.w = 1.0f;
109 }
110 *color = gfx::SnesColor(dropped);
111 changed = true;
112 }
113 }
114 };
115
116 accept(IMGUI_PAYLOAD_TYPE_COLOR_3F, 3);
117 accept(IMGUI_PAYLOAD_TYPE_COLOR_4F, 4);
118 ImGui::EndDragDropTarget();
119 return changed;
120}
121
122} // namespace
123
124// Merged implementation from PaletteWidget and PaletteEditorWidget
125
127 game_data_ = game_data;
128 rom_ = nullptr;
129 rom_palettes_loaded_ = false;
130 if (game_data_) {
132 }
135}
136
138 rom_ = rom;
139 game_data_ = nullptr;
140 rom_palettes_loaded_ = false;
141 // Legacy mode - no palette loading without game_data
144}
145
147 int display_index, std::optional<gfx::SnesColor> color) {
148 if (!game_data_) {
149 return absl::FailedPreconditionError("GameData not loaded");
150 }
151
152 const auto target =
153 ResolveDungeonRenderColorTarget(display_index, current_palette_id_);
154 if (!target.has_value()) {
155 return absl::InvalidArgumentError(absl::StrFormat(
156 "Dungeon render palette slot %d is reserved or out of range",
157 display_index));
158 }
159
160 auto& palette_manager = gfx::PaletteManager::Get();
161 if (!palette_manager.IsManaging(game_data_)) {
162 return absl::FailedPreconditionError(
163 "PaletteManager is bound to a different ROM session");
164 }
165 const char* group_name = PaletteManagerGroupName(target->source);
166 absl::Status status =
167 color.has_value()
168 ? palette_manager.SetColor(group_name, target->palette_index,
169 target->color_index, *color)
170 : palette_manager.ResetColor(group_name, target->palette_index,
171 target->color_index);
172 if (!status.ok()) {
173 return status;
174 }
175
177 on_palette_changed_({current_palette_id_, target->source});
178 }
179 return absl::OkStatus();
180}
181
182// --- Embedded Draw Method (from simple editor) ---
184 if (!game_data_) {
185 ImGui::TextColored(ImVec4(1, 0, 0, 1), tr("GameData not loaded"));
186 return;
187 }
188
189 ImGui::BeginGroup();
190
192 ImGui::Separator();
193
194 auto& dungeon_pal_group = game_data_->palette_groups.dungeon_main;
195 if (dungeon_pal_group.empty()) {
196 ImGui::TextDisabled(tr("Dungeon palettes unavailable"));
197 ImGui::EndGroup();
198 return;
199 }
200
201 current_palette_id_ = std::clamp(
202 current_palette_id_, 0, static_cast<int>(dungeon_pal_group.size()) - 1);
203 if (current_palette_id_ >= 0 &&
204 current_palette_id_ < (int)dungeon_pal_group.size()) {
205 auto palette = dungeon_pal_group[current_palette_id_];
208 } else {
209 DrawPaletteGrid(palette, 15);
210 dungeon_pal_group[current_palette_id_] = palette;
211 }
212 }
213
214 ImGui::Separator();
215
216 if (selected_color_index_ >= 0) {
219 } else {
221 }
222 } else {
223 ImGui::TextDisabled(tr("Select a color to edit"));
224 }
225
226 ImGui::EndGroup();
227}
228
230 if (!game_data_)
231 return;
232 auto& dungeon_pal_group = game_data_->palette_groups.dungeon_main;
233 int num_palettes = dungeon_pal_group.size();
234 if (num_palettes <= 0) {
235 ImGui::TextDisabled(tr("No dungeon palettes"));
236 return;
237 }
238
239 current_palette_id_ = std::clamp(current_palette_id_, 0, num_palettes - 1);
240
241 ImGui::Text(tr("Dungeon Palette:"));
242 ImGui::SameLine();
243 ImGui::SetNextItemWidth(std::min(180.0f, ImGui::GetContentRegionAvail().x));
244
245 if (ImGui::BeginCombo(
246 "##PaletteSelect",
247 absl::StrFormat("Palette %d", current_palette_id_).c_str())) {
248 for (int i = 0; i < num_palettes; i++) {
249 bool is_selected = (current_palette_id_ == i);
250 if (ImGui::Selectable(absl::StrFormat("Palette %d", i).c_str(),
251 is_selected)) {
254 }
255 if (is_selected) {
256 ImGui::SetItemDefaultFocus();
257 }
258 }
259 ImGui::EndCombo();
260 }
261}
262
264 if (!game_data_)
265 return;
266 auto& dungeon_pal_group = game_data_->palette_groups.dungeon_main;
267 if (dungeon_pal_group.empty()) {
268 return;
269 }
270 current_palette_id_ = std::clamp(
271 current_palette_id_, 0, static_cast<int>(dungeon_pal_group.size()) - 1);
272 ImGui::SeparatorText(
273 absl::StrFormat("Edit Color %d", selected_color_index_).c_str());
274
275 auto palette = dungeon_pal_group[current_palette_id_];
276 if (selected_color_index_ < 0 ||
277 selected_color_index_ >= static_cast<int>(palette.size())) {
279 return;
280 }
281 auto original_color = palette[selected_color_index_];
282
283 // Use standardized SnesColorEdit4 for consistency
285 "Color", &palette[selected_color_index_],
286 ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_PickerHueWheel)) {
287 // Update the palette group with the modified palette
288 dungeon_pal_group[current_palette_id_] = palette;
289
290 // Update editing_color_ to match (for consistency with other parts of the
291 // widget)
294
298 }
299 }
300
301 ImGui::Text(tr("RGB (0-255): (%d, %d, %d)"),
302 static_cast<int>(editing_color_.x * 255),
303 static_cast<int>(editing_color_.y * 255),
304 static_cast<int>(editing_color_.z * 255));
305 ImGui::Text(tr("SNES BGR555: 0x%04X"), original_color.snes());
306
307 if (gui::ThemedButton("Reset to Original")) {
309 // Also reset the actual palette color
310 palette[selected_color_index_] = original_color;
311 dungeon_pal_group[current_palette_id_] = palette;
315 }
316 }
317}
318
320 ImGui::TextDisabled(
321 tr("Rows 0-1: HUD / floor / ceiling | Rows 2-7: Dungeon main"));
322 const float swatch_size = ComputeSwatchSize(/*columns=*/16, 14.0f, 28.0f);
323
324 for (int i = 0; i < 128; ++i) {
325 if (i % 16 != 0) {
326 ImGui::SameLine();
327 }
328
329 gfx::SnesColor color(0);
330 bool editable = false;
331 std::string tooltip_label = "Reserved / transparent";
332 int source_index = -1;
333
334 const auto target = ResolveDungeonRenderColorTarget(i, current_palette_id_);
335 if (target.has_value()) {
336 const auto& group = PaletteGroupForTarget(*game_data_, *target);
337 if (target->palette_index >= 0 &&
338 target->palette_index < static_cast<int>(group.size())) {
339 const auto& palette = group.palette_ref(target->palette_index);
340 if (target->color_index >= 0 &&
341 target->color_index < static_cast<int>(palette.size())) {
342 color = palette[target->color_index];
343 editable = true;
344 tooltip_label = DungeonRenderColorSourceLabel(*target);
345 source_index = target->color_index;
346 }
347 }
348 }
349
350 ImVec4 display_color = gui::ConvertSnesColorToImVec4(color);
351 ImGui::PushID(i);
352 if (!editable) {
353 ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 0.25f);
354 }
355 const bool clicked = ImGui::ColorButton("##render_color", display_color,
356 ImGuiColorEditFlags_NoTooltip,
357 ImVec2(swatch_size, swatch_size));
358 const ImVec2 swatch_min = ImGui::GetItemRectMin();
359 const ImVec2 swatch_max = ImGui::GetItemRectMax();
360 const bool double_clicked =
361 editable && ImGui::IsItemHovered() &&
362 ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left);
363 if (!editable) {
364 ImGui::PopStyleVar();
365 }
366
367 if ((clicked || double_clicked) && editable) {
370 temp_color_ = display_color;
371 editing_color_ = display_color;
372 }
373 gfx::SnesColor dropped_color = color;
374 if (editable &&
375 AcceptImGuiColorDrop(&dropped_color, swatch_min, swatch_max)) {
376 const absl::Status status = ApplyDungeonRenderColorEdit(i, dropped_color);
377 if (status.ok()) {
382 } else {
383 LOG_ERROR("PaletteEditorWidget", "Failed to apply dropped color: %s",
384 status.message().data());
385 }
386 }
387
388 if (ImGui::IsItemHovered()) {
389 if (editable) {
390 ImGui::SetTooltip(tr("%s\nSlot %d\nSNES: 0x%04X\nRGB: (%d, %d, %d)"),
391 tooltip_label.c_str(), source_index, color.snes(),
392 static_cast<int>(display_color.x * 255),
393 static_cast<int>(display_color.y * 255),
394 static_cast<int>(display_color.z * 255));
395 } else {
396 ImGui::SetTooltip("%s", tooltip_label.c_str());
397 }
398 }
399 ImGui::PopID();
400 }
401}
402
403float PaletteEditorWidget::ComputeSwatchSize(int columns, float min_size,
404 float max_size) const {
405 const float available_width =
406 std::max(ImGui::GetContentRegionAvail().x, 1.0f);
407 const float spacing =
408 std::max(ImGui::GetStyle().ItemSpacing.x, 2.0f) * (columns - 1);
409 const float raw = (available_width - spacing) / std::max(columns, 1);
410 return std::clamp(raw, min_size, max_size);
411}
412
414 const auto target = ResolveDungeonRenderColorTarget(selected_color_index_,
416 if (!game_data_ || !target.has_value()) {
419 return;
420 }
421
422 const auto& group = PaletteGroupForTarget(*game_data_, *target);
423 if (target->palette_index < 0 ||
424 target->palette_index >= static_cast<int>(group.size())) {
427 return;
428 }
429 const auto& palette = group.palette_ref(target->palette_index);
430 if (target->color_index < 0 ||
431 target->color_index >= static_cast<int>(palette.size())) {
434 return;
435 }
436
437 ImGui::SeparatorText(absl::StrFormat("%s Color %d",
438 DungeonRenderColorSourceLabel(*target),
439 target->color_index)
440 .c_str());
441
442 const gfx::SnesColor current_color = palette[target->color_index];
443 gfx::SnesColor edited_color = current_color;
445 "Color", &edited_color,
446 ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_PickerHueWheel)) {
447 const absl::Status status =
449 if (status.ok()) {
451 } else {
452 LOG_ERROR("PaletteEditorWidget", "Failed to apply palette color: %s",
453 status.message().data());
454 }
455 }
456
457 ImGui::Text(tr("RGB (0-255): (%d, %d, %d)"),
458 static_cast<int>(editing_color_.x * 255),
459 static_cast<int>(editing_color_.y * 255),
460 static_cast<int>(editing_color_.z * 255));
461 ImGui::Text(tr("SNES BGR555: 0x%04X"), current_color.snes());
462
463 if (gui::ThemedButton("Reset to Original")) {
464 const absl::Status status =
466 if (status.ok()) {
468 PaletteManagerGroupName(target->source), target->palette_index,
469 target->color_index);
471 } else {
472 LOG_ERROR("PaletteEditorWidget", "Failed to reset palette color: %s",
473 status.message().data());
474 }
475 }
476}
477
478// --- Modal/Popup Methods (from feature-rich widget) ---
479
481 const std::string& title) {
482 if (ImGui::BeginPopupModal(title.c_str(), nullptr,
483 ImGuiWindowFlags_AlwaysAutoResize)) {
484 ImGui::Text(tr("Enhanced Palette Editor"));
485 ImGui::Separator();
486
487 DrawPaletteGrid(palette);
488 ImGui::Separator();
489
490 if (ImGui::CollapsingHeader(tr("Palette Analysis"))) {
491 DrawPaletteAnalysis(palette);
492 }
493
494 if (ImGui::CollapsingHeader(tr("ROM Palette Manager")) && rom_) {
496
497 if (gui::PrimaryButton("Apply ROM Palette") &&
498 !rom_palette_groups_.empty()) {
500 static_cast<int>(rom_palette_groups_.size())) {
502 }
503 }
504 }
505
506 ImGui::Separator();
507
508 if (gui::ThemedButton("Save Backup")) {
509 SavePaletteBackup(palette);
510 }
511 ImGui::SameLine();
512 if (gui::ThemedButton("Restore Backup")) {
513 RestorePaletteBackup(palette);
514 }
515 ImGui::SameLine();
516 if (gui::ThemedButton("Close")) {
517 ImGui::CloseCurrentPopup();
518 }
519
520 ImGui::EndPopup();
521 }
522}
523
526 return;
527
528 if (ImGui::Begin("ROM Palette Manager", &show_rom_manager_)) {
529 if (!rom_) {
530 ImGui::Text(tr("No ROM loaded"));
531 ImGui::End();
532 return;
533 }
534
537 }
538
540
541 if (current_group_index_ < static_cast<int>(rom_palette_groups_.size())) {
542 ImGui::Separator();
543 ImGui::Text(tr("Preview of %s:"),
545
546 const auto& preview_palette = rom_palette_groups_[current_group_index_];
547 DrawPaletteGrid(const_cast<gfx::SnesPalette&>(preview_palette));
548 DrawPaletteAnalysis(preview_palette);
549 }
550 }
551 ImGui::End();
552}
553
555 const std::string& title) {
557 return;
558
559 if (ImGui::Begin(title.c_str(), &show_color_analysis_)) {
560 ImGui::Text(tr("Bitmap Color Analysis"));
561 ImGui::Separator();
562
563 if (!bitmap.is_active()) {
564 ImGui::Text(tr("Bitmap is not active"));
565 ImGui::End();
566 return;
567 }
568
569 std::map<uint8_t, int> pixel_counts;
570 const auto& data = bitmap.vector();
571
572 for (uint8_t pixel : data) {
573 uint8_t palette_index = pixel & 0x0F;
574 pixel_counts[palette_index]++;
575 }
576
577 ImGui::Text(tr("Bitmap Size: %d x %d (%zu pixels)"), bitmap.width(),
578 bitmap.height(), data.size());
579
580 ImGui::Separator();
581 ImGui::Text(tr("Pixel Distribution:"));
582
583 int total_pixels = static_cast<int>(data.size());
584 plotting::PlotStyleScope plot_style(
585 gui::ThemeManager::Get().GetCurrentTheme());
586 plotting::PlotConfig plot_cfg{.id = "Pixel Distribution",
587 .x_label = "Palette Index",
588 .y_label = "Count",
589 .flags = ImPlotFlags_NoBoxSelect,
590 .x_axis_flags = ImPlotAxisFlags_AutoFit,
591 .y_axis_flags = ImPlotAxisFlags_AutoFit};
592 std::vector<double> x;
593 std::vector<double> y;
594 x.reserve(pixel_counts.size());
595 y.reserve(pixel_counts.size());
596 for (const auto& [index, count] : pixel_counts) {
597 x.push_back(static_cast<double>(index));
598 y.push_back(static_cast<double>(count));
599 }
600 plotting::PlotGuard plot(plot_cfg);
601 if (plot && !x.empty()) {
602 ImPlot::PlotBars("Usage", x.data(), y.data(), static_cast<int>(x.size()),
603 0.67, 0.0, ImPlotBarsFlags_None);
604 }
605 }
606 ImGui::End();
607}
608
610 int palette_index) {
611 if (!bitmap || !rom_palettes_loaded_ || group_index < 0 ||
612 group_index >= static_cast<int>(rom_palette_groups_.size())) {
613 return false;
614 }
615
616 try {
617 const auto& selected_palette = rom_palette_groups_[group_index];
618 SavePaletteBackup(bitmap->palette());
619
620 if (palette_index >= 0 && palette_index < 8) {
621 bitmap->SetPaletteWithTransparent(selected_palette, palette_index);
622 } else {
623 bitmap->SetPalette(selected_palette);
624 }
625
628
629 current_group_index_ = group_index;
630 current_palette_index_ = palette_index;
631 return true;
632 } catch (const std::exception& e) {
633 return false;
634 }
635}
636
639 current_group_index_ >= static_cast<int>(rom_palette_groups_.size())) {
640 return nullptr;
641 }
643}
644
648
650 if (backup_palette_.size() == 0) {
651 return false;
652 }
653 palette = backup_palette_;
654 return true;
655}
656
657// Unified grid drawing function
659 const float swatch_size =
660 ComputeSwatchSize(cols, /*min_size=*/16.0f, /*max_size=*/30.0f);
661 for (int i = 0; i < static_cast<int>(palette.size()); i++) {
662 if (i % cols != 0)
663 ImGui::SameLine();
664
665 auto color = palette[i];
666 ImVec4 display_color = gui::ConvertSnesColorToImVec4(color);
667
668 ImGui::PushID(i);
669 const bool clicked = ImGui::ColorButton("##color", display_color,
670 ImGuiColorEditFlags_NoTooltip,
671 ImVec2(swatch_size, swatch_size));
672 const ImVec2 swatch_min = ImGui::GetItemRectMin();
673 const ImVec2 swatch_max = ImGui::GetItemRectMax();
674 const bool double_clicked =
675 ImGui::IsItemHovered() &&
676 ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left);
677 if (clicked || double_clicked) {
680 temp_color_ = display_color;
681 editing_color_ = display_color;
682 }
683 if (AcceptImGuiColorDrop(&palette[i], swatch_min, swatch_max)) {
691 }
692 }
693
694 if (ImGui::BeginPopupContextItem()) {
695 ImGui::Text(tr("Color %d (0x%04X)"), i, color.snes());
696 ImGui::Separator();
697 if (ImGui::MenuItem(tr("Edit Color"))) {
700 temp_color_ = display_color;
701 editing_color_ = display_color;
702 }
703 if (ImGui::MenuItem(tr("Reset to Black"))) {
704 palette[i] = gfx::SnesColor(0);
708 }
709 }
710 ImGui::EndPopup();
711 }
712
713 if (ImGui::IsItemHovered()) {
714 ImGui::SetTooltip(tr("Color %d\nSNES: 0x%04X\nRGB: (%d, %d, %d)"), i,
715 color.snes(), static_cast<int>(display_color.x * 255),
716 static_cast<int>(display_color.y * 255),
717 static_cast<int>(display_color.z * 255));
718 }
719
720 ImGui::PopID();
721 }
722
723 if (editing_color_index_ >= 0) {
724 // Use a unique ID for the popup to prevent conflicts
725 std::string popup_id =
726 gui::MakePopupIdWithInstance("PaletteEditorWidget", "EditColor", this);
727
728 ImGui::OpenPopup(popup_id.c_str());
729 if (ImGui::BeginPopupModal(popup_id.c_str(), nullptr,
730 ImGuiWindowFlags_AlwaysAutoResize)) {
731 ImGui::Text(tr("Editing Color %d"), editing_color_index_);
732 if (ImGui::ColorEdit4(
733 "Color", &temp_color_.x,
734 ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_DisplayRGB)) {
735 auto new_snes_color = gfx::SnesColor(temp_color_);
736 palette[editing_color_index_] = new_snes_color;
739 }
743 }
744 }
745 if (gui::PrimaryButton("Apply")) {
747 ImGui::CloseCurrentPopup();
748 }
749 ImGui::SameLine();
750 if (gui::ThemedButton("Cancel")) {
752 ImGui::CloseCurrentPopup();
753 }
754 ImGui::EndPopup();
755 }
756 }
757}
758
762 }
763
764 if (rom_palette_groups_.empty()) {
765 ImGui::Text(tr("No ROM palettes available"));
766 return;
767 }
768
769 ImGui::Text(tr("Palette Group:"));
770 if (ImGui::Combo("##PaletteGroup", &current_group_index_,
771 RomPaletteGroupNameGetter, &palette_group_names_,
772 static_cast<int>(palette_group_names_.size()))) {}
773
774 ImGui::Text(tr("Palette Index:"));
775 ImGui::SliderInt("##PaletteIndex", &current_palette_index_, 0, 7, "%d");
776
777 if (current_group_index_ < static_cast<int>(rom_palette_groups_.size())) {
778 ImGui::Text(tr("Preview:"));
779 const auto& preview_palette = rom_palette_groups_[current_group_index_];
780 for (int i = 0; i < 8 && i < static_cast<int>(preview_palette.size());
781 i++) {
782 if (i > 0)
783 ImGui::SameLine();
784 auto color = preview_palette[i];
785 ImVec4 display_color = gui::ConvertSnesColorToImVec4(color);
786 ImGui::ColorButton(("##preview" + std::to_string(i)).c_str(),
787 display_color, ImGuiColorEditFlags_NoTooltip,
788 ImVec2(20, 20));
789 }
790 }
791}
792
794 int color_index) {
795 ImVec4 rgba = gui::ConvertSnesColorToImVec4(color);
796
797 ImGui::PushID(color_index);
798
799 if (ImGui::ColorEdit4(
800 "##color_edit", &rgba.x,
801 ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_DisplayRGB)) {
802 color = gfx::SnesColor(rgba);
803 }
804
805 ImGui::Text(tr("SNES Color: 0x%04X"), color.snes());
806
807 int r = (color.snes() & 0x1F);
808 int g = (color.snes() >> 5) & 0x1F;
809 int b = (color.snes() >> 10) & 0x1F;
810
811 if (ImGui::SliderInt(tr("Red"), &r, 0, 31)) {
812 uint16_t new_color = (color.snes() & 0xFFE0) | (r & 0x1F);
813 color = gfx::SnesColor(new_color);
814 }
815 if (ImGui::SliderInt(tr("Green"), &g, 0, 31)) {
816 uint16_t new_color = (color.snes() & 0xFC1F) | ((g & 0x1F) << 5);
817 color = gfx::SnesColor(new_color);
818 }
819 if (ImGui::SliderInt(tr("Blue"), &b, 0, 31)) {
820 uint16_t new_color = (color.snes() & 0x83FF) | ((b & 0x1F) << 10);
821 color = gfx::SnesColor(new_color);
822 }
823
824 ImGui::PopID();
825}
826
828 ImGui::Text(tr("Palette Information:"));
829 ImGui::Text(tr("Size: %zu colors"), palette.size());
830
831 std::map<uint16_t, int> color_frequency;
832 for (int i = 0; i < static_cast<int>(palette.size()); i++) {
833 color_frequency[palette[i].snes()]++;
834 }
835
836 ImGui::Text(tr("Unique Colors: %zu"), color_frequency.size());
837
838 if (color_frequency.size() < palette.size()) {
839 ImGui::TextColored(ImVec4(1, 1, 0, 1),
840 tr("Warning: Duplicate colors detected!"));
841 if (ImGui::TreeNode("Duplicate Colors")) {
842 for (const auto& [snes_color, count] : color_frequency) {
843 if (count > 1) {
844 ImVec4 display_color =
846 ImGui::ColorButton(("##dup" + std::to_string(snes_color)).c_str(),
847 display_color, ImGuiColorEditFlags_NoTooltip,
848 ImVec2(16, 16));
849 ImGui::SameLine();
850 ImGui::Text(tr("0x%04X appears %d times"), snes_color, count);
851 }
852 }
853 ImGui::TreePop();
854 }
855 }
856
857 // Visual histogram of color reuse
858 {
859 plotting::PlotStyleScope plot_style(
860 gui::ThemeManager::Get().GetCurrentTheme());
861 plotting::PlotConfig plot_cfg{.id = "Palette Color Frequency",
862 .x_label = "Color Index",
863 .y_label = "Count",
864 .flags = ImPlotFlags_NoBoxSelect,
865 .x_axis_flags = ImPlotAxisFlags_AutoFit,
866 .y_axis_flags = ImPlotAxisFlags_AutoFit};
867 std::vector<double> x;
868 std::vector<double> y;
869 x.reserve(color_frequency.size());
870 y.reserve(color_frequency.size());
871 for (const auto& [snes_color, count] : color_frequency) {
872 x.push_back(static_cast<double>(snes_color));
873 y.push_back(static_cast<double>(count));
874 }
875 plotting::PlotGuard plot(plot_cfg);
876 if (plot && !x.empty()) {
877 ImPlot::PlotBars("Count", x.data(), y.data(), static_cast<int>(x.size()),
878 0.5, 0.0, ImPlotBarsFlags_None);
879 }
880 }
881
882 float total_brightness = 0.0f;
883 float min_brightness = 1.0f;
884 float max_brightness = 0.0f;
885
886 for (int i = 0; i < static_cast<int>(palette.size()); i++) {
887 ImVec4 color = gui::ConvertSnesColorToImVec4(palette[i]);
888 float brightness = (color.x + color.y + color.z) / 3.0f;
889 total_brightness += brightness;
890 min_brightness = std::min(min_brightness, brightness);
891 max_brightness = std::max(max_brightness, brightness);
892 }
893
894 float avg_brightness = total_brightness / palette.size();
895
896 ImGui::Separator();
897 ImGui::Text(tr("Brightness Analysis:"));
898 ImGui::Text(tr("Average: %.2f"), avg_brightness);
899 ImGui::Text(tr("Range: %.2f - %.2f"), min_brightness, max_brightness);
900
901 ImGui::Text(tr("Brightness Distribution:"));
902 ImGui::ProgressBar(avg_brightness, ImVec2(-1, 0), "Avg");
903}
904
907 return;
908
909 try {
910 const auto& palette_groups = game_data_->palette_groups;
912 palette_group_names_.clear();
913
914 if (palette_groups.overworld_main.size() > 0) {
915 rom_palette_groups_.push_back(palette_groups.overworld_main[0]);
916 palette_group_names_.push_back("Overworld Main");
917 }
918 if (palette_groups.overworld_aux.size() > 0) {
919 rom_palette_groups_.push_back(palette_groups.overworld_aux[0]);
920 palette_group_names_.push_back("Overworld Aux");
921 }
922 if (palette_groups.overworld_animated.size() > 0) {
923 rom_palette_groups_.push_back(palette_groups.overworld_animated[0]);
924 palette_group_names_.push_back("Overworld Animated");
925 }
926 if (palette_groups.dungeon_main.size() > 0) {
927 rom_palette_groups_.push_back(palette_groups.dungeon_main[0]);
928 palette_group_names_.push_back("Dungeon Main");
929 }
930 if (palette_groups.global_sprites.size() > 0) {
931 rom_palette_groups_.push_back(palette_groups.global_sprites[0]);
932 palette_group_names_.push_back("Global Sprites");
933 }
934 if (palette_groups.armors.size() > 0) {
935 rom_palette_groups_.push_back(palette_groups.armors[0]);
936 palette_group_names_.push_back("Armor");
937 }
938 if (palette_groups.swords.size() > 0) {
939 rom_palette_groups_.push_back(palette_groups.swords[0]);
940 palette_group_names_.push_back("Swords");
941 }
942
944 } catch (const std::exception& e) {
945 LOG_ERROR("Enhanced Palette Editor", "Failed to load ROM palettes");
946 }
947}
948
949} // namespace gui
950} // 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:389
const std::vector< uint8_t > & vector() const
Definition bitmap.h:402
bool is_active() const
Definition bitmap.h:405
int height() const
Definition bitmap.h:395
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:394
void SetPaletteWithTransparent(const SnesPalette &palette, size_t index, int length=7)
Set the palette with a transparent color.
Definition bitmap.cc:456
static PaletteManager & Get()
Get the singleton instance.
SnesColor GetColor(const std::string &group_name, int palette_index, int color_index) const
Get a color from a palette.
SNES Color container.
Definition snes_color.h:110
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_
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)
std::function< void(DungeonPaletteChange)> on_palette_changed_
void ShowColorAnalysis(const gfx::Bitmap &bitmap, const std::string &title="Color Analysis")
void Initialize(zelda3::GameData *game_data)
absl::Status ApplyDungeonRenderColorEdit(int display_index, std::optional< gfx::SnesColor > color)
void DrawColorEditControls(gfx::SnesColor &color, int color_index)
void DrawPaletteAnalysis(const gfx::SnesPalette &palette)
float ComputeSwatchSize(int columns, float min_size, float max_size) const
std::vector< std::string > palette_group_names_
void DrawPaletteGrid(gfx::SnesPalette &palette, int cols=15)
const Theme & GetCurrentTheme() const
static ThemeManager & Get()
#define LOG_ERROR(category, format,...)
Definition log.h:109
const char * RomPaletteGroupNameGetter(void *user_data, int idx)
std::optional< DungeonRenderColorTarget > ResolveDungeonRenderColorTarget(int display_index, int dungeon_palette_id)
const gfx::PaletteGroup & PaletteGroupForTarget(const zelda3::GameData &game_data, const DungeonRenderColorTarget &target)
const char * PaletteManagerGroupName(DungeonRenderPaletteSource source)
std::string DungeonRenderColorSourceLabel(const DungeonRenderColorTarget &target)
ImVec4 ConvertColorToImVec4(const Color &color)
Definition color.h:134
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:23
IMGUI_API bool SnesColorEdit4(absl::string_view label, gfx::SnesColor *color, ImGuiColorEditFlags flags)
Definition color.cc:58
SNES color in 15-bit RGB format (BGR555)
Represents a group of palettes.
gfx::PaletteGroupMap palette_groups
Definition game_data.h:92