8 from PIL import Image, ImageDraw
17 # Don't import pillow if we're running in the CI. We could mock it out but that
18 # would require mocking in all reverse dependencies.
19 if "GITHUB_ACTION" not in os.environ and "READTHEDOCS" not in os.environ:
20 # This will only work on Linux
23 # this would be for Github Actions
24 utils = None # pylint: disable=invalid-name
26 __version__ = "0.0.0-auto.0"
27 __repo__ = "https://github.com/adafruit/Adafruit_Blinka_displayio.git"
33 class _DisplayioSingleton:
38 def release_displays():
39 """Releases any actively used displays so their busses and pins can be used again.
41 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.
43 for _disp in _displays:
49 """Stores values of a certain size in a 2D array"""
51 def __init__(self, width, height, value_count):
52 """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 self._read_only = False
59 raise ValueError("value_count must be > 0")
62 while (value_count - 1) >> bits:
68 self._bits_per_value = bits
71 self._bits_per_value > 8
72 and self._bits_per_value != 16
73 and self._bits_per_value != 32
75 raise NotImplementedError("Invalid bits per value")
77 self._data = (width * height) * [0]
78 self._dirty_area = {"x1": 0, "x2": width, "y1": 0, "y2": height}
80 def __getitem__(self, index):
82 Returns the value at the given index. The index can either be
83 an x,y tuple or an int equal to `y * width + x`.
85 if isinstance(index, (tuple, list)):
86 index = index[1] * self._width + index[0]
87 return self._data[index]
89 def __setitem__(self, index, value):
91 Sets the value at the given index. The index can either be
92 an x,y tuple or an int equal to `y * width + x`.
95 raise RuntimeError("Read-only object")
96 if isinstance(index, (tuple, list)):
99 index = y * self._width + x
100 elif ininstance(index, int):
101 x = index % self._width
102 y = index // self._width
103 self._data[index] = value
104 if self._dirty_area["x1"] == self._dirty_area["x2"]:
105 self._dirty_area["x1"] = x
106 self._dirty_area["x2"] = x + 1
107 self._dirty_area["y1"] = y
108 self._dirty_area["y2"] = y + 1
110 if x < self._dirty_area["x1"]:
111 self._dirty_area["x1"] = x
112 elif x >= self._dirty_area["x2"]:
113 self._dirty_area["x2"] = x + 1
114 if y < self._dirty_area["y1"]:
115 self._dirty_area["y1"] = y
116 elif y >= self._dirty_area["y2"]:
117 self._dirty_area["y2"] = y + 1
119 def _finish_refresh(self):
120 self._dirty_area["x1"] = 0
121 self._dirty_area["x2"] = 0
123 def fill(self, value):
124 """Fills the bitmap with the supplied palette index value."""
125 self._data = (self._width * self._height) * [value]
126 self._dirty_area = {"x1": 0, "x2": self._width, "y1": 0, "y2": self._height}
130 """Width of the bitmap. (read only)"""
135 """Height of the bitmap. (read only)"""
139 class ColorConverter:
140 """Converts one color format to another. Color converter based on original displayio
141 code for consistency.
144 def __init__(self, *, dither=False):
145 """Create a ColorConverter object to convert color formats.
146 Only supports RGB888 to RGB565 currently.
147 :param bool dither: Adds random noise to dither the output image
149 self._dither = dither
152 def _compute_rgb565(self, color):
154 return (color >> 19) << 11 | ((color >> 10) & 0x3F) << 5 | (color >> 3) & 0x1F
156 def _compute_luma(self, color):
158 g8 = (color >> 8) & 0xFF
160 return (r8 * 19) / 255 + (g8 * 182) / 255 + (b8 + 54) / 255
162 def _compute_chroma(self, color):
164 g8 = (color >> 8) & 0xFF
166 return max(r8, g8, b8) - min(r8, g8, b8)
168 def _compute_hue(self, color):
170 g8 = (color >> 8) & 0xFF
172 max_color = max(r8, g8, b8)
173 chroma = self._compute_chroma(color)
178 hue = (((g8 - b8) * 40) / chroma) % 240
179 elif max_color == g8:
180 hue = (((b8 - r8) + (2 * chroma)) * 40) / chroma
181 elif max_color == b8:
182 hue = (((r8 - g8) + (4 * chroma)) * 40) / chroma
188 def _dither_noise_1(self, noise):
190 nn = (n * (n * n * 60493 + 19990303) + 1376312589) & 0x7FFFFFFF
191 return (nn / (1073741824.0 * 2)) * 255
193 def _dither_noise_2(self, x, y):
194 return self._dither_noise_1(x + y * 0xFFFF)
196 def _compute_tricolor(self):
199 def convert(self, color):
200 "Converts the given RGB888 color to RGB565"
202 return color # To Do: return a dithered color
204 return self._compute_rgb565(color)
208 "When true the color converter dithers the output by adding random noise when truncating to display bitdepth"
212 def dither(self, value):
213 if not isinstance(value, bool):
214 raise ValueError("Value should be boolean")
219 """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.
221 Most people should not use this class directly. Use a specific display driver instead that will contain the initialization sequence at minimum.
223 .. 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)
239 pixels_in_byte_share_row=True,
241 reverse_pixels_in_byte=False,
242 set_column_command=0x2A,
243 set_row_command=0x2B,
244 write_ram_command=0x2C,
245 set_vertical_scroll=0,
247 brightness_command=None,
249 auto_brightness=False,
250 single_byte_bounds=False,
251 data_as_commands=False,
253 native_frames_per_second=60
255 """Create a Display object on the given display bus (`displayio.FourWire` or `displayio.ParallelBus`).
257 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:
258 .. code-block:: python
260 init_sequence = (b"\xe1\x0f\x00\x0E\x14\x03\x11\x07\x31\xC1\x48\x08\x0F\x0C\x31\x36\x0F" # Set Gamma
261 b"\x11\x80\x78"# Exit Sleep then delay 0x78 (120ms)
262 b"\x29\x80\x78"# Display on then delay 0x78 (120ms)
264 display = displayio.Display(display_bus, init_sequence, width=320, height=240)
266 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.
268 The initialization sequence should always leave the display memory access inline with the scan of the display to minimize tearing artifacts.
270 self._bus = display_bus
271 self._set_column_command = 0x2A
272 self._set_row_command = 0x2B
273 self._write_ram_command = 0x2C
274 self._brightness_command = brightness_command
275 self._data_as_commands = data_as_commands
276 self._single_byte_bounds = single_byte_bounds
278 self._height = height
279 self._colstart = colstart
280 self._rowstart = rowstart
281 self._rotation = rotation
282 self._auto_brightness = auto_brightness
283 self._brightness = brightness
284 self._auto_refresh = auto_refresh
285 self._initialize(init_sequence)
286 _displays.append(self)
288 def _initialize(self, init_sequence):
290 while i < len(init_sequence):
291 command = bytes([init_sequence[i]])
292 data_size = init_sequence[i + 1]
293 delay = (data_size & 0x80) > 0
295 data_byte = init_sequence[i + 2]
296 if self._single_byte_bounds:
297 data = command + init_sequence[i + 2 : i + 2 + data_size]
298 self._bus.send(True, data, toggle_every_byte=True)
300 self._bus.send(True, command, toggle_every_byte=True)
302 self._bus.send(False, init_sequence[i + 2 : i + 2 + data_size])
306 delay_time_ms = init_sequence[i + 1 + data_size]
307 if delay_time_ms == 255:
309 time.sleep(delay_time_ms / 1000)
316 def show(self, group):
317 """Switches to displaying the given group of layers. When group is None, the default CircuitPython terminal will be shown.
321 def refresh(self, *, target_frames_per_second=60, minimum_frames_per_second=1):
322 """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.
324 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.
326 When auto refresh is on, updates the display immediately. (The display will also update without calls to this.)
330 def fill_row(self, y, buffer):
334 def auto_refresh(self):
335 return self._auto_refresh
338 def auto_refresh(self, value):
339 self._auto_refresh = value
342 def brightness(self):
343 """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.
345 return self._brightness
348 def brightness(self, value):
349 self._brightness = value
352 def auto_brightness(self):
353 """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.
355 return self._auto_brightness
357 @auto_brightness.setter
358 def auto_brightness(self, value):
359 self._auto_brightness = value
371 """The rotation of the display as an int in degrees."""
372 return self._rotation
375 def rotation(self, value):
376 if value not in (0, 90, 180, 270):
377 raise ValueError("Rotation must be 0/90/180/270")
378 self._rotation = value
399 set_column_window_command=None,
400 set_row_window_command=None,
401 single_byte_bounds=False,
402 write_black_ram_command,
403 black_bits_inverted=False,
404 write_color_ram_command=None,
405 color_bits_inverted=False,
406 highlight_color=0x000000,
407 refresh_display_command,
411 seconds_per_frame=180,
412 always_toggle_chip_select=False
415 Create a EPaperDisplay object on the given display bus (displayio.FourWire or displayio.ParallelBus).
417 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.
421 def show(self, group):
422 """Switches to displaying the given group of layers. When group is None, the default CircuitPython terminal will be shown.
427 """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.
432 def time_to_refresh(self):
433 """Time, in fractional seconds, until the ePaper display can be refreshed."""
450 """Manage updating a display over SPI four wire protocol in the background while
451 Python code runs. It doesn’t handle display initialization.
465 """Create a FourWire object associated with the given pins.
467 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.
469 self._dc = digitalio.DigitalInOut(command)
470 self._dc.switch_to_output()
471 self._chip_select = digitalio.DigitalInOut(chip_select)
472 self._chip_select.switch_to_output(value=True)
474 if reset is not None:
475 self._reset = digitalio.DigitalInOut(reset)
476 self._reset.switch_to_output(value=True)
480 while self._spi.try_lock():
482 self._spi.configure(baudrate=baudrate, polarity=polarity, phase=phase)
489 self._chip_select.deinit()
490 if self._reset is not None:
494 if self._reset is not None:
495 self._reset.value = False
497 self._reset.value = True
500 def send(self, command, data, *, toggle_every_byte=False):
501 while self._spi.try_lock():
503 self._dc.value = not command
504 if toggle_every_byte:
506 self._spi.write(bytes([byte]))
507 self._chip_select.value = True
509 self._chip_select.value = False
511 self._spi.write(data)
516 """Manage a group of sprites and groups and how they are inter-related."""
518 def __init__(self, *, max_size=4, scale=1, x=0, y=0):
519 """Create a Group of a given size and scale. Scale is in
520 one dimension. For example, scale=2 leads to a layer’s
521 pixel being 2x2 pixels when in the group.
523 if not isinstance(max_size, int) or max_size < 1:
524 raise ValueError("Max Size must be an integer and >= 1")
525 self._max_size = max_size
526 if not isinstance(scale, int) or scale < 1:
527 raise ValueError("Scale must be an integer and >= 1")
533 self._supported_types = (TileGrid, Group)
535 def append(self, layer):
536 """Append a layer to the group. It will be drawn
539 if not isinstance(layer, self._supported_types):
540 raise ValueError("Invalid Group Memeber")
541 if len(self._layers) == self._max_size:
542 raise RuntimeError("Group full")
543 self._layers.append(layer)
545 def insert(self, index, layer):
546 """Insert a layer into the group."""
547 if not isinstance(layer, self._supported_types):
548 raise ValueError("Invalid Group Memeber")
549 if len(self._layers) == self._max_size:
550 raise RuntimeError("Group full")
551 self._layers.insert(index, layer)
553 def index(self, layer):
554 """Returns the index of the first copy of layer.
555 Raises ValueError if not found.
559 def pop(self, index=-1):
560 """Remove the ith item and return it."""
561 return self._layers.pop(index)
563 def remove(self, layer):
564 """Remove the first copy of layer. Raises ValueError
565 if it is not present."""
569 """Returns the number of layers in a Group"""
570 return len(self._layers)
572 def __getitem__(self, index):
573 """Returns the value at the given index."""
574 return self._layers[index]
576 def __setitem__(self, index, value):
577 """Sets the value at the given index."""
578 self._layers[index] = value
580 def __delitem__(self, index):
581 """Deletes the value at the given index."""
582 del self._layers[index]
589 def hidden(self, value):
590 if not isinstance(value, (bool, int)):
591 raise ValueError("Expecting a boolean or integer value")
592 self._hidden = bool(value)
599 def scale(self, value):
600 if not isinstance(value, int) or value < 1:
601 raise ValueError("Scale must be an integer and at least 1")
610 if not isinstance(value, int):
611 raise ValueError("x must be an integer")
620 if not isinstance(value, int):
621 raise ValueError("y must be an integer")
626 """Manage updating a display over I2C in the background while Python code runs. It doesn’t handle display initialization.
629 def __init__(self, i2c_bus, *, device_address, reset=None):
630 """Create a I2CDisplay object associated with the given I2C bus and reset pin.
632 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.
639 def send(self, command, data):
643 class OnDisplayBitmap:
645 Loads values straight from disk. This minimizes memory use but can lead to much slower pixel load times.
646 These load times may result in frame tearing where only part of the image is visible."""
648 def __init__(self, file):
653 """Width of the bitmap. (read only)"""
658 """Height of the bitmap. (read only)"""
663 """Map a pixel palette_index to a full color. Colors are transformed to the display’s format internally to save memory."""
665 def __init__(self, color_count):
666 """Create a Palette object to store a set number of colors."""
667 self._needs_refresh = False
669 for _ in range(color_count):
670 self._colors.append(self._make_color(0))
672 def _make_color(self, value):
674 "transparent": False,
681 color_converter = ColorConverter()
682 if isinstance(value, (tuple, list, bytes, bytearray)):
683 value = (value[0] & 0xFF) << 16 | (value[1] & 0xFF) << 8 | value[2] & 0xFF
684 elif isinstance(value, int):
685 if not 0 <= value <= 0xFFFFFF:
686 raise ValueError("Color must be between 0x000000 and 0xFFFFFF")
688 raise TypeError("Color buffer must be a buffer, tuple, list, or int")
689 color["rgb888"] = value
690 color["rgb565"] = color_converter._compute_rgb565(value)
691 color["chroma"] = color_converter._compute_chroma(value)
692 color["luma"] = color_converter._compute_luma(value)
693 color["hue"] = color_converter._compute_hue(value)
694 self._needs_refresh = True
699 """Returns the number of colors in a Palette"""
700 return len(self._colors)
702 def __setitem__(self, index, value):
703 """Sets the pixel color at the given index. The index should be an integer in the range 0 to color_count-1.
705 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.
707 if self._colors[index]["rgb888"] != value:
708 self._colors[index] = self._make_color(value)
710 def __getitem__(self, index):
713 def make_transparent(self, palette_index):
714 self._colors[palette_index].transparent = True
716 def make_opaque(self, palette_index):
717 self._colors[palette_index].transparent = False
721 """Manage updating a display over 8-bit parallel bus in the background while Python code runs.
722 This protocol may be refered to as 8080-I Series Parallel Interface in datasheets.
723 It doesn’t handle display initialization.
726 def __init__(self, i2c_bus, *, device_address, reset=None):
727 """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.
729 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.
734 """Performs a hardware reset via the reset pin. Raises an exception if called when no reset pin is available.
738 def send(self, command, data):
739 """Sends the given command value followed by the full set of data. Display state, such as
740 vertical scroll, set via ``send`` may or may not be reset once the code is done.
746 """Create a Shape object with the given fixed size. Each pixel is one bit and is stored by the column
747 boundaries of the shape on each row. Each row’s boundary defaults to the full row.
750 def __init__(self, width, height, *, mirror_x=False, mirror_y=False):
751 """Create a Shape object with the given fixed size. Each pixel is one bit and is stored by the
752 column boundaries of the shape on each row. Each row’s boundary defaults to the full row.
756 def set_boundary(self, y, start_x, end_x):
757 """Loads pre-packed data into the given row."""
762 """Position a grid of tiles sourced from a bitmap and pixel_shader combination. Multiple grids can share bitmaps and pixel shaders.
764 A single tile grid is also known as a Sprite.
780 """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.
782 tile_width and tile_height match the height of the bitmap by default.
784 self._bitmap = bitmap
785 self._pixel_shader = pixel_shader
790 self._height = height
791 if tile_width is None:
793 if tile_height is None:
795 self._tile_width = tile_width
796 self._tile_height = tile_height
800 """True when the TileGrid is hidden. This may be False even when a part of a hidden Group."""
804 def hidden(self, value):
809 """X position of the left edge in the parent."""
814 """Y position of the top edge in the parent."""
819 """If true, the left edge rendered will be the right edge of the right-most tile."""
823 def flip_x(self, value):
824 if not isinstance(value, bool):
825 raise TypeError("Flip X should be a boolean type")
830 """If true, the top edge rendered will be the bottom edge of the bottom-most tile."""
834 def flip_y(self, value):
835 if not isinstance(value, bool):
836 raise TypeError("Flip Y should be a boolean type")
840 def transpose_xy(self):
841 """If true, the TileGrid’s axis will be swapped. When combined with mirroring, any 90 degree
842 rotation can be achieved along with the corresponding mirrored version.
844 return self._transpose_xy
847 def transpose_xy(self, value):
848 if not isinstance(value, bool):
849 raise TypeError("Transpose XY should be a boolean type")
850 self._transpose_xy = value
853 def pixel_shader(self):
854 """The pixel shader of the tilegrid."""
857 def __getitem__(self, index):
858 """Returns the tile index at the given index. The index can either be
859 an x,y tuple or an int equal to ``y * width + x``'.
861 if isinstance(index, (tuple, list)):
862 index = index[1] * self._width + index[0]
863 return self._data[index]
865 def __setitem__(self, index, value):
866 """Sets the tile index at the given index. The index can either be
867 an x,y tuple or an int equal to ``y * width + x``.
870 raise RuntimeError("Read-only object")
871 if isinstance(index, (tuple, list)):
874 index = y * self._width + x
875 elif ininstance(index, int):
876 x = index % self._width
877 y = index // self._width
878 self._data[index] = value