diff --git a/ports/zephyr-cp/boards/adafruit/feather_nrf52840_sense_zephyr/autogen_board_info.toml b/ports/zephyr-cp/boards/adafruit/feather_nrf52840_sense_zephyr/autogen_board_info.toml index 9d7af49936a79..69896f950b56b 100644 --- a/ports/zephyr-cp/boards/adafruit/feather_nrf52840_sense_zephyr/autogen_board_info.toml +++ b/ports/zephyr-cp/boards/adafruit/feather_nrf52840_sense_zephyr/autogen_board_info.toml @@ -58,7 +58,7 @@ i2ctarget = false imagecapture = false ipaddress = false is31fl3741 = false -jpegio = false +jpegio = true keypad = false keypad_demux = false locale = false diff --git a/ports/zephyr-cp/boards/adafruit/feather_nrf52840_zephyr/autogen_board_info.toml b/ports/zephyr-cp/boards/adafruit/feather_nrf52840_zephyr/autogen_board_info.toml index 9860956ca234a..8cd4a71e3fec2 100644 --- a/ports/zephyr-cp/boards/adafruit/feather_nrf52840_zephyr/autogen_board_info.toml +++ b/ports/zephyr-cp/boards/adafruit/feather_nrf52840_zephyr/autogen_board_info.toml @@ -58,7 +58,7 @@ i2ctarget = false imagecapture = false ipaddress = false is31fl3741 = false -jpegio = false +jpegio = true keypad = false keypad_demux = false locale = false diff --git a/ports/zephyr-cp/boards/adafruit/feather_rp2040_zephyr/autogen_board_info.toml b/ports/zephyr-cp/boards/adafruit/feather_rp2040_zephyr/autogen_board_info.toml index 54c1d4d578f6d..33e54d0f7a9f4 100644 --- a/ports/zephyr-cp/boards/adafruit/feather_rp2040_zephyr/autogen_board_info.toml +++ b/ports/zephyr-cp/boards/adafruit/feather_rp2040_zephyr/autogen_board_info.toml @@ -58,7 +58,7 @@ i2ctarget = false imagecapture = false ipaddress = false is31fl3741 = false -jpegio = false +jpegio = true keypad = false keypad_demux = false locale = false diff --git a/ports/zephyr-cp/boards/native/native_sim/autogen_board_info.toml b/ports/zephyr-cp/boards/native/native_sim/autogen_board_info.toml index c9bbc0a9a4356..9ad5fa7849dc4 100644 --- a/ports/zephyr-cp/boards/native/native_sim/autogen_board_info.toml +++ b/ports/zephyr-cp/boards/native/native_sim/autogen_board_info.toml @@ -58,7 +58,7 @@ i2ctarget = false imagecapture = false ipaddress = true # Zephyr networking enabled is31fl3741 = false -jpegio = false +jpegio = true keypad = false keypad_demux = false locale = false diff --git a/ports/zephyr-cp/boards/native/nrf5340bsim/autogen_board_info.toml b/ports/zephyr-cp/boards/native/nrf5340bsim/autogen_board_info.toml index a75b1eee5b4c2..19432b2dc1bc7 100644 --- a/ports/zephyr-cp/boards/native/nrf5340bsim/autogen_board_info.toml +++ b/ports/zephyr-cp/boards/native/nrf5340bsim/autogen_board_info.toml @@ -58,7 +58,7 @@ i2ctarget = false imagecapture = false ipaddress = false is31fl3741 = false -jpegio = false +jpegio = true keypad = false keypad_demux = false locale = false diff --git a/ports/zephyr-cp/boards/nordic/nrf5340dk/autogen_board_info.toml b/ports/zephyr-cp/boards/nordic/nrf5340dk/autogen_board_info.toml index e850884fd2ee8..8f7bdba94bd52 100644 --- a/ports/zephyr-cp/boards/nordic/nrf5340dk/autogen_board_info.toml +++ b/ports/zephyr-cp/boards/nordic/nrf5340dk/autogen_board_info.toml @@ -58,7 +58,7 @@ i2ctarget = false imagecapture = false ipaddress = false is31fl3741 = false -jpegio = false +jpegio = true keypad = false keypad_demux = false locale = false diff --git a/ports/zephyr-cp/boards/nordic/nrf54h20dk/circuitpython.toml b/ports/zephyr-cp/boards/nordic/nrf54h20dk/circuitpython.toml index 3272dd4c5f319..010ae9953e432 100644 --- a/ports/zephyr-cp/boards/nordic/nrf54h20dk/circuitpython.toml +++ b/ports/zephyr-cp/boards/nordic/nrf54h20dk/circuitpython.toml @@ -1 +1,2 @@ CIRCUITPY_BUILD_EXTENSIONS = ["elf"] +DISABLED_MODULES=["jpegio"] diff --git a/ports/zephyr-cp/boards/nordic/nrf54l15dk/autogen_board_info.toml b/ports/zephyr-cp/boards/nordic/nrf54l15dk/autogen_board_info.toml index a0ff21ffa30f5..e13fba484b8e6 100644 --- a/ports/zephyr-cp/boards/nordic/nrf54l15dk/autogen_board_info.toml +++ b/ports/zephyr-cp/boards/nordic/nrf54l15dk/autogen_board_info.toml @@ -58,7 +58,7 @@ i2ctarget = false imagecapture = false ipaddress = false is31fl3741 = false -jpegio = false +jpegio = true keypad = false keypad_demux = false locale = false diff --git a/ports/zephyr-cp/boards/nordic/nrf7002dk/circuitpython.toml b/ports/zephyr-cp/boards/nordic/nrf7002dk/circuitpython.toml index 191ed9d2d2c52..b64fd8756a72d 100644 --- a/ports/zephyr-cp/boards/nordic/nrf7002dk/circuitpython.toml +++ b/ports/zephyr-cp/boards/nordic/nrf7002dk/circuitpython.toml @@ -2,4 +2,4 @@ CIRCUITPY_BUILD_EXTENSIONS = ["elf"] USB_VID=0x239A USB_PID=0x8168 BLOBS=["nrf_wifi"] -DISABLED_MODULES=["aesio", "adafruit_bus_device", "zlib"] +DISABLED_MODULES=["aesio", "adafruit_bus_device", "zlib", "jpegio"] diff --git a/ports/zephyr-cp/boards/nxp/frdm_mcxn947/autogen_board_info.toml b/ports/zephyr-cp/boards/nxp/frdm_mcxn947/autogen_board_info.toml index 32d194c870250..d4f903e515859 100644 --- a/ports/zephyr-cp/boards/nxp/frdm_mcxn947/autogen_board_info.toml +++ b/ports/zephyr-cp/boards/nxp/frdm_mcxn947/autogen_board_info.toml @@ -58,7 +58,7 @@ i2ctarget = false imagecapture = false ipaddress = false is31fl3741 = false -jpegio = false +jpegio = true keypad = false keypad_demux = false locale = false diff --git a/ports/zephyr-cp/boards/nxp/frdm_rw612/autogen_board_info.toml b/ports/zephyr-cp/boards/nxp/frdm_rw612/autogen_board_info.toml index db496abb5317e..659089d20ad4e 100644 --- a/ports/zephyr-cp/boards/nxp/frdm_rw612/autogen_board_info.toml +++ b/ports/zephyr-cp/boards/nxp/frdm_rw612/autogen_board_info.toml @@ -58,7 +58,7 @@ i2ctarget = false imagecapture = false ipaddress = true # Zephyr networking enabled is31fl3741 = false -jpegio = false +jpegio = true keypad = false keypad_demux = false locale = false diff --git a/ports/zephyr-cp/boards/nxp/mimxrt1170_evk/autogen_board_info.toml b/ports/zephyr-cp/boards/nxp/mimxrt1170_evk/autogen_board_info.toml index 3144751db86ad..a5f343c76913f 100644 --- a/ports/zephyr-cp/boards/nxp/mimxrt1170_evk/autogen_board_info.toml +++ b/ports/zephyr-cp/boards/nxp/mimxrt1170_evk/autogen_board_info.toml @@ -58,7 +58,7 @@ i2ctarget = false imagecapture = false ipaddress = false is31fl3741 = false -jpegio = false +jpegio = true keypad = false keypad_demux = false locale = false diff --git a/ports/zephyr-cp/boards/raspberrypi/rpi_pico2_w_zephyr/autogen_board_info.toml b/ports/zephyr-cp/boards/raspberrypi/rpi_pico2_w_zephyr/autogen_board_info.toml index aa41a95b8a0d7..ba59347f21eed 100644 --- a/ports/zephyr-cp/boards/raspberrypi/rpi_pico2_w_zephyr/autogen_board_info.toml +++ b/ports/zephyr-cp/boards/raspberrypi/rpi_pico2_w_zephyr/autogen_board_info.toml @@ -58,7 +58,7 @@ i2ctarget = false imagecapture = false ipaddress = true # Zephyr networking enabled is31fl3741 = false -jpegio = false +jpegio = true keypad = false keypad_demux = false locale = false diff --git a/ports/zephyr-cp/boards/raspberrypi/rpi_pico2_zephyr/autogen_board_info.toml b/ports/zephyr-cp/boards/raspberrypi/rpi_pico2_zephyr/autogen_board_info.toml index a9305e511c93b..138f7f14e4d33 100644 --- a/ports/zephyr-cp/boards/raspberrypi/rpi_pico2_zephyr/autogen_board_info.toml +++ b/ports/zephyr-cp/boards/raspberrypi/rpi_pico2_zephyr/autogen_board_info.toml @@ -58,7 +58,7 @@ i2ctarget = false imagecapture = false ipaddress = false is31fl3741 = false -jpegio = false +jpegio = true keypad = false keypad_demux = false locale = false diff --git a/ports/zephyr-cp/boards/raspberrypi/rpi_pico_w_zephyr/autogen_board_info.toml b/ports/zephyr-cp/boards/raspberrypi/rpi_pico_w_zephyr/autogen_board_info.toml index 0658a5674dc49..f0b10b59783db 100644 --- a/ports/zephyr-cp/boards/raspberrypi/rpi_pico_w_zephyr/autogen_board_info.toml +++ b/ports/zephyr-cp/boards/raspberrypi/rpi_pico_w_zephyr/autogen_board_info.toml @@ -58,7 +58,7 @@ i2ctarget = false imagecapture = false ipaddress = true # Zephyr networking enabled is31fl3741 = false -jpegio = false +jpegio = true keypad = false keypad_demux = false locale = false diff --git a/ports/zephyr-cp/boards/raspberrypi/rpi_pico_zephyr/autogen_board_info.toml b/ports/zephyr-cp/boards/raspberrypi/rpi_pico_zephyr/autogen_board_info.toml index c9184ee81cd1c..647d9ee9ac3b7 100644 --- a/ports/zephyr-cp/boards/raspberrypi/rpi_pico_zephyr/autogen_board_info.toml +++ b/ports/zephyr-cp/boards/raspberrypi/rpi_pico_zephyr/autogen_board_info.toml @@ -58,7 +58,7 @@ i2ctarget = false imagecapture = false ipaddress = false is31fl3741 = false -jpegio = false +jpegio = true keypad = false keypad_demux = false locale = false diff --git a/ports/zephyr-cp/boards/renesas/da14695_dk_usb/autogen_board_info.toml b/ports/zephyr-cp/boards/renesas/da14695_dk_usb/autogen_board_info.toml index 07463f618e84c..f4d6dee7f8a60 100644 --- a/ports/zephyr-cp/boards/renesas/da14695_dk_usb/autogen_board_info.toml +++ b/ports/zephyr-cp/boards/renesas/da14695_dk_usb/autogen_board_info.toml @@ -58,7 +58,7 @@ i2ctarget = false imagecapture = false ipaddress = false is31fl3741 = false -jpegio = false +jpegio = true keypad = false keypad_demux = false locale = false diff --git a/ports/zephyr-cp/boards/renesas/ek_ra6m5/autogen_board_info.toml b/ports/zephyr-cp/boards/renesas/ek_ra6m5/autogen_board_info.toml index 8f3d8612819e3..ff95cc49b424b 100644 --- a/ports/zephyr-cp/boards/renesas/ek_ra6m5/autogen_board_info.toml +++ b/ports/zephyr-cp/boards/renesas/ek_ra6m5/autogen_board_info.toml @@ -58,7 +58,7 @@ i2ctarget = false imagecapture = false ipaddress = false is31fl3741 = false -jpegio = false +jpegio = true keypad = false keypad_demux = false locale = false diff --git a/ports/zephyr-cp/boards/renesas/ek_ra8d1/autogen_board_info.toml b/ports/zephyr-cp/boards/renesas/ek_ra8d1/autogen_board_info.toml index 431d459f06305..41d8923524263 100644 --- a/ports/zephyr-cp/boards/renesas/ek_ra8d1/autogen_board_info.toml +++ b/ports/zephyr-cp/boards/renesas/ek_ra8d1/autogen_board_info.toml @@ -58,7 +58,7 @@ i2ctarget = false imagecapture = false ipaddress = false is31fl3741 = false -jpegio = false +jpegio = true keypad = false keypad_demux = false locale = false diff --git a/ports/zephyr-cp/boards/st/nucleo_n657x0_q/autogen_board_info.toml b/ports/zephyr-cp/boards/st/nucleo_n657x0_q/autogen_board_info.toml index 21f064f05774e..5611f62e33efd 100644 --- a/ports/zephyr-cp/boards/st/nucleo_n657x0_q/autogen_board_info.toml +++ b/ports/zephyr-cp/boards/st/nucleo_n657x0_q/autogen_board_info.toml @@ -58,7 +58,7 @@ i2ctarget = false imagecapture = false ipaddress = false is31fl3741 = false -jpegio = false +jpegio = true keypad = false keypad_demux = false locale = false diff --git a/ports/zephyr-cp/boards/st/nucleo_u575zi_q/autogen_board_info.toml b/ports/zephyr-cp/boards/st/nucleo_u575zi_q/autogen_board_info.toml index dcf94db57ae8a..2ad6616e2bab1 100644 --- a/ports/zephyr-cp/boards/st/nucleo_u575zi_q/autogen_board_info.toml +++ b/ports/zephyr-cp/boards/st/nucleo_u575zi_q/autogen_board_info.toml @@ -58,7 +58,7 @@ i2ctarget = false imagecapture = false ipaddress = false is31fl3741 = false -jpegio = false +jpegio = true keypad = false keypad_demux = false locale = false diff --git a/ports/zephyr-cp/boards/st/stm32h750b_dk/autogen_board_info.toml b/ports/zephyr-cp/boards/st/stm32h750b_dk/autogen_board_info.toml index 813039f653812..ba7593fbc33fd 100644 --- a/ports/zephyr-cp/boards/st/stm32h750b_dk/autogen_board_info.toml +++ b/ports/zephyr-cp/boards/st/stm32h750b_dk/autogen_board_info.toml @@ -58,7 +58,7 @@ i2ctarget = false imagecapture = false ipaddress = false is31fl3741 = false -jpegio = false +jpegio = true keypad = false keypad_demux = false locale = false diff --git a/ports/zephyr-cp/boards/st/stm32h7b3i_dk/autogen_board_info.toml b/ports/zephyr-cp/boards/st/stm32h7b3i_dk/autogen_board_info.toml index c4a3a0be44c1d..1cd4ca1a87f22 100644 --- a/ports/zephyr-cp/boards/st/stm32h7b3i_dk/autogen_board_info.toml +++ b/ports/zephyr-cp/boards/st/stm32h7b3i_dk/autogen_board_info.toml @@ -58,7 +58,7 @@ i2ctarget = false imagecapture = false ipaddress = false is31fl3741 = false -jpegio = false +jpegio = true keypad = false keypad_demux = false locale = false diff --git a/ports/zephyr-cp/boards/st/stm32wba65i_dk1/autogen_board_info.toml b/ports/zephyr-cp/boards/st/stm32wba65i_dk1/autogen_board_info.toml index 54f72b3006347..9751aea8fc693 100644 --- a/ports/zephyr-cp/boards/st/stm32wba65i_dk1/autogen_board_info.toml +++ b/ports/zephyr-cp/boards/st/stm32wba65i_dk1/autogen_board_info.toml @@ -58,7 +58,7 @@ i2ctarget = false imagecapture = false ipaddress = false is31fl3741 = false -jpegio = false +jpegio = true keypad = false keypad_demux = false locale = false diff --git a/ports/zephyr-cp/cptools/build_circuitpython.py b/ports/zephyr-cp/cptools/build_circuitpython.py index 7f3a012064fbe..8ff210def0fcd 100644 --- a/ports/zephyr-cp/cptools/build_circuitpython.py +++ b/ports/zephyr-cp/cptools/build_circuitpython.py @@ -66,6 +66,7 @@ "zlib", "adafruit_bus_device", "getpass", + "jpegio", ] # Flags that don't match with with a *bindings module. Some used by adafruit_requests MPCONFIG_FLAGS = ["array", "errno", "io", "json", "math"] @@ -119,6 +120,7 @@ "lib/uzlib/adler32.c", "lib/uzlib/crc32.c", ], + "jpegio": ["lib/tjpgd/src/tjpgd.c"], } SHARED_MODULE_AND_COMMON_HAL = ["_bleio", "os", "rotaryio"] diff --git a/ports/zephyr-cp/tests/zephyr_display/golden/jpegio_test_pattern_320x240.png b/ports/zephyr-cp/tests/zephyr_display/golden/jpegio_test_pattern_320x240.png new file mode 100644 index 0000000000000..4987b76ff8089 Binary files /dev/null and b/ports/zephyr-cp/tests/zephyr_display/golden/jpegio_test_pattern_320x240.png differ diff --git a/ports/zephyr-cp/tests/zephyr_display/test.jpg b/ports/zephyr-cp/tests/zephyr_display/test.jpg new file mode 100644 index 0000000000000..488357f506a52 Binary files /dev/null and b/ports/zephyr-cp/tests/zephyr_display/test.jpg differ diff --git a/ports/zephyr-cp/tests/zephyr_display/test_jpegio.py b/ports/zephyr-cp/tests/zephyr_display/test_jpegio.py new file mode 100644 index 0000000000000..6368be7a5cfa7 --- /dev/null +++ b/ports/zephyr-cp/tests/zephyr_display/test_jpegio.py @@ -0,0 +1,87 @@ +# SPDX-FileCopyrightText: 2026 Scott Shawcroft for Adafruit Industries +# SPDX-License-Identifier: MIT + +import shutil +from pathlib import Path + +import pytest +from PIL import Image + + +_TEST_JPG_PATH = Path(__file__).parent / "test.jpg" +_TEST_JPG_BYTES = _TEST_JPG_PATH.read_bytes() + + +def _read_image(path: Path) -> tuple[int, int, bytes]: + with Image.open(path) as img: + rgb = img.convert("RGB") + return rgb.width, rgb.height, rgb.tobytes() + + +def _golden_compare_or_update(request, captures, golden_path): + if not captures or not captures[0].exists(): + pytest.skip("display capture was not produced") + + if request.config.getoption("--update-goldens"): + golden_path.parent.mkdir(parents=True, exist_ok=True) + shutil.copy2(captures[0], golden_path) + return + + gw, gh, gpx = _read_image(golden_path) + dw, dh, dpx = _read_image(captures[0]) + assert (dw, dh) == (gw, gh) + assert gpx == dpx + + +JPEGIO_DECODE_CODE = """\ +import board +import displayio +import time +from jpegio import JpegDecoder + +decoder = JpegDecoder() +width, height = decoder.open("/test.jpg") +print('size', width, height) + +bitmap = displayio.Bitmap(width, height, 65535) +decoder.decode(bitmap) + +for i in range(min(width, 8)): + print('px', i, hex(bitmap[i, 0])) + +scale = 10 +tg = displayio.TileGrid( + bitmap, + pixel_shader=displayio.ColorConverter( + input_colorspace=displayio.Colorspace.RGB565_SWAPPED + ), +) +g = displayio.Group(scale=scale) +g.x = (board.DISPLAY.width - width * scale) // 2 +g.y = (board.DISPLAY.height - height * scale) // 2 +g.append(tg) + +board.DISPLAY.auto_refresh = False +board.DISPLAY.root_group = g +board.DISPLAY.refresh() +print('rendered') +while True: + time.sleep(1) +""" + + +_JPEGIO_DRIVE = {"code.py": JPEGIO_DECODE_CODE, "test.jpg": _TEST_JPG_BYTES} + + +@pytest.mark.circuitpy_drive(_JPEGIO_DRIVE) +@pytest.mark.display(capture_times_ns=[14_000_000_000]) +@pytest.mark.duration(18) +def test_jpegio_decode(request, circuitpython): + circuitpython.wait_until_done() + + output = circuitpython.serial.all_output + assert "size 20 20" in output + assert "rendered" in output + + golden = Path(__file__).parent / "golden" / "jpegio_test_pattern_320x240.png" + _golden_compare_or_update(request, circuitpython.display_capture_paths(), golden) diff --git a/shared-bindings/jpegio/JpegDecoder.c b/shared-bindings/jpegio/JpegDecoder.c index ad2a0622e7a61..752d80b4733bb 100644 --- a/shared-bindings/jpegio/JpegDecoder.c +++ b/shared-bindings/jpegio/JpegDecoder.c @@ -23,13 +23,18 @@ //| Example:: //| //| from jpegio import JpegDecoder -//| from displayio import Bitmap +//| from displayio import Bitmap, TileGrid, ColorConverter, Colorspace +//| import supervisor //| //| decoder = JpegDecoder() //| width, height = decoder.open("/sd/example.jpg") //| bitmap = Bitmap(width, height, 65535) //| decoder.decode(bitmap) -//| # .. do something with bitmap +//| tg = TileGrid(bitmap, pixel_shader=ColorConverter(input_colorspace=Colorspace.RGB565_SWAPPED)) +//| supervisor.runtime.display.root_group = tg +//| while True: +//| pass +//| //| """ //| //| def __init__(self) -> None: