yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
palette_widget.cc
Go to the documentation of this file.
2
3#include <algorithm>
4#include <map>
5#include "app/gfx/arena.h"
6#include "app/gui/color.h"
7#include "util/log.h"
8
9namespace yaze {
10namespace gui {
11
13 rom_ = rom;
15 if (rom_) {
17 }
18}
19
20void PaletteWidget::ShowPaletteEditor(gfx::SnesPalette& palette, const std::string& title) {
21 if (ImGui::BeginPopupModal(title.c_str(), nullptr, ImGuiWindowFlags_AlwaysAutoResize)) {
22 ImGui::Text("Enhanced Palette Editor");
23 ImGui::Separator();
24
25 // Palette grid editor
26 DrawPaletteGrid(palette);
27
28 ImGui::Separator();
29
30 // Analysis and tools
31 if (ImGui::CollapsingHeader("Palette Analysis")) {
32 DrawPaletteAnalysis(palette);
33 }
34
35 if (ImGui::CollapsingHeader("ROM Palette Manager") && rom_) {
37
38 if (ImGui::Button("Apply ROM Palette") && !rom_palette_groups_.empty()) {
39 if (current_group_index_ < static_cast<int>(rom_palette_groups_.size())) {
41 }
42 }
43 }
44
45 ImGui::Separator();
46
47 // Action buttons
48 if (ImGui::Button("Save Backup")) {
49 SavePaletteBackup(palette);
50 }
51 ImGui::SameLine();
52 if (ImGui::Button("Restore Backup")) {
53 RestorePaletteBackup(palette);
54 }
55 ImGui::SameLine();
56 if (ImGui::Button("Close")) {
57 ImGui::CloseCurrentPopup();
58 }
59
60 ImGui::EndPopup();
61 }
62}
63
65 if (!show_rom_manager_) return;
66
67 if (ImGui::Begin("ROM Palette Manager", &show_rom_manager_)) {
68 if (!rom_) {
69 ImGui::Text("No ROM loaded");
70 ImGui::End();
71 return;
72 }
73
76 }
77
79
80 if (current_group_index_ < static_cast<int>(rom_palette_groups_.size())) {
81 ImGui::Separator();
82 ImGui::Text("Preview of %s:", palette_group_names_[current_group_index_].c_str());
83
84 const auto& preview_palette = rom_palette_groups_[current_group_index_];
85 DrawPaletteGrid(const_cast<gfx::SnesPalette&>(preview_palette));
86
87 DrawPaletteAnalysis(preview_palette);
88 }
89 }
90 ImGui::End();
91}
92
93void PaletteWidget::ShowColorAnalysis(const gfx::Bitmap& bitmap, const std::string& title) {
94 if (!show_color_analysis_) return;
95
96 if (ImGui::Begin(title.c_str(), &show_color_analysis_)) {
97 ImGui::Text("Bitmap Color Analysis");
98 ImGui::Separator();
99
100 if (!bitmap.is_active()) {
101 ImGui::Text("Bitmap is not active");
102 ImGui::End();
103 return;
104 }
105
106 // Analyze pixel distribution
107 std::map<uint8_t, int> pixel_counts;
108 const auto& data = bitmap.vector();
109
110 for (uint8_t pixel : data) {
111 uint8_t palette_index = pixel & 0x0F; // 4-bit palette index
112 pixel_counts[palette_index]++;
113 }
114
115 ImGui::Text("Bitmap Size: %d x %d (%zu pixels)",
116 bitmap.width(), bitmap.height(), data.size());
117
118 ImGui::Separator();
119 ImGui::Text("Pixel Distribution:");
120
121 // Show distribution as bars
122 int total_pixels = static_cast<int>(data.size());
123 for (const auto& [index, count] : pixel_counts) {
124 float percentage = (static_cast<float>(count) / total_pixels) * 100.0f;
125 ImGui::Text("Index %d: %d pixels (%.1f%%)", index, count, percentage);
126
127 // Progress bar visualization
128 ImGui::SameLine();
129 ImGui::ProgressBar(percentage / 100.0f, ImVec2(100, 0));
130
131 // Color swatch if palette is available
132 if (index < static_cast<int>(bitmap.palette().size())) {
133 ImGui::SameLine();
134 auto color = bitmap.palette()[index];
135 ImVec4 display_color = color.rgb();
136 ImGui::ColorButton(("##color" + std::to_string(index)).c_str(),
137 display_color, ImGuiColorEditFlags_NoTooltip, ImVec2(20, 20));
138 if (ImGui::IsItemHovered()) {
139 ImGui::SetTooltip("SNES Color: 0x%04X\nRGB: (%d, %d, %d)",
140 color.snes(),
141 static_cast<int>(display_color.x * 255),
142 static_cast<int>(display_color.y * 255),
143 static_cast<int>(display_color.z * 255));
144 }
145 }
146 }
147 }
148 ImGui::End();
149}
150
151bool PaletteWidget::ApplyROMPalette(gfx::Bitmap* bitmap, int group_index, int palette_index) {
152 if (!bitmap || !rom_palettes_loaded_ ||
153 group_index < 0 || group_index >= static_cast<int>(rom_palette_groups_.size())) {
154 return false;
155 }
156
157 try {
158 const auto& selected_palette = rom_palette_groups_[group_index];
159
160 // Save current palette as backup
161 SavePaletteBackup(bitmap->palette());
162
163 // Apply new palette
164 if (palette_index >= 0 && palette_index < 8) {
165 bitmap->SetPaletteWithTransparent(selected_palette, palette_index);
166 } else {
167 bitmap->SetPalette(selected_palette);
168 }
169
170 // Queue texture update via Arena's deferred system
173
174 current_group_index_ = group_index;
175 current_palette_index_ = palette_index;
176
177 return true;
178
179 } catch (const std::exception& e) {
180 return false;
181 }
182}
183
186 current_group_index_ >= static_cast<int>(rom_palette_groups_.size())) {
187 return nullptr;
188 }
189
191}
192
194 backup_palette_ = palette;
195}
196
198 if (backup_palette_.size() == 0) {
199 return false;
200 }
201
202 palette = backup_palette_;
203 return true;
204}
205
207 for (int i = 0; i < static_cast<int>(palette.size()); i++) {
208 if (i % cols != 0) ImGui::SameLine();
209
210 auto color = palette[i];
211 ImVec4 display_color = color.rgb();
212
213 ImGui::PushID(i);
214
215 // Color button with editing capability
216 if (ImGui::ColorButton("##color", display_color,
217 ImGuiColorEditFlags_NoTooltip, ImVec2(30, 30))) {
219 temp_color_ = display_color;
220 }
221
222 // Context menu for individual colors
223 if (ImGui::BeginPopupContextItem()) {
224 ImGui::Text("Color %d (0x%04X)", i, color.snes());
225 ImGui::Separator();
226
227 if (ImGui::MenuItem("Edit Color")) {
229 temp_color_ = display_color;
230 }
231
232 if (ImGui::MenuItem("Copy Color")) {
233 // Could implement color clipboard here
234 }
235
236 if (ImGui::MenuItem("Reset to Black")) {
237 palette[i] = gfx::SnesColor(0);
238 }
239
240 ImGui::EndPopup();
241 }
242
243 // Tooltip with detailed info
244 if (ImGui::IsItemHovered()) {
245 ImGui::SetTooltip("Color %d\nSNES: 0x%04X\nRGB: (%d, %d, %d)\nClick to edit",
246 i, color.snes(),
247 static_cast<int>(display_color.x * 255),
248 static_cast<int>(display_color.y * 255),
249 static_cast<int>(display_color.z * 255));
250 }
251
252 ImGui::PopID();
253 }
254
255 // Color editor popup
256 if (editing_color_index_ >= 0) {
257 ImGui::OpenPopup("Edit Color");
258
259 if (ImGui::BeginPopupModal("Edit Color", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) {
260 ImGui::Text("Editing Color %d", editing_color_index_);
261
262 if (ImGui::ColorEdit4("Color", &temp_color_.x,
263 ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_DisplayRGB)) {
264 // Update the palette color in real-time
265 auto new_snes_color = gfx::SnesColor(temp_color_);
266 palette[editing_color_index_] = new_snes_color;
267 }
268
269 if (ImGui::Button("Apply")) {
271 ImGui::CloseCurrentPopup();
272 }
273 ImGui::SameLine();
274 if (ImGui::Button("Cancel")) {
276 ImGui::CloseCurrentPopup();
277 }
278
279 ImGui::EndPopup();
280 }
281 }
282}
283
287 }
288
289 if (rom_palette_groups_.empty()) {
290 ImGui::Text("No ROM palettes available");
291 return;
292 }
293
294 // Group selector
295 ImGui::Text("Palette Group:");
296 if (ImGui::Combo("##PaletteGroup", &current_group_index_,
297 [](void* data, int idx, const char** out_text) -> bool {
298 auto* names = static_cast<std::vector<std::string>*>(data);
299 if (idx < 0 || idx >= static_cast<int>(names->size())) return false;
300 *out_text = (*names)[idx].c_str();
301 return true;
302 }, &palette_group_names_, static_cast<int>(palette_group_names_.size()))) {
303 // Group changed - could trigger preview update
304 }
305
306 // Palette index selector
307 ImGui::Text("Palette Index:");
308 ImGui::SliderInt("##PaletteIndex", &current_palette_index_, 0, 7, "%d");
309
310 // Quick palette preview
311 if (current_group_index_ < static_cast<int>(rom_palette_groups_.size())) {
312 ImGui::Text("Preview:");
313 const auto& preview_palette = rom_palette_groups_[current_group_index_];
314
315 // Show just first 8 colors in a row
316 for (int i = 0; i < 8 && i < static_cast<int>(preview_palette.size()); i++) {
317 if (i > 0) ImGui::SameLine();
318 auto color = preview_palette[i];
319 ImVec4 display_color = color.rgb();
320 ImGui::ColorButton(("##preview" + std::to_string(i)).c_str(),
321 display_color, ImGuiColorEditFlags_NoTooltip, ImVec2(20, 20));
322 }
323 }
324}
325
327 ImVec4 rgba = color.rgb();
328
329 ImGui::PushID(color_index);
330
331 if (ImGui::ColorEdit4("##color_edit", &rgba.x,
332 ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_DisplayRGB)) {
333 color = gfx::SnesColor(rgba);
334 }
335
336 // SNES-specific controls
337 ImGui::Text("SNES Color: 0x%04X", color.snes());
338
339 // Individual RGB component sliders (0-31 for SNES)
340 int r = (color.snes() & 0x1F);
341 int g = (color.snes() >> 5) & 0x1F;
342 int b = (color.snes() >> 10) & 0x1F;
343
344 if (ImGui::SliderInt("Red", &r, 0, 31)) {
345 uint16_t new_color = (color.snes() & 0xFFE0) | (r & 0x1F);
346 color = gfx::SnesColor(new_color);
347 }
348
349 if (ImGui::SliderInt("Green", &g, 0, 31)) {
350 uint16_t new_color = (color.snes() & 0xFC1F) | ((g & 0x1F) << 5);
351 color = gfx::SnesColor(new_color);
352 }
353
354 if (ImGui::SliderInt("Blue", &b, 0, 31)) {
355 uint16_t new_color = (color.snes() & 0x83FF) | ((b & 0x1F) << 10);
356 color = gfx::SnesColor(new_color);
357 }
358
359 ImGui::PopID();
360}
361
363 ImGui::Text("Palette Information:");
364 ImGui::Text("Size: %zu colors", palette.size());
365
366 // Color distribution analysis
367 std::map<uint16_t, int> color_frequency;
368 for (int i = 0; i < static_cast<int>(palette.size()); i++) {
369 color_frequency[palette[i].snes()]++;
370 }
371
372 ImGui::Text("Unique Colors: %zu", color_frequency.size());
373
374 if (color_frequency.size() < palette.size()) {
375 ImGui::TextColored(ImVec4(1, 1, 0, 1), "Warning: Duplicate colors detected!");
376
377 if (ImGui::TreeNode("Duplicate Colors")) {
378 for (const auto& [snes_color, count] : color_frequency) {
379 if (count > 1) {
380 ImVec4 display_color = gfx::SnesColor(snes_color).rgb();
381 ImGui::ColorButton(("##dup" + std::to_string(snes_color)).c_str(),
382 display_color, ImGuiColorEditFlags_NoTooltip, ImVec2(16, 16));
383 ImGui::SameLine();
384 ImGui::Text("0x%04X appears %d times", snes_color, count);
385 }
386 }
387 ImGui::TreePop();
388 }
389 }
390
391 // Brightness analysis
392 float total_brightness = 0.0f;
393 float min_brightness = 1.0f;
394 float max_brightness = 0.0f;
395
396 for (int i = 0; i < static_cast<int>(palette.size()); i++) {
397 ImVec4 color = palette[i].rgb();
398 float brightness = (color.x + color.y + color.z) / 3.0f;
399 total_brightness += brightness;
400 min_brightness = std::min(min_brightness, brightness);
401 max_brightness = std::max(max_brightness, brightness);
402 }
403
404 float avg_brightness = total_brightness / palette.size();
405
406 ImGui::Separator();
407 ImGui::Text("Brightness Analysis:");
408 ImGui::Text("Average: %.2f", avg_brightness);
409 ImGui::Text("Range: %.2f - %.2f", min_brightness, max_brightness);
410
411 // Show brightness as progress bar
412 ImGui::Text("Brightness Distribution:");
413 ImGui::ProgressBar(avg_brightness, ImVec2(-1, 0), "Avg");
414}
415
417 if (!rom_ || rom_palettes_loaded_) return;
418
419 try {
420 const auto& palette_groups = rom_->palette_group();
421 rom_palette_groups_.clear();
422 palette_group_names_.clear();
423
424 // Load all available palette groups
425 if (palette_groups.overworld_main.size() > 0) {
426 rom_palette_groups_.push_back(palette_groups.overworld_main[0]);
427 palette_group_names_.push_back("Overworld Main");
428 }
429 if (palette_groups.overworld_aux.size() > 0) {
430 rom_palette_groups_.push_back(palette_groups.overworld_aux[0]);
431 palette_group_names_.push_back("Overworld Aux");
432 }
433 if (palette_groups.overworld_animated.size() > 0) {
434 rom_palette_groups_.push_back(palette_groups.overworld_animated[0]);
435 palette_group_names_.push_back("Overworld Animated");
436 }
437 if (palette_groups.dungeon_main.size() > 0) {
438 rom_palette_groups_.push_back(palette_groups.dungeon_main[0]);
439 palette_group_names_.push_back("Dungeon Main");
440 }
441 if (palette_groups.global_sprites.size() > 0) {
442 rom_palette_groups_.push_back(palette_groups.global_sprites[0]);
443 palette_group_names_.push_back("Global Sprites");
444 }
445 if (palette_groups.armors.size() > 0) {
446 rom_palette_groups_.push_back(palette_groups.armors[0]);
447 palette_group_names_.push_back("Armor");
448 }
449 if (palette_groups.swords.size() > 0) {
450 rom_palette_groups_.push_back(palette_groups.swords[0]);
451 palette_group_names_.push_back("Swords");
452 }
453
455
456 } catch (const std::exception& e) {
457 LOG_ERROR("Enhanced Palette Editor", "Failed to load ROM palettes");
458 }
459}
460
461} // namespace gui
462} // namespace yaze
The Rom class is used to load, save, and modify Rom data.
Definition rom.h:71
auto palette_group() const
Definition rom.h:213
void QueueTextureCommand(TextureCommandType type, Bitmap *bitmap)
Definition arena.cc:32
static Arena & Get()
Definition arena.cc:15
Represents a bitmap image optimized for SNES ROM hacking.
Definition bitmap.h:66
const SnesPalette & palette() const
Definition bitmap.h:251
const std::vector< uint8_t > & vector() const
Definition bitmap.h:261
bool is_active() const
Definition bitmap.h:264
int height() const
Definition bitmap.h:254
void SetPalette(const SnesPalette &palette)
Set the palette for the bitmap.
Definition bitmap.cc:292
int width() const
Definition bitmap.h:253
void SetPaletteWithTransparent(const SnesPalette &palette, size_t index, int length=7)
Set the palette with a transparent color.
Definition bitmap.cc:303
SNES Color container.
Definition snes_color.h:38
constexpr ImVec4 rgb() const
Definition snes_color.h:74
constexpr uint16_t snes() const
Definition snes_color.h:76
Represents a palette of colors for the Super Nintendo Entertainment System (SNES).
void DrawPaletteAnalysis(const gfx::SnesPalette &palette)
const gfx::SnesPalette * GetSelectedROMPalette() const
Get the currently selected ROM palette.
std::vector< gfx::SnesPalette > rom_palette_groups_
void SavePaletteBackup(const gfx::SnesPalette &palette)
Save current palette as backup.
void ShowROMPaletteManager()
Show the ROM palette manager window.
void ShowColorAnalysis(const gfx::Bitmap &bitmap, const std::string &title="Color Analysis")
Show color analysis window for a bitmap.
void DrawPaletteGrid(gfx::SnesPalette &palette, int cols=8)
void Initialize(Rom *rom)
Initialize the palette editor with ROM data.
void ShowPaletteEditor(gfx::SnesPalette &palette, const std::string &title="Palette Editor")
Show the main palette editor window.
bool RestorePaletteBackup(gfx::SnesPalette &palette)
Restore palette from backup.
std::vector< std::string > palette_group_names_
bool ApplyROMPalette(gfx::Bitmap *bitmap, int group_index, int palette_index)
Apply a ROM palette group to a bitmap.
void DrawColorEditControls(gfx::SnesColor &color, int color_index)
gfx::SnesPalette backup_palette_
#define LOG_ERROR(category, format,...)
Definition log.h:110
Main namespace for the application.
SNES color in 15-bit RGB format (BGR555)
Definition yaze.h:208