From e4408b6bc3429e8f0cb3074b889036075492618e Mon Sep 17 00:00:00 2001 From: Vonce Date: Mon, 17 Jun 2024 00:18:22 -0700 Subject: [PATCH] Screenshot functionality Added Added a screenshot functionality based on this pr https://github.com/xenia-project/xenia/pull/2074 and added some minor adjustments. GUI wise, "take screenshot" is under the Display menu and the keyboard shortcut to take it is F10 The screenshots folder will save to where xenia.exe or xenia_canary.exe is located. As for save paths, it will only use the id of the game due to illegal characters in some titles. --- src/xenia/app/emulator_window.cc | 74 ++++++++++++++++++++++++++++++++ src/xenia/app/emulator_window.h | 5 +++ 2 files changed, 79 insertions(+) diff --git a/src/xenia/app/emulator_window.cc b/src/xenia/app/emulator_window.cc index 779d1aa1c..ef4005e28 100644 --- a/src/xenia/app/emulator_window.cc +++ b/src/xenia/app/emulator_window.cc @@ -17,8 +17,10 @@ #include #include "third_party/cpptoml/include/cpptoml.h" +#include "third_party/fmt/include/fmt/chrono.h" #include "third_party/fmt/include/fmt/format.h" #include "third_party/imgui/imgui.h" +#include "third_party/stb/stb_image_write.h" #include "xenia/base/assert.h" #include "xenia/base/clock.h" #include "xenia/base/cvar.h" @@ -649,6 +651,9 @@ bool EmulatorWindow::Initialize() { display_menu->AddChild( MenuItem::Create(MenuItem::Type::kString, "&Fullscreen", "F11", std::bind(&EmulatorWindow::ToggleFullscreen, this))); + display_menu->AddChild( + MenuItem::Create(MenuItem::Type::kString, "&Take Screenshot", "F10", + std::bind(&EmulatorWindow::TakeScreenshot, this))); } main_menu->AddChild(std::move(display_menu)); @@ -833,6 +838,10 @@ void EmulatorWindow::OnKeyDown(ui::KeyEvent& e) { case ui::VirtualKey::kF11: { ToggleFullscreen(); } break; + case ui::VirtualKey::kF10: { + TakeScreenshot(); + } break; + case ui::VirtualKey::kEscape: { // Allow users to escape fullscreen (but not enter it). if (!window_->IsFullscreen()) { @@ -890,6 +899,71 @@ void EmulatorWindow::OnMouseUp(const ui::MouseEvent& e) { last_mouse_up = steady_clock::now(); } +void EmulatorWindow::TakeScreenshot() { + xe::ui::RawImage image; + if (!GetGraphicsSystemPresenter()->CaptureGuestOutput(image) || + GetGraphicsSystemPresenter() == nullptr) { + XELOGE("Failed to capture guest output for screenshot"); + return; + } + ExportScreenshot(image); +} + +void EmulatorWindow::ExportScreenshot(const xe::ui::RawImage& image) { + auto t = std::time(nullptr); + + // The format is: Year-Month-DayTHours-Minutes-Seconds based off ISO 8601 + std::string datetime = fmt::format("{:%Y-%m-%dT%H-%M-%S}", fmt::localtime(t)); + + // Get the title id of the game because some titles contain characters that + // cannot be used as a directory + std::string title_id; + if (emulator()->title_id()) { + title_id = fmt::format("{:08X}", emulator()->title_id()); + } else { + XELOGE("Failed to get the current title id"); + return; + } + + // Find where xenia.exe or xenia_canary.exe is located and create a + // screenshots folder + auto screenshot_path = + (xe::filesystem::GetExecutableFolder() / "screenshots" / title_id); + + if (!std::filesystem::exists(screenshot_path)) { + std::filesystem::create_directories(screenshot_path); + } + + std::string filename = fmt::format("{} - {}.png", title_id, datetime); + SaveImage(screenshot_path / filename, image); + + xe::ui::ImGuiDialog::ShowMessageBox( + imgui_drawer_.get(), "Screenshot saved", + fmt::format("Screenshot saved to {}", xe::path_to_utf8(screenshot_path))); +} + +// Converts a RawImage into a PNG file +void EmulatorWindow::SaveImage(const std::filesystem::path& filepath, + const xe::ui::RawImage& image) { + auto file = std::ofstream(filepath, std::ios::binary); + if (!file.is_open()) { + XELOGE("Failed to open file for writing: {}", xe::path_to_utf8(filepath)); + return; + } + + auto result = stbi_write_png_to_func( + [](void* context, void* data, int size) { + auto file = reinterpret_cast(context); + file->write(reinterpret_cast(data), size); + }, + &file, image.width, image.height, 4, image.data.data(), + (int)image.stride); + if (result == 0) { + XELOGE("Failed to write PNG to file: {}", xe::path_to_utf8(filepath)); + return; + } +} + void EmulatorWindow::ToggleFullscreenOnDoubleClick() { // this function tests if user has double clicked. // if double click was achieved the fullscreen gets toggled diff --git a/src/xenia/app/emulator_window.h b/src/xenia/app/emulator_window.h index 9f8e066a7..1a0dc9564 100644 --- a/src/xenia/app/emulator_window.h +++ b/src/xenia/app/emulator_window.h @@ -87,6 +87,11 @@ class EmulatorWindow { void ToggleFullscreen(); void SetInitializingShaderStorage(bool initializing); + void TakeScreenshot(); + void ExportScreenshot(const xe::ui::RawImage& image); + void SaveImage(const std::filesystem::path& path, + const xe::ui::RawImage& image); + // Types of button functions for hotkeys. enum class ButtonFunctions { ToggleFullscreen,