Skip to content

Commit afdd1d4

Browse files
committed
Add SDL Renderer and Texture helpers.
Perform some cleanups.
1 parent 9715a46 commit afdd1d4

File tree

5 files changed

+145
-11
lines changed

5 files changed

+145
-11
lines changed

.vscode/settings.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,7 @@
170170
"LMASK",
171171
"lmeta",
172172
"lodepng",
173+
"LPAREN",
173174
"LTCG",
174175
"lucida",
175176
"LWIN",
@@ -250,6 +251,7 @@
250251
"RRGGBB",
251252
"rtype",
252253
"RWIN",
254+
"RWOPS",
253255
"scalex",
254256
"scaley",
255257
"Scancode",
@@ -264,6 +266,7 @@
264266
"setuptools",
265267
"SHADOWCAST",
266268
"SMILIE",
269+
"snprintf",
267270
"stdeb",
268271
"struct",
269272
"structs",

tcod/sdl/__init__.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,16 @@ def _sdl_log_output_function(_userdata: Any, category: int, priority: int, messa
2323
logger.log(_LOG_PRIORITY.get(priority, 0), "%i:%s", category, ffi.string(message).decode("utf-8"))
2424

2525

26+
def _get_error() -> str:
27+
"""Return a message from SDL_GetError as a Unicode string."""
28+
return str(ffi.string(lib.SDL_GetError()), encoding="utf-8")
29+
30+
31+
def _check(result: int) -> int:
32+
"""Check if an SDL function returned without errors, and raise an exception if it did."""
33+
if result < 0:
34+
raise RuntimeError(_get_error())
35+
return result
36+
37+
2638
lib.SDL_LogSetOutputFunction(lib._sdl_log_output_function, ffi.NULL)

tcod/sdl/audio.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
import tcod.sdl.sys
1313
from tcod.loader import ffi, lib
14+
from tcod.sdl import _get_error
1415

1516

1617
def _get_format(format: DTypeLike) -> int:
@@ -82,7 +83,7 @@ def __init__(
8283
obtained,
8384
allowed_changes,
8485
)
85-
assert self.device_id != 0, tcod.sdl.sys._get_error()
86+
assert self.device_id != 0, _get_error()
8687
self.frequency = obtained.freq
8788
self.is_capture = capture
8889
self.format = _dtype_from_format(obtained.format)

tcod/sdl/render.py

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
from __future__ import annotations
2+
3+
from typing import Any, Optional, Tuple
4+
5+
import numpy as np
6+
from numpy.typing import NDArray
7+
8+
from tcod.loader import ffi, lib
9+
from tcod.sdl import _check
10+
11+
12+
class Texture:
13+
def __init__(self, sdl_texture_p: Any, sdl_renderer_p: Any = None) -> None:
14+
self.p = sdl_texture_p
15+
self._sdl_renderer_p = sdl_renderer_p # Keep alive.
16+
17+
def __eq__(self, other: Any) -> bool:
18+
return bool(self.p == getattr(other, "p", None))
19+
20+
def _query(self) -> Tuple[int, int, int, int]:
21+
"""Return (format, access, width, height)."""
22+
format = ffi.new("uint32_t*")
23+
buffer = ffi.new("int[3]")
24+
lib.SDL_QueryTexture(self.p, format, buffer, buffer + 1, buffer + 2)
25+
return int(format), int(buffer[0]), int(buffer[1]), int(buffer[2])
26+
27+
@property
28+
def format(self) -> int:
29+
"""Texture format, read only."""
30+
buffer = ffi.new("uint32_t*")
31+
lib.SDL_QueryTexture(self.p, buffer, ffi.NULL, ffi.NULL, ffi.NULL)
32+
return int(buffer[0])
33+
34+
@property
35+
def access(self) -> int:
36+
"""Texture access mode, read only."""
37+
buffer = ffi.new("int*")
38+
lib.SDL_QueryTexture(self.p, ffi.NULL, buffer, ffi.NULL, ffi.NULL)
39+
return int(buffer[0])
40+
41+
@property
42+
def width(self) -> int:
43+
"""Texture pixel width, read only."""
44+
buffer = ffi.new("int*")
45+
lib.SDL_QueryTexture(self.p, ffi.NULL, ffi.NULL, buffer, ffi.NULL)
46+
return int(buffer[0])
47+
48+
@property
49+
def height(self) -> int:
50+
"""Texture pixel height, read only."""
51+
buffer = ffi.new("int*")
52+
lib.SDL_QueryTexture(self.p, ffi.NULL, ffi.NULL, ffi.NULL, buffer)
53+
return int(buffer[0])
54+
55+
@property
56+
def alpha_mod(self) -> int:
57+
"""Texture alpha modulate value, can be set to: 0 - 255."""
58+
return int(lib.SDL_GetTextureAlphaMod(self.p))
59+
60+
@alpha_mod.setter
61+
def alpha_mod(self, value: int) -> None:
62+
_check(lib.SDL_SetTextureAlphaMod(self.p, value))
63+
64+
@property
65+
def blend_mode(self) -> int:
66+
"""Texture blend mode, can be set."""
67+
return int(lib.SDL_GetTextureBlendMode(self.p))
68+
69+
@blend_mode.setter
70+
def blend_mode(self, value: int) -> None:
71+
_check(lib.SDL_SetTextureBlendMode(self.p, value))
72+
73+
@property
74+
def rgb_mod(self) -> Tuple[int, int, int]:
75+
"""Texture RGB color modulate values, can be set."""
76+
rgb = ffi.new("uint8_t[3]")
77+
_check(lib.SDL_GetTextureColorMod(self.p, rgb, rgb + 1, rgb + 2))
78+
return int(rgb[0]), int(rgb[1]), int(rgb[2])
79+
80+
@rgb_mod.setter
81+
def rgb_mod(self, rgb: Tuple[int, int, int]) -> None:
82+
_check(lib.SDL_SetTextureColorMod(self.p, rgb[0], rgb[1], rgb[2]))
83+
84+
85+
class Renderer:
86+
def __init__(self, sdl_renderer_p: Any) -> None:
87+
if ffi.typeof(sdl_renderer_p) is not ffi.typeof("struct SDL_Renderer*"):
88+
raise TypeError(f"Expected a {ffi.typeof('struct SDL_Window*')} type (was {ffi.typeof(sdl_renderer_p)}).")
89+
self.p = sdl_renderer_p
90+
91+
def __eq__(self, other: Any) -> bool:
92+
return bool(self.p == getattr(other, "p", None))
93+
94+
def new_texture(
95+
self, width: int, height: int, *, format: Optional[int] = None, access: Optional[int] = None
96+
) -> Texture:
97+
"""Allocate and return a new Texture for this renderer."""
98+
if format is None:
99+
format = 0
100+
if access is None:
101+
access = int(lib.SDL_TEXTUREACCESS_STATIC)
102+
format = int(lib.SDL_PIXELFORMAT_RGBA32)
103+
access = int(lib.SDL_TEXTUREACCESS_STATIC)
104+
texture_p = ffi.gc(lib.SDL_CreateTexture(self.p, format, access, width, height), lib.SDL_DestroyTexture)
105+
return Texture(texture_p, self.p)
106+
107+
def upload_texture(
108+
self, pixels: NDArray[Any], *, format: Optional[int] = None, access: Optional[int] = None
109+
) -> Texture:
110+
"""Return a new Texture from an array of pixels."""
111+
if format is None:
112+
assert len(pixels.shape) == 3
113+
assert pixels.dtype == np.uint8
114+
if pixels.shape[2] == 4:
115+
format = int(lib.SDL_PIXELFORMAT_RGBA32)
116+
elif pixels.shape[2] == 3:
117+
format = int(lib.SDL_PIXELFORMAT_RGB32)
118+
else:
119+
assert False
120+
121+
texture = self.new_texture(pixels.shape[1], pixels.shape[0], format=format, access=access)
122+
if not pixels[0].flags["C_CONTIGUOUS"]:
123+
pixels = np.ascontiguousarray(pixels)
124+
_check(
125+
lib.SDL_UpdateTexture(texture.p, ffi.NULL, ffi.cast("const void*", pixels.ctypes.data), pixels.strides[0])
126+
)
127+
return texture

tcod/sdl/sys.py

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from typing import Any, Tuple
55

66
from tcod.loader import ffi, lib
7+
from tcod.sdl import _check
78

89

910
class Subsystem(enum.IntFlag):
@@ -18,12 +19,6 @@ class Subsystem(enum.IntFlag):
1819
EVERYTHING = lib.SDL_INIT_EVERYTHING
1920

2021

21-
def _check(result: int) -> int:
22-
if result < 0:
23-
raise RuntimeError(_get_error())
24-
return result
25-
26-
2722
def init(flags: int = Subsystem.EVERYTHING) -> None:
2823
_check(lib.SDL_InitSubSystem(flags))
2924

@@ -52,10 +47,6 @@ def __exit__(self, *args: Any) -> None:
5247
self.close()
5348

5449

55-
def _get_error() -> str:
56-
return str(ffi.string(lib.SDL_GetError()), encoding="utf-8")
57-
58-
5950
class _PowerState(enum.IntEnum):
6051
UNKNOWN = lib.SDL_POWERSTATE_UNKNOWN
6152
ON_BATTERY = lib.SDL_POWERSTATE_ON_BATTERY

0 commit comments

Comments
 (0)