From 9035351ab85fd6ae046b429a40d335ee71bca235 Mon Sep 17 00:00:00 2001 From: Corran Webster Date: Fri, 18 Oct 2024 13:18:13 +0100 Subject: [PATCH] Add testing infrastructure and test for shapes example (#14) Add a basic byte-checked test for examples. --- .github/workflows/run_tests.yaml | 23 +++ ci/rgb565_to_png.py | 38 ++++ ci/test.py | 53 ++++++ examples/shapes_examples.py | 301 +++++++++++++++++++++++++++++++ tests/tempe/shapes.rgb565 | Bin 0 -> 153600 bytes tests/tempe/test_examples.py | 60 ++++++ 6 files changed, 475 insertions(+) create mode 100644 .github/workflows/run_tests.yaml create mode 100644 ci/rgb565_to_png.py create mode 100644 ci/test.py create mode 100644 examples/shapes_examples.py create mode 100644 tests/tempe/shapes.rgb565 create mode 100644 tests/tempe/test_examples.py diff --git a/.github/workflows/run_tests.yaml b/.github/workflows/run_tests.yaml new file mode 100644 index 0000000..18b5e44 --- /dev/null +++ b/.github/workflows/run_tests.yaml @@ -0,0 +1,23 @@ +name: "Run Tests" + +on: +- workflow_dispatch +- pull_request + +jobs: + run-tests: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Set up Python 3.11 + uses: actions/setup-python@v5 + with: + python-version: '3.11' + - name: Install dependencies and local packages + run: python -m pip install mpremote click + - name: Install MicroPython + uses: BrianPugh/install-micropython@v2 + - name: Install Micropython dependencies + run: micropython -m mip install unittest + - name: Run tests + run: python -m ci.test diff --git a/ci/rgb565_to_png.py b/ci/rgb565_to_png.py new file mode 100644 index 0000000..a3fdcc9 --- /dev/null +++ b/ci/rgb565_to_png.py @@ -0,0 +1,38 @@ +import array +from pathlib import Path + +from PIL import Image + +def read_rgb565(path): + with open(path, 'rb') as fp: + buffer = fp.read() + + rgb565 = array.array('H', buffer) + rgb565.byteswap() + return rgb565 + +def rgb565_to_rgb24(rgb565): + rgb24 = array.array('B', bytearray(3*len(rgb565))) + for i, pixel in enumerate(rgb565): + r = (pixel & 0b1111100000000000) >> 11 + g = (pixel & 0b0000011111100000) >> 5 + b = (pixel & 0b0000000000011111) + rgb24[3 * i] = int((r / 31) * 255) + rgb24[3 * i + 1] = int(round((g / 63) * 255)) + rgb24[3 * i + 2] = int(round((b / 31) * 255)) + return rgb24 + + +def rgb565_to_png(size, in_path, out_path=None): + if out_path is None: + out_path = Path(in_path.stem + '.png') + + rgb565 = read_rgb565(in_path) + rgb24 = rgb565_to_rgb24(rgb565) + image = Image.frombuffer("RGB", size, rgb24) + image.save(out_path) + +if __name__ == "__main__": + import sys + + rgb565_to_png((320, 240), Path(sys.argv[1])) diff --git a/ci/test.py b/ci/test.py new file mode 100644 index 0000000..3d3f586 --- /dev/null +++ b/ci/test.py @@ -0,0 +1,53 @@ +# SPDX-FileCopyrightText: 2024-present Unital Software +# +# SPDX-License-Identifier: MIT + +from pathlib import Path +import os +import subprocess +import sys + +import click + + + +@click.command() +def test(): + """Run unit tests in micropython""" + print("Running Tests") + failures = [] + test_dir = Path("tests/tempe") + os.environ["MICROPYPATH"] = "src:" + os.environ.get('MICROPYPATH', ":.frozen:~/.micropython/lib:/usr/lib/micropython") + for path in sorted(test_dir.glob("*.py")): + print(path.name, "... ", end="", flush=True) + result = run_test(path) + if result: + failures.append(result) + print('FAILED') + else: + print('OK') + print() + + for path, stdout, stderr in failures: + print("FAILURE: ", path.name) + print("STDOUT ", "="*70) + print(stdout.decode('utf-8')) + print() + print("STDERR ", "="*70) + print(stderr.decode('utf-8')) + print() + + if failures: + sys.exit(1) + else: + print("PASSED") + + +def run_test(path): + try: + result = subprocess.run(["micropython", path], capture_output=True, check=True) + except subprocess.CalledProcessError as exc: + return (path, exc.stdout, exc.stderr) + +if __name__ == "__main__": + test() \ No newline at end of file diff --git a/examples/shapes_examples.py b/examples/shapes_examples.py new file mode 100644 index 0000000..af82799 --- /dev/null +++ b/examples/shapes_examples.py @@ -0,0 +1,301 @@ +import asyncio +from array import array +import framebuf +import random +import math + +from tempe.bitmaps import Bitmaps, ColoredBitmaps +from tempe.colormaps.magma import magma +from tempe.colormaps.viridis import viridis +from tempe.data_view import Repeat, Range, Interpolated +from tempe.geometry import RowGeometry, ColumnGeometry +from tempe.surface import Surface +from tempe.text import Text +from tempe.markers import Marker, Markers +from tempe.shapes import Rectangles, Lines, HLines, VLines, Polygons, Circles, Ellipses, BLIT_KEY_RGB565 +from tempe.display import FileDisplay +from tempe.font import TempeFont +from tempe.fonts import roboto16bold + +random.seed(0) + +surface = Surface() + +# a buffer one quarter the size of the screen +working_buffer = array('H', bytearray(2*320*61)) + +# fill the background with white pixels +background = Rectangles([(0, 0, 320, 240)], [0xffff]) +surface.add_shape('BACKGROUND', background) + +# draw some black text in the main drawing layer +font = TempeFont(roboto16bold) +labels = Text( + [ + (4, 4), (4, 44), (4, 84), (4, 124), (4, 184), + (164, 4), (164, 44), (164, 124), (164, 184), + ], + Repeat(0x0000), + [ + "Lines", "HLines", "VLines", "Polygons", "Circles", + "Markers", "Bitmaps", "Rectangles", "Ellipses" + ], + font=font, +) +surface.add_shape('OVERLAY', labels) + +# draw some lines +lines = Lines( + ColumnGeometry([Range(4, 44, 4), Repeat(20), Range(8, 88, 8), Repeat(40)]), + Interpolated(magma, 10), +) +surface.add_shape('DRAWING', lines) + +# draw some hlines +hlines = HLines( + ColumnGeometry([Range(4, 44, 4), Range(60, 80, 2), Range(8, 88, 8)]), + Interpolated(magma, 10), +) +surface.add_shape('DRAWING', hlines) + +# draw some vlines +vlines = VLines( + ColumnGeometry([ + Range(8, 84, 4), + Repeat(100), + [random.randint(10, 20) for _ in range(20)], + ]), + Interpolated(magma, 20), +) +surface.add_shape('DRAWING', vlines) + +# draw some polygons +stars = [[ + (16 + x + int((16 - 8 * (a % 2)) * math.sin(math.pi*a/5)), 160 - int((16 - 8 * (a % 2)) * math.cos(math.pi*a/5))) + for a in range(10) +] +for x in range(0, 128, 32) +] +polys = Polygons( + RowGeometry.from_lists([ + [x for p in star for x in p] + for star in stars[:3] + ]), + Interpolated(viridis, 3), +) +surface.add_shape('DRAWING', polys) +outlines = Polygons( + RowGeometry.from_lists([ + [x for p in star for x in p] + for star in stars + ]), + Repeat(0x0000), + fill=False, +) +surface.add_shape('DRAWING', outlines) + +# draw some circles +circles = Circles( + ColumnGeometry([ + Range(16, 128, 32), + Repeat(220), + Range(8, 16, 2), + ]), + Interpolated(viridis, 3), +) +surface.add_shape('DRAWING', circles) +circle_outlines = Circles( + ColumnGeometry([ + Range(16, 128, 32), + Repeat(220), + Range(8, 16, 2), + ]), + Repeat(0x0000), + fill=False, +) +surface.add_shape('DRAWING', circle_outlines) + +# draw some markers +star = array('h', [ + x for p in [ + ( + int((8 - 4 * (a % 2)) * math.sin(math.pi*a/5)), + -int((8 - 4 * (a % 2)) * math.cos(math.pi*a/5)) + ) + for a in range(10)] + for x in p]) +smiley = framebuf.FrameBuffer(array('B', [ + 0b00111100, + 0b01111110, + 0b11011011, + 0b11011011, + 0b11111111, + 0b11011011, + 0b01100110, + 0b00111100, +]), 8, 8, framebuf.MONO_HLSB) +markers = Markers( + ColumnGeometry([ + Range(160, 320, 16), + Repeat(30), + Range(5, 16, 1), + ]), + Interpolated(viridis, 10), + [ + Marker.PIXEL, + Marker.CIRCLE, + Marker.SQUARE, + Marker.HLINE, + Marker.VLINE, + Marker.PLUS, + Marker.CROSS, + star, + smiley, + "#", + ] +) +surface.add_shape('DRAWING', markers) + +# draw some buffers +t_buf = framebuf.FrameBuffer(bytearray(32*32*2), 32, 32, framebuf.RGB565) +t_buf.fill(BLIT_KEY_RGB565) +for x in range(2, 30): + for y in range(2, 8): + t_buf.pixel(x, y, viridis[32 + 6 * x + 3 * y]) +for x in range(13, 19): + for y in range(8, 30): + t_buf.pixel(x, y, viridis[32 + 6 * x + 3 * y]) + +bitmaps = Bitmaps( + [(164, 60, 32, 32)], + [t_buf], + key=BLIT_KEY_RGB565, +) +surface.add_shape('DRAWING', bitmaps) + +grey_buf = framebuf.FrameBuffer(bytearray(32*32), 32, 32, framebuf.GS8) +grey_buf.fill(0) +for x in range(1, 14): + for y in range(1, 4): + grey_buf.pixel(x, y, 32 + 12 * x + 6 * y) +for x in range(6, 9): + for y in range(4, 15): + grey_buf.pixel(x, y, 32 + 12 * x + 6 * y) +greyscale = Bitmaps( + [(196, 60, 16, 16), (196, 76, 16, 16)], + [grey_buf, grey_buf], + key=magma[0], + palette=magma, +) +surface.add_shape('DRAWING', greyscale) + +smileys = ColoredBitmaps( + ColumnGeometry([ + [random.randint(212, 312) for _ in range(20)], + [random.randint(60, 84) for _ in range(20)], + Repeat(8), + Repeat(8), + ]), + Interpolated(viridis, 20), + Repeat(smiley), +) +surface.add_shape('DRAWING', smileys) + +colormap = framebuf.FrameBuffer(bytearray(128*8), 128, 8, framebuf.GS8) +for x in range(128): + colormap.vline(x, 0, 8, 2*x) +v = Bitmaps( + [(164, 96, 128, 8)], + [colormap], + palette=viridis, +) +surface.add_shape('DRAWING', v) +m = Bitmaps( + [(164, 106, 128, 8)], + [colormap], + palette=magma, +) +surface.add_shape('DRAWING', m) + +# draw some rectangles +rectangles = Rectangles( + ColumnGeometry([ + Range(164, 260, 32), + Repeat(144), + Range(16, 32, 4), + Range(32, 16, -4), + ]), + Interpolated(viridis, 3), +) +surface.add_shape('DRAWING', rectangles) +rectangles_outlines = Rectangles( + ColumnGeometry([ + Range(164, 292, 32), + Repeat(144), + Range(16, 32, 4), + Range(32, 16, -4), + ]), + Repeat(0x0000), + fill=False, +) +surface.add_shape('DRAWING', rectangles_outlines) + +# draw some ellipses +ellipses = Ellipses( + ColumnGeometry([ + Range(172, 300, 32), + Repeat(220), + Range(8, 16, 2), + Range(16, 8, -2), + ]), + Interpolated(viridis, 3), +) +surface.add_shape('DRAWING', ellipses) +ellipses_outlines = Ellipses( + ColumnGeometry([ + Range(172, 300, 32), + Repeat(220), + Range(8, 16, 2), + Range(16, 8, -2), + ]), + Repeat(0x0000), + fill=False, +) +surface.add_shape('DRAWING', ellipses_outlines) + + +def main(surface, working_buffer): + + + async def init_display(): + from devices.st7789 import ST7789 + from machine import Pin, SPI + + spi = SPI(0, baudrate=62_500_000, phase=1, polarity=1, sck=Pin(18, Pin.OUT), mosi=Pin(19, Pin.OUT), miso=Pin(16, Pin.OUT)) + backlight = Pin(20, Pin.OUT) + display = ST7789(spi, cs_pin=Pin(17, Pin.OUT, value=1), dc_pin=Pin(16, Pin.OUT)) + backlight(1) + await display.init() + return display + + # set up the display object + display = asyncio.run(init_display()) + + # refresh the display + display.clear() + surface.refresh(display, working_buffer) + +if __name__ == '__main__': + +# # if we have an actual screen, use it +# main(surface, working_buffer) + +# else: + + # set up the display object + display = FileDisplay('shapes.rgb565', (320, 240)) + + # refresh the display + with display: + display.clear() + surface.refresh(display, working_buffer) diff --git a/tests/tempe/shapes.rgb565 b/tests/tempe/shapes.rgb565 new file mode 100644 index 0000000000000000000000000000000000000000..87e349587b54dd9da3ebdb4edd4045874bd423c4 GIT binary patch literal 153600 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 KFa+imf&T|00xzln literal 0 HcmV?d00001 diff --git a/tests/tempe/test_examples.py b/tests/tempe/test_examples.py new file mode 100644 index 0000000..548bd82 --- /dev/null +++ b/tests/tempe/test_examples.py @@ -0,0 +1,60 @@ +# SPDX-FileCopyrightText: 2024-present Unital Software +# +# SPDX-License-Identifier: MIT + +import array +import unittest + +from tempe.display import FileDisplay + +working_buffer = array.array('H', bytearray(320*61*2)) + +class TestExamples(unittest.TestCase): + + def test_examples(self): + """Bytewise comparison that output of examples is what is expected.""" + + examples = [ + ("examples/shapes_examples.py", "tests/tempe/shapes.rgb565"), + ] + for file, result in examples: + print(file, result) + self.subTest(example=file) + + code = open(file, 'r').read() + locals = {"__name__": "__test__"} + exec(code, locals) + + output = self.display_output(locals["surface"]) + + self.assert_files_equal(output, result) + + def display_output(self, surface, name='example.rgb565'): + display = FileDisplay(name, (320, 240)) + + with display: + display.clear() + surface.refresh(display, working_buffer) + + return name + + def assert_files_equal(self, file1, file2): + with open(file1, 'rb') as f1: + with open(file2, 'rb') as f2: + i = 0 + while True: + b1 = f1.read(1) + b2 = f2.read(1) + if b1 != b2: + print(f"Byte {i}") + self.assertEqual(b1, b2) + if not b1: + break + i += 1 + + +if __name__ == "__main__": + result = unittest.main() + if not result.wasSuccessful(): + import sys + sys.exit(1) \ No newline at end of file