28 return absl::InvalidArgumentError(
29 "Palette/pixel plan has no selected targets");
31 return absl::UnimplementedError(
32 "This source has no authoritative live palette/pixel resolver");
40 return absl::FailedPreconditionError(
41 "Palette/pixel source session or area changed");
43 for (
const auto index : plan.
targets) {
45 return absl::FailedPreconditionError(
"Selected source target changed");
47 const auto& now = fresh.
targets[index];
49 if (!old.writable_palette || old.palette.size() != 8 ||
50 old.sheet_id != now.sheet_id || old.tile_index != now.tile_index ||
51 old.source_orientation_mask != now.source_orientation_mask ||
52 old.writable_palette->row != now.writable_palette->row ||
53 old.writable_palette->half_offset !=
54 now.writable_palette->half_offset ||
55 old.writable_palette->colors != now.writable_palette->colors) {
56 return absl::FailedPreconditionError(
57 "Source assignments changed; reopen Graphics from the source");
59 for (
int raw = 0; raw < 8; ++raw) {
60 auto expected = old.palette[raw];
61 if (!redo && raw != 0) {
62 const auto key =
Key(*old.writable_palette->colors[raw]);
63 for (
const auto& change : plan.
colors)
64 if (
Key(change) == key)
65 expected = change.new_color;
67 if (now.palette[raw].snes() != expected.snes())
68 return absl::FailedPreconditionError(
69 "Source colors changed since planning");
72 return absl::OkStatus();
78 : state_(&state), plan_(std::move(plan)) {}
79 absl::Status
Undo()
override {
82 absl::Status
Redo()
override {
86 return "Reorder palette and selected pixels";
90 for (
const auto& diff : plan_.sheets)
91 size += diff.MemoryUsage();
104 std::set<uint8_t> values(permutation.begin(), permutation.end());
105 if (permutation[0] != 0 || values.size() != 8 || *values.rbegin() != 7)
106 return absl::InvalidArgumentError(
107 "Permutation must be a bijection on 0..7 with zero fixed");
108 if (!source.
owner || targets.empty())
109 return absl::InvalidArgumentError(
110 "Select source tiles in a loaded session");
113 std::set<size_t> selected(targets.begin(), targets.end());
114 plan.
targets.assign(selected.begin(), selected.end());
115 std::map<AddressKey, gfx::PaletteColorChange> colors;
116 std::set<std::pair<uint16_t, uint16_t>> tiles;
117 std::map<uint16_t, std::vector<uint8_t>> sheets;
118 for (
const auto index : plan.
targets) {
119 if (index >= source.
targets.size())
120 return absl::OutOfRangeError(
"Selected source target is unavailable");
121 const auto& target = source.
targets[index];
123 if (target.tile_index >= 64 ||
127 return absl::UnimplementedError(
128 "Only loaded static 3bpp tiles can be permuted");
130 for (
int old_raw = 1; old_raw < 8; ++old_raw) {
131 const int new_raw = permutation[old_raw];
132 const auto key = Key(*target.writable_palette->colors[new_raw]);
134 std::get<0>(key), std::get<1>(key), std::get<2>(key),
135 target.palette[new_raw], target.palette[old_raw], 0};
136 const auto [at, inserted] = colors.emplace(key, change);
138 (at->second.original_color.snes() != change.original_color.snes() ||
139 at->second.new_color.snes() != change.new_color.snes()))
140 return absl::FailedPreconditionError(
141 "Selected palette bindings require conflicting writes");
143 if (!tiles.emplace(target.sheet_id, target.tile_index).second)
146 auto [at, inserted] =
147 sheets.try_emplace(target.sheet_id, before.begin(), before.end());
149 (target.tile_index / 16) * 8 * 128 + (target.tile_index % 16) * 8;
150 for (
int y = 0; y < 8; ++y) {
151 for (
int x = 0; x < 8; ++x) {
152 auto& pixel = at->second[start + y * 128 + x];
154 return absl::FailedPreconditionError(
"Selected tile is not raw 3bpp");
155 pixel = permutation[pixel];
160 for (
const auto& [key, change] : colors)
161 if (change.original_color.snes() != change.new_color.snes())
162 plan.
colors.push_back(change);
163 for (
const auto& [sheet, after] : sheets) {
167 plan.
sheets.push_back(std::move(diff));
174 const std::function<absl::Status(
size_t)>& after_sheet_write) {
178 return absl::FailedPreconditionError(
179 "Palette/pixel command session changed or paste is pending");
181 std::map<uint16_t, std::vector<uint8_t>> next;
182 for (
const auto& diff : plan.
sheets) {
183 if (!store->HasSheet(diff.sheet) ||
184 diff.offsets.size() != diff.before.size() ||
185 diff.offsets.size() != diff.after.size() || next.contains(diff.sheet))
186 return absl::InvalidArgumentError(
"Invalid command sheet diff");
187 const auto current = store->Sheet(diff.sheet);
188 auto& pixels = next[diff.sheet];
189 pixels.assign(current.begin(), current.end());
190 for (
size_t i = 0; i < diff.offsets.size(); ++i) {
191 const auto offset = diff.offsets[i];
192 if (offset >= pixels.size() || diff.before[i] > 7 || diff.after[i] > 7)
193 return absl::InvalidArgumentError(
"Invalid command pixel diff");
194 if (pixels[offset] != (redo ? diff.before[i] : diff.after[i]))
195 return absl::FailedPreconditionError(
196 "Selected pixels changed since planning");
197 pixels[offset] = redo ? diff.after[i] : diff.before[i];
199 if (std::any_of(pixels.begin(), pixels.end(), [](
auto p) { return p > 7; }))
200 return absl::FailedPreconditionError(
"Sheet is not raw 3bpp");
202 auto changes = plan.
colors;
204 for (
auto& c : changes)
205 std::swap(c.original_color, c.new_color);
206 const auto before = *store;
210 for (const auto& [sheet, pixels] : next) {
211 auto status = store->WriteSheet(sheet, pixels, 7);
212 if (status.ok() && after_sheet_write)
213 status = after_sheet_write(++written);
220 for (
const auto& [sheet, pixels] : next) {
224 return absl::OkStatus();
232 return absl::FailedPreconditionError(
233 "Open Graphics from a supported live source");
237 if (!plan.colors.empty() || !plan.sheets.empty())
238 history.
Push(std::make_unique<PalettePixelAction>(state, std::move(plan)));
239 return absl::OkStatus();
Shared state between GraphicsEditor panel components.
void MarkSheetModified(uint16_t sheet_id)
Mark a sheet as modified for save tracking.
std::optional< GraphicsEditContext > source_context
zelda3::GameData * game_data
Abstract base for all undoable actions (Command pattern)
Manages undo/redo stacks for a single editor.
void Push(std::unique_ptr< UndoAction > action)
absl::Status Undo() override
absl::Status Redo() override
PalettePixelAction(GraphicsEditorState &state, PalettePixelPlan plan)
GraphicsEditorState * state_
size_t MemoryUsage() const override
Approximate memory footprint for budget enforcement.
std::string Description() const override
Human-readable description (e.g., "Paint 12 tiles on map 5")
absl::Status ApplyExternalChanges(const zelda3::GameData *owner, const std::vector< PaletteColorChange > &changes, const std::function< absl::Status()> &participant)
static PaletteManager & Get()
Get the singleton instance.
bool HasSheet(uint16_t sheet) const
True when the buffer holds all 4096 bytes of sheet.
absl::Span< const uint8_t > Sheet(uint16_t sheet) const
The sheet's 4096 pixels, or an empty span when it is not held.
Locates graphics sheets in the ROM and writes edited sheets back.
#define ASSIGN_OR_RETURN(type_variable_name, expression)
absl::Status CheckLiveSource(const PalettePixelPlan &plan, bool redo)
AddressKey Key(const zelda3::PaletteColorAddress &a)
std::tuple< std::string, int, int > AddressKey
Editors are the view controllers for the application.
zelda3::GraphicsSheetStore * ActiveSheetStore(const GraphicsEditorState &state)
absl::Status ExecutePalettePixelPlan(GraphicsEditorState &state, const PalettePixelPlan &plan, bool redo, const std::function< absl::Status(size_t)> &after_sheet_write)
std::array< uint8_t, 8 > PalettePermutation
absl::StatusOr< PalettePixelPlan > PlanPalettePixelPermutation(const GraphicsEditContext &source, const std::vector< size_t > &targets, const PalettePermutation &permutation)
void RefreshArenaSheet(GraphicsEditorState &state, uint16_t sheet)
absl::Status ValidateGraphicsPaletteAddressing(const GraphicsEditTarget &target, const zelda3::GameData *owner)
absl::Status ApplyPalettePixelPermutation(GraphicsEditorState &state, UndoManager &history, const std::vector< size_t > &targets, const PalettePermutation &permutation)
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.
const char * PaletteAddressGroupName(PaletteAddressGroup group)
GfxSheetStorageKind GetGfxSheetStorageKind(uint16_t sheet_id)
#define RETURN_IF_ERROR(expr)
zelda3::ResolvedGraphicsContext resolved
std::vector< GraphicsEditTarget > targets
std::function< absl::Status(GraphicsEditContext &) resolve_live_source)
const zelda3::GameData * owner
GraphicsEditContext source
std::vector< SheetPixelDiff > sheets
std::vector< size_t > targets
std::vector< gfx::PaletteColorChange > colors
Represents a single color change operation.
int color_index
Index of color within palette.
std::string group_name
Palette group name (e.g., "ow_main")
int palette_index
Index of palette within group.
GraphicsSheetStore sheet_store
PaletteAddressGroup group