diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index db784cd..880a0f5 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -12,7 +12,7 @@ jobs: strategy: matrix: os: [ubuntu-latest] - python-minor-version: [7, 8, 9, 10] + python-minor-version: [7, 8, 9, 10, 11] name: Python 3.${{ matrix.python-minor-version }} steps: - uses: actions/checkout@v3 diff --git a/LICENSE.rst b/LICENSE.rst index 485e126..6b97214 100644 --- a/LICENSE.rst +++ b/LICENSE.rst @@ -1,7 +1,7 @@ MIT License =========== -Copyright (c) 2017-2022 Richard Hull and contributors +Copyright (c) 2017-2023 Richard Hull and contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.rst b/README.rst index 8aea73f..57cb5cb 100644 --- a/README.rst +++ b/README.rst @@ -22,8 +22,8 @@ Assuming you are using a Raspberry Pi (running Debian Jessie or newer), follow t instructions in the above repositories to wire up your display, then from a command-line:: $ sudo usermod -a -G i2c,spi,gpio pi - $ sudo apt install python3-dev python3-pip libfreetype6-dev libjpeg-dev build-essential - $ sudo apt install libsdl-dev libportmidi-dev libsdl-ttf2.0-dev libsdl-mixer1.2-dev libsdl-image1.2-dev + $ sudo apt install python3-dev python3-pip python3-numpy libfreetype6-dev libjpeg-dev build-essential + $ sudo apt install libsdl2-dev libsdl2-image-dev libsdl2-mixer-dev libsdl2-ttf-dev libportmidi-dev Log out and in again and clone this repository:: @@ -93,29 +93,29 @@ flag to show the options:: $ python3 examples/demo.py --help usage: demo.py [-h] [--config CONFIG] [--display DISPLAY] [--width WIDTH] - [--height HEIGHT] [--rotate ROTATION] [--interface INTERFACE] - [--i2c-port I2C_PORT] [--i2c-address I2C_ADDRESS] - [--spi-port SPI_PORT] [--spi-device SPI_DEVICE] - [--spi-bus-speed SPI_BUS_SPEED] - [--spi-transfer-size SPI_TRANSFER_SIZE] - [--spi-cs-high SPI_CS_HIGH] [--ftdi-device FTDI_DEVICE] - [--framebuffer-device FRAMEBUFFER_DEVICE] [--gpio GPIO] - [--gpio-mode GPIO_MODE] - [--gpio-data-command GPIO_DATA_COMMAND] - [--gpio-chip-select GPIO_CHIP_SELECT] - [--gpio-reset GPIO_RESET] [--gpio-backlight GPIO_BACKLIGHT] - [--gpio-reset-hold-time GPIO_RESET_HOLD_TIME] - [--gpio-reset-release-time GPIO_RESET_RELEASE_TIME] - [--block-orientation ORIENTATION] [--mode MODE] - [--framebuffer FRAMEBUFFER] [--num-segments NUM_SEGMENTS] - [--bgr] [--inverse] [--h-offset H_OFFSET] - [--v-offset V_OFFSET] [--backlight-active VALUE] [--debug] - [--transform TRANSFORM] [--scale SCALE] [--duration DURATION] - [--loop LOOP] [--max-frames MAX_FRAMES] + [--height HEIGHT] [--rotate ROTATION] [--interface INTERFACE] + [--i2c-port I2C_PORT] [--i2c-address I2C_ADDRESS] + [--spi-port SPI_PORT] [--spi-device SPI_DEVICE] + [--spi-bus-speed SPI_BUS_SPEED] + [--spi-transfer-size SPI_TRANSFER_SIZE] + [--spi-cs-high SPI_CS_HIGH] [--ftdi-device FTDI_DEVICE] + [--framebuffer-device FRAMEBUFFER_DEVICE] [--gpio GPIO] + [--gpio-mode GPIO_MODE] + [--gpio-data-command GPIO_DATA_COMMAND] + [--gpio-chip-select GPIO_CHIP_SELECT] + [--gpio-reset GPIO_RESET] [--gpio-backlight GPIO_BACKLIGHT] + [--gpio-reset-hold-time GPIO_RESET_HOLD_TIME] + [--gpio-reset-release-time GPIO_RESET_RELEASE_TIME] + [--block-orientation ORIENTATION] [--mode MODE] + [--framebuffer FRAMEBUFFER] [--num-segments NUM_SEGMENTS] + [--bgr] [--inverse] [--h-offset H_OFFSET] + [--v-offset V_OFFSET] [--backlight-active VALUE] [--debug] + [--transform TRANSFORM] [--scale SCALE] [--duration DURATION] + [--loop LOOP] [--max-frames MAX_FRAMES] luma.examples arguments - optional arguments: + options: -h, --help show this help message and exit General: @@ -126,11 +126,12 @@ flag to show the options:: Display type, supports real devices or emulators. Allowed values are: ssd1306, ssd1309, ssd1322, ssd1362, ssd1322_nhd, ssd1325, ssd1327, ssd1331, - ssd1351, sh1106, ws0010, winstar_weh, pcd8544, st7735, - ht1621, uc1701x, st7567, ili9341, ili9486, hd44780, - max7219, ws2812, neopixel, neosegment, apa102, - unicornhathd, capture, gifanim, pygame, asciiart, - asciiblock, linux_framebuffer (default: ssd1306) + ssd1351, sh1106, sh1107, ws0010, winstar_weh, pcd8544, + st7735, st7789, ht1621, uc1701x, st7567, ili9341, + ili9486, ili9488, hd44780, max7219, ws2812, neopixel, + neosegment, apa102, unicornhathd, capture, gifanim, + pygame, asciiart, asciiblock, linux_framebuffer + (default: ssd1306) --width WIDTH Width of the device in pixels (default: 128) --height HEIGHT Height of the device in pixels (default: 64) --rotate ROTATION, -r ROTATION @@ -252,13 +253,16 @@ and screen capture functionality: * The `luma.emulator.device.pygame` device uses the `pygame` library to render the displayed image to a pygame display surface. -*After installing* luma.emulator (see Documentation link below) you can invoke the demos with:: +After `installing luma.emulator `_ +you can invoke the demos with:: - $ python3 examples/clock.py --display capture + $ python3 examples/clock.py --display pygame or:: - $ python3 examples/clock.py --display pygame + $ python3 examples/clock.py --display gifanim + + $ python3 examples/starfield.py --display capture Documentation ------------- @@ -274,7 +278,7 @@ License ------- The MIT License (MIT) -Copyright (c) 2017-2022 Richard Hull & Contributors +Copyright (c) 2017-2023 Richard Hull & Contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/examples/colors.py b/examples/colors.py index c83b830..b3512cd 100755 --- a/examples/colors.py +++ b/examples/colors.py @@ -1,6 +1,6 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -# Copyright (c) 2014-2020 Richard Hull and contributors +# Copyright (c) 2014-2023 Richard Hull and contributors # See LICENSE.rst for details. # PYTHON_ARGCOMPLETE_OK @@ -32,7 +32,11 @@ def main(): for color in ["black", "white", "red", "orange", "yellow", "green", "blue", "indigo", "violet"]: with canvas(device, dither=True) as draw: draw.rectangle(device.bounding_box, fill=color) - size = draw.textsize(color) + # measure + left, top, right, bottom = draw.textbbox((0, 0), color) + size = right - left, bottom - top + + # draw left = (device.width - size[0]) // 2 top = (device.height - size[1]) // 2 right = left + size[0] @@ -52,7 +56,11 @@ def main(): rgb = (r << 16) | (g << 8) | b draw.rectangle((i * w, 0, (i + 1) * w, device.height), fill=rgb) - size = draw.textsize("rainbow") + # measure + left, top, right, bottom = draw.textbbox((0, 0), "rainbow") + size = right - left, bottom - top + + # draw left = (device.width - size[0]) // 2 top = (device.height - size[1]) // 2 right = left + size[0] @@ -71,7 +79,11 @@ def main(): b = 0 draw.point((x, y), fill=(r, g, b)) - size = draw.textsize("gradient") + # measure + left, top, right, bottom = draw.textbbox((0, 0), "gradient") + size = right - left, bottom - top + + # draw left = (device.width - size[0]) // 2 top = (device.height - size[1]) // 2 right = left + size[0] diff --git a/examples/demo.py b/examples/demo.py index c077d68..bbf3490 100755 --- a/examples/demo.py +++ b/examples/demo.py @@ -1,6 +1,6 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -# Copyright (c) 2014-18 Richard Hull and contributors +# Copyright (c) 2014-2023 Richard Hull and contributors # See LICENSE.rst for details. # PYTHON_ARGCOMPLETE_OK @@ -18,34 +18,42 @@ def primitives(device, draw): - # Draw some shapes. - # First define some constants to allow easy resizing of shapes. + # Draw some shapes + # First define some constants to allow easy resizing of shapes padding = 2 shape_width = 20 top = padding bottom = device.height - padding - 1 - # Move left to right keeping track of the current x position for drawing shapes. + + # Move left to right keeping track of the current x position for drawing shapes x = padding - # Draw an ellipse. + + # Draw an ellipse draw.ellipse((x, top, x + shape_width, bottom), outline="red", fill="black") x += shape_width + padding - # Draw a rectangle. + + # Draw a rectangle draw.rectangle((x, top, x + shape_width, bottom), outline="blue", fill="black") x += shape_width + padding - # Draw a triangle. + + # Draw a triangle draw.polygon([(x, bottom), (x + shape_width / 2, top), (x + shape_width, bottom)], outline="green", fill="black") x += shape_width + padding - # Draw an X. + + # Draw an X draw.line((x, bottom, x + shape_width, top), fill="yellow") draw.line((x, top, x + shape_width, bottom), fill="yellow") x += shape_width + padding - # Write two lines of text. - size = draw.textsize('World!') - x = device.width - padding - size[0] - draw.rectangle((x, top + 4, x + size[0], top + size[1]), fill="black") - draw.rectangle((x, top + 16, x + size[0], top + 16 + size[1]), fill="black") - draw.text((device.width - padding - size[0], top + 4), 'Hello', fill="cyan") - draw.text((device.width - padding - size[0], top + 16), 'World!', fill="purple") + + # Write two lines of text + left, t, right, bottom = draw.textbbox((0, 0), 'World!') + w, h = right - left, bottom - t + x = device.width - padding - w + draw.rectangle((x, top + 4, x + w, top + h), fill="black") + draw.rectangle((x, top + 16, x + w, top + 16 + h), fill="black") + draw.text((device.width - padding - w, top + 4), 'Hello', fill="cyan") + draw.text((device.width - padding - w, top + 16), 'World!', fill="purple") + # Draw a rectangle of the same size of screen draw.rectangle(device.bounding_box, outline="white") diff --git a/examples/font_awesome.py b/examples/font_awesome.py index d0b8a97..95153ec 100755 --- a/examples/font_awesome.py +++ b/examples/font_awesome.py @@ -1,6 +1,6 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -# Copyright (c) 2014-2020 Richard Hull and contributors +# Copyright (c) 2014-2023 Richard Hull and contributors # See LICENSE.rst for details. # PYTHON_ARGCOMPLETE_OK @@ -149,7 +149,8 @@ def main(num_iterations=sys.maxsize): break with canvas(device) as draw: - w, h = draw.textsize(text=code, font=font) + left, top, right, bottom = draw.textbbox((0, 0), code, font) + w, h = right - left, bottom - top left = (device.width - w) / 2 top = (device.height - h) / 2 draw.text((left, top), text=code, font=font, fill="white") diff --git a/examples/game_of_life.py b/examples/game_of_life.py index 950de5f..3ffa715 100755 --- a/examples/game_of_life.py +++ b/examples/game_of_life.py @@ -1,6 +1,6 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -# Copyright (c) 2014-18 Richard Hull and contributors +# Copyright (c) 2014-2023 Richard Hull and contributors # See LICENSE.rst for details. # PYTHON_ARGCOMPLETE_OK @@ -40,6 +40,7 @@ def iterate(board): def main(): + text = "Game of Life" scale = 3 cols = device.width // scale rows = device.height // scale @@ -61,11 +62,13 @@ def main(): draw.rectangle((left, top, right, bottom), fill="white", outline="black") if i == 0: - w, h = draw.textsize("Game of Life") + left, top, right, bottom = draw.textbbox((0, 0), text) + w, h = right - left, bottom - top + left = (device.width - w) // 2 top = (device.height - h) // 2 draw.rectangle((left - 1, top, left + w + 1, top + h), fill="black", outline="white") - draw.text((left + 1, top), text="Game of Life", fill="white") + draw.text((left + 1, top), text=text, fill="white") if i == 0: time.sleep(3) diff --git a/examples/greyscale.py b/examples/greyscale.py index e844796..942bb9d 100755 --- a/examples/greyscale.py +++ b/examples/greyscale.py @@ -1,6 +1,6 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -# Copyright (c) 2014-2020 Richard Hull and contributors +# Copyright (c) 2014-2023 Richard Hull and contributors # See LICENSE.rst for details. # PYTHON_ARGCOMPLETE_OK @@ -37,7 +37,9 @@ def main(): rgb = (color << 16) | (color << 8) | color draw.rectangle((i * w, 0, (i + 1) * w, device.height), fill=rgb) - size = draw.textsize("greyscale") + left, top, right, bottom = draw.textbbox((0, 0), 'greyscale') + size = right - left, bottom - top + left = (device.width - size[0]) // 2 top = (device.height - size[1]) // 2 right = left + size[0] diff --git a/examples/hotspot/common.py b/examples/hotspot/common.py index 9a0bf04..d823c7a 100644 --- a/examples/hotspot/common.py +++ b/examples/hotspot/common.py @@ -1,6 +1,6 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -# Copyright (c) 2014-2022 Richard Hull and contributors +# Copyright (c) 2014-2023 Richard Hull and contributors # See LICENSE.rst for details. from pathlib import Path @@ -34,10 +34,10 @@ def bytes2human(n, fmt="{0:0.2f}"): def right_text(draw, y, width, margin, text): - x = width - margin - draw.textsize(text, font=tiny_font)[0] + x = width - margin - draw.textlength(text, font=tiny_font) draw.text((x, y), text=text, font=tiny_font, fill="white") def title_text(draw, y, width, text): - x = (width - draw.textsize(text)[0]) / 2 + x = (width - draw.textlength(text)) / 2 draw.text((x, y), text=text, fill="yellow") diff --git a/examples/image_composition.py b/examples/image_composition.py index 254fc61..a13c5ce 100755 --- a/examples/image_composition.py +++ b/examples/image_composition.py @@ -1,6 +1,6 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -# Copyright (c) 2014-2020 Richard Hull and contributors +# Copyright (c) 2014-2023 Richard Hull and contributors # See LICENSE.rst for details. # PYTHON_ARGCOMPLETE_OK @@ -26,7 +26,9 @@ class TextImage(): def __init__(self, device, text, font): with canvas(device) as draw: - w, h = draw.textsize(text, font) + left, top, right, bottom = draw.textbbox((0, 0), text, font) + w, h = right - left, bottom - top + self.image = Image.new(device.mode, (w, h)) draw = ImageDraw.Draw(self.image) draw.text((0, 0), text, font=font, fill="white") diff --git a/examples/savepoint.py b/examples/savepoint.py index 8a3a8a2..72f2f56 100755 --- a/examples/savepoint.py +++ b/examples/savepoint.py @@ -1,6 +1,6 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -# Copyright (c) 2014-2022 Richard Hull and contributors +# Copyright (c) 2014-2023 Richard Hull and contributors # See LICENSE.rst for details. # PYTHON_ARGCOMPLETE_OK @@ -16,12 +16,16 @@ def render_box(draw, idx, color): message = f"Nesting level: {idx}" - width, height = draw.textsize(message) + # measure + left, top, right, bottom = draw.textbbox((0, 0), message) + width, height = right - left, bottom - top + left = idx * 4 right = left + width + 2 top = idx * 4 bottom = top + height + 2 + # draw draw.rectangle((left, top, right, bottom), outline="white", fill="black") draw.text((left + 2, top + 1), text=message, fill=color) diff --git a/examples/tweet_scroll.py b/examples/tweet_scroll.py index 64b3544..477ab17 100755 --- a/examples/tweet_scroll.py +++ b/examples/tweet_scroll.py @@ -1,6 +1,6 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -# Copyright (c) 2014-2022 Richard Hull and contributors +# Copyright (c) 2014-2023 Richard Hull and contributors # See LICENSE.rst for details. # PYTHON_ARGCOMPLETE_OK @@ -67,7 +67,8 @@ def scroll_message(status, font=None, speed=1): # First measure the text size with canvas(device) as draw: - w, h = draw.textsize(full_text, font) + left, top, right, bottom = draw.textbbox((0, 0), full_text, font) + w, h = right - left, bottom - top virtual = viewport(device, width=max(device.width, w + x + x), height=max(h, device.height)) with canvas(virtual) as draw: diff --git a/examples/video.py b/examples/video.py index d46623f..295f371 100755 --- a/examples/video.py +++ b/examples/video.py @@ -1,6 +1,6 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -# Copyright (c) 2017-2022 Richard Hull and contributors +# Copyright (c) 2017-2023 Richard Hull and contributors # See LICENSE.rst for details. # PYTHON_ARGCOMPLETE_OK @@ -42,7 +42,7 @@ def main(): if img.width != device.width or img.height != device.height: # resize video to fit device size = device.width, device.height - img = img.resize(size, PIL.Image.ANTIALIAS) + img = img.resize(size, PIL.Image.LANCZOS) device.display(img.convert(device.mode)) diff --git a/examples/welcome.py b/examples/welcome.py index 10e7574..f8adab3 100755 --- a/examples/welcome.py +++ b/examples/welcome.py @@ -1,6 +1,6 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -# Copyright (c) 2014-2020 Richard Hull and contributors +# Copyright (c) 2014-2023 Richard Hull and contributors # See LICENSE.rst for details. # PYTHON_ARGCOMPLETE_OK @@ -163,14 +163,18 @@ def make_snapshot(width, height, text, fonts, color="white"): def render(draw, width, height): t = text + # measure text for font in fonts: - size = draw.multiline_textsize(t, font) + left, top, right, bottom = draw.multiline_textbbox((0, 0), t, font) + size = right - left, bottom - top if size[0] > width: t = text.replace(" ", "\n") - size = draw.multiline_textsize(t, font) + left, top, right, bottom = draw.multiline_textbbox((0, 0), t, font) + size = right - left, bottom - top else: break + # draw text left = (width - size[0]) // 2 top = (height - size[1]) // 2 draw.multiline_text((left, top), text=t, font=font, fill=color, diff --git a/tox.ini b/tox.ini index de3b22f..2df1b54 100644 --- a/tox.ini +++ b/tox.ini @@ -1,4 +1,4 @@ -# Copyright (c) 2017-2022 Richard Hull and contributors +# Copyright (c) 2017-2023 Richard Hull and contributors # See LICENSE.rst for details. [tox]