package screenshot import ( "errors" "image" "syscall" "unsafe" "github.com/kbinani/screenshot/internal/util" win "github.com/lxn/win" ) var ( libUser32, _ = syscall.LoadLibrary("user32.dll") funcGetDesktopWindow, _ = syscall.GetProcAddress(syscall.Handle(libUser32), "GetDesktopWindow") funcEnumDisplayMonitors, _ = syscall.GetProcAddress(syscall.Handle(libUser32), "EnumDisplayMonitors") ) func Capture(x, y, width, height int) (*image.RGBA, error) { rect := image.Rect(0, 0, width, height) img, err := util.CreateImage(rect) if err != nil { return nil, err } hwnd := getDesktopWindow() hdc := win.GetDC(hwnd) if hdc == 0 { return nil, errors.New("GetDC failed") } defer win.ReleaseDC(hwnd, hdc) memory_device := win.CreateCompatibleDC(hdc) if memory_device == 0 { return nil, errors.New("CreateCompatibleDC failed") } defer win.DeleteDC(memory_device) bitmap := win.CreateCompatibleBitmap(hdc, int32(width), int32(height)) if bitmap == 0 { return nil, errors.New("CreateCompatibleBitmap failed") } defer win.DeleteObject(win.HGDIOBJ(bitmap)) var header win.BITMAPINFOHEADER header.BiSize = uint32(unsafe.Sizeof(header)) header.BiPlanes = 1 header.BiBitCount = 32 header.BiWidth = int32(width) header.BiHeight = int32(-height) header.BiCompression = win.BI_RGB header.BiSizeImage = 0 // GetDIBits balks at using Go memory on some systems. The MSDN example uses // GlobalAlloc, so we'll do that too. See: // https://docs.microsoft.com/en-gb/windows/desktop/gdi/capturing-an-image bitmapDataSize := uintptr(((int64(width)*int64(header.BiBitCount) + 31) / 32) * 4 * int64(height)) hmem := win.GlobalAlloc(win.GMEM_MOVEABLE, bitmapDataSize) defer win.GlobalFree(hmem) memptr := win.GlobalLock(hmem) defer win.GlobalUnlock(hmem) old := win.SelectObject(memory_device, win.HGDIOBJ(bitmap)) if old == 0 { return nil, errors.New("SelectObject failed") } defer win.SelectObject(memory_device, old) if !win.BitBlt(memory_device, 0, 0, int32(width), int32(height), hdc, int32(x), int32(y), win.SRCCOPY) { return nil, errors.New("BitBlt failed") } if win.GetDIBits(hdc, bitmap, 0, uint32(height), (*uint8)(memptr), (*win.BITMAPINFO)(unsafe.Pointer(&header)), win.DIB_RGB_COLORS) == 0 { return nil, errors.New("GetDIBits failed") } i := 0 src := uintptr(memptr) for y := 0; y < height; y++ { for x := 0; x < width; x++ { v0 := *(*uint8)(unsafe.Pointer(src)) v1 := *(*uint8)(unsafe.Pointer(src + 1)) v2 := *(*uint8)(unsafe.Pointer(src + 2)) // BGRA => RGBA, and set A to 255 img.Pix[i], img.Pix[i+1], img.Pix[i+2], img.Pix[i+3] = v2, v1, v0, 255 i += 4 src += 4 } } return img, nil } func NumActiveDisplays() int { var count int = 0 enumDisplayMonitors(win.HDC(0), nil, syscall.NewCallback(countupMonitorCallback), uintptr(unsafe.Pointer(&count))) return count } func GetDisplayBounds(displayIndex int) image.Rectangle { var ctx getMonitorBoundsContext ctx.Index = displayIndex ctx.Count = 0 enumDisplayMonitors(win.HDC(0), nil, syscall.NewCallback(getMonitorBoundsCallback), uintptr(unsafe.Pointer(&ctx))) return image.Rect( int(ctx.Rect.Left), int(ctx.Rect.Top), int(ctx.Rect.Right), int(ctx.Rect.Bottom)) } func getDesktopWindow() win.HWND { ret, _, _ := syscall.Syscall(funcGetDesktopWindow, 0, 0, 0, 0) return win.HWND(ret) } func enumDisplayMonitors(hdc win.HDC, lprcClip *win.RECT, lpfnEnum uintptr, dwData uintptr) bool { ret, _, _ := syscall.Syscall6(funcEnumDisplayMonitors, 4, uintptr(hdc), uintptr(unsafe.Pointer(lprcClip)), lpfnEnum, dwData, 0, 0) return int(ret) != 0 } func countupMonitorCallback(hMonitor win.HMONITOR, hdcMonitor win.HDC, lprcMonitor *win.RECT, dwData uintptr) uintptr { var count *int count = (*int)(unsafe.Pointer(dwData)) *count = *count + 1 return uintptr(1) } type getMonitorBoundsContext struct { Index int Rect win.RECT Count int } func getMonitorBoundsCallback(hMonitor win.HMONITOR, hdcMonitor win.HDC, lprcMonitor *win.RECT, dwData uintptr) uintptr { var ctx *getMonitorBoundsContext ctx = (*getMonitorBoundsContext)(unsafe.Pointer(dwData)) if ctx.Count == ctx.Index { ctx.Rect = *lprcMonitor return uintptr(0) } else { ctx.Count = ctx.Count + 1 return uintptr(1) } }