Skip to content

Commit 47a09b1

Browse files
author
BoboTiG
committed
First try with MSS library (all good but loading and setup)
1 parent 526e586 commit 47a09b1

6 files changed

Lines changed: 136 additions & 55 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
build/
2+
dep/linux/mss.so
23
dist/
34
MANIFEST*
45
*.png

dep/linux/mss.c

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
2+
/*
3+
* This is part of the MSS python's module.
4+
* This will be compiled into mss.so and then
5+
* you can call it from ctypes.
6+
*
7+
* See MSSLinux:get_pixels() for a real world example.
8+
*
9+
* Source: https://github.com/BoboTiG/python-mss
10+
*
11+
* Build: python setyp.py build_ext
12+
* or: gcc -shared -rdynamic -fPIC -s -O3 -lX11 -o mss.so mss.c
13+
*
14+
*/
15+
16+
#include <X11/Xlib.h>
17+
18+
/* Prototype from Xutil.h */
19+
extern unsigned long XGetPixel(XImage *ximage, int x, int y);
20+
21+
void GetXImagePixels(
22+
XImage *ximage,
23+
const unsigned int width,
24+
const unsigned int height,
25+
const unsigned int red_mask,
26+
const unsigned int green_mask,
27+
const unsigned int blue_mask,
28+
unsigned char *pixels
29+
) {
30+
unsigned int x, y, offset;
31+
unsigned long pixel;
32+
33+
for ( x = 0; x < width; ++x ) {
34+
for ( y = 0; y < height; ++y ) {
35+
offset = x * 3 + width * y * 3;
36+
pixel = XGetPixel(ximage, x, y);
37+
pixels[offset] = (pixel & red_mask) >> 16;
38+
pixels[offset + 1] = (pixel & green_mask) >> 8;
39+
pixels[offset + 2] = pixel & blue_mask;
40+
}
41+
}
42+
return;
43+
}

doc/CHANGELOG

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,15 @@ History:
55
devel 201y/mm/dd
66
-
77

8+
2.0.0 2015/04/16
9+
- fix README issues link
10+
- fix invalid classifier "Programming Language :: Python :: 3.5"
11+
- prevent export of MSS* classes in case of "from mss import *"
12+
- MSS: rename save_img() to to_png()
13+
- MSS: optimization of to_png()
14+
- MSSWindows: get_pixels() insanely fast
15+
- MSSWindows: a fix to work on Windows 8.1
16+
817
1.0.0 2015/04/16
918
- Python 2.6 to 3.5 ready
1019
- code purgation and review, no more debug informations

