diff --git a/src/Cafe/HW/Latte/Renderer/Renderer.cpp b/src/Cafe/HW/Latte/Renderer/Renderer.cpp index d5376f3a..0e5f1acf 100644 --- a/src/Cafe/HW/Latte/Renderer/Renderer.cpp +++ b/src/Cafe/HW/Latte/Renderer/Renderer.cpp @@ -10,6 +10,10 @@ #include "config/ActiveSettings.h" +#include +#include +#include + std::unique_ptr g_renderer; bool Renderer::GetVRAMInfo(int& usageInMB, int& totalInMB) const @@ -77,126 +81,80 @@ uint8 Renderer::RGBComponentToSRGB(uint8 cli) return (uint8)(cs * 255.0f); } +fs::path _GenerateScreenshotFilename(bool isDRC) +{ + fs::path screendir = ActiveSettings::GetUserDataPath("screenshots"); + // build screenshot name with format Screenshot_YYYY-MM-DD_HH-MM-SS[_GamePad].png + // if the file already exists add a suffix counter (_2.png, _3.png etc) + std::time_t time_t = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()); + std::tm* tm = std::localtime(&time_t); + + std::string screenshotFileName = fmt::format("Screenshot_{:04}-{:02}-{:02}_{:02}-{:02}-{:02}", tm->tm_year+1900, tm->tm_mon+1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec); + if (isDRC) + screenshotFileName.append("_GamePad"); + + fs::path screenshotPath; + for(sint32 i=0; i<999; i++) + { + screenshotPath = screendir; + if (i == 0) + screenshotPath.append(fmt::format("{}.png", screenshotFileName)); + else + screenshotPath.append(fmt::format("{}_{}.png", screenshotFileName, i + 1)); + std::error_code ec; + if (!fs::exists(screenshotPath)) + return screenshotPath; + } + return screenshotPath; // if all exist checks fail, return the last path we tried +} + +std::mutex s_clipboardMutex; + void Renderer::SaveScreenshot(const std::vector& rgb_data, int width, int height, bool mainWindow) const { -#if BOOST_OS_WINDOWS const bool save_screenshot = GetConfig().save_screenshot; std::thread([](std::vector data, bool save_screenshot, int width, int height, bool mainWindow) - { + { +#if BOOST_OS_WINDOWS + // on Windows wxWidgets uses OLE API for the clipboard + // to make this work we need to call OleInitialize() on the same thread + OleInitialize(nullptr); +#endif + + wxImage image(width, height, data.data(), true); + if (mainWindow) { - // copy to clipboard - std::vector buffer(sizeof(BITMAPINFO) + data.size()); - auto* bmpInfo = (BITMAPINFO*)buffer.data(); - bmpInfo->bmiHeader.biSize = sizeof(BITMAPINFOHEADER); - bmpInfo->bmiHeader.biWidth = width; - bmpInfo->bmiHeader.biHeight = height; - bmpInfo->bmiHeader.biPlanes = 1; - bmpInfo->bmiHeader.biBitCount = 24; - bmpInfo->bmiHeader.biCompression = BI_RGB; - - uint8* clipboard_image = buffer.data() + sizeof(BITMAPINFOHEADER); - // RGB -> BGR - for (sint32 iy = 0; iy < height; ++iy) + s_clipboardMutex.lock(); + if (wxTheClipboard->Open()) { - for (sint32 ix = 0; ix < width; ++ix) - { - uint8* pIn = data.data() + (ix + iy * width) * 3; - uint8* pOut = clipboard_image + (ix + (height - iy - 1) * width) * 3; - - pOut[0] = pIn[2]; - pOut[1] = pIn[1]; - pOut[2] = pIn[0]; - } + wxTheClipboard->SetData(new wxImageDataObject(image)); + wxTheClipboard->Close(); + if(!save_screenshot && mainWindow) + LatteOverlay_pushNotification("Screenshot saved to clipboard", 2500); } - - if (OpenClipboard(nullptr)) + else { - EmptyClipboard(); - - const HGLOBAL hGlobal = GlobalAlloc(GMEM_MOVEABLE, buffer.size()); - if (hGlobal) - { - memcpy(GlobalLock(hGlobal), buffer.data(), buffer.size()); - GlobalUnlock(hGlobal); - - SetClipboardData(CF_DIB, hGlobal); - GlobalFree(hGlobal); - } - - CloseClipboard(); + LatteOverlay_pushNotification("Failed to open clipboard", 2500); } - - LatteOverlay_pushNotification("Screenshot saved", 2500); + s_clipboardMutex.unlock(); } // save to png file if (save_screenshot) { - fs::path screendir = ActiveSettings::GetUserDataPath("screenshots"); - if (!fs::exists(screendir)) + fs::path screendir = _GenerateScreenshotFilename(!mainWindow); + if (!fs::exists(screendir.parent_path())) fs::create_directory(screendir); - - auto counter = 0; - for (const auto& it : fs::directory_iterator(screendir)) + if (image.SaveFile(screendir.wstring())) { - int tmp; - if (swscanf_s(it.path().filename().c_str(), L"screenshot_%d", &tmp) == 1) - counter = std::max(counter, tmp); + if(mainWindow) + LatteOverlay_pushNotification("Screenshot saved", 2500); } - - screendir /= fmt::format(L"screenshot_{}.png", ++counter); - FileStream* fs = FileStream::createFile2(screendir); - if (fs) + else { - bool success = true; - auto png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); - if (png_ptr) - { - auto info_ptr = png_create_info_struct(png_ptr); - if (info_ptr) - { - if (!setjmp(png_jmpbuf(png_ptr))) - { - auto pngWriter = [](png_structp png_ptr, png_bytep data, png_size_t length) -> void - { - FileStream* fs = (FileStream*)png_get_io_ptr(png_ptr); - fs->writeData(data, length); - }; - - //png_init_io(png_ptr, file); - png_set_write_fn(png_ptr, (void*)fs, pngWriter, nullptr); - - png_set_IHDR(png_ptr, info_ptr, width, height, 8, PNG_COLOR_TYPE_RGB, PNG_INTERLACE_NONE, - PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE); - png_write_info(png_ptr, info_ptr); - for (int i = 0; i < height; ++i) - { - uint8* pData = data.data() + (i * width) * 3; - png_write_row(png_ptr, pData); - } - - png_write_end(png_ptr, nullptr); - } - else - success = false; - - png_free_data(png_ptr, info_ptr, PNG_FREE_ALL, -1); - } - - png_destroy_write_struct(&png_ptr, nullptr); - } - delete fs; - if (!success) - { - std::error_code ec; - fs::remove(screendir, ec); - } + LatteOverlay_pushNotification("Failed to save screenshot to file", 2500); } } }, rgb_data, save_screenshot, width, height, mainWindow).detach(); - -#else -cemuLog_log(LogType::Force, "Screenshot feature not implemented"); -#endif } diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index 23b30186..4c94365b 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -3,6 +3,7 @@ #include "gui/guiWrapper.h" #include +#include #include "gui/GameUpdateWindow.h" #include "gui/PadViewFrame.h" @@ -386,6 +387,8 @@ namespace coreinit void MainWindow::OnClose(wxCloseEvent& event) { + wxTheClipboard->Flush(); + if(m_game_list) m_game_list->OnClose(event);