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());
38 ImVector<std::string> tile16_names;
39 for (int i = 0; i < 0x200; ++i) {
40 std::string str = util::HexByte(all_tiles_types_[i]);
41 tile16_names.push_back(str);
42 }
43 *tile8_source_canvas_.mutable_labels(0) = tile16_names;
44 *tile8_source_canvas_.custom_labels_enabled() = true;
45
47 [&]() { Text("Tile16 ID: %02X", current_tile16_); });
49 [&]() { Text("Tile8 ID: %02X", current_tile8_); });
50
51 gui::AddTableColumn(tile_edit_table_, "##tile16Flip", [&]() {
52 Checkbox("X Flip", &x_flip);
53 Checkbox("Y Flip", &y_flip);
54 Checkbox("Priority", &priority_tile);
55 });
56
57 return absl::OkStatus();
58}
59
60absl::Status Tile16Editor::Update() {
62 return absl::InvalidArgumentError("Blockset not initialized, open a ROM.");
63 }
64
65 if (BeginMenuBar()) {
66 if (BeginMenu("View")) {
67 Checkbox("Show Collision Types",
68 tile8_source_canvas_.custom_labels_enabled());
69 EndMenu();
70 }
71
72 if (BeginMenu("Edit")) {
73 if (MenuItem("Copy Current Tile16")) {
75 }
76 if (MenuItem("Paste to Current Tile16")) {
78 }
79 Separator();
80 if (MenuItem("Save to Scratch Space 1")) {
82 }
83 if (MenuItem("Save to Scratch Space 2")) {
85 }
86 if (MenuItem("Save to Scratch Space 3")) {
88 }
89 if (MenuItem("Save to Scratch Space 4")) {
91 }
92 Separator();
93 if (MenuItem("Load from Scratch Space 1")) {
95 }
96 if (MenuItem("Load from Scratch Space 2")) {
98 }
99 if (MenuItem("Load from Scratch Space 3")) {
101 }
102 if (MenuItem("Load from Scratch Space 4")) {
104 }
105 EndMenu();
106 }
107
108 if (BeginMenu("Help")) {
109 if (MenuItem("About Tile16 Editor")) {
110 OpenPopup("About Tile16 Editor");
111 }
112 EndMenu();
113 }
114
115 EndMenuBar();
116 }
117
118 // About popup
119 if (BeginPopupModal("About Tile16 Editor", NULL,
120 ImGuiWindowFlags_AlwaysAutoResize)) {
121 Text("Tile16 Editor for Link to the Past");
122 Text("This editor allows you to edit 16x16 tiles used in the game.");
123 Text("Features:");
124 BulletText("Edit Tile16 graphics by placing 8x8 tiles in the quadrants");
125 BulletText("Copy and paste Tile16 graphics");
126 BulletText("Save and load Tile16 graphics to/from scratch space");
127 BulletText("Preview Tile16 graphics at a larger size");
128 Separator();
129 if (Button("Close")) {
130 CloseCurrentPopup();
131 }
132 EndPopup();
133 }
134
135 if (BeginTabBar("Tile16 Editor Tabs")) {
138 EndTabBar();
139 }
140 return absl::OkStatus();
141}
142
144 if (BeginTabItem("Tile16 Editing")) {
145 if (BeginTable("#Tile16EditorTable", 2, TABLE_BORDERS_RESIZABLE,
146 ImVec2(0, 0))) {
147 TableSetupColumn("Blockset", ImGuiTableColumnFlags_WidthFixed,
148 GetContentRegionAvail().x);
149 TableSetupColumn("Properties", ImGuiTableColumnFlags_WidthStretch,
150 GetContentRegionAvail().x);
151 TableHeadersRow();
152 TableNextRow();
153 TableNextColumn();
155
156 TableNextColumn();
158
159 EndTable();
160 }
161 EndTabItem();
162 }
163}
164
167 gui::BeginChildWithScrollbar("##Tile16EditorBlocksetScrollRegion");
168 blockset_canvas_.DrawBackground();
170 blockset_canvas_.DrawContextMenu();
171 blockset_canvas_.DrawTileSelector(32);
173 blockset_canvas_.DrawGrid();
174 blockset_canvas_.DrawOverlay();
175 EndChild();
176
177 if (!blockset_canvas_.points().empty()) {
178 notify_tile16.edit() = blockset_canvas_.GetTileIdFromMousePos();
179 notify_tile16.commit();
180
181 if (notify_tile16.modified()) {
185 auto ow_main_pal_group = rom()->palette_group().overworld_main;
186 current_tile16_bmp_.SetPalette(ow_main_pal_group[current_palette_]);
188 }
189 }
190
191 return absl::OkStatus();
192}
193
194absl::Status Tile16Editor::DrawToCurrentTile16(ImVec2 click_position) {
195 constexpr int tile8_size = 8;
196 constexpr int tile16_size = 16;
197
198 // Calculate the tile index for x and y based on the click_position
199 // Adjusting for Tile16 (16x16) which contains 4 Tile8 (8x8)
200 int tile_index_x = static_cast<int>(click_position.x) / tile8_size;
201 int tile_index_y = static_cast<int>(click_position.y) / tile8_size;
202
203 // Ensure we're within the bounds of the Tile16 (0-1 for both x and y)
204 tile_index_x = std::min(1, std::max(0, tile_index_x));
205 tile_index_y = std::min(1, std::max(0, tile_index_y));
206
207 // Calculate the pixel start position within the Tile16
208 // Each Tile8 is 8x8 pixels, so we multiply by 8 to get the pixel offset
209 int start_x = tile_index_x * tile8_size;
210 int start_y = tile_index_y * tile8_size;
211
212 // Draw the Tile8 to the correct position within the Tile16
213 for (int y = 0; y < tile8_size; ++y) {
214 for (int x = 0; x < tile8_size; ++x) {
215 // Calculate the pixel position in the Tile16 bitmap
216 int pixel_x = start_x + x;
217 int pixel_y = start_y + y;
218 int pixel_index = pixel_y * tile16_size + pixel_x;
219
220 // Calculate the pixel position in the Tile8 bitmap
221 int gfx_pixel_index = y * tile8_size + x;
222
223 // Apply flipping if needed
224 if (x_flip) {
225 gfx_pixel_index = y * tile8_size + (tile8_size - 1 - x);
226 }
227 if (y_flip) {
228 gfx_pixel_index = (tile8_size - 1 - y) * tile8_size + x;
229 }
230 if (x_flip && y_flip) {
231 gfx_pixel_index =
232 (tile8_size - 1 - y) * tile8_size + (tile8_size - 1 - x);
233 }
234
235 // Write the pixel to the Tile16 bitmap
236 current_tile16_bmp_.WriteToPixel(
237 pixel_index,
238 current_gfx_individual_[current_tile8_].data()[gfx_pixel_index]);
239 }
240 }
241
242 return absl::OkStatus();
243}
244
246 auto ow_main_pal_group = rom()->palette_group().overworld_main;
247
248 // Create a more organized layout with tabs
249 if (BeginTabBar("Tile16EditorTabs")) {
250 // Main editing tab
251 if (BeginTabItem("Edit")) {
252 // Top section: Tile8 selector and Tile16 editor side by side
253 if (BeginTable("##Tile16EditorLayout", 2, TABLE_BORDERS_RESIZABLE,
254 ImVec2(0, 0))) {
255 // Left column: Tile8 selector
256 TableSetupColumn("Tile8 Selector", ImGuiTableColumnFlags_WidthFixed,
257 GetContentRegionAvail().x * 0.6f);
258 // Right column: Tile16 editor
259 TableSetupColumn("Tile16 Editor", ImGuiTableColumnFlags_WidthStretch,
260 GetContentRegionAvail().x * 0.4f);
261
262 TableHeadersRow();
263 TableNextRow();
264
265 // Tile8 selector column
266 TableNextColumn();
267 if (BeginChild("Tile8 Selector", ImVec2(0, 0x175), true)) {
268 tile8_source_canvas_.DrawBackground();
269 tile8_source_canvas_.DrawContextMenu();
270 if (tile8_source_canvas_.DrawTileSelector(32)) {
271 current_gfx_individual_[current_tile8_].SetPaletteWithTransparent(
272 ow_main_pal_group[0], current_palette_);
275 }
276 tile8_source_canvas_.DrawBitmap(current_gfx_bmp_, 0, 0, 4.0f);
277 tile8_source_canvas_.DrawGrid();
278 tile8_source_canvas_.DrawOverlay();
279 }
280 EndChild();
281
282 // Tile16 editor column
283 TableNextColumn();
284 if (BeginChild("Tile16 Editor", ImVec2(0, 0x175), true)) {
285 tile16_edit_canvas_.DrawBackground();
286 tile16_edit_canvas_.DrawContextMenu();
287 tile16_edit_canvas_.DrawBitmap(current_tile16_bmp_, 0, 0, 4.0f);
288 if (!tile8_source_canvas_.points().empty()) {
289 if (tile16_edit_canvas_.DrawTilePainter(
292 tile16_edit_canvas_.drawn_tile_position()));
294 }
295 }
296 tile16_edit_canvas_.DrawGrid();
297 tile16_edit_canvas_.DrawOverlay();
298 }
299 EndChild();
300
301 EndTable();
302 }
303
304 // Bottom section: Options and controls
305 Separator();
306
307 // Create a table for the options
308 if (BeginTable("##Tile16EditorOptions", 2, TABLE_BORDERS_RESIZABLE,
309 ImVec2(0, 0))) {
310 // Left column: Tile properties
311 TableSetupColumn("Properties", ImGuiTableColumnFlags_WidthFixed,
312 GetContentRegionAvail().x * 0.5f);
313 // Right column: Actions
314 TableSetupColumn("Actions", ImGuiTableColumnFlags_WidthStretch,
315 GetContentRegionAvail().x * 0.5f);
316
317 TableHeadersRow();
318 TableNextRow();
319
320 // Properties column
321 TableNextColumn();
322 Text("Tile Properties:");
324
325 // Palette selector
326 Text("Palette:");
327 gui::InputHexByte("Palette", &notify_palette.edit());
328 notify_palette.commit();
329 if (notify_palette.modified()) {
330 auto palette = palettesets_[current_palette_].main_;
331 auto value = notify_palette.get();
332 if (notify_palette.get() > 0x04 && notify_palette.get() < 0x06) {
333 palette = palettesets_[current_palette_].aux1;
334 value -= 0x04;
335 } else if (notify_palette.get() > 0x06) {
336 palette = palettesets_[current_palette_].aux2;
337 value -= 0x06;
338 }
339
340 if (value > 0x00) {
341 current_gfx_bmp_.SetPaletteWithTransparent(palette, value);
343
344 current_tile16_bmp_.SetPaletteWithTransparent(palette, value);
346 }
347 }
348
349 // Actions column
350 TableNextColumn();
351 Text("Actions:");
352
353 // Clipboard actions
354 if (Button("Copy Current Tile16")) {
356 }
357 SameLine();
358 if (Button("Paste to Current Tile16")) {
360 }
361
362 // Scratch space actions
363 Separator();
364 Text("Scratch Space:");
365
366 // Create a grid of 4 buttons for scratch space
367 for (int i = 0; i < 4; i++) {
368 if (i > 0) SameLine();
369
370 if (scratch_space_used_[i]) {
371 if (Button(("Slot " + std::to_string(i)).c_str())) {
373 }
374 SameLine();
375 if (Button(("Clear##" + std::to_string(i)).c_str())) {
377 }
378 } else {
379 if (Button(("Empty##" + std::to_string(i)).c_str())) {
381 }
382 }
383 }
384
385 EndTable();
386 }
387
388 EndTabItem();
389 }
390
391 // Preview tab
392 if (BeginTabItem("Preview")) {
393 if (BeginChild("Tile16Preview", ImVec2(0, 0), true)) {
394 // Display the current Tile16 at a larger size
395 auto texture = current_tile16_bmp_.texture();
396 if (texture) {
397 ImGui::Image((ImTextureID)(intptr_t)texture, ImVec2(128, 128));
398 }
399
400 // Display information about the current Tile16
401 Text("Tile16 ID: %02X", current_tile16_);
402 Text("Current Palette: %02X", current_palette_);
403 Text("X Flip: %s", x_flip ? "Yes" : "No");
404 Text("Y Flip: %s", y_flip ? "Yes" : "No");
405 Text("Priority: %s", priority_tile ? "Yes" : "No");
406 }
407 EndChild();
408 EndTabItem();
409 }
410
411 EndTabBar();
412 }
413
414 // The user selected a tile8
415 if (!tile8_source_canvas_.points().empty()) {
416 uint16_t x = tile8_source_canvas_.points().front().x / 16;
417 uint16_t y = tile8_source_canvas_.points().front().y / 16;
418
419 current_tile8_ = x + (y * 8);
420 current_gfx_individual_[current_tile8_].SetPaletteWithTransparent(
421 ow_main_pal_group[0], current_palette_);
423 }
424
425 return absl::OkStatus();
426}
427
429 auto ow_main_pal_group = rom()->palette_group().overworld_main;
430 current_gfx_individual_.reserve(1024);
431
432 std::vector<std::future<std::array<uint8_t, 0x40>>> futures;
433
434 for (int index = 0; index < 1024; index++) {
435 auto task_function = [&]() {
436 std::array<uint8_t, 0x40> tile_data;
437 // Copy the pixel data for the current tile into the vector
438 for (int ty = 0; ty < 8; ty++) {
439 for (int tx = 0; tx < 8; tx++) {
440 // Gfx is 16 sheets of 8x8 tiles ordered 16 wide by 4 tall
441 // Calculate the position in the tile data vector
442 int position = tx + (ty * 0x08);
443
444 // Calculate the position in the current gfx data
445 int num_columns = current_gfx_bmp_.width() / 8;
446 int x = (index % num_columns) * 8 + tx;
447 int y = (index / num_columns) * 8 + ty;
448 int gfx_position = x + (y * 0x100);
449
450 // Get the pixel value from the current gfx data
451 uint8_t value = current_gfx_bmp_.data()[gfx_position];
452
453 if (value & 0x80) {
454 value -= 0x88;
455 }
456
457 tile_data[position] = value;
458 }
459 }
460 return tile_data;
461 };
462 futures.emplace_back(std::async(std::launch::async, task_function));
463 }
464
465 for (auto &future : futures) {
466 future.wait();
467 auto tile_data = future.get();
468 current_gfx_individual_.emplace_back();
469 auto &tile_bitmap = current_gfx_individual_.back();
470 tile_bitmap.Create(0x08, 0x08, 0x08, tile_data);
471 tile_bitmap.SetPaletteWithTransparent(ow_main_pal_group[0],
473 Renderer::Get().RenderBitmap(&tile_bitmap);
474 }
475
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
491#pragma mark - Tile16Transfer
492
494 if (BeginTabItem("Tile16 Transfer")) {
495 if (BeginTable("#Tile16TransferTable", 2, TABLE_BORDERS_RESIZABLE,
496 ImVec2(0, 0))) {
497 TableSetupColumn("Current ROM Tiles", ImGuiTableColumnFlags_WidthFixed,
498 GetContentRegionAvail().x / 2);
499 TableSetupColumn("Transfer ROM Tiles", ImGuiTableColumnFlags_WidthFixed,
500 GetContentRegionAvail().x / 2);
501 TableHeadersRow();
502 TableNextRow();
503
504 TableNextColumn();
506
507 TableNextColumn();
509
510 EndTable();
511 }
512
513 EndTabItem();
514 }
515 return absl::OkStatus();
516}
517
519 // Create a button for loading another ROM
520 if (Button("Load ROM")) {
521 auto transfer_rom = std::make_unique<Rom>();
522 transfer_rom_ = transfer_rom.get();
524 transfer_status_ = transfer_rom_->LoadFromFile(file_name);
525 transfer_started_ = true;
526 }
527
528 // TODO: Implement tile16 transfer
531
532 // Load the Link to the Past overworld.
534 transfer_overworld_.set_current_map(0);
535 palette_ = transfer_overworld_.current_area_palette();
536
537 // Create the tile16 blockset image
539 0x80, 0x2000, 0x80, transfer_overworld_.tile16_blockset_data(),
542 }
543
544 // Create a canvas for holding the tiles which will be exported
546 (8192 * 2), 0x20, transfer_blockset_loaded_, true,
547 3);
548
549 return absl::OkStatus();
550}
551
552absl::Status Tile16Editor::CopyTile16ToClipboard(int tile_id) {
553 if (tile_id < 0 || tile_id >= zelda3::kNumTile16Individual) {
554 return absl::InvalidArgumentError("Invalid tile ID");
555 }
556
557 // Create a copy of the tile16 bitmap
559 clipboard_tile16_.Create(16, 16, 8,
560 tile16_blockset_->tile_bitmaps[tile_id].vector());
561 clipboard_tile16_.SetPalette(
562 tile16_blockset_->tile_bitmaps[tile_id].palette());
564
565 clipboard_has_data_ = true;
566 return absl::OkStatus();
567}
568
570 if (!clipboard_has_data_) {
571 return absl::FailedPreconditionError("Clipboard is empty");
572 }
573
574 // Copy the clipboard data to the current tile16
575 current_tile16_bmp_.Create(16, 16, 8, clipboard_tile16_.vector());
576 current_tile16_bmp_.SetPalette(clipboard_tile16_.palette());
578
579 return absl::OkStatus();
580}
581
583 if (slot < 0 || slot >= 4) {
584 return absl::InvalidArgumentError("Invalid scratch space slot");
585 }
586
587 // Create a copy of the current tile16 bitmap
588 scratch_space_[slot].Create(16, 16, 8, current_tile16_bmp_.vector());
589 scratch_space_[slot].SetPalette(current_tile16_bmp_.palette());
591
592 scratch_space_used_[slot] = true;
593 return absl::OkStatus();
594}
595
597 if (slot < 0 || slot >= 4) {
598 return absl::InvalidArgumentError("Invalid scratch space slot");
599 }
600
601 if (!scratch_space_used_[slot]) {
602 return absl::FailedPreconditionError("Scratch space slot is empty");
603 }
604
605 // Copy the scratch space data to the current tile16
606 current_tile16_bmp_.Create(16, 16, 8, scratch_space_[slot].vector());
607 current_tile16_bmp_.SetPalette(scratch_space_[slot].palette());
609
610 return absl::OkStatus();
611}
612
613absl::Status Tile16Editor::ClearScratchSpace(int slot) {
614 if (slot < 0 || slot >= 4) {
615 return absl::InvalidArgumentError("Invalid scratch space slot");
616 }
617
618 scratch_space_used_[slot] = false;
619 return absl::OkStatus();
620}
621
622} // namespace editor
623} // 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