diff --git a/Core/Screenshot.cpp b/Core/Screenshot.cpp index c049756798..00f7d0a305 100644 --- a/Core/Screenshot.cpp +++ b/Core/Screenshot.cpp @@ -406,8 +406,7 @@ bool Save888RGBScreenshot(const Path &filename, ScreenshotFormat fmt, const u8 * } bool Save8888RGBAScreenshot(const Path &filename, const u8 *buffer, int w, int h) { - png_image png; - memset(&png, 0, sizeof(png)); + png_image png{}; png.version = PNG_IMAGE_VERSION; png.format = PNG_FORMAT_RGBA; png.width = w; @@ -421,3 +420,30 @@ bool Save8888RGBAScreenshot(const Path &filename, const u8 *buffer, int w, int h } return success; } + +bool Save8888RGBAScreenshot(std::vector &bufferPNG, const u8 *bufferRGBA8888, int w, int h) { + png_image png{}; + png.version = PNG_IMAGE_VERSION; + png.format = PNG_FORMAT_RGBA; + png.width = w; + png.height = h; + + png_alloc_size_t allocSize = bufferPNG.size(); + int result = png_image_write_to_memory(&png, allocSize == 0 ? nullptr : bufferPNG.data(), &allocSize, 0, bufferRGBA8888, w * 4, nullptr); + bool success = result != 0 && png.warning_or_error <= 1; + if (!success && allocSize != bufferPNG.size()) { + bufferPNG.resize(allocSize); + png.warning_or_error = 0; + result = png_image_write_to_memory(&png, bufferPNG.data(), &allocSize, 0, bufferRGBA8888, w * 4, nullptr); + success = result != 0 && png.warning_or_error <= 1; + } + if (success) + bufferPNG.resize(allocSize); + png_image_free(&png); + + if (!success) { + ERROR_LOG(IO, "Buffering screenshot to PNG produced errors."); + bufferPNG.clear(); + } + return success; +} diff --git a/Core/Screenshot.h b/Core/Screenshot.h index 117cd5e625..ac124fef9a 100644 --- a/Core/Screenshot.h +++ b/Core/Screenshot.h @@ -39,5 +39,8 @@ const u8 *ConvertBufferToScreenshot(const GPUDebugBuffer &buf, bool alpha, u8 *& // Can only be used while in game. bool TakeGameScreenshot(const Path &filename, ScreenshotFormat fmt, ScreenshotType type, int *width = nullptr, int *height = nullptr, int maxRes = -1); + bool Save888RGBScreenshot(const Path &filename, ScreenshotFormat fmt, const u8 *bufferRGB888, int w, int h); bool Save8888RGBAScreenshot(const Path &filename, const u8 *bufferRGBA8888, int w, int h); +// Overallocate bufferPNG for better encoding speed. +bool Save8888RGBAScreenshot(std::vector &bufferPNG, const u8 *bufferRGBA8888, int w, int h); diff --git a/Windows/GEDebugger/GEDebugger.cpp b/Windows/GEDebugger/GEDebugger.cpp index be4c2d3ea3..49b0d4a6cf 100644 --- a/Windows/GEDebugger/GEDebugger.cpp +++ b/Windows/GEDebugger/GEDebugger.cpp @@ -292,7 +292,10 @@ void CGEDebugger::SetupPreviews() { PreviewExport(primaryBuffer_); break; case ID_GEDBG_COPY_IMAGE: - PreviewToClipboard(primaryBuffer_); + PreviewToClipboard(primaryBuffer_, false); + break; + case ID_GEDBG_COPY_IMAGE_ALPHA: + PreviewToClipboard(primaryBuffer_, true); break; case ID_GEDBG_ENABLE_PREVIEW: previewsEnabled_ ^= 1; @@ -325,7 +328,10 @@ void CGEDebugger::SetupPreviews() { PreviewExport(secondBuffer_); break; case ID_GEDBG_COPY_IMAGE: - PreviewToClipboard(secondBuffer_); + PreviewToClipboard(secondBuffer_, false); + break; + case ID_GEDBG_COPY_IMAGE_ALPHA: + PreviewToClipboard(secondBuffer_, true); break; case ID_GEDBG_ENABLE_PREVIEW: previewsEnabled_ ^= 2; @@ -399,59 +405,94 @@ void CGEDebugger::PreviewExport(const GPUDebugBuffer *dbgBuffer) { } } -void CGEDebugger::PreviewToClipboard(const GPUDebugBuffer *dbgBuffer) { +void CGEDebugger::PreviewToClipboard(const GPUDebugBuffer *dbgBuffer, bool saveAlpha) { if (!OpenClipboard(GetDlgHandle())) { return; } EmptyClipboard(); - uint32_t byteStride = 3 * dbgBuffer->GetStride(); - while ((byteStride & 3) != 0) - ++byteStride; - - HANDLE memHandle = GlobalAlloc(GHND, sizeof(BITMAPV5HEADER) + byteStride * dbgBuffer->GetHeight()); - if (memHandle == NULL) { + uint8_t *flipbuffer = nullptr; + uint32_t w = (uint32_t)-1; + uint32_t h = (uint32_t)-1; + const uint8_t *buffer = ConvertBufferToScreenshot(*dbgBuffer, saveAlpha, flipbuffer, w, h); + if (buffer == nullptr) { + delete [] flipbuffer; CloseClipboard(); return; } - BITMAPV5HEADER *header = (BITMAPV5HEADER *)GlobalLock(memHandle); - header->bV5Size = sizeof(BITMAPV5HEADER); - header->bV5Width = dbgBuffer->GetStride(); - // Bitmaps are flipped, but we can specify negative height to not be flipped. - header->bV5Height = -dbgBuffer->GetHeight(); - header->bV5Planes = 1; - header->bV5BitCount = 24; - header->bV5Compression = BI_RGB; - header->bV5SizeImage = byteStride * dbgBuffer->GetHeight(); - header->bV5CSType = LCS_sRGB; - header->bV5Intent = LCS_GM_GRAPHICS; + uint32_t pixelSize = saveAlpha ? 4 : 3; + uint32_t byteStride = pixelSize * w; + while ((byteStride & 3) != 0) + ++byteStride; - uint8_t *flipbuffer = nullptr; - uint32_t w = (uint32_t)-1; - uint32_t h = (uint32_t)-1; - const uint8_t *buffer = ConvertBufferToScreenshot(*dbgBuffer, false, flipbuffer, w, h); - if (buffer != nullptr) { - uint8_t *pixels = (uint8_t *)(header + 1); - for (uint32_t y = 0; y < dbgBuffer->GetHeight(); ++y) { - const uint8_t *src = buffer + y * 3 * w; - uint8_t *dst = pixels + y * byteStride; - for (uint32_t x = 0; x < w; ++x) { - // Have to swap B/R again for the bitmap, unfortunate. - dst[0] = src[2]; - dst[1] = src[1]; - dst[2] = src[0]; - src += 3; - dst += 3; - } + // Various apps don't support alpha well, so also copy as PNG. + std::vector png; + if (saveAlpha) { + // Overallocate if we can. + png.resize(byteStride * h); + Save8888RGBAScreenshot(png, buffer, w, h); + + W32Util::ClipboardData png1("PNG", png.size()); + W32Util::ClipboardData png2("image/png", png.size()); + if (!png.empty() && png1 && png2) { + memcpy(png1.data, png.data(), png.size()); + memcpy(png2.data, png.data(), png.size()); + png1.Set(); + png2.Set(); } } + + W32Util::ClipboardData bitmap(CF_DIBV5, sizeof(BITMAPV5HEADER) + byteStride * h); + if (!bitmap) { + delete [] flipbuffer; + CloseClipboard(); + return; + } + + BITMAPV5HEADER *header = (BITMAPV5HEADER *)bitmap.data; + header->bV5Size = sizeof(BITMAPV5HEADER); + header->bV5Width = w; + header->bV5Height = h; + header->bV5Planes = 1; + header->bV5BitCount = saveAlpha ? 32 : 24; + header->bV5Compression = saveAlpha ? BI_BITFIELDS : BI_RGB; + header->bV5SizeImage = byteStride * h; + header->bV5CSType = LCS_WINDOWS_COLOR_SPACE; + header->bV5Intent = LCS_GM_GRAPHICS; + + if (saveAlpha) { + header->bV5RedMask = 0x000000FF; + header->bV5GreenMask = 0x0000FF00; + header->bV5BlueMask = 0x00FF0000; + // Only some applications respect the alpha mask... + header->bV5AlphaMask = 0xFF000000; + } + + uint8_t *pixels = (uint8_t *)(header + 1); + for (uint32_t y = 0; y < h; ++y) { + const uint8_t *src = buffer + y * pixelSize * w; + uint8_t *dst = pixels + (h - y - 1) * byteStride; + + if (saveAlpha) { + // No RB swap needed. + memcpy(dst, src, pixelSize * w); + continue; + } + + for (uint32_t x = 0; x < w; ++x) { + // Have to swap B/R again for the bitmap, unfortunate. + dst[0] = src[2]; + dst[1] = src[1]; + dst[2] = src[0]; + src += pixelSize; + dst += pixelSize; + } + } + delete [] flipbuffer; - GlobalUnlock(memHandle); - - // Takes ownership. - SetClipboardData(CF_DIBV5, memHandle); + bitmap.Set(); CloseClipboard(); } diff --git a/Windows/GEDebugger/GEDebugger.h b/Windows/GEDebugger/GEDebugger.h index eb4a7fe051..ba73f479a0 100644 --- a/Windows/GEDebugger/GEDebugger.h +++ b/Windows/GEDebugger/GEDebugger.h @@ -113,7 +113,7 @@ private: void PrimaryPreviewHover(int x, int y); void SecondPreviewHover(int x, int y); void PreviewExport(const GPUDebugBuffer *buffer); - void PreviewToClipboard(const GPUDebugBuffer *buffer); + void PreviewToClipboard(const GPUDebugBuffer *buffer, bool saveAlpha); static void DescribePixel(u32 pix, GPUDebugBufferFormat fmt, int x, int y, char desc[256]); static void DescribePixelRGBA(u32 pix, GPUDebugBufferFormat fmt, int x, int y, char desc[256]); void UpdateMenus(); diff --git a/Windows/W32Util/Misc.cpp b/Windows/W32Util/Misc.cpp index f3d3b63690..4d9d101e41 100644 --- a/Windows/W32Util/Misc.cpp +++ b/Windows/W32Util/Misc.cpp @@ -157,6 +157,31 @@ namespace W32Util } ShellExecute(nullptr, nullptr, moduleFilename.c_str(), cmdline, workingDirectory.c_str(), SW_SHOW); } + + ClipboardData::ClipboardData(const char *format, size_t sz) { + format_ = RegisterClipboardFormatA(format); + handle_ = format_ != 0 ? GlobalAlloc(GHND, sz) : 0; + data = handle_ != 0 ? GlobalLock(handle_) : nullptr; + } + + ClipboardData::ClipboardData(UINT format, size_t sz) { + format_ = format; + handle_ = GlobalAlloc(GHND, sz); + data = handle_ != 0 ? GlobalLock(handle_) : nullptr; + } + + ClipboardData::~ClipboardData() { + if (handle_ != 0) { + GlobalUnlock(handle_); + GlobalFree(handle_); + } + } + + void ClipboardData::Set() { + if (format_ == 0 || handle_ == 0 || data == 0) + return; + SetClipboardData(format_, handle_); + } } static constexpr UINT_PTR IDT_UPDATE = 0xC0DE0042; diff --git a/Windows/W32Util/Misc.h b/Windows/W32Util/Misc.h index 1b723cc241..d20998fdc9 100644 --- a/Windows/W32Util/Misc.h +++ b/Windows/W32Util/Misc.h @@ -14,6 +14,22 @@ namespace W32Util void ExitAndRestart(bool overrideArgs = false, const std::string &args = ""); void SpawnNewInstance(bool overrideArgs = false, const std::string &args = ""); void GetSelfExecuteParams(std::wstring &workingDirectory, std::wstring &moduleFilename); + + struct ClipboardData { + ClipboardData(const char *format, size_t sz); + ClipboardData(UINT format, size_t sz); + ~ClipboardData(); + + void Set(); + + operator bool() { + return data != nullptr; + } + + UINT format_; + HANDLE handle_; + void *data; + }; } struct GenericListViewColumn diff --git a/Windows/ppsspp.rc b/Windows/ppsspp.rc index 31cdd235d5..304ffded6b 100644 --- a/Windows/ppsspp.rc +++ b/Windows/ppsspp.rc @@ -785,6 +785,7 @@ BEGIN BEGIN MENUITEM "Export Image...", ID_GEDBG_EXPORT_IMAGE MENUITEM "Copy Opaque Image", ID_GEDBG_COPY_IMAGE + MENUITEM "Copy Image With Alpha", ID_GEDBG_COPY_IMAGE_ALPHA MENUITEM "Show Prim Preview", ID_GEDBG_ENABLE_PREVIEW END POPUP "matrixoptions" diff --git a/Windows/resource.h b/Windows/resource.h index 0edc2cd6a5..822eb06bf9 100644 --- a/Windows/resource.h +++ b/Windows/resource.h @@ -334,6 +334,7 @@ #define IDC_GEDBG_STEPVSYNC 40222 #define ID_GEDBG_SETCOND 40223 #define ID_GEDBG_COPY_IMAGE 40224 +#define ID_GEDBG_COPY_IMAGE_ALPHA 40225 // Dummy option to let the buffered rendering hotkey cycle through all the options. @@ -347,7 +348,7 @@ #ifdef APSTUDIO_INVOKED #ifndef APSTUDIO_READONLY_SYMBOLS #define _APS_NEXT_RESOURCE_VALUE 256 -#define _APS_NEXT_COMMAND_VALUE 40225 +#define _APS_NEXT_COMMAND_VALUE 40226 #define _APS_NEXT_CONTROL_VALUE 1202 #define _APS_NEXT_SYMED_VALUE 101 #endif