Skip to content

Commit c0e7401

Browse files
committed
Add many more renderer functions.
1 parent 1149f9a commit c0e7401

File tree

2 files changed

+317
-2
lines changed

2 files changed

+317
-2
lines changed

.vscode/settings.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,7 @@
238238
"randomizer",
239239
"rbutton",
240240
"RCTRL",
241+
"rects",
241242
"redist",
242243
"Redistributable",
243244
"redistributables",

tcod/sdl/render.py

Lines changed: 316 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,14 @@
55
from __future__ import annotations
66

77
import enum
8-
from typing import Any, Optional, Tuple
8+
from typing import Any, Optional, Tuple, Union
99

1010
import numpy as np
1111
from numpy.typing import NDArray
1212

1313
import tcod.sdl.video
1414
from tcod.loader import ffi, lib
15-
from tcod.sdl import _check, _check_p
15+
from tcod.sdl import _check, _check_p, _required_version
1616

1717

1818
class TextureAccess(enum.IntEnum):
@@ -43,6 +43,17 @@ def _query(self) -> Tuple[int, int, int, int]:
4343
lib.SDL_QueryTexture(self.p, format, buffer, buffer + 1, buffer + 2)
4444
return int(format), int(buffer[0]), int(buffer[1]), int(buffer[2])
4545

46+
def update(self, pixels: NDArray[Any], rect: Optional[Tuple[int, int, int, int]] = None) -> None:
47+
"""Update the pixel data of this texture.
48+
49+
.. versionadded:: unreleased
50+
"""
51+
if rect is None:
52+
rect = (0, 0, self.width, self.height)
53+
assert pixels.shape[:2] == rect[3], rect[2]
54+
assert pixels[0].flags.c_contiguous
55+
_check(lib.SDL_UpdateTexture(self.p, (rect,), ffi.cast("void*", pixels.ctypes.data), pixels.strides[0]))
56+
4657
@property
4758
def format(self) -> int:
4859
"""Texture format, read only."""
@@ -204,6 +215,309 @@ def upload_texture(
204215
)
205216
return texture
206217

