Skip to content

Commit 0f81477

Browse files
author
BoboTiG
committed
MSSWindows: get_pixels() optimised
1 parent 0bb4cf8 commit 0f81477

File tree

1 file changed

+31
-63
lines changed

1 file changed

+31
-63
lines changed

mss.py

Lines changed: 31 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -88,8 +88,8 @@ class XRRCrtcInfo(Structure):
8888
('outputs', POINTER(c_long)), ('rotations', c_ushort),
8989
('npossible', c_int), ('possible', POINTER(c_long))]
9090
elif system() == 'Windows':
91-
from ctypes import byref, c_void_p, create_string_buffer, pointer, \
92-
sizeof, windll, Structure, POINTER, WINFUNCTYPE
91+
from ctypes import c_void_p, create_string_buffer, sizeof, \
92+
windll, Structure, POINTER, WINFUNCTYPE
9393
from ctypes.wintypes import BOOL, DOUBLE, DWORD, HBITMAP, HDC, \
9494
HGDIOBJ, HWND, INT, LPARAM, LONG, RECT, UINT, WORD
9595

@@ -442,10 +442,7 @@ def enum_display_monitors(self, screen=0):
442442
self.xrandr.XRRFreeScreenResources(mon)
443443

444444
def get_pixels(self, monitor):
445-
''' Retrieve all pixels from a monitor. Pixels have to be RGB.
446-
447-
@TODO: this function takes the most time. Need better solution.
448-
'''
445+
''' Retrieve all pixels from a monitor. Pixels have to be RGB. '''
449446

450447
self.debug('get_pixels')
451448

@@ -465,6 +462,7 @@ def get_pixels(self, monitor):
465462
if not ximage:
466463
raise ScreenshotError('MSS: XGetImage() failed.')
467464

465+
# @TODO: this part takes most of the time. Need a better solution.
468466
def pix(pixel, _resultats={}, b=pack):
469467
''' Apply shifts to a pixel to get the RGB values.
470468
This method uses of memoization.
@@ -573,44 +571,50 @@ def _callback(monitor, dc, rect, data):
573571
yield mon
574572

575573
def get_pixels(self, monitor):
576-
''' Retrieve all pixels from a monitor. Pixels have to be RGB. '''
574+
''' Retrieve all pixels from a monitor. Pixels have to be RGB.
575+
576+
[1] A bottom-up DIB is specified by setting the height to a positive number,
577+
while a top-down DIB is specified by setting the height to a negative number.
578+
https://msdn.microsoft.com/en-us/library/ms787796.aspx
579+
https://msdn.microsoft.com/en-us/library/dd144879%28v=vs.85%29.aspx
580+
'''
577581

578582
self.debug('get_pixels')
579583

580584
width, height = monitor[b'width'], monitor[b'height']
581585
left, top = monitor[b'left'], monitor[b'top']
582-
good_width = (width * 3 + 3) & -4
583586
SRCCOPY = 0xCC0020
584-
DIB_RGB_COLORS = 0
587+
DIB_RGB_COLORS = BI_RGB = 0
585588
srcdc = memdc = bmp = None
586589

587590
try:
591+
bmi = BITMAPINFO()
592+
bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER)
593+
bmi.bmiHeader.biWidth = width
594+
bmi.bmiHeader.biHeight = -height # Why minus? See [1]
595+
bmi.bmiHeader.biPlanes = 1 # Always 1
596+
bmi.bmiHeader.biBitCount = 24
597+
bmi.bmiHeader.biCompression = BI_RGB;
598+
buffer_len = height * width * 3
599+
self.image = create_string_buffer(buffer_len)
588600
srcdc = windll.user32.GetWindowDC(0)
589601
memdc = windll.gdi32.CreateCompatibleDC(srcdc)
590602
bmp = windll.gdi32.CreateCompatibleBitmap(srcdc, width, height)
591603
windll.gdi32.SelectObject(memdc, bmp)
592604
windll.gdi32.BitBlt(memdc, 0, 0, width, height, srcdc, left, top,
593605
SRCCOPY)
594-
bmi = BITMAPINFO()
595-
bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER)
596-
bmi.bmiHeader.biWidth = width
597-
bmi.bmiHeader.biHeight = height
598-
bmi.bmiHeader.biBitCount = 24
599-
bmi.bmiHeader.biPlanes = 1
600-
buffer_len = height * good_width
601-
pixels = create_string_buffer(buffer_len)
602-
bits = windll.gdi32.GetDIBits(memdc, bmp, 0, height, byref(pixels),
603-
pointer(bmi), DIB_RGB_COLORS)
606+
bits = windll.gdi32.GetDIBits(memdc, bmp, 0, height, self.image,
607+
bmi, DIB_RGB_COLORS)
604608

605609
self.debug('get_pixels', 'srcdc', srcdc)
606610
self.debug('get_pixels', 'memdc', memdc)
607611
self.debug('get_pixels', 'bmp', bmp)
608612
self.debug('get_pixels', 'buffer_len', buffer_len)
613+
self.debug('get_pixels', 'len(self.image)', len(self.image))
609614
self.debug('get_pixels', 'bits', bits)
610-
self.debug('get_pixels', 'len(pixels.raw)', len(pixels.raw))
611615

612-
if bits != height or len(pixels.raw) != buffer_len:
613-
raise ScreenshotError('GetDIBits() failed.')
616+
if bits != height:
617+
raise ScreenshotError('MSS: GetDIBits() failed.')
614618
finally:
615619
# Clean up
616620
if srcdc:
@@ -620,47 +624,11 @@ def get_pixels(self, monitor):
620624
if bmp:
621625
windll.gdi32.DeleteObject(bmp)
622626

623-
# Note that the origin of the returned image is in the
624-
# bottom-left corner, 32-bit aligned. And it is BGR.
625-
# Need to "arrange" that.
626-
return self._arrange(pixels.raw, good_width, height)
627-
628-
def _arrange(self, data, width, height):
629-
''' Reorganises data when the origin of the image is in the
630-
bottom-left corner and converts BGR triple to RGB. '''
631-
632-
self.debug('_arrange')
633-
634-
total = width * height
635-
scanlines = [b'0'] * total
636-
# Here we do the same thing but in Python 3, the use of struct.pack
637-
# slowns down the process by a factor of 2.5 or more.
638-
if sys.version < '3':
639-
for y in range(height):
640-
shift = width * (y + 1)
641-
offset = total - shift
642-
for x in range(0, width - 2, 3):
643-
off = offset + x
644-
scanlines[shift + x:shift + x + 3] = \
645-
data[off + 2], data[off + 1], data[off]
646-
else:
647-
def pix(pixel, _resultats={}, b=pack):
648-
''' Apply conversion to a pixel to get the right value.
649-
This method uses of memoization.
650-
'''
651-
if pixel not in _resultats:
652-
_resultats[pixel] = b(b'<B', pixel)
653-
return _resultats[pixel]
654-
655-
for y in range(height):
656-
shift = width * (y + 1)
657-
offset = total - shift
658-
for x in range(0, width - 2, 3):
659-
off = offset + x
660-
scanlines[shift + x:shift + x + 3] = \
661-
pix(data[off + 2]), pix(data[off + 1]), pix(data[off])
662-
663-
return b''.join(scanlines)
627+
# Replace pixels values: BGR to RGB
628+
# @TODO: this part takes most of the time. Need a better solution.
629+
for idx in range(0, buffer_len - 2, 3):
630+
self.image[idx + 2], self.image[idx] = self.image[idx], self.image[idx + 2]
631+
return self.image
664632

665633

666634
def main():

0 commit comments

Comments
 (0)