Skip to content

Commit 5d3ab7c

Browse files
authored
MSS: Add PNG compression level control
1 parent ea45c61 commit 5d3ab7c

File tree

16 files changed

+109
-22
lines changed

16 files changed

+109
-22
lines changed

CHANGELOG

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ History:
33
<see Git checking messages for history>
44

55
dev 2018/xx/xx
6-
6+
- add PNG compression level control
77

88
3.1.2 2018/01/05
99
- removed support for Python 3.3

CHANGES.rst

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,16 @@
1-
3.1.2 (2018-01-xx)
1+
3.1.3 (2018-xx-xx)
2+
==================
3+
4+
base.py
5+
-------
6+
- Added ``MSSBase.compression_level`` to control the PNG compression level
7+
8+
tools.py
9+
--------
10+
- Changed signature of ``to_png(data, size, output=None)`` to ``to_png(data, size, level=6, output=None)``. ``level`` is the Zlib compression level.
11+
12+
13+
3.1.2 (2018-01-05)
214
==================
315

416
tools.py
@@ -19,9 +31,9 @@ base.py
1931

2032
darwin.py
2133
---------
22-
- Add ``CGPoint.__repr__()``
23-
- Add ``CGRect.__repr__()``
24-
- Add ``CGSize.__repr__()``
34+
- Added ``CGPoint.__repr__()``
35+
- Added ``CGRect.__repr__()``
36+
- Added ``CGSize.__repr__()``
2537
- Removed ``get_infinity()`` function
2638

2739
windows.py
@@ -36,7 +48,7 @@ windows.py
3648
base.py
3749
-------
3850
- Added the ``ScreenShot`` class containing data for a given screen shot (support the Numpy array interface [``ScreenShot.__array_interface__``])
39-
- Add ``shot()`` method to ``MSSBase``. It takes the same arguments as the ``save()`` method.
51+
- Added ``shot()`` method to ``MSSBase``. It takes the same arguments as the ``save()`` method.
4052
- Renamed ``get_pixels`` to ``grab``. It now returns a ``ScreenShot`` object.
4153
- Moved ``to_png`` method to ``tools.py``. It is now a simple function.
4254
- Removed ``enum_display_monitors()`` method. Use ``monitors`` property instead.

docs/source/api.rst

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,18 +110,24 @@ Methods
110110

111111
.. module:: mss.tools
112112

113-
.. method:: to_png(data, size, output=None)
113+
.. method:: to_png(data, size, level=6, output=None)
114114

115115
:param bytes data: RGBRGB...RGB data.
116116
:param tuple size: The (width, height) pair.
117+
:param int level: PNG compression level.
117118
:param str output: output's file name.
118119
:raises ScreenShotError: On error when writing ``data`` to ``output``.
120+
:raises zlib.error: On bad compression ``level``.
119121

120122
Dump data to the image file. Pure Python PNG implementation.
121123
If ``output`` is ``None``, create no file but return the whole PNG data.
122124

123125
.. versionadded:: 3.0.0
124126

127+
.. versionadded:: 3.1.3
128+
129+
Added the ``level`` keyword argument to control the PNG compression level.
130+
125131

126132
Properties
127133
==========

docs/source/conf.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
# built documents.
3232
#
3333
# The short X.Y version.
34-
version = '3.1.2'
34+
version = '3.1.3'
3535

3636
# The full version, including alpha/beta/rc tags.
3737
release = 'latest'

docs/source/examples.rst

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,15 @@ This is an example that uses it, but also using percentage values:
5656

5757
.. versionadded:: 3.1.0
5858

59+
PNG Compression
60+
---------------
61+
62+
You can tweak the PNG compression level (see :py:func:`zlib.compress()` for details)::
63+
64+
sct.compression_level = 2
65+
66+
.. versionadded:: 3.1.3
67+
5968
Advanced
6069
========
6170

docs/source/examples/from_pil_tuple.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,4 +27,4 @@
2727
im = sct.grab(bbox)
2828

2929
# Save it!
30-
mss.tools.to_png(im.rgb, im.size, 'screenshot.png')
30+
mss.tools.to_png(im.rgb, im.size, output='screenshot.png')

docs/source/examples/part_of_screen.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,5 +19,5 @@
1919
sct_img = sct.grab(monitor)
2020

2121
# Save to the picture file
22-
mss.tools.to_png(sct_img.rgb, sct_img.size, output)
22+
mss.tools.to_png(sct_img.rgb, sct_img.size, output=output)
2323
print(output)

