]> Repositories - hackapet/Adafruit_Blinka_Displayio.git/blob - displayio.py
Added lots more code
[hackapet/Adafruit_Blinka_Displayio.git] / displayio.py
1 """
2 `displayio`
3 """
4
5 import os
6 import digitalio
7 import time
8 import struct
9 import numpy
10 from collections import namedtuple
11 from PIL import Image, ImageDraw, ImagePalette
12
13 """
14 import asyncio
15 import signal
16 import subprocess
17 """
18
19 # Don't import pillow if we're running in the CI. We could mock it out but that
20 # would require mocking in all reverse dependencies.
21 if "GITHUB_ACTION" not in os.environ and "READTHEDOCS" not in os.environ:
22     # This will only work on Linux
23     pass
24 else:
25     # this would be for Github Actions
26     utils = None  # pylint: disable=invalid-name
27
28 __version__ = "0.0.0-auto.0"
29 __repo__ = "https://github.com/adafruit/Adafruit_Blinka_displayio.git"
30
31 _displays = []
32 _groups = []
33
34 Rectangle = namedtuple("Rectangle", "x1 y1 x2 y2")
35
36 class _DisplayioSingleton:
37     def __init__(self):
38         pass
39
40
41 def release_displays():
42     """Releases any actively used displays so their busses and pins can be used again.
43
44     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.
45     """
46     for _disp in _displays:
47         _disp._release()
48     _displays.clear()
49
50
51 class Bitmap:
52     """Stores values of a certain size in a 2D array"""
53
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.
56         """
57         self._width = width
58         self._height = height
59         self._read_only = False
60
61         if value_count < 0:
62             raise ValueError("value_count must be > 0")
63
64         bits = 1
65         while (value_count - 1) >> bits:
66             if bits < 8:
67                 bits = bits << 1
68             else:
69                 bits += 8
70
71         self._bits_per_value = bits
72
73         if (
74             self._bits_per_value > 8
75             and self._bits_per_value != 16
76             and self._bits_per_value != 32
77         ):
78             raise NotImplementedError("Invalid bits per value")
79
80         self._data = (width * height) * [0]
81         self._dirty_area = Rectangle(0, 0, width, height)
82
83     def __getitem__(self, index):
84         """
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`.
87         """
88         if isinstance(index, (tuple, list)):
89             index = index[1] * self._width + index[0]
90         return self._data[index]
91
92     def __setitem__(self, index, value):
93         """
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`.
96         """
97         if self._read_only:
98             raise RuntimeError("Read-only object")
99         if isinstance(index, (tuple, list)):
100             x = index[0]
101             y = index[1]
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
112         else:
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
121
122     def _finish_refresh(self):
123         self._dirty_area.x1 = 0
124         self._dirty_area.x2 = 0
125
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)
130
131     @property
132     def width(self):
133         """Width of the bitmap. (read only)"""
134         return self._width
135
136     @property
137     def height(self):
138         """Height of the bitmap. (read only)"""
139         return self._height
140
141
142 class ColorConverter:
143     """Converts one color format to another. Color converter based on original displayio
144     code for consistency.
145     """
146
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
151         """
152         self._dither = dither
153         self._depth = 16
154
155     def _compute_rgb565(self, color):
156         self._depth = 16
157         return (color >> 19) << 11 | ((color >> 10) & 0x3F) << 5 | (color >> 3) & 0x1F
158
159     def _compute_luma(self, color):
160         r8 = color >> 16
161         g8 = (color >> 8) & 0xFF
162         b8 = color & 0xFF
163         return (r8 * 19) / 255 + (g8 * 182) / 255 + (b8 + 54) / 255
164
165     def _compute_chroma(self, color):
166         r8 = color >> 16
167         g8 = (color >> 8) & 0xFF
168         b8 = color & 0xFF
169         return max(r8, g8, b8) - min(r8, g8, b8)
170
171     def _compute_hue(self, color):
172         r8 = color >> 16
173         g8 = (color >> 8) & 0xFF
174         b8 = color & 0xFF
175         max_color = max(r8, g8, b8)
176         chroma = self._compute_chroma(color)
177         if chroma == 0:
178             return 0
179         hue = 0
180         if max_color == r8:
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
186         if hue < 0:
187             hue += 240
188
189         return hue
190
191     def _dither_noise_1(self, noise):
192         n = (n >> 13) ^ n
193         nn = (n * (n * n * 60493 + 19990303) + 1376312589) & 0x7FFFFFFF
194         return (nn / (1073741824.0 * 2)) * 255
195
196     def _dither_noise_2(self, x, y):
197         return self._dither_noise_1(x + y * 0xFFFF)
198
199     def _compute_tricolor(self):
200         pass
201
202     def convert(self, color):
203         "Converts the given RGB888 color to RGB565"
204         if self._dither:
205             return color  # To Do: return a dithered color
206         else:
207             return self._compute_rgb565(color)
208
209     @property
210     def dither(self):
211         "When true the color converter dithers the output by adding random noise when truncating to display bitdepth"
212         return self._dither
213
214     @dither.setter
215     def dither(self, value):
216         if not isinstance(value, bool):
217             raise ValueError("Value should be boolean")
218         self._dither = value
219
220
221 class Display:
222     """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.
223
224     Most people should not use this class directly. Use a specific display driver instead that will contain the initialization sequence at minimum.
225     
226     .. 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)
227     
228     """
229
230     def __init__(
231         self,
232         display_bus,
233         init_sequence,
234         *,
235         width,
236         height,
237         colstart=0,
238         rowstart=0,
239         rotation=0,
240         color_depth=16,
241         grayscale=False,
242         pixels_in_byte_share_row=True,
243         bytes_per_cell=1,
244         reverse_pixels_in_byte=False,
245         set_column_command=0x2A,
246         set_row_command=0x2B,
247         write_ram_command=0x2C,
248         set_vertical_scroll=0,
249         backlight_pin=None,
250         brightness_command=None,
251         brightness=1.0,
252         auto_brightness=False,
253         single_byte_bounds=False,
254         data_as_commands=False,
255         auto_refresh=True,
256         native_frames_per_second=60
257     ):
258         """Create a Display object on the given display bus (`displayio.FourWire` or `displayio.ParallelBus`).
259
260         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:
261         .. code-block:: python
262         
263             init_sequence = (b"\xe1\x0f\x00\x0E\x14\x03\x11\x07\x31\xC1\x48\x08\x0F\x0C\x31\x36\x0F" # Set Gamma
264                 b"\x11\x80\x78"# Exit Sleep then delay 0x78 (120ms)
265                 b"\x29\x80\x78"# Display on then delay 0x78 (120ms)
266             )
267             display = displayio.Display(display_bus, init_sequence, width=320, height=240)
268         
269         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.
270
271         The initialization sequence should always leave the display memory access inline with the scan of the display to minimize tearing artifacts.
272         """
273         self._bus = display_bus
274         self._set_column_command = set_column_command
275         self._set_row_command = set_row_command
276         self._write_ram_command = write_ram_command
277         self._brightness_command = brightness_command
278         self._data_as_commands = data_as_commands
279         self._single_byte_bounds = single_byte_bounds
280         self._width = width
281         self._height = height
282         self._colstart = colstart
283         self._rowstart = rowstart
284         self._rotation = rotation
285         self._auto_brightness = auto_brightness
286         self._brightness = brightness
287         self._auto_refresh = auto_refresh
288         self._initialize(init_sequence)
289         self._buffer = Image.new("RGB", (width, height))
290         self._subrectangles = []
291         self._bounds_encoding = ">BB" if single_byte_bounds else ">HH"
292         self._groups = []
293         _displays.append(self)
294         if self._auto_refresh:
295             self.refresh()
296
297     def _initialize(self, init_sequence):
298         i = 0
299         while i < len(init_sequence):
300             command = init_sequence[i]
301             data_size = init_sequence[i + 1]
302             delay = (data_size & 0x80) > 0
303             data_size &= ~0x80
304             data_byte = init_sequence[i + 2]
305             self._write(command, init_sequence[i + 2 : i + 2 + data_size])
306             delay_time_ms = 10
307             if delay:
308                 data_size += 1
309                 delay_time_ms = init_sequence[i + 1 + data_size]
310                 if delay_time_ms == 255:
311                     delay_time_ms = 500
312             time.sleep(delay_time_ms / 1000)
313             i += 2 + data_size
314
315     def _write(self, command, data):
316         if self._single_byte_bounds:
317             self._bus.send(True, bytes([command]) + data, toggle_every_byte=True)
318         else:
319             self._bus.send(True, bytes([command]), toggle_every_byte=True)
320             self._bus.send(False, data)
321
322     def _release(self):
323         self._bus.release()
324         self._bus = None
325
326     def show(self, group):
327         """Switches to displaying the given group of layers. When group is None, the default CircuitPython terminal will be shown.
328         """
329         self._groups.append(group)
330
331     def _group_to_buffer(self, group):
332         """ go through any children and call this function then add group to buffer"""
333         for layer_number in range(len(group.layers)):
334             layer = group.layers[layer_number]
335             if isinstance(layer, Group):
336                 self._group_to_buffer(layer)
337             elif isinstance(layer, TileGrid):
338                 # Get the TileGrid Info and draw to buffer
339                 pass
340             else:
341                 raise TypeError("Invalid layer type found in group")
342
343     def refresh(self, *, target_frames_per_second=60, minimum_frames_per_second=1):
344         """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.
345
346         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.
347
348         When auto refresh is on, updates the display immediately. (The display will also update without calls to this.)
349         """
350         
351         # Go through groups and and add each to buffer
352         #for group in self._groups:
353             
354         
355         # Eventually calculate dirty rectangles here
356         self._subrectangles.append(Rectangle(0, 0, self._width, self._height))
357         
358         for area in self._subrectangles:
359             self._refresh_display_area(area)
360         
361         if self._auto_refresh:
362             self.refresh()
363
364     def _refresh_display_area(self, rectangle):
365         """Loop through dirty rectangles and redraw that area."""
366         """Read or write a block of data."""
367         data = numpy.array(self._buffer.crop(rectangle).convert("RGB")).astype("uint16")
368         color = (
369             ((data[:, :, 0] & 0xF8) << 8)
370             | ((data[:, :, 1] & 0xFC) << 3)
371             | (data[:, :, 2] >> 3)
372         )
373         
374         pixels = list(numpy.dstack(((color >> 8) & 0xFF, color & 0xFF)).flatten().tolist())
375         
376         self._write(
377             self._set_column_command,
378             self._encode_pos(rectangle.x1 + self._colstart, rectangle.x2 + self._colstart)
379         )
380         self._write(
381             self._set_row_command,
382             self._encode_pos(rectangle.y1 + self._rowstart, rectangle.y2 + self._rowstart)
383         )
384         self._write(self._write_ram_command, pixels)
385
386     def _encode_pos(self, x, y):
387         """Encode a postion into bytes."""
388         return struct.pack(self._bounds_encoding, x, y)
389
390     def fill_row(self, y, buffer):
391         pass
392
393     @property
394     def auto_refresh(self):
395         return self._auto_refresh
396
397     @auto_refresh.setter
398     def auto_refresh(self, value):
399         self._auto_refresh = value
400
401     @property
402     def brightness(self):
403         """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.
404         """
405         return self._brightness
406
407     @brightness.setter
408     def brightness(self, value):
409         self._brightness = value
410
411     @property
412     def auto_brightness(self):
413         """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.
414         """
415         return self._auto_brightness
416
417     @auto_brightness.setter
418     def auto_brightness(self, value):
419         self._auto_brightness = value
420
421     @property
422     def width(self):
423         return self._width
424
425     @property
426     def height(self):
427         return self._height
428
429     @property
430     def rotation(self):
431         """The rotation of the display as an int in degrees."""
432         return self._rotation
433
434     @rotation.setter
435     def rotation(self, value):
436         if value not in (0, 90, 180, 270):
437             raise ValueError("Rotation must be 0/90/180/270")
438         self._rotation = value
439
440     @property
441     def bus(self):
442         return self._bus
443
444
445 class EPaperDisplay:
446     def __init__(
447         self,
448         display_bus,
449         start_sequence,
450         stop_sequence,
451         *,
452         width,
453         height,
454         ram_width,
455         ram_height,
456         colstart=0,
457         rowstart=0,
458         rotation=0,
459         set_column_window_command=None,
460         set_row_window_command=None,
461         single_byte_bounds=False,
462         write_black_ram_command,
463         black_bits_inverted=False,
464         write_color_ram_command=None,
465         color_bits_inverted=False,
466         highlight_color=0x000000,
467         refresh_display_command,
468         refresh_time=40,
469         busy_pin=None,
470         busy_state=True,
471         seconds_per_frame=180,
472         always_toggle_chip_select=False
473     ):
474         """
475         Create a EPaperDisplay object on the given display bus (displayio.FourWire or displayio.ParallelBus).
476
477         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.
478         """
479         pass
480
481     def show(self, group):
482         """Switches to displaying the given group of layers. When group is None, the default CircuitPython terminal will be shown.
483         """
484         pass
485
486     def refresh(self):
487         """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.
488         """
489         pass
490
491     @property
492     def time_to_refresh(self):
493         """Time, in fractional seconds, until the ePaper display can be refreshed."""
494         return 0
495
496     @property
497     def width(self):
498         pass
499
500     @property
501     def height(self):
502         pass
503
504     @property
505     def bus(self):
506         pass
507
508
509 class FourWire:
510     """Manage updating a display over SPI four wire protocol in the background while
511     Python code runs. It doesn’t handle display initialization.
512     """
513
514     def __init__(
515         self,
516         spi_bus,
517         *,
518         command,
519         chip_select,
520         reset=None,
521         baudrate=24000000,
522         polarity=0,
523         phase=0
524     ):
525         """Create a FourWire object associated with the given pins.
526
527         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.
528         """
529         self._dc = digitalio.DigitalInOut(command)
530         self._dc.switch_to_output()
531         self._chip_select = digitalio.DigitalInOut(chip_select)
532         self._chip_select.switch_to_output(value=True)
533
534         if reset is not None:
535             self._reset = digitalio.DigitalInOut(reset)
536             self._reset.switch_to_output(value=True)
537         else:
538             self._reset = None
539         self._spi = spi_bus
540         while self._spi.try_lock():
541             pass
542         self._spi.configure(baudrate=baudrate, polarity=polarity, phase=phase)
543         self._spi.unlock()
544
545     def _release(self):
546         self.reset()
547         self._spi.deinit()
548         self._dc.deinit()
549         self._chip_select.deinit()
550         if self._reset is not None:
551             self._reset.deinit()
552
553     def reset(self):
554         if self._reset is not None:
555             self._reset.value = False
556             time.sleep(0.001)
557             self._reset.value = True
558             time.sleep(0.001)
559
560     def send(self, is_command, data, *, toggle_every_byte=False):
561         while self._spi.try_lock():
562             pass
563         self._dc.value = not is_command
564         if toggle_every_byte:
565             for byte in data:
566                 self._spi.write(bytes([byte]))
567                 self._chip_select.value = True
568                 time.sleep(0.000001)
569                 self._chip_select.value = False
570         else:
571             self._spi.write(data)
572         self._spi.unlock()
573
574
575 class Group:
576     """Manage a group of sprites and groups and how they are inter-related."""
577
578     def __init__(self, *, max_size=4, scale=1, x=0, y=0):
579         """Create a Group of a given size and scale. Scale is in
580         one dimension. For example, scale=2 leads to a layer’s
581         pixel being 2x2 pixels when in the group.
582         """
583         if not isinstance(max_size, int) or max_size < 1:
584             raise ValueError("Max Size must be an integer and >= 1")
585         self._max_size = max_size
586         if not isinstance(scale, int) or scale < 1:
587             raise ValueError("Scale must be an integer and >= 1")
588         self._scale = scale
589         self._x = x
590         self._y = y
591         self._hidden = False
592         self._layers = []
593         self._supported_types = (TileGrid, Group)
594
595     def append(self, layer):
596         """Append a layer to the group. It will be drawn
597         above other layers.
598         """
599         if not isinstance(layer, self._supported_types):
600             raise ValueError("Invalid Group Memeber")
601         if len(self._layers) == self._max_size:
602             raise RuntimeError("Group full")
603         self._layers.append(layer)
604
605     def insert(self, index, layer):
606         """Insert a layer into the group."""
607         if not isinstance(layer, self._supported_types):
608             raise ValueError("Invalid Group Memeber")
609         if len(self._layers) == self._max_size:
610             raise RuntimeError("Group full")
611         self._layers.insert(index, layer)
612
613     def index(self, layer):
614         """Returns the index of the first copy of layer.
615         Raises ValueError if not found.
616         """
617         pass
618
619     def pop(self, index=-1):
620         """Remove the ith item and return it."""
621         return self._layers.pop(index)
622
623     def remove(self, layer):
624         """Remove the first copy of layer. Raises ValueError
625         if it is not present."""
626         pass
627
628     def __len__(self):
629         """Returns the number of layers in a Group"""
630         return len(self._layers)
631
632     def __getitem__(self, index):
633         """Returns the value at the given index."""
634         return self._layers[index]
635
636     def __setitem__(self, index, value):
637         """Sets the value at the given index."""
638         self._layers[index] = value
639
640     def __delitem__(self, index):
641         """Deletes the value at the given index."""
642         del self._layers[index]
643
644     @property
645     def hidden(self):
646         return self._hidden
647
648     @hidden.setter
649     def hidden(self, value):
650         if not isinstance(value, (bool, int)):
651             raise ValueError("Expecting a boolean or integer value")
652         self._hidden = bool(value)
653
654     @property
655     def scale(self):
656         return self._scale
657
658     @scale.setter
659     def scale(self, value):
660         if not isinstance(value, int) or value < 1:
661             raise ValueError("Scale must be an integer and at least 1")
662         self._scale = value
663
664     @property
665     def x(self):
666         return self._x
667
668     @x.setter
669     def x(self, value):
670         if not isinstance(value, int):
671             raise ValueError("x must be an integer")
672         self._x = value
673
674     @property
675     def y(self):
676         return self._y
677
678     @y.setter
679     def y(self, value):
680         if not isinstance(value, int):
681             raise ValueError("y must be an integer")
682         self._y = value
683
684
685 class I2CDisplay:
686     """Manage updating a display over I2C in the background while Python code runs. It doesn’t handle display initialization.
687     """
688
689     def __init__(self, i2c_bus, *, device_address, reset=None):
690         """Create a I2CDisplay object associated with the given I2C bus and reset pin.
691
692         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.
693         """
694         pass
695
696     def reset(self):
697         pass
698
699     def send(self, command, data):
700         pass
701
702
703 class OnDisplayBitmap:
704     """
705     Loads values straight from disk. This minimizes memory use but can lead to much slower pixel load times.
706     These load times may result in frame tearing where only part of the image is visible."""
707
708     def __init__(self, file):
709         self._image = Image.open(file)
710
711     @property
712     def width(self):
713         """Width of the bitmap. (read only)"""
714         return self._image.width
715
716     @property
717     def height(self):
718         """Height of the bitmap. (read only)"""
719         return self._image.height
720
721
722 class Palette:
723     """Map a pixel palette_index to a full color. Colors are transformed to the display’s format internally to save memory."""
724
725     def __init__(self, color_count):
726         """Create a Palette object to store a set number of colors."""
727         self._needs_refresh = False
728         
729         self._colors = []
730         for _ in range(color_count):
731             self._colors.append(self._make_color(0))
732
733     def _make_color(self, value):
734         color = {
735             "transparent": False,
736             "rgb888": 0,
737         }
738         color_converter = ColorConverter()
739         if isinstance(value, (tuple, list, bytes, bytearray)):
740             value = (value[0] & 0xFF) << 16 | (value[1] & 0xFF) << 8 | value[2] & 0xFF
741         elif isinstance(value, int):
742             if not 0 <= value <= 0xFFFFFF:
743                 raise ValueError("Color must be between 0x000000 and 0xFFFFFF")
744         else:
745             raise TypeError("Color buffer must be a buffer, tuple, list, or int")
746         color["rgb888"] = value
747         self._needs_refresh = True
748
749         return color
750
751     def __len__(self):
752         """Returns the number of colors in a Palette"""
753         return len(self._colors)
754
755     def __setitem__(self, index, value):
756         """Sets the pixel color at the given index. The index should be an integer in the range 0 to color_count-1.
757
758         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.
759         """
760         if self._colors[index]["rgb888"] != value:
761             self._colors[index] = self._make_color(value)
762
763     def __getitem__(self, index):
764         pass
765
766     def make_transparent(self, palette_index):
767         self._colors[palette_index].transparent = True
768
769     def make_opaque(self, palette_index):
770         self._colors[palette_index].transparent = False
771
772     def _pil_palette(self):
773         "Generate a Pillow ImagePalette and return it"
774         palette = []
775         for channel in range(3):
776             for color in self._colors:
777                 palette.append(color >> (8 * (2 - channel)) & 0xFF)
778             
779         return ImagePalette(mode='RGB', palette=palette, size=self._color_count)
780
781
782 class ParallelBus:
783     """Manage updating a display over 8-bit parallel bus in the background while Python code runs.
784     This protocol may be refered to as 8080-I Series Parallel Interface in datasheets.
785     It doesn’t handle display initialization.
786     """
787
788     def __init__(self, i2c_bus, *, device_address, reset=None):
789         """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.
790
791         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.
792         """
793         pass
794
795     def reset(self):
796         """Performs a hardware reset via the reset pin. Raises an exception if called when no reset pin is available.
797         """
798         pass
799
800     def send(self, command, data):
801         """Sends the given command value followed by the full set of data. Display state, such as
802         vertical scroll, set via ``send`` may or may not be reset once the code is done.
803         """
804         pass
805
806
807 class Shape(Bitmap):
808     """Create a Shape object with the given fixed size. Each pixel is one bit and is stored by the column
809     boundaries of the shape on each row. Each row’s boundary defaults to the full row.
810     """
811
812     def __init__(self, width, height, *, mirror_x=False, mirror_y=False):
813         """Create a Shape object with the given fixed size. Each pixel is one bit and is stored by the
814         column boundaries of the shape on each row. Each row’s boundary defaults to the full row.
815         """
816         pass
817
818     def set_boundary(self, y, start_x, end_x):
819         """Loads pre-packed data into the given row."""
820         pass
821
822
823 class TileGrid:
824     """Position a grid of tiles sourced from a bitmap and pixel_shader combination. Multiple grids can share bitmaps and pixel shaders.
825
826     A single tile grid is also known as a Sprite.
827     """
828
829     def __init__(
830         self,
831         bitmap,
832         *,
833         pixel_shader,
834         width=1,
835         height=1,
836         tile_width=None,
837         tile_height=None,
838         default_tile=0,
839         x=0,
840         y=0
841     ):
842         """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.
843
844         tile_width and tile_height match the height of the bitmap by default.
845         """
846         if not isinstance(bitmap, (Bitmap, OnDiskBitmap, Shape)):
847             raise ValueError("Unsupported Bitmap type")
848         self._bitmap = bitmap
849         bitmap_width = bitmap.width
850         bitmap_height = bitmap.height
851         
852         if not isinstance(pixel_shader, (ColorConverter, Palette)):
853             raise ValueError("Unsupported Pixel Shader type")
854         self._pixel_shader = pixel_shader
855         self_hidden = False
856         self._x = x
857         self._y = y
858         self._width = width # Number of Tiles Wide
859         self._height = height # Number of Tiles High
860         if tile_width is None:
861             tile_width = bitmap_width
862         if tile_height is None:
863             tile_height = bitmap_height
864         if bitmap_width % tile_width != 0:
865             raise ValueError("Tile width must exactly divide bitmap width")
866         self._tile_width = tile_width
867         if bitmap_height % tile_height != 0:
868             raise ValueError("Tile height must exactly divide bitmap height")
869         self._tile_height = tile_height
870         self._tiles = (self._width * self._height) * [default_tile]
871
872
873     @property
874     def hidden(self):
875         """True when the TileGrid is hidden. This may be False even when a part of a hidden Group."""
876         return self_hidden
877
878     @hidden.setter
879     def hidden(self, value):
880         self._hidden = value
881
882     @property
883     def x(self):
884         """X position of the left edge in the parent."""
885         return self._x
886
887     @property
888     def y(self):
889         """Y position of the top edge in the parent."""
890         return self._y
891
892     @property
893     def flip_x(self):
894         """If true, the left edge rendered will be the right edge of the right-most tile."""
895         return self._flip_x
896
897     @flip_x.setter
898     def flip_x(self, value):
899         if not isinstance(value, bool):
900             raise TypeError("Flip X should be a boolean type")
901         self._flip_x = value
902
903     @property
904     def flip_y(self):
905         """If true, the top edge rendered will be the bottom edge of the bottom-most tile."""
906         return self._flip_y
907
908     @flip_y.setter
909     def flip_y(self, value):
910         if not isinstance(value, bool):
911             raise TypeError("Flip Y should be a boolean type")
912         self._flip_y = value
913
914     @property
915     def transpose_xy(self):
916         """If true, the TileGrid’s axis will be swapped. When combined with mirroring, any 90 degree
917         rotation can be achieved along with the corresponding mirrored version.
918         """
919         return self._transpose_xy
920
921     @transpose_xy.setter
922     def transpose_xy(self, value):
923         if not isinstance(value, bool):
924             raise TypeError("Transpose XY should be a boolean type")
925         self._transpose_xy = value
926
927     @property
928     def pixel_shader(self):
929         """The pixel shader of the tilegrid."""
930         pass
931
932     def __getitem__(self, index):
933         """Returns the tile index at the given index. The index can either be
934         an x,y tuple or an int equal to ``y * width + x``'.
935         """
936         if isinstance(index, (tuple, list)):
937             x = index[0]
938             y = index[1]
939             index = y * self._width + x
940         elif ininstance(index, int):
941             x = index % self._width
942             y = index // self._width
943         if x > self._width or y > self._height:
944             raise ValueError("Tile index out of bounds")
945         return self._tiles[index]
946
947     def __setitem__(self, index, value):
948         """Sets the tile index at the given index. The index can either be
949         an x,y tuple or an int equal to ``y * width + x``.
950         """
951         if isinstance(index, (tuple, list)):
952             x = index[0]
953             y = index[1]
954             index = y * self._width + x
955         elif ininstance(index, int):
956             x = index % self._width
957             y = index // self._width
958         if x > width or y > self._height or index > len(self._tiles):
959             raise ValueError("Tile index out of bounds")
960         if not 0 <= value <= 255:
961             raise ValueError("Tile value out of bounds")
962         self._tiles[index] = value