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
5#include <SDL.h>
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(
52 absl::StrFormat("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 (SDL_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 = preferred_path.empty()
76 ? 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 = SDL_CreateRGBSurface(0, width, height, 32, 0x00FF0000,
84 0x0000FF00, 0x000000FF,
85 0xFF000000);
86 if (!surface) {
87 return absl::InternalError(
88 absl::StrFormat("Failed to create SDL surface: %s", SDL_GetError()));
89 }
90
91 if (SDL_RenderReadPixels(renderer, nullptr, SDL_PIXELFORMAT_ARGB8888,
92 surface->pixels, surface->pitch) != 0) {
93 SDL_FreeSurface(surface);
94 return absl::InternalError(
95 absl::StrFormat("Failed to read renderer pixels: %s", SDL_GetError()));
96 }
97
98 if (SDL_SaveBMP(surface, output_path.string().c_str()) != 0) {
99 SDL_FreeSurface(surface);
100 return absl::InternalError(
101 absl::StrFormat("Failed to save BMP: %s", SDL_GetError()));
102 }
103
104 SDL_FreeSurface(surface);
105
106 std::error_code ec;
107 const int64_t file_size =
108 std::filesystem::file_size(output_path, ec);
109 if (ec) {
110 return absl::InternalError(
111 absl::StrFormat("Failed to stat screenshot %s: %s",
112 output_path.string(), ec.message()));
113 }
114
115 ScreenshotArtifact artifact;
116 artifact.file_path = output_path.string();
117 artifact.width = width;
118 artifact.height = height;
119 artifact.file_size_bytes = file_size;
120 return artifact;
121}
122
123absl::StatusOr<ScreenshotArtifact> CaptureHarnessScreenshotRegion(
124 const std::optional<CaptureRegion>& region,
125 const std::string& preferred_path) {
126 ImGuiIO& io = ImGui::GetIO();
127 auto* backend_data =
128 static_cast<ImGui_ImplSDLRenderer2_Data*>(io.BackendRendererUserData);
129
130 if (!backend_data || !backend_data->Renderer) {
131 return absl::FailedPreconditionError("SDL renderer not available");
132 }
133
134 SDL_Renderer* renderer = backend_data->Renderer;
135
136 // Get full renderer size
137 int full_width = 0;
138 int full_height = 0;
139 if (SDL_GetRendererOutputSize(renderer, &full_width, &full_height) != 0) {
140 return absl::InternalError(
141 absl::StrFormat("Failed to get renderer size: %s", SDL_GetError()));
142 }
143
144 // Determine capture region
145 int capture_x = 0;
146 int capture_y = 0;
147 int capture_width = full_width;
148 int capture_height = full_height;
149
150 if (region.has_value()) {
151 capture_x = region->x;
152 capture_y = region->y;
153 capture_width = region->width;
154 capture_height = region->height;
155
156 // Clamp to renderer bounds
157 if (capture_x < 0) capture_x = 0;
158 if (capture_y < 0) capture_y = 0;
159 if (capture_x + capture_width > full_width) {
160 capture_width = full_width - capture_x;
161 }
162 if (capture_y + capture_height > full_height) {
163 capture_height = full_height - capture_y;
164 }
165
166 if (capture_width <= 0 || capture_height <= 0) {
167 return absl::InvalidArgumentError("Invalid capture region");
168 }
169 }
170
171 std::filesystem::path output_path = preferred_path.empty()
172 ? DefaultScreenshotPath()
173 : std::filesystem::path(preferred_path);
174 if (output_path.has_parent_path()) {
175 std::error_code ec;
176 std::filesystem::create_directories(output_path.parent_path(), ec);
177 }
178
179 // Create surface for the capture region
180 SDL_Surface* surface = SDL_CreateRGBSurface(0, capture_width, capture_height,
181 32, 0x00FF0000, 0x0000FF00,
182 0x000000FF, 0xFF000000);
183 if (!surface) {
184 return absl::InternalError(
185 absl::StrFormat("Failed to create SDL surface: %s", SDL_GetError()));
186 }
187
188 // Read pixels from the specified region
189 SDL_Rect region_rect = {capture_x, capture_y, capture_width, capture_height};
190 if (SDL_RenderReadPixels(renderer, &region_rect, SDL_PIXELFORMAT_ARGB8888,
191 surface->pixels, surface->pitch) != 0) {
192 SDL_FreeSurface(surface);
193 return absl::InternalError(
194 absl::StrFormat("Failed to read renderer pixels: %s", SDL_GetError()));
195 }
196
197 if (SDL_SaveBMP(surface, output_path.string().c_str()) != 0) {
198 SDL_FreeSurface(surface);
199 return absl::InternalError(
200 absl::StrFormat("Failed to save BMP: %s", SDL_GetError()));
201 }
202
203 SDL_FreeSurface(surface);
204
205 std::error_code ec;
206 const int64_t file_size = std::filesystem::file_size(output_path, ec);
207 if (ec) {
208 return absl::InternalError(
209 absl::StrFormat("Failed to stat screenshot %s: %s",
210 output_path.string(), ec.message()));
211 }
212
213 ScreenshotArtifact artifact;
214 artifact.file_path = output_path.string();
215 artifact.width = capture_width;
216 artifact.height = capture_height;
217 artifact.file_size_bytes = file_size;
218 return artifact;
219}
220
221absl::StatusOr<ScreenshotArtifact> CaptureActiveWindow(
222 const std::string& preferred_path) {
223 ImGuiContext* ctx = ImGui::GetCurrentContext();
224 if (!ctx || !ctx->NavWindow) {
225 return absl::FailedPreconditionError("No active ImGui window");
226 }
227
228 ImGuiWindow* window = ctx->NavWindow;
229 CaptureRegion region;
230 region.x = static_cast<int>(window->Pos.x);
231 region.y = static_cast<int>(window->Pos.y);
232 region.width = static_cast<int>(window->Size.x);
233 region.height = static_cast<int>(window->Size.y);
234
235 return CaptureHarnessScreenshotRegion(region, preferred_path);
236}
237
238absl::StatusOr<ScreenshotArtifact> CaptureWindowByName(
239 const std::string& window_name,
240 const std::string& preferred_path) {
241 ImGuiContext* ctx = ImGui::GetCurrentContext();
242 if (!ctx) {
243 return absl::FailedPreconditionError("No ImGui context");
244 }
245
246 ImGuiWindow* window = ImGui::FindWindowByName(window_name.c_str());
247 if (!window) {
248 return absl::NotFoundError(
249 absl::StrFormat("Window '%s' not found", window_name));
250 }
251
252 CaptureRegion region;
253 region.x = static_cast<int>(window->Pos.x);
254 region.y = static_cast<int>(window->Pos.y);
255 region.width = static_cast<int>(window->Size.x);
256 region.height = static_cast<int>(window->Size.y);
257
258 return CaptureHarnessScreenshotRegion(region, preferred_path);
259}
260
261} // namespace test
262} // namespace yaze
263
264#endif // YAZE_WITH_GRPC
Main namespace for the application.
Definition controller.cc:20