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,78 @@
|
||||
// grp.SaveScreenShot: CPythonGraphic::SaveScreenShot queues a host frame request (ScreenShotHost.h) and
|
||||
// ScreenShotHost::WriteScreenShot runs the rest of 40250's SaveScreenShot on the frame: X8R8G8B8 rows ->
|
||||
// RGB -> SaveJPEG (jpeg_save, quality 100).
|
||||
#include "EterPythonLib/StdAfx.h"
|
||||
#include "EterPythonLib/PythonGraphic.h"
|
||||
#include "../../src/platform/EterPythonLib/ScreenShotHost.h"
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <string>
|
||||
#include <unistd.h>
|
||||
#include <vector>
|
||||
|
||||
static int g_failures = 0;
|
||||
#define CHECK(cond) \
|
||||
do { \
|
||||
if (!(cond)) { \
|
||||
std::fprintf(stderr, "%s:%d: CHECK(%s)\n", __FILE__, __LINE__, #cond); \
|
||||
++g_failures; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
static std::vector<unsigned char> ReadFile(const std::string& path)
|
||||
{
|
||||
std::vector<unsigned char> data;
|
||||
if (FILE* f = std::fopen(path.c_str(), "rb")) {
|
||||
int c;
|
||||
while ((c = std::fgetc(f)) != EOF)
|
||||
data.push_back(static_cast<unsigned char>(c));
|
||||
std::fclose(f);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
CPythonGraphic graphic;
|
||||
|
||||
// No host: 40250's GetBackBuffer failure path.
|
||||
ScreenShotHost::SetRequest(nullptr);
|
||||
CHECK(!graphic.SaveScreenShot("unused.jpg"));
|
||||
|
||||
char dir[] = "/tmp/mt_screenshot_XXXXXX";
|
||||
CHECK(mkdtemp(dir) != nullptr);
|
||||
const std::string path = std::string(dir) + "/shot.jpg";
|
||||
|
||||
std::vector<std::string> requested;
|
||||
ScreenShotHost::SetRequest([&](const std::string& p) { requested.push_back(p); return true; });
|
||||
CHECK(graphic.SaveScreenShot(path.c_str()));
|
||||
CHECK(requested.size() == 1 && requested[0] == path);
|
||||
CHECK(ReadFile(path).empty()); // written when the frame arrives, not by the call
|
||||
|
||||
// 8x4 frame with a padded pitch; the padding bytes must not reach the image.
|
||||
const unsigned w = 8, h = 4, pitch = w * 4 + 16;
|
||||
std::vector<std::uint8_t> frame(pitch * h, 0xAB);
|
||||
for (unsigned y = 0; y < h; ++y)
|
||||
for (unsigned x = 0; x < w; ++x) {
|
||||
std::uint8_t* px = &frame[y * pitch + x * 4];
|
||||
px[0] = 0x20; px[1] = 0x80; px[2] = 0xE0; px[3] = 0xFF; // B, G, R, X
|
||||
}
|
||||
CHECK(ScreenShotHost::WriteScreenShot(requested[0].c_str(), frame.data(), w, h, pitch));
|
||||
const std::vector<unsigned char> jpeg = ReadFile(path);
|
||||
CHECK(jpeg.size() > 4);
|
||||
CHECK(jpeg.size() > 4 && jpeg[0] == 0xFF && jpeg[1] == 0xD8);
|
||||
CHECK(jpeg.size() > 4 && jpeg[jpeg.size() - 2] == 0xFF && jpeg[jpeg.size() - 1] == 0xD9);
|
||||
|
||||
// An unwritable path is SaveJPEG's failure.
|
||||
CHECK(!ScreenShotHost::WriteScreenShot((std::string(dir) + "/missing/shot.jpg").c_str(), frame.data(), w, h, pitch));
|
||||
|
||||
ScreenShotHost::SetRequest(nullptr);
|
||||
std::remove(path.c_str());
|
||||
rmdir(dir);
|
||||
if (g_failures)
|
||||
std::fprintf(stderr, "port_screenshot_test: %d failure(s)\n", g_failures);
|
||||
else
|
||||
std::puts("port_screenshot_test: ok");
|
||||
return g_failures ? 1 : 0;
|
||||
}
|
||||
Reference in New Issue
Block a user