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 = util::PlatformPaths::GetAppDataSubdirectory("screenshots");
46 std::filesystem::path base_dir;
47 if (base_dir_or.ok()) {
48 base_dir = *base_dir_or;
49 } else {
50 base_dir = std::filesystem::temp_directory_path() / "yaze" / "screenshots";
51 }
52
53 std::error_code ec;
54 std::filesystem::create_directories(base_dir, ec);
55
56 const int64_t timestamp_ms = absl::ToUnixMillis(absl::Now());
57 return base_dir / std::filesystem::path(absl::StrFormat(
58 "yaze_%lld.bmp", static_cast<long long>(timestamp_ms)));
59}
60
61void RevealScreenshot(const std::string& path) {
62#ifdef __APPLE__
63 // On macOS, automatically open the screenshot so the user can verify it
64 std::string cmd = absl::StrFormat("open \"%s\"", path);
65 int result = system(cmd.c_str());
66 (void)result;
67#endif
68}
69
70} // namespace
71
72absl::StatusOr<ScreenshotArtifact> CaptureHarnessScreenshot(
73 const std::string& preferred_path, bool reveal_to_user) {
74 ImGuiIO& io = ImGui::GetIO();
75 auto* backend_data =
76 static_cast<ImGui_ImplSDLRenderer2_Data*>(io.BackendRendererUserData);
77
78 if (!backend_data || !backend_data->Renderer) {
79 return absl::FailedPreconditionError("SDL renderer not available");
80 }
81
82 SDL_Renderer* renderer = backend_data->Renderer;
83 int width = 0;
84 int height = 0;
85 if (platform::GetRendererOutputSize(renderer, &width, &height) != 0) {
86 return absl::InternalError(
87 absl::StrFormat("Failed to get renderer size: %s", SDL_GetError()));
88 }
89
90 std::filesystem::path output_path =
91 preferred_path.empty() ? DefaultScreenshotPath()
92 : std::filesystem::path(preferred_path);
93 if (output_path.has_parent_path()) {
94 std::error_code ec;
95 std::filesystem::create_directories(output_path.parent_path(), ec);
96 }
97
98 SDL_Surface* surface =
99 platform::ReadPixelsToSurface(renderer, width, height, nullptr);
100 if (!surface) {
101 return absl::InternalError(absl::StrFormat(
102 "Failed to read pixels to surface: %s", SDL_GetError()));
103 }
104
105 if (SDL_SaveBMP(surface, output_path.string().c_str()) != 0) {
107 return absl::InternalError(
108 absl::StrFormat("Failed to save BMP: %s", SDL_GetError()));
109 }
110
112
113 std::error_code ec;
114 const int64_t file_size = std::filesystem::file_size(output_path, ec);
115 if (ec) {
116 return absl::InternalError(
117 absl::StrFormat("Failed to stat screenshot %s: %s",
118 output_path.string(), ec.message()));
119 }
120
121 ScreenshotArtifact artifact;
122 artifact.file_path = output_path.string();
123 artifact.width = width;
124 artifact.height = height;
125 artifact.file_size_bytes = file_size;
126
127 if (reveal_to_user) {
128 RevealScreenshot(artifact.file_path);
129 }
130
131 return artifact;
132}
133
134absl::StatusOr<ScreenshotArtifact> CaptureHarnessScreenshotRegion(
135 const std::optional<CaptureRegion>& region,
136 const std::string& preferred_path, bool reveal_to_user) {
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 if (reveal_to_user) {
226 RevealScreenshot(artifact.file_path);
227 }
228
229 return artifact;
230}
231
232absl::StatusOr<ScreenshotArtifact> CaptureActiveWindow(
233 const std::string& preferred_path, bool reveal_to_user) {
234 ImGuiContext* ctx = ImGui::GetCurrentContext();
235 if (!ctx || !ctx->NavWindow) {
236 return absl::FailedPreconditionError("No active ImGui window");
237 }
238
239 ImGuiWindow* window = ctx->NavWindow;
240 CaptureRegion region;
241 region.x = static_cast<int>(window->Pos.x);
242 region.y = static_cast<int>(window->Pos.y);
243 region.width = static_cast<int>(window->Size.x);
244 region.height = static_cast<int>(window->Size.y);
245
246 return CaptureHarnessScreenshotRegion(region, preferred_path, reveal_to_user);
247}
248
249absl::StatusOr<ScreenshotArtifact> CaptureWindowByName(
250 const std::string& window_name, const std::string& preferred_path,
251 bool reveal_to_user) {
252 ImGuiContext* ctx = ImGui::GetCurrentContext();
253 if (!ctx) {
254 return absl::FailedPreconditionError("No ImGui context");
255 }
256
257 ImGuiWindow* window = ImGui::FindWindowByName(window_name.c_str());
258 if (!window) {
259 return absl::NotFoundError(
260 absl::StrFormat("Window '%s' not found", window_name));
261 }
262
263 CaptureRegion region;
264 region.x = static_cast<int>(window->Pos.x);
265 region.y = static_cast<int>(window->Pos.y);
266 region.width = static_cast<int>(window->Size.x);
267 region.height = static_cast<int>(window->Size.y);
268
269 return CaptureHarnessScreenshotRegion(region, preferred_path, reveal_to_user);
270}
271
272} // namespace test
273} // namespace yaze
274
275#endif // YAZE_WITH_GRPC
static absl::StatusOr< std::filesystem::path > GetAppDataSubdirectory(const std::string &subdir)
Get a subdirectory within the app data folder.
void DestroySurface(SDL_Surface *surface)
Destroy a surface.
Definition sdl_compat.h:719
int GetRendererOutputSize(SDL_Renderer *renderer, int *w, int *h)
Get renderer output size.
Definition sdl_compat.h:734
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:748
SDL2/SDL3 compatibility layer.