6#include "absl/strings/str_format.h"
24 auto* names =
static_cast<std::vector<std::string>*
>(user_data);
25 if (!names || idx < 0 || idx >=
static_cast<int>(names->size())) {
28 return (*names)[idx].c_str();
58 ImGui::TextColored(ImVec4(1, 0, 0, 1),
"GameData not loaded");
68 if (dungeon_pal_group.empty()) {
69 ImGui::TextDisabled(
"Dungeon palettes unavailable");
76 static_cast<int>(dungeon_pal_group.size()) - 1);
97 ImGui::TextDisabled(
"Select a color to edit");
107 int num_palettes = dungeon_pal_group.
size();
108 if (num_palettes <= 0) {
109 ImGui::TextDisabled(
"No dungeon palettes");
115 ImGui::Text(
"Dungeon Palette:");
117 ImGui::SetNextItemWidth(std::min(180.0f, ImGui::GetContentRegionAvail().x));
119 if (ImGui::BeginCombo(
122 for (
int i = 0; i < num_palettes; i++) {
124 if (ImGui::Selectable(absl::StrFormat(
"Palette %d", i).c_str(),
130 ImGui::SetItemDefaultFocus();
141 if (dungeon_pal_group.empty()) {
145 static_cast<int>(dungeon_pal_group.size()) - 1);
146 ImGui::SeparatorText(
160 ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_PickerHueWheel)) {
174 ImGui::Text(
"RGB (0-255): (%d, %d, %d)",
178 ImGui::Text(
"SNES BGR555: 0x%04X", original_color.snes());
182 ImVec4(original_color.rgb().x / 255.0f, original_color.rgb().y / 255.0f,
183 original_color.rgb().z / 255.0f, 1.0f);
200 if (display_index < 32) {
205 *color_index = display_index;
207 *source_label =
"HUD / floor-ceiling";
212 const int local_index = display_index - 32;
213 const int row = local_index / 16;
214 const int col = local_index % 16;
215 if (row < 0 || row >= 6 || col == 0) {
226 *color_index = row * 15 + (col - 1);
228 *source_label = absl::StrFormat(
"Dungeon row %d", row + 2);
235 "Rows 0-1: HUD / floor / ceiling | Rows 2-7: Dungeon main");
251 for (
int i = 0; i < 128; ++i) {
257 bool editable =
false;
258 std::string tooltip_label =
"Reserved / transparent";
259 int source_index = -1;
261 if (i < 32 && hud_palette && i <
static_cast<int>(hud_palette->
size())) {
262 color = (*hud_palette)[i];
264 tooltip_label =
"HUD / floor-ceiling";
266 }
else if (i >= 32 && dungeon_palette) {
267 const int local_index = i - 32;
268 const int row = local_index / 16;
269 const int col = local_index % 16;
270 if (row < 6 && col > 0) {
271 source_index = row * 15 + (col - 1);
272 if (source_index <
static_cast<int>(dungeon_palette->
size())) {
273 color = (*dungeon_palette)[source_index];
275 tooltip_label = absl::StrFormat(
"Dungeon row %d", row + 2);
280 ImVec4 display_color = color.
rgb();
283 ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 0.25f);
286 ImGui::ColorButton(
"##render_color", display_color,
287 ImGuiColorEditFlags_NoTooltip,
288 ImVec2(swatch_size, swatch_size));
290 ImGui::PopStyleVar();
293 if (clicked && editable) {
300 if (ImGui::IsItemHovered()) {
302 ImGui::SetTooltip(
"%s\nSlot %d\nSNES: 0x%04X\nRGB: (%d, %d, %d)",
303 tooltip_label.c_str(), source_index, color.
snes(),
304 static_cast<int>(display_color.x * 255),
305 static_cast<int>(display_color.y * 255),
306 static_cast<int>(display_color.z * 255));
308 ImGui::SetTooltip(
"%s", tooltip_label.c_str());
316 float max_size)
const {
317 const float available_width = std::max(ImGui::GetContentRegionAvail().x, 1.0f);
318 const float spacing =
319 std::max(ImGui::GetStyle().ItemSpacing.x, 2.0f) * (columns - 1);
320 const float raw = (available_width - spacing) / std::max(columns, 1);
321 return std::clamp(raw, min_size, max_size);
326 int color_index = -1;
327 std::string source_label;
329 !palette || color_index < 0 ||
330 color_index >=
static_cast<int>(palette->
size())) {
336 ImGui::SeparatorText(
337 absl::StrFormat(
"%s Color %d", source_label, color_index).c_str());
339 auto original_color = (*palette)[color_index];
341 "Color", &(*palette)[color_index],
342 ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_PickerHueWheel)) {
349 ImGui::Text(
"RGB (0-255): (%d, %d, %d)",
353 ImGui::Text(
"SNES BGR555: 0x%04X", original_color.snes());
357 original_color.rgb().y / 255.0f,
358 original_color.rgb().z / 255.0f, 1.0f);
359 (*palette)[color_index] = original_color;
369 const std::string& title) {
370 if (ImGui::BeginPopupModal(title.c_str(),
nullptr,
371 ImGuiWindowFlags_AlwaysAutoResize)) {
372 ImGui::Text(
"Enhanced Palette Editor");
378 if (ImGui::CollapsingHeader(
"Palette Analysis")) {
382 if (ImGui::CollapsingHeader(
"ROM Palette Manager") &&
rom_) {
404 ImGui::CloseCurrentPopup();
417 ImGui::Text(
"No ROM loaded");
430 ImGui::Text(
"Preview of %s:",
442 const std::string& title) {
447 ImGui::Text(
"Bitmap Color Analysis");
451 ImGui::Text(
"Bitmap is not active");
456 std::map<uint8_t, int> pixel_counts;
457 const auto& data = bitmap.
vector();
459 for (uint8_t pixel : data) {
460 uint8_t palette_index = pixel & 0x0F;
461 pixel_counts[palette_index]++;
464 ImGui::Text(
"Bitmap Size: %d x %d (%zu pixels)", bitmap.
width(),
465 bitmap.
height(), data.size());
468 ImGui::Text(
"Pixel Distribution:");
470 int total_pixels =
static_cast<int>(data.size());
474 .x_label =
"Palette Index",
476 .flags = ImPlotFlags_NoBoxSelect,
477 .x_axis_flags = ImPlotAxisFlags_AutoFit,
478 .y_axis_flags = ImPlotAxisFlags_AutoFit};
479 std::vector<double> x;
480 std::vector<double> y;
481 x.reserve(pixel_counts.size());
482 y.reserve(pixel_counts.size());
483 for (
const auto& [index, count] : pixel_counts) {
484 x.push_back(
static_cast<double>(index));
485 y.push_back(
static_cast<double>(count));
488 if (plot && !x.empty()) {
489 ImPlot::PlotBars(
"Usage", x.data(), y.data(),
static_cast<int>(x.size()),
490 0.67, 0.0, ImPlotBarsFlags_None);
507 if (palette_index >= 0 && palette_index < 8) {
519 }
catch (
const std::exception& e) {
546 const float swatch_size =
548 for (
int i = 0; i < static_cast<int>(palette.
size()); i++) {
552 auto color = palette[i];
553 ImVec4 display_color = color.rgb();
556 if (ImGui::ColorButton(
"##color", display_color,
557 ImGuiColorEditFlags_NoTooltip,
558 ImVec2(swatch_size, swatch_size))) {
565 if (ImGui::BeginPopupContextItem()) {
566 ImGui::Text(
"Color %d (0x%04X)", i, color.snes());
568 if (ImGui::MenuItem(
"Edit Color")) {
574 if (ImGui::MenuItem(
"Reset to Black")) {
583 if (ImGui::IsItemHovered()) {
584 ImGui::SetTooltip(
"Color %d\nSNES: 0x%04X\nRGB: (%d, %d, %d)", i,
585 color.snes(),
static_cast<int>(display_color.x * 255),
586 static_cast<int>(display_color.y * 255),
587 static_cast<int>(display_color.z * 255));
595 std::string popup_id =
598 ImGui::OpenPopup(popup_id.c_str());
599 if (ImGui::BeginPopupModal(popup_id.c_str(),
nullptr,
600 ImGuiWindowFlags_AlwaysAutoResize)) {
602 if (ImGui::ColorEdit4(
604 ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_DisplayRGB)) {
616 ImGui::CloseCurrentPopup();
621 ImGui::CloseCurrentPopup();
634 ImGui::Text(
"No ROM palettes available");
638 ImGui::Text(
"Palette Group:");
644 ImGui::Text(
"Palette Index:");
648 ImGui::Text(
"Preview:");
650 for (
int i = 0; i < 8 && i < static_cast<int>(preview_palette.size());
654 auto color = preview_palette[i];
655 ImVec4 display_color = color.rgb();
656 ImGui::ColorButton((
"##preview" + std::to_string(i)).c_str(),
657 display_color, ImGuiColorEditFlags_NoTooltip,
665 ImVec4 rgba = color.
rgb();
667 ImGui::PushID(color_index);
669 if (ImGui::ColorEdit4(
670 "##color_edit", &rgba.x,
671 ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_DisplayRGB)) {
675 ImGui::Text(
"SNES Color: 0x%04X", color.
snes());
677 int r = (color.
snes() & 0x1F);
678 int g = (color.
snes() >> 5) & 0x1F;
679 int b = (color.
snes() >> 10) & 0x1F;
681 if (ImGui::SliderInt(
"Red", &r, 0, 31)) {
682 uint16_t new_color = (color.
snes() & 0xFFE0) | (r & 0x1F);
685 if (ImGui::SliderInt(
"Green", &g, 0, 31)) {
686 uint16_t new_color = (color.
snes() & 0xFC1F) | ((g & 0x1F) << 5);
689 if (ImGui::SliderInt(
"Blue", &b, 0, 31)) {
690 uint16_t new_color = (color.
snes() & 0x83FF) | ((b & 0x1F) << 10);
698 ImGui::Text(
"Palette Information:");
699 ImGui::Text(
"Size: %zu colors", palette.
size());
701 std::map<uint16_t, int> color_frequency;
702 for (
int i = 0; i < static_cast<int>(palette.
size()); i++) {
703 color_frequency[palette[i].snes()]++;
706 ImGui::Text(
"Unique Colors: %zu", color_frequency.size());
708 if (color_frequency.size() < palette.
size()) {
709 ImGui::TextColored(ImVec4(1, 1, 0, 1),
710 "Warning: Duplicate colors detected!");
711 if (ImGui::TreeNode(
"Duplicate Colors")) {
712 for (
const auto& [
snes_color, count] : color_frequency) {
715 ImGui::ColorButton((
"##dup" + std::to_string(
snes_color)).c_str(),
716 display_color, ImGuiColorEditFlags_NoTooltip,
719 ImGui::Text(
"0x%04X appears %d times",
snes_color, count);
731 .x_label =
"Color Index",
733 .flags = ImPlotFlags_NoBoxSelect,
734 .x_axis_flags = ImPlotAxisFlags_AutoFit,
735 .y_axis_flags = ImPlotAxisFlags_AutoFit};
736 std::vector<double> x;
737 std::vector<double> y;
738 x.reserve(color_frequency.size());
739 y.reserve(color_frequency.size());
740 for (
const auto& [
snes_color, count] : color_frequency) {
742 y.push_back(
static_cast<double>(count));
745 if (plot && !x.empty()) {
746 ImPlot::PlotBars(
"Count", x.data(), y.data(),
static_cast<int>(x.size()),
747 0.5, 0.0, ImPlotBarsFlags_None);
751 float total_brightness = 0.0f;
752 float min_brightness = 1.0f;
753 float max_brightness = 0.0f;
755 for (
int i = 0; i < static_cast<int>(palette.
size()); i++) {
756 ImVec4 color = palette[i].rgb();
757 float brightness = (color.x + color.y + color.z) / 3.0f;
758 total_brightness += brightness;
759 min_brightness = std::min(min_brightness, brightness);
760 max_brightness = std::max(max_brightness, brightness);
763 float avg_brightness = total_brightness / palette.
size();
766 ImGui::Text(
"Brightness Analysis:");
767 ImGui::Text(
"Average: %.2f", avg_brightness);
768 ImGui::Text(
"Range: %.2f - %.2f", min_brightness, max_brightness);
770 ImGui::Text(
"Brightness Distribution:");
771 ImGui::ProgressBar(avg_brightness, ImVec2(-1, 0),
"Avg");
783 if (palette_groups.overworld_main.size() > 0) {
787 if (palette_groups.overworld_aux.size() > 0) {
791 if (palette_groups.overworld_animated.size() > 0) {
795 if (palette_groups.dungeon_main.size() > 0) {
799 if (palette_groups.global_sprites.size() > 0) {
803 if (palette_groups.armors.size() > 0) {
807 if (palette_groups.swords.size() > 0) {
813 }
catch (
const std::exception& e) {
814 LOG_ERROR(
"Enhanced Palette Editor",
"Failed to load ROM palettes");
The Rom class is used to load, save, and modify Rom data. This is a generic SNES ROM container and do...
void QueueTextureCommand(TextureCommandType type, Bitmap *bitmap)
Represents a bitmap image optimized for SNES ROM hacking.
const SnesPalette & palette() const
const std::vector< uint8_t > & vector() const
void SetPalette(const SnesPalette &palette)
Set the palette for the bitmap using SNES palette format.
void SetPaletteWithTransparent(const SnesPalette &palette, size_t index, int length=7)
Set the palette with a transparent color.
constexpr ImVec4 rgb() const
Get RGB values (WARNING: stored as 0-255 in ImVec4)
constexpr uint16_t snes() const
Get SNES 15-bit color.
Represents a palette of colors for the Super Nintendo Entertainment System (SNES).
static ThemeManager & Get()
#define LOG_ERROR(category, format,...)
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.
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.
IMGUI_API bool SnesColorEdit4(absl::string_view label, gfx::SnesColor *color, ImGuiColorEditFlags flags)
SNES color in 15-bit RGB format (BGR555)
PaletteGroup dungeon_main
auto mutable_palette(int i)
gfx::PaletteGroupMap palette_groups