Skip to content

Commit ea7c3b0

Browse files
committed
Refactor type support for SDL primitive drawing
Now raises TypeError for incorrect shapes
1 parent 048d180 commit ea7c3b0

File tree

3 files changed

+63
-30
lines changed

3 files changed

+63
-30
lines changed

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,14 @@ This project adheres to [Semantic Versioning](https://semver.org/) since version
66

77
## [Unreleased]
88

9+
### Added
10+
11+
- SDL renderer primitive drawing methods now support sequences of tuples.
12+
13+
### Fixed
14+
15+
- `tcod.sdl.Renderer.draw_lines` type hint was too narrow.
16+
917
## [17.0.0] - 2025-03-28
1018

1119
### Changed

tcod/sdl/render.py

Lines changed: 39 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from __future__ import annotations
77

88
import enum
9-
from typing import Any, Final
9+
from typing import Any, Final, Sequence
1010

1111
import numpy as np
1212
from numpy.typing import NDArray
@@ -594,59 +594,78 @@ def draw_line(self, start: tuple[float, float], end: tuple[float, float]) -> Non
594594
_check(lib.SDL_RenderDrawLineF(self.p, x1, y1, x2, y2))
595595

596596
@staticmethod
597-
def _convert_array(array: NDArray[np.number]) -> NDArray[np.intc] | NDArray[np.float32]:
598-
"""Convert ndarray for a SDL function expecting a C contiguous array of either intc or float32."""
599-
if array.dtype in (np.intc, np.int8, np.int16, np.int32, np.uint8, np.uint16):
600-
return np.ascontiguousarray(array, np.intc)
601-
return np.ascontiguousarray(array, np.float32)
597+
def _convert_array(
598+
array: NDArray[np.number] | Sequence[Sequence[float]], item_length: int
599+
) -> NDArray[np.intc] | NDArray[np.float32]:
600+
"""Convert ndarray for a SDL function expecting a C contiguous array of either intc or float32.
602601
603-
def fill_rects(self, rects: NDArray[np.number]) -> None:
602+
Array shape is enforced to be (n, item_length)
603+
"""
604+
if getattr(array, "dtype", None) in (np.intc, np.int8, np.int16, np.int32, np.uint8, np.uint16):
605+
out = np.ascontiguousarray(array, np.intc)
606+
else:
607+
out = np.ascontiguousarray(array, np.float32)
608+
if len(out.shape) != 2: # noqa: PLR2004
609+
msg = f"Array must have 2 axes, but shape is {out.shape!r}"
610+
raise TypeError(msg)
611+
if out.shape[1] != item_length:
612+
msg = f"Array shape[1] must be {item_length}, but shape is {out.shape!r}"
613+
raise TypeError(msg)
614+
return out
615+
616+
def fill_rects(self, rects: NDArray[np.number] | Sequence[tuple[float, float, float, float]]) -> None:
604617
"""Fill multiple rectangles from an array.
605618
619+
Args:
620+
rects: A sequence or array of (x, y, width, height) rectangles.
621+
606622
.. versionadded:: 13.5
607623
"""
608-
assert len(rects.shape) == 2 # noqa: PLR2004
609-
assert rects.shape[1] == 4 # noqa: PLR2004
610-
rects = self._convert_array(rects)
624+
rects = self._convert_array(rects, item_length=4)
611625
if rects.dtype == np.intc:
612626
_check(lib.SDL_RenderFillRects(self.p, tcod.ffi.from_buffer("SDL_Rect*", rects), rects.shape[0]))
613627
return
614628
_check(lib.SDL_RenderFillRectsF(self.p, tcod.ffi.from_buffer("SDL_FRect*", rects), rects.shape[0]))
615629

616-
def draw_rects(self, rects: NDArray[np.number]) -> None:
630+
def draw_rects(self, rects: NDArray[np.number] | Sequence[tuple[float, float, float, float]]) -> None:
617631
"""Draw multiple outlined rectangles from an array.
618632
633+
Args:
634+
rects: A sequence or array of (x, y, width, height) rectangles.
635+
619636
.. versionadded:: 13.5
620637
"""
638+
rects = self._convert_array(rects, item_length=4)
621639
assert len(rects.shape) == 2 # noqa: PLR2004
622640
assert rects.shape[1] == 4 # noqa: PLR2004
623-
rects = self._convert_array(rects)
624641
if rects.dtype == np.intc:
625642
_check(lib.SDL_RenderDrawRects(self.p, tcod.ffi.from_buffer("SDL_Rect*", rects), rects.shape[0]))
626643
return
627644
_check(lib.SDL_RenderDrawRectsF(self.p, tcod.ffi.from_buffer("SDL_FRect*", rects), rects.shape[0]))
628645

629-
def draw_points(self, points: NDArray[np.number]) -> None:
646+
def draw_points(self, points: NDArray[np.number] | Sequence[tuple[float, float]]) -> None:
630647
"""Draw an array of points.
631648
649+
Args:
650+
points: A sequence or array of (x, y) points.
651+
632652
.. versionadded:: 13.5
633653
"""
634-
assert len(points.shape) == 2 # noqa: PLR2004
635-
assert points.shape[1] == 2 # noqa: PLR2004
636-
points = self._convert_array(points)
654+
points = self._convert_array(points, item_length=2)
637655
if points.dtype == np.intc:
638656
_check(lib.SDL_RenderDrawPoints(self.p, tcod.ffi.from_buffer("SDL_Point*", points), points.shape[0]))
639657
return
640658
_check(lib.SDL_RenderDrawPointsF(self.p, tcod.ffi.from_buffer("SDL_FPoint*", points), points.shape[0]))
641659

642-
def draw_lines(self, points: NDArray[np.intc | np.float32]) -> None:
660+
def draw_lines(self, points: NDArray[np.number] | Sequence[tuple[float, float]]) -> None:
643661
"""Draw a connected series of lines from an array.
644662
663+
Args:
664+
points: A sequence or array of (x, y) points.
665+
645666
.. versionadded:: 13.5
646667
"""
647-
assert len(points.shape) == 2 # noqa: PLR2004
648-
assert points.shape[1] == 2 # noqa: PLR2004
649-
points = self._convert_array(points)
668+
points = self._convert_array(points, item_length=2)
650669
if points.dtype == np.intc:
651670
_check(lib.SDL_RenderDrawLines(self.p, tcod.ffi.from_buffer("SDL_Point*", points), points.shape[0] - 1))
652671
return

tests/test_sdl.py

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -79,19 +79,25 @@ def test_sdl_render(uses_window: None) -> None:
7979
assert render.read_pixels(rect=(1, 2, 3, 4)).shape == (4, 3, 4)
8080

8181
render.draw_point((0, 0))
82-
render.draw_points(np.ones((3, 2), dtype=np.float32))
83-
render.draw_points(np.ones((3, 2), dtype=np.intc))
84-
render.draw_points(np.ones((3, 2), dtype=np.float16))
85-
render.draw_points(np.ones((3, 2), dtype=np.int8))
8682
render.draw_line((0, 0), (1, 1))
87-
render.draw_lines(np.ones((3, 2), dtype=np.float32))
88-
render.draw_lines(np.ones((3, 2), dtype=np.intc))
8983
render.draw_rect((0, 0, 1, 1))
90-
render.draw_rects(np.ones((3, 4), dtype=np.float32))
91-
render.draw_rects(np.ones((3, 4), dtype=np.intc))
9284
render.fill_rect((0, 0, 1, 1))
93-
render.fill_rects(np.ones((3, 4), dtype=np.float32))
94-
render.fill_rects(np.ones((3, 4), dtype=np.intc))
85+
86+
render.draw_points([(0, 0)])
87+
render.draw_lines([(0, 0), (1, 1)])
88+
render.draw_rects([(0, 0, 1, 1)])
89+
render.fill_rects([(0, 0, 1, 1)])
90+
91+
for dtype in (np.intc, np.int8, np.uint8, np.float32, np.float16):
92+
render.draw_points(np.ones((3, 2), dtype=dtype))
93+
render.draw_lines(np.ones((3, 2), dtype=dtype))
94+
render.draw_rects(np.ones((3, 4), dtype=dtype))
95+
render.fill_rects(np.ones((3, 4), dtype=dtype))
96+
97+
with pytest.raises(TypeError, match=r"shape\[1\] must be 2"):
98+
render.draw_points(np.ones((3, 1), dtype=dtype))
99+
with pytest.raises(TypeError, match=r"must have 2 axes"):
100+
render.draw_points(np.ones((3,), dtype=dtype))
95101

96102

97103
def test_sdl_render_bad_types() -> None:

0 commit comments

Comments
 (0)