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
34namespace yaze {
35namespace test {
36
37namespace {
38
39struct ImGui_ImplSDLRenderer2_Data {
40 SDL_Renderer* Renderer;
41};
42
43std::filesystem::path DefaultScreenshotPath() {
44 std::filesystem::path base_dir =
45 std::filesystem::temp_directory_path() / "yaze" / "test-results";
46 std::error_code ec;
47 std::filesystem::create_directories(base_dir, ec);
48
49 const int64_t timestamp_ms = absl::ToUnixMillis(absl::Now());
50 return base_dir /
51 std::filesystem::path(absl::StrFormat(
52 "harness_%lld.bmp", static_cast<long long>(timestamp_ms)));
53}
54
55} // namespace
56
57absl::StatusOr<ScreenshotArtifact> CaptureHarnessScreenshot(
58 const std::string& preferred_path) {
59 ImGuiIO& io = ImGui::GetIO();
60 auto* backend_data =
61 static_cast<ImGui_ImplSDLRenderer2_Data*>(io.BackendRendererUserData);
62
63 if (!backend_data || !backend_data->Renderer) {
64 return absl::FailedPreconditionError("SDL renderer not available");
65 }
66
67 SDL_Renderer* renderer = backend_data->Renderer;
68 int width = 0;
69 int height = 0;
70 if (platform::GetRendererOutputSize(renderer, &width, &height) != 0) {
71 return absl::InternalError(
72 absl::StrFormat("Failed to get renderer size: %s", SDL_GetError()));
73 }
74
75 std::filesystem::path output_path =
76 preferred_path.empty() ? DefaultScreenshotPath()
77 : std::filesystem::path(preferred_path);
78 if (output_path.has_parent_path()) {
79 std::error_code ec;
80 std::filesystem::create_directories(output_path.parent_path(), ec);
81 }
82
83 SDL_Surface* surface = platform::ReadPixelsToSurface(renderer, width, height, nullptr);
84 if (!surface) {
85 return absl::InternalError(
86 absl::StrFormat("Failed to read pixels to surface: %s", SDL_GetError()));
87 }
88
89 if (SDL_SaveBMP(surface, output_path.string().c_str()) != 0) {
91 return absl::InternalError(
92 absl::StrFormat("Failed to save BMP: %s", SDL_GetError()));
93 }
94
96
97 std::error_code ec;
98 const int64_t file_size = std::filesystem::file_size(output_path, ec);
99 if (ec) {
100 return absl::InternalError(
101 absl::StrFormat("Failed to stat screenshot %s: %s",
102 output_path.string(), ec.message()));
103 }
104
105 ScreenshotArtifact artifact;
106 artifact.file_path = output_path.string();
107 artifact.width = width;
108 artifact.height = height;
109 artifact.file_size_bytes = file_size;
110 return artifact;
111}
112
113absl::StatusOr<ScreenshotArtifact> CaptureHarnessScreenshotRegion(
114 const std::optional<CaptureRegion>& region,
115 const std::string& preferred_path) {
116 ImGuiIO& io = ImGui::GetIO();
117 auto* backend_data =
118 static_cast<ImGui_ImplSDLRenderer2_Data*>(io.BackendRendererUserData);
119
120 if (!backend_data || !backend_data->Renderer) {
121 return absl::FailedPreconditionError("SDL renderer not available");
122 }
123
124 SDL_Renderer* renderer = backend_data->Renderer;
125
126 // Get full renderer size
127 int full_width = 0;
128 int full_height = 0;
129 if (platform::GetRendererOutputSize(renderer, &full_width, &full_height) != 0) {
130 return absl::InternalError(
131 absl::StrFormat("Failed to get renderer size: %s", SDL_GetError()));
132 }
133
134 // Determine capture region
135 int capture_x = 0;
136 int capture_y = 0;
137 int capture_width = full_width;
138 int capture_height = full_height;
139
140 if (region.has_value()) {
141 capture_x = region->x;
142 capture_y = region->y;
143 capture_width = region->width;
144 capture_height = region->height;
145
146 // Clamp to renderer bounds
147 if (capture_x < 0)
148 capture_x = 0;
149 if (capture_y < 0)
150 capture_y = 0;
151 if (capture_x + capture_width > full_width) {
152 capture_width = full_width - capture_x;
153 }
154 if (capture_y + capture_height > full_height) {
155 capture_height = full_height - capture_y;
156 }
157
158 if (capture_width <= 0 || capture_height <= 0) {
159 return absl::InvalidArgumentError("Invalid capture region");
160 }
161 }
162
163 std::filesystem::path output_path =
164 preferred_path.empty() ? DefaultScreenshotPath()
165 : std::filesystem::path(preferred_path);
166 if (output_path.has_parent_path()) {
167 std::error_code ec;
168 std::filesystem::create_directories(output_path.parent_path(), ec);
169 }
170
171 // Read pixels from the specified region
172 SDL_Rect region_rect = {capture_x, capture_y, capture_width, capture_height};
173 SDL_Surface* surface = platform::ReadPixelsToSurface(renderer, capture_width, capture_height, &region_rect);
174
175 if (!surface) {
176 return absl::InternalError(
177 absl::StrFormat("Failed to read pixels to surface: %s", SDL_GetError()));
178 }
179
180 if (SDL_SaveBMP(surface, output_path.string().c_str()) != 0) {
182 return absl::InternalError(
183 absl::StrFormat("Failed to save BMP: %s", SDL_GetError()));
184 }
185
187
188 std::error_code ec;
189 const int64_t file_size = std::filesystem::file_size(output_path, ec);
190 if (ec) {
191 return absl::InternalError(
192 absl::StrFormat("Failed to stat screenshot %s: %s",
193 output_path.string(), ec.message()));
194 }
195
196 ScreenshotArtifact artifact;
197 artifact.file_path = output_path.string();
198 artifact.width = capture_width;
199 artifact.height = capture_height;
200 artifact.file_size_bytes = file_size;
201 return artifact;
202}
203
204absl::StatusOr<ScreenshotArtifact> CaptureActiveWindow(
205 const std::string& preferred_path) {
206 ImGuiContext* ctx = ImGui::GetCurrentContext();
207 if (!ctx || !ctx->NavWindow) {
208 return absl::FailedPreconditionError("No active ImGui window");
209 }
210
211 ImGuiWindow* window = ctx->NavWindow;
212 CaptureRegion region;
213 region.x = static_cast<int>(window->Pos.x);
214 region.y = static_cast<int>(window->Pos.y);
215 region.width = static_cast<int>(window->Size.x);
216 region.height = static_cast<int>(window->Size.y);
217
218 return CaptureHarnessScreenshotRegion(region, preferred_path);
219}
220
221absl::StatusOr<ScreenshotArtifact> CaptureWindowByName(
222 const std::string& window_name, const std::string& preferred_path) {
223 ImGuiContext* ctx = ImGui::GetCurrentContext();
224 if (!ctx) {
225 return absl::FailedPreconditionError("No ImGui context");
226 }
227
228 ImGuiWindow* window = ImGui::FindWindowByName(window_name.c_str());
229 if (!window) {
230 return absl::NotFoundError(
231 absl::StrFormat("Window '%s' not found", window_name));
232 }
233
234 CaptureRegion region;
235 region.x = static_cast<int>(window->Pos.x);
236 region.y = static_cast<int>(window->Pos.y);
237 region.width = static_cast<int>(window->Size.x);
238 region.height = static_cast<int>(window->Size.y);
239
240 return CaptureHarnessScreenshotRegion(region, preferred_path);
241}
242
243} // namespace test
244} // namespace yaze
245
246#endif // YAZE_WITH_GRPC
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.