Skip to content

Commit 2e44730

Browse files
committed
Fix Renderer.read_pixels, add simple tests, update docs.
1 parent a756e38 commit 2e44730

File tree

3 files changed

+57
-15
lines changed

3 files changed

+57
-15
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ This project adheres to [Semantic Versioning](https://semver.org/) since version
77
### Added
88
- Added support for `tcod.sdl` namespace packages.
99

10+
### Fixed
11+
- ``Renderer.read_pixels`` method was completely broken.
12+
1013
## [15.0.0] - 2023-01-04
1114
### Changed
1215
- Modified the letter case of window event types to match their type annotations.

tcod/sdl/render.py

Lines changed: 50 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
import numpy as np
1111
from numpy.typing import NDArray
12-
from typing_extensions import Final
12+
from typing_extensions import Final, Literal
1313

1414
import tcod.sdl.video
1515
from tcod.loader import ffi, lib
@@ -484,15 +484,33 @@ def set_vsync(self, enable: bool) -> None:
484484
def read_pixels(
485485
self,
486486
*,
487-
rect: Optional[Tuple[int, int, int, int]] = None,
488-
format: Optional[int] = None,
489-
out: Optional[NDArray[Any]] = None,
490-
) -> NDArray[Any]:
491-
"""
492-
.. versionadded:: 13.5
487+
rect: tuple[int, int, int, int] | None = None,
488+
format: int | Literal["RGB", "RGBA"] = "RGBA",
489+
out: NDArray[np.uint8] | None = None,
490+
) -> NDArray[np.uint8]:
491+
"""Fetch the pixel contents of the current rendering target to an array.
492+
493+
By default returns an RGBA pixel array of the full target in the shape: ``(height, width, rgba)``.
494+
The target can be changed with :any:`set_render_target`
495+
496+
Args:
497+
rect: The ``(left, top, width, height)`` region of the target to fetch, or None for the entire target.
498+
format: The pixel format. Defaults to ``"RGBA"``.
499+
out: The output array.
500+
Can be None or must be an ``np.uint8`` array of shape: ``(height, width, channels)``.
501+
Must be C contiguous along the ``(width, channels)`` axes.
502+
503+
This operation is slow due to coping from VRAM to RAM.
504+
When reading the main rendering target this should be called after rendering and before :any:`present`.
505+
See https://wiki.libsdl.org/SDL2/SDL_RenderReadPixels
506+
507+
Returns:
508+
The output uint8 array of shape: ``(height, width, channels)`` with the fetched pixels.
509+
510+
.. versionadded:: Unreleased
493511
"""
494-
if format is None:
495-
format = lib.SDL_PIXELFORMAT_RGBA32
512+
FORMATS: Final = {"RGB": lib.SDL_PIXELFORMAT_RGB24, "RGBA": lib.SDL_PIXELFORMAT_RGBA32}
513+
sdl_format = FORMATS.get(format) if isinstance(format, str) else format
496514
if rect is None:
497515
texture_p = lib.SDL_GetRenderTarget(self.p)
498516
if texture_p:
@@ -502,15 +520,31 @@ def read_pixels(
502520
rect = (0, 0, *self.output_size)
503521
width, height = rect[2:4]
504522
if out is None:
505-
if format == lib.SDL_PIXELFORMAT_RGBA32:
523+
if sdl_format == lib.SDL_PIXELFORMAT_RGBA32:
506524
out = np.empty((height, width, 4), dtype=np.uint8)
507-
elif format == lib.SDL_PIXELFORMAT_RGB24:
525+
elif sdl_format == lib.SDL_PIXELFORMAT_RGB24:
508526
out = np.empty((height, width, 3), dtype=np.uint8)
509527
else:
510-
raise TypeError("Pixel format not supported yet.")
511-
assert out.shape[:2] == (height, width)
512-
assert out[0].flags.c_contiguous
513-
_check(lib.SDL_RenderReadPixels(self.p, format, ffi.cast("void*", out.ctypes.data), out.strides[0]))
528+
msg = f"Pixel format {format!r} not supported by tcod."
529+
raise TypeError(msg)
530+
if out.dtype != np.uint8:
531+
msg = "`out` must be a uint8 array."
532+
raise TypeError(msg)
533+
expected_shape = (height, width, {lib.SDL_PIXELFORMAT_RGB24: 3, lib.SDL_PIXELFORMAT_RGBA32: 4}[sdl_format])
534+
if out.shape != expected_shape:
535+
msg = f"Expected `out` to be an array of shape {expected_shape}, got {out.shape} instead."
536+
raise TypeError(msg)
537+
if not out[0].flags.c_contiguous:
538+
msg = "`out` array must be C contiguous."
539+
_check(
540+
lib.SDL_RenderReadPixels(
541+
self.p,
542+
(rect,),
543+
sdl_format,
544+
ffi.cast("void*", out.ctypes.data),
545+
out.strides[0],
546+
)
547+
)
514548
return out
515549

516550
def clear(self) -> None:
@@ -522,6 +556,7 @@ def clear(self) -> None:
522556

523557
def fill_rect(self, rect: Tuple[float, float, float, float]) -> None:
524558
"""Fill a rectangle with :any:`draw_color`.
559+
525560
.. versionadded:: 13.5
526561
"""
527562
_check(lib.SDL_RenderFillRectF(self.p, (rect,)))

tests/test_sdl.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,10 @@ def test_sdl_render() -> None:
7070
with pytest.raises(TypeError):
7171
render.upload_texture(np.zeros((8, 8, 5), np.uint8))
7272

73+
assert (render.read_pixels() == (0, 0, 0, 255)).all()
74+
assert (render.read_pixels(format="RGB") == (0, 0, 0)).all()
75+
assert render.read_pixels(rect=(1, 2, 3, 4)).shape == (4, 3, 4)
76+
7377

7478
def test_sdl_render_bad_types() -> None:
7579
with pytest.raises(TypeError):

0 commit comments

Comments
 (0)