7#include "absl/strings/str_format.h"
15#include "imgui/imgui.h"
16#include "imgui/misc/cpp/imgui_stdlib.h"
27constexpr int kSheetWidth = 128;
28constexpr int kSheetHeight = 32;
31ImU32
ToImU32(
const std::array<uint8_t, 4>& color) {
32 return IM_COL32(color[0], color[1], color[2], color[3]);
36 const auto rgb = color.
rgb();
37 return ImVec4(rgb.x / 255.0f, rgb.y / 255.0f, rgb.z / 255.0f, 1.0f);
40void DrawSwatch(
const char*
id,
const ImVec4& color,
const std::string& tip) {
41 ImGui::ColorButton(
id, color, ImGuiColorEditFlags_NoTooltip,
42 ImVec2(18.0f, 18.0f));
43 if (ImGui::IsItemHovered()) {
44 ImGui::SetTooltip(
"%s", tip.c_str());
56 const std::string& source) {
57 auto image = png_bytes.ok()
59 : absl::StatusOr<ImportedSheetImage>(png_bytes.status());
63 SetStatus(std::string(image.status().message()),
true);
66 image_ = std::move(*image);
74 "Imports keep the image's palette order and pixel indices. Use an "
75 "indexed PNG, or an RGB PNG with an 8-pixel palette strip under the "
76 "art (8x8 swatches, swatch k = color k). Colors outside the strip are "
77 "an error, never a nearest match.");
80 png_filter.
filters.push_back({
"PNG image",
"png"});
81 ImGui::SetNextItemWidth(320.0f);
82 ImGui::InputTextWithHint(
"File##ImageImportPath",
"empty: choose in a dialog",
85 "PNG path for Open and the save buttons. Leave it empty to choose the "
88 const std::string path =
97 ImGui::BeginDisabled(!clipboard);
101 ImGui::EndDisabled();
104 ?
"Reads the clipboard image. An indexed PNG keeps its indices; a "
105 "TIFF-only image arrives as RGB and needs a palette strip."
106 :
"The image clipboard is only available on macOS.");
109 ImGui::PushTextWrapPos(0.0f);
113 ImGui::TextDisabled(
"%s",
status_.c_str());
115 ImGui::PopTextWrapPos();
118 return absl::OkStatus();
128 return absl::OkStatus();
132 const auto& image = *
image_;
134 for (
size_t i = 0; i < image.palette.size(); ++i) {
135 const auto& c = image.palette[i];
137 ImGui::SameLine(0.0f, 2.0f);
139 DrawSwatch(absl::StrFormat(
"##ImportColor%zu", i).c_str(),
140 ImVec4(c[0] / 255.0f, c[1] / 255.0f, c[2] / 255.0f, 1.0f),
141 absl::StrFormat(
"%zu: #%02X%02X%02X%s", i, c[0], c[1], c[2],
142 c[3] == 0 ?
" (transparent)" :
""));
145 const size_t pixels =
static_cast<size_t>(image.width) * image.height;
146 if (pixels > kMaxPreviewPixels) {
147 ImGui::TextDisabled(
"Image is too large to preview");
150 const float avail = std::max(ImGui::GetContentRegionAvail().x, 64.0f);
151 const float scale = std::clamp(
152 std::floor(avail /
static_cast<float>(image.width)), 1.0f, 3.0f);
153 const ImVec2 origin = ImGui::GetCursorScreenPos();
154 auto* draw = ImGui::GetWindowDrawList();
155 for (
int y = 0; y < image.height; ++y) {
156 for (
int x = 0; x < image.width; ++x) {
157 const uint8_t index =
158 image.indices[
static_cast<size_t>(y) * image.width + x];
159 if (index >= image.palette.size()) {
162 const ImVec2 a(origin.x + x * scale, origin.y + y * scale);
163 draw->AddRectFilled(a, ImVec2(a.x + scale, a.y + scale),
164 ToImU32(image.palette[index]));
167 const ImVec2 size(image.width * scale, image.height * scale);
168 draw->AddRect(origin, ImVec2(origin.x + size.x, origin.y + size.y),
169 IM_COL32(128, 128, 128, 255));
174 const auto& image = *
image_;
175 auto save_path = [
this](
const std::string& default_name,
176 const std::string& extension) {
178 default_name, extension)
181 auto save = [&](absl::StatusOr<std::vector<uint8_t>> bytes,
182 const std::string& default_name,
183 const std::string& extension) {
185 SetStatus(std::string(bytes.status().message()),
true);
188 const std::string path = save_path(default_name, extension);
193 SetStatus(status.ok() ? absl::StrFormat(
"Saved %zu bytes to %s",
195 : std::string(status.message()),
202 HOVER_HINT(
"SNES 4bpp tiles, 32 bytes per 8x8 tile, tiles row-major.");
204 ImGui::BeginDisabled(image.max_index > 7);
208 ImGui::EndDisabled();
210 ?
"The art uses indices above 7, which 3bpp cannot hold."
211 :
"SNES 3bpp tiles, 24 bytes per 8x8 tile, tiles row-major.");
216 HOVER_HINT(
"The art without the strip, as an indexed PNG in index order.");
220 const auto& image = *
image_;
222 const bool arena_is_ours =
225 const bool fits = image.width == kSheetWidth &&
226 image.height == kSheetHeight && image.max_index <= 7;
227 ImGui::BeginDisabled(!arena_is_ours || !fits);
228 const std::string label = absl::StrFormat(
231 if (ImGui::Button(label.c_str())) {
233 for (
size_t i = 0; i < palette.size() && i < image.palette.size(); ++i) {
234 palette[i] = {image.palette[i][0], image.palette[i][1],
235 image.palette[i][2]};
238 const std::vector<uint8_t> pixels = sheet_id < sheets.size()
239 ? sheets[sheet_id].vector()
240 : std::vector<uint8_t>{};
242 auto preview = png.ok()
244 : absl::StatusOr<SheetPngImportPreview>(png.status());
245 const absl::Status status =
249 "; undo reverts it, Save writes the ROM"
250 : std::string(status.message()),
253 ImGui::EndDisabled();
256 ?
"The sheets in memory belong to another open ROM."
257 : (!fits ?
"Only 128x32 art with indices 0-7 fits a 3bpp sheet."
258 :
"Writes the indices as they are, as one undoable step. "
259 "The palette is not changed."));
263 const auto& image = *
image_;
268 selection !=
nullptr && data !=
nullptr
271 if (selection ==
nullptr || group ==
nullptr || selection->
palette < 0 ||
272 selection->
palette >=
static_cast<int>(group->
size())) {
274 "To import the image's colors too, pick a preview palette for sheet "
275 "0x%02X in Palette Controls. Colors are never imported "
285 std::min<int>(7,
static_cast<int>(image.palette.size()) - 1);
288 const std::string label =
290 " Import colors 1-%d into %s palette %d row "
291 "%d...###ImageImportPalette",
293 ImGui::BeginDisabled(!slots.ok() || count < 1);
294 if (ImGui::Button(label.c_str())) {
295 ImGui::OpenPopup(
"Import palette colors?###ImageImportPaletteConfirm");
297 ImGui::EndDisabled();
300 std::string(slots.status().message()).c_str());
304 "Writes the image's colors 1-7 into the sheet's preview palette row, "
305 "after a confirmation. Undo reverts it. Index 0 stays transparent.");
307 if (!ImGui::BeginPopupModal(
308 "Import palette colors?###ImageImportPaletteConfirm",
nullptr,
309 ImGuiWindowFlags_AlwaysAutoResize)) {
313 "%s palette %d, row %d (SNES colors are 15-bit; low bits "
316 std::vector<gfx::SnesColor> colors;
317 for (
int i = 0; i < count; ++i) {
318 const auto& c = image.palette[i + 1];
319 colors.emplace_back(c[0], c[1], c[2]);
320 const int slot = (*slots)[i];
321 DrawSwatch(absl::StrFormat(
"##Old%d", slot).c_str(), ToImVec4(target[slot]),
322 absl::StrFormat(
"Slot %d now", slot));
326 DrawSwatch(absl::StrFormat(
"##New%d", slot).c_str(), ToImVec4(colors[i]),
327 absl::StrFormat(
"Image color %d: #%02X%02X%02X", i + 1, c[0],
330 ImGui::Text(
"slot %d <- image color %d", slot, i + 1);
332 if (ImGui::Button(
"Import colors")) {
334 absl::Status status = absl::OkStatus();
335 if (!manager.IsManaging(data)) {
336 status = absl::FailedPreconditionError(
337 "Palette editing is not active for this ROM");
339 manager.BeginBatch();
340 for (
int i = 0; i < count && status.ok(); ++i) {
341 status = manager.SetColor(chosen.
group, chosen.
palette, (*slots)[i],
347 SetStatus(status.ok() ? absl::StrFormat(
"Imported %d colors into %s "
351 : std::string(status.message()),
353 ImGui::CloseCurrentPopup();
356 if (ImGui::Button(
"Cancel")) {
357 ImGui::CloseCurrentPopup();
uint16_t current_sheet_id
zelda3::GameData * game_data
const GraphicsPaletteSelection * PreviewPalette(uint16_t sheet) const
std::optional< ImportedSheetImage > image_
void Load(absl::StatusOr< std::vector< uint8_t > > png_bytes, const std::string &source)
void SetStatus(std::string message, bool is_error)
void DrawWriteIntoSheet()
GraphicsEditorState * state_
UndoManager * undo_manager_
const void * gfx_sheets_owner() const
GraphicsSheets & gfx_sheets()
static PaletteManager & Get()
Get the singleton instance.
constexpr ImVec4 rgb() const
Get RGB values (WARNING: stored as 0-255 in ImVec4)
static std::string ShowSaveFileDialog(const std::string &default_name="", const std::string &default_extension="")
ShowSaveFileDialog opens a save file dialog and returns the selected filepath. Uses global feature fl...
static std::string ShowOpenFileDialog()
ShowOpenFileDialog opens a file dialog and returns the selected filepath. Uses global feature flag to...
#define ICON_MD_FILE_OPEN
#define ICON_MD_ARROW_FORWARD
#define ICON_MD_FILE_UPLOAD
#define ICON_MD_CONTENT_PASTE
PNG images on the system clipboard. SDL's clipboard is text-only.
#define HOVER_HINT(string)
void DrawSwatch(const char *id, const ImVec4 &color, const std::string &tip)
constexpr size_t kMaxPreviewPixels
ImU32 ToImU32(const std::array< uint8_t, 4 > &color)
ImVec4 ToImVec4(const gfx::SnesColor &color)
Editors are the view controllers for the application.
std::string DescribeImportedSheetImage(const ImportedSheetImage &image)
"128x32 indexed PNG, 8 colors (strip removed), highest index 7".
std::string DescribeSheetPngImport(const SheetPngImportPreview &preview)
"Sheet 0x20: blocks 0, 3 (5 tiles)" for the preview list.
absl::StatusOr< ImportedSheetImage > ImportSheetImagePng(const std::vector< uint8_t > &png_bytes)
Decodes PNG bytes (file or clipboard) and calls ImportSheetImage.
absl::StatusOr< std::vector< uint8_t > > EncodeImportedSheetTiles(const ImportedSheetImage &image, int bpp)
absl::StatusOr< std::vector< int > > PreviewPaletteSlots(std::string_view group_name, size_t palette_size, int row, int count)
void RefreshGraphicsPreviewPalettes(GraphicsEditorState &state)
absl::StatusOr< std::vector< uint8_t > > EncodeImportedSheetPng(const ImportedSheetImage &image)
Re-encodes the art (without the strip) as an indexed PNG in index order.
absl::StatusOr< SheetPngImportPreview > PreviewSheetPngImport(uint16_t sheet_id, const std::vector< uint8_t > &indexed_pixels, const std::vector< uint8_t > &png_bytes, const zelda3::SheetPalette &palette, int first_block)
absl::Status ApplySheetPngImport(const SheetPngImportPreview &preview, GraphicsEditorState &state, UndoManager *undo_manager)
absl::StatusOr< std::vector< uint8_t > > GetClipboardPng(const std::string &)
absl::Status WriteBinaryFile(const std::string &path, const std::vector< uint8_t > &bytes)
absl::StatusOr< std::vector< uint8_t > > ReadBinaryFile(const std::string &path)
bool ImageClipboardSupported()
std::array< SheetColor, 8 > SheetPalette
Colors for 3bpp indices 0-7. Index 0 is exported as transparent.
PNG export and import for the Graphics editor.
PaletteGroup * get_group(const std::string &group_name)
Represents a group of palettes.
const SnesPalette & palette_ref(int i) const
std::vector< FileDialogFilter > filters
gfx::PaletteGroupMap palette_groups