99
1010import numpy as np
1111from numpy .typing import NDArray
12- from typing_extensions import Final
12+ from typing_extensions import Final , Literal
1313
1414import tcod .sdl .video
1515from 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 ,)))
0 commit comments