@@ -82,14 +82,54 @@ def get_tile(self, codepoint: int) -> NDArray[np.uint8]:
8282 )
8383 return tile
8484
85- def set_tile (self , codepoint : int , tile : ArrayLike ) -> None :
85+ def set_tile (self , codepoint : int , tile : Union [ ArrayLike , NDArray [ np . uint8 ]] ) -> None :
8686 """Upload a tile into this array.
8787
88- The tile can be in 32-bit color (height, width, rgba), or grey-scale
89- (height, width). The tile should have a dtype of ``np.uint8``.
88+ Args:
89+ codepoint (int): The Unicode codepoint you are assigning to.
90+ If the tile is a sprite rather than a common glyph then consider assigning it to a
91+ `Private Use Area <https://en.wikipedia.org/wiki/Private_Use_Areas>`_.
92+ tile (Union[ArrayLike, NDArray[np.uint8]]):
93+ The pixels to use for this tile in row-major order and must be in the same shape as :any:`tile_shape`.
94+ `tile` can be an RGBA array with the shape of ``(height, width, rgba)``, or a grey-scale array with the
95+ shape ``(height, width)``.
96+ The `tile` array will be converted to a dtype of ``np.uint8``.
97+
98+ An RGB array as an input is too ambiguous and an alpha channel must be added, for example if an image has a key
99+ color than the key color pixels must have their alpha channel set to zero.
100+
101+ This data may be immediately sent to VRAM, which can be a slow operation.
102+
103+ Example::
104+
105+ # Examples use imageio for image loading, see https://imageio.readthedocs.io
106+ tileset: tcod.tileset.Tileset # This example assumes you are modifying an existing tileset.
107+
108+ # Normal usage when a tile already has its own alpha channel.
109+ # The loaded tile must be the correct shape for the tileset you assign it to.
110+ # The tile is assigned to a private use area and will not conflict with any exiting codepoint.
111+ tileset.set_tile(0x100000, imageio.load("rgba_tile.png"))
112+
113+ # Load a greyscale tile.
114+ tileset.set_tile(0x100001, imageio.load("greyscale_tile.png"), pilmode="L")
115+ # If you are stuck with an RGB array then you can use the red channel as the input: `rgb[:, :, 0]`
116+
117+ # Loads an RGB sprite without a background.
118+ tileset.set_tile(0x100002, imageio.load("rgb_no_background.png", pilmode="RGBA"))
119+ # If you're stuck with an RGB array then you can pad the channel axis with an alpha of 255:
120+ # rgba = np.pad(rgb, pad_width=((0, 0), (0, 0), (0, 1)), constant_values=255)
121+
122+ # Loads an RGB sprite with a key color background.
123+ KEY_COLOR = np.asarray((255, 0, 255), dtype=np.uint8)
124+ sprite_rgb = imageio.load("rgb_tile.png")
125+ # Compare the RGB colors to KEY_COLOR, compress full matches to a 2D mask.
126+ sprite_mask = (sprite_rgb != KEY_COLOR).all(axis=2)
127+ # Generate the alpha array, with 255 as the foreground and 0 as the background.
128+ sprite_alpha = sprite_mask.astype(np.uint8) * 255
129+ # Combine the RGB and alpha arrays into an RGBA array.
130+ sprite_rgba = np.append(sprite_rgb, sprite_alpha, axis=2)
131+ tileset.set_tile(0x100003, sprite_rgba)
90132
91- This data may need to be sent to graphics card memory, this is a slow
92- operation.
93133 """
94134 tile = np .ascontiguousarray (tile , dtype = np .uint8 )
95135 if tile .shape == self .tile_shape :
@@ -99,7 +139,13 @@ def set_tile(self, codepoint: int, tile: ArrayLike) -> None:
99139 return self .set_tile (codepoint , full_tile )
100140 required = self .tile_shape + (4 ,)
101141 if tile .shape != required :
102- raise ValueError ("Tile shape must be %r or %r, got %r." % (required , self .tile_shape , tile .shape ))
142+ note = ""
143+ if len (tile .shape ) == 3 and tile .shape [2 ] == 3 :
144+ note = (
145+ "\n Note: An RGB array is too ambiguous,"
146+ " an alpha channel must be added to this array to divide the background/foreground areas."
147+ )
148+ raise ValueError (f"Tile shape must be { required } or { self .tile_shape } , got { tile .shape } .{ note } " )
103149 lib .TCOD_tileset_set_tile_ (
104150 self ._tileset_p ,
105151 codepoint ,
@@ -298,6 +344,105 @@ def load_tilesheet(path: Union[str, Path], columns: int, rows: int, charmap: Opt
298344 return Tileset ._claim (cdata )
299345
300346
347+ def procedural_block_elements (tileset : Tileset ) -> None :
348+ """Overwrites the block element codepoints in `tileset` with prodecually generated glyphs.
349+
350+ Args:
351+ tileset (Tileset): A :any:`Tileset` with tiles of any shape.
352+
353+ This will overwrite all of the codepoints `listed here <https://en.wikipedia.org/wiki/Block_Elements>`_
354+ except for the shade glyphs.
355+
356+ This function is useful for other functions such as :any:`Console.draw_semigraphics` which use more types of block
357+ elements than are found in Code Page 437.
358+
359+ .. versionadded:: 13.1
360+
361+ Example::
362+
363+ >>> tileset = tcod.tileset.Tileset(8, 8)
364+ >>> tcod.tileset.procedural_block_elements(tileset)
365+ >>> tileset.get_tile(0x259E)[:, :, 3] # "▞" Quadrant upper right and lower left.
366+ array([[ 0, 0, 0, 0, 255, 255, 255, 255],
367+ [ 0, 0, 0, 0, 255, 255, 255, 255],
368+ [ 0, 0, 0, 0, 255, 255, 255, 255],
369+ [ 0, 0, 0, 0, 255, 255, 255, 255],
370+ [255, 255, 255, 255, 0, 0, 0, 0],
371+ [255, 255, 255, 255, 0, 0, 0, 0],
372+ [255, 255, 255, 255, 0, 0, 0, 0],
373+ [255, 255, 255, 255, 0, 0, 0, 0]], dtype=uint8)
374+ >>> tileset.get_tile(0x2581)[:, :, 3] # "▁" Lower one eighth block.
375+ array([[ 0, 0, 0, 0, 0, 0, 0, 0],
376+ [ 0, 0, 0, 0, 0, 0, 0, 0],
377+ [ 0, 0, 0, 0, 0, 0, 0, 0],
378+ [ 0, 0, 0, 0, 0, 0, 0, 0],
379+ [ 0, 0, 0, 0, 0, 0, 0, 0],
380+ [ 0, 0, 0, 0, 0, 0, 0, 0],
381+ [ 0, 0, 0, 0, 0, 0, 0, 0],
382+ [255, 255, 255, 255, 255, 255, 255, 255]], dtype=uint8)
383+ >>> tileset.get_tile(0x258D)[:, :, 3] # "▍" Left three eighths block.
384+ array([[255, 255, 255, 0, 0, 0, 0, 0],
385+ [255, 255, 255, 0, 0, 0, 0, 0],
386+ [255, 255, 255, 0, 0, 0, 0, 0],
387+ [255, 255, 255, 0, 0, 0, 0, 0],
388+ [255, 255, 255, 0, 0, 0, 0, 0],
389+ [255, 255, 255, 0, 0, 0, 0, 0],
390+ [255, 255, 255, 0, 0, 0, 0, 0],
391+ [255, 255, 255, 0, 0, 0, 0, 0]], dtype=uint8)
392+ """
393+ quadrants : NDArray [np .uint8 ] = np .zeros (tileset .tile_shape , dtype = np .uint8 )
394+ half_height = tileset .tile_height // 2
395+ half_width = tileset .tile_width // 2
396+ quadrants [:half_height , :half_width ] = 0b1000 # Top-left.
397+ quadrants [:half_height , half_width :] = 0b0100 # Top-right.
398+ quadrants [half_height :, :half_width ] = 0b0010 # Bottom-left.
399+ quadrants [half_height :, half_width :] = 0b0001 # Bottom-right.
400+
401+ for codepoint , quad_mask in (
402+ (0x2580 , 0b1100 ), # "▀" Upper half block.
403+ (0x2584 , 0b0011 ), # "▄" Lower half block.
404+ (0x2588 , 0b1111 ), # "█" Full block.
405+ (0x258C , 0b1010 ), # "▌" Left half block.
406+ (0x2590 , 0b0101 ), # "▐" Right half block.
407+ (0x2596 , 0b0010 ), # "▖" Quadrant lower left.
408+ (0x2597 , 0b0001 ), # "▗" Quadrant lower right.
409+ (0x2598 , 0b1000 ), # "▘" Quadrant upper left.
410+ (0x2599 , 0b1011 ), # "▙" Quadrant upper left and lower left and lower right.
411+ (0x259A , 0b1001 ), # "▚" Quadrant upper left and lower right.
412+ (0x259B , 0b1110 ), # "▛" Quadrant upper left and upper right and lower left.
413+ (0x259C , 0b1101 ), # "▜" Quadrant upper left and upper right and lower right.
414+ (0x259D , 0b0100 ), # "▝" Quadrant upper right.
415+ (0x259E , 0b0110 ), # "▞" Quadrant upper right and lower left.
416+ (0x259F , 0b0111 ), # "▟" Quadrant upper right and lower left and lower right.
417+ ):
418+ alpha : NDArray [np .uint8 ] = np .asarray ((quadrants & quad_mask ) != 0 , dtype = np .uint8 ) * 255
419+ tileset .set_tile (codepoint , alpha )
420+
421+ for codepoint , axis , fraction , negative in (
422+ (0x2581 , 0 , 7 , True ), # "▁" Lower one eighth block.
423+ (0x2582 , 0 , 6 , True ), # "▂" Lower one quarter block.
424+ (0x2583 , 0 , 5 , True ), # "▃" Lower three eighths block.
425+ (0x2585 , 0 , 3 , True ), # "▅" Lower five eighths block.
426+ (0x2586 , 0 , 2 , True ), # "▆" Lower three quarters block.
427+ (0x2587 , 0 , 1 , True ), # "▇" Lower seven eighths block.
428+ (0x2589 , 1 , 7 , False ), # "▉" Left seven eighths block.
429+ (0x258A , 1 , 6 , False ), # "▊" Left three quarters block.
430+ (0x258B , 1 , 5 , False ), # "▋" Left five eighths block.
431+ (0x258D , 1 , 3 , False ), # "▍" Left three eighths block.
432+ (0x258E , 1 , 2 , False ), # "▎" Left one quarter block.
433+ (0x258F , 1 , 1 , False ), # "▏" Left one eighth block.
434+ (0x2594 , 0 , 1 , False ), # "▔" Upper one eighth block.
435+ (0x2595 , 1 , 7 , True ), # "▕" Right one eighth block .
436+ ):
437+ indexes = [slice (None ), slice (None )]
438+ divide = tileset .tile_shape [axis ] * fraction // 8
439+ # If negative then shade from the far corner, otherwise shade from the near corner.
440+ indexes [axis ] = slice (divide , None ) if negative else slice (None , divide )
441+ alpha = np .zeros (tileset .tile_shape , dtype = np .uint8 )
442+ alpha [tuple (indexes )] = 255
443+ tileset .set_tile (codepoint , alpha )
444+
445+
301446CHARMAP_CP437 = [
302447 0x0000 ,
303448 0x263A ,
0 commit comments