@@ -3,15 +3,16 @@ use std::mem;
33use windows:: Win32 :: Foundation :: POINT ;
44use 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} ;
99use windows:: Win32 :: UI :: WindowsAndMessaging :: { GetCursorPos , GetSystemMetrics , SM_CXVIRTUALSCREEN , SM_CYVIRTUALSCREEN } ;
1010
1111pub struct WindowsSampler {
1212 hdc : HDC ,
1313 screen_width : i32 ,
1414 screen_height : i32 ,
15+ dpi_scale : f64 ,
1516}
1617
1718impl 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 {
4958impl 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 {
209228impl 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