From 18d480d6b2c0abd0044810e6c10e8c1f4c3cb860 Mon Sep 17 00:00:00 2001 From: Liz Date: Tue, 22 Jul 2025 17:10:07 -0400 Subject: [PATCH 1/2] quad color --- adafruit_epd/jd79661.py | 610 ++++++++++++++++++++++++++++++++++++++++ examples/cp_8.bmp | Bin 0 -> 21088 bytes 2 files changed, 610 insertions(+) create mode 100644 adafruit_epd/jd79661.py create mode 100644 examples/cp_8.bmp diff --git a/adafruit_epd/jd79661.py b/adafruit_epd/jd79661.py new file mode 100644 index 0000000..e9563bc --- /dev/null +++ b/adafruit_epd/jd79661.py @@ -0,0 +1,610 @@ +# SPDX-FileCopyrightText: 2024 +# +# SPDX-License-Identifier: MIT + +""" +`adafruit_epd.jd79661` - Adafruit JD79661 - quad-color ePaper display driver +==================================================================================== +CircuitPython driver for Adafruit JD79661 quad-color display breakouts +* Author(s): [Your name here] + +**Hardware:** +* JD79661 Quad-Color ePaper Display + +**Notes on Architecture:** +This is the first quad-color display in the CircuitPython EPD library. Unlike tri-color +displays that use separate buffers for black and red/yellow, the JD79661 uses a single +buffer with 2 bits per pixel to represent 4 colors (black, white, yellow, red). + +This driver overrides the parent class's dual-buffer architecture to accommodate the +quad-color packed pixel format. All drawing operations are reimplemented to work with +the 2-bit color depth. +""" + +import time + +import adafruit_framebuf +from micropython import const + +from adafruit_epd.epd import Adafruit_EPD + +try: + """Needed for type annotations""" + import typing + + from busio import SPI + from digitalio import DigitalInOut + from typing_extensions import Literal + +except ImportError: + pass + +__version__ = "0.0.0+auto.0" +__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_EPD.git" + +# Command constants +_JD79661_PANEL_SETTING = const(0x00) +_JD79661_POWER_SETTING = const(0x01) +_JD79661_POWER_OFF = const(0x02) +_JD79661_POWER_ON = const(0x04) +_JD79661_BOOSTER_SOFTSTART = const(0x06) +_JD79661_DEEP_SLEEP = const(0x07) +_JD79661_DATA_START_XMIT = const(0x10) +_JD79661_DISPLAY_REFRESH = const(0x12) +_JD79661_PLL_CONTROL = const(0x30) +_JD79661_CDI = const(0x50) +_JD79661_RESOLUTION = const(0x61) + +# Color constants for internal use (2-bit values) +_JD79661_BLACK = const(0b00) +_JD79661_WHITE = const(0b01) +_JD79661_YELLOW = const(0b10) +_JD79661_RED = const(0b11) + +# Other command constants from init sequence +_JD79661_POFS = const(0x03) +_JD79661_TCON = const(0x60) +_JD79661_CMD_E7 = const(0xE7) +_JD79661_CMD_E3 = const(0xE3) +_JD79661_CMD_B4 = const(0xB4) +_JD79661_CMD_B5 = const(0xB5) +_JD79661_CMD_E9 = const(0xE9) +_JD79661_CMD_4D = const(0x4D) + + +class Adafruit_JD79661(Adafruit_EPD): + """driver class for Adafruit JD79661 quad-color ePaper display breakouts + + This driver implements a quad-color display with a single buffer using 2 bits + per pixel. This differs from the parent class architecture which assumes + separate buffers for black and color pixels in tri-color displays. + + **Color Architecture:** + - Uses a single buffer with 2 bits per pixel + - Supports 4 colors: BLACK (0b00), WHITE (0b01), YELLOW (0b10), RED (0b11) + - All drawing methods are overridden to handle the 2-bit packed pixel format + - The parent class's dual-buffer methods (_blackframebuf/_colorframebuf) are + set to the same buffer for compatibility but are not used directly + """ + + # Add color constants for convenience - these match parent class where applicable + BLACK = const(0) # 0b00 in the display buffer + WHITE = const(1) # 0b01 in the display buffer + YELLOW = const(2) # 0b10 in the display buffer + RED = const(3) # 0b11 in the display buffer + + def __init__( + self, + width: int, + height: int, + spi: SPI, + *, + cs_pin: DigitalInOut, + dc_pin: DigitalInOut, + sramcs_pin: DigitalInOut, + rst_pin: DigitalInOut, + busy_pin: DigitalInOut, + ) -> None: + """Initialize the quad-color display driver. + + Note: This driver uses a different buffer architecture than the parent class. + Instead of separate black and color buffers, it uses a single buffer with + 2 bits per pixel to represent 4 colors. + """ + super().__init__(width, height, spi, cs_pin, dc_pin, sramcs_pin, rst_pin, busy_pin) + + # Adjust width to be divisible by 8 for proper byte alignment + stride = width + if stride % 8 != 0: + stride += 8 - stride % 8 + + # For quad-color display, we need 2 bits per pixel + # So buffer size is width * height / 4 bytes + self._buffer1_size = int(stride * height / 4) + self._buffer2_size = 0 # No second buffer for this display + + if sramcs_pin: + self._buffer1 = self.sram.get_view(0) + # IMPORTANT: Both buffers point to the same memory for compatibility + # with parent class, but only _buffer1 is actually used + self._buffer2 = self._buffer1 + else: + self._buffer1 = bytearray(self._buffer1_size) + # IMPORTANT: Both buffers point to the same memory for compatibility + # with parent class, but only _buffer1 is actually used + self._buffer2 = self._buffer1 + + # Create framebuffers for API compatibility with parent class + # NOTE: These framebuffers are not used for actual drawing operations + # since they don't support 2-bit color depth. All drawing is done + # through overridden methods that directly manipulate the buffer. + self._framebuf1 = adafruit_framebuf.FrameBuffer( + self._buffer1, + width, + height, + stride=stride, + buf_format=adafruit_framebuf.MHMSB, + ) + self._framebuf2 = self._framebuf1 # Same framebuffer for compatibility + + # Set single byte transactions + self._single_byte_tx = True + + # Set up buffer references for parent class compatibility + # Both point to the same buffer since we don't have separate color planes + self.set_black_buffer(0, False) + self.set_color_buffer(0, False) + + # Initialize with default fill + self.fill(Adafruit_JD79661.WHITE) + + def begin(self, reset: bool = True) -> None: + """Begin communication with the display and set basic settings""" + if reset: + self.hardware_reset() + time.sleep(0.1) + self.power_down() + + def busy_wait(self) -> None: + """Wait for display to be done with current task, either by polling the + busy pin, or pausing. Note: JD79661 busy is HIGH when busy""" + if self._busy: + while not self._busy.value: # Wait for busy HIGH + time.sleep(0.01) + else: + time.sleep(0.5) + + def power_up(self) -> None: + """Power up the display in preparation for writing RAM and updating""" + self.hardware_reset() + self.busy_wait() + + # Send initialization sequence + time.sleep(0.01) # Wait 10ms + + self.command(_JD79661_CMD_4D, bytearray([0x78])) + self.command(_JD79661_PANEL_SETTING, bytearray([0x8F, 0x29])) # PSR, Display resolution is 128x250 + self.command(_JD79661_POWER_SETTING, bytearray([0x07, 0x00])) # PWR + self.command(_JD79661_POFS, bytearray([0x10, 0x54, 0x44])) # POFS + self.command(_JD79661_BOOSTER_SOFTSTART, bytearray([0x05, 0x00, 0x3F, 0x0A, 0x25, 0x12, 0x1A])) + self.command(_JD79661_CDI, bytearray([0x37])) # CDI + self.command(_JD79661_TCON, bytearray([0x02, 0x02])) # TCON + self.command(_JD79661_RESOLUTION, bytearray([0, 128, 0, 250])) # TRES + self.command(_JD79661_CMD_E7, bytearray([0x1C])) + self.command(_JD79661_CMD_E3, bytearray([0x22])) + self.command(_JD79661_CMD_B4, bytearray([0xD0])) + self.command(_JD79661_CMD_B5, bytearray([0x03])) + self.command(_JD79661_CMD_E9, bytearray([0x01])) + self.command(_JD79661_PLL_CONTROL, bytearray([0x08])) + self.command(_JD79661_POWER_ON) + + self.busy_wait() + + def power_down(self) -> None: + """Power down the display - required when not actively displaying!""" + # Only deep sleep if we have a reset pin + if self._rst: + self.command(_JD79661_POWER_OFF, bytearray([0x00])) + self.busy_wait() + self.command(_JD79661_DEEP_SLEEP, bytearray([0xA5])) + time.sleep(0.1) + + def update(self) -> None: + """Update the display from internal memory""" + self.command(_JD79661_DISPLAY_REFRESH, bytearray([0x00])) + self.busy_wait() + if not self._busy: + time.sleep(1) # Wait 1 second if no busy pin + + def write_ram(self, index: Literal[0, 1]) -> int: + """Send the one byte command for starting the RAM write process. + + Note: The index parameter is ignored since JD79661 uses a single buffer. + This parameter exists for API compatibility with the parent class. + """ + # JD79661 uses same command for all data + return self.command(_JD79661_DATA_START_XMIT, end=False) + + def set_ram_address(self, x: int, y: int) -> None: + """Set the RAM address location. + + Note: Not used on JD79661 chipset. Exists for API compatibility. + """ + # Not used for JD79661 + pass + + def fill(self, color: int) -> None: + """Fill the entire display with the specified color. + + This method is overridden to handle the 2-bit packed pixel format + used by the quad-color display. + + Args: + color: Color value (BLACK, WHITE, YELLOW, or RED) + + Raises: + ValueError: If an invalid color is specified + """ + # Map colors to fill patterns (4 pixels per byte) + color_map = { + Adafruit_JD79661.BLACK: 0x00, # 0b00000000 - all pixels black + Adafruit_JD79661.WHITE: 0x55, # 0b01010101 - all pixels white + Adafruit_JD79661.YELLOW: 0xAA, # 0b10101010 - all pixels yellow + Adafruit_JD79661.RED: 0xFF, # 0b11111111 - all pixels red + } + + if color not in color_map: + raise ValueError(f"Invalid color: {color}. Use BLACK (0), WHITE (1), YELLOW (2), or RED (3).") + + fill_byte = color_map[color] + + if self.sram: + self.sram.erase(0x00, self._buffer1_size, fill_byte) + else: + for i in range(self._buffer1_size): + self._buffer1[i] = fill_byte + + def pixel(self, x: int, y: int, color: int) -> None: + """Draw a single pixel in the display buffer. + + This method is overridden to handle the 2-bit packed pixel format. + Each byte contains 4 pixels, with 2 bits per pixel. + + Args: + x: X coordinate + y: Y coordinate + color: Color value (BLACK, WHITE, YELLOW, or RED) + """ + if (x < 0) or (x >= self.width) or (y < 0) or (y >= self.height): + return + + # Handle rotation + if self.rotation == 1: + x, y = y, x + x = self._width - x - 1 + if self._width % 8 != 0: + x -= self._width % 8 + elif self.rotation == 2: + x = self._width - x - 1 + y = self._height - y - 1 + if self._width % 8 != 0: + x += self._width % 8 + elif self.rotation == 3: + x, y = y, x + y = self._height - y - 1 + + # Calculate stride (width adjusted to be divisible by 8) + stride = self._width + if stride % 8 != 0: + stride += 8 - stride % 8 + + # Map color constants to 2-bit values + color_map = { + Adafruit_JD79661.BLACK: _JD79661_BLACK, + Adafruit_JD79661.WHITE: _JD79661_WHITE, + Adafruit_JD79661.YELLOW: _JD79661_YELLOW, + Adafruit_JD79661.RED: _JD79661_RED, + } + + if color not in color_map: + # Default to white for invalid colors + pixel_color = _JD79661_WHITE + else: + pixel_color = color_map[color] + + # Calculate byte address (4 pixels per byte) + addr = (x + y * stride) // 4 + + # Calculate bit offset within byte (2 bits per pixel) + # Pixels are packed left-to-right, MSB first + bit_offset = (3 - (x % 4)) * 2 + + # Create masks + byte_mask = 0x3 << bit_offset + byte_value = (pixel_color & 0x3) << bit_offset + + # Read, modify, write + if self.sram: + current = self.sram.read8(addr) + current &= ~byte_mask + current |= byte_value + self.sram.write8(addr, current) + else: + self._buffer1[addr] &= ~byte_mask + self._buffer1[addr] |= byte_value + + # Override these methods to handle quad-color properly + def rect(self, x: int, y: int, width: int, height: int, color: int) -> None: + """Draw a rectangle. + + Overridden to use the quad-color pixel method. + """ + for i in range(x, x + width): + self.pixel(i, y, color) + self.pixel(i, y + height - 1, color) + for j in range(y + 1, y + height - 1): + self.pixel(x, j, color) + self.pixel(x + width - 1, j, color) + + def fill_rect(self, x: int, y: int, width: int, height: int, color: int) -> None: + """Fill a rectangle with the passed color. + + Overridden to use the quad-color pixel method. + """ + for i in range(x, x + width): + for j in range(y, y + height): + self.pixel(i, j, color) + + def line(self, x_0: int, y_0: int, x_1: int, y_1: int, color: int) -> None: + """Draw a line from (x_0, y_0) to (x_1, y_1) in passed color. + + Overridden to use the quad-color pixel method. + """ + # Bresenham's line algorithm + dx = abs(x_1 - x_0) + dy = abs(y_1 - y_0) + sx = 1 if x_0 < x_1 else -1 + sy = 1 if y_0 < y_1 else -1 + err = dx - dy + + while True: + self.pixel(x_0, y_0, color) + if x_0 == x_1 and y_0 == y_1: + break + e2 = 2 * err + if e2 > -dy: + err -= dy + x_0 += sx + if e2 < dx: + err += dx + y_0 += sy + + def text( + self, + string: str, + x: int, + y: int, + color: int, + *, + font_name: str = "font5x8.bin", + size: int = 1, + ) -> None: + """Write text string at location (x, y) in given color, using font file. + + This method is for CircuitPython's built-in bitmap fonts only. + For TrueType fonts, use PIL/Pillow to draw text on an image and then + display the image using the image() method. + """ + # Validate color + if color not in [Adafruit_JD79661.BLACK, Adafruit_JD79661.WHITE, + Adafruit_JD79661.YELLOW, Adafruit_JD79661.RED]: + raise ValueError(f"Invalid color: {color}. Use BLACK (0), WHITE (1), YELLOW (2), or RED (3).") + + # Since we can't use the parent's framebuffer text method directly + # (it only supports 1-bit depth), we need to render to a temporary buffer + # and then copy the pixels in the requested color + + # Estimate text dimensions + text_width = len(string) * 6 * size # ~6 pixels per char + text_height = 8 * size # 8 pixel high font + + # Bounds check + text_width = min(text_width, self.width - x) + text_height = min(text_height, self.height - y) + + if text_width <= 0 or text_height <= 0: + return + + # Create temporary monochrome buffer + temp_buf_width = ((text_width + 7) // 8) * 8 + temp_buf = bytearray((temp_buf_width * text_height) // 8) + + # Create temporary framebuffer + temp_fb = adafruit_framebuf.FrameBuffer( + temp_buf, + temp_buf_width, + text_height, + buf_format=adafruit_framebuf.MHMSB + ) + + # Render text + temp_fb.fill(0) + temp_fb.text(string, 0, 0, 1, font_name=font_name, size=size) + + # Copy pixels in the requested color + for j in range(text_height): + for i in range(text_width): + byte_index = (j * temp_buf_width + i) // 8 + bit_index = 7 - ((j * temp_buf_width + i) % 8) + + if byte_index < len(temp_buf): + if (temp_buf[byte_index] >> bit_index) & 1: + self.pixel(x + i, y + j, color) + + def image(self, image: Image) -> None: + """Set buffer to value of Python Imaging Library image. The image should + be in RGB mode and a size equal to the display size. + """ + imwidth, imheight = image.size + if imwidth != self.width or imheight != self.height: + raise ValueError( + f"Image must be same dimensions as display ({self.width}x{self.height})." + ) + if self.sram: + raise RuntimeError("PIL image is not for use with SRAM assist") + # Grab all the pixels from the image, faster than getpixel. + pix = image.load() + # clear out any display buffers + self.fill(Adafruit_EPD.WHITE) + + if image.mode == "RGB": # RGB Mode + for y in range(image.size[1]): + for x in range(image.size[0]): + pixel = pix[x, y] + # Check for yellow first (high red + green, low blue) + if (pixel[0] >= 0x80) and (pixel[1] >= 0x80) and (pixel[2] < 0x80): + # yellowish + self.pixel(x, y, Adafruit_JD79661.YELLOW) + # Then check for red (high red, low green and blue) + elif (pixel[0] >= 0x80) and (pixel[1] < 0x80) and (pixel[2] < 0x80): + # reddish + self.pixel(x, y, Adafruit_JD79661.RED) + # Then black (all low) + elif (pixel[0] < 0x80) and (pixel[1] < 0x80) and (pixel[2] < 0x80): + # dark + self.pixel(x, y, Adafruit_JD79661.BLACK) + # else: remains white (from fill) + + elif image.mode == "L": # Grayscale + for y in range(image.size[1]): + for x in range(image.size[0]): + pixel = pix[x, y] + if pixel < 0x40: # 0-63 + self.pixel(x, y, Adafruit_JD79661.BLACK) + elif pixel < 0x80: # 64-127 + self.pixel(x, y, Adafruit_JD79661.YELLOW) + elif pixel < 0xC0: # 128-191 + self.pixel(x, y, Adafruit_JD79661.RED) + # else: 192-255 remains white + else: + raise ValueError("Image must be in mode RGB or mode L.") + + def image_dithered(self, image, dither_type="floyd-steinberg") -> None: + """Display an image with dithering to better represent colors/shades. + + This method converts the image to use only the 4 available colors + using error diffusion dithering for better visual quality. + + Args: + image: PIL Image object + dither_type: Type of dithering - "floyd-steinberg" or "simple" + + Raises: + ValueError: If image dimensions don't match display + RuntimeError: If SRAM is being used (not supported) + """ + imwidth, imheight = image.size + if imwidth != self.width or imheight != self.height: + raise ValueError( + f"Image must be same dimensions as display ({self.width}x{self.height})." + ) + if self.sram: + raise RuntimeError("PIL image dithering is not supported with SRAM assist") + + # Convert to RGB if not already + if image.mode != "RGB": + image = image.convert("RGB") + + # Define our 4 color palette in RGB + palette = [ + (0, 0, 0), # BLACK + (255, 255, 255), # WHITE + (255, 255, 0), # YELLOW + (255, 0, 0), # RED + ] + + # Create a working copy of the image as a list of lists + pixels = [] + for y in range(imheight): + row = [] + for x in range(imwidth): + r, g, b = image.getpixel((x, y)) + row.append([r, g, b]) + pixels.append(row) + + if dither_type == "floyd-steinberg": + # Floyd-Steinberg dithering + for y in range(imheight): + for x in range(imwidth): + old_pixel = pixels[y][x] + + # Find closest color in palette + min_dist = float('inf') + closest_color = 0 + closest_rgb = palette[0] + + for i, pal_color in enumerate(palette): + dist = sum((old_pixel[j] - pal_color[j])**2 for j in range(3)) + if dist < min_dist: + min_dist = dist + closest_color = i + closest_rgb = pal_color + + # Set pixel to closest color + self.pixel(x, y, closest_color) + + # Calculate error + error = [old_pixel[i] - closest_rgb[i] for i in range(3)] + + # Distribute error to neighboring pixels + if x + 1 < imwidth: + for i in range(3): + pixels[y][x + 1][i] += error[i] * 7 / 16 + + if y + 1 < imheight: + if x > 0: + for i in range(3): + pixels[y + 1][x - 1][i] += error[i] * 3 / 16 + + for i in range(3): + pixels[y + 1][x][i] += error[i] * 5 / 16 + + if x + 1 < imwidth: + for i in range(3): + pixels[y + 1][x + 1][i] += error[i] * 1 / 16 + + else: # Simple nearest-color mapping + for y in range(imheight): + for x in range(imwidth): + pixel = pixels[y][x] + + # Find closest color in palette + min_dist = float('inf') + closest_color = 0 + + for i, pal_color in enumerate(palette): + dist = sum((pixel[j] - pal_color[j])**2 for j in range(3)) + if dist < min_dist: + min_dist = dist + closest_color = i + + self.pixel(x, y, closest_color) + + # Parent class drawing method overrides for documentation + def set_black_buffer(self, index: Literal[0, 1], inverted: bool) -> None: + """Set the index for the black buffer data. + + Note: This method exists for API compatibility but has no effect on the + JD79661 since it uses a single buffer with 2-bit color depth rather than + separate black/color buffers. + """ + super().set_black_buffer(index, inverted) + + def set_color_buffer(self, index: Literal[0, 1], inverted: bool) -> None: + """Set the index for the color buffer data. + + Note: This method exists for API compatibility but has no effect on the + JD79661 since it uses a single buffer with 2-bit color depth rather than + separate black/color buffers. + """ + super().set_color_buffer(index, inverted) \ No newline at end of file diff --git a/examples/cp_8.bmp b/examples/cp_8.bmp new file mode 100644 index 0000000000000000000000000000000000000000..8fb66258151f5875d65f5b69232a914070228cb2 GIT binary patch literal 21088 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 literal 0 HcmV?d00001 From 711012ec84fc174cacc7772f22e0b14a872af006 Mon Sep 17 00:00:00 2001 From: Liz Date: Wed, 23 Jul 2025 15:23:18 -0400 Subject: [PATCH 2/2] pre-commit and examples --- adafruit_epd/jd79661.py | 377 +++++++++++------------------------ examples/cp_8.bmp | Bin 21088 -> 0 bytes examples/epd_blinka.py | 16 +- examples/epd_pillow_demo.py | 2 + examples/epd_pillow_image.py | 34 ++++ examples/epd_simpletest.py | 29 ++- 6 files changed, 186 insertions(+), 272 deletions(-) delete mode 100644 examples/cp_8.bmp diff --git a/adafruit_epd/jd79661.py b/adafruit_epd/jd79661.py index e9563bc..db9012f 100644 --- a/adafruit_epd/jd79661.py +++ b/adafruit_epd/jd79661.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2024 +# SPDX-FileCopyrightText: 2025 Liz Clark for Adafruit Industries # # SPDX-License-Identifier: MIT @@ -6,19 +6,7 @@ `adafruit_epd.jd79661` - Adafruit JD79661 - quad-color ePaper display driver ==================================================================================== CircuitPython driver for Adafruit JD79661 quad-color display breakouts -* Author(s): [Your name here] - -**Hardware:** -* JD79661 Quad-Color ePaper Display - -**Notes on Architecture:** -This is the first quad-color display in the CircuitPython EPD library. Unlike tri-color -displays that use separate buffers for black and red/yellow, the JD79661 uses a single -buffer with 2 bits per pixel to represent 4 colors (black, white, yellow, red). - -This driver overrides the parent class's dual-buffer architecture to accommodate the -quad-color packed pixel format. All drawing operations are reimplemented to work with -the 2-bit color depth. +* Author(s): Liz Clark """ import time @@ -33,6 +21,7 @@ import typing from busio import SPI + from circuitpython_typing.pil import Image from digitalio import DigitalInOut from typing_extensions import Literal @@ -73,25 +62,12 @@ class Adafruit_JD79661(Adafruit_EPD): - """driver class for Adafruit JD79661 quad-color ePaper display breakouts - - This driver implements a quad-color display with a single buffer using 2 bits - per pixel. This differs from the parent class architecture which assumes - separate buffers for black and color pixels in tri-color displays. - - **Color Architecture:** - - Uses a single buffer with 2 bits per pixel - - Supports 4 colors: BLACK (0b00), WHITE (0b01), YELLOW (0b10), RED (0b11) - - All drawing methods are overridden to handle the 2-bit packed pixel format - - The parent class's dual-buffer methods (_blackframebuf/_colorframebuf) are - set to the same buffer for compatibility but are not used directly - """ - - # Add color constants for convenience - these match parent class where applicable - BLACK = const(0) # 0b00 in the display buffer - WHITE = const(1) # 0b01 in the display buffer - YELLOW = const(2) # 0b10 in the display buffer - RED = const(3) # 0b11 in the display buffer + """Driver for the JD79661 quad-color ePaper display breakouts""" + + BLACK = const(0) # 0b00 in the display buffer + WHITE = const(1) # 0b01 in the display buffer + YELLOW = const(2) # 0b10 in the display buffer + RED = const(3) # 0b11 in the display buffer def __init__( self, @@ -106,38 +82,27 @@ def __init__( busy_pin: DigitalInOut, ) -> None: """Initialize the quad-color display driver. - + Note: This driver uses a different buffer architecture than the parent class. Instead of separate black and color buffers, it uses a single buffer with 2 bits per pixel to represent 4 colors. """ super().__init__(width, height, spi, cs_pin, dc_pin, sramcs_pin, rst_pin, busy_pin) - # Adjust width to be divisible by 8 for proper byte alignment stride = width if stride % 8 != 0: stride += 8 - stride % 8 - # For quad-color display, we need 2 bits per pixel - # So buffer size is width * height / 4 bytes self._buffer1_size = int(stride * height / 4) self._buffer2_size = 0 # No second buffer for this display if sramcs_pin: self._buffer1 = self.sram.get_view(0) - # IMPORTANT: Both buffers point to the same memory for compatibility - # with parent class, but only _buffer1 is actually used self._buffer2 = self._buffer1 else: self._buffer1 = bytearray(self._buffer1_size) - # IMPORTANT: Both buffers point to the same memory for compatibility - # with parent class, but only _buffer1 is actually used self._buffer2 = self._buffer1 - # Create framebuffers for API compatibility with parent class - # NOTE: These framebuffers are not used for actual drawing operations - # since they don't support 2-bit color depth. All drawing is done - # through overridden methods that directly manipulate the buffer. self._framebuf1 = adafruit_framebuf.FrameBuffer( self._buffer1, width, @@ -146,15 +111,15 @@ def __init__( buf_format=adafruit_framebuf.MHMSB, ) self._framebuf2 = self._framebuf1 # Same framebuffer for compatibility - + # Set single byte transactions self._single_byte_tx = True - + # Set up buffer references for parent class compatibility # Both point to the same buffer since we don't have separate color planes self.set_black_buffer(0, False) self.set_color_buffer(0, False) - + # Initialize with default fill self.fill(Adafruit_JD79661.WHITE) @@ -178,15 +143,19 @@ def power_up(self) -> None: """Power up the display in preparation for writing RAM and updating""" self.hardware_reset() self.busy_wait() - + # Send initialization sequence time.sleep(0.01) # Wait 10ms - + self.command(_JD79661_CMD_4D, bytearray([0x78])) - self.command(_JD79661_PANEL_SETTING, bytearray([0x8F, 0x29])) # PSR, Display resolution is 128x250 + self.command( + _JD79661_PANEL_SETTING, bytearray([0x8F, 0x29]) + ) # PSR, Display resolution is 128x250 self.command(_JD79661_POWER_SETTING, bytearray([0x07, 0x00])) # PWR self.command(_JD79661_POFS, bytearray([0x10, 0x54, 0x44])) # POFS - self.command(_JD79661_BOOSTER_SOFTSTART, bytearray([0x05, 0x00, 0x3F, 0x0A, 0x25, 0x12, 0x1A])) + self.command( + _JD79661_BOOSTER_SOFTSTART, bytearray([0x05, 0x00, 0x3F, 0x0A, 0x25, 0x12, 0x1A]) + ) self.command(_JD79661_CDI, bytearray([0x37])) # CDI self.command(_JD79661_TCON, bytearray([0x02, 0x02])) # TCON self.command(_JD79661_RESOLUTION, bytearray([0, 128, 0, 250])) # TRES @@ -197,12 +166,11 @@ def power_up(self) -> None: self.command(_JD79661_CMD_E9, bytearray([0x01])) self.command(_JD79661_PLL_CONTROL, bytearray([0x08])) self.command(_JD79661_POWER_ON) - + self.busy_wait() def power_down(self) -> None: """Power down the display - required when not actively displaying!""" - # Only deep sleep if we have a reset pin if self._rst: self.command(_JD79661_POWER_OFF, bytearray([0x00])) self.busy_wait() @@ -217,47 +185,39 @@ def update(self) -> None: time.sleep(1) # Wait 1 second if no busy pin def write_ram(self, index: Literal[0, 1]) -> int: - """Send the one byte command for starting the RAM write process. - - Note: The index parameter is ignored since JD79661 uses a single buffer. - This parameter exists for API compatibility with the parent class. - """ + """Send the one byte command for starting the RAM write process.""" # JD79661 uses same command for all data return self.command(_JD79661_DATA_START_XMIT, end=False) def set_ram_address(self, x: int, y: int) -> None: - """Set the RAM address location. - - Note: Not used on JD79661 chipset. Exists for API compatibility. - """ + """Set the RAM address location.""" # Not used for JD79661 pass def fill(self, color: int) -> None: """Fill the entire display with the specified color. - - This method is overridden to handle the 2-bit packed pixel format - used by the quad-color display. - + Args: color: Color value (BLACK, WHITE, YELLOW, or RED) - + Raises: ValueError: If an invalid color is specified """ # Map colors to fill patterns (4 pixels per byte) color_map = { - Adafruit_JD79661.BLACK: 0x00, # 0b00000000 - all pixels black - Adafruit_JD79661.WHITE: 0x55, # 0b01010101 - all pixels white + Adafruit_JD79661.BLACK: 0x00, # 0b00000000 - all pixels black + Adafruit_JD79661.WHITE: 0x55, # 0b01010101 - all pixels white Adafruit_JD79661.YELLOW: 0xAA, # 0b10101010 - all pixels yellow - Adafruit_JD79661.RED: 0xFF, # 0b11111111 - all pixels red + Adafruit_JD79661.RED: 0xFF, # 0b11111111 - all pixels red } - + if color not in color_map: - raise ValueError(f"Invalid color: {color}. Use BLACK (0), WHITE (1), YELLOW (2), or RED (3).") - + raise ValueError( + f"Invalid color: {color}. Use BLACK (0), WHITE (1), YELLOW (2), or RED (3)." + ) + fill_byte = color_map[color] - + if self.sram: self.sram.erase(0x00, self._buffer1_size, fill_byte) else: @@ -266,13 +226,10 @@ def fill(self, color: int) -> None: def pixel(self, x: int, y: int, color: int) -> None: """Draw a single pixel in the display buffer. - - This method is overridden to handle the 2-bit packed pixel format. - Each byte contains 4 pixels, with 2 bits per pixel. - + Args: x: X coordinate - y: Y coordinate + y: Y coordinate color: Color value (BLACK, WHITE, YELLOW, or RED) """ if (x < 0) or (x >= self.width) or (y < 0) or (y >= self.height): @@ -305,7 +262,7 @@ def pixel(self, x: int, y: int, color: int) -> None: Adafruit_JD79661.YELLOW: _JD79661_YELLOW, Adafruit_JD79661.RED: _JD79661_RED, } - + if color not in color_map: # Default to white for invalid colors pixel_color = _JD79661_WHITE @@ -314,15 +271,15 @@ def pixel(self, x: int, y: int, color: int) -> None: # Calculate byte address (4 pixels per byte) addr = (x + y * stride) // 4 - + # Calculate bit offset within byte (2 bits per pixel) # Pixels are packed left-to-right, MSB first bit_offset = (3 - (x % 4)) * 2 - + # Create masks byte_mask = 0x3 << bit_offset byte_value = (pixel_color & 0x3) << bit_offset - + # Read, modify, write if self.sram: current = self.sram.read8(addr) @@ -333,10 +290,9 @@ def pixel(self, x: int, y: int, color: int) -> None: self._buffer1[addr] &= ~byte_mask self._buffer1[addr] |= byte_value - # Override these methods to handle quad-color properly def rect(self, x: int, y: int, width: int, height: int, color: int) -> None: """Draw a rectangle. - + Overridden to use the quad-color pixel method. """ for i in range(x, x + width): @@ -348,7 +304,7 @@ def rect(self, x: int, y: int, width: int, height: int, color: int) -> None: def fill_rect(self, x: int, y: int, width: int, height: int, color: int) -> None: """Fill a rectangle with the passed color. - + Overridden to use the quad-color pixel method. """ for i in range(x, x + width): @@ -357,16 +313,15 @@ def fill_rect(self, x: int, y: int, width: int, height: int, color: int) -> None def line(self, x_0: int, y_0: int, x_1: int, y_1: int, color: int) -> None: """Draw a line from (x_0, y_0) to (x_1, y_1) in passed color. - + Overridden to use the quad-color pixel method. """ - # Bresenham's line algorithm dx = abs(x_1 - x_0) dy = abs(y_1 - y_0) sx = 1 if x_0 < x_1 else -1 sy = 1 if y_0 < y_1 else -1 err = dx - dy - + while True: self.pixel(x_0, y_0, color) if x_0 == x_1 and y_0 == y_1: @@ -389,54 +344,42 @@ def text( font_name: str = "font5x8.bin", size: int = 1, ) -> None: - """Write text string at location (x, y) in given color, using font file. - - This method is for CircuitPython's built-in bitmap fonts only. - For TrueType fonts, use PIL/Pillow to draw text on an image and then - display the image using the image() method. - """ - # Validate color - if color not in [Adafruit_JD79661.BLACK, Adafruit_JD79661.WHITE, - Adafruit_JD79661.YELLOW, Adafruit_JD79661.RED]: - raise ValueError(f"Invalid color: {color}. Use BLACK (0), WHITE (1), YELLOW (2), or RED (3).") - - # Since we can't use the parent's framebuffer text method directly - # (it only supports 1-bit depth), we need to render to a temporary buffer - # and then copy the pixels in the requested color - - # Estimate text dimensions - text_width = len(string) * 6 * size # ~6 pixels per char - text_height = 8 * size # 8 pixel high font - - # Bounds check + """Write text string at location (x, y) in given color, using font file.""" + color_map = { + Adafruit_JD79661.BLACK: _JD79661_BLACK, + Adafruit_JD79661.WHITE: _JD79661_WHITE, + Adafruit_JD79661.YELLOW: _JD79661_YELLOW, + Adafruit_JD79661.RED: _JD79661_RED, + } + if color not in color_map: + raise ValueError( + f"Invalid color: {color}. Use BLACK (0), WHITE (1), YELLOW (2), or RED (3)." + ) + + text_width = len(string) * 6 * size + text_height = 8 * size + text_width = min(text_width, self.width - x) text_height = min(text_height, self.height - y) - + if text_width <= 0 or text_height <= 0: return - - # Create temporary monochrome buffer + temp_buf_width = ((text_width + 7) // 8) * 8 temp_buf = bytearray((temp_buf_width * text_height) // 8) - - # Create temporary framebuffer + temp_fb = adafruit_framebuf.FrameBuffer( - temp_buf, - temp_buf_width, - text_height, - buf_format=adafruit_framebuf.MHMSB + temp_buf, temp_buf_width, text_height, buf_format=adafruit_framebuf.MHMSB ) - - # Render text + temp_fb.fill(0) temp_fb.text(string, 0, 0, 1, font_name=font_name, size=size) - - # Copy pixels in the requested color + for j in range(text_height): for i in range(text_width): byte_index = (j * temp_buf_width + i) // 8 bit_index = 7 - ((j * temp_buf_width + i) % 8) - + if byte_index < len(temp_buf): if (temp_buf[byte_index] >> bit_index) & 1: self.pixel(x + i, y + j, color) @@ -452,159 +395,73 @@ def image(self, image: Image) -> None: ) if self.sram: raise RuntimeError("PIL image is not for use with SRAM assist") + # Grab all the pixels from the image, faster than getpixel. pix = image.load() - # clear out any display buffers - self.fill(Adafruit_EPD.WHITE) + + # Clear out any display buffers (assuming white background) + self.fill(Adafruit_JD79661.WHITE) if image.mode == "RGB": # RGB Mode for y in range(image.size[1]): for x in range(image.size[0]): pixel = pix[x, y] - # Check for yellow first (high red + green, low blue) - if (pixel[0] >= 0x80) and (pixel[1] >= 0x80) and (pixel[2] < 0x80): - # yellowish + r, g, b = pixel[0], pixel[1], pixel[2] + + # Calculate brightness/luminance for better color detection + brightness = (r + g + b) / 3 + + # Color detection logic with thresholds + if brightness >= 200: # Light colors -> White + # White is typically the default, so we might not need to set it + # self.pixel(x, y, Adafruit_EPD.WHITE) + pass + elif r >= 128 and g >= 128 and b < 80: # Yellow detection + # High red and green, low blue self.pixel(x, y, Adafruit_JD79661.YELLOW) - # Then check for red (high red, low green and blue) - elif (pixel[0] >= 0x80) and (pixel[1] < 0x80) and (pixel[2] < 0x80): - # reddish + elif r >= 128 and g < 80 and b < 80: # Red detection + # High red, low green and blue + self.pixel(x, y, Adafruit_JD79661.RED) + elif brightness < 80: # Dark colors -> Black + # All RGB values are low + self.pixel(x, y, Adafruit_JD79661.BLACK) + elif r > g and r > b and r >= 100: + # Red-dominant self.pixel(x, y, Adafruit_JD79661.RED) - # Then black (all low) - elif (pixel[0] < 0x80) and (pixel[1] < 0x80) and (pixel[2] < 0x80): - # dark + elif r >= 100 and g >= 100: + # Both red and green high -> Yellow + self.pixel(x, y, Adafruit_JD79661.YELLOW) + elif brightness < 128: + # Medium-dark -> Black self.pixel(x, y, Adafruit_JD79661.BLACK) - # else: remains white (from fill) - - elif image.mode == "L": # Grayscale + # else: remains white (default) + + elif image.mode == "L": # Grayscale Mode for y in range(image.size[1]): for x in range(image.size[0]): pixel = pix[x, y] - if pixel < 0x40: # 0-63 + + # Map grayscale to 4 levels + if pixel < 64: self.pixel(x, y, Adafruit_JD79661.BLACK) - elif pixel < 0x80: # 64-127 + elif pixel < 128: + self.pixel(x, y, Adafruit_JD79661.RED) # Or could use YELLOW + elif pixel < 192: self.pixel(x, y, Adafruit_JD79661.YELLOW) - elif pixel < 0xC0: # 128-191 - self.pixel(x, y, Adafruit_JD79661.RED) - # else: 192-255 remains white + # else: pixel >= 192 -> WHITE (default) + + elif image.mode == "P": # Palette Mode (optional, for indexed color) + # Convert to RGB first for easier processing + rgb_image = image.convert("RGB") + self.image(rgb_image) # Recursive call with RGB image + else: - raise ValueError("Image must be in mode RGB or mode L.") - - def image_dithered(self, image, dither_type="floyd-steinberg") -> None: - """Display an image with dithering to better represent colors/shades. - - This method converts the image to use only the 4 available colors - using error diffusion dithering for better visual quality. - - Args: - image: PIL Image object - dither_type: Type of dithering - "floyd-steinberg" or "simple" - - Raises: - ValueError: If image dimensions don't match display - RuntimeError: If SRAM is being used (not supported) - """ - imwidth, imheight = image.size - if imwidth != self.width or imheight != self.height: - raise ValueError( - f"Image must be same dimensions as display ({self.width}x{self.height})." - ) - if self.sram: - raise RuntimeError("PIL image dithering is not supported with SRAM assist") - - # Convert to RGB if not already - if image.mode != "RGB": - image = image.convert("RGB") - - # Define our 4 color palette in RGB - palette = [ - (0, 0, 0), # BLACK - (255, 255, 255), # WHITE - (255, 255, 0), # YELLOW - (255, 0, 0), # RED - ] - - # Create a working copy of the image as a list of lists - pixels = [] - for y in range(imheight): - row = [] - for x in range(imwidth): - r, g, b = image.getpixel((x, y)) - row.append([r, g, b]) - pixels.append(row) - - if dither_type == "floyd-steinberg": - # Floyd-Steinberg dithering - for y in range(imheight): - for x in range(imwidth): - old_pixel = pixels[y][x] - - # Find closest color in palette - min_dist = float('inf') - closest_color = 0 - closest_rgb = palette[0] - - for i, pal_color in enumerate(palette): - dist = sum((old_pixel[j] - pal_color[j])**2 for j in range(3)) - if dist < min_dist: - min_dist = dist - closest_color = i - closest_rgb = pal_color - - # Set pixel to closest color - self.pixel(x, y, closest_color) - - # Calculate error - error = [old_pixel[i] - closest_rgb[i] for i in range(3)] - - # Distribute error to neighboring pixels - if x + 1 < imwidth: - for i in range(3): - pixels[y][x + 1][i] += error[i] * 7 / 16 - - if y + 1 < imheight: - if x > 0: - for i in range(3): - pixels[y + 1][x - 1][i] += error[i] * 3 / 16 - - for i in range(3): - pixels[y + 1][x][i] += error[i] * 5 / 16 - - if x + 1 < imwidth: - for i in range(3): - pixels[y + 1][x + 1][i] += error[i] * 1 / 16 - - else: # Simple nearest-color mapping - for y in range(imheight): - for x in range(imwidth): - pixel = pixels[y][x] - - # Find closest color in palette - min_dist = float('inf') - closest_color = 0 - - for i, pal_color in enumerate(palette): - dist = sum((pixel[j] - pal_color[j])**2 for j in range(3)) - if dist < min_dist: - min_dist = dist - closest_color = i - - self.pixel(x, y, closest_color) - - # Parent class drawing method overrides for documentation + raise ValueError("Image must be in mode RGB, L, or P.") + def set_black_buffer(self, index: Literal[0, 1], inverted: bool) -> None: - """Set the index for the black buffer data. - - Note: This method exists for API compatibility but has no effect on the - JD79661 since it uses a single buffer with 2-bit color depth rather than - separate black/color buffers. - """ + """Set the index for the black buffer data.""" super().set_black_buffer(index, inverted) def set_color_buffer(self, index: Literal[0, 1], inverted: bool) -> None: - """Set the index for the color buffer data. - - Note: This method exists for API compatibility but has no effect on the - JD79661 since it uses a single buffer with 2-bit color depth rather than - separate black/color buffers. - """ - super().set_color_buffer(index, inverted) \ No newline at end of file + """Set the index for the color buffer data.""" + super().set_color_buffer(index, inverted) diff --git a/examples/cp_8.bmp b/examples/cp_8.bmp deleted file mode 100644 index 8fb66258151f5875d65f5b69232a914070228cb2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 21088 zcmbuG3tUrYp7)>L)|txv5|h={0}4ri?R2KMsl8aWGqtw0R-Mjt#%{IRnOO*O(d`7A z3l~i~p&>|aBoZ2s>qJ3XAWmy_AtD`+RVy90Y8AWar?yqA?TekD@c#bKNdViK-Ff%@ zLz0t|lbrAQ{V&gR&Uv0i&#YO&xZQs{V+t(oz+*EWx3KT=^=Zo(i<>S!_zFC&d)dPZ z^)2jmqo2h^+1T?d{OstF)9kma``EIj`E0{FAN%0Y2-{}b$?|@)m8B+Uuor*1mi_jX zEVe1{ckJKy9$>E(>o&OK4bL&0qkL^Bo`NC<|RWh9&bqnl28y;nQ@!b#4z0VfD z_yhK))uucXu)sw%LSjG}vZqm5-V0+S%qUrEE2} zTaWEj*lrWHdwP=%->t@X>+s#3?3bHL*($7CkM}CPUytoo*nShX*I@fi_+1TtceUnC zwq<)ed-#`n_L{wet$JlM`_1o6?CC80-bN35@YyW3^w%b~zGN3$jo(w@_nvq@6Th>Q zP5-C+*{aOnvnPM`Dt_O?RM^KR>_dZnyixTwGuWEhKmFioc1OYwm?UKZOZo9H*w0ir zKK$77qebxk#nA8cSHzG z2X5mVF19}ZP3EqBm(5K7HCvV4#U6j^6}I(_de+o>kf~oYu&3AeV*MeeHXdM`3R{`B zq>Am?JHQ@aeF%>O_W}R4RRQ)izW4O%0Q>oG2eI8D_Watn*qZEamQ^swezU2c&5$XW-t?bLm1kwCKQyt& zR}8X&Eo{MKPqBZTcbGl&ct3k))gkuC;wtv^ z%KdEB>Rz_yw?pi~NA&E)bpiH@>JZEQw|{3bk}j4w>wOkCqlL|$w~yU@-#+%llcntW z7wXx}e;Qu@7tMj z!V+K;ZzdKNFJ3%0Hnw=NxPq3)Ldz4I{QoVCk57J|mgylO8y8I?`QI>t_-4$T5ALrW z=_JN~yA)=`MgXJ-Ou|SQ(uvE(Uuk(Ui3$s?o?MS@72z*T-H_26h~hR}>6-}n8~!1og%*h5 zyPMKl90xs(O;jg*1#ehRPIr5aKYCer${vckz?ZGnb5(`8R+s zu7|etRWP=+8h!1?n!%>iyLL4Jb5|3r+0$;Z8Y(mzjop@*kGZEq{RubX_+VzF9#4e_z5u{A!m!N038Q?o$^yFR)m5Ws&Ru)eU! z?{^t@h-EbP+|KnmW#91Q#()+D-Au4~;$(J(a); z{WUrMni}#nG#u=z>NOe-y^1P;{a^F*U(&?8b2f*2sdA z@zEG^1eX-5kt38!aNWya6A%@oqauMBjV(F&iwHTns>)L1^MR&Rtu|{ate&QEX__2I z(9|^8)Li8u#wd%`LK=&v_S*yRjmchgd#zTD-I$qa&n&dtd)tlOMq_JpO<_%HnpE2A z$6+8mq)LA#j>2efcUy8=+6_f!HR8$MP3uH^F)cPeD%#%?Kc1K*lPl%mWQH1JW?|u) zCLV{Ns51Dlv9F>+U0PZJQ_E$t#JFbUR0Kt=OfHjmRk__Buh)z2Cs-{ei^*#-x-7L7 zYPACdnf5}v)o3g%G#V@xL#7dovRG(a6TnZ&{T#<`@LHmC+BHU_8i-R&a4U!fhoCQfw_%Ei(` zlb7Bp$K_G&?q&q)$jE8_&#M-a%(TVoFlw|KO=e~$9)%9V%<5hvl_e&|7Znxl&5`2( zWAQ0|+v4r@zliuYs;7uZi~cl(My1`}qLf7_Q)Q{CGC4D3y_{*V8VW-?HiJ=Z#J-48 z11NJqmMW8yI8AP!T0JV4D{CNdh0ka8RuL$StLm{LN)T3=YY~x|MTJEUM`2c0mLsqR z2PqA>T&@ROr;|ZqA5j(OMJbsWk|gfLt1p zAd|(*#+58Hv%;)%bQ%rBxUIR$i^zxB!K1O`Ph(WdW#ip=VcwH}Yb?lXvJh4eyC zYh;P36w_^z*og5&nN*H`Pa!I^I=3^2>qv|!2Ih*1DACxv$LA@P$VQF587>JbA63S3 z*AOWE7?)ISE!{$9HCUIHW!zE_^4@&%=h8$m7TA;}^w6~)Ha93^Hwtf3Hx4YJ4vcSbMd+sX+ zLyQtx(~%v>RymCxPi?ISK~Y%b)UMU6&C&pp;y56c%3Bc8U~KlM%2VY=H7hF0%F4{D zaEUrrxjhwTGY;Nvucu?t)@vQ3%6sKGST1TVv}@KVA5D;QMlB8iaAN7us0%6z3QAG7 z5TI0Hk%m;_ExqmTrbMJly9FA@;Aj;!6-J}RSmgH9dP+4#>$9?ovMXKAjx4(ity5-Y zAXO^w&?08-Rn3LLxe}$H7#&VcmbStr>ge{Cn$23R28TrHRu7Pu6!VnQ!c1*eQ&Fa- zHDR7SkO|k&Ke-zlVOzV|TmUl{ru&VB>z|DJMZJyI4(GH;F;!|r2iwf-qYGpJ!?S?g}sgl+#W0pqM)f|vZB#IjKqeV{Sp+T+53^P`k z5!1BaOeh7@1kivcg3;boRH$uDn5&d=Kyr6|h|!*DCM2^NiBVG%b@!7$)o5+zT(eE1 z)73QuQ&SNv8jUs>GgG4Mw%7T3+tExRBoGnXwr@jGZnqlHD49x2HL^sh908D-oz>Oc zB9SW8!EUqaPw~v1=XLA_3h@KmRrmZCbtAoNLSV zDR3Sj6JnaZs9x@%JXO+!eA~5sFrq3oRj%l&Vos-BlereDB{F)c_5g9x9QGKwVsVRB z+u*jBl?BG+N||Hw)0>tY^=7li=(9fg^iR1^ga~Vu$)ICiz1AqqlPjY`3Y+W8wG^=h zzI1fJW~2&kCxGh$@>GQp`BzZt8SItGl!C#qz*U%qU9J@wQ69a>Yx4DkIUGnwcu4zh zt-b4gHh>woy;>>BbO)4Fc_GtNaaMVtksf^#HcpgY&S7_1Dx^Y z`dY*8-9e>XA#?myA!((#Sc~qKL+e0ngg3JLQ6B?)p=0-&1k_5nnbt#LEO> zJi+TjZ7?G#CA|4Edj|bzc$1Aj=5nbtx^-}qsH4fE);b86Jme>g_^el8~wpELO9vpq_N> zwrMoEW>0ggM6O6ss|%flPHebArw;krnrpM!^6=$`kZG;m68WQ&qQ-KU$LT=7XH3>m zRu=l0_Cm(DrG;Egjn7OBI7AMUWv?+L%$q0imEnD%B2}UYGt%Z{c43}ONKX?iCUZfq z9h%E?z^KtwH3yWBDug;SbAeH()9EQ5$=4!dnFGBl82v^^w;UeY+hB8bIUE$5vWEPZ z`KR=GXo0rZYV$SuV0=^5i8E$7jF#q@l)18QVlEVv@^o}8ZweN>kS?IQDTuM4o&w$o zPiiz>5u-|(LZ(p*g)JRG%-8BgMvo8SXw$xobuh@Ib!v)mZm(=!c zLB%{_Jd3Z!p*iXB_#1j^&^6fJ%vGD~0YHqvXuuY}x`0BN5Ey3f?q+nfIrMcp{t%@7 zdV0E$oD9Z#?rTK~d<`X=yKvrmqsFEdQ~=D*uB@bf&vtapz_{6#w>dA582Rp9#u$ZC z9`AG(4NgOSR4RiZF#0rPVOU#n0S=JxcsMlRD9}@DgU`kr%gP(--@{Ui179LWQOYtc z7}J$A6oz&iF(xQd{37EJt_$4oo9hIee5kTL;W0?(U;BXj675B=e zjrDcS)6TTXY^Bv)$MC0MaXHr{NR$bU4iLYjkR-Ge;ln99f>B6U z-bgokGzI4Rph7vbg%C8&L#T5%{H4`GCr*dP*k5+mRAN9wTLNl zcZ0zjrFc{(>(IHHJWens1{-x+;B%$(%V}SPNa z`+h(n5oDyJXgnld&TMfMp;u}3qxxl9OXmi1cu6leuF~Zl1bidtf6WS=l#hWRdVZ~V7Y%}Q%@U>iUmX|j;i)JX(g(-~S?o6H`BuE0?TB`?r_p%OL%Lr(- zd0>o8nVFtrx4kL2DuIZH$hgy*i&l(8nzUN2wn z`Lmxr|Fh?xdg_^{@Ob8#WlNW?Lg{x_x+*&ym99YN-q&6u<-mw8NgkG7eU%I^Yn*H(C(#l>Ys=+`C?qqbS5PzZiosafdUKq^l7 z+LAjdAT8yW{p#uGf%eR@Wy_Z>U%qVFv&)vPq{XGH>Dbyj+gwErWjaTB8D}in>(bZ| z(0Eh)2CXT#SuI}I==fZD>$_LZyz5L>rp#!WqGP`9y^Q2!{9ms(cpSyW+Cu0UgZ4a6 z+bql#l8vAhTy2#bDv2|cGr2rp^YamDM5ujf*|KKx^+%hmZF za7D;UW09lM*@#b&K%2`NbZst+*V(3+s|?(FWox4g=LN|vd89Og7s78Y17n6VSx9fF z@Pe_dP$5qd83(V=RAhA5+lqxYF&ZmHU%M@Yrqh?}_2>iW%JMUp64tX&k0e|UUfA-L zE0-@{U6!vcDbZ^6ZEcm6jxf4}Guh;3aI3rX9B}jouimt@pYT8g&Qqrd5NtXXx+Hvt_lgsXC;Grlo|EAPr zDt8LXM2X6&Yt-E##H0uC34K`1DLG?;;(Ddp>(IScT!=H}7!oS4Nf6SB@pWNCTib>W z@ODT?gRR`A;$e@9tuqf<~oN0rmrfSzJX5G_DR#o~V}?#xK$IyxG>E?e>M)i^&8ir+LB=W<4e z&8!hryzpd)eT}YJ3AG*6+tY*cUtXXvd=`A%%aqeRBA2dG=|hG?L12`Z=fmB3c_uqt z{Td+?B~VPcBZ{A8Bu)FXV@#e-L~Rlqf$9H##~TcU>;H^ z(r@6@%qF-uh|Uhz@VtC@m^W@ZG!uH=3oFUFNSu%nB5c_zRrv%Zz5&oi{l?9kbtbz8 zl@dn&f^D|edG*fs6sfWfl}cCEfjeHK_28Ri1Yt&cvS3$x63d8DCIcg^2BqgleSN7- zFe0s~_Jn+`ww3edmNJI+t5dC}WF~(vU$Jtkss8Y6!7GqtD|~>0VOEQd=AKjpa46pge{cL-zaPPIMiU)8~S*y~CDYU{|Kz4MwDV zVCFm_rruTtM$y+BL>)aETfV*oJsFXa+~`_`WLdf5xyLgWz%m|xZp8{pndMpKx(4i( zZ{m!}AiEJP)|u+cwK~Ky44n!*JFM-h#x{9;wCnir@vZh8ziiYVCM*`?F+Dk5e%)T- zDa47PLoQ2|)Wg@dmuAjO4`^&VY=Y?Nu#R9v+lyEvfAbyby-SfID<3C7ZW+&!nk$$8 z3P*x}CqX*XIb+7?=5hFVgYie48=y@UXLWZ0XP#&*4 z)NZ5BoZBIjN)yS~W|>@`-h!4@WW+_vkgq+N8uVp|Fl>#6RW~7$luJOld^JTN3F*{3 z_bQVKGCk%so3+k{V}#KW>KdKS`gX5VSLBbX+57Gv&NMoF)29c}y}%|o4s=Jf#5f~e zcHORa*WlWPBQ_~XYR=6yYg(m=0-ZJ3gbnCa5M#*KSw`x+PBjx4SA<2R*NlwCkQO?U zqX7aP8*d01GcwSDvE`cJ20M%~_;sOd#cberF~f>Bzh20q{V4)#GO zHcl|&TtO}r<$7xBaM>*?I$HYcUvDZf$EHcasM&o+sB-aMIA?6y<}p;Fy~TACZaC%8 z>Fm#~TsrA#Vtn9cojlUwm=# z;@PtoFJ8U~yL|TYJ7Oo8bi^69Uy~xDHKR22Smg zg?h6Wr>-I+@7=tnJ9XP7!UEjIF9;J$U(lP&0}v6vF$(zw#f|hjwL7XMHtDXr9(w4Z z`|i6hDe1oZ(s1Vvw_gM=7+u+6Mh_V4>g(wAmO$ykX2lD{Nb)_9{={Msf)SO7Gm?s^ z%sTxY@>%KBdC`JVLyXuttWlS*JAUc%WzdO;oDy&F_KTC0_&Qzi26RkGPVWX|QuJN- z0a2s`=sijP7JHrGDe7>dm2y!$;?|_40{3-r&y5ThB5}vj{n9f(UAlD1l9el$@>-5+ zj-J}`<*TyzX%lC0-ay76ZEiFeaJfgrucF{01wy4^D*#1At{~CD&f?|Ltz#10|7vY@ z>~(Y-eYsHoUoc>Zy^_5l<{7MhB7bkg(r@Q{)+fGTBad=zVqE=u>1&UW_V*^fxW zk3KpJyL|CvrCd2PJyDvHJY&WK_ue3d<>Ad+)QRQ_L8fcn!N_Ah2Q<*tQbzw zTNSnRxsm6WlZgE9@zaBYiBBwDzG{-jSa&^T-b_I$5sY4}6&(;Ft*11GdXy$1M~lms z-n}z5NuDlGOTqD_{4>|_F;PN#CeDQ*#J@l+y}cHLh3nYtZMWRP@9twb0&Y{$r!`A{ z^7GS+pA`|8J+ol;;NZ~w<;#{B$OR!AO_)1#o-#?|^U@KAAn7yp?#tZ6JWap|S{MO; zsc}hW%2H&JGt*O26!Y#%i++g6L^=)#K^H!@2#mPBhcO3UOS{PEZVyDZTlgphJn_K@ zh9x6|r=y;xPA>U-{_Mfiar2ifUr97!!yOA;zmUnS-rn#kyb@&1DKV{iGE-!dreOT& zgs=5(h?pjor=+AL%)KiuY9by-|A}?O@b*IgdVibsMo^0m4yYEWcG3P#F<8%{A10fd)!^g?qdh4y7J9lo|xzp|O z7{i2krJ)_(CuYwcjF?aTGYZPb=a0yUape#V_y}1`YCO6ENlBJwazdD%NUhc@m(QL( zG3&;SSs6DnW_|za<*&Z_iu`@q*Bu+37B8KfIyd!hB^Z0FIOAiFJtAsILL#ddJtQ0s z+sCwUe&AeR-^r81C&J`__q;Iw^x%y7xO_g@lI2T=Ffs))ClM=y>+xrE7k4A7U}+N}iOKc6VG-Z}WW*Kladr z55OM0{}J*u*+P-={Q2{QhsPmWI^WZC`23zdhxZ&hMA(N1-kU!^c|NiPMv1ckh)=9s zzQlu@C&a|-w@mT1+&m=%D8?TIs5XJn&_eU z=*ak_*r@2Jw79$4q2vAO1jy}yd!XXOaQ3$tNlUUGJVGjB>5U~|M1kZr{F!Cq>EX&3 z_8;00+jnRmz3%HjaNv`^FHfEz)sNf#CoX*-?c{=4vuVtDbB{EJRW<2o2hfp8C>uVD5yVkjU*-F%K7%oVm&qL=5D~=H1 zAxzr|Gw$o}-?wl7{zH542Wj_#9^%5jB(TfGMb(i?Iu*$;P9DGV>9s4LdU_4f0YQk4 zc0(6|xSzyN_0 zBit_{{^>IKIM)|ne0A}}=lmUX{Dk=bbl^Zg{tomVfaK9)eGHXIv8`G3Enoi)#PbJ_ zu3AoYC&UQE<-Z&lAV^{hBSORd`zIOq9SAWVxa~HvJtP%RG>!b_OD^Z-FNu*G->|>$ z+?-JTrY4aa>9}wX<@LuO^V;|~2q}a1yt!ieO1S!&XK|81DsDKk2ax3H{Xj%K6707a zvF1;tQ_slAUoMk#VPEw|oQ@aOY4ei=-V_a~H zamfCwU|hg;eE8ve=7RCTg(BnkgwKwBcJ#uL;lMRar~Kxd_iwEX1jldr?BK!AK0EmF z$>VK7jISQUfZoOOAdUT>+iohG#fgKzP@Mv?AeJwgriAEFv5;r zI2mL|v2g6O&o1;0w0-^=5MdWi+{r%v?Bmbi^nsIC87Ph%xq9^q>%QfdTPxdw!I-hd z;|U4!L}?n_fR5NSQhkD*;9-5=2iAM&>HzZW#lPUqFz@%A{bULvO5|{IddB^5E(PQx z3+B&SaL*z@E?oFy&Uow~F%lr`=(!8Q&-oH({N7P$dho*GfxaVuK0GjhUeLe^#;9FK z!+G*l;P%@ol&@a>bKsU+?`-Q#7#n?5E|E#oq)F3ju1%W;!9Tq^YyN^ONE5P)U(A|y z$0tzn>{k~_L^7)1eSQ6>7GzLghY%loc>aO~vmPPDMT>}$5PFCa3()Ui--W=Wj)CtT z#p;8H5AQj7hJ8$p+@V7!&M@k9oIec4;CX6NzQ{= z@GaNAcjoh3ZvD%x3y{flCWHU8Uw`p6P8H7L3HW5k#gG{uo|Q45BU08pvS8K^Ipe}b zKRzNHJ9g}|k9+z?*~dJAdwTkA?>uq=b)DExw6UYKaQM)`$v?0jsyBP~h>Yl99s=WG zYWw!@JMk_UKm72W509Rz?D+hhcRoMX(|795e?ZzDzHqMZ(&ev7L!89m=_5!8`}%S) znD8hDMB=)wG(I+Z`t+!j`*>DUZZEw5hmY_A4#rc$JI9W`eQNmAm}>(^ju7RAb4TC% zP_z#|IMMd`!DFAj{dUjcfs^lELovkvo)hmf zcI?ArV7$HU$eu&}hkFj3eCGsdNRZs<^!n`C|AX3wUuZjmBs+5C_;B|%21Z^TseKV` z(ZWS{O&1~~lVYXP)S<2}4E){Rg*hO1r zdG_o_U*Q_SF^Y)3BW-8)^q^fjaq7+^J>R3gH!qNQ=7xA||FaMqH!^~03UN`9QPJ^% zHBm!NBZIiS78eO}snj1C7a12f6j|esmDNl``Mfsm(`#*Q@ah><`{8doPMt!b`QyP8 zmA8Dr?clKFo1>qdM;$tOymOdRqW|29Td#cyM#}87U+{|*Ks?`b^g_?UlUIWmde9{7 zIdL*@q~`*fmPzATz3|6Z0$_|7ii?VlZ4F4{qvD33XBT?MIDbOl6LFFh2X;(FL4Api zii?Z%$KnKVXlRIL0$6HN{kwO+d*;m5E1w4ceB{j0Be$RHJB})Ll+x$zqn$itK1T(+ zb~_-suPI;7esuQh4^REqf1&iB9ERBa=+P77z<~cDLZrmzsJKY6Xzs8}=!(EV^j)V1 zBZ1lKkB*M)8jP3&=$v?ItUoR~5<*5L`EiaZO^S|!ra(>tXS_cy4qa$mbd8ON;gkd$ zqSue3iiQ{B+NVY3wEnqzH)AN6!E0B0zJ!ctaoS2(d%phqQ1CB*!4Vuga3pvHnxE@C zaUw|dCkaAQ>jFcs4CmHqA0F=(wr#__gXXHPq4uF6Lye`kd8lisYY>Cy5dhKgt&;&IWn1 ze0}zlE7R`0in5B6wrPP=Xc`83j$iG3hh!(n2^kkIO1rTC#BL!!Uz?Zra{lK0mtWpU zgS~Nip{Z#oI!=1moSDgS@pIfG7QVGW9=;$Otz{T_g zG~UbVfcISOCL04tiRL^H=|B9myi1t6bGj5ITvxJ84+2{&*PBcjS}i1NqBBuWz# zVghSAI*RO=LPD3<5k_ldva~s}MA?gWM@I)nX$OLsJr!)tiQUEL;%sYbZua8xSXJ|O zOn&2&Nlp(&L=2vu%2-(fO56`7#`svAtqYj>cC)=ij8fcBxqkC{dWXAy8G~T-OJz|K zC?9a)ZXtm2hw@ltlCv&e3!Y2AgXMPwciMu_j&!6!ag zagoM^OCBEgwyBIjgpM2Yw>Nb~#YJE?p%A(~pwr?0IJd0PgoM-xNIH%y{aDBUG4v51 zIuk|{(!khjj736WP8*F`)R)jK7d})F6J2nDa%Oy@Yz-z35G1U+qI8GRzpIKE=@O$= zT)l&gPtu@kM#NZBQo?(K|kgd|ZGz;M`V+_kL6OstBE9md#QG(n7; zRfNa%j8ZV}z#j~ZA|O!$5{$pu-sJVaq)3Q~;bWUQZiQ^49Zd?0m~4gd)#kiB5pu1$EhtNxjes=r`>F-ic&V<~(Lp(bi3>0{A%-B~?2x~~ z$nofje63eO$8GI3Ly-xBfU$P#*4@OY8y$;5mV=S&I7Q$_{V2i!bN||n7!RHfC)8j- zyBRm4@@WLJO>0|g17mOq<;Vzfy3>!bt?+fngpTeI;IFVJMXhqJ@eN|+xju+QP=!l%D^48?cl+B14lVUEI z$!t>Nz8S{0V|Ins=E7jxIAu_lL-&3$O9Z6VrCXpQ#)gWxJ|?{MBA9 zHs=2hLtZ^L++fx_A>z%9dQ4;q1l!sIZBBsEokAi6F5bO_LJu81mYN!ze_45 z)8O=C@UX^b)EK%^5UD`6RA5S{fe_8r6I-9jZMvc!oQD!@B%e ziP5}~iw7ZT2nZRC_R*r{G`9%DE$JXGauR8iGY=~OPguJNU@v7|1y8}r>TUPv}m=aj^MaFCN(%-WA)fQ8nbCfg`u=yM^VcZ9jP24X~I$>t9MXd zlZ6Pepx13CMm2QgxAQTK(C4;mH4P;VYJughY<4Kb_RV)7YO6&0DTY^T0F zh-Nba)fir@HkVR)sW4f+8mrx_bsBV5trlPgqt#$vYqwhM7OTdqG2u{lI9tPvZV#1B z&MQKD*j!QMuY(y@aPTQy){Lq4c=@$ z=HL1p?RJ&R>%ve`ms9WRa1~*sKIX!CN)cxj)yND0iryedj2gwbFBnZL$-}HuTfr*@ z)jLey2_0(@qXbEeG|x^fkekG;=BVUz6mdPznX-)-s)cYYe_5x}XJfdL%Bjj$VK|lp zx4SW#2%+gfU}98swq2ElkLm?N0M5E~K;r)*Cr1o1ZUG}y3aLoV(*z};0ZAI7T-s11 z3o5plykQ-8)|#sU3CyWFZdt{1n(~@INJkZXU7HWBpdtqd5fT~IM9vr@; zOTH$?ogSOH_)Q8)9+ZR#A-80s8Jxnn0q^vvkG0y3`IIqJ_W#!w;cAS$A)|#!Mm3`D zX2x)9C9dV^4M^ztikR1)Fe4B-BoYUJ+}FAAFm)jDLWayKaZ(LOVdMGo*T^Q;@kHW) zp;b<*j~-L48RPyZ8OssdjT6r10wWeMK$4QC0y<*us~C^a@y+Vji4vU+-XMZ<%QIr_ zq3jMbl8zosjF`%Jb8bvPetRLzSck;;4kL=f#5&aS5M#lXEv6}q#hj6REqa=KJzp$2 zA|h0Tj$uYUAOS(B-)8w6?^U`gs^KQ|?54ycRReJ}>?9+uJRohThsGH(L<0BnDIR&s z@PK@sblkB6bI*SuX7z-#!Ke~-#L(5h8!pTY#%A(0;xQ~4I!=>}6Oj2dsu0r?;M5(^ zao08slc?Ic(?bbEe`pNDT_5C38z0xqt0Rxd4Pe~9&4aly`oC8`PROHy>*%hnt(asa o1w|eCvxtt|1|p_;fDvP}$=5s}Ye8N8COnOnMr5QW`$5M3FUpormjD0& diff --git a/examples/epd_blinka.py b/examples/epd_blinka.py index cb91569..c950e4d 100644 --- a/examples/epd_blinka.py +++ b/examples/epd_blinka.py @@ -11,6 +11,7 @@ from adafruit_epd.il0373 import Adafruit_IL0373 from adafruit_epd.il0398 import Adafruit_IL0398 from adafruit_epd.il91874 import Adafruit_IL91874 +from adafruit_epd.jd79661 import Adafruit_JD79661 from adafruit_epd.ssd1608 import Adafruit_SSD1608 from adafruit_epd.ssd1675 import Adafruit_SSD1675 from adafruit_epd.ssd1675b import Adafruit_SSD1675B @@ -18,9 +19,6 @@ from adafruit_epd.ssd1681 import Adafruit_SSD1681 from adafruit_epd.uc8151d import Adafruit_UC8151D -# create the spi device and pins we will need -spi = busio.SPI(board.SCK, MOSI=board.MOSI, MISO=board.MISO) - # create the spi device and pins we will need spi = busio.SPI(board.SCK, MOSI=board.MOSI, MISO=board.MISO) ecs = digitalio.DigitalInOut(board.D4) @@ -32,6 +30,7 @@ # give them all to our driver print("Creating display") +# display = Adafruit_JD79661(122, 150, # 2.13" Quad-color display # display = Adafruit_SSD1608(200, 200, # 1.54" HD mono display # display = Adafruit_SSD1680(122, 250, # 2.13" HD Tri-color display # display = Adafruit_SSD1681(200, 200, # 1.54" HD Tri-color display @@ -70,6 +69,7 @@ image = Image.new("RGB", (width, height)) WHITE = (0xFF, 0xFF, 0xFF) +YELLOW = (0xFF, 0xFF, 0x00) RED = (0xFF, 0x00, 0x00) BLACK = (0x00, 0x00, 0x00) @@ -118,9 +118,15 @@ # Some other nice fonts to try: http://www.dafont.com/bitmap.php # font = ImageFont.truetype('Minecraftia.ttf', 8) +if type(display) == Adafruit_JD79661: + # for quad color, test yellow + fill = YELLOW +else: + # otherwise, text is red + fill = RED # Write two lines of text. -draw.text((x, top), "Hello", font=font, fill=RED) -draw.text((x, top + 20), "World!", font=font, fill=RED) +draw.text((x, top), "Hello", font=font, fill=fill) +draw.text((x, top + 20), "World!", font=font, fill=fill) # Display image. display.image(image) diff --git a/examples/epd_pillow_demo.py b/examples/epd_pillow_demo.py index 7dcea7a..11dfb3a 100644 --- a/examples/epd_pillow_demo.py +++ b/examples/epd_pillow_demo.py @@ -15,6 +15,7 @@ from adafruit_epd.il0373 import Adafruit_IL0373 from adafruit_epd.il0398 import Adafruit_IL0398 from adafruit_epd.il91874 import Adafruit_IL91874 +from adafruit_epd.jd79661 import Adafruit_JD79661 from adafruit_epd.ssd1608 import Adafruit_SSD1608 from adafruit_epd.ssd1675 import Adafruit_SSD1675 from adafruit_epd.ssd1680 import Adafruit_SSD1680, Adafruit_SSD1680Z @@ -42,6 +43,7 @@ busy = digitalio.DigitalInOut(board.D17) # give them all to our driver +# display = Adafruit_JD79661(122, 150, # 2.13" Quad-color display # display = Adafruit_SSD1608(200, 200, # 1.54" HD mono display # display = Adafruit_SSD1675(122, 250, # 2.13" HD mono display # display = Adafruit_SSD1680(122, 250, # 2.13" HD Tri-color or mono display diff --git a/examples/epd_pillow_image.py b/examples/epd_pillow_image.py index 02238ef..cf96d27 100644 --- a/examples/epd_pillow_image.py +++ b/examples/epd_pillow_image.py @@ -17,6 +17,7 @@ from adafruit_epd.il0373 import Adafruit_IL0373 from adafruit_epd.il0398 import Adafruit_IL0398 from adafruit_epd.il91874 import Adafruit_IL91874 +from adafruit_epd.jd79661 import Adafruit_JD79661 from adafruit_epd.ssd1608 import Adafruit_SSD1608 from adafruit_epd.ssd1675 import Adafruit_SSD1675 from adafruit_epd.ssd1680 import Adafruit_SSD1680, Adafruit_SSD1680Z @@ -32,6 +33,7 @@ busy = digitalio.DigitalInOut(board.D17) # give them all to our driver +# display = Adafruit_JD79661(122, 150, # 2.13" Quad-color display # display = Adafruit_SSD1608(200, 200, # 1.54" HD mono display # display = Adafruit_SSD1675(122, 250, # 2.13" HD mono display # display = Adafruit_SSD1680(122, 250, # 2.13" HD Tri-color or mono display @@ -86,6 +88,38 @@ # Convert to Monochrome and Add dithering # image = image.convert("1").convert("L") +if type(display) == Adafruit_JD79661: + # Create a palette with the 4 colors: Black, White, Red, Yellow + # The palette needs 768 values (256 colors × 3 channels) + palette = [] + + # We'll map the 256 palette indices to our 4 colors + # 0-63: Black, 64-127: Red, 128-191: Yellow, 192-255: White + for i in range(256): + if i < 64: + palette.extend([0, 0, 0]) # Black + elif i < 128: + palette.extend([255, 0, 0]) # Red + elif i < 192: + palette.extend([255, 255, 0]) # Yellow + else: + palette.extend([255, 255, 255]) # White + + # Create a palette image + palette_img = Image.new("P", (1, 1)) + palette_img.putpalette(palette) + + # Optional: Enhance colors before dithering for better results + # from PIL import ImageEnhance + # enhancer = ImageEnhance.Color(image) + # image = enhancer.enhance(1.5) # Increase color saturation + + # Quantize the image using Floyd-Steinberg dithering + image = image.quantize(palette=palette_img, dither=Image.FLOYDSTEINBERG) + + # Convert back to RGB for the display driver + image = image.convert("RGB") + # Display image. display.image(image) display.display() diff --git a/examples/epd_simpletest.py b/examples/epd_simpletest.py index 63c0a29..b8d311e 100644 --- a/examples/epd_simpletest.py +++ b/examples/epd_simpletest.py @@ -10,6 +10,7 @@ from adafruit_epd.il0373 import Adafruit_IL0373 from adafruit_epd.il0398 import Adafruit_IL0398 from adafruit_epd.il91874 import Adafruit_IL91874 +from adafruit_epd.jd79661 import Adafruit_JD79661 from adafruit_epd.ssd1608 import Adafruit_SSD1608 from adafruit_epd.ssd1675 import Adafruit_SSD1675 from adafruit_epd.ssd1680 import Adafruit_SSD1680, Adafruit_SSD1680Z @@ -26,6 +27,7 @@ # give them all to our drivers print("Creating display") +# display = Adafruit_JD79661(122, 150, # 2.13" Quad-color display # display = Adafruit_SSD1608(200, 200, # 1.54" HD mono display # display = Adafruit_SSD1675(122, 250, # 2.13" HD mono display # display = Adafruit_SSD1680(122, 250, # 2.13" HD Tri-color display @@ -58,20 +60,33 @@ # display.set_color_buffer(1, True) display.rotation = 1 +if type(display) == Adafruit_JD79661: + WHITE = Adafruit_JD79661.WHITE + BLACK = Adafruit_JD79661.BLACK + RED = Adafruit_JD79661.RED + YELLOW = Adafruit_JD79661.YELLOW +else: + WHITE = Adafruit_EPD.WHITE + BLACK = Adafruit_EPD.BLACK + RED = Adafruit_EPD.RED # clear the buffer print("Clear buffer") -display.fill(Adafruit_EPD.WHITE) -display.pixel(10, 100, Adafruit_EPD.BLACK) +display.fill(WHITE) +display.pixel(10, 100, BLACK) print("Draw Rectangles") -display.fill_rect(5, 5, 10, 10, Adafruit_EPD.RED) -display.rect(0, 0, 20, 30, Adafruit_EPD.BLACK) +display.fill_rect(5, 5, 10, 10, RED) +display.rect(0, 0, 20, 30, BLACK) print("Draw lines") -display.line(0, 0, display.width - 1, display.height - 1, Adafruit_EPD.BLACK) -display.line(0, display.height - 1, display.width - 1, 0, Adafruit_EPD.RED) +if type(display) == Adafruit_JD79661: + display.line(0, 0, display.width - 1, display.height - 1, YELLOW) + display.line(0, display.height - 1, display.width - 1, 0, YELLOW) +else: + display.line(0, 0, display.width - 1, display.height - 1, BLACK) + display.line(0, display.height - 1, display.width - 1, 0, RED) print("Draw text") -display.text("hello world", 25, 10, Adafruit_EPD.BLACK) +display.text("hello world", 25, 10, BLACK) display.display()