screen_shot/
lib.rs

1#![doc = include_str!("../README.md")]
2
3//! Capture a bitmap image of a display. The resulting screenshot is stored in
4//! the `Screenshot` type, which varies per platform.
5//!
6//! # Platform-specific details
7//!
8//! Despite OS X's CoreGraphics documentation, the bitmap returned has its
9//! origin at the top left corner. It uses ARGB pixels.
10//!
11//! The Windows GDI bitmap has its coordinate origin at the bottom left. We
12//! attempt to undo this by reordering the rows. Windows also uses ARGB pixels.
13
14pub use ffi::get_screenshot;
15
16#[derive(Clone, Copy)]
17pub struct Pixel {
18    pub a: u8,
19    pub r: u8,
20    pub g: u8,
21    pub b: u8,
22}
23
24/// An image buffer containing the screenshot.
25/// Pixels are stored as [ARGB](https://en.wikipedia.org/wiki/ARGB).
26pub struct Screenshot {
27    data: Vec<u8>,
28    height: usize,
29    width: usize,
30    row_len: usize, // Might be superfluous
31    pixel_width: usize,
32}
33
34impl Screenshot {
35    /// Height of image in pixels.
36    #[inline]
37    pub fn height(&self) -> usize {
38        self.height
39    }
40
41    /// Width of image in pixels.
42    #[inline]
43    pub fn width(&self) -> usize {
44        self.width
45    }
46
47    /// Number of bytes in one row of bitmap.
48    #[inline]
49    pub fn row_len(&self) -> usize {
50        self.row_len
51    }
52
53    /// Width of pixel in bytes.
54    #[inline]
55    pub fn pixel_width(&self) -> usize {
56        self.pixel_width
57    }
58
59    /// # Safety
60    /// Raw bitmap.
61    #[inline]
62    pub unsafe fn raw_data(&self) -> *const u8 {
63        &self.data[0] as *const u8
64    }
65
66    /// # Safety
67    /// Raw bitmap.
68    #[inline]
69    pub unsafe fn raw_data_mut(&mut self) -> *mut u8 {
70        &mut self.data[0] as *mut u8
71    }
72
73    /// Number of bytes in bitmap
74    #[inline]
75    pub fn raw_len(&self) -> usize {
76        self.data.len() * size_of::<u8>()
77    }
78
79    /// Gets pixel at (row, col)
80    pub fn get_pixel(&self, row: usize, col: usize) -> Pixel {
81        let idx = (row * self.row_len() + col * self.pixel_width()) as isize;
82        unsafe {
83            let data = &self.data[0] as *const u8;
84            if idx as usize > self.raw_len() {
85                panic!("Bounds overflow");
86            }
87
88            Pixel {
89                a: *data.offset(idx + 3),
90                r: *data.offset(idx + 2),
91                g: *data.offset(idx + 1),
92                b: *data.offset(idx),
93            }
94        }
95    }
96}
97
98impl AsRef<[u8]> for Screenshot {
99    #[inline]
100    fn as_ref(&self) -> &[u8] {
101        self.data.as_slice()
102    }
103}
104
105pub type ScreenResult = Result<Screenshot, &'static str>;
106
107#[cfg(target_os = "linux")]
108mod ffi {
109    use crate::{ScreenResult, Screenshot};
110    use libc::{c_int, c_uint};
111    use std::ptr::null_mut;
112    use std::slice;
113    use x11::xlib::{
114        XAllPlanes, XCloseDisplay, XDestroyImage, XDestroyWindow, XGetImage, XGetWindowAttributes,
115        XOpenDisplay, XRootWindowOfScreen, XScreenOfDisplay, XWindowAttributes, ZPixmap,
116    };
117
118    pub fn get_screenshot(screen: u32) -> ScreenResult {
119        unsafe {
120            let display = XOpenDisplay(null_mut());
121            let screen = XScreenOfDisplay(display, screen as c_int);
122            let root = XRootWindowOfScreen(screen);
123
124            let mut attr = std::mem::MaybeUninit::<XWindowAttributes>::uninit();
125            XGetWindowAttributes(display, root, attr.as_mut_ptr());
126            let attr = attr.assume_init();
127
128            let img = &mut *XGetImage(
129                display,
130                root,
131                0,
132                0,
133                attr.width as c_uint,
134                attr.height as c_uint,
135                XAllPlanes(),
136                ZPixmap,
137            );
138            XDestroyWindow(display, root);
139            XCloseDisplay(display);
140            let height = img.height as usize;
141            let width = img.width as usize;
142            let row_len = img.bytes_per_line as usize;
143            let pixel_bits = img.bits_per_pixel as usize;
144            if !pixel_bits.is_multiple_of(8) {
145                XDestroyImage(&mut *img);
146                return Err("Pixels aren't integral bytes.");
147            }
148            let pixel_width = pixel_bits / 8;
149
150            // Create a Vec for image
151            let size = width * height * pixel_width;
152            let mut data = slice::from_raw_parts(img.data as *mut u8, size as usize).to_vec();
153            XDestroyImage(&mut *img);
154
155            // Fix Alpha channel when xlib cannot retrieve info correctly
156            let has_alpha = data.iter().enumerate().any(|(n, x)| n % 4 == 3 && *x != 0);
157            if !has_alpha {
158                for (n, channel) in data.iter_mut().enumerate() {
159                    if n % 4 == 3 {
160                        *channel = 255;
161                    }
162                }
163            }
164
165            Ok(Screenshot {
166                data,
167                height,
168                width,
169                row_len,
170                pixel_width,
171            })
172        }
173    }
174}
175
176#[cfg(target_os = "macos")]
177mod ffi {
178    #![allow(non_upper_case_globals, dead_code)]
179
180    use crate::{ScreenResult, Screenshot};
181    use std::slice;
182
183    type CFIndex = libc::c_long;
184    type CFDataRef = *const u8; // *const CFData
185
186    #[cfg(target_arch = "x86")]
187    type CGFloat = libc::c_float;
188    #[cfg(target_arch = "x86_64")]
189    type CGFloat = libc::c_double;
190    type CGError = i32;
191
192    type CGDirectDisplayID = u32;
193    type CGDisplayCount = u32;
194    type CGImageRef = *mut u8; // *mut CGImage
195    type CGDataProviderRef = *mut u8; // *mut CGDataProvider
196
197    const kCGErrorSuccess: CGError = 0;
198    const kCGErrorFailure: CGError = 1000;
199    const CGDisplayNoErr: CGError = kCGErrorSuccess;
200
201    #[link(name = "CoreGraphics", kind = "framework")]
202    unsafe extern "C" {
203        fn CGGetActiveDisplayList(
204            max_displays: u32,
205            active_displays: *mut CGDirectDisplayID,
206            display_count: *mut CGDisplayCount,
207        ) -> CGError;
208        fn CGDisplayCreateImage(displayID: CGDirectDisplayID) -> CGImageRef;
209        fn CGImageRelease(image: CGImageRef);
210
211        fn CGImageGetBitsPerComponent(image: CGImageRef) -> libc::size_t;
212        fn CGImageGetBitsPerPixel(image: CGImageRef) -> libc::size_t;
213        fn CGImageGetBytesPerRow(image: CGImageRef) -> libc::size_t;
214        fn CGImageGetDataProvider(image: CGImageRef) -> CGDataProviderRef;
215        fn CGImageGetHeight(image: CGImageRef) -> libc::size_t;
216        fn CGImageGetWidth(image: CGImageRef) -> libc::size_t;
217
218        fn CGDataProviderCopyData(provider: CGDataProviderRef) -> CFDataRef;
219    }
220    #[link(name = "CoreFoundation", kind = "framework")]
221    unsafe extern "C" {
222        fn CFDataGetLength(theData: CFDataRef) -> CFIndex;
223        fn CFDataGetBytePtr(theData: CFDataRef) -> *const u8;
224        fn CFRelease(cf: *const libc::c_void);
225    }
226
227    /// Get a screenshot of the requested display.
228    pub fn get_screenshot(screen: usize) -> ScreenResult {
229        unsafe {
230            // Get number of displays
231            let mut count: CGDisplayCount = 0;
232            use std::ptr::null_mut;
233            let err = CGGetActiveDisplayList(0, null_mut::<CGDirectDisplayID>(), &mut count);
234            if err != CGDisplayNoErr {
235                return Err("Error getting number of displays.");
236            }
237
238            // Get list of displays
239            let mut disps: Vec<CGDisplayCount> = vec![0; count as usize];
240            let err = CGGetActiveDisplayList(
241                disps.len() as u32,
242                &mut disps[0] as *mut CGDirectDisplayID,
243                &mut count,
244            );
245            if err != CGDisplayNoErr {
246                return Err("Error getting list of displays.");
247            }
248
249            // Get screenshot of requested display
250            let disp_id = disps[screen];
251            let cg_img = CGDisplayCreateImage(disp_id);
252
253            // Get info about image
254            let width = CGImageGetWidth(cg_img) as usize;
255            let height = CGImageGetHeight(cg_img) as usize;
256            let row_len = CGImageGetBytesPerRow(cg_img) as usize;
257            let pixel_bits = CGImageGetBitsPerPixel(cg_img) as usize;
258            if !pixel_bits.is_multiple_of(8) {
259                return Err("Pixels aren't integral bytes.");
260            }
261
262            // Copy image into a Vec buffer
263            let cf_data = CGDataProviderCopyData(CGImageGetDataProvider(cg_img));
264            let raw_len = CFDataGetLength(cf_data) as usize;
265
266            let res = if width * height * pixel_bits != raw_len * 8 {
267                Err("Image size is inconsistent with W*H*D.")
268            } else {
269                let data = slice::from_raw_parts(CFDataGetBytePtr(cf_data), raw_len).to_vec();
270                Ok(Screenshot {
271                    data,
272                    height,
273                    width,
274                    row_len,
275                    pixel_width: pixel_bits / 8,
276                })
277            };
278
279            // Release native objects
280            CGImageRelease(cg_img);
281            CFRelease(cf_data as *const libc::c_void);
282
283            res
284        }
285    }
286}
287
288#[cfg(target_os = "windows")]
289mod ffi {
290    #![allow(clippy::upper_case_acronyms)]
291    #![allow(non_snake_case, dead_code)]
292
293    use libc::{c_int, c_long, c_uint, c_void};
294
295    use crate::{ScreenResult, Screenshot};
296
297    type PVOID = *mut c_void;
298    type LPVOID = *mut c_void;
299    type WORD = u16; // c_uint;
300    type DWORD = u32; // c_ulong;
301    type BOOL = c_int;
302    type BYTE = u8;
303    type UINT = c_uint;
304    type LONG = c_long;
305    type LPARAM = c_long;
306
307    #[repr(C)]
308    struct RECT {
309        left: LONG,
310        top: LONG,
311        right: LONG,  // immediately outside rect
312        bottom: LONG, // immediately outside rect
313    }
314    type LPCRECT = *const RECT;
315    type LPRECT = *mut RECT;
316
317    type HANDLE = PVOID;
318    type HMONITOR = HANDLE;
319    type HWND = HANDLE;
320    type HDC = HANDLE;
321    #[repr(C)]
322    struct MONITORINFO {
323        cbSize: DWORD,
324        rcMonitor: RECT,
325        rcWork: RECT,
326        dwFlags: DWORD,
327    }
328    type LPMONITORINFO = *mut MONITORINFO;
329    type MONITORENUMPROC = Option<extern "system" fn(HMONITOR, HDC, LPRECT, LPARAM) -> BOOL>;
330
331    type HBITMAP = HANDLE;
332    type HGDIOBJ = HANDLE;
333    type LPBITMAPINFO = PVOID; // Hack
334
335    const NULL: *mut c_void = std::ptr::null_mut::<c_void>();
336    const HGDI_ERROR: *mut c_void = -1isize as *mut c_void;
337    const SM_CXSCREEN: c_int = 0;
338    const SM_CYSCREEN: c_int = 1;
339
340    /// TODO verify value
341    const SRCCOPY: u32 = 0x00CC0020;
342    const CAPTUREBLT: u32 = 0x40000000;
343    const DIB_RGB_COLORS: UINT = 0;
344    const BI_RGB: DWORD = 0;
345
346    #[repr(C)]
347    struct BITMAPINFOHEADER {
348        biSize: DWORD,
349        biWidth: LONG,
350        biHeight: LONG,
351        biPlanes: WORD,
352        biBitCount: WORD,
353        biCompression: DWORD,
354        biSizeImage: DWORD,
355        biXPelsPerMeter: LONG,
356        biYPelsPerMeter: LONG,
357        biClrUsed: DWORD,
358        biClrImportant: DWORD,
359    }
360
361    #[repr(C)]
362    struct RGBQUAD {
363        rgbBlue: BYTE,
364        rgbGreen: BYTE,
365        rgbRed: BYTE,
366        rgbReserved: BYTE,
367    }
368
369    /// WARNING variable sized struct
370    #[repr(C)]
371    struct BITMAPINFO {
372        bmiHeader: BITMAPINFOHEADER,
373        bmiColors: [RGBQUAD; 1],
374    }
375
376    #[link(name = "user32")]
377    unsafe extern "system" {
378        fn GetSystemMetrics(m: c_int) -> c_int;
379        fn EnumDisplayMonitors(
380            hdc: HDC,
381            lprcClip: LPCRECT,
382            lpfnEnum: MONITORENUMPROC,
383            dwData: LPARAM,
384        ) -> BOOL;
385        fn GetMonitorInfo(hMonitor: HMONITOR, lpmi: LPMONITORINFO) -> BOOL;
386        fn GetDesktopWindow() -> HWND;
387        fn GetDC(hWnd: HWND) -> HDC;
388    }
389
390    #[link(name = "gdi32")]
391    unsafe extern "system" {
392        fn CreateCompatibleDC(hdc: HDC) -> HDC;
393        fn CreateCompatibleBitmap(hdc: HDC, nWidth: c_int, nHeight: c_int) -> HBITMAP;
394        fn SelectObject(hdc: HDC, hgdiobj: HGDIOBJ) -> HGDIOBJ;
395        fn BitBlt(
396            hdcDest: HDC,
397            nXDest: c_int,
398            nYDest: c_int,
399            nWidth: c_int,
400            nHeight: c_int,
401            hdcSrc: HDC,
402            nXSrc: c_int,
403            nYSrc: c_int,
404            dwRop: DWORD,
405        ) -> BOOL;
406        fn GetDIBits(
407            hdc: HDC,
408            hbmp: HBITMAP,
409            uStartScan: UINT,
410            cScanLines: UINT,
411            lpvBits: LPVOID,
412            lpbi: LPBITMAPINFO,
413            uUsage: UINT,
414        ) -> c_int;
415
416        fn DeleteObject(hObject: HGDIOBJ) -> BOOL;
417        fn ReleaseDC(hWnd: HWND, hDC: HDC) -> c_int;
418        fn DeleteDC(hdc: HDC) -> BOOL;
419    }
420
421    /// Reorder rows in bitmap, last to first.
422    /// TODO rewrite functionally
423    fn flip_rows(data: Vec<u8>, height: usize, row_len: usize) -> Vec<u8> {
424        let mut new_data = vec![0; data.len()];
425        for row_i in 0..height {
426            for byte_i in 0..row_len {
427                let old_idx = (height - row_i - 1) * row_len + byte_i;
428                let new_idx = row_i * row_len + byte_i;
429                new_data[new_idx] = data[old_idx];
430            }
431        }
432        new_data
433    }
434
435    /// TODO Support multiple screens
436    /// This may never happen, given the horrific quality of Win32 APIs
437    pub fn get_screenshot(_screen: usize) -> ScreenResult {
438        unsafe {
439            // Enumerate monitors, getting a handle and DC for requested monitor.
440            // loljk, because doing that on Windows is worse than death
441            let h_wnd_screen = GetDesktopWindow();
442            let h_dc_screen = GetDC(h_wnd_screen);
443            let width = GetSystemMetrics(SM_CXSCREEN);
444            let height = GetSystemMetrics(SM_CYSCREEN);
445
446            // Create a Windows Bitmap, and copy the bits into it
447            let h_dc = CreateCompatibleDC(h_dc_screen);
448            if h_dc == NULL {
449                return Err("Can't get a Windows display.");
450            }
451
452            let h_bmp = CreateCompatibleBitmap(h_dc_screen, width, height);
453            if h_bmp == NULL {
454                return Err("Can't create a Windows buffer");
455            }
456
457            let res = SelectObject(h_dc, h_bmp);
458            if res == NULL || res == HGDI_ERROR {
459                return Err("Can't select Windows buffer.");
460            }
461
462            let res = BitBlt(
463                h_dc,
464                0,
465                0,
466                width,
467                height,
468                h_dc_screen,
469                0,
470                0,
471                SRCCOPY | CAPTUREBLT,
472            );
473            if res == 0 {
474                return Err("Failed to copy screen to Windows buffer");
475            }
476
477            // Get image info
478            let pixel_width: usize = 4; // FIXME
479            let mut bmi = BITMAPINFO {
480                bmiHeader: BITMAPINFOHEADER {
481                    biSize: size_of::<BITMAPINFOHEADER>() as DWORD,
482                    biWidth: width as LONG,
483                    biHeight: height as LONG,
484                    biPlanes: 1,
485                    biBitCount: 8 * pixel_width as WORD,
486                    biCompression: BI_RGB,
487                    biSizeImage: (width * height * pixel_width as c_int) as DWORD,
488                    biXPelsPerMeter: 0,
489                    biYPelsPerMeter: 0,
490                    biClrUsed: 0,
491                    biClrImportant: 0,
492                },
493                bmiColors: [RGBQUAD {
494                    rgbBlue: 0,
495                    rgbGreen: 0,
496                    rgbRed: 0,
497                    rgbReserved: 0,
498                }],
499            };
500
501            // Create a Vec for image
502            let size: usize = (width * height) as usize * pixel_width;
503            let mut data: Vec<u8> = vec![0; size];
504
505            // copy bits into Vec
506            GetDIBits(
507                h_dc,
508                h_bmp,
509                0,
510                height as DWORD,
511                &mut data[0] as *mut u8 as *mut c_void,
512                &mut bmi as *mut BITMAPINFO as *mut c_void,
513                DIB_RGB_COLORS,
514            );
515
516            // Release native image buffers
517            ReleaseDC(h_wnd_screen, h_dc_screen); // don't need screen anymore
518            DeleteDC(h_dc);
519            DeleteObject(h_bmp);
520
521            let data = flip_rows(data, height as usize, width as usize * pixel_width);
522
523            Ok(Screenshot {
524                data,
525                height: height as usize,
526                width: width as usize,
527                row_len: width as usize * pixel_width,
528                pixel_width,
529            })
530        }
531    }
532}
533
534#[test]
535fn test_get_screenshot() {
536    #[cfg(target_os = "linux")]
537    if std::env::var("DISPLAY").is_err() {
538        eprintln!("Skipping screenshot test: DISPLAY not set");
539        return;
540    }
541    let s: Screenshot = get_screenshot(0).unwrap();
542    println!(
543        "width: {}\n height: {}\npixel width: {}\n bytes: {}",
544        s.width(),
545        s.height(),
546        s.pixel_width(),
547        s.raw_len()
548    );
549}