├── LICENSE ├── README.md ├── example └── ss.go ├── screenshot_darwin.go ├── screenshot_freebsd.go ├── screenshot_linux.go └── screenshot_windows.go /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2012 vova616 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Screenshot 2 | Simple cross-platform pure Go screen shot library. (tested on linux&windows&osx) 3 | 4 |
5 | 6 | ## Installation 7 | ```go 8 | go get github.com/vova616/screenshot 9 | ``` 10 | 11 |
12 | 13 | ## Basic Usage 14 | Import the package 15 | ```go 16 | import ( 17 | "github.com/vova616/screenshot" 18 | ) 19 | ``` 20 | 21 | ```go 22 | func main() { 23 | img, err := screenshot.CaptureScreen() 24 | myImg := image.Image(img) 25 | } 26 | ``` 27 | 28 | 29 |
30 | 31 | ## Dependencies 32 | * **Windows** - None 33 | * **Linux/FreeBSD** - https://github.com/BurntSushi/xgb 34 | * **OSX** - cgo (CoreGraphics,CoreFoundation, that should not be a problem) 35 | 36 |
37 | 38 | ## Examples 39 | Look at `example/` folder. 40 | -------------------------------------------------------------------------------- /example/ss.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/vova616/screenshot" 5 | "image/png" 6 | "os" 7 | ) 8 | 9 | func main() { 10 | img, err := screenshot.CaptureScreen() 11 | if err != nil { 12 | panic(err) 13 | } 14 | f, err := os.Create("./ss.png") 15 | if err != nil { 16 | panic(err) 17 | } 18 | err = png.Encode(f, img) 19 | if err != nil { 20 | panic(err) 21 | } 22 | f.Close() 23 | } 24 | -------------------------------------------------------------------------------- /screenshot_darwin.go: -------------------------------------------------------------------------------- 1 | package screenshot 2 | 3 | import ( 4 | // #cgo LDFLAGS: -framework CoreGraphics 5 | // #cgo LDFLAGS: -framework CoreFoundation 6 | // #include 7 | // #include 8 | "C" 9 | "image" 10 | "reflect" 11 | "unsafe" 12 | "math" 13 | ) 14 | 15 | func ScreenRect() (image.Rectangle, error) { 16 | displayID := C.CGMainDisplayID() 17 | width := int(C.CGDisplayPixelsWide(displayID)) 18 | height := int(C.CGDisplayPixelsHigh(displayID)) 19 | return image.Rect(0, 0, width, height), nil 20 | } 21 | 22 | func CaptureScreen() (*image.RGBA, error) { 23 | rect, err := ScreenRect() 24 | if err != nil { 25 | return nil, err 26 | } 27 | return CaptureRect(rect) 28 | } 29 | 30 | func CaptureRect(rect image.Rectangle) (*image.RGBA, error) { 31 | displayID := C.CGMainDisplayID() 32 | width := int(math.Ceil(float64(C.CGDisplayPixelsWide(displayID))/16)*16) 33 | rawData := C.CGDataProviderCopyData(C.CGImageGetDataProvider(C.CGDisplayCreateImage(displayID))) 34 | 35 | length := int(C.CFDataGetLength(rawData)) 36 | ptr := unsafe.Pointer(C.CFDataGetBytePtr(rawData)) 37 | 38 | var slice []byte 39 | hdrp := (*reflect.SliceHeader)(unsafe.Pointer(&slice)) 40 | hdrp.Data = uintptr(ptr) 41 | hdrp.Len = length 42 | hdrp.Cap = length 43 | 44 | imageBytes := make([]byte, length) 45 | 46 | for i := 0; i < length; i += 4 { 47 | imageBytes[i], imageBytes[i+2], imageBytes[i+1], imageBytes[i+3] = slice[i+2], slice[i], slice[i+1], slice[i+3] 48 | } 49 | 50 | C.CFRelease(C.CFTypeRef(rawData)) 51 | 52 | img := &image.RGBA{Pix: imageBytes, Stride: 4 * width, Rect: rect} 53 | return img, nil 54 | } 55 | -------------------------------------------------------------------------------- /screenshot_freebsd.go: -------------------------------------------------------------------------------- 1 | package screenshot 2 | 3 | import ( 4 | "image" 5 | 6 | "github.com/BurntSushi/xgb" 7 | "github.com/BurntSushi/xgb/xproto" 8 | ) 9 | 10 | func ScreenRect() (image.Rectangle, error) { 11 | c, err := xgb.NewConn() 12 | if err != nil { 13 | return image.Rectangle{}, err 14 | } 15 | defer c.Close() 16 | 17 | screen := xproto.Setup(c).DefaultScreen(c) 18 | x := screen.WidthInPixels 19 | y := screen.HeightInPixels 20 | 21 | return image.Rect(0, 0, int(x), int(y)), nil 22 | } 23 | 24 | func CaptureScreen() (*image.RGBA, error) { 25 | r, e := ScreenRect() 26 | if e != nil { 27 | return nil, e 28 | } 29 | return CaptureRect(r) 30 | } 31 | 32 | func CaptureRect(rect image.Rectangle) (*image.RGBA, error) { 33 | c, err := xgb.NewConn() 34 | if err != nil { 35 | return nil, err 36 | } 37 | defer c.Close() 38 | 39 | screen := xproto.Setup(c).DefaultScreen(c) 40 | x, y := rect.Dx(), rect.Dy() 41 | xImg, err := xproto.GetImage(c, xproto.ImageFormatZPixmap, xproto.Drawable(screen.Root), int16(rect.Min.X), int16(rect.Min.Y), uint16(x), uint16(y), 0xffffffff).Reply() 42 | if err != nil { 43 | return nil, err 44 | } 45 | 46 | data := xImg.Data 47 | for i := 0; i < len(data); i += 4 { 48 | data[i], data[i+2], data[i+3] = data[i+2], data[i], 255 49 | } 50 | 51 | img := &image.RGBA{data, 4 * x, image.Rect(0, 0, x, y)} 52 | return img, nil 53 | } 54 | -------------------------------------------------------------------------------- /screenshot_linux.go: -------------------------------------------------------------------------------- 1 | package screenshot 2 | 3 | import ( 4 | "image" 5 | 6 | "github.com/BurntSushi/xgb" 7 | "github.com/BurntSushi/xgb/xproto" 8 | ) 9 | 10 | func ScreenRect() (image.Rectangle, error) { 11 | c, err := xgb.NewConn() 12 | if err != nil { 13 | return image.Rectangle{}, err 14 | } 15 | defer c.Close() 16 | 17 | screen := xproto.Setup(c).DefaultScreen(c) 18 | x := screen.WidthInPixels 19 | y := screen.HeightInPixels 20 | 21 | return image.Rect(0, 0, int(x), int(y)), nil 22 | } 23 | 24 | func CaptureScreen() (*image.RGBA, error) { 25 | r, e := ScreenRect() 26 | if e != nil { 27 | return nil, e 28 | } 29 | return CaptureRect(r) 30 | } 31 | 32 | func CaptureRect(rect image.Rectangle) (*image.RGBA, error) { 33 | c, err := xgb.NewConn() 34 | if err != nil { 35 | return nil, err 36 | } 37 | defer c.Close() 38 | 39 | screen := xproto.Setup(c).DefaultScreen(c) 40 | x, y := rect.Dx(), rect.Dy() 41 | xImg, err := xproto.GetImage(c, xproto.ImageFormatZPixmap, xproto.Drawable(screen.Root), int16(rect.Min.X), int16(rect.Min.Y), uint16(x), uint16(y), 0xffffffff).Reply() 42 | if err != nil { 43 | return nil, err 44 | } 45 | 46 | data := xImg.Data 47 | for i := 0; i < len(data); i += 4 { 48 | data[i], data[i+2], data[i+3] = data[i+2], data[i], 255 49 | } 50 | 51 | img := &image.RGBA{data, 4 * x, image.Rect(0, 0, x, y)} 52 | return img, nil 53 | } 54 | -------------------------------------------------------------------------------- /screenshot_windows.go: -------------------------------------------------------------------------------- 1 | package screenshot 2 | 3 | import ( 4 | "fmt" 5 | "image" 6 | "reflect" 7 | "syscall" 8 | "unsafe" 9 | ) 10 | 11 | func ScreenRect() (image.Rectangle, error) { 12 | hDC := GetDC(0) 13 | if hDC == 0 { 14 | return image.Rectangle{}, fmt.Errorf("Could not Get primary display err:%d\n", GetLastError()) 15 | } 16 | defer ReleaseDC(0, hDC) 17 | x := GetDeviceCaps(hDC, DESKTOPHORZRES) 18 | y := GetDeviceCaps(hDC, DESKTOPVERTRES) 19 | return image.Rect(0, 0, x, y), nil 20 | } 21 | 22 | func CaptureScreen() (*image.RGBA, error) { 23 | r, e := ScreenRect() 24 | if e != nil { 25 | return nil, e 26 | } 27 | return CaptureRect(r) 28 | } 29 | 30 | func CaptureRect(rect image.Rectangle) (*image.RGBA, error) { 31 | hDC := GetDC(0) 32 | if hDC == 0 { 33 | return nil, fmt.Errorf("Could not Get primary display err:%d.\n", GetLastError()) 34 | } 35 | defer ReleaseDC(0, hDC) 36 | 37 | m_hDC := CreateCompatibleDC(hDC) 38 | if m_hDC == 0 { 39 | return nil, fmt.Errorf("Could not Create Compatible DC err:%d.\n", GetLastError()) 40 | } 41 | defer DeleteDC(m_hDC) 42 | 43 | x, y := rect.Dx(), rect.Dy() 44 | 45 | bt := BITMAPINFO{} 46 | bt.BmiHeader.BiSize = uint32(reflect.TypeOf(bt.BmiHeader).Size()) 47 | bt.BmiHeader.BiWidth = int32(x) 48 | bt.BmiHeader.BiHeight = int32(-y) 49 | bt.BmiHeader.BiPlanes = 1 50 | bt.BmiHeader.BiBitCount = 32 51 | bt.BmiHeader.BiCompression = BI_RGB 52 | 53 | ptr := unsafe.Pointer(uintptr(0)) 54 | 55 | m_hBmp := CreateDIBSection(m_hDC, &bt, DIB_RGB_COLORS, &ptr, 0, 0) 56 | if m_hBmp == 0 { 57 | return nil, fmt.Errorf("Could not Create DIB Section err:%d.\n", GetLastError()) 58 | } 59 | if m_hBmp == InvalidParameter { 60 | return nil, fmt.Errorf("One or more of the input parameters is invalid while calling CreateDIBSection.\n") 61 | } 62 | defer DeleteObject(HGDIOBJ(m_hBmp)) 63 | 64 | obj := SelectObject(m_hDC, HGDIOBJ(m_hBmp)) 65 | if obj == 0 { 66 | return nil, fmt.Errorf("error occurred and the selected object is not a region err:%d.\n", GetLastError()) 67 | } 68 | if obj == 0xffffffff { //GDI_ERROR 69 | return nil, fmt.Errorf("GDI_ERROR while calling SelectObject err:%d.\n", GetLastError()) 70 | } 71 | defer DeleteObject(obj) 72 | 73 | if !BitBlt(m_hDC, 0, 0, x, y, hDC, rect.Min.X, rect.Min.Y, SRCCOPY) { 74 | return nil, fmt.Errorf("BitBlt failed err:%d.\n", GetLastError()) 75 | } 76 | 77 | var slice []byte 78 | hdrp := (*reflect.SliceHeader)(unsafe.Pointer(&slice)) 79 | hdrp.Data = uintptr(ptr) 80 | hdrp.Len = x * y * 4 81 | hdrp.Cap = x * y * 4 82 | 83 | imageBytes := make([]byte, len(slice)) 84 | 85 | for i := 0; i < len(imageBytes); i += 4 { 86 | imageBytes[i], imageBytes[i+2], imageBytes[i+1], imageBytes[i+3] = slice[i+2], slice[i], slice[i+1], slice[i+3] 87 | } 88 | 89 | img := &image.RGBA{imageBytes, 4 * x, image.Rect(0, 0, x, y)} 90 | return img, nil 91 | } 92 | 93 | func GetDeviceCaps(hdc HDC, index int) int { 94 | ret, _, _ := procGetDeviceCaps.Call( 95 | uintptr(hdc), 96 | uintptr(index)) 97 | 98 | return int(ret) 99 | } 100 | 101 | func GetDC(hwnd HWND) HDC { 102 | ret, _, _ := procGetDC.Call( 103 | uintptr(hwnd)) 104 | 105 | return HDC(ret) 106 | } 107 | 108 | func ReleaseDC(hwnd HWND, hDC HDC) bool { 109 | ret, _, _ := procReleaseDC.Call( 110 | uintptr(hwnd), 111 | uintptr(hDC)) 112 | 113 | return ret != 0 114 | } 115 | 116 | func DeleteDC(hdc HDC) bool { 117 | ret, _, _ := procDeleteDC.Call( 118 | uintptr(hdc)) 119 | 120 | return ret != 0 121 | } 122 | 123 | func GetLastError() uint32 { 124 | ret, _, _ := procGetLastError.Call() 125 | return uint32(ret) 126 | } 127 | 128 | func BitBlt(hdcDest HDC, nXDest, nYDest, nWidth, nHeight int, hdcSrc HDC, nXSrc, nYSrc int, dwRop uint) bool { 129 | ret, _, _ := procBitBlt.Call( 130 | uintptr(hdcDest), 131 | uintptr(nXDest), 132 | uintptr(nYDest), 133 | uintptr(nWidth), 134 | uintptr(nHeight), 135 | uintptr(hdcSrc), 136 | uintptr(nXSrc), 137 | uintptr(nYSrc), 138 | uintptr(dwRop)) 139 | 140 | return ret != 0 141 | } 142 | 143 | func SelectObject(hdc HDC, hgdiobj HGDIOBJ) HGDIOBJ { 144 | ret, _, _ := procSelectObject.Call( 145 | uintptr(hdc), 146 | uintptr(hgdiobj)) 147 | 148 | if ret == 0 { 149 | panic("SelectObject failed") 150 | } 151 | 152 | return HGDIOBJ(ret) 153 | } 154 | 155 | func DeleteObject(hObject HGDIOBJ) bool { 156 | ret, _, _ := procDeleteObject.Call( 157 | uintptr(hObject)) 158 | 159 | return ret != 0 160 | } 161 | 162 | func CreateDIBSection(hdc HDC, pbmi *BITMAPINFO, iUsage uint, ppvBits *unsafe.Pointer, hSection HANDLE, dwOffset uint) HBITMAP { 163 | ret, _, _ := procCreateDIBSection.Call( 164 | uintptr(hdc), 165 | uintptr(unsafe.Pointer(pbmi)), 166 | uintptr(iUsage), 167 | uintptr(unsafe.Pointer(ppvBits)), 168 | uintptr(hSection), 169 | uintptr(dwOffset)) 170 | 171 | return HBITMAP(ret) 172 | } 173 | 174 | func CreateCompatibleDC(hdc HDC) HDC { 175 | ret, _, _ := procCreateCompatibleDC.Call( 176 | uintptr(hdc)) 177 | 178 | if ret == 0 { 179 | panic("Create compatible DC failed") 180 | } 181 | 182 | return HDC(ret) 183 | } 184 | 185 | type ( 186 | HANDLE uintptr 187 | HWND HANDLE 188 | HGDIOBJ HANDLE 189 | HDC HANDLE 190 | HBITMAP HANDLE 191 | ) 192 | 193 | type BITMAPINFO struct { 194 | BmiHeader BITMAPINFOHEADER 195 | BmiColors *RGBQUAD 196 | } 197 | 198 | type BITMAPINFOHEADER struct { 199 | BiSize uint32 200 | BiWidth int32 201 | BiHeight int32 202 | BiPlanes uint16 203 | BiBitCount uint16 204 | BiCompression uint32 205 | BiSizeImage uint32 206 | BiXPelsPerMeter int32 207 | BiYPelsPerMeter int32 208 | BiClrUsed uint32 209 | BiClrImportant uint32 210 | } 211 | 212 | type RGBQUAD struct { 213 | RgbBlue byte 214 | RgbGreen byte 215 | RgbRed byte 216 | RgbReserved byte 217 | } 218 | 219 | const ( 220 | HORZRES = 8 221 | VERTRES = 10 222 | DESKTOPHORZRES = 118 223 | DESKTOPVERTRES = 117 224 | BI_RGB = 0 225 | InvalidParameter = 2 226 | DIB_RGB_COLORS = 0 227 | SRCCOPY = 0x00CC0020 228 | ) 229 | 230 | var ( 231 | modgdi32 = syscall.NewLazyDLL("gdi32.dll") 232 | moduser32 = syscall.NewLazyDLL("user32.dll") 233 | modkernel32 = syscall.NewLazyDLL("kernel32.dll") 234 | procGetDC = moduser32.NewProc("GetDC") 235 | procReleaseDC = moduser32.NewProc("ReleaseDC") 236 | procDeleteDC = modgdi32.NewProc("DeleteDC") 237 | procBitBlt = modgdi32.NewProc("BitBlt") 238 | procDeleteObject = modgdi32.NewProc("DeleteObject") 239 | procSelectObject = modgdi32.NewProc("SelectObject") 240 | procCreateDIBSection = modgdi32.NewProc("CreateDIBSection") 241 | procCreateCompatibleDC = modgdi32.NewProc("CreateCompatibleDC") 242 | procGetDeviceCaps = modgdi32.NewProc("GetDeviceCaps") 243 | procGetLastError = modkernel32.NewProc("GetLastError") 244 | ) 245 | --------------------------------------------------------------------------------