yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
tile16_edit_session.cc
Go to the documentation of this file.
2
3#include <map>
4
5#include "absl/status/status.h"
6#include "absl/strings/str_format.h"
11#include "rom/rom.h"
12#include "util/log.h"
13#include "util/macro.h"
14#include "zelda3/game_data.h"
20
21namespace yaze {
22namespace editor {
23
24namespace {
25
27constexpr float kTile16PreviewDisplayScale = 4.0f;
28
30 std::vector<gfx::SnesColor> grayscale;
31 grayscale.reserve(gfx::SnesPalette::kMaxColors);
32 for (int i = 0; i < static_cast<int>(gfx::SnesPalette::kMaxColors); ++i) {
33 const uint8_t value = static_cast<uint8_t>(i);
34 grayscale.emplace_back(value, value, value);
35 }
36 if (!grayscale.empty()) {
37 grayscale[0].set_transparent(true);
38 }
39 return gfx::SnesPalette(grayscale);
40}
41
43 return zelda3::MutableTile16QuadrantInfo(*tile, quadrant);
44}
45
49
50} // namespace
51
53 return RunEdit([&]() { return PasteTile16FromClipboardImpl(); });
54}
55
57 return RunEdit([&]() { return LoadTile16FromScratchSpaceImpl(slot); });
58}
59
61 return RunEdit([&]() { return FlipTile16HorizontalImpl(); });
62}
63
65 return RunEdit([&]() { return FlipTile16VerticalImpl(); });
66}
67
69 return RunEdit([&]() { return RotateTile16Impl(); });
70}
71
72absl::Status Tile16EditSession::FillTile16WithTile8(int tile8_id) {
73 return RunEdit([&]() { return FillTile16WithTile8Impl(tile8_id); });
74}
75
77 return RunEdit([&]() { return ClearTile16Impl(); });
78}
79
80absl::Status Tile16EditSession::ApplyPaletteToAll(uint8_t palette_id) {
81 return RunEdit([&]() { return ApplyPaletteToAllImpl(palette_id); });
82}
83
85 uint8_t palette_id) {
86 return RunEdit(
87 [&]() { return ApplyPaletteToQuadrantImpl(quadrant, palette_id); });
88}
89
91 Tile16LocalPos pos, const gfx::Bitmap* source_tile) {
92 return RunEdit([&]() { return DrawToCurrentTile16Impl(pos, source_tile); });
93}
94
96 gfx::Bitmap& tile16_blockset_bmp, gfx::Bitmap& current_gfx_bmp,
97 std::array<uint8_t, 0x200>& all_tiles_types) {
98 document_edits_.clear();
105
107 std::vector<uint8_t>(kTile16PixelCount, 0));
111
112 if (tile16_blockset_) {
114 }
115
117 return absl::OkStatus();
118}
119
122
123 if (palette.size() >= 256) {
126 "Tile16 editor received complete overworld palette with %zu colors",
127 palette.size());
128 } else {
129 util::logf("Warning: Received incomplete palette with %zu colors",
130 palette.size());
132 }
133
135 auto status = LoadTile8();
136 if (!status.ok()) {
137 util::logf("Failed to load tile8 graphics with new palette: %s",
138 status.message().data());
139 } else {
141 "Successfully loaded tile8 graphics with complete overworld "
142 "palette");
143 }
144 }
145
146 util::logf("Tile16 editor palette coordination complete");
147}
148
150 if (!rom_ || current_tile16_ < 0 || current_tile16_ >= kTile16Count) {
151 return nullptr;
152 }
153 return &current_tile16_data_;
154}
155
158 return absl::FailedPreconditionError(
159 "Use document Save or Undo for the shared Tile16 document");
160 }
161
162 auto* tile_data = GetCurrentTile16Data();
163 if (!tile_data) {
164 return absl::FailedPreconditionError("Cannot access current tile16 data");
165 }
166
167 // Write the modified tile16 data back to ROM
170
171 util::logf("ROM Tile16 data written for tile %d", current_tile16_);
172 return absl::OkStatus();
173}
174
176 if (!tile16_blockset_) {
177 return absl::FailedPreconditionError("Tile16 blockset not available");
178 }
179
180 // CRITICAL FIX: Force regeneration without using problematic tile cache
181 // Directly mark atlas as modified to trigger regeneration from ROM data
182
183 // Mark atlas as modified to trigger regeneration
185
186 // Queue texture update via Arena's deferred system
189
190 util::logf("Tile16 blockset refreshed and regenerated");
191 return absl::OkStatus();
192}
193
195 const gfx::Tile16& tile_data, gfx::Bitmap* output_bitmap) const {
197 tile_data, current_gfx_individual_, output_bitmap);
198}
199
201 int tile_id, const gfx::Bitmap& tile_bitmap) {
202 if (!tile_bitmap.is_active()) {
203 return;
204 }
205
206 if (tile16_blockset_bmp_ != nullptr) {
208 }
211 }
212
215 tile_bitmap);
217 }
218}
219
221 gfx::ScopedTimer timer("tile16_blockset_update");
222
223 if (!tile16_blockset_) {
224 return absl::FailedPreconditionError("Tile16 blockset not initialized");
225 }
226
227 if (current_tile16_ < 0 || current_tile16_ >= kTile16Count) {
228 return absl::OutOfRangeError("Current tile16 ID out of range");
229 }
230
232 return absl::FailedPreconditionError("Current tile16 bitmap is not active");
233 }
234
236
240 }
244 }
245
246 return absl::OkStatus();
247}
248
250 // Rebuild preview from `current_tile16_data_` (SetCurrentTile prefers pending
251 // maps over ROM). Name is historical.
252 auto* tile_data = GetCurrentTile16Data();
253 if (!tile_data) {
254 return absl::FailedPreconditionError("Cannot access current tile16 data");
255 }
256
257 // Tests and some initialization paths reach regeneration before tile8 previews
258 // are built; lazily populate them so metadata->bitmap rendering can proceed.
259 if (current_gfx_individual_.empty()) {
260 if (!HasCurrentGfxBitmap()) {
261 return absl::FailedPreconditionError("Tile8 source bitmap not active");
262 }
264 }
265
266 // Shared render path used by regeneration, stamping, and multi-tile updates.
268
269 // Set the appropriate palette using the same system as overworld
271
272 // Queue texture creation via Arena's deferred system
275
276 util::logf("Regenerated Tile16 bitmap for tile %d from ROM data",
278 return absl::OkStatus();
279}
280
281absl::StatusOr<std::vector<zelda3::Tile16StampMutation>>
283 // Validate inputs
284 if (current_tile8_ < 0 ||
285 current_tile8_ >= static_cast<int>(current_gfx_individual_.size())) {
286 return absl::OutOfRangeError(
287 absl::StrFormat("Invalid tile8 index: %d", current_tile8_));
288 }
289
291 return absl::FailedPreconditionError("Target tile16 bitmap not active");
292 }
293
294 if (!HasCurrentGfxBitmap()) {
295 return absl::FailedPreconditionError("Tile8 source bitmap not active");
296 }
297
298 const int tile8_count =
299 static_cast<int>(std::min<size_t>(current_gfx_individual_.size(), 1024));
300 const int max_tile8_id = std::max(0, tile8_count - 1);
301 const int tile8_row_stride =
302 std::max(1, current_gfx_bmp_->width() / kTile8Size);
303 const int quadrant_x = (pos.x >= kTile8Size) ? 1 : 0;
304 const int quadrant_y = (pos.y >= kTile8Size) ? 1 : 0;
305 const int quadrant_index = quadrant_x + (quadrant_y * 2);
306
307 zelda3::Tile16StampRequest stamp_request;
308 stamp_request.current_tile16 = current_tile16_data_;
309 stamp_request.current_tile16_id = current_tile16_;
310 stamp_request.selected_tile8_id = current_tile8_;
311 stamp_request.stamp_size = tile8_stamp_size_;
312 stamp_request.quadrant_index = quadrant_index;
313 stamp_request.palette_id = current_palette_;
314 stamp_request.x_flip = x_flip_;
315 stamp_request.y_flip = y_flip_;
316 stamp_request.priority = priority_tile_;
317 stamp_request.tile8_row_stride = tile8_row_stride;
318 stamp_request.tile16_row_stride = kTilesPerRow;
319 stamp_request.max_tile8_id = max_tile8_id;
320 stamp_request.max_tile16_id =
322 ? static_cast<int>(document_definitions_->size()) - 1
323 : kTile16Count - 1;
324
325 return zelda3::BuildTile16StampMutations(stamp_request);
326}
327
329 gfx::Bitmap* output) const {
330 if (!output) {
331 return absl::InvalidArgumentError("Stamp preview bitmap is null");
332 }
333 ASSIGN_OR_RETURN(const auto mutations, BuildStampMutations(pos));
334 const int size = tile8_stamp_size_ == 4 ? 32 : 16;
335 std::vector<uint8_t> pixels(size * size, 0);
336 for (const auto& mutation : mutations) {
337 const int offset = mutation.tile16_id - current_tile16_;
338 const int x = (offset % kTilesPerRow) * 16;
339 const int y = (offset / kTilesPerRow) * 16;
341 mutation.tile_data, current_gfx_individual_);
342 for (int row = 0; row < 16 && y + row < size; ++row) {
343 std::copy_n(tile.begin() + row * 16, 16,
344 pixels.begin() + (y + row) * size + x);
345 }
346 }
347 output->Create(size, size, 8, pixels);
349 return absl::OkStatus();
350}
351
353 Tile16LocalPos pos, const gfx::Bitmap* source_tile) {
354 (void)source_tile;
355 ASSIGN_OR_RETURN(auto staged_tiles, BuildStampMutations(pos));
356 active_quadrant_ = (pos.x >= 8 ? 1 : 0) + (pos.y >= 8 ? 2 : 0);
357
358 for (const auto& mutation : staged_tiles) {
359 const int tile16_id = mutation.tile16_id;
360 const gfx::Tile16& tile_data = mutation.tile_data;
361 gfx::Bitmap staged_bitmap;
362 RETURN_IF_ERROR(BuildTile16BitmapFromData(tile_data, &staged_bitmap));
363 if (current_tile16_bmp_.palette().size() > 0) {
364 staged_bitmap.SetPalette(current_tile16_bmp_.palette());
365 }
366
367 if (tile16_id == current_tile16_) {
368 current_tile16_data_ = tile_data;
369 SyncTilesInfoArray(&current_tile16_data_);
371 staged_bitmap.vector());
377 } else {
378 document_edits_[tile16_id] = tile_data;
379 edited_tile_bitmaps_[tile16_id] = staged_bitmap;
380 preview_dirty_ = true;
381 }
382
383 CopyTileBitmapToBlockset(tile16_id, staged_bitmap);
384 }
385
389 }
393 }
394
396
399 }
400
401 util::logf("Edited Tile16 stamp (size=%dx, tiles=%zu).", tile8_stamp_size_,
402 staged_tiles.size());
403
404 return absl::OkStatus();
405}
406
408 Tile16LocalPos tile_position, bool left_click, bool right_click) {
409 if (!left_click && !right_click) {
410 return absl::OkStatus();
411 }
412
413 if (right_click) {
414 RETURN_IF_ERROR(PickTile8FromTile16(tile_position));
415 util::logf("Picked tile8 from tile16 at (%d, %d)",
416 static_cast<int>(tile_position.x),
417 static_cast<int>(tile_position.y));
418 return absl::OkStatus();
419 }
420
421 switch (edit_mode_) {
423 // Pass nullptr to let DrawToCurrentTile16 handle flipping and store
424 // correct TileInfo metadata. The preview bitmap is pre-flipped for
425 // display only.
426 RETURN_IF_ERROR(DrawToCurrentTile16(tile_position, nullptr));
427 break;
430 RETURN_IF_ERROR(PickTile8FromTile16(tile_position));
431 break;
432 }
433
434 return absl::OkStatus();
435}
436
438 Tile16LocalPos display_position) {
439 return Tile16LocalPos{display_position.x / kTile16PreviewDisplayScale,
440 display_position.y / kTile16PreviewDisplayScale};
441}
443 if (!HasCurrentGfxBitmap() || current_gfx_bmp_->data() == nullptr) {
444 return absl::FailedPreconditionError(
445 "Current graphics bitmap not initialized");
446 }
447
449
450 // Calculate how many 8x8 tiles we can fit based on the current graphics
451 // bitmap size SNES graphics are typically 128 pixels wide (16 tiles of 8
452 // pixels each)
453 const int tiles_per_row = current_gfx_bmp_->width() / 8;
454 const int total_rows = current_gfx_bmp_->height() / 8;
455 const int total_tiles = tiles_per_row * total_rows;
456
457 current_gfx_individual_.reserve(total_tiles);
458
459 // Extract individual 8x8 tiles from the graphics bitmap
460 for (int tile_y = 0; tile_y < total_rows; ++tile_y) {
461 for (int tile_x = 0; tile_x < tiles_per_row; ++tile_x) {
462 zelda3::Tile8PixelData tile_data{};
463
464 // Extract tile data from the main graphics bitmap.
465 // Preserve encoded palette offsets unless normalization is enabled.
466 for (int py = 0; py < 8; ++py) {
467 for (int px = 0; px < 8; ++px) {
468 int src_x = tile_x * 8 + px;
469 int src_y = tile_y * 8 + py;
470 int src_index = src_y * current_gfx_bmp_->width() + src_x;
471 int dst_index = py * 8 + px;
472
473 if (src_index < static_cast<int>(current_gfx_bmp_->size()) &&
474 dst_index < 64) {
475 uint8_t pixel_value = current_gfx_bmp_->data()[src_index];
476
478 pixel_value &= palette_normalization_mask_;
479 }
480
481 tile_data[dst_index] = pixel_value;
482 }
483 }
484 }
485
486 current_gfx_individual_.push_back(tile_data);
487 }
488 }
489
490 // Apply current palette settings to all tiles when a display palette is ready.
491 // Some integration/headless initialization paths populate tile graphics before
492 // the overworld palette is available; defer palette refresh in that case.
493 if (rom_) {
494 const gfx::SnesPalette* display_palette = ResolveDisplayPalette();
495 if (display_palette && !display_palette->empty()) {
497 } else {
499 "LoadTile8: display palette not available yet; deferring refresh");
500 }
501 }
502
504 on_tile8_sheet_resized_(static_cast<float>(current_gfx_bmp_->width()) *
506 static_cast<float>(current_gfx_bmp_->height()) *
508 }
509
510 util::logf("Loaded %zu individual tile8 graphics",
512 return absl::OkStatus();
513}
514
515absl::Status Tile16EditSession::SetCurrentTile(int tile_id) {
516 if (tile_id < 0 || tile_id >= kTile16Count) {
517 return absl::OutOfRangeError(
518 absl::StrFormat("Invalid tile16 id: %d", tile_id));
519 }
520
521 if (!tile16_blockset_ || !rom_) {
522 return absl::FailedPreconditionError(
523 "Tile16 blockset or ROM not initialized");
524 }
525
526 ASSIGN_OR_RETURN(auto definition, ReadDocumentTile(tile_id));
527 current_tile16_ = tile_id;
528 jump_to_tile_id_ = tile_id;
529 current_tile16_data_ = definition;
530 const bool loaded_pending_metadata = document_edits_.contains(tile_id);
531 SyncTilesInfoArray(&current_tile16_data_);
532
534
535 if (loaded_pending_metadata) {
537 }
538
539 util::logf("SetCurrentTile: loaded tile %d successfully", tile_id);
540
543 }
546 }
547 return absl::OkStatus();
548}
549
550void Tile16EditSession::RequestTileSwitch(int target_tile_id) {
551 // Validate that the tile16 editor is properly initialized
552 if (!tile16_blockset_ || !rom_) {
554 "RequestTileSwitch: Editor not initialized (blockset=%p, rom=%p)",
556 return;
557 }
558
559 // Validate target tile ID
560 if (target_tile_id < 0 || target_tile_id >= kTile16Count) {
561 util::logf("RequestTileSwitch: Invalid target tile ID %d", target_tile_id);
562 return;
563 }
564
565 // Check if we're already on this tile
566 if (target_tile_id == current_tile16_) {
567 return;
568 }
569
570 const auto status = SetCurrentTile(target_tile_id);
571 if (!status.ok())
572 util::logf("Tile selection failed: %s", status.message().data());
573}
574
576 if (tile_id < 0 || tile_id >= kTile16Count) {
577 return absl::InvalidArgumentError("Invalid tile ID");
578 }
579 if (!rom_) {
580 return absl::FailedPreconditionError("ROM not available");
581 }
582
583 auto pending_tile_it = document_edits_.find(tile_id);
584 if (tile_id == current_tile16_) {
586 } else if (pending_tile_it != document_edits_.end()) {
587 clipboard_tile16_.tile_data = pending_tile_it->second;
588 } else {
590 }
591 SyncTilesInfoArray(&clipboard_tile16_.tile_data);
592
593 bool bitmap_copied = false;
594 auto pending_bitmap_it = edited_tile_bitmaps_.find(tile_id);
595 if (tile_id == current_tile16_ && current_tile16_bmp_.is_active()) {
599 bitmap_copied = true;
600 } else if (pending_bitmap_it != edited_tile_bitmaps_.end() &&
601 pending_bitmap_it->second.is_active()) {
603 pending_bitmap_it->second.vector());
604 clipboard_tile16_.bitmap.SetPalette(pending_bitmap_it->second.palette());
605 bitmap_copied = true;
606 } else if (tile16_blockset_) {
607 auto tile_pixels = gfx::GetTilemapData(*tile16_blockset_, tile_id);
608 if (!tile_pixels.empty()) {
611 bitmap_copied = true;
612 }
613 }
614
615 if (bitmap_copied) {
618 }
619
621 return absl::OkStatus();
622}
623
650
652 if (slot < 0 || slot >= kNumScratchSlots) {
653 return absl::InvalidArgumentError("Invalid scratch space slot");
654 }
656 return absl::FailedPreconditionError("No active tile16 to save");
657 }
658
659 scratch_space_[slot].tile_data = current_tile16_data_;
660 SyncTilesInfoArray(&scratch_space_[slot].tile_data);
661 scratch_space_[slot].bitmap.Create(kTile16Size, kTile16Size, 8,
663 scratch_space_[slot].bitmap.SetPalette(current_tile16_bmp_.palette());
664 // Queue texture creation via Arena's deferred system
666 &scratch_space_[slot].bitmap);
667
668 scratch_space_[slot].has_data = true;
669 return absl::OkStatus();
670}
671
673 if (slot < 0 || slot >= kNumScratchSlots) {
674 return absl::InvalidArgumentError("Invalid scratch space slot");
675 }
676
677 if (!scratch_space_[slot].has_data) {
678 return absl::FailedPreconditionError("Scratch space slot is empty");
679 }
680
681 current_tile16_data_ = scratch_space_[slot].tile_data;
682 SyncTilesInfoArray(&current_tile16_data_);
683
684 if (scratch_space_[slot].bitmap.is_active()) {
686 scratch_space_[slot].bitmap.vector());
687 current_tile16_bmp_.SetPalette(scratch_space_[slot].bitmap.palette());
691 } else {
693 }
694
698 }
700 return absl::OkStatus();
701}
702
704 if (slot < 0 || slot >= kNumScratchSlots) {
705 return absl::InvalidArgumentError("Invalid scratch space slot");
706 }
707
708 scratch_space_[slot].has_data = false;
709 return absl::OkStatus();
710}
711
712// Advanced editing features
715 return absl::FailedPreconditionError("No active tile16 to flip");
716 }
717
719
724 }
725
726 // Record metadata for the enclosing document edit transaction.
728
729 return absl::OkStatus();
730}
731
734 return absl::FailedPreconditionError("No active tile16 to flip");
735 }
736
738
743 }
744
745 // Record metadata for the enclosing document edit transaction.
747
748 return absl::OkStatus();
749}
750
753 return absl::FailedPreconditionError("No active tile16 to rotate");
754 }
755
756 // Tile16 metadata does not support arbitrary 8x8 rotation flags.
757 // Rotate the 2x2 quadrant layout in a persistable way.
759
764 }
765
766 // Record metadata for the enclosing document edit transaction.
768
769 return absl::OkStatus();
770}
771
773 if (current_gfx_individual_.empty()) {
774 if (!HasCurrentGfxBitmap()) {
775 return absl::FailedPreconditionError("Source tile8 bitmap not active");
776 }
778 }
779
780 if (tile8_id < 0 ||
781 tile8_id >= static_cast<int>(current_gfx_individual_.size())) {
782 return absl::InvalidArgumentError("Invalid tile8 ID");
783 }
784
785 const gfx::TileInfo fill_info(static_cast<uint16_t>(tile8_id),
788 for (int quadrant = 0; quadrant < 4; ++quadrant) {
789 TileInfoForQuadrant(&current_tile16_data_, quadrant) = fill_info;
790 }
791 SyncTilesInfoArray(&current_tile16_data_);
792
797 }
798
799 // Record metadata for the enclosing document edit transaction.
801
802 return absl::OkStatus();
803}
804
807 return absl::FailedPreconditionError("No active tile16 to clear");
808 }
809
810 const gfx::TileInfo clear_info(0, current_palette_, false, false, false);
811 for (int quadrant = 0; quadrant < 4; ++quadrant) {
812 TileInfoForQuadrant(&current_tile16_data_, quadrant) = clear_info;
813 }
814 SyncTilesInfoArray(&current_tile16_data_);
815
820 }
821
822 // Record metadata for the enclosing document edit transaction.
824
825 return absl::OkStatus();
826}
827
828// Palette management
829absl::Status Tile16EditSession::CyclePalette(bool forward) {
830 uint8_t new_palette = current_palette_;
831
832 if (forward) {
833 new_palette = (new_palette + 1) % 8;
834 } else {
835 new_palette = (new_palette == 0) ? 7 : new_palette - 1;
836 }
837
838 current_palette_ = new_palette;
839
840 // Use the RefreshAllPalettes method which handles all the coordination
842
843 util::logf("Cycled to palette slot %d", current_palette_);
844 return absl::OkStatus();
845}
846
847absl::Status Tile16EditSession::PreviewPaletteChange(uint8_t palette_id) {
849 return absl::OkStatus();
850 }
851
852 if (palette_id >= 8) {
853 return absl::InvalidArgumentError("Invalid palette ID");
854 }
855
856 // Create a preview bitmap with the new palette
857 if (!preview_tile16_.is_active()) {
859 } else {
860 // Recreate the preview bitmap with new data
862 }
863
864 const gfx::SnesPalette* display_palette = ResolveDisplayPalette();
865 if (!display_palette || display_palette->empty()) {
866 return absl::OkStatus();
867 }
868
869 const bool use_sub_palette_view =
871 if (use_sub_palette_view) {
872 const int sheet_index = GetSheetIndexForTile8(current_tile8_);
873 const int palette_slot =
874 GetActualPaletteSlot(static_cast<int>(palette_id), sheet_index);
875 if (palette_slot >= 0 &&
876 static_cast<size_t>(palette_slot + 16) <= display_palette->size()) {
878 *display_palette, static_cast<size_t>(palette_slot + 1), 15);
879 } else {
880 preview_tile16_.SetPaletteWithTransparent(*display_palette, 1, 15);
881 }
882 } else {
883 preview_tile16_.SetPalette(*display_palette);
884 }
885
886 // Queue texture update via Arena's deferred system
889 preview_dirty_ = true;
890
891 return absl::OkStatus();
892}
893
894absl::Status Tile16EditSession::ApplyPaletteToAllImpl(uint8_t palette_id) {
895 if (palette_id >= 8) {
896 return absl::InvalidArgumentError("Invalid palette ID");
897 }
898
899 auto* tile_data = GetCurrentTile16Data();
900 if (!tile_data) {
901 return absl::FailedPreconditionError("No current tile16 data");
902 }
903
904 zelda3::SetTile16AllQuadrantPalettes(tile_data, palette_id);
905
906 // Update current palette to match
907 current_palette_ = palette_id;
908
909 // Regenerate bitmap with new per-quadrant palette metadata
911
912 // Keep blockset/editor previews in sync with other tile16 edit operations.
916 }
917
918 // Mark as modified
920
921 util::logf("Applied palette %d to all quadrants of tile %d", palette_id,
923 return absl::OkStatus();
924}
925
927 uint8_t palette_id) {
928 if (palette_id >= 8) {
929 return absl::InvalidArgumentError("Invalid palette ID");
930 }
931 if (quadrant < 0 || quadrant > 3) {
932 return absl::InvalidArgumentError("Invalid quadrant index");
933 }
934
935 auto* tile_data = GetCurrentTile16Data();
936 if (!tile_data) {
937 return absl::FailedPreconditionError("No current tile16 data");
938 }
939
940 if (!zelda3::SetTile16QuadrantPalette(tile_data, quadrant, palette_id)) {
941 return absl::InvalidArgumentError("Invalid quadrant index");
942 }
943 current_palette_ = palette_id;
944
949 }
950
952 util::logf("Applied palette %d to quadrant %d of tile %d", palette_id,
953 quadrant, current_tile16_);
954 return absl::OkStatus();
955}
956
957// Undo/Redo system (unified UndoManager framework)
958
960 if (before_edit_)
961 before_edit_();
962 return history_->Undo();
963}
965 if (before_edit_)
966 before_edit_();
967 return history_->Redo();
968}
969
971 if (!tile16_blockset_) {
972 return absl::FailedPreconditionError("Tile16 blockset not initialized");
973 }
974
975 if (current_tile16_ < 0 || current_tile16_ >= kTile16Count) {
976 return absl::OutOfRangeError("Current tile16 ID out of range");
977 }
978
979 if (current_palette_ >= 8) {
980 return absl::OutOfRangeError("Current palette ID out of range");
981 }
982
983 return absl::OkStatus();
984}
985
986bool Tile16EditSession::IsTile16Valid(int tile_id) const {
987 return tile16_blockset_ != nullptr && tile_id >= 0 && tile_id < kTile16Count;
988}
989
990// Integration with overworld system
993 return absl::FailedPreconditionError(
994 "Use document Save or Undo for the shared Tile16 document");
995 }
996
997 if (!rom_) {
998 return absl::FailedPreconditionError("ROM not available");
999 }
1000
1002 return absl::FailedPreconditionError("No active tile16 to save");
1003 }
1004
1005 // Write the tile16 data to ROM first
1007
1008 // Update the tile16 blockset with current changes
1010
1011 // Commit changes to the tile16 blockset
1013
1016
1017 // Mark ROM as dirty so changes persist when saving
1018 rom_->set_dirty(true);
1021 last_rom_write_time_ = std::chrono::steady_clock::now();
1023
1024 util::logf("Tile16 %d saved to ROM", current_tile16_);
1025 return absl::OkStatus();
1026}
1027
1029 if (!tile16_blockset_) {
1030 return absl::FailedPreconditionError("Tile16 blockset not initialized");
1031 }
1032
1033 if (current_tile16_ < 0 || current_tile16_ >= kTile16Count) {
1034 return absl::OutOfRangeError("Current tile16 ID out of range");
1035 }
1036
1037 // Update atlas directly instead of using problematic tile cache
1039
1040 return absl::OkStatus();
1041}
1042
1044 if (!tile16_blockset_) {
1045 return absl::FailedPreconditionError("Tile16 blockset not initialized");
1046 }
1047
1048 // Regenerate the tilemap data if needed
1050 // Queue texture update via Arena's deferred system
1053 }
1054
1055 // Update individual cached tiles
1056 // Note: With the new tile cache system, tiles are automatically managed
1057 // and don't need manual modification tracking like the old system
1058 // The cache handles LRU eviction and automatic updates
1059
1060 return absl::OkStatus();
1061}
1062
1065 return absl::FailedPreconditionError(
1066 "Use document Save or Undo for the shared Tile16 document");
1067 }
1068
1069 std::vector<Tile16Commit> commits;
1070 commits.push_back({current_tile16_, current_tile16_data_});
1071
1072 // Step 1: Update ROM data with current tile16 changes
1074
1075 // Step 2: Update the local blockset to reflect changes
1077
1078 // Step 3: Update the atlas directly
1080
1081 // Step 4: Notify the parent editor (overworld editor) to regenerate its
1082 // blockset
1085 }
1086
1091 last_rom_write_time_ = std::chrono::steady_clock::now();
1093
1094 util::logf("Committed Tile16 %d changes to overworld system",
1096 return absl::OkStatus();
1097}
1098
1101 return absl::FailedPreconditionError(
1102 "Use document Save or Undo for the shared Tile16 document");
1103 }
1104
1105 // Drop the current tile's staged copy first; SetCurrentTile consults pending
1106 // state before ROM state.
1110
1111 // Reload the current tile16 from ROM to discard any local changes
1113
1114 util::logf("Discarded Tile16 changes for tile %d", current_tile16_);
1115 return absl::OkStatus();
1116}
1117
1120 return absl::FailedPreconditionError(
1121 "Use document Save or Undo for the shared Tile16 document");
1122 }
1123
1124 if (document_edits_.empty()) {
1125 return absl::OkStatus(); // Nothing to commit
1126 }
1127
1128 const int written_count = static_cast<int>(document_edits_.size());
1129 std::vector<Tile16Commit> commits;
1130 commits.reserve(document_edits_.size());
1131 util::logf("Committing %zu pending tile16 changes to ROM",
1132 document_edits_.size());
1133
1134 // Write all pending changes to ROM
1135 for (const auto& [tile_id, tile_data] : document_edits_) {
1136 auto status = rom_->WriteTile16(tile_id, zelda3::kTile16Ptr, tile_data);
1137 if (!status.ok()) {
1138 util::logf("Failed to write tile16 %d: %s", tile_id,
1139 status.message().data());
1140 return status;
1141 }
1142 commits.push_back({tile_id, tile_data});
1143 }
1144
1145 // Clear pending changes before parent refresh (overworld reads committed ROM).
1146 document_edits_.clear();
1147 edited_tile_bitmaps_.clear();
1148
1149 // Local atlas hint; full rebuild is typically done in overworld callback.
1151
1152 // Notify parent editor to refresh overworld display
1155 }
1156
1157 rom_->set_dirty(true);
1159 last_rom_write_count_ = written_count;
1160 last_rom_write_time_ = std::chrono::steady_clock::now();
1162 util::logf("All pending tile16 changes committed successfully");
1163 return absl::OkStatus();
1164}
1165
1168 return;
1169
1170 if (document_edits_.empty()) {
1171 return;
1172 }
1173
1174 util::logf("Discarding %zu pending tile16 changes", document_edits_.size());
1175
1176 document_edits_.clear();
1177 edited_tile_bitmaps_.clear();
1179
1180 // Reload current tile to restore original state
1181 auto status = SetCurrentTile(current_tile16_);
1182 if (!status.ok()) {
1183 util::logf("Failed to reload tile after discard: %s",
1184 status.message().data());
1185 }
1186}
1187
1190 return;
1191
1192 auto it = document_edits_.find(current_tile16_);
1193 if (it != document_edits_.end()) {
1194 document_edits_.erase(it);
1197 util::logf("Discarded pending changes for tile %d", current_tile16_);
1198 }
1199
1200 // Reload tile from ROM
1201 auto status = SetCurrentTile(current_tile16_);
1202 if (!status.ok()) {
1203 util::logf("Failed to reload tile after discard: %s",
1204 status.message().data());
1205 }
1206}
1207
1209 if (current_tile16_ < 0 || current_tile16_ >= kTile16Count) {
1210 return;
1211 }
1212
1213 SyncTilesInfoArray(&current_tile16_data_);
1216 preview_dirty_ = true;
1218
1219 util::logf("Updated Tile16 %d (document edits: %zu)", current_tile16_,
1220 document_edits_.size());
1221}
1222
1224 if (!rom_) {
1225 return absl::FailedPreconditionError("ROM not available for usage cache");
1226 }
1227
1228 const int total_tiles = zelda3::ComputeTile16Count(tile16_blockset_);
1229 auto tile_provider = [this](int tile_id) -> absl::StatusOr<gfx::Tile16> {
1230 auto pending_it = document_edits_.find(tile_id);
1231 if (pending_it != document_edits_.end()) {
1232 return pending_it->second;
1233 }
1234 return ReadDocumentTile(tile_id);
1235 };
1236
1237 RETURN_IF_ERROR(zelda3::BuildTile8UsageIndex(total_tiles, tile_provider,
1239
1241 return absl::OkStatus();
1242}
1244 if (!rom_ || current_tile16_ < 0 || current_tile16_ >= kTile16Count) {
1245 return absl::InvalidArgumentError("Invalid tile16 or ROM not set");
1246 }
1247
1248 // Determine which quadrant of the tile16 was clicked
1249 int quad_x = (position.x < 8) ? 0 : 1; // Left or right half
1250 int quad_y = (position.y < 8) ? 0 : 1; // Top or bottom half
1251 int quadrant = quad_x + (quad_y * 2); // 0=TL, 1=TR, 2=BL, 3=BR
1252 active_quadrant_ = std::clamp(quadrant, 0, 3);
1253
1254 // Get the tile16 data structure
1255 auto* tile16_data = GetCurrentTile16Data();
1256 if (!tile16_data) {
1257 return absl::FailedPreconditionError("Failed to get tile16 data");
1258 }
1259
1260 // Extract tile metadata from the clicked quadrant.
1261 gfx::TileInfo tile_info = zelda3::Tile16QuadrantInfo(*tile16_data, quadrant);
1262
1263 // Set the current tile8 and palette
1264 current_tile8_ = tile_info.id_;
1265 current_palette_ = tile_info.palette_;
1266
1267 // Update the flip states based on the tile info
1268 x_flip_ = tile_info.horizontal_mirror_;
1269 y_flip_ = tile_info.vertical_mirror_;
1270 priority_tile_ = tile_info.over_;
1271
1272 // Refresh the palette to match the picked tile
1275
1276 util::logf("Picked tile8 %d with palette %d from quadrant %d of tile16 %d",
1278
1279 return absl::OkStatus();
1280}
1281
1282// Get the actual CGRAM palette slot for a Tile16 palette button.
1283// ZScream and the authoritative OverworldMap::BuildTiles16Gfx path encode
1284// Tile16 colors as `(pixel & 0x0F) + (tile.palette * 0x10)`. The graphics
1285// sheet contributes the low nibble, including the left/right half of each
1286// palette row; it does not offset the tile palette row itself.
1288 int sheet_index) const {
1289 (void)sheet_index;
1290 const int clamped_button = std::clamp(palette_button, 0, 7);
1291 return clamped_button * 16;
1292}
1293
1294// Get the graphics chunk that contains a tile8 ID.
1296 // Overworld current_gfx is packed into 0x1000-byte graphics chunks. In the
1297 // editor's 128px-wide 8bpp bitmap view, each chunk is 64 Tile8 entries.
1298 constexpr int kTile8sPerGraphicsChunk = 64;
1299 int sheet_index = tile8_id / kTile8sPerGraphicsChunk;
1300
1301 return std::min(15, std::max(0, sheet_index));
1302}
1303
1307
1309 const gfx::SnesPalette& source, int target_row) const {
1310 return CreateRemappedPaletteForTile8(source, target_row, current_tile8_);
1311}
1312
1314 const gfx::SnesPalette& source, int target_row, int tile8_id) const {
1315 // Create a remapped 256-color palette where all pixel values (0-255)
1316 // are mapped to the target palette row based on their low nibble.
1317 //
1318 // This allows the source bitmap (which has pre-encoded palette offsets)
1319 // to be viewed with the same Tile16 palette row that painting will encode.
1320 //
1321 // For each palette index i:
1322 // - Extract the color index: low_nibble = i & 0x0F
1323 // - Map to target row: target_row * 16 + low_nibble
1324 // - Copy the color from source palette at that position
1325
1326 gfx::SnesPalette remapped;
1327 (void)tile8_id;
1328 const int actual_target_row = std::clamp(target_row, 0, 7);
1329
1330 for (int i = 0; i < 256; ++i) {
1331 int low_nibble = i & 0x0F;
1332 int target_index = (actual_target_row * 16) + low_nibble;
1333
1334 // Make color 0 of each row transparent
1335 if (low_nibble == 0) {
1336 gfx::SnesColor transparent_color(0);
1337 transparent_color.set_transparent(true);
1338 remapped.AddColor(transparent_color);
1339 } else if (target_index < static_cast<int>(source.size())) {
1340 remapped.AddColor(source[target_index]);
1341 } else {
1342 // Fallback to black if out of bounds
1343 remapped.AddColor(gfx::SnesColor(0));
1344 }
1345 }
1346
1347 return remapped;
1348}
1349
1350int Tile16EditSession::GetEncodedPaletteRow(uint8_t pixel_value) const {
1351 // Determine which palette row a pixel value encodes
1352 // ProcessGraphicsBuffer adds 0x88 (136) to sheets 0, 3, 4, 5
1353 // So pixel values map to rows as follows:
1354 // 0x00-0x0F (0-15): Row 0
1355 // 0x10-0x1F (16-31): Row 1
1356 // ...
1357 // 0x80-0x8F (128-143): Row 8
1358 // 0x90-0x9F (144-159): Row 9
1359 // etc.
1360 return pixel_value / 16;
1361}
1362
1364 if (overworld_palette_.size() >= 256) {
1365 return &overworld_palette_;
1366 }
1367 if (palette_.size() >= 256) {
1368 return &palette_;
1369 }
1370 if (game_data() && !game_data()->palette_groups.overworld_main.empty()) {
1372 }
1373 return nullptr;
1374}
1375
1377 const gfx::Bitmap& bitmap) const {
1378 if (!bitmap.is_active() || bitmap.data() == nullptr) {
1379 return false;
1380 }
1381
1382 for (size_t i = 0; i < bitmap.size(); ++i) {
1383 if ((bitmap.data()[i] & 0xF0) != 0) {
1384 return true;
1385 }
1386 }
1387 return false;
1388}
1389
1392 return;
1393 }
1394
1395 const gfx::SnesPalette* display_palette = ResolveDisplayPalette();
1396 gfx::SnesPalette fallback_palette;
1397 if (!display_palette || display_palette->empty()) {
1398 fallback_palette = BuildFallbackDisplayPalette();
1399 display_palette = &fallback_palette;
1400 }
1401
1402 // Most tile16 edit paths now encode the palette row directly in pixel indices
1403 // via (pixel & 0x0F) + (palette * 0x10). In that case, apply the full palette.
1404 // If normalization produced low-nibble-only pixels, keep the legacy sub-palette
1405 // view path so the advanced normalization workflow still renders correctly.
1408 const int palette_slot = GetActualPaletteSlotForCurrentTile16();
1409 if (palette_slot >= 0 &&
1410 static_cast<size_t>(palette_slot + 16) <= display_palette->size()) {
1412 *display_palette, static_cast<size_t>(palette_slot + 1), 15);
1413 } else {
1414 current_tile16_bmp_.SetPaletteWithTransparent(*display_palette, 1, 15);
1415 }
1416 } else {
1417 current_tile16_bmp_.SetPalette(*display_palette);
1418 }
1419
1423}
1424
1425// Helper methods for palette management
1426absl::Status Tile16EditSession::UpdateTile8Palette(int tile8_id) {
1427 if (tile8_id < 0 ||
1428 tile8_id >= static_cast<int>(current_gfx_individual_.size())) {
1429 return absl::InvalidArgumentError("Invalid tile8 ID");
1430 }
1431
1432 if (!rom_) {
1433 return absl::FailedPreconditionError("ROM not set");
1434 }
1435
1436 const gfx::SnesPalette* display_palette = ResolveDisplayPalette();
1437 if (!display_palette || display_palette->empty()) {
1438 return absl::FailedPreconditionError("No overworld palette available");
1439 }
1440
1441 // Validate current_palette_ index
1442 if (current_palette_ < 0 || current_palette_ >= 8) {
1443 util::logf("Warning: Invalid palette index %d, using 0", current_palette_);
1444 current_palette_ = 0;
1445 }
1446
1447 const int sheet_index = GetSheetIndexForTile8(tile8_id);
1448 const int palette_slot =
1449 GetActualPaletteSlot(static_cast<int>(current_palette_), sheet_index);
1450
1451 if (HasCurrentGfxBitmap()) {
1453 *display_palette, current_palette_, tile8_id));
1457 }
1458
1459 util::logf("Updated tile8 %d with palette slot %d (palette size: %zu colors)",
1460 tile8_id, palette_slot, display_palette->size());
1461
1462 return absl::OkStatus();
1463}
1464
1466 if (!rom_) {
1467 return absl::FailedPreconditionError("ROM not set");
1468 }
1469
1470 // Validate current_palette_ index
1471 if (current_palette_ < 0 || current_palette_ >= 8) {
1472 util::logf("Warning: Invalid palette index %d, using 0", current_palette_);
1473 current_palette_ = 0;
1474 }
1475
1476 const gfx::SnesPalette* display_palette = ResolveDisplayPalette();
1477 gfx::SnesPalette fallback_palette;
1478 if (!display_palette || display_palette->empty()) {
1479 fallback_palette = BuildFallbackDisplayPalette();
1480 display_palette = &fallback_palette;
1481 util::logf("Display palette unavailable; using fallback grayscale palette");
1482 }
1483 util::logf("Using resolved display palette with %zu colors",
1484 display_palette->size());
1485
1486 // The source bitmap (current_gfx_bmp_) contains 8bpp indexed pixel data with
1487 // palette rows already encoded in the high nibble. Remap the display palette
1488 // so every source index shows the currently selected brush row while the
1489 // underlying pixel data remains untouched.
1490 if (HasCurrentGfxBitmap()) {
1491 gfx::SnesPalette remapped_palette =
1493 current_gfx_bmp_->SetPalette(remapped_palette);
1494 util::logf("Applied brush palette %d to source bitmap", current_palette_);
1495
1497 // Queue texture update via Arena's deferred system
1500 }
1501
1502 // Update current tile16 being edited - regenerate from ROM so per-quadrant
1503 // palette metadata is applied via the pixel transform
1505 auto regen_status = RegenerateTile16BitmapFromROM();
1506 if (!regen_status.ok()) {
1507 // Fallback: just apply palette directly
1508 current_tile16_bmp_.SetPalette(*display_palette);
1512 }
1513 }
1514
1515 util::logf(
1516 "Successfully refreshed all palettes in tile16 editor with palette %d",
1518 return absl::OkStatus();
1519}
1520
1522 util::logf("=== TILE8 SOURCE DATA ANALYSIS ===");
1523
1524 // Analyze current_gfx_bmp_
1525 util::logf("current_gfx_bmp_:");
1526 util::logf(" - Active: %s", HasCurrentGfxBitmap() ? "yes" : "no");
1527 util::logf(" - Size: %dx%d", current_gfx_bmp_->width(),
1529 util::logf(" - Depth: %d bpp", current_gfx_bmp_->depth());
1530 util::logf(" - Data size: %zu bytes", current_gfx_bmp_->size());
1531 util::logf(" - Palette size: %zu colors",
1533
1534 // Analyze pixel value distribution in first 64 pixels (first tile8)
1535 if (current_gfx_bmp_->data() && current_gfx_bmp_->size() >= 64) {
1536 std::map<uint8_t, int> pixel_counts;
1537 for (size_t i = 0; i < 64; ++i) {
1538 uint8_t val = current_gfx_bmp_->data()[i];
1539 pixel_counts[val]++;
1540 }
1541 util::logf(" - First tile8 (Sheet 0) pixel distribution:");
1542 for (const auto& [val, count] : pixel_counts) {
1543 int row = GetEncodedPaletteRow(val);
1544 int col = val & 0x0F;
1545 util::logf(" Value 0x%02X (%3d) = Row %d, Col %d: %d pixels", val, val,
1546 row, col, count);
1547 }
1548
1549 // Check if values are in expected 4bpp range
1550 bool all_4bpp = true;
1551 for (const auto& [val, count] : pixel_counts) {
1552 if (val > 15) {
1553 all_4bpp = false;
1554 break;
1555 }
1556 }
1557 util::logf(" - Values in raw 4bpp range (0-15): %s",
1558 all_4bpp ? "yes" : "NO (pre-encoded)");
1559
1560 // Show what the remapping does
1561 util::logf(" - Palette remapping for viewing:");
1562 util::logf(" Selected palette: %d (row %d)", current_palette_,
1564 util::logf(" Pixels are remapped: (value & 0x0F) + (selected_row * 16)");
1565 }
1566
1567 // Analyze current_gfx_individual_
1568 util::logf("current_gfx_individual_:");
1569 util::logf(" - Count: %zu tiles", current_gfx_individual_.size());
1570
1571 if (!current_gfx_individual_.empty()) {
1572 const auto& first_tile = current_gfx_individual_[0];
1573 util::logf(" - First tile:");
1574 util::logf(" - Size: 8x8");
1575 util::logf(" - Depth: 8 bpp");
1576 std::map<uint8_t, int> pixel_counts;
1577 for (uint8_t val : first_tile) {
1578 pixel_counts[val]++;
1579 }
1580 util::logf(" - Pixel distribution:");
1581 for (const auto& [val, count] : pixel_counts) {
1582 util::logf(" Value 0x%02X (%3d): %d pixels", val, val, count);
1583 }
1584 }
1585
1586 // Analyze palette state
1587 util::logf("Palette state:");
1588 util::logf(" - current_palette_: %d", current_palette_);
1589 util::logf(" - overworld_palette_ size: %zu", overworld_palette_.size());
1590 util::logf(" - palette_ size: %zu", palette_.size());
1591
1592 // Calculate expected palette slot
1593 int palette_slot = GetActualPaletteSlot(current_palette_, 0);
1594 util::logf(" - GetActualPaletteSlot(%d, 0) = %d", current_palette_,
1595 palette_slot);
1596 util::logf(" - Expected palette offset for SetPaletteWithTransparent: %d",
1597 palette_slot + 1);
1598
1599 // Show first 16 colors of the overworld palette
1600 if (overworld_palette_.size() >= 16) {
1601 util::logf(" - First 16 palette colors (row 0):");
1602 for (int i = 0; i < 16; ++i) {
1603 auto color = overworld_palette_[i];
1604 util::logf(" [%2d] SNES: 0x%04X RGB: (%d,%d,%d)", i, color.snes(),
1605 static_cast<int>(color.rgb().x),
1606 static_cast<int>(color.rgb().y),
1607 static_cast<int>(color.rgb().z));
1608 }
1609 }
1610
1611 // Show colors at the selected palette slot
1612 if (overworld_palette_.size() >= static_cast<size_t>(palette_slot + 16)) {
1613 util::logf(" - Colors at palette slot %d (row %d):", palette_slot,
1614 palette_slot / 16);
1615 for (int i = 0; i < 16; ++i) {
1616 auto color = overworld_palette_[palette_slot + i];
1617 util::logf(" [%2d] SNES: 0x%04X RGB: (%d,%d,%d)", i, color.snes(),
1618 static_cast<int>(color.rgb().x),
1619 static_cast<int>(color.rgb().y),
1620 static_cast<int>(color.rgb().z));
1621 }
1622 }
1623
1624 util::logf("=== END ANALYSIS ===");
1625}
1627 if (slot < 0 || slot >= 4) {
1628 return absl::InvalidArgumentError("Invalid scratch slot");
1629 }
1630
1632 if (total_tiles <= 0) {
1633 return absl::FailedPreconditionError("Tile16 blockset is not available");
1634 }
1635
1636 const int start_tile = std::clamp(current_tile16_, 0, total_tiles - 1);
1637 for (int y = 0; y < 8; ++y) {
1638 for (int x = 0; x < 8; ++x) {
1639 const int tile_id = start_tile + (y * 8) + x;
1640 layout_scratch_[slot].tile_layout[y][x] =
1641 (tile_id < total_tiles) ? tile_id : -1;
1642 }
1643 }
1644
1645 layout_scratch_[slot].in_use = true;
1646 layout_scratch_[slot].name =
1647 absl::StrFormat("From %03X", static_cast<uint16_t>(start_tile));
1648
1649 return absl::OkStatus();
1650}
1651
1653 if (slot < 0 || slot >= 4) {
1654 return absl::InvalidArgumentError("Invalid scratch slot");
1655 }
1656
1657 if (!layout_scratch_[slot].in_use) {
1658 return absl::FailedPreconditionError("Scratch slot is empty");
1659 }
1660
1661 const int first_tile = layout_scratch_[slot].tile_layout[0][0];
1662 if (first_tile < 0) {
1663 return absl::FailedPreconditionError("Scratch slot has no valid tile data");
1664 }
1665
1666 RETURN_IF_ERROR(SetCurrentTile(first_tile));
1667 scroll_to_current_ = true;
1668
1669 return absl::OkStatus();
1670}
1672 // Skip if live preview is disabled
1673 if (!live_preview_enabled_) {
1674 return absl::OkStatus();
1675 }
1676
1677 // Check if preview needs updating
1678 if (!preview_dirty_) {
1679 return absl::OkStatus();
1680 }
1681
1682 // Ensure we have valid tile data
1684 preview_dirty_ = false;
1685 return absl::OkStatus();
1686 }
1687
1688 // Update the preview bitmap from current tile16
1689 if (!preview_tile16_.is_active()) {
1691 } else {
1692 // Recreate with updated data
1694 }
1695
1696 // Apply the current palette
1697 const gfx::SnesPalette* display_palette = ResolveDisplayPalette();
1698 if (display_palette && !display_palette->empty()) {
1699 const bool use_sub_palette_view =
1701 if (use_sub_palette_view) {
1702 const int sheet_index = GetSheetIndexForTile8(current_tile8_);
1703 const int palette_slot =
1704 GetActualPaletteSlot(static_cast<int>(current_palette_), sheet_index);
1705 if (palette_slot >= 0 &&
1706 static_cast<size_t>(palette_slot + 16) <= display_palette->size()) {
1708 *display_palette, static_cast<size_t>(palette_slot + 1), 15);
1709 } else {
1710 preview_tile16_.SetPaletteWithTransparent(*display_palette, 1, 15);
1711 }
1712 } else {
1713 preview_tile16_.SetPalette(*display_palette);
1714 }
1715 }
1716
1717 // Queue texture update
1720
1721 // Clear the dirty flag
1722 preview_dirty_ = false;
1723
1724 return absl::OkStatus();
1725}
1739
1740} // namespace editor
1741} // namespace yaze
absl::Status WriteTile16(int tile16_id, uint32_t tile16_ptr, const gfx::Tile16 &tile)
Definition rom.cc:840
void set_dirty(bool dirty)
Definition rom.h:189
gfx::SnesPalette CreateRemappedPaletteForTile8(const gfx::SnesPalette &source, int target_row, int tile8_id) const
absl::Status ApplyPaletteToAllImpl(uint8_t palette_id)
int GetActualPaletteSlot(int palette_button, int sheet_index) const
gfx::SnesPalette CreateRemappedPaletteForViewing(const gfx::SnesPalette &source, int target_row) const
std::array< Tile16ScratchData, 4 > scratch_space_
bool IsTile16Valid(int tile_id) const
absl::Status HandleTile16CanvasClick(Tile16LocalPos tile_position, bool left_click, bool right_click)
absl::Status RunEdit(const std::function< absl::Status()> &operation)
absl::Status ApplyPaletteToQuadrantImpl(int quadrant, uint8_t palette_id)
zelda3::GameData * game_data() const
absl::StatusOr< gfx::Tile16 > ReadDocumentTile(int id) const
absl::Status FillTile16WithTile8Impl(int tile8_id)
void CopyTileBitmapToBlockset(int tile_id, const gfx::Bitmap &tile_bitmap)
void set_palette(const gfx::SnesPalette &palette)
const std::array< uint8_t, 0x200 > & all_tiles_types() const
std::map< int, gfx::Bitmap > edited_tile_bitmaps_
absl::Status CyclePalette(bool forward=true)
absl::Status InitializeBitmaps(gfx::Bitmap &tile16_blockset_bmp, gfx::Bitmap &current_gfx_bmp, std::array< uint8_t, 0x200 > &all_tiles_types)
absl::Status PickTile8FromTile16(Tile16LocalPos position)
absl::Status ApplyPaletteToAll(uint8_t palette_id)
std::array< uint8_t, 0x200 > all_tiles_types_
absl::Status BuildStampPreview(Tile16LocalPos pos, gfx::Bitmap *output) const
absl::Status DrawToCurrentTile16Impl(Tile16LocalPos pos, const gfx::Bitmap *source_tile)
absl::Status DrawToCurrentTile16(Tile16LocalPos pos, const gfx::Bitmap *source_tile=nullptr)
std::function< void(int)> on_tile_selection_changed_
absl::Status LoadLayoutFromScratch(int slot)
std::function< void(int)> on_current_tile_changed_
absl::Status CopyTile16ToClipboard(int tile_id)
std::chrono::steady_clock::time_point last_rom_write_time_
std::map< int, gfx::Tile16 > document_edits_
absl::Status PreviewPaletteChange(uint8_t palette_id)
absl::Status BuildTile16BitmapFromData(const gfx::Tile16 &tile_data, gfx::Bitmap *output_bitmap) const
absl::Status SaveLayoutToScratch(int slot)
absl::Status FillTile16WithTile8(int tile8_id)
absl::Status ApplyPaletteToQuadrant(int quadrant, uint8_t palette_id)
std::function< void(float, float)> on_tile8_sheet_resized_
absl::Status LoadTile16FromScratchSpace(int slot)
std::array< Tile16LayoutScratch, 4 > layout_scratch_
zelda3::Tile8UsageIndex tile8_usage_cache_
void RequestTileSwitch(int target_tile_id)
absl::Status SaveTile16ToScratchSpace(int slot)
std::function< absl::Status(const std::vector< Tile16Commit > &) on_changes_committed_)
absl::Status UpdateTile8Palette(int tile8_id)
int GetEncodedPaletteRow(uint8_t pixel_value) const
const gfx::SnesPalette & palette() const
const gfx::SnesPalette * ResolveDisplayPalette() const
int GetSheetIndexForTile8(int tile8_id) const
static Tile16LocalPos Tile16PreviewDisplayPixelToTilePosition(Tile16LocalPos display_position)
bool BitmapHasEncodedPaletteRows(const gfx::Bitmap &bitmap) const
absl::Status LoadTile16FromScratchSpaceImpl(int slot)
std::vector< gfx::Tile16 > * document_definitions_
absl::StatusOr< std::vector< zelda3::Tile16StampMutation > > BuildStampMutations(Tile16LocalPos pos) const
std::vector< zelda3::Tile8PixelData > current_gfx_individual_
absl::Status ClearScratchSpace(int slot)
absl::Status Redo()
Redo the top action. Returns error if stack is empty.
absl::Status Undo()
Undo the top action. Returns error if stack is empty.
void QueueTextureCommand(TextureCommandType type, Bitmap *bitmap)
Definition arena.cc:39
static Arena & Get()
Definition arena.cc:24
Represents a bitmap image optimized for SNES ROM hacking.
Definition bitmap.h:70
const uint8_t * data() const
Definition bitmap.h:407
const SnesPalette & palette() const
Definition bitmap.h:393
void Create(int width, int height, int depth, std::span< uint8_t > data)
Create a bitmap with the given dimensions and data.
Definition bitmap.cc:198
const std::vector< uint8_t > & vector() const
Definition bitmap.h:411
auto size() const
Definition bitmap.h:406
bool is_active() const
Definition bitmap.h:414
void set_modified(bool modified)
Definition bitmap.h:418
int height() const
Definition bitmap.h:399
void SetPalette(const SnesPalette &palette)
Set the palette for the bitmap using SNES palette format.
Definition bitmap.cc:452
int width() const
Definition bitmap.h:398
int depth() const
Definition bitmap.h:400
void SetPaletteWithTransparent(const SnesPalette &palette, size_t index, int length=7)
Set the palette with a transparent color.
Definition bitmap.cc:524
bool modified() const
Definition bitmap.h:413
RAII timer for automatic timing management.
SNES Color container.
Definition snes_color.h:110
constexpr void set_transparent(bool t)
Definition snes_color.h:197
Represents a palette of colors for the Super Nintendo Entertainment System (SNES).
void AddColor(const SnesColor &color)
static constexpr size_t kMaxColors
Tile composition of four 8x8 tiles.
Definition snes_tile.h:142
SNES 16-bit tile metadata container.
Definition snes_tile.h:52
#define ASSIGN_OR_RETURN(type_variable_name, expression)
Definition macro.h:62
gfx::TileInfo & TileInfoForQuadrant(gfx::Tile16 *tile, int quadrant)
constexpr int kTile16PixelCount
constexpr int kTile8Size
constexpr int kTile16Size
constexpr int kNumScratchSlots
std::vector< uint8_t > GetTilemapData(Tilemap &tilemap, int tile_id)
Definition tilemap.cc:268
void logf(const absl::FormatSpec< Args... > &format, Args &&... args)
Definition log.h:146
gfx::Tile16 HorizontalFlipTile16(gfx::Tile16 tile)
gfx::TileInfo & MutableTile16QuadrantInfo(gfx::Tile16 &tile, int quadrant)
void SetTile16AllQuadrantPalettes(gfx::Tile16 *tile, uint8_t palette_id)
absl::Status RenderTile16BitmapFromMetadata(const gfx::Tile16 &tile_data, const std::vector< Tile8PixelData > &tile8_pixels, gfx::Bitmap *output_bitmap)
void SyncTile16TilesInfo(gfx::Tile16 *tile)
constexpr int kNumTile16Individual
Definition overworld.h:243
gfx::Tile16 VerticalFlipTile16(gfx::Tile16 tile)
gfx::Tile16 RotateTile16Clockwise(gfx::Tile16 tile)
constexpr uint32_t kTile16Ptr
Definition game_data.h:61
int ComputeTile16Count(const gfx::Tilemap *tile16_blockset)
absl::StatusOr< std::vector< Tile16StampMutation > > BuildTile16StampMutations(const Tile16StampRequest &request)
std::vector< uint8_t > RenderTile16PixelsFromMetadata(const gfx::Tile16 &tile_data, const std::vector< Tile8PixelData > &tile8_pixels)
const gfx::TileInfo & Tile16QuadrantInfo(const gfx::Tile16 &tile, int quadrant)
absl::Status BuildTile8UsageIndex(int total_tiles, const std::function< absl::StatusOr< gfx::Tile16 >(int)> &tile_provider, Tile8UsageIndex *usage_index)
void BlitTile16BitmapToAtlas(gfx::Bitmap *destination, int tile_id, const gfx::Bitmap &source_bitmap)
bool SetTile16QuadrantPalette(gfx::Tile16 *tile, int quadrant, uint8_t palette_id)
std::array< uint8_t, 64 > Tile8PixelData
#define RETURN_IF_ERROR(expr)
Definition snes.cc:22
const SnesPalette & palette_ref(int i) const
Bitmap atlas
Master bitmap containing all tiles.
Definition tilemap.h:119
gfx::PaletteGroupMap palette_groups
Definition game_data.h:95