From bf6f32d392573ed33ecc43e8f62a289f3ceca758 Mon Sep 17 00:00:00 2001 From: foamyguy Date: Fri, 17 Jan 2025 10:20:08 -0600 Subject: [PATCH 1/9] load png test case --- tests/test_png.png | Bin 0 -> 571 bytes tests/test_png.png.license | 2 ++ tests/test_png_load.py | 24 ++++++++++++++++++++++++ 3 files changed, 26 insertions(+) create mode 100644 tests/test_png.png create mode 100644 tests/test_png.png.license create mode 100644 tests/test_png_load.py diff --git a/tests/test_png.png b/tests/test_png.png new file mode 100644 index 0000000000000000000000000000000000000000..314173af2fe07661454dcf2fc3fda8366117de38 GIT binary patch literal 571 zcmV-B0>u4^P)EX>4Tx04R}tkv&MmP!xqvTcsi`B6d)52w0u$q9Ts93Pq?8YK2xEOkVm2O&XFE z7e~Rh;NZ_<)xpJCR|i)?5c~mg7n~Gbq{RD@LW>wLJl@B7_Z;545AZiCOf|dWfT~$W zI++l%xm6+f3Lp9r#t=p&X6kdIn1W}0-BUN!U5saW_x)L6C2umoClJS(Zdk+{#50?g z&Uv3W!b*}td`>)J&;^Mfxh}i>#<}RQpJzslOnRO;LM#+JSngm}GF0Me;;5o(l<&{F ztZ?4qtX68Qbx;1nP+nVE<~pqrB(aDkh!7y7hB7L!5Tjiq#YCFU6CVCy$1jpgCRZ7Z z91EyIh2;3b|KRs*&BD~An-od_-7mKNF$M&8fo9#dzmILZc>)BUfh(=;uQq_$Ptxmc zEqVm>Z37qAZB5<-E_Z;zCtWfmM+(sL7Ye}p8GTa@7`O#`*W9_a&T;wxWN22Y8{ps& z7%x)xy2rbFI_LIpPiuZZgLQJP;t&>p00009P)t-s00960000300A{uIP5=M^32;bR za{vG?BLDy{BLR4&KXw2B00(qQO+^Rk0TB=cG;ySBVE_OC8FWQhbVF}#ZDnqB07G(R zVRU6=Aa`kWXdp*PO;A^X4i^9b01!z;K~xCWV^Cr6WSGRD0ssU{0VHZfD^vgg002ov JPDHLkV1lOI;%5K= literal 0 HcmV?d00001 diff --git a/tests/test_png.png.license b/tests/test_png.png.license new file mode 100644 index 0000000..beff0b4 --- /dev/null +++ b/tests/test_png.png.license @@ -0,0 +1,2 @@ +# SPDX-FileCopyrightText: 2025 Tim Cocks for Adafruit Industries +# SPDX-License-Identifier: MIT diff --git a/tests/test_png_load.py b/tests/test_png_load.py new file mode 100644 index 0000000..78e0a1c --- /dev/null +++ b/tests/test_png_load.py @@ -0,0 +1,24 @@ +# SPDX-FileCopyrightText: 2025 Tim Cocks for Adafruit Industries +# SPDX-License-Identifier: MIT + +from unittest import TestCase + +from adafruit_imageload import load + + +class TestPngLoad(TestCase): + def test_expected_pixels(self): + img, palette = load("tests/test_png.png") + self.assertEqual(len(palette), 3) + self.assertEqual(img.width, 4) + self.assertEqual(img.height, 4) + + self.assertEqual(img[0, 0], 0) + self.assertEqual(img[1, 0], 2) + self.assertEqual(img[2, 0], 1) + self.assertEqual(img[3, 0], 0) + + self.assertEqual(img[0, 3], 0) + self.assertEqual(img[1, 3], 2) + self.assertEqual(img[2, 3], 1) + self.assertEqual(img[3, 3], 0) From 92f5030b799f6020fedee4405064884b3fd1051d Mon Sep 17 00:00:00 2001 From: foamyguy Date: Mon, 17 Feb 2025 10:48:18 -0600 Subject: [PATCH 2/9] dont try to set pixels outside of bounds --- adafruit_imageload/png.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/adafruit_imageload/png.py b/adafruit_imageload/png.py index 010dff3..0c5892f 100644 --- a/adafruit_imageload/png.py +++ b/adafruit_imageload/png.py @@ -113,7 +113,10 @@ def load( # noqa: PLR0912, PLR0915, Too many branches, Too many statements for x in range(0, width, pixels_per_byte): byte = data_bytes[src_b] for pixel in range(pixels_per_byte): - bmp[x + pixel, y] = (byte >> ((pixels_per_byte - pixel - 1) * depth)) & pixmask + if x + pixel < width: + bmp[x + pixel, y] = ( + byte >> ((pixels_per_byte - pixel - 1) * depth) + ) & pixmask src_b += 1 src += scanline + 1 src_b = src From 4223574348fbe7abb6904982a435657f6760f8e4 Mon Sep 17 00:00:00 2001 From: Neradoc Date: Fri, 21 Feb 2025 16:05:17 +0100 Subject: [PATCH 3/9] implement line filters for indexed PNG --- adafruit_imageload/png.py | 44 +++++++++++++++++++++++++++++++++++---- 1 file changed, 40 insertions(+), 4 deletions(-) mode change 100644 => 100755 adafruit_imageload/png.py diff --git a/adafruit_imageload/png.py b/adafruit_imageload/png.py old mode 100644 new mode 100755 index 0c5892f..b00ee9d --- a/adafruit_imageload/png.py +++ b/adafruit_imageload/png.py @@ -28,7 +28,7 @@ import struct import zlib -__version__ = "0.0.0-auto.0" +__version__ = "0.0.0+auto.0" __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_ImageLoad.git" @@ -106,12 +106,48 @@ def load( # noqa: PLR0912, PLR0915, Too many branches, Too many statements if mode == 3: # indexed bmp = bitmap(width, height, 1 << depth) pixels_per_byte = 8 // depth - src = 1 - src_b = 1 + src = 0 pixmask = (1 << depth) - 1 + line = bytearray(scanline) + prev = bytearray(scanline) for y in range(height): + filter_ = data_bytes[src] + src_b = src + 1 for x in range(0, width, pixels_per_byte): + # relative position on the line + pos = x // pixels_per_byte byte = data_bytes[src_b] + if filter_ == 0: + pass + elif filter_ == 1: # sub + prev_b = line[pos - unit] if pos >= unit else 0 + byte = (byte + prev_b) & 0xFF + elif filter_ == 2: # up + byte = (byte + prev[pos]) & 0xFF + elif filter_ == 3: # average + prev_b = line[pos - unit] if pos >= unit else 0 + byte = (byte + (prev_b + prev[pos]) // 2) & 0xFF + elif filter_ == 4: # paeth + a = line[pos - unit] if pos >= unit else 0 + if y > 0: + b = prev[pos] + c = prev[pos - unit] if pos >= unit else 0 + else: + b = c = 0 + p = a + b - c + pa = abs(p - a) + pb = abs(p - b) + pc = abs(p - c) + if pa <= pb and pa <= pc: + p = a + elif pb <= pc: + p = b + else: + p = c + byte = (data_bytes[src] + p) & 0xFF + else: + raise ValueError("Wrong filter.") + line[pos] = byte for pixel in range(pixels_per_byte): if x + pixel < width: bmp[x + pixel, y] = ( @@ -119,7 +155,7 @@ def load( # noqa: PLR0912, PLR0915, Too many branches, Too many statements ) & pixmask src_b += 1 src += scanline + 1 - src_b = src + prev, line = line, prev return bmp, pal # RGB, RGBA or Grayscale import displayio From 790497ee21c1025499850a3507fa2c844689817f Mon Sep 17 00:00:00 2001 From: Neradoc Date: Fri, 21 Feb 2025 16:25:07 +0100 Subject: [PATCH 4/9] fix paeth --- adafruit_imageload/png.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adafruit_imageload/png.py b/adafruit_imageload/png.py index b00ee9d..215e313 100755 --- a/adafruit_imageload/png.py +++ b/adafruit_imageload/png.py @@ -144,7 +144,7 @@ def load( # noqa: PLR0912, PLR0915, Too many branches, Too many statements p = b else: p = c - byte = (data_bytes[src] + p) & 0xFF + byte = (byte + p) & 0xFF else: raise ValueError("Wrong filter.") line[pos] = byte From 961c433f85f4575c7bc109aedf9c13915bc67a50 Mon Sep 17 00:00:00 2001 From: Justin Myers Date: Thu, 27 Feb 2025 15:41:14 -0800 Subject: [PATCH 5/9] Remove secrets usage --- examples/imageload_from_web.py | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/examples/imageload_from_web.py b/examples/imageload_from_web.py index 9a873a8..79c78dd 100644 --- a/examples/imageload_from_web.py +++ b/examples/imageload_from_web.py @@ -5,34 +5,32 @@ adafruit_requests using BytesIO """ -import ssl from io import BytesIO +from os import getenv +import adafruit_connection_manager import adafruit_requests as requests import board import displayio -import socketpool import wifi import adafruit_imageload -# Get wifi details and more from a secrets.py file -try: - from secrets import secrets -except ImportError: - print("WiFi secrets are kept in secrets.py, please add them there!") - raise +# Get WiFi details, ensure these are setup in settings.toml +ssid = getenv("CIRCUITPY_WIFI_SSID") +password = getenv("CIRCUITPY_WIFI_PASSWORD") -wifi.radio.connect(secrets["ssid"], secrets["password"]) +wifi.radio.connect(ssid, password) print("My IP address is", wifi.radio.ipv4_address) -socket = socketpool.SocketPool(wifi.radio) -https = requests.Session(socket, ssl.create_default_context()) +pool = adafruit_connection_manager.get_radio_socketpool(wifi.radio) +ssl_context = adafruit_connection_manager.get_radio_ssl_context(wifi.radio) +https = requests.Session(pool, ssl_context) url = "https://raw.githubusercontent.com/adafruit/Adafruit_CircuitPython_ImageLoad/main/examples/images/4bit.bmp" -print("Fetching text from %s" % url) +print(f"Fetching text from {url}") response = https.get(url) print("GET complete") From 23f7866f2e69fbf9af54a0b9094ee3db53611b38 Mon Sep 17 00:00:00 2001 From: Karolis Stasaitis Date: Wed, 9 Apr 2025 22:22:41 +0200 Subject: [PATCH 6/9] fix padded pbm binary loading --- adafruit_imageload/pnm/pbm_binary.py | 6 +++-- examples/images/netpbm_p4_mono_width.pbm | 3 +++ .../images/netpbm_p4_mono_width.pbm.license | 2 ++ tests/test_pbm_load.py | 23 ++++++++++++++++++- 4 files changed, 31 insertions(+), 3 deletions(-) create mode 100644 examples/images/netpbm_p4_mono_width.pbm create mode 100644 examples/images/netpbm_p4_mono_width.pbm.license diff --git a/adafruit_imageload/pnm/pbm_binary.py b/adafruit_imageload/pnm/pbm_binary.py index cecdba5..53d92df 100644 --- a/adafruit_imageload/pnm/pbm_binary.py +++ b/adafruit_imageload/pnm/pbm_binary.py @@ -38,6 +38,7 @@ def load( """ Load a P4 'PBM' binary image into the Bitmap """ + padded_width = (width + 7) // 8 * 8 x = 0 y = 0 while True: @@ -45,9 +46,10 @@ def load( if not next_byte: break # out of bits for bit in iterbits(next_byte): - bitmap[x, y] = bit + if x < width: + bitmap[x, y] = bit x += 1 - if x > width - 1: + if x > padded_width - 1: y += 1 x = 0 if y > height - 1: diff --git a/examples/images/netpbm_p4_mono_width.pbm b/examples/images/netpbm_p4_mono_width.pbm new file mode 100644 index 0000000..ef6a9e6 --- /dev/null +++ b/examples/images/netpbm_p4_mono_width.pbm @@ -0,0 +1,3 @@ +P4 +14 20 +ÏÌ·´·´·´·´·´·´·´·´{x|øøg˜Cøø{x¼ôßìà \ No newline at end of file diff --git a/examples/images/netpbm_p4_mono_width.pbm.license b/examples/images/netpbm_p4_mono_width.pbm.license new file mode 100644 index 0000000..5e64ae4 --- /dev/null +++ b/examples/images/netpbm_p4_mono_width.pbm.license @@ -0,0 +1,2 @@ +# SPDX-FileCopyrightText: 2025 Karolis Stasaitis +# SPDX-License-Identifier: MIT diff --git a/tests/test_pbm_load.py b/tests/test_pbm_load.py index f1ba849..e999657 100644 --- a/tests/test_pbm_load.py +++ b/tests/test_pbm_load.py @@ -72,7 +72,7 @@ def test_load_works_p1_ascii(self): palette.validate() def test_load_works_p4_in_mem(self): - file = BytesIO(b"P4\n4 2\n\x55") + file = BytesIO(b"P4\n4 2\n\x5f\x5f") bitmap, palette = pnm.load( file, b"P4", bitmap=Bitmap_C_Interface, palette=Palette_C_Interface ) @@ -103,6 +103,27 @@ def test_load_works_p4_binary(self): self.assertEqual(15, bitmap.height) bitmap.validate() + def test_load_works_p4_binary_padded_width(self): + test_file = os.path.join( + os.path.dirname(__file__), + "..", + "examples", + "images", + "netpbm_p4_mono_width.pbm", + ) + with open(test_file, "rb") as file: + bitmap, palette = pnm.load( + file, b"P4", bitmap=Bitmap_C_Interface, palette=Palette_C_Interface + ) + self.assertEqual(1, palette.num_colors) + palette.validate() + self.assertEqual(b"\xff\xff\xff", palette[0]) + self.assertTrue(isinstance(bitmap, Bitmap_C_Interface)) + self.assertEqual(1, bitmap.colors) + self.assertEqual(14, bitmap.width) + self.assertEqual(20, bitmap.height) + bitmap.validate() + def test_load_works_p4_binary_high_res(self): test_file = os.path.join( os.path.dirname(__file__), From b06450917bee3a345d820ae7ae2d89335545d734 Mon Sep 17 00:00:00 2001 From: foamyguy Date: Fri, 30 May 2025 14:21:13 -0500 Subject: [PATCH 7/9] displayio API update --- examples/imageload_netpbm.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/imageload_netpbm.py b/examples/imageload_netpbm.py index 06ca3f1..13c9b58 100644 --- a/examples/imageload_netpbm.py +++ b/examples/imageload_netpbm.py @@ -15,6 +15,7 @@ import adafruit_ili9341 import board import displayio +import fourwire import adafruit_imageload @@ -23,7 +24,7 @@ tft_dc = board.D10 displayio.release_displays() -display_bus = displayio.FourWire(spi, command=tft_dc, chip_select=tft_cs) +display_bus = fourwire.FourWire(spi, command=tft_dc, chip_select=tft_cs) display = adafruit_ili9341.ILI9341(display_bus, width=320, height=240) From 0dbfcf122ccea1af94f5768279aabc0f1dada7fb Mon Sep 17 00:00:00 2001 From: foamyguy Date: Fri, 30 May 2025 14:37:17 -0500 Subject: [PATCH 8/9] fix mock Bitmap interface for tests --- tests/displayio_shared_bindings.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/displayio_shared_bindings.py b/tests/displayio_shared_bindings.py index 5d796d7..d938e6c 100644 --- a/tests/displayio_shared_bindings.py +++ b/tests/displayio_shared_bindings.py @@ -50,6 +50,13 @@ def __init__(self, width: int, height: int, colors: int) -> None: self.height = height self.colors = colors self.data = {} + bits = 1 + while (colors - 1) >> bits: + if bits < 8: + bits = bits << 1 + else: + bits += 8 + self._bits_per_value = bits def _abs_pos(self, width: int, height: int) -> int: if height >= self.height: From 135b0e4478b34e1271e6bd87fa6d8efa0bef64b5 Mon Sep 17 00:00:00 2001 From: foamyguy Date: Wed, 4 Jun 2025 10:00:20 -0500 Subject: [PATCH 9/9] update rtd.yml file Signed-off-by: foamyguy --- .readthedocs.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 88bca9f..255dafd 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -12,7 +12,7 @@ sphinx: configuration: docs/conf.py build: - os: ubuntu-20.04 + os: ubuntu-lts-latest tools: python: "3"