mirror of
https://github.com/cemu-project/Cemu.git
synced 2024-11-26 02:54:17 +01:00
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:
parent
2cfb7f3737
commit
b361b154d8
@ -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
|
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user