derkalle4 / python3-idotmatrix-client

reverse engineered python3 client to control all your 16x16 or 32x32 pixel displays (experimental)
GNU General Public License v3.0
148 stars 29 forks source link

Show more characters at a time on the screen #29

Open zhs628 opened 4 months ago

zhs628 commented 4 months ago

First of all, thank you to this project for unlocking greater potential for iDotMatrix in the hands of users.

While researching the library you’ve written, I intend to implement a small feature that can monitor keyboard inputs in real time and update them to iDotMatrix. However, I unexpectedly found that the _StringToBitmaps implemented in the Text class seems to only display two characters on the screen. Based on this, I made some modifications so that the program can arrange as many characters as possible on the screen based on the set character size (“char_width”, “char_height”).


def string_to_bitmaps(string, font, char_width=10, char_height=10):

    images = []

    for char in string:
        image = Image.new("1", (char_width, char_height), 0)
        draw = ImageDraw.Draw(image)
        _, _, text_width, text_height = draw.textbbox((0, 0), text=char, font=font)
        text_x = (char_width - text_width) // 2
        text_y = (char_height - text_height) // 2
        draw.text((text_x, text_y), char, fill=1, font=font)
        images.append(image)

    screen_image = join_images(images, char_width, char_height)

    width, height = screen_image.size
    half_width = width // 2

    left_half_image = screen_image.crop((0, 0, half_width, height))
    right_half_image = screen_image.crop((half_width, 0, width, height))

    return (construct_bit_map(left_half_image), construct_bit_map(right_half_image))

def join_images(images, char_width=10, char_height=10):
    width, height = images[0].size
    images = [ImageOps.fit(img.convert('1'), (width, height)) for img in images]

    result_width = 32  # My device is 32x32 pixels.
    result_height = 32
    result = Image.new('1', (result_width, result_height), color=0)  # 0: black

    x_char_num = 32 // char_width
    y_char_num = 32 // char_height

    for i, img in enumerate(images):
        x = (i % x_char_num) * width
        y = (i // y_char_num ) * height
        result.paste(img, (x, y))

    return result

def construct_bit_map(image:Image):
    """Converts half screen images (16x32) to bitmap images which suitable for iDotMatrix devices."""
    bitmap = bytearray()
    for y in range(32):
        for x in range(16):
            if x % 8 == 0:
                byte = 0
            pixel = image.getpixel((x, y))
            byte |= (pixel & 1) << (x % 8)
            if x % 8 == 7:
                bitmap.append(byte)

    return bitmap

# -----------
# tools for print binary image 
# -----------
# def decimal_to_binary_list(num):
#     binary_str = bin(num)[2:].zfill(8)
#     binary_list = ["#" if bit == "1" else "." for bit in binary_str]
#     return binary_list

# def print_binary_image(image: Image):
#     width, height = image._size
#
#     ascii_symbols = ['.', '#']
#     for y in range(height):
#         row_str = ''
#         for x in range(width):
#             pixel_value = image.getpixel((x, y))
#             row_str += ascii_symbols[pixel_value] + " "
#         print(row_str)

class NewText(Text):

    def _StringToBitmaps(self,text: str,font_path: Optional[str] = None, font_size: Optional[int] = 20) -> bytearray:
        """Converts text to bitmap images suitable for iDotMatrix devices."""
        # using open source font from https://www.fontspace.com/rain-font-f22577
        font_path = font_path or "./fonts/Rain-DRM3.otf"
        font_size = font_size or 20
        font = ImageFont.truetype(font_path, font_size)

        text = text.upper()  # In low resolution, uppercase letters are easier to recognize.

        # Construct bitmap from text, and split it into two halves (16x32)   
        left, right = string_to_bitmaps(text, font)

        # Join the two halves of the bitmap in a way that is compatible with iDotMatrix
        byte_stream = bytearray(b"\x05\xff\xff\xff" + left + b"\x05\xff\xff\xff" + right) 
        return byte_stream

I only have a 32x32 screen, sorry for not being able to adapt to 16x16 and 64x64 screens, hope the above content can bring you some convenience for your development❤️.

zhs628 commented 4 months ago

image

derkalle4 commented 4 months ago

Thanks, that was actually a ToDo which is open ;) I'll check on this over the next days and push an update to the library :)

derkalle4 commented 4 months ago

Hi @zhs628,

I currently do not understand why it shows more characters for you. Can you explain it to me? I also looked into the topic and added newly found things to #14 - where you can see my latest attempts to update the library. We would need something that works with all display sizes (16x16, 32x32 and 64x64) :)

zhs628 commented 4 months ago

Sorry, I didn’t specify the functionality and process. I will further explain how to transmit a 32x32 image to a 32x32 screen through the text interface.

First, I generate a complete 32x32 image based on the input text, then I cut it along the vertical midline into two halves (16x32), and then convert each of them into a bitmap. Finally, I prepend b"\x05\xff\xff\xff" to each of the bitmaps and concatenate them to create a byte_stream that can display a perfect 32x32 image on a 32x32 screen.

image

zhs628 commented 4 months ago

image