218+
@property
219+
def draw_color(self) -> Tuple[int, int, int, int]:
220+
"""Get or set the active RGBA draw color for this renderer.
221+
222+
.. versionadded:: unreleased
223+
"""
224+
rgba = ffi.new("uint8_t[4]")
225+
_check(lib.SDL_GetRenderDrawColor(self.p, rgba, rgba + 1, rgba + 2, rgba + 3))
226+
return tuple(rgba) # type: ignore[return-value]
227+
228+
@draw_color.setter
229+
def draw_color(self, rgba: Tuple[int, int, int, int]) -> None:
230+
_check(lib.SDL_SetRenderDrawColor(self.p, *rgba))
231+
232+
@property
233+
def draw_blend_mode(self) -> int:
234+
"""Get or set the active blend mode of this renderer.
235+
236+
.. versionadded:: unreleased
237+
"""
238+
out = ffi.new("SDL_BlendMode*")
239+
_check(lib.SDL_GetRenderDrawBlendMode(self.p, out))
240+
return int(out[0])
241+
242+
@draw_blend_mode.setter
243+
def draw_blend_mode(self, value: int) -> None:
244+
_check(lib.SDL_SetRenderDrawBlendMode(self.p, value))
245+
246+
@property
247+
def output_size(self) -> Tuple[int, int]:
248+
"""Get the (width, height) pixel resolution of the rendering context.
249+
250+
.. seealso::
251+
https://wiki.libsdl.org/SDL_GetRendererOutputSize
252+
253+
.. versionadded:: unreleased
254+
"""
255+
out = ffi.new("int[2]")
256+
_check(lib.SDL_GetRendererOutputSize(self.p, out, out + 1))
257+
return out[0], out[1]
258+
259+
@property
260+
def clip_rect(self) -> Optional[Tuple[int, int, int, int]]:
261+
"""Get or set the clipping rectangle of this renderer.
262+
263+
Set to None to disable clipping.
264+
265+
.. versionadded:: unreleased
266+
"""
267+
if not lib.SDL_RenderIsClipEnabled(self.p):
268+
return None
269+
rect = ffi.new("SDL_Rect*")
270+
lib.SDL_RenderGetClipRect(self.p, rect)
271+
return rect.x, rect.y, rect.w, rect.h
272+
273+
@clip_rect.setter
274+
def clip_rect(self, rect: Optional[Tuple[int, int, int, int]]) -> None:
275+
rect_p = ffi.NULL if rect is None else ffi.new("SDL_Rect*", rect)
276+
_check(lib.SDL_RenderSetClipRect(self.p, rect_p))
277+
278+
@property
279+
def integer_scaling(self) -> bool:
280+
"""Get or set if this renderer enforces integer scaling.
281+
282+
.. seealso::
283+
https://wiki.libsdl.org/SDL_RenderSetIntegerScale
284+
285+
.. versionadded:: unreleased
286+
"""
287+
return bool(lib.SDL_RenderGetIntegerScale(self.p))
288+
289+
@integer_scaling.setter
290+
def integer_scaling(self, enable: bool) -> None:
291+
_check(lib.SDL_RenderSetIntegerScale(self.p, enable))
292+
293+
@property
294+
def logical_size(self) -> Tuple[int, int]:
295+
"""Get or set a device independent (width, height) resolution.
296+
297+
Might be (0, 0) if a resolution was never assigned.
298+
299+
.. seealso::
300+
https://wiki.libsdl.org/SDL_RenderSetLogicalSize
301+
302+
.. versionadded:: unreleased
303+
"""
304+
out = ffi.new("int[2]")
305+
lib.SDL_RenderGetLogicalSize(self.p, out, out + 1)
306+
return out[0], out[1]
307+
308+
@logical_size.setter
309+
def logical_size(self, size: Tuple[int, int]) -> None:
310+
_check(lib.SDL_RenderSetLogicalSize(self.p, *size))
311+
312+
@property
313+
def scale(self) -> Tuple[float, float]:
314+
"""Get or set an (x_scale, y_scale) multiplier for drawing.
315+
316+
.. seealso::
317+
https://wiki.libsdl.org/SDL_RenderSetScale
318+
319+
.. versionadded:: unreleased
320+
"""
321+
out = ffi.new("float[2]")
322+
lib.SDL_RenderGetScale(self.p, out, out + 1)
323+
return out[0], out[1]
324+
325+
@scale.setter
326+
def scale(self, scale: Tuple[float, float]) -> None:
327+
_check(lib.SDL_RenderSetScale(self.p, *scale))
328+
329+
@property
330+
def viewport(self) -> Optional[Tuple[int, int, int, int]]:
331+
"""Get or set the drawing area for the current rendering target.
332+
333+
.. seealso::
334+
https://wiki.libsdl.org/SDL_RenderSetViewport
335+
336+
.. versionadded:: unreleased
337+
"""
338+
rect = ffi.new("SDL_Rect*")
339+
lib.SDL_RenderGetViewport(self.p, rect)
340+
return rect.x, rect.y, rect.w, rect.h
341+
342+
@viewport.setter
343+
def viewport(self, rect: Optional[Tuple[int, int, int, int]]) -> None:
344+
_check(lib.SDL_RenderSetViewport(self.p, (rect,)))
345+
346+
def read_pixels(
347+
self,
348+
*,
349+
rect: Optional[Tuple[int, int, int, int]] = None,
350+
format: Optional[int] = None,
351+
out: Optional[NDArray[Any]] = None,
352+
) -> NDArray[Any]:
353+
"""
354+
.. versionadded:: unreleased
355+
"""
356+
if format is None:
357+
format = lib.SDL_PIXELFORMAT_RGBA32
358+
if rect is None:
359+
texture_p = lib.SDL_GetRenderTarget(self.p)
360+
if texture_p:
361+
texture = Texture(texture_p)
362+
rect = (0, 0, texture.width, texture.height)
363+
else:
364+
rect = (0, 0, *self.output_size)
365+
width, height = rect[2:4]
366+
if out is None:
367+
if format == lib.SDL_PIXELFORMAT_RGBA32:
368+
out = np.empty((height, width, 4), dtype=np.uint8)
369+
elif format == lib.SDL_PIXELFORMAT_RGB24:
370+
out = np.empty((height, width, 3), dtype=np.uint8)
371+
else:
372+
raise TypeError("Pixel format not supported yet.")
373+
assert out.shape[:2] == height, width
374+
assert out[0].flags.c_contiguous
375+
_check(lib.SDL_RenderReadPixels(self.p, format, ffi.cast("void*", out.ctypes.data), out.strides[0]))
376+
return out
377+
378+
def clear(self) -> None:
379+
"""Clear the current render target with :any:`draw_color`.
380+
381+
.. versionadded:: unreleased
382+
"""
383+
_check(lib.SDL_RenderClear(self.p))
384+
385+
def fill_rect(self, rect: Tuple[float, float, float, float]) -> None:
386+
"""Fill a rectangle with :any:`draw_color`.
387+
.. versionadded:: unreleased
388+
"""
389+
_check(lib.SDL_RenderFillRectF(self.p, (rect,)))
390+
391+
def draw_rect(self, rect: Tuple[float, float, float, float]) -> None:
392+
"""Draw a rectangle outline.
393+
394+
.. versionadded:: unreleased
395+
"""
396+
_check(lib.SDL_RenderDrawRectF(self.p, (rect,)))
397+
398+
def draw_point(self, xy: Tuple[float, float]) -> None:
399+
"""Draw a point.
400+
401+
.. versionadded:: unreleased
402+
"""
403+
_check(lib.SDL_RenderDrawPointF(self.p, (xy,)))
404+
405+
def draw_line(self, start: Tuple[float, float], end: Tuple[float, float]) -> None:
406+
"""Draw a single line.
407+
408+
.. versionadded:: unreleased
409+
"""
410+
_check(lib.SDL_RenderDrawLineF(self.p, *start, *end))
411+
412+
def fill_rects(self, rects: NDArray[Union[np.intc, np.float32]]) -> None:
413+
"""Fill multiple rectangles from an array.
414+
415+
.. versionadded:: unreleased
416+
"""
417+
assert len(rects.shape) == 2
418+
assert rects.shape[1] == 4
419+
rects = np.ascontiguousarray(rects)
420+
if rects.dtype == np.intc:
421+
_check(lib.SDL_RenderFillRects(self.p, tcod.ffi.from_buffer("SDL_Rect*", rects), rects.shape[0]))
422+
elif rects.dtype == np.float32:
423+
_check(lib.SDL_RenderFillRectsF(self.p, tcod.ffi.from_buffer("SDL_FRect*", rects), rects.shape[0]))
424+
else:
425+
raise TypeError(f"Array must be an np.intc or np.float32 type, got {rects.dtype}.")
426+
427+
def draw_rects(self, rects: NDArray[Union[np.intc, np.float32]]) -> None:
428+
"""Draw multiple outlined rectangles from an array.
429+
430+
.. versionadded:: unreleased
431+
"""
432+
assert len(rects.shape) == 2
433+
assert rects.shape[1] == 4
434+
rects = np.ascontiguousarray(rects)
435+
if rects.dtype == np.intc:
436+
_check(lib.SDL_RenderDrawRects(self.p, tcod.ffi.from_buffer("SDL_Rect*", rects), rects.shape[0]))
437+
elif rects.dtype == np.float32:
438+
_check(lib.SDL_RenderDrawRectsF(self.p, tcod.ffi.from_buffer("SDL_FRect*", rects), rects.shape[0]))
439+
else:
440+
raise TypeError(f"Array must be an np.intc or np.float32 type, got {rects.dtype}.")
441+
442+
def draw_points(self, points: NDArray[Union[np.intc, np.float32]]) -> None:
443+
"""Draw an array of points.
444+
445+
.. versionadded:: unreleased
446+
"""
447+
assert len(points.shape) == 2
448+
assert points.shape[1] == 2
449+
points = np.ascontiguousarray(points)
450+
if points.dtype == np.intc:
451+
_check(lib.SDL_RenderDrawRects(self.p, tcod.ffi.from_buffer("SDL_Point*", points), points.shape[0]))
452+
elif points.dtype == np.float32:
453+
_check(lib.SDL_RenderDrawRectsF(self.p, tcod.ffi.from_buffer("SDL_FPoint*", points), points.shape[0]))
454+
else:
455+
raise TypeError(f"Array must be an np.intc or np.float32 type, got {points.dtype}.")
456+
457+
def draw_lines(self, points: NDArray[Union[np.intc, np.float32]]) -> None:
458+
"""Draw a connected series of lines from an array.
459+
460+
.. versionadded:: unreleased
461+
"""
462+
assert len(points.shape) == 2
463+
assert points.shape[1] == 2
464+
points = np.ascontiguousarray(points)
465+
if points.dtype == np.intc:
466+
_check(lib.SDL_RenderDrawRects(self.p, tcod.ffi.from_buffer("SDL_Point*", points), points.shape[0] - 1))
467+
elif points.dtype == np.float32:
468+
_check(lib.SDL_RenderDrawRectsF(self.p, tcod.ffi.from_buffer("SDL_FPoint*", points), points.shape[0] - 1))
469+
else:
470+
raise TypeError(f"Array must be an np.intc or np.float32 type, got {points.dtype}.")
471+
472+
@_required_version((2, 0, 18))
473+
def geometry(
474+
self,
475+
texture: Optional[Texture],
476+
xy: NDArray[np.float32],
477+
color: NDArray[np.uint8],
478+
uv: NDArray[np.float32],
479+
indices: Optional[NDArray[Union[np.uint8, np.uint16, np.uint32]]] = None,
480+
) -> None:
481+
"""Render triangles from texture and vertex data.
482+
483+
.. versionadded:: unreleased
484+
"""
485+
assert xy.dtype == np.float32
486+
assert len(xy.shape) == 2
487+
assert xy.shape[1] == 2
488+
assert xy[0].flags.c_contiguous
489+
490+
assert color.dtype == np.uint8
491+
assert len(color.shape) == 2
492+
assert color.shape[1] == 4
493+
assert color[0].flags.c_contiguous
494+
495+
assert uv.dtype == np.float32
496+
assert len(uv.shape) == 2
497+
assert uv.shape[1] == 2
498+
assert uv[0].flags.c_contiguous
499+
if indices is not None:
500+
assert indices.dtype.type in (np.uint8, np.uint16, np.uint32, np.int8, np.int16, np.int32)
501+
indices = np.ascontiguousarray(indices)
502+
assert len(indices.shape) == 1
503+
assert xy.shape[0] == color.shape[0] == uv.shape[0]
504+
_check(
505+
lib.SDL_RenderGeometryRaw(
506+
self.p,
507+
texture.p if texture else ffi.NULL,
508+
ffi.cast("float*", xy.ctypes.data),
509+
xy.strides[0],
510+
ffi.cast("uint8_t*", color.ctypes.data),
511+
color.strides[0],
512+
ffi.cast("float*", uv.ctypes.data),
513+
uv.strides[0],
514+
xy.shape[0], # Number of vertices.
515+
ffi.cast("void*", indices.ctypes.data) if indices is not None else ffi.NULL,
516+
indices.size if indices is not None else 0,
517+
indices.itemsize if indices is not None else 0,
518+
)
519+
)
520+
207521

208522
def new_renderer(
209523
window: tcod.sdl.video.Window,

0 commit comments

Comments
 (0)