yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
screenshot_utils.cc
Go to the documentation of this file.
2
3#ifdef YAZE_WITH_GRPC
4
6
7// Undefine Windows macros that conflict with protobuf generated code
8// SDL.h includes Windows.h on Windows, which defines these macros
9#ifdef _WIN32
10#ifdef DWORD
11#undef DWORD
12#endif
13#ifdef ERROR
14#undef ERROR
15#endif
16#ifdef OVERFLOW
17#undef OVERFLOW
18#endif
19#ifdef IGNORE
20#undef IGNORE
21#endif
22#endif // _WIN32
23
24#include <filesystem>
25#include <string>
26
27#include "absl/status/status.h"
28#include "absl/status/statusor.h"
29#include "absl/strings/str_format.h"
30#include "absl/time/clock.h"
31#include "imgui.h"
32#include "imgui_internal.h"
33#include "util/platform_paths.h"
34
35namespace yaze {
36namespace test {
37
38namespace {
39
40struct ImGui_ImplSDLRenderer2_Data {
41 SDL_Renderer* Renderer;
42};
43
44std::filesystem::path DefaultScreenshotPath() {
45 auto base_dir_or =
47 std::filesystem::path base_dir;
48 if (base_dir_or.ok()) {
49 base_dir = *base_dir_or;
50 } else {
51 base_dir = std::filesystem::temp_directory_path() / "yaze" / "screenshots";
52 }
53
54 std::error_code ec;
55 std::filesystem::create_directories(base_dir, ec);
56
57 const int64_t timestamp_ms = absl::ToUnixMillis(absl::Now());
58 return base_dir / std::filesystem::path(absl::StrFormat(
59 "yaze_%lld.bmp", static_cast<long long>(timestamp_ms)));
60}
61
62void RevealScreenshot(const std::string& path) {
63#ifdef __APPLE__
64 // On macOS, automatically open the screenshot so the user can verify it
65 std::string cmd = absl::StrFormat("open \"%s\"", path);
66 int result = system(cmd.c_str());
67 (void)result;
68#endif
69}
70
71} // namespace
72
73absl::StatusOr<ScreenshotArtifact> CaptureHarnessScreenshot(
74 const std::string& preferred_path) {
75 ImGuiIO& io = ImGui::GetIO();
76 auto* backend_data =
77 static_cast<ImGui_ImplSDLRenderer2_Data*>(io.BackendRendererUserData);
78
79 if (!backend_data || !backend_data->Renderer) {
80 return absl::FailedPreconditionError("SDL renderer not available");
81 }
82
83 SDL_Renderer* renderer = backend_data->Renderer;
84 int width = 0;
85 int height = 0;
86 if (platform::GetRendererOutputSize(renderer, &width, &height) != 0) {
87 return absl::InternalError(
88 absl::StrFormat("Failed to get renderer size: %s", SDL_GetError()));
89 }
90
91 std::filesystem::path output_path =
92 preferred_path.empty() ? DefaultScreenshotPath()
93 : std::filesystem::path(preferred_path);
94 if (output_path.has_parent_path()) {
95 std::error_code ec;
96 std::filesystem::create_directories(output_path.parent_path(), ec);
97 }
98
99 SDL_Surface* surface =
100 platform::ReadPixelsToSurface(renderer, width, height, nullptr);
101 if (!surface) {
102 return absl::InternalError(absl::StrFormat(
103 "Failed to read pixels to surface: %s", SDL_GetError()));
104 }
105
106 if (SDL_SaveBMP(surface, output_path.string().c_str()) != 0) {
108 return absl::InternalError(
109 absl::StrFormat("Failed to save BMP: %s", SDL_GetError()));
110 }
111
113
114 std::error_code ec;
115 const int64_t file_size = std::filesystem::file_size(output_path, ec);
116 if (ec) {
117 return absl::InternalError(
118 absl::StrFormat("Failed to stat screenshot %s: %s",
119 output_path.string(), ec.message()));
120 }
121
122 ScreenshotArtifact artifact;
123 artifact.file_path = output_path.string();
124 artifact.width = width;
125 artifact.height = height;
126 artifact.file_size_bytes = file_size;
127
128 // Reveal to user
129 RevealScreenshot(artifact.file_path);
130
131 return artifact;
132}
133
134absl::StatusOr<ScreenshotArtifact> CaptureHarnessScreenshotRegion(
135 const std::optional<CaptureRegion>& region,
136 const std::string& preferred_path) {
137 ImGuiIO& io = ImGui::GetIO();
138 auto* backend_data =
139 static_cast<ImGui_ImplSDLRenderer2_Data*>(io.BackendRendererUserData);
140
141 if (!backend_data || !backend_data->Renderer) {
142 return absl::FailedPreconditionError("SDL renderer not available");
143 }
144
145 SDL_Renderer* renderer = backend_data->Renderer;
146
147 // Get full renderer size
148 int full_width = 0;
149 int full_height = 0;
150 if (platform::GetRendererOutputSize(renderer, &full_width, &full_height) !=
151 0) {
152 return absl::InternalError(
153 absl::StrFormat("Failed to get renderer size: %s", SDL_GetError()));
154 }
155
156 // Determine capture region
157 int capture_x = 0;
158 int capture_y = 0;
159 int capture_width = full_width;
160 int capture_height = full_height;
161
162 if (region.has_value()) {
163 capture_x = region->x;
164 capture_y = region->y;
165 capture_width = region->width;
166 capture_height = region->height;
167
168 // Clamp to renderer bounds
169 if (capture_x < 0)
170 capture_x = 0;
171 if (capture_y < 0)
172 capture_y = 0;
173 if (capture_x + capture_width > full_width) {
174 capture_width = full_width - capture_x;
175 }
176 if (capture_y + capture_height > full_height) {
177 capture_height = full_height - capture_y;
178 }
179
180 if (capture_width <= 0 || capture_height <= 0) {
181 return absl::InvalidArgumentError("Invalid capture region");
182 }
183 }
184
185 std::filesystem::path output_path =
186 preferred_path.empty() ? DefaultScreenshotPath()
187 : std::filesystem::path(preferred_path);
188 if (output_path.has_parent_path()) {
189 std::error_code ec;
190 std::filesystem::create_directories(output_path.parent_path(), ec);
191 }
192
193 // Read pixels from the specified region
194 SDL_Rect region_rect = {capture_x, capture_y, capture_width, capture_height};
195 SDL_Surface* surface = platform::ReadPixelsToSurface(
196 renderer, capture_width, capture_height, &region_rect);
197
198 if (!surface) {
199 return absl::InternalError(absl::StrFormat(
200 "Failed to read pixels to surface: %s", SDL_GetError()));
201 }
202
203 if (SDL_SaveBMP(surface, output_path.string().c_str()) != 0) {
205 return absl::InternalError(
206 absl::StrFormat("Failed to save BMP: %s", SDL_GetError()));
207 }
208
210
211 std::error_code ec;
212 const int64_t file_size = std::filesystem::file_size(output_path, ec);
213 if (ec) {
214 return absl::InternalError(
215 absl::StrFormat("Failed to stat screenshot %s: %s",
216 output_path.string(), ec.message()));
217 }
218
219 ScreenshotArtifact artifact;
220 artifact.file_path = output_path.string();
221 artifact.width = capture_width;
222 artifact.height = capture_height;
223 artifact.file_size_bytes = file_size;
224
225 // Reveal to user
226 RevealScreenshot(artifact.file_path);
227
228 return artifact;
229}
230
231absl::StatusOr<ScreenshotArtifact> CaptureActiveWindow(
232 const std::string& preferred_path) {
233 ImGuiContext* ctx = ImGui::GetCurrentContext();
234 if (!ctx || !ctx->NavWindow) {
235 return absl::FailedPreconditionError("No active ImGui window");
236 }
237
238 ImGuiWindow* window = ctx->NavWindow;
239 CaptureRegion region;
240 region.x = static_cast<int>(window->Pos.x);
241 region.y = static_cast<int>(window->Pos.y);
242 region.width = static_cast<int>(window->Size.x);
243 region.height = static_cast<int>(window->Size.y);
244
245 return CaptureHarnessScreenshotRegion(region, preferred_path);
246}
247
248absl::StatusOr<ScreenshotArtifact> CaptureWindowByName(
249 const std::string& window_name, const std::string& preferred_path) {
250 ImGuiContext* ctx = ImGui::GetCurrentContext();
251 if (!ctx) {
252 return absl::FailedPreconditionError("No ImGui context");
253 }
254
255 ImGuiWindow* window = ImGui::FindWindowByName(window_name.c_str());
256 if (!window) {
257 return absl::NotFoundError(
258 absl::StrFormat("Window '%s' not found", window_name));
259 }
260
261 CaptureRegion region;
262 region.x = static_cast<int>(window->Pos.x);
263 region.y = static_cast<int>(window->Pos.y);
264 region.width = static_cast<int>(window->Size.x);
265 region.height = static_cast<int>(window->Size.y);
266
267 return CaptureHarnessScreenshotRegion(region, preferred_path);
268}
269
270} // namespace test
271} // namespace yaze
272
273#endif // YAZE_WITH_GRPC
static absl::StatusOr< std::filesystem::path > GetUserDocumentsSubdirectory(const std::string &subdir)
Get a subdirectory within the user documents folder.
void DestroySurface(SDL_Surface *surface)
Destroy a surface.
Definition sdl_compat.h:694
int GetRendererOutputSize(SDL_Renderer *renderer, int *w, int *h)
Get renderer output size.
Definition sdl_compat.h:709
SDL_Surface * ReadPixelsToSurface(SDL_Renderer *renderer, int width, int height, const SDL_Rect *rect)
Read pixels from renderer to a surface.
Definition sdl_compat.h:723
SDL2/SDL3 compatibility layer.