11use crate :: types:: { Color , PixelSampler , Point } ;
2+ use std:: mem;
23use windows:: Win32 :: Foundation :: POINT ;
3- use windows:: Win32 :: Graphics :: Gdi :: { GetDC , GetPixel , ReleaseDC , HDC , CLR_INVALID } ;
4- use windows:: Win32 :: UI :: WindowsAndMessaging :: GetCursorPos ;
4+ use windows:: Win32 :: Graphics :: Gdi :: {
5+ BitBlt , CreateCompatibleBitmap , CreateCompatibleDC , DeleteDC , DeleteObject , GetDC ,
6+ GetDIBits , GetPixel , ReleaseDC , SelectObject , BITMAPINFO , BITMAPINFOHEADER , BI_RGB ,
7+ CLR_INVALID , DIB_RGB_COLORS , HDC , SRCCOPY ,
8+ } ;
9+ use windows:: Win32 :: UI :: WindowsAndMessaging :: { GetCursorPos , GetSystemMetrics , SM_CXVIRTUALSCREEN , SM_CYVIRTUALSCREEN } ;
510
611pub struct WindowsSampler {
712 hdc : HDC ,
13+ screen_width : i32 ,
14+ screen_height : i32 ,
815}
916
1017impl WindowsSampler {
@@ -16,9 +23,17 @@ impl WindowsSampler {
1623 return Err ( "Failed to get device context" . to_string ( ) ) ;
1724 }
1825
19- eprintln ! ( "Windows sampler initialized" ) ;
26+ // Get virtual screen dimensions (supports multi-monitor)
27+ let screen_width = GetSystemMetrics ( SM_CXVIRTUALSCREEN ) ;
28+ let screen_height = GetSystemMetrics ( SM_CYVIRTUALSCREEN ) ;
2029
21- Ok ( WindowsSampler { hdc } )
30+ eprintln ! ( "Windows sampler initialized ({}x{})" , screen_width, screen_height) ;
31+
32+ Ok ( WindowsSampler {
33+ hdc,
34+ screen_width,
35+ screen_height,
36+ } )
2237 }
2338 }
2439}
@@ -67,4 +82,149 @@ impl PixelSampler for WindowsSampler {
6782 } )
6883 }
6984 }
85+
86+ // Optimized grid sampling using BitBlt for batch capture
87+ // This is ~100x faster than calling GetPixel 81 times (for 9x9 grid)
88+ fn sample_grid ( & mut self , center_x : i32 , center_y : i32 , grid_size : usize , _scale_factor : f64 ) -> Result < Vec < Vec < Color > > , String > {
89+ unsafe {
90+ let half_size = ( grid_size / 2 ) as i32 ;
91+
92+ // Calculate capture region
93+ let x_start = center_x - half_size;
94+ let y_start = center_y - half_size;
95+ let width = grid_size as i32 ;
96+ let height = grid_size as i32 ;
97+
98+ // Create memory DC compatible with screen DC
99+ let mem_dc = CreateCompatibleDC ( self . hdc ) ;
100+ if mem_dc. is_invalid ( ) {
101+ return Err ( "Failed to create compatible DC" . to_string ( ) ) ;
102+ }
103+
104+ // Create compatible bitmap
105+ let bitmap = CreateCompatibleBitmap ( self . hdc , width, height) ;
106+ if bitmap. is_invalid ( ) {
107+ let _ = DeleteDC ( mem_dc) ;
108+ return Err ( "Failed to create compatible bitmap" . to_string ( ) ) ;
109+ }
110+
111+ // Select bitmap into memory DC
112+ let old_bitmap = SelectObject ( mem_dc, bitmap) ;
113+
114+ // Copy screen region to memory bitmap using BitBlt
115+ // This is the key optimization - ONE API call instead of grid_size^2 calls
116+ if let Err ( _) = BitBlt (
117+ mem_dc,
118+ 0 ,
119+ 0 ,
120+ width,
121+ height,
122+ self . hdc ,
123+ x_start,
124+ y_start,
125+ SRCCOPY ,
126+ ) {
127+ // BitBlt failed - clean up and fall back to default implementation
128+ SelectObject ( mem_dc, old_bitmap) ;
129+ let _ = DeleteObject ( bitmap) ;
130+ let _ = DeleteDC ( mem_dc) ;
131+
132+ eprintln ! ( "BitBlt failed, falling back to pixel-by-pixel sampling" ) ;
133+ return self . sample_grid_fallback ( center_x, center_y, grid_size) ;
134+ }
135+
136+ // Prepare bitmap info for GetDIBits
137+ let mut bmi = BITMAPINFO {
138+ bmiHeader : BITMAPINFOHEADER {
139+ biSize : mem:: size_of :: < BITMAPINFOHEADER > ( ) as u32 ,
140+ biWidth : width,
141+ biHeight : -height, // Negative for top-down DIB
142+ biPlanes : 1 ,
143+ biBitCount : 32 , // 32-bit BGRA
144+ biCompression : BI_RGB . 0 as u32 ,
145+ biSizeImage : 0 ,
146+ biXPelsPerMeter : 0 ,
147+ biYPelsPerMeter : 0 ,
148+ biClrUsed : 0 ,
149+ biClrImportant : 0 ,
150+ } ,
151+ bmiColors : [ Default :: default ( ) ; 1 ] ,
152+ } ;
153+
154+ // Allocate buffer for pixel data (4 bytes per pixel: BGRA)
155+ let buffer_size = ( width * height * 4 ) as usize ;
156+ let mut buffer: Vec < u8 > = vec ! [ 0 ; buffer_size] ;
157+
158+ // Get bitmap bits
159+ let scan_lines = GetDIBits (
160+ mem_dc,
161+ bitmap,
162+ 0 ,
163+ height as u32 ,
164+ Some ( buffer. as_mut_ptr ( ) as * mut _ ) ,
165+ & mut bmi,
166+ DIB_RGB_COLORS ,
167+ ) ;
168+
169+ // Clean up GDI resources
170+ SelectObject ( mem_dc, old_bitmap) ;
171+ let _ = DeleteObject ( bitmap) ;
172+ let _ = DeleteDC ( mem_dc) ;
173+
174+ if scan_lines == 0 {
175+ eprintln ! ( "GetDIBits failed, falling back to pixel-by-pixel sampling" ) ;
176+ return self . sample_grid_fallback ( center_x, center_y, grid_size) ;
177+ }
178+
179+ // Parse buffer and build grid
180+ let mut grid = Vec :: with_capacity ( grid_size) ;
181+
182+ for row in 0 ..grid_size {
183+ let mut row_pixels = Vec :: with_capacity ( grid_size) ;
184+ for col in 0 ..grid_size {
185+ // Calculate offset in buffer (BGRA format, 4 bytes per pixel)
186+ let offset = ( ( row * grid_size + col) * 4 ) as usize ;
187+
188+ if offset + 3 < buffer. len ( ) {
189+ // Windows DIB format is BGRA
190+ let b = buffer[ offset] ;
191+ let g = buffer[ offset + 1 ] ;
192+ let r = buffer[ offset + 2 ] ;
193+ // Alpha channel at offset + 3 is ignored
194+
195+ row_pixels. push ( Color :: new ( r, g, b) ) ;
196+ } else {
197+ // Fallback for out-of-bounds
198+ row_pixels. push ( Color :: new ( 128 , 128 , 128 ) ) ;
199+ }
200+ }
201+ grid. push ( row_pixels) ;
202+ }
203+
204+ Ok ( grid)
205+ }
206+ }
207+ }
208+
209+ impl WindowsSampler {
210+ // Fallback to default pixel-by-pixel sampling if BitBlt fails
211+ 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) ;
224+ }
225+ grid. push ( row_pixels) ;
226+ }
227+
228+ Ok ( grid)
229+ }
70230}
0 commit comments