Skip to content

Commit 094c1fd

Browse files
Make Windows capturer DPI aware (#2468)
* Make Windows capturer DPI aware * Fix duplicates * Update windows_sampler_tests.rs * Update windows_sampler_tests.rs
1 parent 71e9ea7 commit 094c1fd

File tree

2 files changed

+371
-27
lines changed

2 files changed

+371
-27
lines changed

electron-app/magnifier/rust-sampler/src/sampler/windows.rs

Lines changed: 60 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,16 @@ use std::mem;
33
use windows::Win32::Foundation::POINT;
44
use windows::Win32::Graphics::Gdi::{
55
BitBlt, CreateCompatibleBitmap, CreateCompatibleDC, DeleteDC, DeleteObject, GetDC,
6-
GetDIBits, GetPixel, ReleaseDC, SelectObject, BITMAPINFO, BITMAPINFOHEADER, BI_RGB,
7-
CLR_INVALID, DIB_RGB_COLORS, HDC, SRCCOPY,
6+
GetDeviceCaps, GetDIBits, GetPixel, LOGPIXELSX, ReleaseDC, SelectObject, BITMAPINFO,
7+
BITMAPINFOHEADER, BI_RGB, CLR_INVALID, DIB_RGB_COLORS, HDC, SRCCOPY,
88
};
99
use windows::Win32::UI::WindowsAndMessaging::{GetCursorPos, GetSystemMetrics, SM_CXVIRTUALSCREEN, SM_CYVIRTUALSCREEN};
1010

1111
pub struct WindowsSampler {
1212
hdc: HDC,
1313
screen_width: i32,
1414
screen_height: i32,
15+
dpi_scale: f64,
1516
}
1617

1718
impl WindowsSampler {
@@ -27,12 +28,20 @@ impl WindowsSampler {
2728
let screen_width = GetSystemMetrics(SM_CXVIRTUALSCREEN);
2829
let screen_height = GetSystemMetrics(SM_CYVIRTUALSCREEN);
2930

30-
eprintln!("Windows sampler initialized ({}x{})", screen_width, screen_height);
31+
// Get DPI scaling factor
32+
// GetDeviceCaps returns DPI (e.g., 96 for 100%, 192 for 200%)
33+
// Standard DPI is 96, so scale = actual_dpi / 96
34+
let dpi = GetDeviceCaps(hdc, LOGPIXELSX);
35+
let dpi_scale = dpi as f64 / 96.0;
36+
37+
eprintln!("Windows sampler initialized ({}x{}, DPI scale: {})",
38+
screen_width, screen_height, dpi_scale);
3139

3240
Ok(WindowsSampler {
3341
hdc,
3442
screen_width,
3543
screen_height,
44+
dpi_scale,
3645
})
3746
}
3847
}
@@ -49,7 +58,11 @@ impl Drop for WindowsSampler {
4958
impl PixelSampler for WindowsSampler {
5059
fn sample_pixel(&mut self, x: i32, y: i32) -> Result<Color, String> {
5160
unsafe {
52-
let color_ref = GetPixel(self.hdc, x, y);
61+
// Convert from physical pixels to logical pixels
62+
let logical_x = (x as f64 / self.dpi_scale) as i32;
63+
let logical_y = (y as f64 / self.dpi_scale) as i32;
64+
65+
let color_ref = GetPixel(self.hdc, logical_x, logical_y);
5366

5467
// Check for error (CLR_INVALID is returned on error)
5568
// COLORREF is a newtype wrapper around u32
@@ -89,9 +102,15 @@ impl PixelSampler for WindowsSampler {
89102
unsafe {
90103
let half_size = (grid_size / 2) as i32;
91104

92-
// Calculate capture region
93-
let x_start = center_x - half_size;
94-
let y_start = center_y - half_size;
105+
// Convert cursor coordinates from physical pixels to logical pixels
106+
// GetCursorPos returns physical pixels, but DC uses logical pixels
107+
// At 200% DPI: physical 2000 -> logical 1000
108+
let logical_x = (center_x as f64 / self.dpi_scale) as i32;
109+
let logical_y = (center_y as f64 / self.dpi_scale) as i32;
110+
111+
// Calculate capture region in logical pixels
112+
let x_start = logical_x - half_size;
113+
let y_start = logical_y - half_size;
95114
let width = grid_size as i32;
96115
let height = grid_size as i32;
97116

@@ -209,22 +228,41 @@ impl PixelSampler for WindowsSampler {
209228
impl WindowsSampler {
210229
// Fallback to default pixel-by-pixel sampling if BitBlt fails
211230
fn sample_grid_fallback(&mut self, center_x: i32, center_y: i32, grid_size: usize) -> Result<Vec<Vec<Color>>, String> {
212-
let half_size = (grid_size / 2) as i32;
213-
let mut grid = Vec::with_capacity(grid_size);
214-
215-
for row in 0..grid_size {
216-
let mut row_pixels = Vec::with_capacity(grid_size);
217-
for col in 0..grid_size {
218-
let x = center_x + (col as i32 - half_size);
219-
let y = center_y + (row as i32 - half_size);
220-
221-
let color = self.sample_pixel(x, y)
222-
.unwrap_or(Color::new(128, 128, 128)); // Gray fallback
223-
row_pixels.push(color);
231+
unsafe {
232+
let half_size = (grid_size / 2) as i32;
233+
let mut grid = Vec::with_capacity(grid_size);
234+
235+
// Convert center from physical to logical pixels
236+
// This ensures we sample distinct logical pixels, not duplicates at high DPI
237+
let logical_center_x = (center_x as f64 / self.dpi_scale) as i32;
238+
let logical_center_y = (center_y as f64 / self.dpi_scale) as i32;
239+
240+
for row in 0..grid_size {
241+
let mut row_pixels = Vec::with_capacity(grid_size);
242+
for col in 0..grid_size {
243+
// Work in logical pixel space to match main path behavior
244+
let logical_x = logical_center_x + (col as i32 - half_size);
245+
let logical_y = logical_center_y + (row as i32 - half_size);
246+
247+
// Sample directly in logical space using GetPixel
248+
let color_ref = GetPixel(self.hdc, logical_x, logical_y);
249+
250+
let color = if color_ref.0 == CLR_INVALID {
251+
Color::new(128, 128, 128) // Gray fallback for out-of-bounds
252+
} else {
253+
let color_value = color_ref.0;
254+
let r = (color_value & 0xFF) as u8;
255+
let g = ((color_value >> 8) & 0xFF) as u8;
256+
let b = ((color_value >> 16) & 0xFF) as u8;
257+
Color::new(r, g, b)
258+
};
259+
260+
row_pixels.push(color);
261+
}
262+
grid.push(row_pixels);
224263
}
225-
grid.push(row_pixels);
264+
265+
Ok(grid)
226266
}
227-
228-
Ok(grid)
229267
}
230268
}

0 commit comments

Comments
 (0)