docs/source/support.rst

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@ Tested successfully on Pypy 5.1.0 on Windows, but speed is terrible.
2323
Abandoned
2424
=========
2525

26-
- Support for Python 2.6 was dropped on 2016-10-08: too old and introduced optimizations broke it.
27-
- Support for Python 3.0, 3.1, and 3.2 was dropped on 2016-10-08: there is no more tests facilities.
28-
- Support for Python 3.3 was dropped on 2017-12-05: there is no more tests facilities for the documentation.
26+
- 2016-10-08 Support for Python 2.6 was dropped: too old and introduced optimizations broke it.
27+
- 2016-10-08 Support for Python 3.0, 3.1, and 3.2 was dropped: there is no more tests facilities.
28+
- 2017-12-05 Support for Python 3.3 was dropped: there is no more tests facilities for the documentation.
2929

3030
By the way, if you find the *force*, give it a try and tell us if you managed to make one of these versions working (a patch should be quite easy).

docs/source/where.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ AI, Computer Vison
1313
- `Europilot <https://github.com/jsistla/eu-pilot>`_, a self-driving algorithm using Euro Truck Simulator (ETS2);
1414
- `gym-mupen64plus <https://github.com/bzier/gym-mupen64plus>`_, an OpenAI Gym environment wrapper for the Mupen64Plus N64 emulator;
1515
- `Open Source Self Driving Car Initiative <https://github.com/OSSDC/OSSDC-VisionBasedACC>`_;
16+
- `PUBGIS <https://github.com/andrewzwicky/PUBGIS>`_, a map generator of your position throughout PUBG gameplay;
1617
- `Self-Driving-Car-3D-Simulator-With-CNN <https://github.com/sagar448/Self-Driving-Car-3D-Simulator-With-CNN>`_;
1718

1819
Games

mss/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
from .exception import ScreenShotError
1414
from .factory import mss
1515

16-
__version__ = '3.1.2'
16+
__version__ = '3.1.3'
1717
__author__ = "Mickaël 'Tiger-222' Schoentgen"
1818
__copyright__ = """
1919
Copyright (c) 2013-2018, Mickaël 'Tiger-222' Schoentgen

mss/__main__.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ def main(args=None):
2424
cli_args.add_argument('-c', '--coordinates', default='', type=str,
2525
help='the part of the screen to capture:'
2626
' top, left, width, height')
27+
cli_args.add_argument('-l', '--level', default=6, type=int,
28+
choices=list(range(10)),
29+
help='the PNG compression level')
2730
cli_args.add_argument('-m', '--monitor', default=0, type=int,
2831
help='the monitor to screen shot')
2932
cli_args.add_argument('-o', '--output', default='monitor-{mon}.png',
@@ -59,7 +62,10 @@ def main(args=None):
5962
if options.coordinates:
6063
output = kwargs['output'].format(**kwargs['mon'])
6164
sct_img = sct.grab(kwargs['mon'])
62-
to_png(sct_img.rgb, sct_img.size, output)
65+
to_png(sct_img.rgb,
66+
sct_img.size,
67+
level=options.level,
68+
output=output)
6369
if not options.quiet:
6470
print(os.path.realpath(output))
6571
else:

mss/base.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ class MSSBase(object):
1515
""" This class will be overloaded by a system specific one. """
1616

1717
cls_image = ScreenShot # type: object
18+
compression_level = 6 # type: int
1819
_monitors = [] # type: List[Dict[str, int]]
1920

2021
def __enter__(self):
@@ -106,7 +107,10 @@ def save(self, mon=0, output='monitor-{mon}.png', callback=None):
106107
if callable(callback):
107108
callback(fname)
108109
sct = self.grab(monitor)
109-
to_png(sct.rgb, sct.size, fname)
110+
to_png(sct.rgb,
111+
sct.size,
112+
level=self.compression_level,
113+
output=fname)
110114
yield fname
111115
else:
112116
# A screen shot of all monitors together or
@@ -121,7 +125,10 @@ def save(self, mon=0, output='monitor-{mon}.png', callback=None):
121125
if callable(callback):
122126
callback(output)
123127
sct = self.grab(monitor)
124-
to_png(sct.rgb, sct.size, output)
128+
to_png(sct.rgb,
129+
sct.size,
130+
level=self.compression_level,
131+
output=output)
125132
yield output
126133

127134
def shot(self, **kwargs):

mss/linux.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@
1414
__all__ = ('MSS',)
1515

1616

17+
PLAINMASK = 0x00ffffff
18+
ZPIXMAP = 2
19+
20+
1721
class Display(ctypes.Structure):
1822
"""
1923
Structure that serves as the connection to the X server
@@ -289,7 +293,7 @@ def grab(self, monitor):
289293
ximage = self.xlib.XGetImage(self.display, root,
290294
monitor['left'], monitor['top'],
291295
monitor['width'], monitor['height'],
292-
0x00ffffff, 2) # ZPIXMAP
296+
PLAINMASK, ZPIXMAP)
293297
if not ximage:
294298
raise ScreenShotError('xlib.XGetImage() failed.', locals())
295299

