yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
pixel_index_command.cc
Go to the documentation of this file.
2
3#include <algorithm>
4#include <map>
5#include <memory>
6#include <set>
7#include <string>
8#include <tuple>
9#include <utility>
10
13#include "util/macro.h"
14#include "zelda3/game_data.h"
17
18namespace yaze::editor {
19namespace {
20
21using TileKey = std::pair<uint16_t, uint16_t>;
22using PixelOffsets = std::map<uint16_t, std::set<uint16_t>>;
23using SourceCellKey = std::tuple<int, int, size_t, bool, bool>;
24constexpr int kSheetWidth = zelda3::GraphicsSheetStore::kSheetWidth;
26constexpr int kTilesPerRow = kSheetWidth / 8;
27constexpr int kTilesPerSheet = kSheetWidth * kSheetHeight / 64;
28
29absl::Status ValidatePermutation(const PixelIndexPermutation& permutation) {
30 std::array<bool, 8> seen{};
31 for (const auto value : permutation) {
32 if (value >= seen.size() || seen[value]) {
33 return absl::InvalidArgumentError(
34 "Pixel permutation must be a bijection on 0..7 with zero fixed");
35 }
36 seen[value] = true;
37 }
38 if (permutation[0] != 0) {
39 return absl::InvalidArgumentError(
40 "Pixel permutation must keep transparent index zero fixed");
41 }
42 return absl::OkStatus();
43}
44
45uint64_t PhysicalMask(const PixelIndexTarget& target) {
46 return PhysicalPixelMask(target.pixel_mask, target.hflip, target.vflip);
47}
48
49bool IsRaw3bpp(absl::Span<const uint8_t> pixels) {
50 return pixels.size() == zelda3::GraphicsSheetStore::kSheetBytes &&
51 std::all_of(pixels.begin(), pixels.end(),
52 [](uint8_t value) { return value <= 7; });
53}
54
56 auto& selection = state.selection;
57 const auto* store = ActiveSheetStore(state);
58 if (!selection.is_active || selection.is_floating || !store ||
59 !store->HasSheet(state.current_sheet_id) || selection.width <= 0 ||
60 selection.height <= 0 || selection.width > kSheetWidth ||
61 selection.height > kSheetHeight || selection.x < 0 || selection.y < 0 ||
62 selection.x > kSheetWidth - selection.width ||
63 selection.y > kSheetHeight - selection.height) {
64 return;
65 }
66 const auto pixels = store->Sheet(state.current_sheet_id);
67 selection.pixel_data.resize(selection.width * selection.height);
68 for (int y = 0; y < selection.height; ++y) {
69 for (int x = 0; x < selection.width; ++x) {
70 selection.pixel_data[y * selection.width + x] =
71 pixels[(selection.y + y) * kSheetWidth + selection.x + x];
72 }
73 }
74}
75
76absl::StatusOr<PixelOffsets> SelectionOffsets(const PixelIndexPlan& plan) {
77 if (plan.targets.empty() || plan.sheet_revisions.empty() ||
78 plan.sheet_revisions.size() != plan.before_sheets.size()) {
79 return absl::InvalidArgumentError("Pixel edit plan has no valid selection");
80 }
81 std::set<TileKey> target_keys;
82 PixelOffsets selected_offsets;
83 for (const auto& target : plan.targets) {
84 const auto snapshot = plan.before_sheets.find(target.sheet_id);
85 if (target.sheet_id >= zelda3::GraphicsSheetStore::kSheetCount ||
86 target.tile_index >= kTilesPerSheet || target.pixel_mask == 0 ||
87 target.hflip || target.vflip ||
88 !plan.sheet_revisions.contains(target.sheet_id) ||
89 snapshot == plan.before_sheets.end() || !IsRaw3bpp(snapshot->second) ||
90 !target_keys.emplace(target.sheet_id, target.tile_index).second ||
91 zelda3::GetGfxSheetStorageKind(target.sheet_id) ==
93 return absl::InvalidArgumentError(
94 "Invalid physical pixel edit selection");
95 }
96 const int start = (target.tile_index / kTilesPerRow) * 8 * kSheetWidth +
97 (target.tile_index % kTilesPerRow) * 8;
98 for (int y = 0; y < 8; ++y) {
99 for (int x = 0; x < 8; ++x) {
100 if ((target.pixel_mask & (uint64_t{1} << (y * 8 + x))) != 0) {
101 selected_offsets[target.sheet_id].insert(start + y * kSheetWidth + x);
102 }
103 }
104 }
105 }
106 return selected_offsets;
107}
108
109absl::StatusOr<std::vector<SourceCellKey>> SelectedSourceCells(
110 const GraphicsEditContext& source, const std::set<TileKey>& selected) {
111 std::vector<SourceCellKey> cells;
112 for (const auto& cell : source.pattern_cells) {
113 if (cell.target >= source.targets.size()) {
114 return absl::FailedPreconditionError(
115 "Graphics source pattern has an unavailable tile target");
116 }
117 const auto& target = source.targets[cell.target];
118 if (selected.contains({target.sheet_id, target.tile_index})) {
119 cells.emplace_back(cell.x, cell.y, cell.target, cell.hflip, cell.vflip);
120 }
121 }
122 std::sort(cells.begin(), cells.end());
123 return cells;
124}
125
126absl::Status CheckLiveSource(const PixelIndexPlan& plan) {
127 if (!plan.source) {
128 if (!plan.source_targets.empty()) {
129 return absl::InvalidArgumentError("Pixel edit source guard is missing");
130 }
131 return absl::OkStatus();
132 }
133 if (plan.source->owner != plan.owner || plan.source_targets.empty() ||
134 !plan.source->resolve_live_source) {
135 return absl::FailedPreconditionError(
136 "This graphics source cannot resolve live pixel assignments");
137 }
139 RETURN_IF_ERROR(plan.source->resolve_live_source(fresh));
140 if (fresh.owner != plan.owner ||
141 fresh.resolved.surface != plan.source->resolved.surface ||
142 fresh.resolved.id != plan.source->resolved.id ||
143 fresh.resolved.parent_id != plan.source->resolved.parent_id) {
144 return absl::FailedPreconditionError(
145 "Pixel edit source session or area changed");
146 }
147 std::set<TileKey> selected;
148 for (const auto& target : plan.targets) {
149 selected.emplace(target.sheet_id, target.tile_index);
150 }
151 std::vector<size_t> fresh_targets;
152 for (size_t i = 0; i < fresh.targets.size(); ++i) {
153 const auto& target = fresh.targets[i];
154 if (selected.contains({target.sheet_id, target.tile_index})) {
155 fresh_targets.push_back(i);
156 }
157 }
158 if (fresh_targets != plan.source_targets) {
159 return absl::FailedPreconditionError(
160 "Selected graphics source tile aliases changed");
161 }
162 for (const auto index : plan.source_targets) {
163 if (index >= plan.source->targets.size() || index >= fresh.targets.size()) {
164 return absl::FailedPreconditionError("Selected graphics source changed");
165 }
166 const auto& old = plan.source->targets[index];
167 const auto& now = fresh.targets[index];
168 if (old.sheet_id != now.sheet_id || old.tile_index != now.tile_index ||
169 old.source_orientation_mask != now.source_orientation_mask) {
170 return absl::FailedPreconditionError(
171 "Source tile assignments changed; reopen Graphics from the source");
172 }
173 }
174 ASSIGN_OR_RETURN(const auto old_cells,
175 SelectedSourceCells(*plan.source, selected));
176 ASSIGN_OR_RETURN(const auto new_cells, SelectedSourceCells(fresh, selected));
177 if (fresh.pattern_width != plan.source->pattern_width ||
178 fresh.pattern_height != plan.source->pattern_height ||
179 old_cells != new_cells) {
180 return absl::FailedPreconditionError(
181 "Source pattern positions or orientations changed; reopen Graphics "
182 "from the source");
183 }
184 return absl::OkStatus();
185}
186
187absl::Status CaptureSourceGuard(const GraphicsEditorState& state,
188 PixelIndexPlan& plan) {
189 if (!state.source_context) {
190 return absl::OkStatus();
191 }
192 if (state.source_context->owner != plan.owner) {
193 return absl::FailedPreconditionError("Graphics source owner changed");
194 }
195 std::set<TileKey> selected;
196 for (const auto& target : plan.targets) {
197 selected.emplace(target.sheet_id, target.tile_index);
198 }
199 for (size_t i = 0; i < state.source_context->targets.size(); ++i) {
200 const auto& target = state.source_context->targets[i];
201 if (selected.contains({target.sheet_id, target.tile_index})) {
202 plan.source_targets.push_back(i);
203 }
204 }
205 if (!plan.source_targets.empty()) {
206 plan.source = state.source_context;
207 RETURN_IF_ERROR(CheckLiveSource(plan));
208 }
209 return absl::OkStatus();
210}
211
212class PixelIndexAction final : public UndoAction {
213 public:
215 : state_(&state), plan_(plan) {}
216
217 absl::Status Undo() override {
218 return ExecutePixelIndexPlan(*state_, plan_, PixelIndexExecution::kUndo);
219 }
220 absl::Status Redo() override {
221 return ExecutePixelIndexPlan(*state_, plan_, PixelIndexExecution::kRedo);
222 }
223 std::string Description() const override { return plan_.description; }
224 size_t MemoryUsage() const override {
225 size_t size = plan_.targets.size() * sizeof(PixelIndexTarget) +
226 plan_.description.size();
227 for (const auto& [sheet, pixels] : plan_.before_sheets) {
228 size += pixels.size();
229 }
230 for (const auto& diff : plan_.sheets) {
231 size += diff.MemoryUsage();
232 }
233 return size;
234 }
235
236 private:
239};
240
241} // namespace
242
243absl::StatusOr<std::vector<PixelIndexTarget>> MakePixelIndexRectangleSelection(
244 uint16_t sheet_id, int x, int y, int width, int height) {
245 if (sheet_id >= zelda3::GraphicsSheetStore::kSheetCount || width <= 0 ||
246 height <= 0 || width > kSheetWidth || height > kSheetHeight || x < 0 ||
247 y < 0 || x > kSheetWidth - width || y > kSheetHeight - height) {
248 return absl::InvalidArgumentError(
249 "Pixel selection must fit within one loaded 128x32 sheet");
250 }
251 std::map<uint16_t, uint64_t> masks;
252 for (int yy = y; yy < y + height; ++yy) {
253 for (int xx = x; xx < x + width; ++xx) {
254 const uint16_t tile = (yy / 8) * kTilesPerRow + xx / 8;
255 masks[tile] |= uint64_t{1} << ((yy % 8) * 8 + xx % 8);
256 }
257 }
258 std::vector<PixelIndexTarget> targets;
259 for (const auto& [tile, mask] : masks) {
260 targets.push_back({sheet_id, tile, mask});
261 }
262 return targets;
263}
264
265absl::StatusOr<PixelIndexPlan> PlanPixelIndexPermutation(
266 const GraphicsEditorState& state,
267 const std::vector<PixelIndexTarget>& targets,
268 const PixelIndexPermutation& permutation) {
269 RETURN_IF_ERROR(ValidatePermutation(permutation));
270 const auto* store = ActiveSheetStore(state);
271 if (!store || state.selection.is_floating) {
272 return absl::FailedPreconditionError(
273 "Load this session's graphics and finish the floating paste first");
274 }
275 if (targets.empty()) {
276 return absl::InvalidArgumentError("Select tile pixels to remap");
277 }
278
279 PixelIndexPlan plan;
280 plan.owner = state.game_data;
282 std::map<TileKey, uint64_t> masks;
283 for (const auto& target : targets) {
284 if (target.tile_index >= kTilesPerSheet ||
285 !store->HasSheet(target.sheet_id)) {
286 return absl::OutOfRangeError("Selected graphics tile is unavailable");
287 }
288 if (zelda3::GetGfxSheetStorageKind(target.sheet_id) ==
290 return absl::UnimplementedError(
291 "Pixel index permutations require a 3bpp graphics sheet");
292 }
293 const uint64_t mask = PhysicalMask(target);
294 if (mask != 0) {
295 masks[{target.sheet_id, target.tile_index}] |= mask;
296 }
297 }
298 if (masks.empty()) {
299 return absl::InvalidArgumentError("Select at least one tile pixel");
300 }
301
302 std::map<uint16_t, std::vector<uint8_t>> next;
303 for (const auto& [key, mask] : masks) {
304 const auto [sheet, tile] = key;
305 if (!plan.before_sheets.contains(sheet)) {
306 const auto pixels = store->Sheet(sheet);
307 if (!IsRaw3bpp(pixels)) {
308 return absl::FailedPreconditionError(
309 "Selected graphics sheet is not loaded as raw 3bpp indices");
310 }
311 plan.sheet_revisions[sheet] = store->Revision(sheet);
312 plan.before_sheets[sheet] =
313 std::vector<uint8_t>(pixels.begin(), pixels.end());
314 next[sheet] = plan.before_sheets[sheet];
315 }
316 plan.targets.push_back({sheet, tile, mask});
317 const int start =
318 (tile / kTilesPerRow) * 8 * kSheetWidth + (tile % kTilesPerRow) * 8;
319 for (int y = 0; y < 8; ++y) {
320 for (int x = 0; x < 8; ++x) {
321 if ((mask & (uint64_t{1} << (y * 8 + x))) != 0) {
322 auto& pixel = next[sheet][start + y * kSheetWidth + x];
323 pixel = permutation[pixel];
324 ++plan.selected_pixels;
325 }
326 }
327 }
328 }
329 plan.physical_tiles = masks.size();
330 RETURN_IF_ERROR(CaptureSourceGuard(state, plan));
331 for (const auto& [sheet, pixels] : next) {
332 auto diff = MakeSheetPixelDiff(sheet, plan.before_sheets.at(sheet), pixels);
333 if (!diff.empty()) {
334 plan.sheets.push_back(std::move(diff));
335 }
336 }
337 return plan;
338}
339
340absl::StatusOr<PixelIndexPlan> PlanPixelIndexReplacement(
341 const GraphicsEditorState& state,
342 const std::vector<PixelIndexTarget>& targets,
343 const std::map<uint16_t, std::vector<uint8_t>>& after_sheets,
344 std::string description) {
345 constexpr PixelIndexPermutation identity{0, 1, 2, 3, 4, 5, 6, 7};
346 ASSIGN_OR_RETURN(auto plan,
347 PlanPixelIndexPermutation(state, targets, identity));
348 if (after_sheets.size() != plan.before_sheets.size()) {
349 return absl::InvalidArgumentError(
350 "Artwork replacement needs exactly the selected graphics sheets");
351 }
352 ASSIGN_OR_RETURN(const auto selected_offsets, SelectionOffsets(plan));
353 for (const auto& [sheet, before] : plan.before_sheets) {
354 const auto after = after_sheets.find(sheet);
355 if (after == after_sheets.end() || !IsRaw3bpp(after->second)) {
356 return absl::InvalidArgumentError(
357 "Artwork replacement needs full 4096-byte sheets with indices 0..7");
358 }
359 auto diff = MakeSheetPixelDiff(sheet, before, after->second);
360 for (const auto offset : diff.offsets) {
361 if (!selected_offsets.at(sheet).contains(offset)) {
362 return absl::InvalidArgumentError(
363 "Artwork replacement changes pixels outside the selected mask");
364 }
365 }
366 if (!diff.empty()) {
367 plan.sheets.push_back(std::move(diff));
368 }
369 }
371 plan.keep_transparency = false;
372 if (!description.empty()) {
373 plan.description = std::move(description);
374 } else {
375 plan.description = "Apply art to object source";
376 }
377 return plan;
378}
379
381 GraphicsEditorState& state, const PixelIndexPlan& plan,
382 PixelIndexExecution execution,
383 const std::function<absl::Status(size_t)>& after_sheet_write) {
384 auto* store = ActiveSheetStore(state);
385 if (!store || !plan.owner || state.game_data != plan.owner ||
386 state.selection.is_floating) {
387 return absl::FailedPreconditionError(
388 "Pixel edit session changed or a floating paste is pending");
389 }
390 if (execution != PixelIndexExecution::kApply &&
391 execution != PixelIndexExecution::kUndo &&
392 execution != PixelIndexExecution::kRedo) {
393 return absl::InvalidArgumentError("Invalid pixel edit execution mode");
394 }
398 !plan.keep_transparency)) {
399 return absl::InvalidArgumentError("Invalid pixel edit operation");
400 }
401 ASSIGN_OR_RETURN(const auto selected_offsets, SelectionOffsets(plan));
402 RETURN_IF_ERROR(CheckLiveSource(plan));
403 if (execution == PixelIndexExecution::kApply) {
405 return absl::FailedPreconditionError(
406 "Graphics source changed since the pixel edit was planned");
407 }
408 for (const auto& [sheet, revision] : plan.sheet_revisions) {
409 const auto snapshot = plan.before_sheets.find(sheet);
410 const auto pixels = store->Sheet(sheet);
411 if (snapshot == plan.before_sheets.end() ||
412 !IsRaw3bpp(snapshot->second)) {
413 return absl::InvalidArgumentError("Invalid pixel edit sheet snapshot");
414 }
415 if (!store->HasSheet(sheet) || store->Revision(sheet) != revision ||
416 !std::equal(pixels.begin(), pixels.end(), snapshot->second.begin(),
417 snapshot->second.end())) {
418 return absl::FailedPreconditionError(
419 "Graphics pixels changed since the pixel edit was planned");
420 }
421 }
422 }
423
424 const bool redo = execution != PixelIndexExecution::kUndo;
425 std::map<uint16_t, std::vector<uint8_t>> next;
426 for (const auto& diff : plan.sheets) {
427 if (!store->HasSheet(diff.sheet) ||
428 !plan.sheet_revisions.contains(diff.sheet) ||
429 !plan.before_sheets.contains(diff.sheet) || diff.offsets.empty() ||
430 diff.offsets.size() != diff.before.size() ||
431 diff.offsets.size() != diff.after.size() || next.contains(diff.sheet)) {
432 return absl::InvalidArgumentError("Invalid pixel edit sheet diff");
433 }
434 const auto current = store->Sheet(diff.sheet);
435 if (!IsRaw3bpp(current)) {
436 return absl::FailedPreconditionError(
437 "Graphics sheet no longer contains raw 3bpp indices");
438 }
439 auto& pixels = next[diff.sheet];
440 pixels.assign(current.begin(), current.end());
441 std::set<uint16_t> offsets;
442 for (size_t i = 0; i < diff.offsets.size(); ++i) {
443 const auto offset = diff.offsets[i];
444 if (offset >= pixels.size() || !offsets.insert(offset).second ||
445 !selected_offsets.contains(diff.sheet) ||
446 !selected_offsets.at(diff.sheet).contains(offset) ||
447 diff.before[i] > 7 || diff.after[i] > 7 ||
448 diff.before[i] == diff.after[i] ||
449 (plan.keep_transparency &&
450 (diff.before[i] == 0 || diff.after[i] == 0)) ||
451 diff.before[i] != plan.before_sheets.at(diff.sheet)[offset]) {
452 return absl::InvalidArgumentError("Invalid pixel edit pixel diff");
453 }
454 if (pixels[offset] != (redo ? diff.before[i] : diff.after[i])) {
455 return absl::FailedPreconditionError(
456 "Edited pixels changed since this history action");
457 }
458 pixels[offset] = redo ? diff.after[i] : diff.before[i];
459 }
460 }
461
462 const auto before = *store;
463 size_t written = 0;
464 for (const auto& [sheet, pixels] : next) {
465 auto status = store->WriteSheet(sheet, pixels, 7);
466 if (status.ok() && after_sheet_write) {
467 status = after_sheet_write(++written);
468 }
469 if (!status.ok()) {
470 *store = before;
471 return status;
472 }
473 }
474 if (next.contains(state.current_sheet_id)) {
475 RefreshSelectionPixels(state);
476 }
477 for (const auto& [sheet, pixels] : next) {
478 RefreshArenaSheet(state, sheet);
479 state.MarkSheetModified(sheet);
480 }
481 return absl::OkStatus();
482}
483
485 UndoManager& history,
486 const PixelIndexPlan& plan) {
489 if (!plan.sheets.empty()) {
490 history.Push(std::make_unique<PixelIndexAction>(state, plan));
491 }
492 return absl::OkStatus();
493}
494
496 GraphicsEditorState& state, UndoManager& history,
497 const std::vector<PixelIndexTarget>& targets,
498 const PixelIndexPermutation& permutation) {
499 ASSIGN_OR_RETURN(const auto plan,
500 PlanPixelIndexPermutation(state, targets, permutation));
501 return ApplyPixelIndexPlan(state, history, plan);
502}
503
504absl::StatusOr<PixelIndexPlan> PlanColorIdPermutation(
505 const GraphicsEditorState& state,
506 const std::vector<PixelIndexTarget>& targets,
507 const ColorIdPermutation& permutation) {
508 for (const auto& target : targets) {
509 if (target.sheet_id < zelda3::GraphicsSheetStore::kSheetCount &&
510 zelda3::GetGfxSheetStorageKind(target.sheet_id) ==
512 return absl::FailedPreconditionError(
513 "2bpp ROM sheets are read-only; swap IDs in a 2bpp scratch BIN");
514 }
515 }
518 std::copy_n(permutation.ids.begin(), legacy.size(), legacy.begin());
519 return PlanPixelIndexPermutation(state, targets, legacy);
520}
521
523 GraphicsEditorState& state, UndoManager& history,
524 const std::vector<PixelIndexTarget>& targets,
525 const ColorIdPermutation& permutation) {
526 ASSIGN_OR_RETURN(const auto plan,
527 PlanColorIdPermutation(state, targets, permutation));
528 return ApplyPixelIndexPlan(state, history, plan);
529}
530
531} // namespace yaze::editor
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
Abstract base for all undoable actions (Command pattern)
Definition undo_action.h:20
Manages undo/redo stacks for a single editor.
void Push(std::unique_ptr< UndoAction > action)
std::string Description() const override
Human-readable description (e.g., "Paint 12 tiles on map 5")
PixelIndexAction(GraphicsEditorState &state, const PixelIndexPlan &plan)
size_t MemoryUsage() const override
Approximate memory footprint for budget enforcement.
static constexpr uint16_t kSheetCount
Locates graphics sheets in the ROM and writes edited sheets back.
One ROM session's graphics sheet pixels, with a revision per sheet.
#define ASSIGN_OR_RETURN(type_variable_name, expression)
Definition macro.h:62
std::tuple< int, int, size_t, bool, bool > SourceCellKey
absl::StatusOr< std::vector< SourceCellKey > > SelectedSourceCells(const GraphicsEditContext &source, const std::set< TileKey > &selected)
uint64_t PhysicalMask(const PixelIndexTarget &target)
std::map< uint16_t, std::set< uint16_t > > PixelOffsets
absl::StatusOr< PixelOffsets > SelectionOffsets(const PixelIndexPlan &plan)
absl::Status CaptureSourceGuard(const GraphicsEditorState &state, PixelIndexPlan &plan)
absl::Status ValidatePermutation(const PixelIndexPermutation &permutation)
Editors are the view controllers for the application.
zelda3::GraphicsSheetStore * ActiveSheetStore(const GraphicsEditorState &state)
absl::StatusOr< PixelIndexPlan > PlanColorIdPermutation(const GraphicsEditorState &state, const std::vector< PixelIndexTarget > &targets, const ColorIdPermutation &permutation)
absl::StatusOr< PixelIndexPlan > PlanPixelIndexReplacement(const GraphicsEditorState &state, const std::vector< PixelIndexTarget > &targets, const std::map< uint16_t, std::vector< uint8_t > > &after_sheets, std::string description)
absl::Status ApplyColorIdPermutation(GraphicsEditorState &state, UndoManager &history, const std::vector< PixelIndexTarget > &targets, const ColorIdPermutation &permutation)
absl::Status ExecutePixelIndexPlan(GraphicsEditorState &state, const PixelIndexPlan &plan, PixelIndexExecution execution, const std::function< absl::Status(size_t)> &after_sheet_write)
absl::Status ApplyPixelIndexPermutation(GraphicsEditorState &state, UndoManager &history, const std::vector< PixelIndexTarget > &targets, const PixelIndexPermutation &permutation)
absl::Status ApplyPixelIndexPlan(GraphicsEditorState &state, UndoManager &history, const PixelIndexPlan &plan)
absl::StatusOr< PixelIndexPlan > PlanPixelIndexPermutation(const GraphicsEditorState &state, const std::vector< PixelIndexTarget > &targets, const PixelIndexPermutation &permutation)
void RefreshArenaSheet(GraphicsEditorState &state, uint16_t sheet)
absl::StatusOr< std::vector< PixelIndexTarget > > MakePixelIndexRectangleSelection(uint16_t sheet_id, int x, int y, int width, int height)
std::array< uint8_t, 8 > PixelIndexPermutation
absl::Status ValidateColorIdPermutationForBacking(const ColorIdPermutation &permutation, int backing_bpp)
uint64_t PhysicalPixelMask(uint64_t displayed_mask, bool hflip, bool vflip)
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.
GfxSheetStorageKind GetGfxSheetStorageKind(uint16_t sheet_id)
#define RETURN_IF_ERROR(expr)
Definition snes.cc:22
zelda3::ResolvedGraphicsContext resolved
std::vector< GraphicsEditTarget > targets
std::vector< GraphicsSourceCell > pattern_cells
std::vector< SheetPixelDiff > sheets
std::vector< size_t > source_targets
std::vector< PixelIndexTarget > targets
std::map< uint16_t, uint64_t > sheet_revisions
std::map< uint16_t, std::vector< uint8_t > > before_sheets
const zelda3::GameData * owner
std::optional< GraphicsEditContext > source