10#include "imgui/imgui.h"
11#include "imgui/misc/cpp/imgui_stdlib.h"
22 for (
int i = 0; i < 16; ++i) {
23 const int level = i * 31 / ((1 << asset.
bpp) - 1);
24 asset.
palette[i] = std::min(level, 31) * 0x421;
38 return "Edit graphics scratch asset";
52 return absl::OkStatus();
61 std::string library_path)
62 : state_(state), history_(history), path_(std::move(library_path)) {
88 path_ = (*config /
"GraphicsScratch.json").
string();
95 library_ = std::move(loaded->library);
96 existed_ = loaded->bytes.has_value();
104 error_ = status.ok() ?
"" : status.ToString();
109 return absl::OkStatus();
111 return absl::FailedPreconditionError(
112 "Existing scratch library was not loaded; it will not be replaced");
114 return absl::FailedPreconditionError(
115 "Finish the graphics scratch stroke before saving");
116 const auto expected =
122 return absl::OkStatus();
135 std::make_unique<LibraryEdit>(
this, std::move(before),
library_));
136 const auto status =
Flush();
142 if (!
stroke_ || ImGui::IsMouseDown(ImGuiMouseButton_Left))
144 auto before = std::move(*
stroke_);
146 Record(std::move(before));
152 return absl::FailedPreconditionError(
153 "Finish the scratch stroke before changing assets");
154 return absl::OkStatus();
160 {asset.source_path, asset.source_offset, asset.bpp});
162 asset.original_chunk = found->second;
169 return absl::FailedPreconditionError(
"Scratch library could not be loaded");
172 return absl::FailedPreconditionError(
173 "Load graphics in this ROM session first");
178 asset.
pixels.assign(pixels.begin(), pixels.end());
180 DefaultPalette(asset);
183 for (
size_t i = 0; i < target->palette.size() && i < 16; ++i)
184 asset.
palette[i] = target->palette[i].snes();
185 }
else if (
const auto* colors =
187 for (
int i = 0; i < colors->ncolors && i < 8; ++i) {
188 const auto& c = colors->colors[i];
189 asset.
palette[i] = (c.r >> 3) | ((c.g >> 3) << 5) | ((c.b >> 3) << 10);
197 return Record(std::move(before));
205 return absl::FailedPreconditionError(
"Scratch library could not be loaded");
208 auto* asset = &imported;
209 DefaultPalette(*asset);
210 for (
size_t i = 0; i < palette.
size() && i < 16; ++i)
211 asset->palette[i] = palette[i].snes();
217 return Record(std::move(before));
224 if (!asset || !store)
225 return absl::FailedPreconditionError(
226 "Select a scratch asset and load session graphics first");
229 std::any_of(asset->pixels.begin(), asset->pixels.end(),
230 [](uint8_t pixel) { return pixel > 7; }))
231 return absl::FailedPreconditionError(
232 "This ROM writer supports writable 3bpp sheets and indices 0-7; keep "
233 "other assets as BIN files");
243 return absl::OkStatus();
248 history_->
Push(std::make_unique<GraphicsPixelEditAction>(
249 state_, std::move(diff),
"Apply graphics scratch asset"));
250 return absl::OkStatus();
257 return absl::FailedPreconditionError(
"Select a scratch asset");
260 if (result.same_source) {
262 std::move(result.published_chunk);
265 const auto saved =
Flush();
269 "BIN exported; updated scratch baseline remains unsaved: " +
270 std::string(saved.message()));
272 return absl::OkStatus();
276 const std::vector<ScratchColorIdTarget>& targets,
280 return absl::FailedPreconditionError(
"Scratch library could not be loaded");
283 if (plan.edit.ranges.empty())
284 return absl::OkStatus();
289 return Record(std::move(before));
296 const uint64_t asset_id = selected.
id;
297 const int bpp = selected.
bpp;
298 const int last_id = (1 << bpp) - 1;
299 ImGui::TextUnformatted(
"Swap color IDs");
300 ImGui::SetNextItemWidth(120);
301 ImGui::InputInt(
"From ID##scratch_swap", &
swap_from_id_, 0, 0);
304 ImGui::SetNextItemWidth(120);
305 ImGui::InputInt(
"To ID##scratch_swap", &
swap_to_id_, 0, 0);
311 ImGui::TextDisabled(
"ID 0 stays transparent; IDs 1-%d at %dbpp.", last_id,
314 if (ImGui::Button(
"Swap color IDs##scratch")) {
315 std::vector<ScratchColorIdTarget> targets;
317 if (candidate.id == asset_id ||
320 targets.insert(targets.end(), whole.begin(), whole.end());
325 : permutation.status());
328 ImGui::EndDisabled();
333 ImGui::SliderFloat(
"Zoom", &
zoom_, 2, 8,
"%.0fx");
335 ImGui::SliderInt(
"Color", &
color_, 0, (1 << asset.
bpp) - 1);
336 ImGui::BeginChild(
"ScratchPixels", ImVec2(0, 32 *
zoom_ + 24),
false,
337 ImGuiWindowFlags_HorizontalScrollbar);
338 const auto origin = ImGui::GetCursorScreenPos();
339 ImGui::InvisibleButton(
341 ImGuiButtonFlags_MouseButtonLeft | ImGuiButtonFlags_MouseButtonRight);
343 auto* draw = ImGui::GetWindowDrawList();
344 for (
int y = 0; y < 32; ++y) {
345 for (
int x = 0; x < 128; ++x) {
348 IM_COL32((c & 31) * 255 / 31, ((c >> 5) & 31) * 255 / 31,
349 ((c >> 10) & 31) * 255 / 31, 255);
352 draw->PrimReserve(6, 4);
354 ImVec2(origin.x + x *
zoom_, origin.y + y *
zoom_),
355 ImVec2(origin.x + (x + 1) *
zoom_, origin.y + (y + 1) *
zoom_),
359 if (ImGui::IsItemHovered()) {
360 const auto mouse = ImGui::GetMousePos();
361 const int x =
static_cast<int>((mouse.x - origin.x) /
zoom_);
362 const int y =
static_cast<int>((mouse.y - origin.y) /
zoom_);
363 if (x >= 0 && x < 128 && y >= 0 && y < 32) {
364 auto& pixel = asset.
pixels[y * 128 + x];
365 if (ImGui::IsMouseClicked(ImGuiMouseButton_Right))
367 if (ImGui::IsMouseDown(ImGuiMouseButton_Left) && pixel !=
color_) {
381 uint64_t hash = 1469598103934665603ULL;
382 for (
const auto pixel : asset->pixels)
383 hash = (hash ^ pixel) * 1099511628211ULL;
384 for (
const auto color : asset->palette)
385 hash = (hash ^ color) * 1099511628211ULL;
394 const size_t bytes_per_pixel = indexed ? 1 : 4;
395 if (image->width <= 0 || image->height <= 0 || image->width > 1024 ||
396 image->height > 1024 ||
397 image->pixels.size() !=
static_cast<size_t>(image->width) *
398 image->height * bytes_per_pixel) {
399 preview_error_ =
"Associated preview returned invalid image dimensions";
402 const int depth = indexed ? 8 : 32;
413 std::vector<SDL_Color> colors(image->colors.begin(),
414 image->colors.end());
415 if (image->index0_transparent)
429 ImGui::TextWrapped(
"Associated sprite preview: %s",
preview_label_.c_str());
431 "Captured pose; scratch pixels update before BIN export.");
433 ImGui::TextWrapped(
"Preview unavailable: %s",
preview_error_.c_str());
438 const float scale = std::clamp(
459 "Keep named sheets here until you choose their destination. Left drag "
460 "paints; right click picks a color. Scratch assets retain their own "
461 "pixels and palette.");
462 ImGui::TextWrapped(
"Library: %s",
path_.c_str());
464 ImGui::TextWrapped(
"%s",
error_.c_str());
467 ImGui::SliderInt(
"SNES bpp", &
bpp_, 2, 4);
468 if (ImGui::Button(
"New blank")) {
473 DefaultPalette(asset);
474 const auto status =
library_.
Add(std::move(asset));
477 Record(std::move(before));
483 if (ImGui::Button(
"Keep current sheet"))
485 ImGui::InputText(
"BIN path", &
bin_path_);
488 if (ImGui::Button(
"Import 64 tiles from BIN")) {
498 ImGui::PushID(
static_cast<int>(asset.id));
499 if (ImGui::Selectable(asset.name.c_str(), asset.id ==
selected_id_))
505 ImGui::EndDisabled();
509 ImGui::Text(
"%s (%d bpp) | %s", asset->name.c_str(), asset->bpp,
510 asset->source.c_str());
511 ImGui::BeginDisabled(
stroke_.has_value());
512 if (ImGui::Button(
"Rename to asset name")) {
513 auto candidate = *asset;
515 if (
auto status = candidate.Validate(); status.ok()) {
518 Record(std::move(before));
524 if (ImGui::Button(
"Duplicate")) {
531 Record(std::move(before));
539 if (ImGui::Button(
"Remove asset")) {
544 [
id](
const auto& entry) { return entry.id == id; }),
547 Record(std::move(before));
548 ImGui::EndDisabled();
551 if (ImGui::Button(
"Apply pixels to current sheet"))
554 if (ImGui::Button(
"Export BIN")) {
561 if (ImGui::Button(
"Save scratch library"))
563 ImGui::EndDisabled();
Shared state between GraphicsEditor panel components.
uint16_t current_sheet_id
const GraphicsEditTarget * SourceTarget() const
absl::Status Restore(const GraphicsScratchLibrary &library)
GraphicsScratchLibrary before_
LibraryEdit(GraphicsScratchView *owner, GraphicsScratchLibrary before, GraphicsScratchLibrary after)
absl::Status Redo() override
absl::Status Undo() override
size_t MemoryUsage() const override
Approximate memory footprint for budget enforcement.
GraphicsScratchView * owner_
std::string Description() const override
Human-readable description (e.g., "Paint 12 tiles on map 5")
GraphicsScratchLibrary after_
void SetAssociatedPreview(std::string label, ScratchPreviewCallback callback)
void DrawAssociatedPreview()
absl::Status PrepareCommand()
void DrawPixels(GraphicsScratchAsset &asset)
uint64_t preview_asset_id_
void Draw(bool *p_open) override
Draw the panel content.
std::optional< GraphicsScratchLibrary > stroke_
std::function< absl::Status(const std::string &) export_validator_)
gui::Canvas preview_canvas_
absl::Status CaptureSheet()
GraphicsScratchLibrary library_
absl::Status ImportBin(const std::string &path, int bpp, size_t offset, const gfx::SnesPalette &palette={})
ScratchPreviewCallback preview_callback_
void SetStatus(absl::Status status)
const GraphicsScratchLibrary & library() const
void DrawColorIdSwap(const GraphicsScratchAsset &selected)
absl::Status SwapColorIds(const std::vector< ScratchColorIdTarget > &targets, const ColorIdPermutation &permutation)
std::string loaded_bytes_
void RefreshPublishedBaselines()
gfx::Bitmap preview_bitmap_
absl::Status ApplyToSheet()
~GraphicsScratchView() override
GraphicsScratchAsset * Selected()
GraphicsScratchView(GraphicsEditorState *state, UndoManager *history, std::string library_path="")
std::optional< uint64_t > preview_hash_
GraphicsEditorState * state_
absl::Status Record(GraphicsScratchLibrary before)
absl::Status ExportBin(const std::string &path)
std::map< std::tuple< std::string, uint64_t, int >, std::vector< uint8_t > > published_chunks_
std::string preview_error_
std::function< absl::Status(uint16_t)> apply_validator_
std::string preview_label_
Abstract base for all undoable actions (Command pattern)
Manages undo/redo stacks for a single editor.
void Push(std::unique_ptr< UndoAction > action)
void QueueTextureCommand(TextureCommandType type, Bitmap *bitmap)
void RetireBitmap(Bitmap &bitmap)
Explicitly retire resources owned by a bitmap that is being erased.
GraphicsSheets & gfx_sheets()
void Create(int width, int height, int depth, std::span< uint8_t > data)
Create a bitmap with the given dimensions and data.
BitmapMetadata & metadata()
void set_data(const std::vector< uint8_t > &data)
void SetPalette(const SnesPalette &palette)
Set the palette for the bitmap using SNES palette format.
Represents a palette of colors for the Super Nintendo Entertainment System (SNES).
void DrawBitmap(Bitmap &bitmap, int border_offset, float scale)
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...
Locates graphics sheets in the ROM and writes edited sheets back.
One ROM session's graphics sheet pixels, with a revision per sheet.
The Graphics editor's path for sheet pixel writes.
#define ASSIGN_OR_RETURN(type_variable_name, expression)
void DefaultPalette(GraphicsScratchAsset &asset)
absl::StatusOr< LibrarySnapshot > LoadLibrary(const std::string &path)
absl::StatusOr< BinExportResult > ExportBin(const GraphicsScratchAsset &asset, const std::string &path, const std::string &library_path, const std::function< absl::Status(const std::string &)> &validator)
absl::StatusOr< std::string > SaveLibrary(const std::string &path, const GraphicsScratchLibrary &library, const std::optional< std::string > &expected_bytes)
absl::StatusOr< GraphicsScratchAsset > ImportBin(const std::string &path, int bpp, size_t offset, const std::string &name)
Editors are the view controllers for the application.
zelda3::GraphicsSheetStore * ActiveSheetStore(const GraphicsEditorState &state)
std::vector< ScratchColorIdTarget > WholeScratchAssetSelection(uint64_t asset_id)
absl::StatusOr< ColorIdPermutation > MakeColorIdSwap(int bpp, int from, int to)
absl::Status CommitSheetPixels(GraphicsEditorState &state, uint16_t sheet, absl::Span< const uint8_t > pixels)
absl::StatusOr< ScratchColorIdPlan > PlanScratchColorIdPermutation(const GraphicsScratchLibrary &library, const std::vector< ScratchColorIdTarget > &targets, const ColorIdPermutation &permutation)
absl::Status ApplyScratchColorIdPlan(GraphicsScratchLibrary &library, const ScratchColorIdPlan &plan)
std::function< absl::StatusOr< usage_preview::PreviewImage >( const GraphicsScratchAsset &)> ScratchPreviewCallback
SheetPixelDiff MakeSheetPixelDiff(uint16_t sheet, absl::Span< const uint8_t > before, absl::Span< const uint8_t > after)
The pixels that differ between two 4096-byte sheets.
void EndCanvas(Canvas &canvas)
void AutoRegisterLastItem(const std::string &widget_type, const std::string &explicit_label, const std::string &description)
Automatically register the last ImGui item.
void BeginCanvas(Canvas &canvas, ImVec2 child_size)
void BeginChildWithScrollbar(const char *str_id)
GfxSheetStorageKind GetGfxSheetStorageKind(uint16_t sheet_id)
#define RETURN_IF_ERROR(expr)
std::vector< uint8_t > pixels
std::array< uint16_t, 16 > palette
absl::Status Add(GraphicsScratchAsset asset)
std::vector< GraphicsScratchAsset > assets