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 ImVec4(original_color.rgb().x / 255.0f, original_color.rgb().y / 255.0f,
310 original_color.rgb().z / 255.0f, 1.0f);
311 // Also reset the actual palette color
312 palette[selected_color_index_] = original_color;
313 dungeon_pal_group[current_palette_id_] = palette;
317 }
318 }
319}
320
322 ImGui::TextDisabled(
323 tr("Rows 0-1: HUD / floor / ceiling | Rows 2-7: Dungeon main"));
324 const float swatch_size = ComputeSwatchSize(/*columns=*/16, 14.0f, 28.0f);
325
326 for (int i = 0; i < 128; ++i) {
327 if (i % 16 != 0) {
328 ImGui::SameLine();
329 }
330
331 gfx::SnesColor color(0);
332 bool editable = false;
333 std::string tooltip_label = "Reserved / transparent";
334 int source_index = -1;
335
336 const auto target = ResolveDungeonRenderColorTarget(i, current_palette_id_);
337 if (target.has_value()) {
338 const auto& group = PaletteGroupForTarget(*game_data_, *target);
339 if (target->palette_index >= 0 &&
340 target->palette_index < static_cast<int>(group.size())) {
341 const auto& palette = group.palette_ref(target->palette_index);
342 if (target->color_index >= 0 &&
343 target->color_index < static_cast<int>(palette.size())) {
344 color = palette[target->color_index];
345 editable = true;
346 tooltip_label = DungeonRenderColorSourceLabel(*target);
347 source_index = target->color_index;
348 }
349 }
350 }
351
352 ImVec4 display_color = color.rgb();
353 ImGui::PushID(i);
354 if (!editable) {
355 ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 0.25f);
356 }
357 const bool clicked = ImGui::ColorButton("##render_color", display_color,
358 ImGuiColorEditFlags_NoTooltip,
359 ImVec2(swatch_size, swatch_size));
360 const ImVec2 swatch_min = ImGui::GetItemRectMin();
361 const ImVec2 swatch_max = ImGui::GetItemRectMax();
362 const bool double_clicked =
363 editable && ImGui::IsItemHovered() &&
364 ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left);
365 if (!editable) {
366 ImGui::PopStyleVar();
367 }
368
369 if ((clicked || double_clicked) && editable) {
372 temp_color_ = display_color;
373 editing_color_ = display_color;
374 }
375 gfx::SnesColor dropped_color = color;
376 if (editable &&
377 AcceptImGuiColorDrop(&dropped_color, swatch_min, swatch_max)) {
378 const absl::Status status = ApplyDungeonRenderColorEdit(i, dropped_color);
379 if (status.ok()) {
382 temp_color_ = dropped_color.rgb();
384 } else {
385 LOG_ERROR("PaletteEditorWidget", "Failed to apply dropped color: %s",
386 status.message().data());
387 }
388 }
389
390 if (ImGui::IsItemHovered()) {
391 if (editable) {
392 ImGui::SetTooltip(tr("%s\nSlot %d\nSNES: 0x%04X\nRGB: (%d, %d, %d)"),
393 tooltip_label.c_str(), source_index, color.snes(),
394 static_cast<int>(display_color.x * 255),
395 static_cast<int>(display_color.y * 255),
396 static_cast<int>(display_color.z * 255));
397 } else {
398 ImGui::SetTooltip("%s", tooltip_label.c_str());
399 }
400 }
401 ImGui::PopID();
402 }
403}
404
405float PaletteEditorWidget::ComputeSwatchSize(int columns, float min_size,
406 float max_size) const {
407 const float available_width =
408 std::max(ImGui::GetContentRegionAvail().x, 1.0f);
409 const float spacing =
410 std::max(ImGui::GetStyle().ItemSpacing.x, 2.0f) * (columns - 1);
411 const float raw = (available_width - spacing) / std::max(columns, 1);
412 return std::clamp(raw, min_size, max_size);
413}
414
416 const auto target = ResolveDungeonRenderColorTarget(selected_color_index_,
418 if (!game_data_ || !target.has_value()) {
421 return;
422 }
423
424 const auto& group = PaletteGroupForTarget(*game_data_, *target);
425 if (target->palette_index < 0 ||
426 target->palette_index >= static_cast<int>(group.size())) {
429 return;
430 }
431 const auto& palette = group.palette_ref(target->palette_index);
432 if (target->color_index < 0 ||
433 target->color_index >= static_cast<int>(palette.size())) {
436 return;
437 }
438
439 ImGui::SeparatorText(absl::StrFormat("%s Color %d",
440 DungeonRenderColorSourceLabel(*target),
441 target->color_index)
442 .c_str());
443
444 const gfx::SnesColor current_color = palette[target->color_index];
445 gfx::SnesColor edited_color = current_color;
447 "Color", &edited_color,
448 ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_PickerHueWheel)) {
449 const absl::Status status =
451 if (status.ok()) {
453 } else {
454 LOG_ERROR("PaletteEditorWidget", "Failed to apply palette color: %s",
455 status.message().data());
456 }
457 }
458
459 ImGui::Text(tr("RGB (0-255): (%d, %d, %d)"),
460 static_cast<int>(editing_color_.x * 255),
461 static_cast<int>(editing_color_.y * 255),
462 static_cast<int>(editing_color_.z * 255));
463 ImGui::Text(tr("SNES BGR555: 0x%04X"), current_color.snes());
464
465 if (gui::ThemedButton("Reset to Original")) {
466 const absl::Status status =
468 if (status.ok()) {
470 PaletteManagerGroupName(target->source), target->palette_index,
471 target->color_index);
473 } else {
474 LOG_ERROR("PaletteEditorWidget", "Failed to reset palette color: %s",
475 status.message().data());
476 }
477 }
478}
479
480// --- Modal/Popup Methods (from feature-rich widget) ---
481
483 const std::string& title) {
484 if (ImGui::BeginPopupModal(title.c_str(), nullptr,
485 ImGuiWindowFlags_AlwaysAutoResize)) {
486 ImGui::Text(tr("Enhanced Palette Editor"));
487 ImGui::Separator();
488
489 DrawPaletteGrid(palette);
490 ImGui::Separator();
491
492 if (ImGui::CollapsingHeader(tr("Palette Analysis"))) {
493 DrawPaletteAnalysis(palette);
494 }
495
496 if (ImGui::CollapsingHeader(tr("ROM Palette Manager")) && rom_) {
498
499 if (gui::PrimaryButton("Apply ROM Palette") &&
500 !rom_palette_groups_.empty()) {
502 static_cast<int>(rom_palette_groups_.size())) {
504 }
505 }
506 }
507
508 ImGui::Separator();
509
510 if (gui::ThemedButton("Save Backup")) {
511 SavePaletteBackup(palette);
512 }
513 ImGui::SameLine();
514 if (gui::ThemedButton("Restore Backup")) {
515 RestorePaletteBackup(palette);
516 }
517 ImGui::SameLine();
518 if (gui::ThemedButton("Close")) {
519 ImGui::CloseCurrentPopup();
520 }
521
522 ImGui::EndPopup();
523 }
524}
525
528 return;
529
530 if (ImGui::Begin("ROM Palette Manager", &show_rom_manager_)) {
531 if (!rom_) {
532 ImGui::Text(tr("No ROM loaded"));
533 ImGui::End();
534 return;
535 }
536
539 }
540
542
543 if (current_group_index_ < static_cast<int>(rom_palette_groups_.size())) {
544 ImGui::Separator();
545 ImGui::Text(tr("Preview of %s:"),
547
548 const auto& preview_palette = rom_palette_groups_[current_group_index_];
549 DrawPaletteGrid(const_cast<gfx::SnesPalette&>(preview_palette));
550 DrawPaletteAnalysis(preview_palette);
551 }
552 }
553 ImGui::End();
554}
555
557 const std::string& title) {
559 return;
560
561 if (ImGui::Begin(title.c_str(), &show_color_analysis_)) {
562 ImGui::Text(tr("Bitmap Color Analysis"));
563 ImGui::Separator();
564
565 if (!bitmap.is_active()) {
566 ImGui::Text(tr("Bitmap is not active"));
567 ImGui::End();
568 return;
569 }
570
571 std::map<uint8_t, int> pixel_counts;
572 const auto& data = bitmap.vector();
573
574 for (uint8_t pixel : data) {
575 uint8_t palette_index = pixel & 0x0F;
576 pixel_counts[palette_index]++;
577 }
578
579 ImGui::Text(tr("Bitmap Size: %d x %d (%zu pixels)"), bitmap.width(),
580 bitmap.height(), data.size());
581
582 ImGui::Separator();
583 ImGui::Text(tr("Pixel Distribution:"));
584
585 int total_pixels = static_cast<int>(data.size());
586 plotting::PlotStyleScope plot_style(
587 gui::ThemeManager::Get().GetCurrentTheme());
588 plotting::PlotConfig plot_cfg{.id = "Pixel Distribution",
589 .x_label = "Palette Index",
590 .y_label = "Count",
591 .flags = ImPlotFlags_NoBoxSelect,
592 .x_axis_flags = ImPlotAxisFlags_AutoFit,
593 .y_axis_flags = ImPlotAxisFlags_AutoFit};
594 std::vector<double> x;
595 std::vector<double> y;
596 x.reserve(pixel_counts.size());
597 y.reserve(pixel_counts.size());
598 for (const auto& [index, count] : pixel_counts) {
599 x.push_back(static_cast<double>(index));
600 y.push_back(static_cast<double>(count));
601 }
602 plotting::PlotGuard plot(plot_cfg);
603 if (plot && !x.empty()) {
604 ImPlot::PlotBars("Usage", x.data(), y.data(), static_cast<int>(x.size()),
605 0.67, 0.0, ImPlotBarsFlags_None);
606 }
607 }
608 ImGui::End();
609}
610
612 int palette_index) {
613 if (!bitmap || !rom_palettes_loaded_ || group_index < 0 ||
614 group_index >= static_cast<int>(rom_palette_groups_.size())) {
615 return false;
616 }
617
618 try {
619 const auto& selected_palette = rom_palette_groups_[group_index];
620 SavePaletteBackup(bitmap->palette());
621
622 if (palette_index >= 0 && palette_index < 8) {
623 bitmap->SetPaletteWithTransparent(selected_palette, palette_index);
624 } else {
625 bitmap->SetPalette(selected_palette);
626 }
627
630
631 current_group_index_ = group_index;
632 current_palette_index_ = palette_index;
633 return true;
634 } catch (const std::exception& e) {
635 return false;
636 }
637}
638
641 current_group_index_ >= static_cast<int>(rom_palette_groups_.size())) {
642 return nullptr;
643 }
645}
646
650
652 if (backup_palette_.size() == 0) {
653 return false;
654 }
655 palette = backup_palette_;
656 return true;
657}
658
659// Unified grid drawing function
661 const float swatch_size =
662 ComputeSwatchSize(cols, /*min_size=*/16.0f, /*max_size=*/30.0f);
663 for (int i = 0; i < static_cast<int>(palette.size()); i++) {
664 if (i % cols != 0)
665 ImGui::SameLine();
666
667 auto color = palette[i];
668 ImVec4 display_color = color.rgb();
669
670 ImGui::PushID(i);
671 const bool clicked = ImGui::ColorButton("##color", display_color,
672 ImGuiColorEditFlags_NoTooltip,
673 ImVec2(swatch_size, swatch_size));
674 const ImVec2 swatch_min = ImGui::GetItemRectMin();
675 const ImVec2 swatch_max = ImGui::GetItemRectMax();
676 const bool double_clicked =
677 ImGui::IsItemHovered() &&
678 ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left);
679 if (clicked || double_clicked) {
682 temp_color_ = display_color;
683 editing_color_ = display_color;
684 }
685 if (AcceptImGuiColorDrop(&palette[i], swatch_min, swatch_max)) {
688 temp_color_ = palette[i].rgb();
693 }
694 }
695
696 if (ImGui::BeginPopupContextItem()) {
697 ImGui::Text(tr("Color %d (0x%04X)"), i, color.snes());
698 ImGui::Separator();
699 if (ImGui::MenuItem(tr("Edit Color"))) {
702 temp_color_ = display_color;
703 editing_color_ = display_color;
704 }
705 if (ImGui::MenuItem(tr("Reset to Black"))) {
706 palette[i] = gfx::SnesColor(0);
710 }
711 }
712 ImGui::EndPopup();
713 }
714
715 if (ImGui::IsItemHovered()) {
716 ImGui::SetTooltip(tr("Color %d\nSNES: 0x%04X\nRGB: (%d, %d, %d)"), i,
717 color.snes(), static_cast<int>(display_color.x * 255),
718 static_cast<int>(display_color.y * 255),
719 static_cast<int>(display_color.z * 255));
720 }
721
722 ImGui::PopID();
723 }
724
725 if (editing_color_index_ >= 0) {
726 // Use a unique ID for the popup to prevent conflicts
727 std::string popup_id =
728 gui::MakePopupIdWithInstance("PaletteEditorWidget", "EditColor", this);
729
730 ImGui::OpenPopup(popup_id.c_str());
731 if (ImGui::BeginPopupModal(popup_id.c_str(), nullptr,
732 ImGuiWindowFlags_AlwaysAutoResize)) {
733 ImGui::Text(tr("Editing Color %d"), editing_color_index_);
734 if (ImGui::ColorEdit4(
735 "Color", &temp_color_.x,
736 ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_DisplayRGB)) {
737 auto new_snes_color = gfx::SnesColor(temp_color_);
738 palette[editing_color_index_] = new_snes_color;
741 }
745 }
746 }
747 if (gui::PrimaryButton("Apply")) {
749 ImGui::CloseCurrentPopup();
750 }
751 ImGui::SameLine();
752 if (gui::ThemedButton("Cancel")) {
754 ImGui::CloseCurrentPopup();
755 }
756 ImGui::EndPopup();
757 }
758 }
759}
760
764 }
765
766 if (rom_palette_groups_.empty()) {
767 ImGui::Text(tr("No ROM palettes available"));
768 return;
769 }
770
771 ImGui::Text(tr("Palette Group:"));
772 if (ImGui::Combo("##PaletteGroup", &current_group_index_,
773 RomPaletteGroupNameGetter, &palette_group_names_,
774 static_cast<int>(palette_group_names_.size()))) {}
775
776 ImGui::Text(tr("Palette Index:"));
777 ImGui::SliderInt("##PaletteIndex", &current_palette_index_, 0, 7, "%d");
778
779 if (current_group_index_ < static_cast<int>(rom_palette_groups_.size())) {
780 ImGui::Text(tr("Preview:"));
781 const auto& preview_palette = rom_palette_groups_[current_group_index_];
782 for (int i = 0; i < 8 && i < static_cast<int>(preview_palette.size());
783 i++) {
784 if (i > 0)
785 ImGui::SameLine();
786 auto color = preview_palette[i];
787 ImVec4 display_color = color.rgb();
788 ImGui::ColorButton(("##preview" + std::to_string(i)).c_str(),
789 display_color, ImGuiColorEditFlags_NoTooltip,
790 ImVec2(20, 20));
791 }
792 }
793}
794
796 int color_index) {
797 ImVec4 rgba = color.rgb();
798
799 ImGui::PushID(color_index);
800
801 if (ImGui::ColorEdit4(
802 "##color_edit", &rgba.x,
803 ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_DisplayRGB)) {
804 color = gfx::SnesColor(rgba);
805 }
806
807 ImGui::Text(tr("SNES Color: 0x%04X"), color.snes());
808
809 int r = (color.snes() & 0x1F);
810 int g = (color.snes() >> 5) & 0x1F;
811 int b = (color.snes() >> 10) & 0x1F;
812
813 if (ImGui::SliderInt(tr("Red"), &r, 0, 31)) {
814 uint16_t new_color = (color.snes() & 0xFFE0) | (r & 0x1F);
815 color = gfx::SnesColor(new_color);
816 }
817 if (ImGui::SliderInt(tr("Green"), &g, 0, 31)) {
818 uint16_t new_color = (color.snes() & 0xFC1F) | ((g & 0x1F) << 5);
819 color = gfx::SnesColor(new_color);
820 }
821 if (ImGui::SliderInt(tr("Blue"), &b, 0, 31)) {
822 uint16_t new_color = (color.snes() & 0x83FF) | ((b & 0x1F) << 10);
823 color = gfx::SnesColor(new_color);
824 }
825
826 ImGui::PopID();
827}
828
830 ImGui::Text(tr("Palette Information:"));
831 ImGui::Text(tr("Size: %zu colors"), palette.size());
832
833 std::map<uint16_t, int> color_frequency;
834 for (int i = 0; i < static_cast<int>(palette.size()); i++) {
835 color_frequency[palette[i].snes()]++;
836 }
837
838 ImGui::Text(tr("Unique Colors: %zu"), color_frequency.size());
839
840 if (color_frequency.size() < palette.size()) {
841 ImGui::TextColored(ImVec4(1, 1, 0, 1),
842 tr("Warning: Duplicate colors detected!"));
843 if (ImGui::TreeNode("Duplicate Colors")) {
844 for (const auto& [snes_color, count] : color_frequency) {
845 if (count > 1) {
846 ImVec4 display_color = gfx::SnesColor(snes_color).rgb();
847 ImGui::ColorButton(("##dup" + std::to_string(snes_color)).c_str(),
848 display_color, ImGuiColorEditFlags_NoTooltip,
849 ImVec2(16, 16));
850 ImGui::SameLine();
851 ImGui::Text(tr("0x%04X appears %d times"), snes_color, count);
852 }
853 }
854 ImGui::TreePop();
855 }
856 }
857
858 // Visual histogram of color reuse
859 {
860 plotting::PlotStyleScope plot_style(
861 gui::ThemeManager::Get().GetCurrentTheme());
862 plotting::PlotConfig plot_cfg{.id = "Palette Color Frequency",
863 .x_label = "Color Index",
864 .y_label = "Count",
865 .flags = ImPlotFlags_NoBoxSelect,
866 .x_axis_flags = ImPlotAxisFlags_AutoFit,
867 .y_axis_flags = ImPlotAxisFlags_AutoFit};
868 std::vector<double> x;
869 std::vector<double> y;
870 x.reserve(color_frequency.size());
871 y.reserve(color_frequency.size());
872 for (const auto& [snes_color, count] : color_frequency) {
873 x.push_back(static_cast<double>(snes_color));
874 y.push_back(static_cast<double>(count));
875 }
876 plotting::PlotGuard plot(plot_cfg);
877 if (plot && !x.empty()) {
878 ImPlot::PlotBars("Count", x.data(), y.data(), static_cast<int>(x.size()),
879 0.5, 0.0, ImPlotBarsFlags_None);
880 }
881 }
882
883 float total_brightness = 0.0f;
884 float min_brightness = 1.0f;
885 float max_brightness = 0.0f;
886
887 for (int i = 0; i < static_cast<int>(palette.size()); i++) {
888 ImVec4 color = palette[i].rgb();
889 float brightness = (color.x + color.y + color.z) / 3.0f;
890 total_brightness += brightness;
891 min_brightness = std::min(min_brightness, brightness);
892 max_brightness = std::max(max_brightness, brightness);
893 }
894
895 float avg_brightness = total_brightness / palette.size();
896
897 ImGui::Separator();
898 ImGui::Text(tr("Brightness Analysis:"));
899 ImGui::Text(tr("Average: %.2f"), avg_brightness);
900 ImGui::Text(tr("Range: %.2f - %.2f"), min_brightness, max_brightness);
901
902 ImGui::Text(tr("Brightness Distribution:"));
903 ImGui::ProgressBar(avg_brightness, ImVec2(-1, 0), "Avg");
904}
905
908 return;
909
910 try {
911 const auto& palette_groups = game_data_->palette_groups;
913 palette_group_names_.clear();
914
915 if (palette_groups.overworld_main.size() > 0) {
916 rom_palette_groups_.push_back(palette_groups.overworld_main[0]);
917 palette_group_names_.push_back("Overworld Main");
918 }
919 if (palette_groups.overworld_aux.size() > 0) {
920 rom_palette_groups_.push_back(palette_groups.overworld_aux[0]);
921 palette_group_names_.push_back("Overworld Aux");
922 }
923 if (palette_groups.overworld_animated.size() > 0) {
924 rom_palette_groups_.push_back(palette_groups.overworld_animated[0]);
925 palette_group_names_.push_back("Overworld Animated");
926 }
927 if (palette_groups.dungeon_main.size() > 0) {
928 rom_palette_groups_.push_back(palette_groups.dungeon_main[0]);
929 palette_group_names_.push_back("Dungeon Main");
930 }
931 if (palette_groups.global_sprites.size() > 0) {
932 rom_palette_groups_.push_back(palette_groups.global_sprites[0]);
933 palette_group_names_.push_back("Global Sprites");
934 }
935 if (palette_groups.armors.size() > 0) {
936 rom_palette_groups_.push_back(palette_groups.armors[0]);
937 palette_group_names_.push_back("Armor");
938 }
939 if (palette_groups.swords.size() > 0) {
940 rom_palette_groups_.push_back(palette_groups.swords[0]);
941 palette_group_names_.push_back("Swords");
942 }
943
945 } catch (const std::exception& e) {
946 LOG_ERROR("Enhanced Palette Editor", "Failed to load ROM palettes");
947 }
948}
949
950} // namespace gui
951} // 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 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_
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