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"
35 Rectangle = namedtuple("Rectangle", "x1 y1 x2 y2")
37 class _DisplayioSingleton:
42 def release_displays():
43 """Releases any actively used displays so their busses and pins can be used again.
45 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.
47 for _disp in _displays:
52 """Stores values of a certain size in a 2D array"""
54 def __init__(self, width, height, value_count):
55 """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.
59 self._read_only = False
62 raise ValueError("value_count must be > 0")
65 while (value_count - 1) >> bits:
71 self._bits_per_value = bits
74 self._bits_per_value > 8
75 and self._bits_per_value != 16
76 and self._bits_per_value != 32
78 raise NotImplementedError("Invalid bits per value")
80 self._data = (width * height) * [0]
81 self._dirty_area = Rectangle(0, 0, width, height)
83 def __getitem__(self, index):
85 Returns the value at the given index. The index can either be
86 an x,y tuple or an int equal to `y * width + x`.
88 if isinstance(index, (tuple, list)):
89 index = index[1] * self._width + index[0]
90 return self._data[index]
92 def __setitem__(self, index, value):
94 Sets the value at the given index. The index can either be
95 an x,y tuple or an int equal to `y * width + x`.
98 raise RuntimeError("Read-only object")
99 if isinstance(index, (tuple, list)):
102 index = y * self._width + x
103 elif ininstance(index, int):
104 x = index % self._width
105 y = index // self._width
106 self._data[index] = value
107 if self._dirty_area.x1 == self._dirty_area.x2:
108 self._dirty_area.x1 = x
109 self._dirty_area.x2 = x + 1
110 self._dirty_area.y1 = y
111 self._dirty_area.y2 = y + 1
113 if x < self._dirty_area.x1:
114 self._dirty_area.x1 = x
115 elif x >= self._dirty_area.x2:
116 self._dirty_area.x2 = x + 1
117 if y < self._dirty_area.y1:
118 self._dirty_area.y1 = y
119 elif y >= self._dirty_area.y2:
120 self._dirty_area.y2 = y + 1
122 def _finish_refresh(self):
123 self._dirty_area.x1 = 0
124 self._dirty_area.x2 = 0
126 def fill(self, value):
127 """Fills the bitmap with the supplied palette index value."""
128 self._data = (self._width * self._height) * [value]
129 self._dirty_area = Rectangle(0, 0, self._width, self._height)
133 """Width of the bitmap. (read only)"""
138 """Height of the bitmap. (read only)"""
142 class ColorConverter:
143 """Converts one color format to another. Color converter based on original displayio
144 code for consistency.
147 def __init__(self, *, dither=False):
148 """Create a ColorConverter object to convert color formats.
149 Only supports RGB888 to RGB565 currently.
150 :param bool dither: Adds random noise to dither the output image
152 self._dither = dither
155 def _compute_rgb565(self, color):
157 return (color >> 19) << 11 | ((color >> 10) & 0x3F) << 5 | (color >> 3) & 0x1F
159 def _compute_luma(self, color):
161 g8 = (color >> 8) & 0xFF
163 return (r8 * 19) / 255 + (g8 * 182) / 255 + (b8 + 54) / 255
165 def _compute_chroma(self, color):
167 g8 = (color >> 8) & 0xFF
169 return max(r8, g8, b8) - min(r8, g8, b8)
171 def _compute_hue(self, color):
173 g8 = (color >> 8) & 0xFF
175 max_color = max(r8, g8, b8)
176 chroma = self._compute_chroma(color)
181 hue = (((g8 - b8) * 40) / chroma) % 240
182 elif max_color == g8:
183 hue = (((b8 - r8) + (2 * chroma)) * 40) / chroma
184 elif max_color == b8:
185 hue = (((r8 - g8) + (4 * chroma)) * 40) / chroma
191 def _dither_noise_1(self, noise):
193 nn = (n * (n * n * 60493 + 19990303) + 1376312589) & 0x7FFFFFFF
194 return (nn / (1073741824.0 * 2)) * 255
196 def _dither_noise_2(self, x, y):
197 return self._dither_noise_1(x + y * 0xFFFF)
199 def _compute_tricolor(self):
202 def convert(self, color):
203 "Converts the given RGB888 color to RGB565"
205 return color # To Do: return a dithered color
207 return self._compute_rgb565(color)
209 def _pil_palette(self):
214 "When true the color converter dithers the output by adding random noise when truncating to display bitdepth"
218 def dither(self, value):
219 if not isinstance(value, bool):
220 raise ValueError("Value should be boolean")
225 """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.
227 Most people should not use this class directly. Use a specific display driver instead that will contain the initialization sequence at minimum.
229 .. 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)
245 pixels_in_byte_share_row=True,
247 reverse_pixels_in_byte=False,
248 set_column_command=0x2A,
249 set_row_command=0x2B,
250 write_ram_command=0x2C,
251 set_vertical_scroll=0,
253 brightness_command=None,
255 auto_brightness=False,
256 single_byte_bounds=False,
257 data_as_commands=False,
259 native_frames_per_second=60
261 """Create a Display object on the given display bus (`displayio.FourWire` or `displayio.ParallelBus`).
263 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:
264 .. code-block:: python
266 init_sequence = (b"\xe1\x0f\x00\x0E\x14\x03\x11\x07\x31\xC1\x48\x08\x0F\x0C\x31\x36\x0F" # Set Gamma
267 b"\x11\x80\x78"# Exit Sleep then delay 0x78 (120ms)
268 b"\x29\x80\x78"# Display on then delay 0x78 (120ms)
270 display = displayio.Display(display_bus, init_sequence, width=320, height=240)
272 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.
274 The initialization sequence should always leave the display memory access inline with the scan of the display to minimize tearing artifacts.
276 self._bus = display_bus
277 self._set_column_command = set_column_command
278 self._set_row_command = set_row_command
279 self._write_ram_command = write_ram_command
280 self._brightness_command = brightness_command
281 self._data_as_commands = data_as_commands
282 self._single_byte_bounds = single_byte_bounds
284 self._height = height
285 self._colstart = colstart
286 self._rowstart = rowstart
287 self._rotation = rotation
288 self._auto_brightness = auto_brightness
289 self._brightness = brightness
290 self._auto_refresh = auto_refresh
291 self._initialize(init_sequence)
292 self._buffer = Image.new("RGB", (width, height))
293 self._subrectangles = []
294 self._bounds_encoding = ">BB" if single_byte_bounds else ">HH"
295 self._current_group = None
296 _displays.append(self)
297 self._refresh_thread = None
298 if self._auto_refresh:
299 self.auto_refresh = True
301 def _initialize(self, init_sequence):
303 while i < len(init_sequence):
304 command = init_sequence[i]
305 data_size = init_sequence[i + 1]
306 delay = (data_size & 0x80) > 0
308 data_byte = init_sequence[i + 2]
309 self._write(command, init_sequence[i + 2 : i + 2 + data_size])
313 delay_time_ms = init_sequence[i + 1 + data_size]
314 if delay_time_ms == 255:
316 time.sleep(delay_time_ms / 1000)
319 def _write(self, command, data):
320 if self._single_byte_bounds:
321 self._bus.send(True, bytes([command]) + data, toggle_every_byte=True)
323 self._bus.send(True, bytes([command]), toggle_every_byte=True)
324 self._bus.send(False, data)
330 def show(self, group):
331 """Switches to displaying the given group of layers. When group is None, the default CircuitPython terminal will be shown.
333 self._current_group = group
335 def refresh(self, *, target_frames_per_second=60, minimum_frames_per_second=1):
336 """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.
338 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.
340 When auto refresh is on, updates the display immediately. (The display will also update without calls to this.)
342 # Go through groups and and add each to buffer
343 if self._current_group is not None:
344 buffer = Image.new("RGB", (self._width, self._height))
345 # Recursively have everything draw to the image
346 self._current_group._fill_area(buffer)
347 # save image to buffer (or probably refresh buffer so we can compare)
348 self._buffer.paste(buffer)
351 # Eventually calculate dirty rectangles here
352 self._subrectangles.append(Rectangle(0, 0, self._width, self._height))
354 for area in self._subrectangles:
355 self._refresh_display_area(area)
357 def _refresh_loop(self):
358 while self._auto_refresh:
361 def _refresh_display_area(self, rectangle):
362 """Loop through dirty rectangles and redraw that area."""
363 """Read or write a block of data."""
364 data = numpy.array(self._buffer.crop(rectangle).convert("RGB")).astype("uint16")
366 ((data[:, :, 0] & 0xF8) << 8)
367 | ((data[:, :, 1] & 0xFC) << 3)
368 | (data[:, :, 2] >> 3)
371 pixels = list(numpy.dstack(((color >> 8) & 0xFF, color & 0xFF)).flatten().tolist())
374 self._set_column_command,
375 self._encode_pos(rectangle.x1 + self._colstart, rectangle.x2 + self._colstart)
378 self._set_row_command,
379 self._encode_pos(rectangle.y1 + self._rowstart, rectangle.y2 + self._rowstart)
381 self._write(self._write_ram_command, pixels)
383 def _encode_pos(self, x, y):
384 """Encode a postion into bytes."""
385 return struct.pack(self._bounds_encoding, x, y)
387 def fill_row(self, y, buffer):
391 def auto_refresh(self):
392 return self._auto_refresh
395 def auto_refresh(self, value):
396 self._auto_refresh = value
397 if self._refresh_thread is None:
398 self._refresh_thread = threading.Thread(target=self._refresh_loop, daemon=True)
399 if value and not self._refresh_thread.is_alive():
401 self._refresh_thread.start()
402 elif not value and self._refresh_thread.is_alive():
404 self._refresh_thread.join()
407 def brightness(self):
408 """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.
410 return self._brightness
413 def brightness(self, value):
414 self._brightness = value
417 def auto_brightness(self):
418 """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.
420 return self._auto_brightness
422 @auto_brightness.setter
423 def auto_brightness(self, value):
424 self._auto_brightness = value
436 """The rotation of the display as an int in degrees."""
437 return self._rotation
440 def rotation(self, value):
441 if value not in (0, 90, 180, 270):
442 raise ValueError("Rotation must be 0/90/180/270")
443 self._rotation = value
464 set_column_window_command=None,
465 set_row_window_command=None,
466 single_byte_bounds=False,
467 write_black_ram_command,
468 black_bits_inverted=False,
469 write_color_ram_command=None,
470 color_bits_inverted=False,
471 highlight_color=0x000000,
472 refresh_display_command,
476 seconds_per_frame=180,
477 always_toggle_chip_select=False
480 Create a EPaperDisplay object on the given display bus (displayio.FourWire or displayio.ParallelBus).
482 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.
486 def show(self, group):
487 """Switches to displaying the given group of layers. When group is None, the default CircuitPython terminal will be shown.
492 """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.
497 def time_to_refresh(self):
498 """Time, in fractional seconds, until the ePaper display can be refreshed."""
515 """Manage updating a display over SPI four wire protocol in the background while
516 Python code runs. It doesn’t handle display initialization.
530 """Create a FourWire object associated with the given pins.
532 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.
534 self._dc = digitalio.DigitalInOut(command)
535 self._dc.switch_to_output()
536 self._chip_select = digitalio.DigitalInOut(chip_select)
537 self._chip_select.switch_to_output(value=True)
539 if reset is not None:
540 self._reset = digitalio.DigitalInOut(reset)
541 self._reset.switch_to_output(value=True)
545 while self._spi.try_lock():
547 self._spi.configure(baudrate=baudrate, polarity=polarity, phase=phase)
554 self._chip_select.deinit()
555 if self._reset is not None:
559 if self._reset is not None:
560 self._reset.value = False
562 self._reset.value = True
565 def send(self, is_command, data, *, toggle_every_byte=False):
566 while self._spi.try_lock():
568 self._dc.value = not is_command
569 if toggle_every_byte:
571 self._spi.write(bytes([byte]))
572 self._chip_select.value = True
574 self._chip_select.value = False
576 self._spi.write(data)
581 """Manage a group of sprites and groups and how they are inter-related."""
583 def __init__(self, *, max_size=4, scale=1, x=0, y=0):
584 """Create a Group of a given size and scale. Scale is in
585 one dimension. For example, scale=2 leads to a layer’s
586 pixel being 2x2 pixels when in the group.
588 if not isinstance(max_size, int) or max_size < 1:
589 raise ValueError("Max Size must be an integer and >= 1")
590 self._max_size = max_size
591 if not isinstance(scale, int) or scale < 1:
592 raise ValueError("Scale must be an integer and >= 1")
598 self._supported_types = (TileGrid, Group)
599 print("Creating Group")
601 def append(self, layer):
602 """Append a layer to the group. It will be drawn
605 if not isinstance(layer, self._supported_types):
606 raise ValueError("Invalid Group Memeber")
607 if len(self._layers) == self._max_size:
608 raise RuntimeError("Group full")
609 self._layers.append(layer)
611 def insert(self, index, layer):
612 """Insert a layer into the group."""
613 if not isinstance(layer, self._supported_types):
614 raise ValueError("Invalid Group Memeber")
615 if len(self._layers) == self._max_size:
616 raise RuntimeError("Group full")
617 self._layers.insert(index, layer)
619 def index(self, layer):
620 """Returns the index of the first copy of layer.
621 Raises ValueError if not found.
625 def pop(self, index=-1):
626 """Remove the ith item and return it."""
627 return self._layers.pop(index)
629 def remove(self, layer):
630 """Remove the first copy of layer. Raises ValueError
631 if it is not present."""
635 """Returns the number of layers in a Group"""
636 return len(self._layers)
638 def __getitem__(self, index):
639 """Returns the value at the given index."""
640 return self._layers[index]
642 def __setitem__(self, index, value):
643 """Sets the value at the given index."""
644 self._layers[index] = value
646 def __delitem__(self, index):
647 """Deletes the value at the given index."""
648 del self._layers[index]
650 def _fill_area(self, buffer):
654 for layer in self._layers:
655 if isinstance(layer, (Group, TileGrid)):
656 layer._fill_area(buffer)
663 def hidden(self, value):
664 if not isinstance(value, (bool, int)):
665 raise ValueError("Expecting a boolean or integer value")
666 self._hidden = bool(value)
673 def scale(self, value):
674 if not isinstance(value, int) or value < 1:
675 raise ValueError("Scale must be an integer and at least 1")
684 if not isinstance(value, int):
685 raise ValueError("x must be an integer")
694 if not isinstance(value, int):
695 raise ValueError("y must be an integer")
700 """Manage updating a display over I2C in the background while Python code runs. It doesn’t handle display initialization.
703 def __init__(self, i2c_bus, *, device_address, reset=None):
704 """Create a I2CDisplay object associated with the given I2C bus and reset pin.
706 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.
713 def send(self, command, data):
719 Loads values straight from disk. This minimizes memory use but can lead to much slower pixel load times.
720 These load times may result in frame tearing where only part of the image is visible."""
722 def __init__(self, file):
723 self._image = Image.open(file)
727 """Width of the bitmap. (read only)"""
728 return self._image.width
732 """Height of the bitmap. (read only)"""
733 return self._image.height
736 """Map a pixel palette_index to a full color. Colors are transformed to the display’s format internally to save memory."""
738 def __init__(self, color_count):
739 """Create a Palette object to store a set number of colors."""
740 self._needs_refresh = False
743 for _ in range(color_count):
744 self._colors.append(self._make_color(0))
746 def _make_color(self, value):
748 "transparent": False,
751 color_converter = ColorConverter()
752 if isinstance(value, (tuple, list, bytes, bytearray)):
753 value = (value[0] & 0xFF) << 16 | (value[1] & 0xFF) << 8 | value[2] & 0xFF
754 elif isinstance(value, int):
755 if not 0 <= value <= 0xFFFFFF:
756 raise ValueError("Color must be between 0x000000 and 0xFFFFFF")
758 raise TypeError("Color buffer must be a buffer, tuple, list, or int")
759 color["rgb888"] = value
760 self._needs_refresh = True
765 """Returns the number of colors in a Palette"""
766 return len(self._colors)
768 def __setitem__(self, index, value):
769 """Sets the pixel color at the given index. The index should be an integer in the range 0 to color_count-1.
771 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.
773 if self._colors[index]["rgb888"] != value:
774 self._colors[index] = self._make_color(value)
776 def __getitem__(self, index):
777 if not 0 <= index < len(self._colors):
778 raise ValueError("Palette index out of range")
779 return self._colors[index]
781 def make_transparent(self, palette_index):
782 self._colors[palette_index].transparent = True
784 def make_opaque(self, palette_index):
785 self._colors[palette_index].transparent = False
788 """Manage updating a display over 8-bit parallel bus in the background while Python code runs.
789 This protocol may be refered to as 8080-I Series Parallel Interface in datasheets.
790 It doesn’t handle display initialization.
793 def __init__(self, i2c_bus, *, device_address, reset=None):
794 """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.
796 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.
801 """Performs a hardware reset via the reset pin. Raises an exception if called when no reset pin is available.
805 def send(self, command, data):
806 """Sends the given command value followed by the full set of data. Display state, such as
807 vertical scroll, set via ``send`` may or may not be reset once the code is done.
813 """Create a Shape object with the given fixed size. Each pixel is one bit and is stored by the column
814 boundaries of the shape on each row. Each row’s boundary defaults to the full row.
817 def __init__(self, width, height, *, mirror_x=False, mirror_y=False):
818 """Create a Shape object with the given fixed size. Each pixel is one bit and is stored by the
819 column boundaries of the shape on each row. Each row’s boundary defaults to the full row.
823 def set_boundary(self, y, start_x, end_x):
824 """Loads pre-packed data into the given row."""
829 """Position a grid of tiles sourced from a bitmap and pixel_shader combination. Multiple grids can share bitmaps and pixel shaders.
831 A single tile grid is also known as a Sprite.
847 """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.
849 tile_width and tile_height match the height of the bitmap by default.
851 if not isinstance(bitmap, (Bitmap, OnDiskBitmap, Shape)):
852 raise ValueError("Unsupported Bitmap type")
853 self._bitmap = bitmap
854 bitmap_width = bitmap.width
855 bitmap_height = bitmap.height
857 if not isinstance(pixel_shader, (ColorConverter, Palette)):
858 raise ValueError("Unsupported Pixel Shader type")
859 self._pixel_shader = pixel_shader
863 self._width = width # Number of Tiles Wide
864 self._height = height # Number of Tiles High
865 if tile_width is None:
866 tile_width = bitmap_width
867 if tile_height is None:
868 tile_height = bitmap_height
869 if bitmap_width % tile_width != 0:
870 raise ValueError("Tile width must exactly divide bitmap width")
871 self._tile_width = tile_width
872 if bitmap_height % tile_height != 0:
873 raise ValueError("Tile height must exactly divide bitmap height")
874 self._tile_height = tile_height
875 self._tiles = (self._width * self._height) * [default_tile]
877 def _fill_area(self, buffer):
878 """Draw onto the image"""
879 print("Drawing TileGrid")
883 image = Image.new("RGB", (self._width * self._tile_width, self._height * self._tile_height))
885 for tile_x in range(self._width):
886 for tile_y in range(self._height):
887 tile_index = self._tiles[tile_y * self._width + tile_x]
888 tile_index_x = tile_index % self._width
889 tile_index_y = tile_index // self._width
890 for pixel_x in range(self._tile_width):
891 for pixel_y in range(self._tile_height):
892 image_x = tile_x * self._tile_width + pixel_x
893 image_y = tile_y * self._tile_height + pixel_y
894 bitmap_x = tile_index_x * self._tile_width + pixel_x
895 bitmap_y = tile_index_y * self._tile_height + pixel_y
896 pixel_color = self._pixel_shader[self._bitmap[bitmap_x, bitmap_y]]
897 image.putpixel((image_x, image_y), pixel_color["rgb888"])
899 buffer.paste(image, (self._x, self._y))
905 Do any transforms or mirrors or whatever
906 Paste into buffer at our x,y position
912 """True when the TileGrid is hidden. This may be False even when a part of a hidden Group."""
916 def hidden(self, value):
917 if not isinstance(value, (bool, int)):
918 raise ValueError("Expecting a boolean or integer value")
919 self._hidden = bool(value)
923 """X position of the left edge in the parent."""
928 """Y position of the top edge in the parent."""
933 """If true, the left edge rendered will be the right edge of the right-most tile."""
937 def flip_x(self, value):
938 if not isinstance(value, bool):
939 raise TypeError("Flip X should be a boolean type")
944 """If true, the top edge rendered will be the bottom edge of the bottom-most tile."""
948 def flip_y(self, value):
949 if not isinstance(value, bool):
950 raise TypeError("Flip Y should be a boolean type")
954 def transpose_xy(self):
955 """If true, the TileGrid’s axis will be swapped. When combined with mirroring, any 90 degree
956 rotation can be achieved along with the corresponding mirrored version.
958 return self._transpose_xy
961 def transpose_xy(self, value):
962 if not isinstance(value, bool):
963 raise TypeError("Transpose XY should be a boolean type")
964 self._transpose_xy = value
967 def pixel_shader(self):
968 """The pixel shader of the tilegrid."""
971 def __getitem__(self, index):
972 """Returns the tile index at the given index. The index can either be
973 an x,y tuple or an int equal to ``y * width + x``'.
975 if isinstance(index, (tuple, list)):
978 index = y * self._width + x
979 elif ininstance(index, int):
980 x = index % self._width
981 y = index // self._width
982 if x > self._width or y > self._height:
983 raise ValueError("Tile index out of bounds")
984 return self._tiles[index]
986 def __setitem__(self, index, value):
987 """Sets the tile index at the given index. The index can either be
988 an x,y tuple or an int equal to ``y * width + x``.
990 if isinstance(index, (tuple, list)):
993 index = y * self._width + x
994 elif ininstance(index, int):
995 x = index % self._width
996 y = index // self._width
997 if x > width or y > self._height or index > len(self._tiles):
998 raise ValueError("Tile index out of bounds")
999 if not 0 <= value <= 255:
1000 raise ValueError("Tile value out of bounds")
1001 self._tiles[index] = value