Skip to content

Commit 2e46eb8

Browse files
authored
feat: Change the ScreenShot API to be buffer-oriented. (#521)
This implements many of the changes discussed in #476, part 1. It does not make the ScreenShot object itself accessible as a buffer; that's a separate matter. This change makes `_raw` (previously `raw`) private. `_raw` is now the original object passed in. The `bgra` and `rgb` properties now return `memoryview` objects, rather than `bytes` or `bytearray` objects. This means that the backends can now capture data to an OS-provided buffer, such as one provided by `mmap` or `CreateDIBSection`. The `ScreenShot` object can then give the user direct access to this buffer, without ever needing to copy the data. Similarly, ScreenShot objects now can be given their data as any buffer type, not just a bytearray. The backends haven't yet been changed to take advantage of these new features. Docs, examples, and demos are updated.
1 parent 01cb187 commit 2e46eb8

16 files changed

Lines changed: 103 additions & 67 deletions

demos/cat-detector.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -281,7 +281,7 @@ def main() -> None:
281281
# We transfer the image from MSS to PyTorch via a Pillow Image. Faster approaches exist (see
282282
# screenshot_to_tensor), but PIL is more readable. The bulk of the time in this program is spent doing
283283
# the AI work, so we just use the most convenient mechanism.
284-
img = Image.frombytes("RGB", sct_img.size, sct_img.bgra, "raw", "BGRX")
284+
img = Image.frombuffer("RGB", sct_img.size, sct_img.bgra, "raw", "BGRX")
285285

286286
# We explicitly convert it to a tensor here, even though Torchvision can also convert it in the preprocess
287287
# step. This is so that we send it to the GPU before we do the preprocessing: PIL Images are always on

demos/tinytv-stream-simple.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ def main() -> None:
9797
# The next step is to resize the image to fit the TinyTV's screen. There's a great image
9898
# manipulation library called PIL, or Pillow, that can do that. Let's transfer the raw pixels in
9999
# the ScreenShot object into a PIL Image.
100-
original_image = Image.frombytes("RGB", screenshot.size, screenshot.bgra, "raw", "BGRX")
100+
original_image = Image.frombuffer("RGB", screenshot.size, screenshot.bgra, "raw", "BGRX")
101101

102102
# Now, we can resize it. The resize method may stretch the image to make it match the TinyTV's
103103
# screen; the advanced demo gives other options. Using a reducing gap is optional, but speeds up

demos/tinytv-stream.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -348,7 +348,7 @@ def capture_image(
348348

349349
while True:
350350
sct_img = sct.grab(rect)
351-
pil_img = Image.frombytes("RGB", sct_img.size, sct_img.bgra, "raw", "BGRX")
351+
pil_img = Image.frombuffer("RGB", sct_img.size, sct_img.bgra, "raw", "BGRX")
352352
yield pil_img
353353

354354

demos/video-capture-simple.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ def main() -> None:
129129
# use PIL: you can create an Image from the screenshot, and create a VideoFrame from that. That said,
130130
# if you want to boost the fps rate by about 50%, check out the full demo, and search for
131131
# from_numpy_buffer.
132-
img = Image.frombytes("RGB", screenshot.size, screenshot.bgra, "raw", "BGRX")
132+
img = Image.frombuffer("RGB", screenshot.size, screenshot.bgra, "raw", "BGRX")
133133
frame = av.VideoFrame.from_image(img)
134134

135135
# When we encode frames, we get back a list of packets. Often, we'll get no packets at first: the

docs/source/examples.rst

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ PIL
123123
===
124124

125125
You can use the Python Image Library (aka Pillow) to do whatever you want with raw pixels.
126-
This is an example using `frombytes() <http://pillow.readthedocs.io/en/latest/reference/Image.html#PIL.Image.frombytes>`_:
126+
This is an example using `frombuffer() <http://pillow.readthedocs.io/en/latest/reference/Image.html#PIL.Image.frombuffer>`_:
127127

128128
.. literalinclude:: examples/pil.py
129129
:lines: 7-
@@ -188,7 +188,7 @@ Different possibilities to convert raw BGRA values to RGB::
188188

189189

190190
def numpy_flip(im):
191-
""" Most efficient Numpy version as of now. """
191+
""" Most efficient Numpy version as of MSS 10.1. """
192192
frame = numpy.array(im, dtype=numpy.uint8)
193193
return numpy.flip(frame[:, :, :3], 2).tobytes()
194194

@@ -198,14 +198,14 @@ Different possibilities to convert raw BGRA values to RGB::
198198
return numpy.array(im, dtype=numpy.uint8)[..., [2, 1, 0]].tobytes()
199199

200200

201-
def pil_frombytes(im):
201+
def pil_frombuffer(im):
202202
""" Efficient Pillow version. """
203-
return Image.frombytes('RGB', im.size, im.bgra, 'raw', 'BGRX').tobytes()
203+
return Image.frombuffer('RGB', im.size, im.bgra, 'raw', 'BGRX').tobytes()
204204

205205

206206
with mss.MSS() as sct:
207207
im = sct.grab(sct.monitors[1])
208-
rgb = pil_frombytes(im)
208+
rgb = pil_frombuffer(im)
209209
...
210210

211211
.. versionadded:: 3.2.0

docs/source/examples/pil.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,9 @@
1515
sct_img = sct.grab(monitor)
1616

1717
# Create the Image
18-
img = Image.frombytes("RGB", sct_img.size, sct_img.bgra, "raw", "BGRX")
18+
img = Image.frombuffer("RGB", sct_img.size, sct_img.bgra, "raw", "BGRX")
1919
# The same, but less efficient:
20-
# img = Image.frombytes('RGB', sct_img.size, sct_img.rgb)
20+
# img = Image.frombuffer('RGB', sct_img.size, sct_img.rgb, 'raw', 'RGB')
2121

2222
# And save it!
2323
output = f"monitor-{num}.png"

docs/source/examples/pil_pixels.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
img = Image.new("RGB", sct_img.size)
1717

1818
# Best solution: create a list(tuple(R, G, B), ...) for putdata()
19-
pixels = zip(sct_img.raw[2::4], sct_img.raw[1::4], sct_img.raw[::4], strict=False)
19+
pixels = zip(sct_img.bgra[2::4], sct_img.bgra[1::4], sct_img.bgra[::4], strict=False)
2020
img.putdata(list(pixels))
2121

2222
# But you can set individual pixels too (slower)

docs/source/release-history/v11.0.0.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,19 @@ Release date: 2026-xx-x
88

99
## Highlights
1010

11+
### API changes
12+
13+
The API changes discussed in the 10.2 release notes are now implemented. (They're not all implemented as of this
14+
writing, but should be by the time we release 11.0!)
15+
16+
#### ScreenShot attributes
17+
18+
The :py:attr:`mss.ScreenShot.raw` attribute has been removed. Use the :py:attr:`mss.ScreenShot.bgra` property instead.
19+
20+
The :py:attr:`mss.ScreenShot.bgra` and :py:attr:`mss.ScreenShot.rgb` properties now will return read-only bytes-like
21+
:py:type:`memoryview` objects, not necessarily :py:type:`bytes` or :py:type:`bytearray` objects. For practical use
22+
cases, this should not be noticible. This change was allows faster access to screenshot data, with fewer memory copies.
23+
1124
### Python 3.9 EOL
1225

1326
Python 3.9 reached [end-of-life](https://devguide.python.org/developer-workflow/development-cycle/index.html#end-of-life-branches) on [October 31, 2025](https://devguide.python.org/versions/). It is no longer receiving any updates, even security updates.

docs/source/support.rst

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@ Support
55
Feel free to try MSS on a system we had not tested, and let us know by creating an `issue <https://github.com/BoboTiG/python-mss/issues>`_.
66

77
- OS: GNU/Linux, macOS, and Windows
8-
- Python: 3.10 and newer
8+
- Python: CPython 3.10 and newer
9+
10+
Python implementations other than CPython are unlikely to ever be supported, due to MSS's extensive use of ctypes.
911

1012

1113
Future
@@ -18,8 +20,9 @@ Future
1820
Others
1921
======
2022

21-
Tested successfully on Pypy 5.1.0 on Windows, but speed is terrible.
22-
23+
Previous version of MSS were tested successfully on PyPy 5.1.0 on Windows, but speed is terrible. In general, PyPy
24+
support is not a priority, but if you want to help, please create an
25+
`issue <https://github.com/BoboTiG/python-mss/issues>`_.
2326

2427
Abandoned
2528
=========

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ dev = [
7575
"mypy==1.20.2",
7676
"ruff==0.15.12",
7777
"twine==6.2.0",
78+
"typing_extensions==4.15.0",
7879
]
7980
docs = [
8081
"myst-parser==5.0.0 ; python_version >= '3.12'",

0 commit comments

Comments
 (0)