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