mss/screenshot.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@ class ScreenShot(object):
2828

2929
def __init__(self, data, monitor, size=None):
3030
# type: (bytearray, Dict[str, int], Any) -> None
31-
#: Bytearray of the raw BGRA pixels retrieved by ctype
31+
32+
#: Bytearray of the raw BGRA pixels retrieved by ctypes
3233
#: OS independent implementations.
3334
self.raw = bytearray(data) # type: bytearray
3435

mss/tools.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,15 @@
88
import zlib
99

1010

11-
def to_png(data, size, output=None):
12-
# type: (bytes, Tuple[int, int], Optional[str]) -> Union[None, bytes]
11+
def to_png(data, size, level=6, output=None):
12+
# type: (bytes, Tuple[int, int], int, Optional[str]) -> Union[None, bytes]
1313
"""
1414
Dump data to a PNG file. If `output` is `None`, create no file but return
1515
the whole PNG data.
1616
1717
:param bytes data: RGBRGB...RGB data.
1818
:param tuple size: The (width, height) pair.
19+
:param int level: PNG compression level.
1920
:param str output: Output file name.
2021
"""
2122

@@ -35,7 +36,7 @@ def to_png(data, size, output=None):
3536
ihdr[0] = struct.pack('>I', len(ihdr[2]))
3637

3738
# Data: size, marker, data, CRC32
38-
idat = [b'', b'IDAT', zlib.compress(scanlines), b'']
39+
idat = [b'', b'IDAT', zlib.compress(scanlines, level), b'']
3940
idat[3] = struct.pack('>I', zlib.crc32(b''.join(idat[1:3])) & 0xffffffff)
4041
idat[0] = struct.pack('>I', len(idat[2]))
4142

tests/test_tools.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22

33
import hashlib
44
import os.path
5+
import zlib
6+
7+
import pytest
58

69
from mss.tools import to_png
710

@@ -11,6 +14,43 @@
1114
MD5SUM = '055e615b74167c9bdfea16a00539450c'
1215

1316

17+
def test_bad_compression_level(sct):
18+
sct.compression_level = 42
19+
try:
20+
with pytest.raises(zlib.error):
21+
sct.shot()
22+
finally:
23+
sct.compression_level = 6
24+
25+
26+
def test_compression_level(sct):
27+
data = b'rgb' * WIDTH * HEIGHT
28+
output = '{}x{}.png'.format(WIDTH, HEIGHT)
29+
30+
to_png(data, (WIDTH, HEIGHT), level=sct.compression_level, output=output)
31+
with open(output, 'rb') as png:
32+
assert hashlib.md5(png.read()).hexdigest() == MD5SUM
33+
34+
35+
@pytest.mark.parametrize('level, checksum', [
36+
(0, 'f37123dbc08ed7406d933af11c42563e'),
37+
(1, '7d5dcf2a2224445daf19d6d91cf31cb5'),
38+
(2, 'bde05376cf51cf951e26c31c5f55e9d5'),
39+
(3, '3d7e73c2a9c2d8842b363eeae8085919'),
40+
(4, '9565a5caf89a9221459ee4e02b36bf6e'),
41+
(5, '4d722e21e7d62fbf1e3154de7261fc67'),
42+
(6, '055e615b74167c9bdfea16a00539450c'),
43+
(7, '4d88d3f5923b6ef05b62031992294839'),
44+
(8, '4d88d3f5923b6ef05b62031992294839'),
45+
(9, '4d88d3f5923b6ef05b62031992294839'),
46+
])
47+
def test_compression_levels(level, checksum):
48+
data = b'rgb' * WIDTH * HEIGHT
49+
raw = to_png(data, (WIDTH, HEIGHT), level=level)
50+
md5 = hashlib.md5(raw).hexdigest()
51+
assert md5 == checksum
52+
53+
1454
def test_output_file():
1555
data = b'rgb' * WIDTH * HEIGHT
1656
output = '{}x{}.png'.format(WIDTH, HEIGHT)

0 commit comments

Comments
 (0)