Skip to content

Commit 55ee12a

Browse files
authored
Brush up screenshot functions (#2188)
1 parent 535a28c commit 55ee12a

File tree

4 files changed

+48
-24
lines changed

4 files changed

+48
-24
lines changed

arcade/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -150,8 +150,8 @@ def configure_logging(level: Optional[int] = None):
150150
from .draw import draw_lbwh_rectangle_outline
151151
from .draw import draw_rect_filled_kwargs
152152
from .draw import draw_rect_outline_kwargs
153-
from .draw import get_image
154-
from .draw import get_pixel
153+
from .screenshot import get_image
154+
from .screenshot import get_pixel
155155

156156
# We don't have joysticks game controllers in headless mode
157157
if not pyglet.options["headless"]:

arcade/draw/__init__.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@
3333
draw_sprite_rect,
3434
)
3535
from .helpers import get_points_for_thick_line
36-
from .screenshot import get_pixel, get_image
3736

3837
__all__ = [
3938
# arc
@@ -74,7 +73,4 @@
7473
"draw_sprite_rect",
7574
# helpers
7675
"get_points_for_thick_line",
77-
# screenshot
78-
"get_pixel",
79-
"get_image",
8076
]
Lines changed: 26 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
1+
"""
2+
Original screenshot utilities from arcade 2.
3+
4+
These functions are flawed because they only read from the screen.
5+
"""
6+
17
from typing import Optional
28

39
import PIL.Image
410
import PIL.ImageOps
5-
from pyglet import gl
611

712
from arcade.window_commands import get_window
813

@@ -22,34 +27,43 @@ def get_pixel(x: int, y: int, components: int = 3) -> tuple[int, ...]:
2227
# The window may be 'scaled' on hi-res displays. Particularly Macs. OpenGL
2328
# won't account for this, so we need to.
2429
window = get_window()
30+
ctx = window.ctx
2531

2632
pixel_ratio = window.get_pixel_ratio()
2733
x = int(pixel_ratio * x)
2834
y = int(pixel_ratio * y)
2935

30-
a = (gl.GLubyte * 4)(0)
31-
gl.glReadPixels(x, y, 1, 1, gl.GL_RGBA, gl.GL_UNSIGNED_BYTE, a)
32-
return tuple(int(i) for i in a[:components])
36+
data = ctx.screen.read(viewport=(x, y, 1, 1), components=components)
37+
return tuple(data) # bytes gets converted to ints in the tuple creation
3338

3439

3540
def get_image(
36-
x: int = 0, y: int = 0, width: Optional[int] = None, height: Optional[int] = None
41+
x: int = 0,
42+
y: int = 0,
43+
width: Optional[int] = None,
44+
height: Optional[int] = None,
45+
components: int = 4,
3746
) -> PIL.Image.Image:
3847
"""
3948
Get an image from the screen.
4049
4150
Example::
4251
43-
image = get_image()
44-
image.save('screenshot.png', 'PNG')
52+
# Create and image of the entire screen and save it to a file
53+
image = arcade.get_image()
54+
image.save('screenshot.png')
4555
4656
:param x: Start (left) x location
47-
:param y: Start (top) y location
57+
:param y: Start (bottom) y location
4858
:param width: Width of image. Leave blank for grabbing the 'rest' of the image
4959
:param height: Height of image. Leave blank for grabbing the 'rest' of the image
50-
:returns: A Pillow Image
60+
:param components: Number of components to fetch. By default we fetch 4 (4=RGBA, 3=RGB)
5161
"""
5262
window = get_window()
63+
ctx = window.ctx
64+
65+
if components not in (3, 4):
66+
raise ValueError("components must be 3 or 4")
5367

5468
pixel_ratio = window.get_pixel_ratio()
5569
x = int(pixel_ratio * x)
@@ -63,12 +77,6 @@ def get_image(
6377
width = int(pixel_ratio * width)
6478
height = int(pixel_ratio * height)
6579

66-
# Create an image buffer
67-
# noinspection PyTypeChecker
68-
image_buffer = (gl.GLubyte * (4 * width * height))(0)
69-
70-
gl.glReadPixels(x, y, width, height, gl.GL_RGBA, gl.GL_UNSIGNED_BYTE, image_buffer)
71-
image = PIL.Image.frombytes("RGBA", (width, height), image_buffer)
72-
image = PIL.ImageOps.flip(image)
73-
74-
return image
80+
data = ctx.screen.read(viewport=(x, y, width, height), components=components)
81+
image = PIL.Image.frombytes("RGBA" if components == 4 else "RGB", (width, height), data)
82+
return PIL.ImageOps.flip(image)

tests/unit/test_screenshot.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import arcade
2+
3+
4+
def test_read_pixel(window):
5+
"""Read zero pixel from active framebuffer."""
6+
window.clear(color=arcade.color.WHITE)
7+
px = arcade.get_pixel(0, 0)
8+
assert px == arcade.color.WHITE.rgb
9+
px = arcade.get_pixel(0, 0, components=4)
10+
assert px == arcade.color.WHITE
11+
12+
13+
def test_get_image(window):
14+
"""Get image from active framebuffer."""
15+
window.clear(color=arcade.color.WHITE)
16+
image = arcade.get_image()
17+
assert image.tobytes()[0:16] == b'\xff' * 16
18+
19+
image = arcade.get_image(components=3)
20+
assert image.tobytes()[0:16] == b'\xff' * 16

0 commit comments

Comments
 (0)