mirror of https://github.com/liamg/aminal.git
152 lines
4.2 KiB
Go
152 lines
4.2 KiB
Go
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)
|
|
}
|
|
}
|