|
5 | 5 | from __future__ import annotations |
6 | 6 |
|
7 | 7 | import enum |
8 | | -from typing import Any, Optional, Tuple |
| 8 | +from typing import Any, Optional, Tuple, Union |
9 | 9 |
|
10 | 10 | import numpy as np |
11 | 11 | from numpy.typing import NDArray |
12 | 12 |
|
13 | 13 | import tcod.sdl.video |
14 | 14 | from tcod.loader import ffi, lib |
15 | | -from tcod.sdl import _check, _check_p |
| 15 | +from tcod.sdl import _check, _check_p, _required_version |
16 | 16 |
|
17 | 17 |
|
18 | 18 | class TextureAccess(enum.IntEnum): |
@@ -43,6 +43,17 @@ def _query(self) -> Tuple[int, int, int, int]: |
43 | 43 | lib.SDL_QueryTexture(self.p, format, buffer, buffer + 1, buffer + 2) |
44 | 44 | return int(format), int(buffer[0]), int(buffer[1]), int(buffer[2]) |
45 | 45 |
|
| 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 | + |
46 | 57 | @property |
47 | 58 | def format(self) -> int: |
48 | 59 | """Texture format, read only.""" |
@@ -204,6 +215,309 @@ def upload_texture( |
204 | 215 | ) |
205 | 216 | return texture |
206 | 217 |
|
| 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 | + |
207 | 521 |
|
208 | 522 | def new_renderer( |
209 | 523 | window: tcod.sdl.video.Window, |
|
0 commit comments