11 from collections import namedtuple
12 from PIL import Image, ImageDraw, ImagePalette
20 # Don't import pillow if we're running in the CI. We could mock it out but that
21 # would require mocking in all reverse dependencies.
22 if "GITHUB_ACTION" not in os.environ and "READTHEDOCS" not in os.environ:
23 # This will only work on Linux
26 # this would be for Github Actions
27 utils = None # pylint: disable=invalid-name
29 __version__ = "0.0.0-auto.0"
30 __repo__ = "https://github.com/adafruit/Adafruit_Blinka_displayio.git"
34 Rectangle = namedtuple("Rectangle", "x1 y1 x2 y2")
37 def release_displays():
38 """Releases any actively used displays so their busses and pins can be used again.
40 Use this once in your code.py if you initialize a display. Place it right before the initialization so the display is active as long as possible.
42 for _disp in _displays:
48 """Stores values of a certain size in a 2D array"""
50 def __init__(self, width, height, value_count):
51 """Create a Bitmap object with the given fixed size. Each pixel stores a value that is used to index into a corresponding palette. This enables differently colored sprites to share the underlying Bitmap. value_count is used to minimize the memory used to store the Bitmap.
55 self._read_only = False
58 raise ValueError("value_count must be > 0")
61 while (value_count - 1) >> bits:
67 self._bits_per_value = bits
70 self._bits_per_value > 8
71 and self._bits_per_value != 16
72 and self._bits_per_value != 32
74 raise NotImplementedError("Invalid bits per value")
76 self._data = (width * height) * [0]
77 self._dirty_area = Rectangle(0, 0, width, height)
79 def __getitem__(self, index):
81 Returns the value at the given index. The index can either be
82 an x,y tuple or an int equal to `y * width + x`.
84 if isinstance(index, (tuple, list)):
85 index = (index[1] * self._width) + index[0]
86 if index >= len(self._data):
87 raise ValueError("Index {} is out of range".format(index))
88 return self._data[index]
90 def __setitem__(self, index, value):
92 Sets the value at the given index. The index can either be
93 an x,y tuple or an int equal to `y * width + x`.
96 raise RuntimeError("Read-only object")
97 if isinstance(index, (tuple, list)):
100 index = y * self._width + x
101 elif ininstance(index, int):
102 x = index % self._width
103 y = index // self._width
104 self._data[index] = value
105 if self._dirty_area.x1 == self._dirty_area.x2:
106 self._dirty_area.x1 = x
107 self._dirty_area.x2 = x + 1
108 self._dirty_area.y1 = y
109 self._dirty_area.y2 = y + 1
111 if x < self._dirty_area.x1:
112 self._dirty_area.x1 = x
113 elif x >= self._dirty_area.x2:
114 self._dirty_area.x2 = x + 1
115 if y < self._dirty_area.y1:
116 self._dirty_area.y1 = y
117 elif y >= self._dirty_area.y2:
118 self._dirty_area.y2 = y + 1
120 def _finish_refresh(self):
121 self._dirty_area.x1 = 0
122 self._dirty_area.x2 = 0
124 def fill(self, value):
125 """Fills the bitmap with the supplied palette index value."""
126 self._data = (self._width * self._height) * [value]
127 self._dirty_area = Rectangle(0, 0, self._width, self._height)
131 """Width of the bitmap. (read only)"""
136 """Height of the bitmap. (read only)"""
140 class ColorConverter:
141 """Converts one color format to another. Color converter based on original displayio
142 code for consistency.
145 def __init__(self, *, dither=False):
146 """Create a ColorConverter object to convert color formats.
147 Only supports RGB888 to RGB565 currently.
148 :param bool dither: Adds random noise to dither the output image
150 self._dither = dither
153 def _compute_rgb565(self, color):
155 return (color >> 19) << 11 | ((color >> 10) & 0x3F) << 5 | (color >> 3) & 0x1F
157 def _compute_luma(self, color):
159 g8 = (color >> 8) & 0xFF
161 return (r8 * 19) / 255 + (g8 * 182) / 255 + (b8 + 54) / 255
163 def _compute_chroma(self, color):
165 g8 = (color >> 8) & 0xFF
167 return max(r8, g8, b8) - min(r8, g8, b8)
169 def _compute_hue(self, color):
171 g8 = (color >> 8) & 0xFF
173 max_color = max(r8, g8, b8)
174 chroma = self._compute_chroma(color)
179 hue = (((g8 - b8) * 40) / chroma) % 240
180 elif max_color == g8:
181 hue = (((b8 - r8) + (2 * chroma)) * 40) / chroma
182 elif max_color == b8:
183 hue = (((r8 - g8) + (4 * chroma)) * 40) / chroma
189 def _dither_noise_1(self, noise):
191 nn = (n * (n * n * 60493 + 19990303) + 1376312589) & 0x7FFFFFFF
192 return (nn / (1073741824.0 * 2)) * 255
194 def _dither_noise_2(self, x, y):
195 return self._dither_noise_1(x + y * 0xFFFF)
197 def _compute_tricolor(self):
200 def convert(self, color):
201 "Converts the given RGB888 color to RGB565"
203 return color # To Do: return a dithered color
205 return self._compute_rgb565(color)
207 def _pil_palette(self):
212 "When true the color converter dithers the output by adding random noise when truncating to display bitdepth"
216 def dither(self, value):
217 if not isinstance(value, bool):
218 raise ValueError("Value should be boolean")
223 """This initializes a display and connects it into CircuitPython. Unlike other objects in CircuitPython, Display objects live until ``displayio.release_displays()`` is called. This is done so that CircuitPython can use the display itself.
225 Most people should not use this class directly. Use a specific display driver instead that will contain the initialization sequence at minimum.
227 .. class:: Display(display_bus, init_sequence, *, width, height, colstart=0, rowstart=0, rotation=0, color_depth=16, grayscale=False, pixels_in_byte_share_row=True, bytes_per_cell=1, reverse_pixels_in_byte=False, set_column_command=0x2a, set_row_command=0x2b, write_ram_command=0x2c, set_vertical_scroll=0, backlight_pin=None, brightness_command=None, brightness=1.0, auto_brightness=False, single_byte_bounds=False, data_as_commands=False, auto_refresh=True, native_frames_per_second=60)
243 pixels_in_byte_share_row=True,
245 reverse_pixels_in_byte=False,
246 set_column_command=0x2A,
247 set_row_command=0x2B,
248 write_ram_command=0x2C,
249 set_vertical_scroll=0,
251 brightness_command=None,
253 auto_brightness=False,
254 single_byte_bounds=False,
255 data_as_commands=False,
257 native_frames_per_second=60
259 """Create a Display object on the given display bus (`displayio.FourWire` or `displayio.ParallelBus`).
261 The ``init_sequence`` is bitpacked to minimize the ram impact. Every command begins with a command byte followed by a byte to determine the parameter count and if a delay is need after. When the top bit of the second byte is 1, the next byte will be the delay time in milliseconds. The remaining 7 bits are the parameter count excluding any delay byte. The third through final bytes are the remaining command parameters. The next byte will begin a new command definition. Here is a portion of ILI9341 init code:
262 .. code-block:: python
264 init_sequence = (b"\xe1\x0f\x00\x0E\x14\x03\x11\x07\x31\xC1\x48\x08\x0F\x0C\x31\x36\x0F" # Set Gamma
265 b"\x11\x80\x78"# Exit Sleep then delay 0x78 (120ms)
266 b"\x29\x80\x78"# Display on then delay 0x78 (120ms)
268 display = displayio.Display(display_bus, init_sequence, width=320, height=240)
270 The first command is 0xe1 with 15 (0xf) parameters following. The second and third are 0x11 and 0x29 respectively with delays (0x80) of 120ms (0x78) and no parameters. Multiple byte literals (b”“) are merged together on load. The parens are needed to allow byte literals on subsequent lines.
272 The initialization sequence should always leave the display memory access inline with the scan of the display to minimize tearing artifacts.
274 self._bus = display_bus
275 self._set_column_command = set_column_command
276 self._set_row_command = set_row_command
277 self._write_ram_command = write_ram_command
278 self._brightness_command = brightness_command
279 self._data_as_commands = data_as_commands
280 self._single_byte_bounds = single_byte_bounds
282 self._height = height
283 self._colstart = colstart
284 self._rowstart = rowstart
285 self._rotation = rotation
286 self._auto_brightness = auto_brightness
287 self._brightness = brightness
288 self._auto_refresh = auto_refresh
289 self._initialize(init_sequence)
290 self._buffer = Image.new("RGB", (width, height))
291 self._subrectangles = []
292 self._bounds_encoding = ">BB" if single_byte_bounds else ">HH"
293 self._current_group = None
294 _displays.append(self)
295 self._refresh_thread = None
296 if self._auto_refresh:
297 self.auto_refresh = True
299 def _initialize(self, init_sequence):
301 while i < len(init_sequence):
302 command = init_sequence[i]
303 data_size = init_sequence[i + 1]
304 delay = (data_size & 0x80) > 0
306 data_byte = init_sequence[i + 2]
307 self._write(command, init_sequence[i + 2 : i + 2 + data_size])
311 delay_time_ms = init_sequence[i + 1 + data_size]
312 if delay_time_ms == 255:
314 time.sleep(delay_time_ms / 1000)
317 def _write(self, command, data):
318 if self._single_byte_bounds:
319 self._bus.send(True, bytes([command]) + data, toggle_every_byte=True)
321 self._bus.send(True, bytes([command]), toggle_every_byte=True)
322 self._bus.send(False, data)
328 def show(self, group):
329 """Switches to displaying the given group of layers. When group is None, the default CircuitPython terminal will be shown.
331 self._current_group = group
333 def refresh(self, *, target_frames_per_second=60, minimum_frames_per_second=1):
334 """When auto refresh is off, waits for the target frame rate and then refreshes the display, returning True. If the call has taken too long since the last refresh call for the given target frame rate, then the refresh returns False immediately without updating the screen to hopefully help getting caught up.
336 If the time since the last successful refresh is below the minimum frame rate, then an exception will be raised. Set minimum_frames_per_second to 0 to disable.
338 When auto refresh is on, updates the display immediately. (The display will also update without calls to this.)
340 # Go through groups and and add each to buffer
341 if self._current_group is not None:
342 buffer = Image.new("RGB", (self._width, self._height))
343 # Recursively have everything draw to the image
344 self._current_group._fill_area(buffer)
345 # save image to buffer (or probably refresh buffer so we can compare)
346 self._buffer.paste(buffer)
349 # Eventually calculate dirty rectangles here
350 self._subrectangles.append(Rectangle(0, 0, self._width, self._height))
352 for area in self._subrectangles:
353 self._refresh_display_area(area)
355 def _refresh_loop(self):
356 while self._auto_refresh:
359 def _refresh_display_area(self, rectangle):
360 """Loop through dirty rectangles and redraw that area."""
361 """Read or write a block of data."""
362 data = numpy.array(self._buffer.crop(rectangle).convert("RGB")).astype("uint16")
364 ((data[:, :, 0] & 0xF8) << 8)
365 | ((data[:, :, 1] & 0xFC) << 3)
366 | (data[:, :, 2] >> 3)
370 numpy.dstack(((color >> 8) & 0xFF, color & 0xFF)).flatten().tolist()
374 self._set_column_command,
376 rectangle.x1 + self._colstart, rectangle.x2 + self._colstart
380 self._set_row_command,
382 rectangle.y1 + self._rowstart, rectangle.y2 + self._rowstart
385 self._write(self._write_ram_command, pixels)
387 def _encode_pos(self, x, y):
388 """Encode a postion into bytes."""
389 return struct.pack(self._bounds_encoding, x, y)
391 def fill_row(self, y, buffer):
395 def auto_refresh(self):
396 return self._auto_refresh
399 def auto_refresh(self, value):
400 self._auto_refresh = value
401 if self._refresh_thread is None:
402 self._refresh_thread = threading.Thread(
403 target=self._refresh_loop, daemon=True
405 if value and not self._refresh_thread.is_alive():
407 self._refresh_thread.start()
408 elif not value and self._refresh_thread.is_alive():
410 self._refresh_thread.join()
413 def brightness(self):
414 """The brightness of the display as a float. 0.0 is off and 1.0 is full `brightness`. When `auto_brightness` is True, the value of `brightness` will change automatically. If `brightness` is set, `auto_brightness` will be disabled and will be set to False.
416 return self._brightness
419 def brightness(self, value):
420 self._brightness = value
423 def auto_brightness(self):
424 """True when the display brightness is adjusted automatically, based on an ambient light sensor or other method. Note that some displays may have this set to True by default, but not actually implement automatic brightness adjustment. `auto_brightness` is set to False if `brightness` is set manually.
426 return self._auto_brightness
428 @auto_brightness.setter
429 def auto_brightness(self, value):
430 self._auto_brightness = value
442 """The rotation of the display as an int in degrees."""
443 return self._rotation
446 def rotation(self, value):
447 if value not in (0, 90, 180, 270):
448 raise ValueError("Rotation must be 0/90/180/270")
449 self._rotation = value
470 set_column_window_command=None,
471 set_row_window_command=None,
472 single_byte_bounds=False,
473 write_black_ram_command,
474 black_bits_inverted=False,
475 write_color_ram_command=None,
476 color_bits_inverted=False,
477 highlight_color=0x000000,
478 refresh_display_command,
482 seconds_per_frame=180,
483 always_toggle_chip_select=False
486 Create a EPaperDisplay object on the given display bus (displayio.FourWire or displayio.ParallelBus).
488 The start_sequence and stop_sequence are bitpacked to minimize the ram impact. Every command begins with a command byte followed by a byte to determine the parameter count and if a delay is need after. When the top bit of the second byte is 1, the next byte will be the delay time in milliseconds. The remaining 7 bits are the parameter count excluding any delay byte. The third through final bytes are the remaining command parameters. The next byte will begin a new command definition.
492 def show(self, group):
493 """Switches to displaying the given group of layers. When group is None, the default CircuitPython terminal will be shown.
498 """Refreshes the display immediately or raises an exception if too soon. Use ``time.sleep(display.time_to_refresh)`` to sleep until a refresh can occur.
503 def time_to_refresh(self):
504 """Time, in fractional seconds, until the ePaper display can be refreshed."""
521 """Manage updating a display over SPI four wire protocol in the background while
522 Python code runs. It doesn’t handle display initialization.
536 """Create a FourWire object associated with the given pins.
538 The SPI bus and pins are then in use by the display until displayio.release_displays() is called even after a reload. (It does this so CircuitPython can use the display after your code is done.) So, the first time you initialize a display bus in code.py you should call :py:func`displayio.release_displays` first, otherwise it will error after the first code.py run.
540 self._dc = digitalio.DigitalInOut(command)
541 self._dc.switch_to_output()
542 self._chip_select = digitalio.DigitalInOut(chip_select)
543 self._chip_select.switch_to_output(value=True)
545 if reset is not None:
546 self._reset = digitalio.DigitalInOut(reset)
547 self._reset.switch_to_output(value=True)
551 while self._spi.try_lock():
553 self._spi.configure(baudrate=baudrate, polarity=polarity, phase=phase)
560 self._chip_select.deinit()
561 if self._reset is not None:
565 if self._reset is not None:
566 self._reset.value = False
568 self._reset.value = True
571 def send(self, is_command, data, *, toggle_every_byte=False):
572 while self._spi.try_lock():
574 self._dc.value = not is_command
575 if toggle_every_byte:
577 self._spi.write(bytes([byte]))
578 self._chip_select.value = True
580 self._chip_select.value = False
582 self._spi.write(data)
587 """Manage a group of sprites and groups and how they are inter-related."""
589 def __init__(self, *, max_size=4, scale=1, x=0, y=0):
590 """Create a Group of a given size and scale. Scale is in
591 one dimension. For example, scale=2 leads to a layer’s
592 pixel being 2x2 pixels when in the group.
594 if not isinstance(max_size, int) or max_size < 1:
595 raise ValueError("Max Size must be an integer and >= 1")
596 self._max_size = max_size
597 if not isinstance(scale, int) or scale < 1:
598 raise ValueError("Scale must be an integer and >= 1")
604 self._supported_types = (TileGrid, Group)
606 def append(self, layer):
607 """Append a layer to the group. It will be drawn
610 if not isinstance(layer, self._supported_types):
611 raise ValueError("Invalid Group Memeber")
612 if len(self._layers) == self._max_size:
613 raise RuntimeError("Group full")
614 self._layers.append(layer)
616 def insert(self, index, layer):
617 """Insert a layer into the group."""
618 if not isinstance(layer, self._supported_types):
619 raise ValueError("Invalid Group Memeber")
620 if len(self._layers) == self._max_size:
621 raise RuntimeError("Group full")
622 self._layers.insert(index, layer)
624 def index(self, layer):
625 """Returns the index of the first copy of layer.
626 Raises ValueError if not found.
630 def pop(self, index=-1):
631 """Remove the ith item and return it."""
632 return self._layers.pop(index)
634 def remove(self, layer):
635 """Remove the first copy of layer. Raises ValueError
636 if it is not present."""
640 """Returns the number of layers in a Group"""
641 return len(self._layers)
643 def __getitem__(self, index):
644 """Returns the value at the given index."""
645 return self._layers[index]
647 def __setitem__(self, index, value):
648 """Sets the value at the given index."""
649 self._layers[index] = value
651 def __delitem__(self, index):
652 """Deletes the value at the given index."""
653 del self._layers[index]
655 def _fill_area(self, buffer):
659 for layer in self._layers:
660 if isinstance(layer, (Group, TileGrid)):
661 layer._fill_area(buffer)
668 def hidden(self, value):
669 if not isinstance(value, (bool, int)):
670 raise ValueError("Expecting a boolean or integer value")
671 self._hidden = bool(value)
678 def scale(self, value):
679 if not isinstance(value, int) or value < 1:
680 raise ValueError("Scale must be an integer and at least 1")
689 if not isinstance(value, int):
690 raise ValueError("x must be an integer")
699 if not isinstance(value, int):
700 raise ValueError("y must be an integer")
705 """Manage updating a display over I2C in the background while Python code runs. It doesn’t handle display initialization.
708 def __init__(self, i2c_bus, *, device_address, reset=None):
709 """Create a I2CDisplay object associated with the given I2C bus and reset pin.
711 The I2C bus and pins are then in use by the display until displayio.release_displays() is called even after a reload. (It does this so CircuitPython can use the display after your code is done.) So, the first time you initialize a display bus in code.py you should call :py:func`displayio.release_displays` first, otherwise it will error after the first code.py run.
718 def send(self, command, data):
724 Loads values straight from disk. This minimizes memory use but can lead to much slower pixel load times.
725 These load times may result in frame tearing where only part of the image is visible."""
727 def __init__(self, file):
728 self._image = Image.open(file)
732 """Width of the bitmap. (read only)"""
733 return self._image.width
737 """Height of the bitmap. (read only)"""
738 return self._image.height
742 """Map a pixel palette_index to a full color. Colors are transformed to the display’s format internally to save memory."""
744 def __init__(self, color_count):
745 """Create a Palette object to store a set number of colors."""
746 self._needs_refresh = False
749 for _ in range(color_count):
750 self._colors.append(self._make_color(0))
752 def _make_color(self, value):
754 "transparent": False,
757 color_converter = ColorConverter()
758 if isinstance(value, (tuple, list, bytes, bytearray)):
759 value = (value[0] & 0xFF) << 16 | (value[1] & 0xFF) << 8 | value[2] & 0xFF
760 elif isinstance(value, int):
761 if not 0 <= value <= 0xFFFFFF:
762 raise ValueError("Color must be between 0x000000 and 0xFFFFFF")
764 raise TypeError("Color buffer must be a buffer, tuple, list, or int")
765 color["rgb888"] = value
766 self._needs_refresh = True
771 """Returns the number of colors in a Palette"""
772 return len(self._colors)
774 def __setitem__(self, index, value):
775 """Sets the pixel color at the given index. The index should be an integer in the range 0 to color_count-1.
777 The value argument represents a color, and can be from 0x000000 to 0xFFFFFF (to represent an RGB value). Value can be an int, bytes (3 bytes (RGB) or 4 bytes (RGB + pad byte)), bytearray, or a tuple or list of 3 integers.
779 if self._colors[index]["rgb888"] != value:
780 self._colors[index] = self._make_color(value)
782 def __getitem__(self, index):
783 if not 0 <= index < len(self._colors):
784 raise ValueError("Palette index out of range")
785 return self._colors[index]
787 def make_transparent(self, palette_index):
788 self._colors[palette_index]["transparent"] = True
790 def make_opaque(self, palette_index):
791 self._colors[palette_index]["transparent"] = False
795 """Manage updating a display over 8-bit parallel bus in the background while Python code runs.
796 This protocol may be refered to as 8080-I Series Parallel Interface in datasheets.
797 It doesn’t handle display initialization.
800 def __init__(self, i2c_bus, *, device_address, reset=None):
801 """Create a ParallelBus object associated with the given pins. The bus is inferred from data0 by implying the next 7 additional pins on a given GPIO port.
803 The parallel bus and pins are then in use by the display until displayio.release_displays() is called even after a reload. (It does this so CircuitPython can use the display after your code is done.) So, the first time you initialize a display bus in code.py you should call :py:func`displayio.release_displays` first, otherwise it will error after the first code.py run.
808 """Performs a hardware reset via the reset pin. Raises an exception if called when no reset pin is available.
812 def send(self, command, data):
813 """Sends the given command value followed by the full set of data. Display state, such as
814 vertical scroll, set via ``send`` may or may not be reset once the code is done.
820 """Create a Shape object with the given fixed size. Each pixel is one bit and is stored by the column
821 boundaries of the shape on each row. Each row’s boundary defaults to the full row.
824 def __init__(self, width, height, *, mirror_x=False, mirror_y=False):
825 """Create a Shape object with the given fixed size. Each pixel is one bit and is stored by the
826 column boundaries of the shape on each row. Each row’s boundary defaults to the full row.
830 def set_boundary(self, y, start_x, end_x):
831 """Loads pre-packed data into the given row."""
836 """Position a grid of tiles sourced from a bitmap and pixel_shader combination. Multiple grids can share bitmaps and pixel shaders.
838 A single tile grid is also known as a Sprite.
854 """Create a TileGrid object. The bitmap is source for 2d pixels. The pixel_shader is used to convert the value and its location to a display native pixel color. This may be a simple color palette lookup, a gradient, a pattern or a color transformer.
856 tile_width and tile_height match the height of the bitmap by default.
858 if not isinstance(bitmap, (Bitmap, OnDiskBitmap, Shape)):
859 raise ValueError("Unsupported Bitmap type")
860 self._bitmap = bitmap
861 bitmap_width = bitmap.width
862 bitmap_height = bitmap.height
864 if not isinstance(pixel_shader, (ColorConverter, Palette)):
865 raise ValueError("Unsupported Pixel Shader type")
866 self._pixel_shader = pixel_shader
870 self._width = width # Number of Tiles Wide
871 self._height = height # Number of Tiles High
872 if tile_width is None:
873 tile_width = bitmap_width
874 if tile_height is None:
875 tile_height = bitmap_height
876 if bitmap_width % tile_width != 0:
877 raise ValueError("Tile width must exactly divide bitmap width")
878 self._tile_width = tile_width
879 if bitmap_height % tile_height != 0:
880 raise ValueError("Tile height must exactly divide bitmap height")
881 self._tile_height = tile_height
882 if not 0 <= default_tile <= 255:
883 raise ValueError("Default Tile is out of range")
884 self._tiles = (self._width * self._height) * [default_tile]
886 def _fill_area(self, buffer):
887 """Draw onto the image"""
892 "RGB", (self._width * self._tile_width, self._height * self._tile_height)
895 tile_count_x = self._bitmap.width // self._tile_width
896 tile_count_y = self._bitmap.height // self._tile_height
898 for tile_x in range(0, self._width):
899 for tile_y in range(0, self._height):
900 tile_index = self._tiles[tile_y * self._width + tile_x]
901 tile_index_x = tile_index % tile_count_x
902 tile_index_y = tile_index // tile_count_x
903 for pixel_x in range(self._tile_width):
904 for pixel_y in range(self._tile_height):
905 image_x = tile_x * self._tile_width + pixel_x
906 image_y = tile_y * self._tile_height + pixel_y
907 bitmap_x = tile_index_x * self._tile_width + pixel_x
908 bitmap_y = tile_index_y * self._tile_height + pixel_y
909 pixel_color = self._pixel_shader[
910 self._bitmap[bitmap_x, bitmap_y]
912 if not pixel_color["transparent"]:
913 image.putpixel((image_x, image_y), pixel_color["rgb888"])
915 # Apply transforms here
916 if self._tile_width == 6:
917 print("Putting at {}".format((self._x, self._y)))
918 buffer.paste(image, (self._x, self._y))
923 Do any transforms or mirrors or whatever
924 Paste into buffer at our x,y position
929 """True when the TileGrid is hidden. This may be False even when a part of a hidden Group."""
933 def hidden(self, value):
934 if not isinstance(value, (bool, int)):
935 raise ValueError("Expecting a boolean or integer value")
936 self._hidden = bool(value)
940 """X position of the left edge in the parent."""
945 """Y position of the top edge in the parent."""
950 """If true, the left edge rendered will be the right edge of the right-most tile."""
954 def flip_x(self, value):
955 if not isinstance(value, bool):
956 raise TypeError("Flip X should be a boolean type")
961 """If true, the top edge rendered will be the bottom edge of the bottom-most tile."""
965 def flip_y(self, value):
966 if not isinstance(value, bool):
967 raise TypeError("Flip Y should be a boolean type")
971 def transpose_xy(self):
972 """If true, the TileGrid’s axis will be swapped. When combined with mirroring, any 90 degree
973 rotation can be achieved along with the corresponding mirrored version.
975 return self._transpose_xy
978 def transpose_xy(self, value):
979 if not isinstance(value, bool):
980 raise TypeError("Transpose XY should be a boolean type")
981 self._transpose_xy = value
984 def pixel_shader(self):
985 """The pixel shader of the tilegrid."""
988 def __getitem__(self, index):
989 """Returns the tile index at the given index. The index can either be
990 an x,y tuple or an int equal to ``y * width + x``'.
992 if isinstance(index, (tuple, list)):
995 index = y * self._width + x
996 elif ininstance(index, int):
997 x = index % self._width
998 y = index // self._width
999 if x > self._width or y > self._height:
1000 raise ValueError("Tile index out of bounds")
1001 return self._tiles[index]
1003 def __setitem__(self, index, value):
1004 """Sets the tile index at the given index. The index can either be
1005 an x,y tuple or an int equal to ``y * width + x``.
1007 if isinstance(index, (tuple, list)):
1010 index = y * self._width + x
1011 elif ininstance(index, int):
1012 x = index % self._width
1013 y = index // self._width
1014 if x > width or y > self._height or index >= len(self._tiles):
1015 raise ValueError("Tile index out of bounds")
1016 if not 0 <= value <= 255:
1017 raise ValueError("Tile value out of bounds")
1018 self._tiles[index] = value