Skip to content

Commit 153f062

Browse files
committed
Linux: get rid of libmss, insanely fast using only ctypes :)
1 parent 7fe0475 commit 153f062

13 files changed

Lines changed: 35 additions & 201 deletions

File tree

CHANGELOG

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ History:
55
dev
66
- change license to MIT
77
- add documentation (fix #10)
8-
- Linux: MSS library optional at installation
9-
- Linux: optimization of get_pixels_slow()
8+
- Linux: remove MSS library
9+
- Linux: insanely fast using only ctypes
1010

1111
2.0.0 2016/06/04
1212
- split the module into several files

docs/source/api.rst

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,14 @@ Methods
2525

2626
.. class:: MSSBase
2727

28+
.. method:: bgra_to_rgb(raw) -> bytes
29+
30+
:param mixed raw: raw data containing BGRA values.
31+
32+
It converts pixels values from BGRA to RGB.
33+
This is the method called to populate :attr:`image` into :attr:`get_pixels`.
34+
35+
2836
.. method:: enum_display_monitors(force=False) -> list(dict)
2937

3038
:param bool force: force rescan of monitors informations.
@@ -53,7 +61,7 @@ Methods
5361
:param dict monitor: monitor's informations generated by :attr:`enum_display_monitors()`.
5462
:exception NotImplementedError: Subclasses need to implement this.
5563

56-
Retrieve screen pixels for a given monitor.
64+
Retrieve screen pixels for a given monitor.
5765
This method has to define :attr:`width` and :attr:`height`.
5866
It stocks pixels data into :attr:`image` (RGB) and returns it.
5967

docs/source/installation.rst

Lines changed: 1 addition & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -19,25 +19,6 @@ Alternatively, you can get a copy of the module from GitHub::
1919
cd python-mss
2020

2121

22-
Optional dependency
23-
-------------------
24-
25-
The MSS library is already compiled for 32 and 64 bits architectures but you can build for your system::
26-
27-
cd mss/linux
28-
sh build.sh
29-
cd ../..
30-
31-
The resulting file will be located into ``mss/linux/$ARCH/libmss.so`` and will be used for the next step.
32-
33-
34-
Installation
35-
------------
36-
37-
Install them module::
22+
And then::
3823

3924
sudo python setup.py install
40-
41-
You can avoid the MSS library dependency by specifying the `--no-dependency` argument::
42-
43-
sudo python setup.py install --no-dependency

mss/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
from .exception import ScreenshotError
1616
from .factory import mss
1717

18-
__version__ = '2.0.5'
18+
__version__ = '2.0.6'
1919
__author__ = "Mickaël 'Tiger-222' Schoentgen"
2020
__copyright__ = '''
2121
Copyright (c) 2013-2016, Mickaël 'Tiger-222' Schoentgen

mss/base.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,13 @@ def __enter__(self):
2727
def __exit__(self, exc_type, exc_value, traceback):
2828
''' For the cool call `with MSS() as mss:`. '''
2929

30+
def bgra_to_rgb(self, raw):
31+
''' Converts pixels values from BGRA to RGB. '''
32+
33+
image = bytearray(self.height * self.width * 3)
34+
image[0::3], image[1::3], image[2::3] = raw[2::4], raw[1::4], raw[0::4]
35+
return bytes(image)
36+
3037
def enum_display_monitors(self, force=False):
3138
''' Get positions of one or more monitors.
3239
If the monitor has rotation, you have to deal with it

mss/darwin.py

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -158,11 +158,5 @@ def get_pixels(self, monitor):
158158
buf_len = self.height * self.width * 4 # or CFDataGetLength()
159159
data = cast(data_ref, POINTER(c_ubyte * buf_len))
160160
self.core.CGDataProviderRelease(prov)
161-
162-
# Replace pixels values: BGRA to RGB.
163-
image_data = bytearray(data.contents)
164-
image = bytearray(self.height * self.width * 3)
165-
image[0::3], image[1::3], image[2::3] = \
166-
image_data[2::4], image_data[1::4], image_data[0::4]
167-
self.image = bytes(image)
161+
self.image = self.bgra_to_rgb(bytearray(data.contents))
168162
return self.image

mss/linux.py

Lines changed: 8 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,10 @@
55
'''
66

77
from ctypes import (
8-
POINTER, Structure, byref, c_char_p, c_int, c_int32, c_long, c_uint,
9-
c_uint32, c_ulong, c_ushort, c_void_p, cast, cdll, create_string_buffer)
8+
POINTER, Structure, byref, c_char_p, c_int, c_int32, c_long, c_ubyte,
9+
c_uint, c_uint32, c_ulong, c_ushort, c_void_p, cast, cdll)
1010
from ctypes.util import find_library
1111
from os import environ
12-
from os.path import abspath, dirname, isfile, realpath
13-
from struct import pack
1412
from sys import maxsize, version
1513

1614
from .base import MSSBase
@@ -85,11 +83,7 @@ class MSS(MSSBase):
8583
It uses intensively the Xlib and Xrandr extension.
8684
'''
8785

88-
# pylint: disable=too-many-instance-attributes
89-
9086
display = None
91-
use_mss = False
92-
mss = None
9387
xlib = None
9488
xrandr = None
9589
display = None
@@ -127,15 +121,6 @@ def __init__(self, display=None):
127121
raise ScreenshotError('No Xrandr extension found.')
128122
self.xrandr = cdll.LoadLibrary(xrandr)
129123

130-
# libmss = find_library('mss')
131-
libmss = '{0}/linux/{1}/libmss.so'.format(
132-
dirname(realpath(abspath(__file__))), arch())
133-
if isfile(libmss):
134-
self.mss = cdll.LoadLibrary(libmss)
135-
self.use_mss = True
136-
# else:
137-
# print('No MSS library found. Using slow native function.')
138-
139124
self._set_argtypes()
140125
self._set_restypes()
141126

@@ -148,13 +133,7 @@ def __init__(self, display=None):
148133
self.root = self.xlib.XDefaultRootWindow(self.display, screen)
149134

150135
def _set_argtypes(self):
151-
''' Functions arguments.
152-
153-
Curiously, if we set up XGetPixel arguments type,
154-
the entire process takes twice more time.
155-
So, no need to waste this precious time :)
156-
Note: this issue does not occur when using libmss.
157-
'''
136+
''' Functions arguments. '''
158137

159138
self.xlib.XOpenDisplay.argtypes = [c_char_p]
160139
self.xlib.XDefaultScreen.argtypes = [POINTER(Display)]
@@ -166,7 +145,6 @@ def _set_argtypes(self):
166145
self.xlib.XGetImage.argtypes = [POINTER(Display), POINTER(Display),
167146
c_int, c_int, c_uint, c_uint, c_ulong,
168147
c_int]
169-
# self.xlib.XGetPixel.argtypes = [POINTER(XImage), c_int, c_int]
170148
self.xlib.XDestroyImage.argtypes = [POINTER(XImage)]
171149
self.xlib.XCloseDisplay.argtypes = [POINTER(Display)]
172150
self.xrandr.XRRGetScreenResources.argtypes = [POINTER(Display),
@@ -177,17 +155,9 @@ def _set_argtypes(self):
177155
self.xrandr.XRRFreeScreenResources.argtypes = \
178156
[POINTER(XRRScreenResources)]
179157
self.xrandr.XRRFreeCrtcInfo.argtypes = [POINTER(XRRCrtcInfo)]
180-
if self.use_mss:
181-
self.mss.GetXImagePixels.argtypes = [POINTER(XImage), c_void_p]
182158

183159
def _set_restypes(self):
184-
''' Functions return type.
185-
186-
Curiously, if we set up XGetPixel return type,
187-
the entire process takes a little more time.
188-
So, no need to waste this precious time :)
189-
Note: this issue does not occur when using libmss.
190-
'''
160+
''' Functions return type. '''
191161

192162
def validate(value):
193163
''' Validate the returned value of xrandr.XRRGetScreenResources().
@@ -207,16 +177,13 @@ def validate(value):
207177
self.xlib.XGetWindowAttributes.restype = c_int
208178
self.xlib.XAllPlanes.restype = c_ulong
209179
self.xlib.XGetImage.restype = POINTER(XImage)
210-
# self.xlib.XGetPixel.restype = c_ulong
211180
self.xlib.XDestroyImage.restype = c_void_p
212181
self.xlib.XCloseDisplay.restype = c_void_p
213182
self.xlib.XDefaultRootWindow.restype = POINTER(XWindowAttributes)
214183
self.xrandr.XRRGetScreenResources.restype = validate
215184
self.xrandr.XRRGetCrtcInfo.restype = POINTER(XRRCrtcInfo)
216185
self.xrandr.XRRFreeScreenResources.restype = c_void_p
217186
self.xrandr.XRRFreeCrtcInfo.restype = c_void_p
218-
if self.use_mss:
219-
self.mss.GetXImagePixels.restype = c_int
220187

221188
def enum_display_monitors(self, force=False):
222189
''' Get positions of monitors (see parent class). '''
@@ -277,52 +244,10 @@ def get_pixels(self, monitor):
277244
err = err.strip(', ')
278245
raise ScreenshotError(err)
279246

280-
if not self.use_mss:
281-
self.get_pixels_slow(ximage)
282-
else:
283-
image = create_string_buffer(self.height * self.width * 3)
284-
ret = self.mss.GetXImagePixels(ximage, image)
285-
if not ret:
286-
self.xlib.XDestroyImage(ximage)
287-
err = 'libmss.GetXImagePixels() failed (retcode={0}).'
288-
raise ScreenshotError(err.format(ret))
289-
self.image = bytes(bytearray(image))
290-
self.xlib.XDestroyImage(ximage)
291-
return self.image
292-
293-
def get_pixels_slow(self, ximage):
294-
''' Retrieve all pixels from a monitor. Pixels have to be RGB.
295-
296-
(!) This version is damn slow, but if you consider that ~1 second
297-
for a screenshot of decent monitor is not too long, you can
298-
continue. There should be few things to tweak here to gain speed.
299-
'''
300-
301-
# @TODO: this part takes most of the time. Need a better solution.
302-
def pix(pixel, _resultats={}, p__=pack):
303-
''' Apply shifts to a pixel to get the RGB values.
304-
This method uses of memoization.
305-
'''
306-
307-
# pylint: disable=dangerous-default-value
308-
309-
try:
310-
return _resultats[pixel]
311-
except KeyError:
312-
_resultats[pixel] = p__('<B', (pixel & rmask) >> 16) + \
313-
p__('<B', (pixel & gmask) >> 8) + \
314-
p__('<B', pixel & bmask)
315-
return _resultats[pixel]
316-
317-
self.width = ximage.contents.width
318-
self.height = ximage.contents.height
319-
rmask = ximage.contents.red_mask
320-
bmask = ximage.contents.blue_mask
321-
gmask = ximage.contents.green_mask
322-
xgetpixel = self.xlib.XGetPixel
323-
pixels = [pix(xgetpixel(ximage, x, y))
324-
for y in range(self.height) for x in range(self.width)]
325-
self.image = b''.join(pixels)
247+
# Replace pixels values: BGRA to RGB.
248+
buf_len = self.height * self.width * 4
249+
data = cast(ximage.contents.data, POINTER(c_ubyte * buf_len))
250+
self.image = self.bgra_to_rgb(bytearray(data.contents))
326251
return self.image
327252

328253

mss/linux/32/libmss.so

-3.1 KB
Binary file not shown.

mss/linux/64/libmss.so

-4.27 KB
Binary file not shown.

mss/linux/build.sh

Lines changed: 0 additions & 29 deletions
This file was deleted.

0 commit comments

Comments
 (0)