diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 33c2a61..255dafd 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -8,8 +8,11 @@ # Required version: 2 +sphinx: + configuration: docs/conf.py + build: - os: ubuntu-20.04 + os: ubuntu-lts-latest tools: python: "3" diff --git a/adafruit_imageload/png.py b/adafruit_imageload/png.py old mode 100644 new mode 100755 index 010dff3..215e313 --- 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,17 +106,56 @@ 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 = (byte + p) & 0xFF + else: + raise ValueError("Wrong filter.") + line[pos] = byte 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 + prev, line = line, prev return bmp, pal # RGB, RGBA or Grayscale import displayio 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/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") 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) 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/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: 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__), diff --git a/tests/test_png.png b/tests/test_png.png new file mode 100644 index 0000000..314173a Binary files /dev/null and b/tests/test_png.png differ 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)