port: grp.SaveScreenShot via next-frame swapchain readback + JPEG
CPythonGraphic::SaveScreenShot queues a readback of the next presented frame (ScreenShotHost -> VulkanWindow::request_frame); WriteScreenShot runs the rest of 40250's function verbatim (X8R8G8B8 rows, SaveJPEG, EXIF tag). jpeg_save is encoded with stb_image_write. SHGetSpecialFolderPath returns $HOME/Documents (else cwd); CreateDirectory/_access/jpeg_save accept '\\'. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
c21e012965
commit
bda85eea50
@@ -0,0 +1,37 @@
|
||||
// Platform implementation of EterLib/JpegFile.h. 40250 writes it with libjpeg-9a (JpegFile.cpp: a stdio
|
||||
// destination manager, JCS_RGB, jpeg_set_defaults + jpeg_set_quality); here stb_image_write encodes the
|
||||
// same input (top-down RGB rows, 3 bytes per pixel) at the same quality into a baseline JFIF file.
|
||||
//
|
||||
// Only jpeg_save has a caller in 40250 (CPythonGraphic::SaveJPEG, the screenshot key); the other
|
||||
// declarations of JpegFile.h stay undefined.
|
||||
#include "EterLib/StdAfx.h"
|
||||
#include "EterLib/JpegFile.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
|
||||
#define STB_IMAGE_WRITE_IMPLEMENTATION
|
||||
#define STB_IMAGE_WRITE_STATIC
|
||||
#define STBI_WRITE_NO_STDIO
|
||||
#include "../third_party/stb_image_write.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
void write_to_file(void* context, void* data, int size)
|
||||
{
|
||||
fwrite(data, 1, size_t(size), static_cast<FILE*>(context));
|
||||
}
|
||||
}
|
||||
|
||||
int jpeg_save(unsigned char*data_d, int width, int height, int quality, const char*filename)
|
||||
{
|
||||
// The Win32 CRT takes '\\' as a separator (grp.SaveScreenShot builds "<Documents>\\METIN2\\<time>.jpg").
|
||||
std::string path(filename);
|
||||
std::replace(path.begin(), path.end(), '\\', '/');
|
||||
FILE* fi = fopen(path.c_str(), "wb");
|
||||
if (fi == NULL)
|
||||
return 0;
|
||||
const int ok = stbi_write_jpg_to_func(write_to_file, fi, width, height, 3, data_d, quality);
|
||||
const bool closed = fclose(fi) == 0;
|
||||
return ok && closed ? 1 : 0;
|
||||
}
|
||||
@@ -9,6 +9,9 @@
|
||||
#include "EterPythonLib/StdAfx.h"
|
||||
#include "EterPythonLib/PythonGraphic.h"
|
||||
#include "EterLib/StateManager.h"
|
||||
#include "EterLib/JpegFile.h"
|
||||
#include "EterBase/CRC32.h"
|
||||
#include "ScreenShotHost.h"
|
||||
|
||||
#include "../PlatformStub.h"
|
||||
#include "../EterLib/UIRenderCommands.h"
|
||||
@@ -163,7 +166,162 @@ void CPythonGraphic::RenderUpButton(float sx, float sy, float ex, float ey)
|
||||
RenderLine2d(sx, ey, ex, ey);
|
||||
RenderLine2d(ex, sy, ex, ey);
|
||||
}
|
||||
bool CPythonGraphic::SaveScreenShot(const char*) { MT_PLATFORM_STUB(); return false; }
|
||||
|
||||
// 40250 PythonGraphic.cpp:6
|
||||
bool g_isScreenShotKey = false;
|
||||
|
||||
// 40250 PythonGraphic.cpp:157
|
||||
void GenScreenShotTag(const char* src, DWORD crc32, char* leaf, size_t leafLen)
|
||||
{
|
||||
const char* p = src;
|
||||
const char* n = p;
|
||||
while (n = strchr(p, '\\'))
|
||||
p = n + 1;
|
||||
|
||||
_snprintf(leaf, leafLen, "YMIR_METIN2:%s:0x%.8x", p, crc32);
|
||||
}
|
||||
|
||||
// 40250 PythonGraphic.cpp:167
|
||||
bool CPythonGraphic::SaveJPEG(const char * pszFileName, LPBYTE pbyBuffer, UINT uWidth, UINT uHeight)
|
||||
{
|
||||
return jpeg_save(pbyBuffer, uWidth, uHeight, 100, pszFileName) != 0;
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
ScreenShotHost::Request g_screenShotRequest;
|
||||
}
|
||||
|
||||
void ScreenShotHost::SetRequest(Request request)
|
||||
{
|
||||
g_screenShotRequest = std::move(request);
|
||||
}
|
||||
|
||||
// 40250 PythonGraphic.cpp:172. PORT: the back-buffer lock becomes a request for the next presented frame
|
||||
// (ScreenShotHost.h); the rest of the function runs in ScreenShotHost::WriteScreenShot when it arrives, so
|
||||
// a true return means the capture was queued.
|
||||
bool CPythonGraphic::SaveScreenShot(const char * c_pszFileName)
|
||||
{
|
||||
if (!g_screenShotRequest || !g_screenShotRequest(c_pszFileName))
|
||||
{
|
||||
TraceError("Failed to get back buffer (0x%08x)", E_FAIL);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// 40250 PythonGraphic.cpp:172, from the locked surface on (the swapchain reads back as D3DFMT_X8R8G8B8).
|
||||
bool ScreenShotHost::WriteScreenShot(const char* c_pszFileName, const std::uint8_t* bgrx, unsigned uWidth, unsigned uHeight, unsigned pitch)
|
||||
{
|
||||
BYTE* pbyBuffer = new BYTE[uWidth * uHeight * 3];
|
||||
const BYTE* pbySource = bgrx;
|
||||
BYTE* pbyDestination = (BYTE*) pbyBuffer;
|
||||
for(UINT y = 0; y < uHeight; ++y) {
|
||||
const BYTE *pRow = pbySource;
|
||||
|
||||
for(UINT x = 0; x < uWidth; ++x) {
|
||||
*pbyDestination++ = pRow[2]; // Blue
|
||||
*pbyDestination++ = pRow[1]; // Green
|
||||
*pbyDestination++ = pRow[0]; // Red
|
||||
pRow += 4;
|
||||
}
|
||||
|
||||
// increase by one line
|
||||
pbySource += pitch;
|
||||
}
|
||||
|
||||
bool bSaved = CPythonGraphic::Instance().SaveJPEG(c_pszFileName, pbyBuffer, uWidth, uHeight);
|
||||
|
||||
if(pbyBuffer) {
|
||||
delete [] pbyBuffer;
|
||||
pbyBuffer = NULL;
|
||||
}
|
||||
|
||||
if(bSaved == false) {
|
||||
TraceError("Failed to save JPEG file. (%s, %d, %d)", c_pszFileName, uWidth, uHeight);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (g_isScreenShotKey)
|
||||
{
|
||||
FILE* srcFilePtr = fopen(c_pszFileName, "rb");
|
||||
if (srcFilePtr)
|
||||
{
|
||||
fseek(srcFilePtr, 0, SEEK_END);
|
||||
size_t fileSize = ftell(srcFilePtr);
|
||||
fseek(srcFilePtr, 0, SEEK_SET);
|
||||
|
||||
char head[21];
|
||||
size_t tailSize = fileSize - sizeof(head);
|
||||
char* tail = (char*)malloc(tailSize);
|
||||
|
||||
fread(head, sizeof(head), 1, srcFilePtr);
|
||||
fread(tail, tailSize, 1, srcFilePtr);
|
||||
fclose(srcFilePtr);
|
||||
|
||||
char imgDesc[64];
|
||||
GenScreenShotTag(c_pszFileName, GetCRC32(tail, tailSize), imgDesc, sizeof(imgDesc));
|
||||
|
||||
int imgDescLen = strlen(imgDesc) + 1;
|
||||
|
||||
unsigned char exifHeader[] = {
|
||||
0xe1,
|
||||
0, // blockLen[1],
|
||||
0, // blockLen[0],
|
||||
0x45,
|
||||
0x78,
|
||||
0x69,
|
||||
0x66,
|
||||
0x0,
|
||||
0x0,
|
||||
0x49,
|
||||
0x49,
|
||||
0x2a,
|
||||
0x0,
|
||||
0x8,
|
||||
0x0,
|
||||
0x0,
|
||||
0x0,
|
||||
0x1,
|
||||
0x0,
|
||||
0xe,
|
||||
0x1,
|
||||
0x2,
|
||||
0x0,
|
||||
static_cast<unsigned char>(imgDescLen), // textLen[0],
|
||||
0, // textLen[1],
|
||||
0, // textLen[2],
|
||||
0, // textLen[3],
|
||||
0x1a,
|
||||
0x0,
|
||||
0x0,
|
||||
0x0,
|
||||
0x0,
|
||||
0x0,
|
||||
0x0,
|
||||
0x0,
|
||||
};
|
||||
|
||||
exifHeader[2] = sizeof(exifHeader) + imgDescLen;
|
||||
|
||||
FILE* dstFilePtr = fopen(c_pszFileName, "wb");
|
||||
//FILE* dstFilePtr = fopen("temp.jpg", "wb");
|
||||
if (dstFilePtr)
|
||||
{
|
||||
fwrite(head, sizeof(head), 1, dstFilePtr);
|
||||
fwrite(exifHeader, sizeof(exifHeader), 1, dstFilePtr);
|
||||
fwrite(imgDesc, imgDescLen, 1, dstFilePtr);
|
||||
fputc(0x00, dstFilePtr);
|
||||
fputc(0xff, dstFilePtr);
|
||||
fwrite(tail, tailSize, 1, dstFilePtr);
|
||||
fclose(dstFilePtr);
|
||||
}
|
||||
|
||||
free(tail);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
// 40250 PythonGraphic.cpp:52.
|
||||
void CPythonGraphic::SetCursorPosition(int x, int y)
|
||||
{
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
// The frame readback behind CPythonGraphic::SaveScreenShot (grp.SaveScreenShot / SaveScreenShotToPath).
|
||||
//
|
||||
// 40250 locks the D3D8 back buffer inside the call and writes the JPEG before returning. Here the script
|
||||
// thread and the host take turns, so the call cannot wait for the host to read the frame back: it asks the
|
||||
// host for the next presented frame and returns true; the host hands the pixels to WriteScreenShot, which
|
||||
// runs the rest of 40250's SaveScreenShot (X8R8G8B8 -> RGB rows, SaveJPEG, the g_isScreenShotKey EXIF tag)
|
||||
// one frame later.
|
||||
//
|
||||
// Plain std types only: the SDL host includes this without the port headers.
|
||||
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <string>
|
||||
|
||||
namespace ScreenShotHost
|
||||
{
|
||||
// Asks the host to call WriteScreenShot(path, ...) with the next presented frame. False when the host
|
||||
// cannot read the swapchain back.
|
||||
using Request = std::function<bool(const std::string& path)>;
|
||||
void SetRequest(Request request);
|
||||
|
||||
// `bgrx`: top-down rows, 4 bytes per pixel in D3DFMT_X8R8G8B8 memory order, `pitch` bytes apart.
|
||||
bool WriteScreenShot(const char* path, const std::uint8_t* bgrx, unsigned width, unsigned height, unsigned pitch);
|
||||
}
|
||||
+1724
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user