diff --git a/drivers/st7789/st7789_4bit.py b/drivers/st7789/st7789_4bit.py index 178639c..a1c3b13 100644 --- a/drivers/st7789/st7789_4bit.py +++ b/drivers/st7789/st7789_4bit.py @@ -36,6 +36,24 @@ DFR0995 = (34, 0, 0) # DFR0995 Contributed by @EdgarKluge WAVESHARE_13 = (0, 0, 16) # Waveshare 1.3" 240x240 LCD contributed by Aaron Mittelmeier ADAFRUIT_1_9 = (35, 0, PORTRAIT) # 320x170 TFT https://www.adafruit.com/product/5394 +# ST7789 commands +_ST7789_SWRESET = b"\x01" +_ST7789_SLPIN = b"\x10" +_ST7789_SLPOUT = b"\x11" +_ST7789_NORON = b"\x13" +_ST7789_INVOFF = b"\x20" +_ST7789_INVON = b"\x21" +_ST7789_DISPOFF = b"\x28" +_ST7789_DISPON = b"\x29" +_ST7789_CASET = b"\x2a" +_ST7789_RASET = b"\x2b" +_ST7789_RAMWR = b"\x2c" +_ST7789_VSCRDEF = b"\x33" +_ST7789_COLMOD = b"\x3a" +_ST7789_MADCTL = b"\x36" +_ST7789_VSCSAD = b"\x37" +_ST7789_RAMCTL = b"\xb0" + @micropython.viper def _lcopy(dest: ptr16, source: ptr8, lut: ptr16, length: int, gscale: bool): # rgb565 - 16bit/pixel @@ -143,13 +161,13 @@ class ST7789(framebuf.FrameBuffer): self._spi_init(self._spi) # Bus may be shared cmd = self._wcmd wcd = self._wcd - cmd(b"\x01") # SW reset datasheet specifies 120ms before SLPOUT + cmd(_ST7789_SWRESET) # SW reset datasheet specifies 120ms before SLPOUT sleep_ms(150) - cmd(b"\x11") # SLPOUT: exit sleep mode + cmd(_ST7789_SLPOUT) # SLPOUT: exit sleep mode sleep_ms(10) # Adafruit delay 500ms (datsheet 5ms) - wcd(b"\x3a", b"\x55") # _COLMOD 16 bit/pixel, 65Kbit color space - cmd(b"\x20") # INVOFF Adafruit turn inversion on. This driver fixes .rgb - cmd(b"\x13") # NORON Normal display mode + wcd(_ST7789_COLMOD, b"\x55") # _COLMOD 16 bit/pixel, 65Kbit color space + cmd(_ST7789_INVOFF) # INVOFF Adafruit turn inversion on. This driver fixes .rgb + cmd(_ST7789_NORON) # NORON Normal display mode # Table maps user request onto hardware values. index values: # 0 Normal @@ -171,8 +189,8 @@ class ST7789(framebuf.FrameBuffer): mode = (0x60, 0xE0, 0xA0, 0x20, 0, 0x40, 0xC0, 0x80)[user_mode] # Set display window depending on mode, .height and .width. self.set_window(mode) - wcd(b"\x36", int.to_bytes(mode, 1, "little")) - cmd(b"\x29") # DISPON. Adafruit then delay 500ms. + wcd(_ST7789_MADCTL, int.to_bytes(mode, 1, "little")) + cmd(_ST7789_DISPON) # DISPON. Adafruit then delay 500ms. # Define the mapping between RAM and the display. # Datasheet section 8.12 p124. @@ -210,9 +228,9 @@ class ST7789(framebuf.FrameBuffer): xe = rwd - xoff - 1 # Col address set. - self._wcd(b"\x2a", int.to_bytes((xs << 16) + xe, 4, "big")) + self._wcd(_ST7789_CASET, int.to_bytes((xs << 16) + xe, 4, "big")) # Row address set - self._wcd(b"\x2b", int.to_bytes((ys << 16) + ye, 4, "big")) + self._wcd(_ST7789_RASET, int.to_bytes((ys << 16) + ye, 4, "big")) def greyscale(self, gs=None): if gs is not None: diff --git a/utils/image_converter.py b/utils/image_converter.py new file mode 100755 index 0000000..ae9b067 --- /dev/null +++ b/utils/image_converter.py @@ -0,0 +1,158 @@ +#!/usr/bin/env python3 +""" +Convert an image file to a python module for use with the bitmap method. Use redirection to save the +output to a file. The image is converted to a bitmap using the number of bits per pixel you specify. +The bitmap is saved as a python module that can be imported and used with the bitmap method. + +.. seealso:: + - :ref:`alien.py`. + +Example +^^^^^^^ + +.. code-block:: console + + ./create_png_examples.py cat.png 4 > cat_bitmap.py + +The python file can be imported and displayed with the bitmap method. For example: + +.. code-block:: python + + import tft_config + import cat_bitmap + tft = tft_config.config(1) + tft.bitmap(cat_bitmap, 0, 0) + +Usage +^^^^^ + +.. code-block:: console + + usage: image_converter.py [-h] image_file bits_per_pixel + + Convert image file to python module for use with bitmap method. + + positional arguments: image_file Name of file containing image to convert bits_per_pixel + The number of bits to use per pixel (1..8) + + optional arguments: -h, --help show this help message and exit + +""" + +import sys +import argparse +from PIL import Image + + +def rgb_to_color565(r, g, b): + """ + Convert RGB color to the 16-bit color format (565). + + Args: + r (int): Red component of the RGB color (0-255). + g (int): Green component of the RGB color (0-255). + b (int): Blue component of the RGB color (0-255). + + Returns: + int: Converted color value in the 16-bit color format (565). + """ + + return ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b & 0xF8) + + +def convert_to_bitmap(image_file, bits_requested): + """ + Convert image file to python module for use with bitmap method. + + Args: + image_file (str): Name of file containing image to convert. + bits (int): The number of bits to use per pixel (1..8). + """ + + colors_requested = 1 << bits_requested + img = Image.open(image_file).convert("RGB") + img = img.convert("P", palette=Image.Palette.ADAPTIVE, colors=colors_requested) + palette = img.getpalette() + palette_colors = len(palette) // 3 + actual_colors = min(palette_colors, colors_requested) + bits_required = actual_colors.bit_length() + if bits_required < bits_requested: + print( + f"\nNOTE: Quantization reduced colors to {palette_colors} from the {bits_requested} " + f"requested, reconverting using {bits_required} bit per pixel could save memory.\n", + file=sys.stderr, + ) + + colors = [ + f"{rgb_to_color565(palette[color * 3], palette[color * 3 + 1], palette[color * 3 + 2]):04x}" + for color in range(actual_colors) + ] + + image_bitstring = "".join( + "".join( + "1" if (img.getpixel((x, y)) & (1 << bit - 1)) else "0" + for bit in range(bits_required, 0, -1) + ) + for y in range(img.height) + for x in range(img.width) + ) + + bitmap_bits = len(image_bitstring) + + print(f"HEIGHT = {img.height}") + print(f"WIDTH = {img.width}") + print(f"COLORS = {actual_colors}") + print(f"BITS = {bitmap_bits}") + print(f"BPP = {bits_required}") + print("PALETTE = [", end="") + + for i, rgb in enumerate(colors): + if i > 0: + print(",", end="") + print(f"0x{rgb}", end="") + + print("]") + + print("_bitmap =\\\nb'", end="") + + for i in range(0, bitmap_bits, 8): + if i and i % (16 * 8) == 0: + print("'\\\nb'", end="") + value = image_bitstring[i : i + 8] + color = int(value, 2) + print(f"\\x{color:02x}", end="") + + print("'\nBITMAP = memoryview(_bitmap)") + + +def main(): + """ + Convert image file to python module for use with bitmap method. + + Args: + image_file (str): Name of file containing image to convert. + bits_per_pixel (int): The number of bits to use per pixel (1..8). + """ + + parser = argparse.ArgumentParser( + description="Convert image file to python module for use with bitmap method.", + ) + + parser.add_argument("image_file", help="Name of file containing image to convert") + + parser.add_argument( + "bits_per_pixel", + type=int, + choices=range(1, 9), + default=1, + metavar="bits_per_pixel", + help="The number of bits to use per pixel (1..8)", + ) + + args = parser.parse_args() + bits = args.bits_per_pixel + convert_to_bitmap(args.image_file, bits) + + +if __name__ == "__main__": + main()