Cross-platform screenshots (#543)

Co-authored-by: Tom Lally <tomlally@protonmail.com>
Co-authored-by: Exzap <13877693+Exzap@users.noreply.github.com>
This commit is contained in:
Tom Lally 2022-12-02 03:51:07 +00:00 committed by GitHub
parent 2cfb7f3737
commit b361b154d8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 61 additions and 100 deletions

View File

@ -10,6 +10,10 @@
#include "config/ActiveSettings.h" #include "config/ActiveSettings.h"
#include <wx/image.h>
#include <wx/dataobj.h>
#include <wx/clipbrd.h>
std::unique_ptr<Renderer> g_renderer; std::unique_ptr<Renderer> g_renderer;
bool Renderer::GetVRAMInfo(int& usageInMB, int& totalInMB) const bool Renderer::GetVRAMInfo(int& usageInMB, int& totalInMB) const
@ -77,126 +81,80 @@ uint8 Renderer::RGBComponentToSRGB(uint8 cli)
return (uint8)(cs * 255.0f); 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<uint8>& rgb_data, int width, int height, bool mainWindow) const void Renderer::SaveScreenshot(const std::vector<uint8>& rgb_data, int width, int height, bool mainWindow) const
{ {
#if BOOST_OS_WINDOWS
const bool save_screenshot = GetConfig().save_screenshot; const bool save_screenshot = GetConfig().save_screenshot;
std::thread([](std::vector<uint8> data, bool save_screenshot, int width, int height, bool mainWindow) std::thread([](std::vector<uint8> 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) if (mainWindow)
{ {
// copy to clipboard s_clipboardMutex.lock();
std::vector<uint8> buffer(sizeof(BITMAPINFO) + data.size()); if (wxTheClipboard->Open())
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)
{ {
for (sint32 ix = 0; ix < width; ++ix) wxTheClipboard->SetData(new wxImageDataObject(image));
wxTheClipboard->Close();
if(!save_screenshot && mainWindow)
LatteOverlay_pushNotification("Screenshot saved to clipboard", 2500);
}
else
{ {
uint8* pIn = data.data() + (ix + iy * width) * 3; LatteOverlay_pushNotification("Failed to open clipboard", 2500);
uint8* pOut = clipboard_image + (ix + (height - iy - 1) * width) * 3;
pOut[0] = pIn[2];
pOut[1] = pIn[1];
pOut[2] = pIn[0];
} }
} s_clipboardMutex.unlock();
if (OpenClipboard(nullptr))
{
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("Screenshot saved", 2500);
} }
// save to png file // save to png file
if (save_screenshot) if (save_screenshot)
{ {
fs::path screendir = ActiveSettings::GetUserDataPath("screenshots"); fs::path screendir = _GenerateScreenshotFilename(!mainWindow);
if (!fs::exists(screendir)) if (!fs::exists(screendir.parent_path()))
fs::create_directory(screendir); fs::create_directory(screendir);
if (image.SaveFile(screendir.wstring()))
auto counter = 0;
for (const auto& it : fs::directory_iterator(screendir))
{ {
int tmp; if(mainWindow)
if (swscanf_s(it.path().filename().c_str(), L"screenshot_%d", &tmp) == 1) LatteOverlay_pushNotification("Screenshot saved", 2500);
counter = std::max(counter, tmp);
}
screendir /= fmt::format(L"screenshot_{}.png", ++counter);
FileStream* fs = FileStream::createFile2(screendir);
if (fs)
{
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 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; LatteOverlay_pushNotification("Failed to save screenshot to file", 2500);
fs::remove(screendir, ec);
}
} }
} }
}, rgb_data, save_screenshot, width, height, mainWindow).detach(); }, rgb_data, save_screenshot, width, height, mainWindow).detach();
#else
cemuLog_log(LogType::Force, "Screenshot feature not implemented");
#endif
} }

View File

@ -3,6 +3,7 @@
#include "gui/guiWrapper.h" #include "gui/guiWrapper.h"
#include <wx/mstream.h> #include <wx/mstream.h>
#include <wx/clipbrd.h>
#include "gui/GameUpdateWindow.h" #include "gui/GameUpdateWindow.h"
#include "gui/PadViewFrame.h" #include "gui/PadViewFrame.h"
@ -386,6 +387,8 @@ namespace coreinit
void MainWindow::OnClose(wxCloseEvent& event) void MainWindow::OnClose(wxCloseEvent& event)
{ {
wxTheClipboard->Flush();
if(m_game_list) if(m_game_list)
m_game_list->OnClose(event); m_game_list->OnClose(event);