yaze 0.2.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
tile16_editor.cc
Go to the documentation of this file.
1#include "tile16_editor.h"
2
3#include <future>
4
5#include "absl/status/status.h"
8#include "app/gfx/bitmap.h"
10#include "app/gui/canvas.h"
11#include "app/gui/input.h"
12#include "app/gui/style.h"
13#include "app/rom.h"
15#include "imgui/imgui.h"
16#include "util/hex.h"
17
18namespace yaze {
19namespace editor {
20
21using core::Renderer;
22using namespace ImGui;
23
25 const gfx::Bitmap &tile16_blockset_bmp, const gfx::Bitmap &current_gfx_bmp,
26 std::array<uint8_t, 0x200> &all_tiles_types) {
27 all_tiles_types_ = all_tiles_types;
28 current_gfx_bmp_.Create(current_gfx_bmp.width(), current_gfx_bmp.height(),
29 current_gfx_bmp.depth(), current_gfx_bmp.vector());
30 current_gfx_bmp_.SetPalette(current_gfx_bmp.palette());
33 tile16_blockset_bmp.width(), tile16_blockset_bmp.height(),
34 tile16_blockset_bmp.depth(), tile16_blockset_bmp.vector());
35 tile16_blockset_bmp_.SetPalette(tile16_blockset_bmp.palette());
37 // RETURN_IF_ERROR(LoadTile8());
39
40 ImVector<std::string> tile16_names;
41 for (int i = 0; i < 0x200; ++i) {
42 std::string str = util::HexByte(all_tiles_types_[i]);
43 tile16_names.push_back(str);
44 }
45 *tile8_source_canvas_.mutable_labels(0) = tile16_names;
46 *tile8_source_canvas_.custom_labels_enabled() = true;
47
49 [&]() { Text("Tile16 ID: %02X", current_tile16_); });
51 [&]() { Text("Tile8 ID: %02X", current_tile8_); });
52
53 gui::AddTableColumn(tile_edit_table_, "##tile16Flip", [&]() {
54 Checkbox("X Flip", &x_flip);
55 Checkbox("Y Flip", &y_flip);
56 Checkbox("Priority", &priority_tile);
57 });
58
59 return absl::OkStatus();
60}
61
62absl::Status Tile16Editor::Update() {
64 return absl::InvalidArgumentError("Blockset not initialized, open a ROM.");
65 }
66
67 if (BeginMenuBar()) {
68 if (BeginMenu("View")) {
69 Checkbox("Show Collision Types",
70 tile8_source_canvas_.custom_labels_enabled());
71 EndMenu();
72 }
73
74 if (BeginMenu("Edit")) {
75 if (MenuItem("Copy Current Tile16")) {
77 }
78 if (MenuItem("Paste to Current Tile16")) {
80 }
81 Separator();
82 if (MenuItem("Save to Scratch Space 1")) {
84 }
85 if (MenuItem("Save to Scratch Space 2")) {
87 }
88 if (MenuItem("Save to Scratch Space 3")) {
90 }
91 if (MenuItem("Save to Scratch Space 4")) {
93 }
94 Separator();
95 if (MenuItem("Load from Scratch Space 1")) {
97 }
98 if (MenuItem("Load from Scratch Space 2")) {
100 }
101 if (MenuItem("Load from Scratch Space 3")) {
103 }
104 if (MenuItem("Load from Scratch Space 4")) {
106 }
107 EndMenu();
108 }
109
110 if (BeginMenu("Help")) {
111 if (MenuItem("About Tile16 Editor")) {
112 OpenPopup("About Tile16 Editor");
113 }
114 EndMenu();
115 }
116
117 EndMenuBar();
118 }
119
120 // About popup
121 if (BeginPopupModal("About Tile16 Editor", NULL,
122 ImGuiWindowFlags_AlwaysAutoResize)) {
123 Text("Tile16 Editor for Link to the Past");
124 Text("This editor allows you to edit 16x16 tiles used in the game.");
125 Text("Features:");
126 BulletText("Edit Tile16 graphics by placing 8x8 tiles in the quadrants");
127 BulletText("Copy and paste Tile16 graphics");
128 BulletText("Save and load Tile16 graphics to/from scratch space");
129 BulletText("Preview Tile16 graphics at a larger size");
130 Separator();
131 if (Button("Close")) {
132 CloseCurrentPopup();
133 }
134 EndPopup();
135 }
136
137 if (BeginTabBar("Tile16 Editor Tabs")) {
140 EndTabBar();
141 }
142 return absl::OkStatus();
143}
144
146 if (BeginTabItem("Tile16 Editing")) {
147 if (BeginTable("#Tile16EditorTable", 2, TABLE_BORDERS_RESIZABLE,
148 ImVec2(0, 0))) {
149 TableSetupColumn("Blockset", ImGuiTableColumnFlags_WidthFixed,
150 GetContentRegionAvail().x);
151 TableSetupColumn("Properties", ImGuiTableColumnFlags_WidthStretch,
152 GetContentRegionAvail().x);
153 TableHeadersRow();
154 TableNextRow();
155 TableNextColumn();
157
158 TableNextColumn();
160
161 EndTable();
162 }
163 EndTabItem();
164 }
165}
166
169 gui::BeginChildWithScrollbar("##Tile16EditorBlocksetScrollRegion");
170 blockset_canvas_.DrawBackground();
172 blockset_canvas_.DrawContextMenu();
173 blockset_canvas_.DrawTileSelector(32);
175 blockset_canvas_.DrawGrid();
176 blockset_canvas_.DrawOverlay();
177 EndChild();
178
179 if (!blockset_canvas_.points().empty()) {
180 notify_tile16.edit() = blockset_canvas_.GetTileIdFromMousePos();
181 notify_tile16.commit();
182
183 if (notify_tile16.modified()) {
187 auto ow_main_pal_group = rom()->palette_group().overworld_main;
188 current_tile16_bmp_.SetPalette(ow_main_pal_group[current_palette_]);
190 }
191 }
192
193 return absl::OkStatus();
194}
195
196absl::Status Tile16Editor::DrawToCurrentTile16(ImVec2 click_position) {
197 constexpr int tile8_size = 8;
198 constexpr int tile16_size = 16;
199
200 // Calculate the tile index for x and y based on the click_position
201 // Adjusting for Tile16 (16x16) which contains 4 Tile8 (8x8)
202 int tile_index_x = static_cast<int>(click_position.x) / tile8_size;
203 int tile_index_y = static_cast<int>(click_position.y) / tile8_size;
204
205 // Ensure we're within the bounds of the Tile16 (0-1 for both x and y)
206 tile_index_x = std::min(1, std::max(0, tile_index_x));
207 tile_index_y = std::min(1, std::max(0, tile_index_y));
208
209 // Calculate the pixel start position within the Tile16
210 // Each Tile8 is 8x8 pixels, so we multiply by 8 to get the pixel offset
211 int start_x = tile_index_x * tile8_size;
212 int start_y = tile_index_y * tile8_size;
213
214 // Draw the Tile8 to the correct position within the Tile16
215 for (int y = 0; y < tile8_size; ++y) {
216 for (int x = 0; x < tile8_size; ++x) {
217 // Calculate the pixel position in the Tile16 bitmap
218 int pixel_x = start_x + x;
219 int pixel_y = start_y + y;
220 int pixel_index = pixel_y * tile16_size + pixel_x;
221
222 // Calculate the pixel position in the Tile8 bitmap
223 int gfx_pixel_index = y * tile8_size + x;
224
225 // Apply flipping if needed
226 if (x_flip) {
227 gfx_pixel_index = y * tile8_size + (tile8_size - 1 - x);
228 }
229 if (y_flip) {
230 gfx_pixel_index = (tile8_size - 1 - y) * tile8_size + x;
231 }
232 if (x_flip && y_flip) {
233 gfx_pixel_index =
234 (tile8_size - 1 - y) * tile8_size + (tile8_size - 1 - x);
235 }
236
237 // Write the pixel to the Tile16 bitmap
238 current_tile16_bmp_.WriteToPixel(
239 pixel_index,
240 current_gfx_individual_[current_tile8_].data()[gfx_pixel_index]);
241 }
242 }
243
244 return absl::OkStatus();
245}
246
248 auto ow_main_pal_group = rom()->palette_group().overworld_main;
249
250 // Create a more organized layout with tabs
251 if (BeginTabBar("Tile16EditorTabs")) {
252 // Main editing tab
253 if (BeginTabItem("Edit")) {
254 // Top section: Tile8 selector and Tile16 editor side by side
255 if (BeginTable("##Tile16EditorLayout", 2, TABLE_BORDERS_RESIZABLE,
256 ImVec2(0, 0))) {
257 // Left column: Tile8 selector
258 TableSetupColumn("Tile8 Selector", ImGuiTableColumnFlags_WidthFixed,
259 GetContentRegionAvail().x * 0.6f);
260 // Right column: Tile16 editor
261 TableSetupColumn("Tile16 Editor", ImGuiTableColumnFlags_WidthStretch,
262 GetContentRegionAvail().x * 0.4f);
263
264 TableHeadersRow();
265 TableNextRow();
266
267 // Tile8 selector column
268 TableNextColumn();
269 if (BeginChild("Tile8 Selector", ImVec2(0, 0x175), true)) {
270 tile8_source_canvas_.DrawBackground();
271 tile8_source_canvas_.DrawContextMenu();
272 if (tile8_source_canvas_.DrawTileSelector(32)) {
273 current_gfx_individual_[current_tile8_].SetPaletteWithTransparent(
274 ow_main_pal_group[0], current_palette_);
277 }
278 tile8_source_canvas_.DrawBitmap(current_gfx_bmp_, 0, 0, 4.0f);
279 tile8_source_canvas_.DrawGrid();
280 tile8_source_canvas_.DrawOverlay();
281 }
282 EndChild();
283
284 // Tile16 editor column
285 TableNextColumn();
286 if (BeginChild("Tile16 Editor", ImVec2(0, 0x175), true)) {
287 tile16_edit_canvas_.DrawBackground();
288 tile16_edit_canvas_.DrawContextMenu();
289 tile16_edit_canvas_.DrawBitmap(current_tile16_bmp_, 0, 0, 4.0f);
290 if (!tile8_source_canvas_.points().empty()) {
291 if (tile16_edit_canvas_.DrawTilePainter(
294 tile16_edit_canvas_.drawn_tile_position()));
296 }
297 }
298 tile16_edit_canvas_.DrawGrid();
299 tile16_edit_canvas_.DrawOverlay();
300 }
301 EndChild();
302
303 EndTable();
304 }
305
306 // Bottom section: Options and controls
307 Separator();
308
309 // Create a table for the options
310 if (BeginTable("##Tile16EditorOptions", 2, TABLE_BORDERS_RESIZABLE,
311 ImVec2(0, 0))) {
312 // Left column: Tile properties
313 TableSetupColumn("Properties", ImGuiTableColumnFlags_WidthFixed,
314 GetContentRegionAvail().x * 0.5f);
315 // Right column: Actions
316 TableSetupColumn("Actions", ImGuiTableColumnFlags_WidthStretch,
317 GetContentRegionAvail().x * 0.5f);
318
319 TableHeadersRow();
320 TableNextRow();
321
322 // Properties column
323 TableNextColumn();
324 Text("Tile Properties:");
326
327 // Palette selector
328 Text("Palette:");
329 gui::InputHexByte("Palette", &notify_palette.edit());
330 notify_palette.commit();
331 if (notify_palette.modified()) {
332 auto palette = palettesets_[current_palette_].main_;
333 auto value = notify_palette.get();
334 if (notify_palette.get() > 0x04 && notify_palette.get() < 0x06) {
335 palette = palettesets_[current_palette_].aux1;
336 value -= 0x04;
337 } else if (notify_palette.get() > 0x06) {
338 palette = palettesets_[current_palette_].aux2;
339 value -= 0x06;
340 }
341
342 if (value > 0x00) {
343 current_gfx_bmp_.SetPaletteWithTransparent(palette, value);
345
346 current_tile16_bmp_.SetPaletteWithTransparent(palette, value);
348 }
349 }
350
351 // Actions column
352 TableNextColumn();
353 Text("Actions:");
354
355 // Clipboard actions
356 if (Button("Copy Current Tile16")) {
358 }
359 SameLine();
360 if (Button("Paste to Current Tile16")) {
362 }
363
364 // Scratch space actions
365 Separator();
366 Text("Scratch Space:");
367
368 // Create a grid of 4 buttons for scratch space
369 for (int i = 0; i < 4; i++) {
370 if (i > 0) SameLine();
371
372 if (scratch_space_used_[i]) {
373 if (Button(("Slot " + std::to_string(i)).c_str())) {
375 }
376 SameLine();
377 if (Button(("Clear##" + std::to_string(i)).c_str())) {
379 }
380 } else {
381 if (Button(("Empty##" + std::to_string(i)).c_str())) {
383 }
384 }
385 }
386
387 EndTable();
388 }
389
390 EndTabItem();
391 }
392
393 // Preview tab
394 if (BeginTabItem("Preview")) {
395 if (BeginChild("Tile16Preview", ImVec2(0, 0), true)) {
396 // Display the current Tile16 at a larger size
397 auto texture = current_tile16_bmp_.texture();
398 if (texture) {
399 ImGui::Image((ImTextureID)(intptr_t)texture, ImVec2(128, 128));
400 }
401
402 // Display information about the current Tile16
403 Text("Tile16 ID: %02X", current_tile16_);
404 Text("Current Palette: %02X", current_palette_);
405 Text("X Flip: %s", x_flip ? "Yes" : "No");
406 Text("Y Flip: %s", y_flip ? "Yes" : "No");
407 Text("Priority: %s", priority_tile ? "Yes" : "No");
408 }
409 EndChild();
410 EndTabItem();
411 }
412
413 EndTabBar();
414 }
415
416 // The user selected a tile8
417 if (!tile8_source_canvas_.points().empty()) {
418 uint16_t x = tile8_source_canvas_.points().front().x / 16;
419 uint16_t y = tile8_source_canvas_.points().front().y / 16;
420
421 current_tile8_ = x + (y * 8);
422 current_gfx_individual_[current_tile8_].SetPaletteWithTransparent(
423 ow_main_pal_group[0], current_palette_);
425 }
426
427 return absl::OkStatus();
428}
429
431 auto ow_main_pal_group = rom()->palette_group().overworld_main;
432 current_gfx_individual_.reserve(1024);
433
434 std::vector<std::future<std::array<uint8_t, 0x40>>> futures;
435
436 for (int index = 0; index < 1024; index++) {
437 auto task_function = [&]() {
438 std::array<uint8_t, 0x40> tile_data;
439 // Copy the pixel data for the current tile into the vector
440 for (int ty = 0; ty < 8; ty++) {
441 for (int tx = 0; tx < 8; tx++) {
442 // Gfx is 16 sheets of 8x8 tiles ordered 16 wide by 4 tall
443 // Calculate the position in the tile data vector
444 int position = tx + (ty * 0x08);
445
446 // Calculate the position in the current gfx data
447 int num_columns = current_gfx_bmp_.width() / 8;
448 int x = (index % num_columns) * 8 + tx;
449 int y = (index / num_columns) * 8 + ty;
450 int gfx_position = x + (y * 0x100);
451
452 // Get the pixel value from the current gfx data
453 uint8_t value = current_gfx_bmp_.data()[gfx_position];
454
455 if (value & 0x80) {
456 value -= 0x88;
457 }
458
459 tile_data[position] = value;
460 }
461 }
462 return tile_data;
463 };
464 futures.emplace_back(std::async(std::launch::async, task_function));
465 }
466
467 for (auto &future : futures) {
468 future.wait();
469 auto tile_data = future.get();
470 current_gfx_individual_.emplace_back();
471 auto &tile_bitmap = current_gfx_individual_.back();
472 tile_bitmap.Create(0x08, 0x08, 0x08, tile_data);
473 tile_bitmap.SetPaletteWithTransparent(ow_main_pal_group[0],
475 Renderer::Get().RenderBitmap(&tile_bitmap);
476 }
477
478 return absl::OkStatus();
479}
480
481absl::Status Tile16Editor::SetCurrentTile(int id) {
482 current_tile16_ = id;
485 auto ow_main_pal_group = rom()->palette_group().overworld_main;
486 current_tile16_bmp_.SetPalette(ow_main_pal_group[current_palette_]);
488 return absl::OkStatus();
489}
490
492 if (BeginTabItem("Tile16 Transfer")) {
493 if (BeginTable("#Tile16TransferTable", 2, TABLE_BORDERS_RESIZABLE,
494 ImVec2(0, 0))) {
495 TableSetupColumn("Current ROM Tiles", ImGuiTableColumnFlags_WidthFixed,
496 GetContentRegionAvail().x / 2);
497 TableSetupColumn("Transfer ROM Tiles", ImGuiTableColumnFlags_WidthFixed,
498 GetContentRegionAvail().x / 2);
499 TableHeadersRow();
500 TableNextRow();
501
502 TableNextColumn();
504
505 TableNextColumn();
507
508 EndTable();
509 }
510
511 EndTabItem();
512 }
513 return absl::OkStatus();
514}
515
517 // Create a button for loading another ROM
518 if (Button("Load ROM")) {
519 auto transfer_rom = std::make_unique<Rom>();
520 transfer_rom_ = transfer_rom.get();
522 transfer_status_ = transfer_rom_->LoadFromFile(file_name);
523 transfer_started_ = true;
524 }
525
526 // TODO: Implement tile16 transfer
529
530 // Load the Link to the Past overworld.
532 transfer_overworld_.set_current_map(0);
533 palette_ = transfer_overworld_.current_area_palette();
534
535 // Create the tile16 blockset image
537 0x80, 0x2000, 0x80, transfer_overworld_.tile16_blockset_data(),
540 }
541
542 // Create a canvas for holding the tiles which will be exported
544 (8192 * 2), 0x20, transfer_blockset_loaded_, true,
545 3);
546
547 return absl::OkStatus();
548}
549
550absl::Status Tile16Editor::CopyTile16ToClipboard(int tile_id) {
551 if (tile_id < 0 || tile_id >= zelda3::kNumTile16Individual) {
552 return absl::InvalidArgumentError("Invalid tile ID");
553 }
554
555 // Create a copy of the tile16 bitmap
557 clipboard_tile16_.Create(16, 16, 8,
558 tile16_blockset_->tile_bitmaps[tile_id].vector());
559 clipboard_tile16_.SetPalette(
560 tile16_blockset_->tile_bitmaps[tile_id].palette());
562
563 clipboard_has_data_ = true;
564 return absl::OkStatus();
565}
566
568 if (!clipboard_has_data_) {
569 return absl::FailedPreconditionError("Clipboard is empty");
570 }
571
572 // Copy the clipboard data to the current tile16
573 current_tile16_bmp_.Create(16, 16, 8, clipboard_tile16_.vector());
574 current_tile16_bmp_.SetPalette(clipboard_tile16_.palette());
576
577 return absl::OkStatus();
578}
579
581 if (slot < 0 || slot >= 4) {
582 return absl::InvalidArgumentError("Invalid scratch space slot");
583 }
584
585 // Create a copy of the current tile16 bitmap
586 scratch_space_[slot].Create(16, 16, 8, current_tile16_bmp_.vector());
587 scratch_space_[slot].SetPalette(current_tile16_bmp_.palette());
589
590 scratch_space_used_[slot] = true;
591 return absl::OkStatus();
592}
593
595 if (slot < 0 || slot >= 4) {
596 return absl::InvalidArgumentError("Invalid scratch space slot");
597 }
598
599 if (!scratch_space_used_[slot]) {
600 return absl::FailedPreconditionError("Scratch space slot is empty");
601 }
602
603 // Copy the scratch space data to the current tile16
604 current_tile16_bmp_.Create(16, 16, 8, scratch_space_[slot].vector());
605 current_tile16_bmp_.SetPalette(scratch_space_[slot].palette());
607
608 return absl::OkStatus();
609}
610
611absl::Status Tile16Editor::ClearScratchSpace(int slot) {
612 if (slot < 0 || slot >= 4) {
613 return absl::InvalidArgumentError("Invalid scratch space slot");
614 }
615
616 scratch_space_used_[slot] = false;
617 return absl::OkStatus();
618}
619
620} // namespace editor
621} // namespace yaze
auto rom()
Definition rom.h:290
static std::string ShowOpenFileDialog()
ShowOpenFileDialog opens a file dialog and returns the selected filepath.
void CreateAndRenderBitmap(int width, int height, int depth, const std::vector< uint8_t > &data, gfx::Bitmap &bitmap, gfx::SnesPalette &palette)
Definition renderer.h:53
void UpdateBitmap(gfx::Bitmap *bitmap)
Definition renderer.h:49
static Renderer & Get()
Definition renderer.h:26
void RenderBitmap(gfx::Bitmap *bitmap)
Definition renderer.h:45
static Renderer & Get()
Definition renderer.h:26
absl::Status SaveTile16ToScratchSpace(int slot)
util::NotifyValue< uint32_t > notify_tile16
absl::Status UpdateTransferTileCanvas()
gfx::Tilemap * tile16_blockset_
std::array< uint8_t, 0x200 > all_tiles_types_
absl::Status PasteTile16FromClipboard()
absl::Status LoadTile16FromScratchSpace(int slot)
absl::Status UpdateTile16Transfer()
absl::Status Initialize(const gfx::Bitmap &tile16_blockset_bmp, const gfx::Bitmap &current_gfx_bmp, std::array< uint8_t, 0x200 > &all_tiles_types)
absl::Status SetCurrentTile(int id)
std::array< gfx::Bitmap, 4 > scratch_space_
absl::Status DrawToCurrentTile16(ImVec2 pos)
zelda3::Overworld transfer_overworld_
std::vector< gfx::Bitmap > current_gfx_individual_
absl::Status CopyTile16ToClipboard(int tile_id)
std::array< gfx::Bitmap, kNumGfxSheets > transfer_gfx_
absl::Status ClearScratchSpace(int slot)
std::array< bool, 4 > scratch_space_used_
util::NotifyValue< uint8_t > notify_palette
Represents a bitmap image.
Definition bitmap.h:59
const SnesPalette & palette() const
Definition bitmap.h:149
const std::vector< uint8_t > & vector() const
Definition bitmap.h:159
int height() const
Definition bitmap.h:152
int width() const
Definition bitmap.h:151
int depth() const
Definition bitmap.h:153
static std::unordered_map< uint8_t, gfx::Paletteset > palettesets_
#define TABLE_BORDERS_RESIZABLE
Definition macro.h:91
#define PRINT_IF_ERROR(expression)
Definition macro.h:25
#define RETURN_IF_ERROR(expression)
Definition macro.h:51
#define ASSIGN_OR_RETURN(type_variable_name, expression)
Definition macro.h:59
Editors are the view controllers for the application.
void RenderTile(Tilemap &tilemap, int tile_id)
Definition tilemap.cc:30
void BitmapCanvasPipeline(gui::Canvas &canvas, gfx::Bitmap &bitmap, int width, int height, int tile_size, bool is_loaded, bool scrollbar, int canvas_id)
Definition canvas.cc:909
void BeginPadding(int i)
Definition style.cc:372
void AddTableColumn(Table &table, const std::string &label, GuiElement element)
Definition input.cc:371
void DrawTable(Table &params)
Definition input.cc:377
void EndPadding()
Definition style.cc:376
bool InputHexByte(const char *label, uint8_t *data, float input_width, bool no_step)
Definition input.cc:176
void BeginChildWithScrollbar(const char *str_id)
Definition style.cc:384
std::string HexByte(uint8_t byte, HexStringParams params)
Definition hex.cc:30
constexpr int kNumTile16Individual
Definition overworld.h:97
Main namespace for the application.
Definition controller.cc:18
absl::StatusOr< std::array< gfx::Bitmap, kNumGfxSheets > > LoadAllGraphicsData(Rom &rom, bool defer_render)
This function iterates over all graphics sheets in the Rom and loads them into memory....
Definition rom.cc:135