diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 9150273b..ce6df54a 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -86,6 +86,7 @@ if(APPLE) app/core/platform/file_dialog.mm app/core/platform/app_delegate.mm app/core/platform/font_loader.mm + app/core/platform/clipboard.mm ) find_library(COCOA_LIBRARY Cocoa) diff --git a/src/app/core/platform/clipboard.h b/src/app/core/platform/clipboard.h new file mode 100644 index 00000000..9f8ae347 --- /dev/null +++ b/src/app/core/platform/clipboard.h @@ -0,0 +1,10 @@ +#ifdef _WIN32 + +#elif defined(__APPLE__) + +#include + +void CopyImageToClipboard(const std::vector& data); +void GetImageFromClipboard(std::vector& data, int& width, int& height); + +#endif \ No newline at end of file diff --git a/src/app/core/platform/clipboard.mm b/src/app/core/platform/clipboard.mm new file mode 100644 index 00000000..b3dc6f9c --- /dev/null +++ b/src/app/core/platform/clipboard.mm @@ -0,0 +1,42 @@ +#include "clipboard.h" + +#include +#include + +#import + +void CopyImageToClipboard(const std::vector& pngData) { + NSData* data = [NSData dataWithBytes:pngData.data() length:pngData.size()]; + NSImage* image = [[NSImage alloc] initWithData:data]; + + NSPasteboard* pasteboard = [NSPasteboard generalPasteboard]; + [pasteboard clearContents]; + [pasteboard writeObjects:@[ image ]]; +} + +void GetImageFromClipboard(std::vector& pixel_data, int& width, int& height) { + NSPasteboard* pasteboard = [NSPasteboard generalPasteboard]; + NSArray* classArray = [NSArray arrayWithObject:[NSImage class]]; + NSDictionary* options = [NSDictionary dictionary]; + + NSImage* image = [pasteboard readObjectsForClasses:classArray options:options].firstObject; + if (!image) { + width = height = 0; + return; + } + + // Assuming the image is in an RGBA format + CGImageRef cgImage = [image CGImageForProposedRect:nil context:nil hints:nil]; + width = (int)CGImageGetWidth(cgImage); + height = (int)CGImageGetHeight(cgImage); + + size_t bytesPerRow = 4 * width; + size_t totalBytes = bytesPerRow * height; + pixel_data.resize(totalBytes); + + CGContextRef context = CGBitmapContextCreate( + pixel_data.data(), width, height, 8, bytesPerRow, CGColorSpaceCreateDeviceRGB(), + kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big); + CGContextDrawImage(context, CGRectMake(0, 0, width, height), cgImage); + CGContextRelease(context); +} \ No newline at end of file diff --git a/src/app/editor/graphics_editor.cc b/src/app/editor/graphics_editor.cc index 05cf9c87..95f1037d 100644 --- a/src/app/editor/graphics_editor.cc +++ b/src/app/editor/graphics_editor.cc @@ -7,6 +7,7 @@ #include "absl/status/status.h" #include "absl/status/statusor.h" +#include "app/core/platform/clipboard.h" #include "app/editor/modules/palette_editor.h" #include "app/gfx/bitmap.h" #include "app/gfx/compression.h" @@ -28,6 +29,16 @@ using ImGui::InputInt; using ImGui::InputText; using ImGui::SameLine; +constexpr ImGuiTableFlags kGfxEditTableFlags = + ImGuiTableFlags_Borders | ImGuiTableFlags_Resizable | + ImGuiTableFlags_Reorderable | ImGuiTableFlags_Hideable | + ImGuiTableFlags_SizingFixedFit; + +constexpr ImGuiTabBarFlags kGfxEditTabBarFlags = + ImGuiTabBarFlags_AutoSelectNewTabs | ImGuiTabBarFlags_Reorderable | + ImGuiTabBarFlags_FittingPolicyResizeDown | + ImGuiTabBarFlags_TabListPopupButton; + absl::Status GraphicsEditor::Update() { TAB_BAR("##TabBar") status_ = UpdateGfxEdit(); @@ -41,11 +52,7 @@ absl::Status GraphicsEditor::Update() { absl::Status GraphicsEditor::UpdateGfxEdit() { TAB_ITEM("Graphics Editor") - if (ImGui::BeginTable("##GfxEditTable", 3, - ImGuiTableFlags_Borders | ImGuiTableFlags_Resizable | - ImGuiTableFlags_Reorderable | - ImGuiTableFlags_Hideable | - ImGuiTableFlags_SizingFixedFit, + if (ImGui::BeginTable("##GfxEditTable", 3, kGfxEditTableFlags, ImVec2(0, 0))) { for (const auto& name : kGfxEditColumnNames) ImGui::TableSetupColumn(name.data()); @@ -73,33 +80,62 @@ absl::Status GraphicsEditor::UpdateGfxEdit() { } void GraphicsEditor::DrawGfxEditToolset() { - if (ImGui::BeginTable("##GfxEditToolset", 7, ImGuiTableFlags_SizingFixedFit, + if (ImGui::BeginTable("##GfxEditToolset", 9, ImGuiTableFlags_SizingFixedFit, ImVec2(0, 0))) { - for (const auto& name : {"Select", "Pencil", "Fill", "Zoom Out", "Zoom In", - "Current Color", "Tile Size"}) + for (const auto& name : + {"Select", "Pencil", "Fill", "Copy Sheet", "Paste Sheet", "Zoom Out", + "Zoom In", "Current Color", "Tile Size"}) ImGui::TableSetupColumn(name); ImGui::TableNextColumn(); - if (ImGui::Button(ICON_MD_SELECT_ALL)) { + if (Button(ICON_MD_SELECT_ALL)) { + gfx_edit_mode_ = GfxEditMode::kSelect; } ImGui::TableNextColumn(); - if (ImGui::Button(ICON_MD_DRAW)) { + if (Button(ICON_MD_DRAW)) { + gfx_edit_mode_ = GfxEditMode::kPencil; } ImGui::TableNextColumn(); - if (ImGui::Button(ICON_MD_FORMAT_COLOR_FILL)) { + if (Button(ICON_MD_FORMAT_COLOR_FILL)) { + gfx_edit_mode_ = GfxEditMode::kFill; } ImGui::TableNextColumn(); - if (ImGui::Button(ICON_MD_ZOOM_OUT)) { + if (Button(ICON_MD_CONTENT_COPY)) { + std::vector png_data = + rom()->bitmap_manager().GetBitmap(current_sheet_)->GetPngData(); + CopyImageToClipboard(png_data); + } + + ImGui::TableNextColumn(); + if (Button(ICON_MD_CONTENT_PASTE)) { + std::vector png_data; + int width, height; + GetImageFromClipboard(png_data, width, height); + if (png_data.size() > 0) { + rom() + ->bitmap_manager() + .GetBitmap(current_sheet_) + ->LoadFromPngData(png_data, width, height); + rom()->UpdateBitmap(rom() + ->mutable_bitmap_manager() + ->mutable_bitmap(current_sheet_) + .get()); + } + } + HOVER_HINT("Paste from Clipboard"); + + ImGui::TableNextColumn(); + if (Button(ICON_MD_ZOOM_OUT)) { if (current_scale_ >= 0.0f) { current_scale_ -= 1.0f; } } ImGui::TableNextColumn(); - if (ImGui::Button(ICON_MD_ZOOM_IN)) { + if (Button(ICON_MD_ZOOM_IN)) { if (current_scale_ <= 16.0f) { current_scale_ += 1.0f; } @@ -129,38 +165,42 @@ absl::Status GraphicsEditor::UpdateGfxSheetList() { ImGuiWindowFlags_NoDecoration); ImGui::PopStyleVar(); gui::Canvas graphics_bin_canvas_; - graphics_bin_canvas_.UpdateEvent( - [&]() { - if (value.get()->IsActive()) { - auto texture = value.get()->texture(); - graphics_bin_canvas_.GetDrawList()->AddImage( - (void*)texture, - ImVec2(graphics_bin_canvas_.GetZeroPoint().x + 2, - graphics_bin_canvas_.GetZeroPoint().y + 2), - ImVec2(graphics_bin_canvas_.GetZeroPoint().x + - value.get()->width() * sheet_scale_, - graphics_bin_canvas_.GetZeroPoint().y + - value.get()->height() * sheet_scale_)); - if (ImGui::IsItemClicked(ImGuiMouseButton_Left)) { - current_sheet_ = key; - open_sheets_.insert(key); - } + auto select_tile_event = [&]() { + if (value.get()->IsActive()) { + auto texture = value.get()->texture(); + graphics_bin_canvas_.GetDrawList()->AddImage( + (void*)texture, + ImVec2(graphics_bin_canvas_.GetZeroPoint().x + 2, + graphics_bin_canvas_.GetZeroPoint().y + 2), + ImVec2(graphics_bin_canvas_.GetZeroPoint().x + + value.get()->width() * sheet_scale_, + graphics_bin_canvas_.GetZeroPoint().y + + value.get()->height() * sheet_scale_)); - // Add a slightly transparent rectangle behind the text - ImVec2 textPos(graphics_bin_canvas_.GetZeroPoint().x + 2, - graphics_bin_canvas_.GetZeroPoint().y + 2); - ImVec2 textSize = - ImGui::CalcTextSize(absl::StrFormat("%02X", key).c_str()); - ImVec2 rectMin(textPos.x, textPos.y); - ImVec2 rectMax(textPos.x + textSize.x, textPos.y + textSize.y); - graphics_bin_canvas_.GetDrawList()->AddRectFilled( - rectMin, rectMax, IM_COL32(0, 125, 0, 128)); - graphics_bin_canvas_.GetDrawList()->AddText( - textPos, IM_COL32(125, 255, 125, 255), - absl::StrFormat("%02X", key).c_str()); - } - }, - ImVec2(0x100 + 1, 0x40 + 1), 0x20, sheet_scale_, + if (ImGui::IsItemClicked(ImGuiMouseButton_Left)) { + current_sheet_ = key; + open_sheets_.insert(key); + } + + // Add a slightly transparent rectangle behind the text + ImVec2 text_pos(graphics_bin_canvas_.GetZeroPoint().x + 2, + graphics_bin_canvas_.GetZeroPoint().y + 2); + ImVec2 text_size = + ImGui::CalcTextSize(absl::StrFormat("%02X", key).c_str()); + ImVec2 rent_min(text_pos.x, text_pos.y); + ImVec2 rent_max(text_pos.x + text_size.x, text_pos.y + text_size.y); + + graphics_bin_canvas_.GetDrawList()->AddRectFilled( + rent_min, rent_max, IM_COL32(0, 125, 0, 128)); + + graphics_bin_canvas_.GetDrawList()->AddText( + text_pos, IM_COL32(125, 255, 125, 255), + absl::StrFormat("%02X", key).c_str()); + } + }; + + graphics_bin_canvas_.UpdateEvent( + select_tile_event, ImVec2(0x100 + 1, 0x40 + 1), 0x20, sheet_scale_, /*grid_size=*/16.0f); ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0)); ImGui::EndChild(); @@ -173,11 +213,7 @@ absl::Status GraphicsEditor::UpdateGfxSheetList() { absl::Status GraphicsEditor::UpdateGfxTabView() { static int next_tab_id = 0; - if (ImGui::BeginTabBar("##GfxEditTabBar", - ImGuiTabBarFlags_AutoSelectNewTabs | - ImGuiTabBarFlags_Reorderable | - ImGuiTabBarFlags_FittingPolicyResizeDown | - ImGuiTabBarFlags_TabListPopupButton)) { + if (ImGui::BeginTabBar("##GfxEditTabBar", kGfxEditTabBarFlags)) { if (ImGui::TabItemButton( "+", ImGuiTabItemFlags_Trailing | ImGuiTabItemFlags_NoTooltip)) { open_sheets_.insert(next_tab_id++); @@ -185,6 +221,8 @@ absl::Status GraphicsEditor::UpdateGfxTabView() { // Submit our regular tabs for (auto& each : open_sheets_) { + current_sheet_ = each; + bool open = true; if (ImGui::BeginTabItem(absl::StrFormat("%d", each).c_str(), &open, ImGuiTabItemFlags_None)) { @@ -197,59 +235,61 @@ absl::Status GraphicsEditor::UpdateGfxTabView() { child_window_sheets_.insert(each); } } - ImGui::BeginChild( - absl::StrFormat("##GfxEditPaletteChild%d", each).c_str(), - ImVec2(0, 0), true, - ImGuiWindowFlags_NoDecoration | - ImGuiWindowFlags_AlwaysVerticalScrollbar | - ImGuiWindowFlags_AlwaysHorizontalScrollbar); - current_sheet_ = each; + + const auto child_id = + absl::StrFormat("##GfxEditPaletteChildWindow%d", each); + ImGui::BeginChild(child_id.c_str(), ImVec2(0, 0), true, + ImGuiWindowFlags_NoDecoration | + ImGuiWindowFlags_AlwaysVerticalScrollbar | + ImGuiWindowFlags_AlwaysHorizontalScrollbar); + + auto draw_tile_event = [&]() { + // Convert the ImVec4 into a 16-bit value + Uint8 r = static_cast(current_color_.x * 31); + Uint8 g = static_cast(current_color_.y * 31); + Uint8 b = static_cast(current_color_.z * 31); + Uint16 snes_color = + ((r & 0x1F) << 10) | ((g & 0x1F) << 5) | (b & 0x1F); + + auto click_position = + current_sheet_canvas_.GetCurrentDrawnTilePosition(); + + // Calculate the tile index for x and y based on the + // click_position + int tile_index_x = + (static_cast(click_position.x) % 8) / tile_size_; + int tile_index_y = + (static_cast(click_position.y) % 8) / tile_size_; + + // Calculate the pixel start position based on tile index and tile + // size + ImVec2 start_position; + start_position.x = tile_index_x * tile_size_; + start_position.y = tile_index_y * tile_size_; + + // Get the current map's bitmap from the BitmapTable + gfx::Bitmap& current_bitmap = *rom()->bitmap_manager()[each]; + + // Update the bitmap's pixel data based on the start_position and + // tile_data + for (int y = 0; y < tile_size_; ++y) { + for (int x = 0; x < tile_size_; ++x) { + int pixel_index = + (start_position.y + y) * current_bitmap.width() + + (start_position.x + x); + current_bitmap.WriteToPixel(pixel_index, snes_color); + } + } + + // rom()->bitmap_manager()[each]->WriteToPixel(position, + // snesColor); + + rom()->UpdateBitmap( + rom()->mutable_bitmap_manager()->mutable_bitmap(each).get()); + }; + current_sheet_canvas_.UpdateColorPainter( - *rom()->bitmap_manager()[each], current_color_, - [&]() { - // Convert the ImVec4 into a 16-bit value - Uint8 r = static_cast(current_color_.x * 31); - Uint8 g = static_cast(current_color_.y * 31); - Uint8 b = static_cast(current_color_.z * 31); - Uint16 snesColor = - ((r & 0x1F) << 10) | ((g & 0x1F) << 5) | (b & 0x1F); - - auto click_position = - current_sheet_canvas_.GetCurrentDrawnTilePosition(); - - // Calculate the tile index for x and y based on the - // click_position - int tile_index_x = - (static_cast(click_position.x) % 8) / tile_size_; - int tile_index_y = - (static_cast(click_position.y) % 8) / tile_size_; - - // Calculate the pixel start position based on tile index and tile - // size - ImVec2 start_position; - start_position.x = tile_index_x * tile_size_; - start_position.y = tile_index_y * tile_size_; - - // Get the current map's bitmap from the BitmapTable - gfx::Bitmap& current_bitmap = *rom()->bitmap_manager()[each]; - - // Update the bitmap's pixel data based on the start_position and - // tile_data - for (int y = 0; y < tile_size_; ++y) { - for (int x = 0; x < tile_size_; ++x) { - int pixel_index = - (start_position.y + y) * current_bitmap.width() + - (start_position.x + x); - current_bitmap.WriteToPixel(pixel_index, snesColor); - } - } - - // rom()->bitmap_manager()[each]->WriteToPixel(position, - // snesColor); - - rom()->UpdateBitmap( - rom()->mutable_bitmap_manager()->mutable_bitmap(each).get()); - }, + *rom()->bitmap_manager()[each], current_color_, draw_tile_event, ImVec2(0x100, 0x40), tile_size_, current_scale_, 8.0f); ImGui::EndChild(); ImGui::EndTabItem(); @@ -297,7 +337,7 @@ absl::Status GraphicsEditor::UpdateGfxTabView() { } absl::Status GraphicsEditor::UpdatePaletteColumn() { - auto palette_group = rom()->GetPaletteGroup( + auto palette_group = rom()->palette_group( kPaletteGroupAddressesKeys[edit_palette_group_name_index_]); auto palette = palette_group[edit_palette_index_]; @@ -678,7 +718,7 @@ absl::Status GraphicsEditor::DecompressImportData(int size) { converted_sheet); if (rom()->isLoaded()) { - auto palette_group = rom()->GetPaletteGroup("ow_main"); + auto palette_group = rom()->palette_group("ow_main"); z3_rom_palette_ = palette_group[current_palette_]; if (col_file_) { bin_bitmap_.ApplyPalette(col_file_palette_); @@ -711,7 +751,7 @@ absl::Status GraphicsEditor::DecompressSuperDonkey() { } else { // ROM palette auto palette_group = - rom()->GetPaletteGroup(kPaletteGroupAddressesKeys[current_palette_]); + rom()->palette_group(kPaletteGroupAddressesKeys[current_palette_]); z3_rom_palette_ = palette_group[current_palette_index_]; graphics_bin_[i].ApplyPalette(z3_rom_palette_); } @@ -736,7 +776,7 @@ absl::Status GraphicsEditor::DecompressSuperDonkey() { } else { // ROM palette auto palette_group = - rom()->GetPaletteGroup(kPaletteGroupAddressesKeys[current_palette_]); + rom()->palette_group(kPaletteGroupAddressesKeys[current_palette_]); z3_rom_palette_ = palette_group[current_palette_index_]; graphics_bin_[i].ApplyPalette(z3_rom_palette_); } diff --git a/src/app/editor/graphics_editor.h b/src/app/editor/graphics_editor.h index 22ce4caf..2dbf9719 100644 --- a/src/app/editor/graphics_editor.h +++ b/src/app/editor/graphics_editor.h @@ -67,12 +67,18 @@ class GraphicsEditor : public SharedROM { absl::Status Update(); private: + enum class GfxEditMode { + kSelect, + kPencil, + kFill, + }; + // Graphics Editor Tab absl::Status UpdateGfxEdit(); - void DrawGfxEditToolset(); absl::Status UpdateGfxSheetList(); absl::Status UpdateGfxTabView(); absl::Status UpdatePaletteColumn(); + void DrawGfxEditToolset(); // Link Graphics Edit Tab absl::Status UpdateLinkGfxView(); @@ -80,23 +86,24 @@ class GraphicsEditor : public SharedROM { // Prototype Graphics Viewer absl::Status UpdateScadView(); - absl::Status DrawToolset(); - + // Import Functions absl::Status DrawCgxImport(); absl::Status DrawScrImport(); absl::Status DrawFileImport(); absl::Status DrawObjImport(); absl::Status DrawTilemapImport(); + // Other Functions + absl::Status DrawToolset(); absl::Status DrawPaletteControls(); absl::Status DrawClipboardImport(); absl::Status DrawExperimentalFeatures(); absl::Status DrawMemoryEditor(); absl::Status DecompressImportData(int size); - absl::Status DecompressSuperDonkey(); + // Member Variables ImVec4 current_color_; uint16_t current_sheet_ = 0; uint8_t tile_size_ = 0x08; @@ -115,92 +122,67 @@ class GraphicsEditor : public SharedROM { uint64_t current_offset_ = 0; uint64_t current_size_ = 0; uint64_t current_palette_index_ = 0; - int current_bpp_ = 0; - int scr_mod_value_ = 0; uint64_t num_sheets_to_load_ = 1; - uint64_t bin_size_ = 0; - uint64_t clipboard_offset_ = 0; uint64_t clipboard_size_ = 0; bool refresh_graphics_ = false; bool open_memory_editor_ = false; - bool gfx_loaded_ = false; bool is_open_ = false; bool super_donkey_ = false; - bool col_file_ = false; bool cgx_loaded_ = false; bool scr_loaded_ = false; bool obj_loaded_ = false; - bool tilemap_loaded_ = false; char file_path_[256] = ""; char col_file_path_[256] = ""; char col_file_name_[256] = ""; - char cgx_file_path_[256] = ""; char cgx_file_name_[256] = ""; - char scr_file_path_[256] = ""; char scr_file_name_[256] = ""; - char obj_file_path_[256] = ""; - char tilemap_file_path_[256] = ""; char tilemap_file_name_[256] = ""; + GfxEditMode gfx_edit_mode_ = GfxEditMode::kSelect; + ROM temp_rom_; ROM tilemap_rom_; - zelda3::Overworld overworld_; - MemoryEditor cgx_memory_editor_; MemoryEditor col_memory_editor_; - PaletteEditor palette_editor_; - Bytes import_data_; Bytes graphics_buffer_; - std::vector decoded_cgx_; std::vector cgx_data_; std::vector extra_cgx_data_; std::vector decoded_col_; - std::vector scr_data_; std::vector decoded_scr_data_; - gfx::Bitmap cgx_bitmap_; gfx::Bitmap scr_bitmap_; gfx::Bitmap bin_bitmap_; - gfx::Bitmap link_full_sheet_; - gfx::BitmapTable graphics_bin_; gfx::BitmapTable clipboard_graphics_bin_; - gfx::BitmapTable link_graphics_; - gfx::PaletteGroup col_file_palette_group_; - gfx::SNESPalette z3_rom_palette_; gfx::SNESPalette col_file_palette_; - gfx::SNESPalette link_palette_; - gui::Canvas import_canvas_; gui::Canvas scr_canvas_; gui::Canvas super_donkey_canvas_; gui::Canvas current_sheet_canvas_; - // gui::Canvas graphics_bin_canvas_; - absl::Status status_; }; diff --git a/src/app/gfx/bitmap.cc b/src/app/gfx/bitmap.cc index c254817e..c6168aff 100644 --- a/src/app/gfx/bitmap.cc +++ b/src/app/gfx/bitmap.cc @@ -1,6 +1,7 @@ #include "bitmap.h" #include +#include #include #include @@ -23,6 +24,174 @@ void GrayscalePalette(SDL_Palette *palette) { palette->colors[i].b = i * 31; } } + +void PngWriteCallback(png_structp png_ptr, png_bytep data, png_size_t length) { + std::vector *p = (std::vector *)png_get_io_ptr(png_ptr); + p->insert(p->end(), data, data + length); +} + +bool ConvertSurfaceToPNG(SDL_Surface *surface, std::vector &buffer) { + png_structp png_ptr = png_create_write_struct("1.6.40", NULL, NULL, NULL); + if (!png_ptr) { + SDL_Log("Failed to create PNG write struct"); + return false; + } + + png_infop info_ptr = png_create_info_struct(png_ptr); + if (!info_ptr) { + png_destroy_write_struct(&png_ptr, (png_infopp)NULL); + SDL_Log("Failed to create PNG info struct"); + return false; + } + + if (setjmp(png_jmpbuf(png_ptr))) { + png_destroy_write_struct(&png_ptr, &info_ptr); + SDL_Log("Error during PNG write"); + return false; + } + + png_set_write_fn(png_ptr, &buffer, PngWriteCallback, NULL); + + png_colorp pal_ptr; + + /* Prepare chunks */ + int colortype = PNG_COLOR_MASK_COLOR; + int i = 0; + SDL_Palette *pal; + if (surface->format->BytesPerPixel > 0 && + surface->format->BytesPerPixel <= 8 && (pal = surface->format->palette)) { + SDL_Log("Writing PNG image with palette"); + colortype |= PNG_COLOR_MASK_PALETTE; + pal_ptr = (png_colorp)malloc(pal->ncolors * sizeof(png_color)); + for (i = 0; i < pal->ncolors; i++) { + pal_ptr[i].red = pal->colors[i].r; + pal_ptr[i].green = pal->colors[i].g; + pal_ptr[i].blue = pal->colors[i].b; + } + png_set_PLTE(png_ptr, info_ptr, pal_ptr, pal->ncolors); + free(pal_ptr); + } + + auto depth = surface->format->BitsPerPixel; + + // Set image attributes. + png_set_IHDR(png_ptr, info_ptr, surface->w, surface->h, depth, colortype, + PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, + PNG_FILTER_TYPE_DEFAULT); + + png_set_bgr(png_ptr); + + // Write the image data. + std::vector row_pointers(surface->h); + for (int y = 0; y < surface->h; ++y) { + row_pointers[y] = (png_bytep)(surface->pixels) + y * surface->pitch; + } + + png_set_rows(png_ptr, info_ptr, row_pointers.data()); + + SDL_Log("Writing PNG image..."); + png_write_png(png_ptr, info_ptr, PNG_TRANSFORM_IDENTITY, NULL); + SDL_Log("PNG image write complete"); + + png_destroy_write_struct(&png_ptr, &info_ptr); + + return true; +} + +void PngReadCallback(png_structp png_ptr, png_bytep outBytes, + png_size_t byteCountToRead) { + png_voidp io_ptr = png_get_io_ptr(png_ptr); + if (!io_ptr) return; + + std::vector *png_data = + reinterpret_cast *>(io_ptr); + size_t pos = png_data->size() - byteCountToRead; + memcpy(outBytes, png_data->data() + pos, byteCountToRead); + png_data->resize(pos); // Reduce the buffer size +} + +void ConvertPngToSurface(const std::vector &png_data, + SDL_Surface **outSurface) { + std::vector data(png_data); + png_structp png_ptr = png_create_read_struct("1.6.40", NULL, NULL, NULL); + if (!png_ptr) { + throw std::runtime_error("Failed to create PNG read struct"); + } + + png_infop info_ptr = png_create_info_struct(png_ptr); + if (!info_ptr) { + png_destroy_read_struct(&png_ptr, NULL, NULL); + throw std::runtime_error("Failed to create PNG info struct"); + } + + if (setjmp(png_jmpbuf(png_ptr))) { + png_destroy_read_struct(&png_ptr, &info_ptr, NULL); + throw std::runtime_error("Error during PNG read"); + } + + // Set our custom read function + png_set_read_fn(png_ptr, &data, PngReadCallback); + + // Read the PNG info + png_read_info(png_ptr, info_ptr); + + uint32_t width = png_get_image_width(png_ptr, info_ptr); + uint32_t height = png_get_image_height(png_ptr, info_ptr); + png_byte color_type = png_get_color_type(png_ptr, info_ptr); + png_byte bit_depth = png_get_bit_depth(png_ptr, info_ptr); + + // Set up transformations, e.g., strip 16-bit PNGs down to 8-bit, expand + // palettes, etc. + if (bit_depth == 16) { + png_set_strip_16(png_ptr); + } + + if (color_type == PNG_COLOR_TYPE_PALETTE) { + png_set_palette_to_rgb(png_ptr); + } + + // PNG files pack pixels, expand them + if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8) { + png_set_expand_gray_1_2_4_to_8(png_ptr); + } + + if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) { + png_set_tRNS_to_alpha(png_ptr); + } + + if (color_type == PNG_COLOR_TYPE_RGB || color_type == PNG_COLOR_TYPE_GRAY || + color_type == PNG_COLOR_TYPE_PALETTE) { + png_set_filler(png_ptr, 0xFF, PNG_FILLER_AFTER); + } + + if (color_type == PNG_COLOR_TYPE_GRAY || + color_type == PNG_COLOR_TYPE_GRAY_ALPHA) { + png_set_gray_to_rgb(png_ptr); + } + + // Update info structure with transformations + png_read_update_info(png_ptr, info_ptr); + + // Read the file + std::vector row_pointers(height); + std::vector raw_data(width * height * + 4); // Assuming 4 bytes per pixel (RGBA) + for (size_t y = 0; y < height; y++) { + row_pointers[y] = &raw_data[y * width * 4]; + } + + png_read_image(png_ptr, row_pointers.data()); + png_destroy_read_struct(&png_ptr, &info_ptr, NULL); + + // Create SDL_Surface from raw pixel data + *outSurface = SDL_CreateRGBSurfaceWithFormatFrom( + raw_data.data(), width, height, 32, width * 4, SDL_PIXELFORMAT_RGBA32); + if (*outSurface == nullptr) { + SDL_Log("SDL_CreateRGBSurfaceWithFormatFrom failed: %s\n", SDL_GetError()); + } else { + SDL_Log("Successfully created SDL_Surface from PNG data"); + } +} } // namespace Bitmap::Bitmap(int width, int height, int depth, int data_size) { @@ -143,6 +312,20 @@ void Bitmap::SetSurface(SDL_Surface *surface) { surface, SDL_Surface_Deleter()); } +std::vector Bitmap::GetPngData() { + ConvertSurfaceToPNG(surface_.get(), png_data_); + return png_data_; +} + +void Bitmap::LoadFromPngData(const std::vector &png_data, int width, + int height) { + width_ = width; + height_ = height; + SDL_Surface *surface = surface_.get(); + ConvertPngToSurface(png_data, &surface); + surface_.reset(surface); +} + // Convert SNESPalette to SDL_Palette for surface. void Bitmap::ApplyPalette(const SNESPalette &palette) { palette_ = palette; @@ -215,15 +398,6 @@ void Bitmap::InitializeFromData(uint32_t width, uint32_t height, uint32_t depth, GrayscalePalette(surface_->format->palette); } -void Bitmap::ReserveData(uint32_t width, uint32_t height, uint32_t depth, - uint32_t size) { - width_ = width; - height_ = height; - depth_ = depth; - data_.reserve(size); - pixel_data_ = data_.data(); -} - } // namespace gfx } // namespace app } // namespace yaze diff --git a/src/app/gfx/bitmap.h b/src/app/gfx/bitmap.h index 139c4a3c..21348a1b 100644 --- a/src/app/gfx/bitmap.h +++ b/src/app/gfx/bitmap.h @@ -27,24 +27,11 @@ class Bitmap { InitializeFromData(width, height, depth, data); } - // Function to create texture from pixel data - void CreateTextureFromData() { - active_ = true; - surface_ = std::shared_ptr( - SDL_CreateRGBSurfaceWithFormat(0, width_, height_, depth_, - SDL_PIXELFORMAT_INDEX8), - SDL_Surface_Deleter()); - surface_->pixels = data_.data(); - data_size_ = data_.size(); - } - void Create(int width, int height, int depth, int data_size); void Create(int width, int height, int depth, const Bytes &data); void InitializeFromData(uint32_t width, uint32_t height, uint32_t depth, const Bytes &data); - void ReserveData(uint32_t width, uint32_t height, uint32_t depth, - uint32_t size); void CreateTexture(std::shared_ptr renderer); void UpdateTexture(std::shared_ptr renderer); @@ -53,6 +40,9 @@ class Bitmap { void SaveSurfaceToFile(std::string_view filename); void SetSurface(SDL_Surface *surface); + std::vector GetPngData(); + void LoadFromPngData(const std::vector &png_data, int width, + int height); void ApplyPalette(const SNESPalette &palette); void ApplyPaletteWithTransparent(const SNESPalette &palette, int index); @@ -75,6 +65,22 @@ class Bitmap { modified_ = true; } + void Get8x8Tile(int tile_index, int x, int y, std::vector &tile_data, + int &tile_data_offset) { + int tile_offset = tile_index * 64; + int tile_x = x * 8; + int tile_y = y * 8; + for (int i = 0; i < 8; i++) { + int row_offset = tile_offset + (i * 8); + for (int j = 0; j < 8; j++) { + int pixel_offset = row_offset + j; + int pixel_value = data_[pixel_offset]; + tile_data[tile_data_offset] = pixel_value; + tile_data_offset++; + } + } + } + void Cleanup() { // Reset texture_ if (texture_) { @@ -105,10 +111,11 @@ class Bitmap { auto depth() const { return depth_; } auto size() const { return data_size_; } auto data() const { return data_.data(); } - auto mutable_data() { return data_; } + auto &mutable_data() { return data_; } auto mutable_pixel_data() { return pixel_data_; } auto surface() const { return surface_.get(); } auto mutable_surface() { return surface_.get(); } + void set_data(const Bytes &data) { data_ = data; } auto vector() const { return data_; } auto at(int i) const { return data_[i]; } @@ -142,12 +149,17 @@ class Bitmap { int height_ = 0; int depth_ = 0; int data_size_ = 0; + bool freed_ = false; bool active_ = false; bool modified_ = false; void *texture_pixels = nullptr; + uchar *pixel_data_; Bytes data_; + + std::vector png_data_; + gfx::SNESPalette palette_; std::shared_ptr texture_ = nullptr; std::shared_ptr surface_ = nullptr; diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index ad9b0d36..3664e35f 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -45,6 +45,7 @@ target_include_directories( ../src/lib/ # ../src/lib/asar/src/asar/ ${SDL2_INCLUDE_DIR} + ${PNG_INCLUDE_DIRS} ) target_link_libraries(