Skip to content

Commit 2dbd2f9

Browse files
Fix Windows DPI issues more (#2470)
* Fix Windows DPI issues more * Update windows_sampler_tests.rs * Another one * Update windows_sampler_tests.rs * Update windows_sampler_tests.rs * Update windows_sampler_tests.rs
1 parent 218b603 commit 2dbd2f9

File tree

2 files changed

+166
-175
lines changed

2 files changed

+166
-175
lines changed

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

Lines changed: 24 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,10 @@ use windows::Win32::Graphics::Gdi::{
66
GetDeviceCaps, GetDIBits, GetPixel, LOGPIXELSX, ReleaseDC, SelectObject, BITMAPINFO,
77
BITMAPINFOHEADER, BI_RGB, CLR_INVALID, DIB_RGB_COLORS, HDC, SRCCOPY,
88
};
9-
use windows::Win32::UI::WindowsAndMessaging::{GetCursorPos, GetSystemMetrics, SM_CXVIRTUALSCREEN, SM_CYVIRTUALSCREEN};
9+
use windows::Win32::UI::WindowsAndMessaging::GetCursorPos;
1010

1111
pub struct WindowsSampler {
1212
hdc: HDC,
13-
screen_width: i32,
14-
screen_height: i32,
1513
dpi_scale: f64,
1614
}
1715

@@ -24,23 +22,14 @@ impl WindowsSampler {
2422
return Err("Failed to get device context".to_string());
2523
}
2624

27-
// Get virtual screen dimensions (supports multi-monitor)
28-
let screen_width = GetSystemMetrics(SM_CXVIRTUALSCREEN);
29-
let screen_height = GetSystemMetrics(SM_CYVIRTUALSCREEN);
30-
3125
// Get DPI scaling factor
3226
// GetDeviceCaps returns DPI (e.g., 96 for 100%, 192 for 200%)
3327
// Standard DPI is 96, so scale = actual_dpi / 96
3428
let dpi = GetDeviceCaps(hdc, LOGPIXELSX);
3529
let dpi_scale = dpi as f64 / 96.0;
3630

37-
eprintln!("Windows sampler initialized ({}x{}, DPI scale: {})",
38-
screen_width, screen_height, dpi_scale);
39-
4031
Ok(WindowsSampler {
4132
hdc,
42-
screen_width,
43-
screen_height,
4433
dpi_scale,
4534
})
4635
}
@@ -58,11 +47,14 @@ impl Drop for WindowsSampler {
5847
impl PixelSampler for WindowsSampler {
5948
fn sample_pixel(&mut self, x: i32, y: i32) -> Result<Color, String> {
6049
unsafe {
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;
50+
// Electron is DPI-aware, so:
51+
// - GetCursorPos returns VIRTUAL pixels (e.g., 0-2559 at 200% on 5120 wide screen)
52+
// - GetPixel expects PHYSICAL pixels (e.g., 0-5119)
53+
// We must convert: physical = virtual * dpi_scale
54+
let physical_x = (x as f64 * self.dpi_scale) as i32;
55+
let physical_y = (y as f64 * self.dpi_scale) as i32;
6456

65-
let color_ref = GetPixel(self.hdc, logical_x, logical_y);
57+
let color_ref = GetPixel(self.hdc, physical_x, physical_y);
6658

6759
// Check for error (CLR_INVALID is returned on error)
6860
// COLORREF is a newtype wrapper around u32
@@ -102,15 +94,15 @@ impl PixelSampler for WindowsSampler {
10294
unsafe {
10395
let half_size = (grid_size / 2) as i32;
10496

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;
97+
// Electron is DPI-aware, so GetCursorPos returns virtual coordinates
98+
// but GetDC/BitBlt use physical coordinates
99+
// Convert: physical = virtual * dpi_scale
100+
let physical_center_x = (center_x as f64 * self.dpi_scale) as i32;
101+
let physical_center_y = (center_y as f64 * self.dpi_scale) as i32;
110102

111-
// Calculate capture region in logical pixels
112-
let x_start = logical_x - half_size;
113-
let y_start = logical_y - half_size;
103+
// Calculate capture region in physical pixel coordinates
104+
let x_start = physical_center_x - half_size;
105+
let y_start = physical_center_y - half_size;
114106
let width = grid_size as i32;
115107
let height = grid_size as i32;
116108

@@ -148,7 +140,6 @@ impl PixelSampler for WindowsSampler {
148140
let _ = DeleteObject(bitmap);
149141
let _ = DeleteDC(mem_dc);
150142

151-
eprintln!("BitBlt failed, falling back to pixel-by-pixel sampling");
152143
return self.sample_grid_fallback(center_x, center_y, grid_size);
153144
}
154145

@@ -191,7 +182,6 @@ impl PixelSampler for WindowsSampler {
191182
let _ = DeleteDC(mem_dc);
192183

193184
if scan_lines == 0 {
194-
eprintln!("GetDIBits failed, falling back to pixel-by-pixel sampling");
195185
return self.sample_grid_fallback(center_x, center_y, grid_size);
196186
}
197187

@@ -232,20 +222,19 @@ impl WindowsSampler {
232222
let half_size = (grid_size / 2) as i32;
233223
let mut grid = Vec::with_capacity(grid_size);
234224

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;
225+
// Convert virtual cursor coordinates to physical for DC sampling
226+
let physical_center_x = (center_x as f64 * self.dpi_scale) as i32;
227+
let physical_center_y = (center_y as f64 * self.dpi_scale) as i32;
239228

240229
for row in 0..grid_size {
241230
let mut row_pixels = Vec::with_capacity(grid_size);
242231
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);
232+
// Calculate physical pixel coordinates
233+
let physical_x = physical_center_x + (col as i32 - half_size);
234+
let physical_y = physical_center_y + (row as i32 - half_size);
246235

247-
// Sample directly in logical space using GetPixel
248-
let color_ref = GetPixel(self.hdc, logical_x, logical_y);
236+
// Sample using physical coordinates
237+
let color_ref = GetPixel(self.hdc, physical_x, physical_y);
249238

250239
let color = if color_ref.0 == CLR_INVALID {
251240
Color::new(128, 128, 128) // Gray fallback for out-of-bounds

0 commit comments

Comments
 (0)