GE Debugger: Copy images optionally with alpha.

Although, lots of apps don't support this.
This commit is contained in:
Unknown W. Brackets 2022-09-24 11:43:52 -07:00
parent f3722faef4
commit 3cc628beb4
8 changed files with 158 additions and 45 deletions

View file

@ -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<uint8_t> &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;
}

View file

@ -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<uint8_t> &bufferPNG, const u8 *bufferRGBA8888, int w, int h);

View file

@ -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<uint8_t> 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();
}

View file

@ -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();

View file

@ -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;

View file

@ -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

View file

@ -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"

View file

@ -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