doc/QUALITY

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,6 @@ pep8 --hang-closing mss.py
99
codespell mss.py
1010
codespell README.rst
1111
codespell doc/*
12+
13+
# Dry run
14+
python setup.py check

mss.py

Lines changed: 70 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,8 @@ class ScreenshotError(Exception):
4545
from os import environ
4646
from ctypes.util import find_library
4747
from ctypes import byref, cast, cdll, POINTER, Structure, c_char_p,\
48-
c_int, c_int32, c_long, c_uint, c_uint32, c_ulong, c_ushort, c_void_p
48+
c_int, c_int32, c_long, c_uint, c_uint32, c_ulong, c_ushort, c_void_p, \
49+
create_string_buffer
4950

5051
class Display(Structure):
5152
pass
@@ -144,8 +145,6 @@ def get_pixels(self, monitor):
144145
'width': the width,
145146
'heigth': the height
146147
}
147-
148-
Returns a dict of pixels.
149148
'''
150149
raise NotImplementedError('MSS: subclasses need to implement this!')
151150

@@ -324,6 +323,13 @@ def __init__(self):
324323
raise ScreenshotError('MSS: no Xrandr library found.')
325324
self.xrandr = cdll.LoadLibrary(xrandr)
326325

326+
mss = find_library('libmss.so')
327+
self.mss = False
328+
if mss:
329+
self.mss = cdll.LoadLibrary(mss)
330+
else:
331+
print('MSS: no MSS library found. Using native function (slow).')
332+
327333
self._set_argtypes()
328334
self._set_restypes()
329335

@@ -363,6 +369,10 @@ def _set_argtypes(self):
363369
POINTER(XRRScreenResources)
364370
]
365371
self.xrandr.XRRFreeCrtcInfo.argtypes = [POINTER(XRRCrtcInfo)]
372+
if self.mss:
373+
self.mss.GetXImagePixels.argtypes = [POINTER(XImage), c_uint,
374+
c_uint, c_uint, c_uint,
375+
c_uint, c_void_p]
366376

367377
def _set_restypes(self):
368378
''' Functions return type.
@@ -385,6 +395,8 @@ def _set_restypes(self):
385395
self.xrandr.XRRGetCrtcInfo.restype = POINTER(XRRCrtcInfo)
386396
self.xrandr.XRRFreeScreenResources.restype = c_void_p
387397
self.xrandr.XRRFreeCrtcInfo.restype = c_void_p
398+
if self.mss:
399+
self.mss.GetXImagePixels.restype = c_void_p
388400

389401
def enum_display_monitors(self, screen=0):
390402
''' Get positions of one or more monitors.
@@ -394,12 +406,10 @@ def enum_display_monitors(self, screen=0):
394406
if screen == -1:
395407
gwa = XWindowAttributes()
396408
self.xlib.XGetWindowAttributes(self.display, self.root, byref(gwa))
397-
yield ({
398-
b'left': int(gwa.x),
399-
b'top': int(gwa.y),
400-
b'width': int(gwa.width),
401-
b'height': int(gwa.height)
402-
})
409+
yield {b'left': int(gwa.x),
410+
b'top': int(gwa.y),
411+
b'width': int(gwa.width),
412+
b'height': int(gwa.height)}
403413
else:
404414
# Fix for XRRGetScreenResources:
405415
# expected LP_Display instance instead of LP_XWindowAttributes
@@ -408,18 +418,51 @@ def enum_display_monitors(self, screen=0):
408418
for num in range(mon.contents.ncrtc):
409419
crtc = self.xrandr.XRRGetCrtcInfo(self.display, mon,
410420
mon.contents.crtcs[num])
411-
yield ({
412-
b'left': int(crtc.contents.x),
413-
b'top': int(crtc.contents.y),
414-
b'width': int(crtc.contents.width),
415-
b'height': int(crtc.contents.height)
416-
})
421+
yield {b'left': int(crtc.contents.x),
422+
b'top': int(crtc.contents.y),
423+
b'width': int(crtc.contents.width),
424+
b'height': int(crtc.contents.height)}
417425
self.xrandr.XRRFreeCrtcInfo(crtc)
418426
self.xrandr.XRRFreeScreenResources(mon)
419427

420428
def get_pixels(self, monitor):
429+
''' Retrieve all pixels from a monitor. Pixels have to be RGB. '''
430+
431+
width, height = monitor[b'width'], monitor[b'height']
432+
left, top = monitor[b'left'], monitor[b'top']
433+
ZPixmap = 2
434+
allplanes = self.xlib.XAllPlanes()
435+
436+
# Fix for XGetImage:
437+
# expected LP_Display instance instead of LP_XWindowAttributes
438+
root = cast(self.root, POINTER(Display))
439+
440+
ximage = self.xlib.XGetImage(self.display, root, left, top, width,
441+
height, allplanes, ZPixmap)
442+
if not ximage:
443+
raise ScreenshotError('MSS: XGetImage() failed.')
444+
445+
if not self.mss:
446+
self.image = self.get_pixels_slow(ximage, width, height,
447+
ximage.contents.red_mask,
448+
ximage.contents.green_mask,
449+
ximage.contents.blue_mask)
450+
else:
451+
buffer_len = height * width * 3
452+
self.image = create_string_buffer(buffer_len)
453+
self.mss.GetXImagePixels(ximage, width, height,
454+
ximage.contents.red_mask,
455+
ximage.contents.green_mask,
456+
ximage.contents.blue_mask,
457+
self.image)
458+
self.xlib.XDestroyImage(ximage)
459+
return self.image
460+
461+
def get_pixels_slow(self, ximage, width, height, rmask, gmask, bmask):
421462
''' Retrieve all pixels from a monitor. Pixels have to be RGB.
422463
464+
/!\ Insanely slow version using ctypes.
465+
423466
The XGetPixel() C code can be found at this URL:
424467
http://cgit.freedesktop.org/xorg/lib/libX11/tree/src/ImUtil.c#n444
425468
@@ -451,29 +494,15 @@ def get_pixels(self, monitor):
451494
self.image = create_string_buffer(sizeof(c_char) * buffer_len)
452495
for x in range(width):
453496
for y in range(height):
454-
offset = width * y * 3
497+
offset = x * 3 + width * y * 3
455498
addr = data[y * bpl + (x << 2)][0]
456499
pixel = addr[3] << 24 | addr[2] << 16 | addr[1] << 8 | addr[0]
457-
self.image[x * 3 + offset] = (pixel & rmask) >> 16
458-
self.image[x * 3 + offset + 1] = (pixel & gmask) >> 8
459-
self.image[x * 3 + offset + 2] = pixel & bmask
500+
self.image[offset] = (pixel & rmask) >> 16
501+
self.image[offset + 1] = (pixel & gmask) >> 8
502+
self.image[offset + 2] = pixel & bmask
460503
return self.image
461504
'''
462505

463-
width, height = monitor[b'width'], monitor[b'height']
464-
left, top = monitor[b'left'], monitor[b'top']
465-
ZPixmap = 2
466-
allplanes = self.xlib.XAllPlanes()
467-
468-
# Fix for XGetImage:
469-
# expected LP_Display instance instead of LP_XWindowAttributes
470-
root = cast(self.root, POINTER(Display))
471-
472-
ximage = self.xlib.XGetImage(self.display, root, left, top, width,
473-
height, allplanes, ZPixmap)
474-
if not ximage:
475-
raise ScreenshotError('MSS: XGetImage() failed.')
476-
477506
# @TODO: this part takes most of the time. Need a better solution.
478507
def pix(pixel, _resultats={}, _b=pack):
479508
''' Apply shifts to a pixel to get the RGB values.
@@ -484,14 +513,10 @@ def pix(pixel, _resultats={}, _b=pack):
484513
_b(b'<B', (pixel & gmask) >> 8) + _b(b'<B', pixel & bmask)
485514
return _resultats[pixel]
486515

487-
rmask = ximage.contents.red_mask
488-
gmask = ximage.contents.green_mask
489-
bmask = ximage.contents.blue_mask
490516
get_pix = self.xlib.XGetPixel
491517
pixels = [pix(get_pix(ximage, x, y))
492518
for y in range(height) for x in range(width)]
493519
self.image = b''.join(pixels)
494-
self.xlib.XDestroyImage(ximage)
495520
return self.image
496521

497522

@@ -548,25 +573,21 @@ def enum_display_monitors(self, screen=-1):
548573
right = windll.user32.GetSystemMetrics(SM_CXVIRTUALSCREEN)
549574
top = windll.user32.GetSystemMetrics(SM_YVIRTUALSCREEN)
550575
bottom = windll.user32.GetSystemMetrics(SM_CYVIRTUALSCREEN)
551-
yield ({
552-
b'left': int(left),
553-
b'top': int(top),
554-
b'width': int(right - left),
555-
b'height': int(bottom - top)
556-
})
576+
yield {b'left': int(left),
577+
b'top': int(top),
578+
b'width': int(right - left),
579+
b'height': int(bottom - top)}
557580
else:
558581

559582
def _callback(monitor, dc, rect, data):
560583
''' Callback for MONITORENUMPROC() function, it will return
561584
a RECT with appropriate values.
562585
'''
563586
rct = rect.contents
564-
monitors.append({
565-
b'left': int(rct.left),
566-
b'top': int(rct.top),
567-
b'width': int(rct.right - rct.left),
568-
b'height': int(rct.bottom - rct.top)
569-
})
587+
monitors.append({b'left': int(rct.left),
588+
b'top': int(rct.top),
589+
b'width': int(rct.right - rct.left),
590+
b'height': int(rct.bottom - rct.top)})
570591
return 1
571592

572593
monitors = []

setup.py

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,18 @@
11

2-
try:
3-
from distutils.core import setup
4-
except ImportError:
5-
from setuptools import setup
2+
from distutils.core import setup, Extension
3+
from platform import system
4+
from mss import __version__
5+
66

77
open('MANIFEST.in', 'w').write("\n".join((
88
'include *.rst',
99
'include doc/*'
1010
)))
1111

12-
from mss import __version__
12+
libmss = False
13+
if system() == 'Linux':
14+
libmss = [Extension('libmss', sources = ['dep/linux/mss.c'],
15+
libraries = ['X11'])]
1316

1417
setup(
1518
name='mss',
@@ -57,6 +60,7 @@
5760
'Topic :: Software Development :: Libraries :: Python Modules',
5861
'Topic :: Utilities'
5962
],
60-
url='https://github.com/BoboTiG/python-mss'
63+
url='https://github.com/BoboTiG/python-mss',
64+
ext_modules = libmss
6165
)
6266

0 commit comments

Comments
 (0)