From 1870142c861ccd440afc3cc218bd7cbcd34655fe Mon Sep 17 00:00:00 2001 From: Brad Barnett <127794626+bdbarnett@users.noreply.github.com> Date: Tue, 6 Oct 2026 10:19:25 -0500 Subject: [PATCH] jpegio moves to micropython-pydevices' modules/jpegio The JPEG decoder leaves displayif for micropython-pydevices (the media modules roadmap's Phase 1): src/jpegio and tests/jpegio go, the root micropython.cmake/.mk no longer include it, and clean-build.yml loses its jpegio steps and the unix-lvgl-jpegio job (they run there now). The qstr checker's CORE_POOL is empty without jpegio's __init__. The README, AGENTS.md and docs say where it went. displayif's CircuitPython patch is unchanged: it uses CircuitPython's own jpegio entries only as insertion anchors. --- .github/workflows/clean-build.yml | 107 -- AGENTS.md | 5 +- README.md | 8 +- docs/newcomers.md | 1 - docs/qstrs-and-usermod-defines.md | 10 +- micropython.cmake | 6 +- micropython.mk | 7 +- src/jpegio/README.md | 344 ----- src/jpegio/jpegio.c | 543 -------- src/jpegio/lvgl_decoder.c | 304 ----- src/jpegio/lvgl_decoder.h | 27 - src/jpegio/micropython.cmake | 46 - src/jpegio/micropython.mk | 40 - src/jpegio/tjpgd/tjpgd.c | 1157 ----------------- src/jpegio/tjpgd/tjpgd.h | 123 -- src/jpegio/tjpgd/tjpgdcnf.h | 32 - tests/README.md | 1 - tests/jpegio/frames/README.md | 17 - tests/jpegio/frames/baseline_jfif_320x240.jpg | Bin 11172 -> 0 bytes tests/jpegio/frames/baseline_jfif_64x48.jpg | Bin 1540 -> 0 bytes tests/jpegio/frames/c920e_320x240_dri.jpg | Bin 8064 -> 0 bytes .../frames/dri_first_mjpeg_style_320x240.jpg | Bin 11348 -> 0 bytes tests/jpegio/frames/golden_tjpgd.json | 54 - tests/jpegio/frames/grayscale_64x48.jpg | Bin 960 -> 0 bytes tests/jpegio/frames/nodht_320x240.jpg | Bin 10740 -> 0 bytes tests/jpegio/frames/odd_size_37x29.jpg | Bin 1427 -> 0 bytes tests/jpegio/frames/progressive_320x240.jpg | Bin 6074 -> 0 bytes tests/jpegio/frames/reference.json | 603 --------- tests/jpegio/frames/restart_dri_320x240.jpg | Bin 11348 -> 0 bytes tests/jpegio/jpegwalk.py | 198 --- tests/jpegio/make_corpus.py | 244 ---- tests/jpegio/reference.py | 170 --- tests/jpegio/test_jpegio.py | 580 --------- tests/jpegio/test_jpegio_lvgl.py | 416 ------ tools/check_qstr_reachability.py | 4 +- 35 files changed, 17 insertions(+), 5030 deletions(-) delete mode 100644 src/jpegio/README.md delete mode 100644 src/jpegio/jpegio.c delete mode 100644 src/jpegio/lvgl_decoder.c delete mode 100644 src/jpegio/lvgl_decoder.h delete mode 100644 src/jpegio/micropython.cmake delete mode 100644 src/jpegio/micropython.mk delete mode 100644 src/jpegio/tjpgd/tjpgd.c delete mode 100644 src/jpegio/tjpgd/tjpgd.h delete mode 100644 src/jpegio/tjpgd/tjpgdcnf.h delete mode 100644 tests/jpegio/frames/README.md delete mode 100644 tests/jpegio/frames/baseline_jfif_320x240.jpg delete mode 100644 tests/jpegio/frames/baseline_jfif_64x48.jpg delete mode 100644 tests/jpegio/frames/c920e_320x240_dri.jpg delete mode 100644 tests/jpegio/frames/dri_first_mjpeg_style_320x240.jpg delete mode 100644 tests/jpegio/frames/golden_tjpgd.json delete mode 100644 tests/jpegio/frames/grayscale_64x48.jpg delete mode 100644 tests/jpegio/frames/nodht_320x240.jpg delete mode 100644 tests/jpegio/frames/odd_size_37x29.jpg delete mode 100644 tests/jpegio/frames/progressive_320x240.jpg delete mode 100644 tests/jpegio/frames/reference.json delete mode 100644 tests/jpegio/frames/restart_dri_320x240.jpg delete mode 100644 tests/jpegio/jpegwalk.py delete mode 100644 tests/jpegio/make_corpus.py delete mode 100644 tests/jpegio/reference.py delete mode 100644 tests/jpegio/test_jpegio.py delete mode 100644 tests/jpegio/test_jpegio_lvgl.py diff --git a/.github/workflows/clean-build.yml b/.github/workflows/clean-build.yml index f70f0b4..8518b1f 100644 --- a/.github/workflows/clean-build.yml +++ b/.github/workflows/clean-build.yml @@ -69,110 +69,3 @@ jobs: - name: Run tests/test_lifecycle_api.py under the built interpreter run: | ./micropython/ports/unix/build-standard/micropython displayif/tests/test_lifecycle_api.py - - - name: Run tests/jpegio/test_jpegio.py under the built interpreter - # The jpegio Phase 1 gate: the corpus in tests/jpegio/frames against - # the Pillow reference and TJpgDec's golden digests (no Pillow needed). - run: | - ./micropython/ports/unix/build-standard/micropython displayif/tests/jpegio/test_jpegio.py - - - name: Run tests/jpegio/test_jpegio_lvgl.py (must print SKIP -- no lvgl here) - # This leg is the only one that can check the decoder-less contract: - # register_lvgl_decoder() and lvgl_decoders() are module attributes on - # every build (they must be, or the CMake ports' QSTR pass never sees - # their names -- see src/jpegio/README.md), the first raising - # RuntimeError here, the second returning (). - run: | - ./micropython/ports/unix/build-standard/micropython displayif/tests/jpegio/test_jpegio_lvgl.py | tee lvgl.out - grep -q 'register_lvgl_decoder() -> RuntimeError' lvgl.out - grep -q 'lvgl_decoders() == ()' lvgl.out - grep -q '^SKIP' lvgl.out - - # Scenario: the same stranger adds the LVGL family. jpegio Phase 2 (org - # docs/jpegio-vision.md, "no LVGL fork"): LVGL's own TJPGD is off by config - # in lvgl-bindings' lv_conf.h, displayif's vendored TJpgDec is the firmware's - # only one, and src/jpegio/lvgl_decoder.c registers it as LVGL's JPEG - # decoder when the lvgl-micropython sibling is on the USER_C_MODULES scan - # path (D4: displayif self-detects; nothing is passed by hand). - unix-lvgl-jpegio: - runs-on: ubuntu-latest - timeout-minutes: 45 - steps: - - uses: actions/checkout@v7 - with: - path: displayif - - - name: Check out upstream MicroPython v1.28.0 - uses: actions/checkout@v7 - with: - repository: micropython/micropython - ref: v1.29.0 - path: micropython - - # Pinned to the lvgl-bindings commit that turns LV_USE_TJPGD off on - # MicroPython/CircuitPython (tjpgd_init/deinit CPython-only) and the - # lvgl-micropython sync that consumes it. Move both together: the - # lvgl-micropython build refuses a bindings checkout that does not - # match its LVGL_BINDINGS_COMMIT pin. - - name: Check out lvgl-bindings (LV_USE_TJPGD 0 on MicroPython) - uses: actions/checkout@v7 - with: - repository: PyDevices/lvgl-bindings - ref: d5f3d341e6bcfc97d52d66f776587c215c95ff4d - path: lvgl-bindings - submodules: true - - - name: Check out lvgl-micropython (pins that lvgl-bindings commit) - uses: actions/checkout@v7 - with: - repository: PyDevices/lvgl-micropython - ref: 410e2e8dcf38e316f5da7bc63b35ba4b3fd07dfd - path: lvgl-micropython - - - name: Install SDL2 - run: | - sudo apt-get update - sudo apt-get install -y libsdl2-dev - - - name: Build the unix port with displayif + lvgl-micropython + lvgl-bindings - # Both modules are named by their own manifests (MicroPython 1.29 - # c_module()), so a two-line manifest selects them and there is no - # USER_C_MODULES: passing the parent directory as well would include - # each module twice. FROZEN_MANIFEST also freezes lvgl-micropython's - # display_driver.py / fs_driver.py (the file-source test uses the - # latter). jpegio sees lvgl-micropython in USER_C_MODULES (appended by - # the manifest) and compiles its LVGL decoder. - run: | - printf 'include("displayif/manifest.py")\ninclude("lvgl-micropython/manifest.py")\n' > ci-manifest.py - make -C micropython/mpy-cross -j2 - make -C micropython/ports/unix submodules - make -C micropython/ports/unix -j2 \ - FROZEN_MANIFEST=$GITHUB_WORKSPACE/ci-manifest.py - - - name: One TJpgDec in the firmware, and it is jpegio's - # Exactly one object defines jd_prepare (displayif's tjpgd.o; LVGL's - # tjpgd.o is empty under LV_USE_TJPGD 0), no jpegio_jd_ prefix left, - # and both jpegio.o and lvgl_decoder.o import it. - run: | - cd micropython/ports/unix/build-standard - find . -name '*.o' -exec nm -A {} + 2>/dev/null | grep -E ' T jd_(prepare|decomp)$' | tee jd.txt - test "$(grep -c ' T jd_prepare$' jd.txt)" = 1 - grep -q '^./displayif/src/jpegio/tjpgd/tjpgd.o:.* T jd_prepare$' jd.txt - ! find . -name '*.o' -exec nm -A {} + 2>/dev/null | grep -q 'jpegio_jd_' - nm displayif/src/jpegio/jpegio.o | grep -q ' U jd_prepare$' - nm displayif/src/jpegio/lvgl_decoder.o | grep -q ' U jd_decomp$' - test "$(find . -name tjpgd.o | wc -l)" = 2 - - - name: Run tests/jpegio/test_jpegio.py (goldens unchanged beside LVGL) - run: | - ./micropython/ports/unix/build-standard/micropython displayif/tests/jpegio/test_jpegio.py - - - name: Run tests/jpegio/test_jpegio_lvgl.py (the LVGL half of the Phase 2 gate) - run: | - ./micropython/ports/unix/build-standard/micropython displayif/tests/jpegio/test_jpegio_lvgl.py | tee lvgl.out - ! grep -q '^SKIP' lvgl.out - grep -q 'digests exact through lv.image: 3 of 3' lvgl.out - - - name: Run lvgl-bindings' smoke test (tjpgd_init absent on MicroPython) - run: | - ./micropython/ports/unix/build-standard/micropython lvgl-bindings/tools/test_lvgl_smoke.py diff --git a/AGENTS.md b/AGENTS.md index 578e775..e875f58 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -2,8 +2,8 @@ Native MicroPython display **interface** modules for PyDevices `displaydev` (`USER_C_MODULES`). Portable code in `src/ports/common/`; SoC code under `src/ports//`; -desktop SDL (`usdl2`) under `src/ports/desktop/usdl2/`; the `jpegio` JPEG -decoder (every port) under `src/jpegio/`. CircuitPython: MCU +desktop SDL (`usdl2`) under `src/ports/desktop/usdl2/`. The `jpegio` JPEG +decoder moved to micropython-pydevices' `modules/jpegio` (2026-10-06). CircuitPython: MCU interfaces stay on stock CP; only `usdl2` uses `./apply_cp_patches.sh` (unix). **Before editing lifecycle / soft-reset / a new board bring-up**, read: @@ -21,7 +21,6 @@ attr / soft-reset patterns when changing siblings. |------|----------| | `src/include/` | Public headers (`displayif/` subdir) | | `src/ports/` | Port C sources + per-port `micropython.mk` / `.cmake` (`common/`, `esp32/`, `desktop/usdl2/`, …) | -| `src/jpegio/` | `jpegio` JPEG decoder (CircuitPython API, vendored TJpgDec in `tjpgd/`); platform-neutral, built on every port; `lvgl_decoder.c` registers it as LVGL's JPEG decoder when the `lvgl-micropython` sibling usermod is in the build — [src/jpegio/README.md](src/jpegio/README.md) | | `src/circuitpython_spike/` | CP unix spike for `usdl2` (copied by `apply_cp_patches.sh`) | | `docs/` | Markdown docs (root keeps `README.md` and `AGENTS.md` only) | | `docs/ports/` | Per-port notes moved from former `ports/*/README.md` | diff --git a/README.md b/README.md index de9bf48..ce97b9e 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,6 @@ failure modes (P4 `mipidsi`, Qualia `dotclockframebuffer.DotClockFramebuffer`): | `picodvi` | `rp2` (RP2040 PIO / RP2350 HSTX) | **FBDisplay** | | `rgbmatrix` | `esp32` (S3) / `mimxrt` (1062) / `samd` (SAMD51) / `rp2` | **FBDisplay** | | `usdl2` | `desktop` (MicroPython `unix` / `windows`; CircuitPython unix via `apply_cp_patches.sh`) | **SDLDisplay** | -| `jpegio` | every port (`src/jpegio/`, platform-neutral; CircuitPython has it natively) | any — decodes JPEG to RGB565 for `blit_rect`; beside `lvgl-micropython` it is also LVGL's JPEG decoder ([docs](src/jpegio/README.md)) | | stubs | `samd` / `rp2` / non-1062 mimxrt (`dotclockframebuffer.DotClockFramebuffer`, `mipidsi`); non-1176 mimxrt (`mipidsi`); non-S3 `qspibus` | ctor raises | @@ -68,7 +67,7 @@ cd micropython/ports/unix && make submodules && make The manifest freezes no Python; it only names the C module, and the build glue picks the modules your port supports (see [docs/port-matrix.md](docs/port-matrix.md)). -On unix that is `usdl2` and `jpegio`; the bus and framebuffer modules are +On unix that is `usdl2`; the bus and framebuffer modules are MCU-only. Tested on the unix port against MicroPython v1.29.0. If you would rather not edit the MicroPython tree, write a manifest of your own @@ -138,9 +137,8 @@ Then run the lifecycle API test under the same interpreter: [the protomatter/ README](src/ports/common/rgbmatrix/protomatter/README.md) for the identified upstream revision and license text. -`src/jpegio/tjpgd/` vendors ChaN's TJpgDec R0.03 (with patch1, via -CircuitPython's `lib/tjpgd`) under its own permissive notice; see -[src/jpegio/README.md](src/jpegio/README.md#notice--tjpgdec). +The JPEG decoder, `jpegio`, moved to micropython-pydevices' +[modules/jpegio](https://github.com/PyDevices/micropython-pydevices/tree/main/modules/jpegio) on 2026-10-06, with its vendored TJpgDec and its notice. ## Related diff --git a/docs/newcomers.md b/docs/newcomers.md index e08f427..dab1137 100644 --- a/docs/newcomers.md +++ b/docs/newcomers.md @@ -50,7 +50,6 @@ commands and shows how to combine several CMake user modules. | `src/ports/common/` | Shared buses, helpers, RGB matrix code, and lifecycle support. | | `src/ports//` | SoC-specific backends for ESP32, RP2, SAMD, and MIMXRT; `stm32` builds the portable buses only. | | `src/ports/desktop/usdl2/` | SDL2 desktop backend for MicroPython unix and Windows. | -| `src/jpegio/` | Platform-neutral JPEG decoder and optional LVGL decoder integration. | | `src/include/displayif/` | Public native headers. | | `micropython.mk`, `micropython.cmake` | Make/CMake user-module entrypoints. | | `docs/port-matrix.md` | Module/port/board-config support matrix. | diff --git a/docs/qstrs-and-usermod-defines.md b/docs/qstrs-and-usermod-defines.md index 38c33fb..39a6e82 100644 --- a/docs/qstrs-and-usermod-defines.md +++ b/docs/qstrs-and-usermod-defines.md @@ -52,9 +52,9 @@ months looking like nothing. A guarded name is only safe if something else collects it: 1. **It is in MicroPython's static pool** (`py/qstrdefs.h`) — `__init__`, - `__name__` and the rest. `src/jpegio/jpegio.c` relies on this for - `MP_QSTR___init__`, which is why the checker carries a short `CORE_POOL` - list. Add to that list only with the reason written down; the pool is + `__name__` and the rest. jpegio relied on this for `MP_QSTR___init__` + while it lived here (it's now micropython-pydevices' `modules/jpegio`), which + is why the checker has a `CORE_POOL` list, empty today. Add to that list only with the reason written down; the pool is MicroPython's, and it changes between versions. 2. **The same file references it unguarded elsewhere.** `src/ports/common/spi/mod_spibus.c` guards `sck`/`mosi`/`miso` behind @@ -81,8 +81,8 @@ pass does, with no INTERFACE define: | before | 0 | 14 | | after | 14 | 14 | -`src/jpegio/README.md` has the same story from the day it was found, with the -LVGL decoder's specifics. +jpegio's README (now micropython-pydevices' `modules/jpegio/README.md`) has the +same story from the day it was found, with the LVGL decoder's specifics. diff --git a/micropython.cmake b/micropython.cmake index 6429f14..28d77a8 100644 --- a/micropython.cmake +++ b/micropython.cmake @@ -6,8 +6,7 @@ # want displayif plus other modules — no aggregator file required, e.g.: # -DUSER_C_MODULES="/path/to/displayif;/path/to/other_mod" # Hardware interfaces build only on MCU ports. Desktop usdl2 is Make-only -# (unix/windows via micropython.mk); no cmake desktop path yet. `jpegio` -# builds everywhere. +# (unix/windows via micropython.mk); no cmake desktop path yet. set(DISPLAYIF_MOD_DIR ${CMAKE_CURRENT_LIST_DIR}) @@ -76,5 +75,4 @@ if(DISPLAYIF_PORT_STM32) include(${DISPLAYIF_MOD_DIR}/src/ports/stm32/micropython.cmake) endif() -# JPEG decoder (`import jpegio`): platform-neutral C, built on every port. -include(${DISPLAYIF_MOD_DIR}/src/jpegio/micropython.cmake) +# The JPEG decoder, jpegio, moved to micropython-pydevices' modules/jpegio. diff --git a/micropython.mk b/micropython.mk index 385c84f..10ba71b 100644 --- a/micropython.mk +++ b/micropython.mk @@ -3,7 +3,7 @@ # Discovered via USER_C_MODULES pointing at the workspace directory that # contains this repo (its parent), e.g. `make USER_C_MODULES=../../..`. # Hardware interfaces (spibus, dotclockframebuffer, …) build only on MCU ports. -# Desktop SDL (`usdl2`) builds on unix and windows ports. `jpegio` builds everywhere. +# Desktop SDL (`usdl2`) builds on unix and windows ports. DISPLAYIF_MOD_DIR := $(USERMOD_DIR) @@ -122,12 +122,11 @@ ifneq ($(DISPLAYIF_PORT_UNIX)$(DISPLAYIF_PORT_WINDOWS),00) include $(DISPLAYIF_MOD_DIR)/src/ports/desktop/usdl2/micropython.mk endif -# JPEG decoder (`import jpegio`): platform-neutral C, built on every port. -include $(DISPLAYIF_MOD_DIR)/src/jpegio/micropython.mk +# The JPEG decoder, jpegio, moved to micropython-pydevices' modules/jpegio. # displayif_revision_obj, read as .__revision__ on every module here. # src/include is on the path here, not only in ports/common/micropython.mk, -# because a port that builds jpegio alone (webassembly) never includes that. +# because a port that builds no display module (webassembly) never includes that. CFLAGS_USERMOD += -I$(DISPLAYIF_MOD_DIR)/src/include SRC_USERMOD_C += $(DISPLAYIF_MOD_DIR)/src/ports/common/build.c diff --git a/src/jpegio/README.md b/src/jpegio/README.md deleted file mode 100644 index 732ee99..0000000 --- a/src/jpegio/README.md +++ /dev/null @@ -1,344 +0,0 @@ -# jpegio — baseline JPEG decoder (CircuitPython API, TJpgDec R0.03) - -`import jpegio` gives MicroPython the same `JpegDecoder` CircuitPython ships -natively, so one script decodes on both — CircuitPython uses its own, a -MicroPython firmware built with displayif uses this one. It exists so a -`displaydev` firmware can show a camera's MJPEG frames (or a JPEG on flash) -without carrying LVGL for its decoder (org `docs/jpegio-vision.md`). - -Platform-neutral C: built on every port from the root `micropython.mk` / -`micropython.cmake`. Output is native-order RGB565, block by block, straight -from TJpgDec — no full-frame buffer inside the module. - -Beside the `lvgl-micropython` usermod it is also LVGL's JPEG decoder: the -same TJpgDec, registered through LVGL's public `lv_image_decoder_create` -API, so `lv.image` shows JPEGs (webcam MJPEG frames included) with one -decoder in the firmware — see [LVGL image decoder](#lvgl-image-decoder). - -```python -import jpegio -decoder = jpegio.JpegDecoder() # allocates its ~3.7 KB work area once -width, height = decoder.open("/sd/photo.jpg") -buf = bytearray(width * height * 2) # RGB565, tight, row-major -decoder.decode(buf) -display_drv.blit_rect(buf, 0, 0, width, height) - -# camera: hand each MCU block straight to the display, no frame buffer -width, height = decoder.open(frame) # bytes/bytearray/memoryview of one MJPEG frame -decoder.decode(lambda x, y, w, h, mv: display_drv.blit_rect(mv, x, y, w, h), 1) -``` - -## API - -### `jpegio.JpegDecoder()` - -No arguments. Holds TJpgDec's decoder state and its `TJPGD_WORKSPACE_SIZE` -(3500-byte, CircuitPython's number) work area inside the object, allocated -once — create one decoder and reuse it, as CP's docs advise. - -### `open(source) -> (width, height)` - -Positional-only, like CP. `source` is one of: - -- `str` — a path; opened `"rb"` by the module and closed after `decode()` - (or when `open()` fails, or on the next `open()`). -- a bytes-like object (`bytes`, `bytearray`, `memoryview`, anything with the - buffer protocol) — read in place through the buffer protocol; the source is - not copied. Keep it alive and unchanged until `decode()` returns. -- a binary stream: any object with MicroPython's native stream protocol - (`open(...)` files, `io.BytesIO`, sockets). Unseekable streams are fine; - skipped segments are read through, never seeked. Text streams, - pure-Python objects that merely define `read()`, and anything else - (`None`, an int, a list) are not accepted (TypeError). - -Parses the headers (`jd_prepare`) and returns the image size. `width` and -`height` are also readable as properties and track the last `open()`: -valid after a successful one (still valid after its `decode()`, so -`blit_rect(buf, 0, 0, decoder.width, decoder.height)` works), RuntimeError -`width needs a successful open()` before any `open()` or after one that -failed — a failed `open()` never leaves the previous image's size behind. - -**What is sniffed: nothing beyond SOI.** The stream goes to `jd_prepare`, -which scans for `FF D8` and then takes the segments in whatever order they -arrive. There is deliberately no JFIF/APP0 check: UVC MJPEG frames from a -webcam are `SOI → DQT → SOF0 → DHT → … → DRI → SOS`, not JFIF-first, and an -`is_jpg()`-style sniff rejects every one of them. Restart markers (DRI) are -handled by TJpgDec. - -### `decode(target, scale=0, x=0, y=0, *, stride=None) -> None` - -Decodes the image opened by the last `open()`. One-shot, like CP: the stream -is consumed, so `open()` again before the next `decode()` (RuntimeError -`decode() without open()` otherwise). - -- `scale` 0..3 — downscale by 1, 1/2, 1/4, 1/8 (TJpgDec's `JD_USE_SCALE`). - The decoded image is `(width >> scale) x (height >> scale)`; 1/8 is the - cheap one (DC-only, no IDCT). Other values: ValueError. -- `x`, `y` (0..65535, the range of the image size itself) — where the - decoded image's top-left corner lands, in pixels, in both target modes. - Anything else: ValueError. -- `target` — either a **buffer** or a **callable**. - -**Buffer target.** A writable buffer-protocol object (`bytearray`, -`memoryview`, a `framebuf.FrameBuffer`-compatible object) of native-order -RGB565 pixels, row-major. Rows are `stride` pixels apart (default: the -decoded width, i.e. a tight buffer of exactly -`(width >> scale) * (height >> scale) * 2` bytes). Before decoding, the -module checks `x + decoded_width <= stride` and that the last pixel written -fits in the buffer; otherwise ValueError naming the numbers, e.g. -`target too small: 320x240 at (0, 0) with stride 320 needs 153600 bytes, -buffer has 1024`. The fit is decided without computing -`(y + decoded_height - 1) * stride`, so a `stride` / `y` pair whose product -wraps `size_t` (32 bits on the MCU ports) is refused too (`... the last -pixel's offset overflows size_t`), never let through. Because the default -`stride` is the decoded width, `x > 0` always needs `stride` — the row -width of the target in pixels — and the error says so (`x + decoded width -(5 + 320) exceeds the default stride 320 (the decoded width): pass -stride=...`); `y > 0` needs only a buffer with enough rows. A rejected call -leaves the opened image in place, so `decode()` can be retried with a -better target without another `open()`. Each TJpgDec output block is -copied row by row into place. - -**Callable target.** Called once per TJpgDec output block, in raster order: - -```python -callback(x, y, w, h, mv) -``` - -`x, y` are the block's position (with `decode()`'s `x`, `y` added), `w, h` -its size, `mv` a read-only `memoryview('H')` of `w * h` native-order RGB565 -pixels — `len(mv) == w * h`, `mv[0]` is the first pixel as an int, and -`bytes(mv)` / any buffer-protocol consumer (`display_drv.blit_rect`) sees -`w * h * 2` bytes. `mv` views TJpgDec's own output buffer and is valid only -until the callback returns; a reference kept past that reads a zero-length -view (the module truncates it after each decode). One memoryview object is -created per decoder and reused for every block, so the callback path -allocates nothing per block. Blocks are MCU-sized (8x8, 16x8 or 16x16 pixels -before scaling) and clipped at the right/bottom edge; together they cover -the decoded image exactly once. An exception raised inside the callback -propagates out of `decode()`; the decoder closes its source and needs a new -`open()`. `stride` is not accepted with a callable (TypeError). - -**No byte swapping.** Pixels are native-order RGB565 (on little-endian -targets `0xF800` red is the bytes `00 F8`). Displays that want big-endian -RGB565 get it from `display_drv`, which byte-swaps itself; CP's -`RGB565_SWAPPED` output is CP's convention, not this module's. - -## Errors - -TJpgDec's `JRESULT` codes map to exceptions whose message starts with the -TJpgDec name, so a caller can tell them apart without module constants: - -| `JRESULT` | Exception | When | -|-----------|-----------|------| -| `JDR_INP` | `ValueError` | input ended before SOI, or the JPEG is truncated (an I/O error on a stream raises `OSError(errno)` instead) | -| `JDR_FMT1` | `ValueError` | unsupported or malformed JPEG: corrupt entropy data, a missing DQT/DHT — **this is what a DHT-less MJPEG frame produces** | -| `JDR_FMT2` | `ValueError` | right format but not supported (unused by R0.03) | -| `JDR_FMT3` | `ValueError` | not baseline: **progressive** (SOF2), lossless, arithmetic, CMYK, or 4:1:1 / 4:4:0 chroma subsampling | -| `JDR_MEM1` | `MemoryError` | the image's tables do not fit the 3500-byte work area | -| `JDR_MEM2` | `MemoryError` | a segment is larger than TJpgDec's 512-byte input buffer (`JD_SZBUF`) | -| `JDR_PAR` | `ValueError` | parameter error (scale is validated before TJpgDec sees it) | -| `JDR_INTR` | `RuntimeError` | output function interrupted the decode (the module's output functions never do) | - -A DHT-less frame (some cameras omit the standard Huffman tables and expect -the decoder to supply them) fails `open()` with `JDR_FMT1`; whether the -module should inject the default tables is an open decision recorded in the -vision doc, not something done silently here. - -## Differences from CircuitPython - -One, deliberate: `decode()` takes a buffer or a callable instead of a -`displayio.Bitmap`, and consequently has **no** `x1`/`y1`/`x2`/`y2` crop -window and no `skip_source_index` / `skip_dest_index`. Those are -`bitmaptools.blit` palette semantics that only make sense against a -`displayio.Bitmap`; the `stride` keyword and the callable target are the -replacements. Everything else — constructor, `open()` (positional-only, -str/buffer/stream), the `(width, height)` return, `scale` 0..3, `x`/`y`, -one-shot decode — follows CP's `shared-bindings/jpegio`. - -Also unlike CP, TJpgDec here emits native-order RGB565 (CP patches its copy -to byte-swap for `RGB565_SWAPPED`); see above. And errors are typed -(`ValueError` / `MemoryError` / `RuntimeError`) rather than CP's uniform -`RuntimeError`. - -## Build - -- `jpegio.c` — the module (`MP_REGISTER_MODULE(MP_QSTR_jpegio, ...)`). -- `tjpgd/` — vendored TJpgDec: `tjpgd.c`, `tjpgd.h`, `tjpgdcnf.h`. The - firmware's only TJpgDec, always compiled. -- `lvgl_decoder.c` / `lvgl_decoder.h` — the LVGL image decoder on that - TJpgDec; compiled only beside `lvgl-micropython` (below). -- `micropython.mk` / `micropython.cmake` — glue, included unconditionally - from displayif's root build files. - -`tjpgdcnf.h` is CircuitPython's, verbatim: `JD_SZBUF 512`, `JD_FORMAT 1` -(RGB565), `JD_USE_SCALE 1`, `JD_TBLCLIP 1`, `JD_FASTDECODE 1`. `jpegio.c` -refuses to build with any other `JD_FORMAT`. - -**With LVGL (D4 of the org's `docs/jpegio-vision.md`: displayif -self-detects).** LVGL's own TJPGD is off by config on MicroPython -(`LV_USE_TJPGD 0` in lvgl-bindings' `lv_conf.h`), so nothing clashes with -the vendored copy and LVGL has no JPEG decoder of its own; this module -supplies one. Make ports: when -`$(USER_C_MODULES)/lvgl-micropython/micropython.mk` exists — py.mk's own -usermod glob, one level under `USER_C_MODULES` — `micropython.mk` compiles -`lvgl_decoder.c` with `-DJPEGIO_LVGL_DECODER=1` and adds the bindings -checkout (`lv_conf.h`) and its `lvgl/` (`lvgl.h`) to the include path. -`JPEGIO_LVGL_BINDINGS_DIR` is derived the way `lvgl-micropython/micropython.mk` -derives `BINDINGS_DIR` (the sibling `lvgl-bindings`), honours a -`BINDINGS_DIR` given on the make command line, and can be overridden -itself; `JPEGIO_LVGL=0` / `1` forces the decision. CMake ports: the default -is the sibling `../lvgl-micropython/micropython.cmake` of this repo, or an -`lv_micropython` target an earlier entry of a semicolon-separated -`USER_C_MODULES` list already defined; `-DJPEGIO_LVGL=ON` plus -`-DJPEGIO_LVGL_BINDINGS_DIR=...` covers any other layout. Without the -sibling nothing links against LVGL — displayif's own clean-build CI is -LVGL-less and stays so — but the module still exports -`register_lvgl_decoder()` and `lvgl_decoders()`; see *Why these two names -are unconditional* below. - -## NOTICE — TJpgDec - -`tjpgd/tjpgd.c`, `tjpgd/tjpgd.h` and `tjpgd/tjpgdcnf.h` are ChaN's TJpgDec -R0.03 with patch1, taken from CircuitPython's `lib/tjpgd/src/` (which -imported it from http://elm-chan.org/fsw/tjpgd/00index.html), with two -deviations. First, the RGB565 store in `mcu_output` is ChaN's original -`*d++ = w;` where CircuitPython has `__builtin_bswap16(w)`. Second, the -`stdint.h` guard at the top of `tjpgd.h` tests for the header with -`__has_include` rather than for `_WIN32`: upstream's comment says it means -"a compiler without stdint.h", but mingw defines `_WIN32` *and* ships -`stdint.h`, where `uint32_t` is `unsigned int` rather than the fallback's -`unsigned long`, so every mingw cross-build failed with conflicting types -(displayif#25). The fallback typedefs are kept for the compiler the comment -was written for, MSVC before 2010. Both CircuitPython's copy and ChaN's -R0.03 still carry the platform test; LVGL's fork sidesteps it by including -`stdint.h` unconditionally. The files keep ChaN's license header: - -> Copyright (C) 2021, ChaN, all right reserved. -> -> The TJpgDec module is a free software and there is NO WARRANTY. -> No restriction on use. You can use, modify and redistribute it for -> personal, non-profit or commercial products UNDER YOUR RESPONSIBILITY. -> Redistributions of source code must retain the above copyright notice. - -`jpegio.c` is a port of CircuitPython's `shared-bindings/jpegio` and -`shared-module/jpegio` (Copyright (c) 2023 Jeff Epler for Adafruit -Industries, MIT). - -## LVGL image decoder - -The decoder itself (`lvgl_decoder.c`) is compiled only beside -`lvgl-micropython` (see Build); the two Python names below exist on every -build. It is an ordinary LVGL image decoder named `"jpegio"` -(`lv_image_decoder_t.name`), created with `lv_image_decoder_create`, so -`lv.image(...).set_src(...)` and every other LVGL image path decode -baseline JPEGs through this module's TJpgDec: - -```python -import lvgl as lv -import jpegio -lv.init() -jpegio.register_lvgl_decoder() # needed only if `import jpegio` ran before lv.init() - -with open("/sd/photo.jpg", "rb") as f: - jpeg = f.read() -dsc = lv.image_dsc_t({"header": {"w": 320, "h": 240, "cf": lv.COLOR_FORMAT.RAW}, - "data_size": len(jpeg), "data": jpeg}) -lv.image(lv.screen_active()).set_src(dsc) -``` - -**Registration.** Two ways, both idempotent — registering twice never adds -a second decoder: - -- On `import jpegio`, if LVGL is already initialised (`lv.init()` ran), the - module registers itself. MicroPython runs a built-in module's `__init__` - on every `import` statement (`MICROPY_MODULE_BUILTIN_INIT`, on by default - at the "extra features" ROM level: unix, esp32, rp2, stm32, mimxrt, - SAMD51; not on SAMD21's "basic" level). Upstream's windows variants sit at - the core level and leave it off; the PyDevices windows variant in - micropython-pydevices turns it on, so `micropython.exe` registers on import - like unix. On any other windows build, call `register_lvgl_decoder()`. -- `jpegio.register_lvgl_decoder() -> bool` registers explicitly: `True` - when this call added the decoder, `False` when it was already there, - `RuntimeError` when LVGL is not initialised. Use it when `import jpegio` - came before `lv.init()`, and after every `lv.deinit()` / `lv.init()` cycle - (LVGL clears its decoder list on deinit). On a build with no decoder in it - the call always raises `RuntimeError`, and the message names both the cause - and the way out: *no lvgl-micropython sibling at build time (rebuild with - `JPEGIO_LVGL=1` to force it)*. - -`jpegio.lvgl_decoders() -> tuple` lists LVGL's registered image decoders by -name in the order LVGL consults them — `("jpegio", "LODEPNG", "BIN")` once -registered (a new decoder goes to the head of LVGL's list), `()` before -`lv.init()`. A diagnostic: the binding cannot walk that list from Python -(MicroPython's builtin-method self check refuses -`lv.image_decoder_t.get_next(None)`), and it is how the test proves that -registering twice adds nothing. On a build with no decoder in it, `()` -always. - -**Why these two names are unconditional.** The general rule and the check that -enforces it are in [../../docs/qstrs-and-usermod-defines.md](../../docs/qstrs-and-usermod-defines.md); -this is where it was first found. They are module attributes on -every build, and only `lvgl_decoder.c` is conditional. Partly so the API -never silently disappears when the sibling scan misses — a decoder-less -build says so, in an exception, instead of raising `AttributeError` — and -partly because the CMake ports cannot compile them any other way. -MicroPython's `py/mkrules.cmake` builds the QSTR-extraction preprocessor -flags from the *port target's* own `COMPILE_DEFINITIONS` -(`get_target_property(MICROPY_CPP_DEF ${MICROPY_TARGET} COMPILE_DEFINITIONS)`), -so a usermod's `INTERFACE` define — which is how `micropython.cmake` -passes `JPEGIO_LVGL_DECODER=1` — never reaches `makeqstrdefs.py`. An -`MP_QSTR_*` referenced only inside -`#if JPEGIO_LVGL_DECODER` is therefore never collected on a CMake port, and -the build fails with `error: 'MP_QSTR_register_lvgl_decoder' undeclared`. -In MicroPython v1.28.0 that means esp32 and rp2, the only two ports with a -`CMakeLists.txt`; samd, mimxrt and stm32 carry CMake glue here for the day -they gain one, and build through their Makefile today. (The Makefile ports -put the define in `CFLAGS_USERMOD`, which `py/py.mk` folds into `CFLAGS`, -and their QSTR pass reads `CFLAGS`, which is why unix builds never showed -it.) `MP_QSTR___init__` survives inside the `#if` only because it is one of -MicroPython's static-pool qstrs (`py/makeqstrdata.py`), not because -anything here collects it, so any *new* `MP_QSTR_*` put under that `#if` -brings the break straight back. Do not re-conditionalise either function. - -**Sources.** A variable source (`lv.image_dsc_t`) whose `data` starts with -the SOI marker `FF D8`, or a file (through a registered `lv.fs_drv_t`, e.g. -lvgl-bindings' `fs_driver.py`) whose first two bytes are `FF D8` — the -extension is not consulted. Nothing more is sniffed: LVGL's own `is_jpg()` -wants a 10-byte JFIF signature and so rejects every UVC MJPEG frame; here -`jd_prepare` judges, for both source kinds, in the decoder's *info* step. -So the image size LVGL sees is the stream's own — the `w`/`h` in your -`lv.image_dsc_t` header are not echoed (LVGL's TJPGD echoed them) — and a -pixel buffer that merely starts `FF D8` fails `jd_prepare` and falls -through to LVGL's built-in decoder. `cf` in the header should be -`lv.COLOR_FORMAT.RAW` (or `RAW_ALPHA`), the convention for "not pixels"; -`data_size` must be the JPEG's length. - -**Output.** `LV_COLOR_FORMAT_RGB565`, native byte order, stride `w * 2`, -the same pixels `JpegDecoder.decode()` produces (the LVGL test asserts -jpegio's own golden digests through `lv.image`). LVGL's software renderer -blends RGB565 into an RGB565 display by row copy, its fast path. - -**Memory.** The decoder decodes the whole image in its *open* step into a -draw buffer of `w * h * 2` bytes (from LVGL's image-cache allocator, i.e. -the MicroPython heap), which LVGL frees at *close* — every refresh, unless -LVGL's image cache is on (`LV_CACHE_DEF_SIZE`, 0 in lvgl-bindings). That is -what LVGL's own full-decode decoders (`lv_lodepng.c`) do. It is not the -block-wise, no-frame-buffer path `JpegDecoder.decode(callable)` offers: -LVGL's `lv_tjpgd.c` decodes MCU by MCU only through exports it added to its -private TJpgDec (`jd_mcu_load`, `jd_mcu_output`, `jd_restart`), which ChaN's -R0.03 does not have, and `jd_decomp` is all-or-nothing. A camera preview -that must not hold a frame buffer uses `jpegio.JpegDecoder` and -`display_drv.blit_rect` directly. Scaling (`scale` 1..3) is a -`JpegDecoder` feature, not exposed to LVGL (its zoom is its own). - -**Errors.** A source the decoder does not recognise in its *info* step (no -`FF D8`, or `jd_prepare` refuses the headers: progressive, DHT-less, not a -JPEG at all) is not claimed (`LV_RESULT_INVALID`), so LVGL tries its other -decoders and, when none claims it, draws nothing. A source whose *info* -succeeded but whose *open* fails (a truncated scan, a decode error inside -the frame, no memory for the `w * h * 2` buffer) also draws nothing, but -LVGL does **not** try another decoder after a failed open — its -`lv_image_decoder.c` assumes a decoder that could read the info can open -the image — so the failure is final for that draw and logged at -`LV_LOG_WARN`. No Python exception is raised from inside LVGL's draw path. diff --git a/src/jpegio/jpegio.c b/src/jpegio/jpegio.c deleted file mode 100644 index 1a559c7..0000000 --- a/src/jpegio/jpegio.c +++ /dev/null @@ -1,543 +0,0 @@ -// SPDX-License-Identifier: MIT -// jpegio: CircuitPython-compatible baseline JPEG decoder (TJpgDec R0.03) as a -// MicroPython native module. -// -// Port of CircuitPython's shared-bindings/jpegio + shared-module/jpegio -// (Copyright (c) 2023 Jeff Epler for Adafruit Industries, MIT) with the -// platform's RGB565 buffer or a per-block callback in place of -// displayio.Bitmap. The API contract is in README.md next to this file. - -#include -#include - -#include "py/obj.h" -#include "py/objarray.h" -#include "py/runtime.h" -#include "displayif/build.h" -#include "py/stream.h" -#include "py/builtin.h" -#include "py/mperrno.h" - -#include "tjpgd.h" - -#if JD_FORMAT != 1 -#error "jpegio needs TJpgDec built with JD_FORMAT 1 (RGB565) -- see tjpgd/tjpgdcnf.h" -#endif - -// Defined by micropython.mk / micropython.cmake only when the lvgl-micropython -// sibling usermod is in the same build: jpegio then also registers an LVGL -// image decoder on its TJpgDec (lvgl_decoder.c). -#ifndef JPEGIO_LVGL_DECODER -#define JPEGIO_LVGL_DECODER 0 -#endif -#if JPEGIO_LVGL_DECODER -#include "lvgl_decoder.h" -#endif - -// CircuitPython's work-area size (shared-module/jpegio/JpegDecoder.h). Holds -// TJpgDec's 512-byte stream buffer, the quantiser and Huffman tables, and the -// MCU + IDCT scratch for a 16x16 (4:2:0) MCU. Allocated once, inside the object. -#define TJPGD_WORKSPACE_SIZE 3500 - -typedef struct _jpegio_jpegdecoder_obj_t { - mp_obj_base_t base; - JDEC decoder; - uint8_t workspace[TJPGD_WORKSPACE_SIZE]; - // Source handed to open(): a buffer-protocol object or a binary stream. - mp_obj_t source; // MP_OBJ_NULL once closed - mp_buffer_info_t bufinfo; // unread remainder of a buffer source - bool owns_source; // opened from a str path by us: close it when done - bool ready; // jd_prepare succeeded and decode() has not consumed it - uint16_t width, height; // of the last successfully opened image; 0 after a failed open() - // Target of the decode() in progress. Exactly one of callback / pixels is live. - mp_obj_t callback; // callable target, else MP_OBJ_NULL - mp_obj_t target; // buffer target object, kept alive while its pointer is used - uint16_t *pixels; // buffer target, native-order RGB565 - size_t stride; // buffer row stride in pixels - mp_int_t x, y; // placement of the decoded image - mp_obj_t block_view; // reusable memoryview handed to the callback per block -} jpegio_jpegdecoder_obj_t; - -#define JPEGIO_MIN(a, b) ((a) < (b) ? (a) : (b)) - -// decode()'s x / y limit: the image size is a uint16 in TJpgDec (and in -// CircuitPython's Bitmap), so a placement beyond it is never meaningful. It -// also keeps every coordinate sum below 2^17, so the buffer-fit arithmetic -// cannot overflow and the callback's coordinates always fit a small int. -#define JPEGIO_MAX_COORD 0xFFFF - -// --- TJpgDec result → exception -------------------------------------------- - -static void jpegio_raise_jresult(JRESULT rc) { - switch (rc) { - case JDR_OK: - return; - case JDR_INTR: - mp_raise_msg(&mp_type_RuntimeError, MP_ERROR_TEXT("JDR_INTR: interrupted by output function")); - case JDR_INP: - mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("JDR_INP: input ended early (no SOI marker, or truncated JPEG)")); - case JDR_MEM1: - mp_raise_msg_varg(&mp_type_MemoryError, MP_ERROR_TEXT("JDR_MEM1: image needs more than the %d-byte work area"), TJPGD_WORKSPACE_SIZE); - case JDR_MEM2: - mp_raise_msg_varg(&mp_type_MemoryError, MP_ERROR_TEXT("JDR_MEM2: a JPEG segment is larger than the %d-byte input buffer"), JD_SZBUF); - case JDR_PAR: - mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("JDR_PAR: parameter error")); - case JDR_FMT1: - mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("JDR_FMT1: unsupported or malformed JPEG (missing tables or corrupt data)")); - case JDR_FMT2: - mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("JDR_FMT2: right format but not supported")); - case JDR_FMT3: - default: - mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("JDR_FMT3: progressive JPEG is not supported (nor lossless, arithmetic, CMYK, or 4:1:1 / 4:4:0 subsampling)")); - } -} - -// --- source lifecycle ------------------------------------------------------- - -static void jpegio_close(jpegio_jpegdecoder_obj_t *self) { - if (self->owns_source && self->source != MP_OBJ_NULL) { - mp_stream_close(self->source); - } - self->owns_source = false; - self->source = MP_OBJ_NULL; - self->ready = false; - memset(&self->bufinfo, 0, sizeof(self->bufinfo)); -} - -// TJpgDec input function for a buffer-protocol source: hand out the next -// `len` bytes of the caller's buffer (no copy of the source itself; TJpgDec -// fills its own 512-byte stream buffer from it). NULL dest means skip. -static size_t jpegio_buffer_input(JDEC *jd, uint8_t *dest, size_t len) { - jpegio_jpegdecoder_obj_t *self = jd->device; - size_t n = JPEGIO_MIN(len, self->bufinfo.len); - if (dest != NULL) { - memcpy(dest, self->bufinfo.buf, n); - } - self->bufinfo.buf = (uint8_t *)self->bufinfo.buf + n; - self->bufinfo.len -= n; - return n; -} - -// TJpgDec input function for a native binary stream (file, io.BytesIO, -// socket, ...). NULL dest means skip: read it through a scratch buffer rather -// than seeking, so unseekable streams work. -static size_t jpegio_stream_input(JDEC *jd, uint8_t *dest, size_t len) { - jpegio_jpegdecoder_obj_t *self = jd->device; - if (dest == NULL) { - uint8_t skip[128]; - size_t total = 0; - while (total < len) { - size_t n = jpegio_stream_input(jd, skip, JPEGIO_MIN(len - total, sizeof(skip))); - if (n == 0) { - break; - } - total += n; - } - return total; - } - int errcode = 0; - mp_uint_t n = mp_stream_rw(self->source, dest, len, &errcode, MP_STREAM_RW_READ); - if (errcode != 0) { - // An I/O failure is more useful than the JDR_INP it would become. - mp_raise_OSError(errcode); - } - return n; -} - -// --- TJpgDec output functions ----------------------------------------------- - -// Buffer target: copy each block's rows into place. TJpgDec hands blocks in -// raster order, already clipped to the image edge, as w*h native-order RGB565. -static int jpegio_buffer_output(JDEC *jd, void *data, JRECT *rect) { - jpegio_jpegdecoder_obj_t *self = jd->device; - size_t w = (size_t)rect->right - rect->left + 1; - size_t h = (size_t)rect->bottom - rect->top + 1; - const uint16_t *src = data; - // decode() proved (y + dh - 1) * stride + x + dw <= the buffer's pixel - // count without overflowing, so every index formed here is inside it. - uint16_t *dst = self->pixels + ((size_t)self->y + rect->top) * self->stride + (size_t)self->x + rect->left; - for (size_t row = 0; row < h; row++) { - memcpy(dst + row * self->stride, src + row * w, w * sizeof(uint16_t)); - } - return 1; -} - -// Callable target: callback(x, y, w, h, block) with `block` a view of TJpgDec's -// own output buffer -- valid only until the callback returns. One view object -// is reused for every block, so the callback path allocates nothing per block. -static int jpegio_callback_output(JDEC *jd, void *data, JRECT *rect) { - jpegio_jpegdecoder_obj_t *self = jd->device; - size_t w = (size_t)rect->right - rect->left + 1; - size_t h = (size_t)rect->bottom - rect->top + 1; - mp_obj_array_t *view = MP_OBJ_TO_PTR(self->block_view); - view->items = data; - #if MICROPY_PY_BUILTINS_MEMORYVIEW - view->len = w * h; // memoryview('H'): one element per pixel - #else - view->len = w * h * 2; // bytearray fallback: length in bytes - #endif - mp_obj_t args[5] = { - MP_OBJ_NEW_SMALL_INT(self->x + rect->left), - MP_OBJ_NEW_SMALL_INT(self->y + rect->top), - MP_OBJ_NEW_SMALL_INT(w), - MP_OBJ_NEW_SMALL_INT(h), - self->block_view, - }; - mp_call_function_n_kw(self->callback, 5, 0, args); - return 1; -} - -static void jpegio_decode_done(jpegio_jpegdecoder_obj_t *self) { - if (self->block_view != MP_OBJ_NULL) { - // A reference the callback kept must not read the workspace later. - mp_obj_array_t *view = MP_OBJ_TO_PTR(self->block_view); - view->items = NULL; - view->len = 0; - } - self->callback = MP_OBJ_NULL; - self->target = MP_OBJ_NULL; - self->pixels = NULL; - jpegio_close(self); -} - -// --- JpegDecoder ------------------------------------------------------------ - -static mp_obj_t jpegio_jpegdecoder_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { - (void)all_args; - mp_arg_check_num(n_args, n_kw, 0, 0, false); - jpegio_jpegdecoder_obj_t *self = mp_obj_malloc(jpegio_jpegdecoder_obj_t, type); - memset(&self->decoder, 0, sizeof(self->decoder)); - self->source = MP_OBJ_NULL; - memset(&self->bufinfo, 0, sizeof(self->bufinfo)); - self->owns_source = false; - self->ready = false; - self->width = 0; - self->height = 0; - self->callback = MP_OBJ_NULL; - self->target = MP_OBJ_NULL; - self->pixels = NULL; - self->stride = 0; - self->x = 0; - self->y = 0; - self->block_view = MP_OBJ_NULL; - return MP_OBJ_FROM_PTR(self); -} - -static void jpegio_jpegdecoder_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { - (void)kind; - jpegio_jpegdecoder_obj_t *self = MP_OBJ_TO_PTR(self_in); - if (self->ready) { - mp_printf(print, "", self->width, self->height); - } else { - mp_printf(print, ""); - } -} - -// open(source) -> (width, height). Positional-only, like CircuitPython. -static mp_obj_t jpegio_jpegdecoder_open(mp_obj_t self_in, mp_obj_t source) { - jpegio_jpegdecoder_obj_t *self = MP_OBJ_TO_PTR(self_in); - jpegio_close(self); - // width / height track the last open(): a failed one reports nothing. - self->width = 0; - self->height = 0; - - bool owns = false; - if (mp_obj_is_str(source)) { - source = mp_call_function_2(MP_OBJ_FROM_PTR(&mp_builtin_open_obj), source, MP_OBJ_NEW_QSTR(MP_QSTR_rb)); - owns = true; - } - - size_t (*infunc)(JDEC *, uint8_t *, size_t); - if (mp_get_buffer(source, &self->bufinfo, MP_BUFFER_READ)) { - infunc = jpegio_buffer_input; - } else { - // Not mp_get_stream(): that reads the type's protocol slot unguarded, - // and a type without one (int, None, list, a Python class with a - // read() method) yields a wild pointer -- a segfault, not a TypeError. - const mp_stream_p_t *stream = MP_OBJ_TYPE_GET_SLOT_OR_NULL(mp_obj_get_type(source), protocol); - if (stream != NULL && stream->read != NULL && !stream->is_text) { - infunc = jpegio_stream_input; - } else { - mp_raise_TypeError(MP_ERROR_TEXT("source must be a str path, a bytes-like object, or a binary stream")); - } - } - self->source = source; - self->owns_source = owns; - - // Nothing is sniffed here: jd_prepare scans for SOI itself and then takes - // the segments in whatever order the stream carries them (UVC MJPEG frames - // are not JFIF-first). A stream error inside it is raised by the input - // function; close the source on that path too. - JRESULT rc = JDR_OK; - nlr_buf_t nlr; - if (nlr_push(&nlr) == 0) { - rc = jd_prepare(&self->decoder, infunc, self->workspace, sizeof(self->workspace), self); - nlr_pop(); - } else { - jpegio_close(self); - nlr_jump(nlr.ret_val); - } - if (rc != JDR_OK) { - jpegio_close(self); - jpegio_raise_jresult(rc); - } - self->ready = true; - self->width = self->decoder.width; - self->height = self->decoder.height; - - mp_obj_t elems[] = { - MP_OBJ_NEW_SMALL_INT(self->width), - MP_OBJ_NEW_SMALL_INT(self->height), - }; - return mp_obj_new_tuple(MP_ARRAY_SIZE(elems), elems); -} -static MP_DEFINE_CONST_FUN_OBJ_2(jpegio_jpegdecoder_open_obj, jpegio_jpegdecoder_open); - -// decode(target, scale=0, x=0, y=0, *, stride=None) -> None -static mp_obj_t jpegio_jpegdecoder_decode(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { - jpegio_jpegdecoder_obj_t *self = MP_OBJ_TO_PTR(pos_args[0]); - - enum { ARG_target, ARG_scale, ARG_x, ARG_y, ARG_stride }; - static const mp_arg_t allowed_args[] = { - { MP_QSTR_target, MP_ARG_OBJ | MP_ARG_REQUIRED, {.u_obj = MP_OBJ_NULL} }, - { MP_QSTR_scale, MP_ARG_INT, {.u_int = 0} }, - { MP_QSTR_x, MP_ARG_INT, {.u_int = 0} }, - { MP_QSTR_y, MP_ARG_INT, {.u_int = 0} }, - { MP_QSTR_stride, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} }, - }; - mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; - mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); - - if (!self->ready) { - mp_raise_msg_varg(&mp_type_RuntimeError, MP_ERROR_TEXT("%q() without %q()"), MP_QSTR_decode, MP_QSTR_open); - } - mp_int_t scale = args[ARG_scale].u_int; - if (scale < 0 || scale > 3) { - mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("%q must be 0..3, not %d"), MP_QSTR_scale, (int)scale); - } - mp_int_t x = args[ARG_x].u_int; - mp_int_t y = args[ARG_y].u_int; - if (x < 0 || x > JPEGIO_MAX_COORD || y < 0 || y > JPEGIO_MAX_COORD) { - mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("x, y must be 0..%d, not (%ld, %ld)"), JPEGIO_MAX_COORD, (long)x, (long)y); - } - size_t dw = self->width >> scale; - size_t dh = self->height >> scale; - - mp_obj_t target = args[ARG_target].u_obj; - int (*outfunc)(JDEC *, void *, JRECT *); - if (mp_obj_is_callable(target)) { - if (args[ARG_stride].u_obj != mp_const_none) { - mp_raise_TypeError(MP_ERROR_TEXT("stride applies to buffer targets only")); - } - if (self->block_view == MP_OBJ_NULL) { - #if MICROPY_PY_BUILTINS_MEMORYVIEW - self->block_view = mp_obj_new_memoryview('H', 0, NULL); - #else - self->block_view = mp_obj_new_bytearray_by_ref(0, NULL); - #endif - } - self->callback = target; - self->target = MP_OBJ_NULL; - self->pixels = NULL; - self->stride = 0; - outfunc = jpegio_callback_output; - } else { - mp_buffer_info_t buf; - mp_get_buffer_raise(target, &buf, MP_BUFFER_WRITE); - bool stride_given = args[ARG_stride].u_obj != mp_const_none; - size_t stride = dw; - if (stride_given) { - mp_int_t s = mp_obj_get_int(args[ARG_stride].u_obj); - if (s < 0) { - mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("%q must be >= 0, not %ld"), MP_QSTR_stride, (long)s); - } - stride = s; - } - if ((size_t)x + dw > stride) { - if (stride_given) { - mp_raise_msg_varg(&mp_type_ValueError, - MP_ERROR_TEXT("x + decoded width (%d + %d) exceeds stride %lu"), - (int)x, (int)dw, (unsigned long)stride); - } - // The default stride is the decoded width: x > 0 can never fit it. - mp_raise_msg_varg(&mp_type_ValueError, - MP_ERROR_TEXT("x + decoded width (%d + %d) exceeds the default stride %d (the decoded width): pass stride= to place at x > 0"), - (int)x, (int)dw, (int)stride); - } - // Buffer fit. The last pixel written is at index last_row * stride + - // x + dw - 1, so the buffer needs last_row * stride + tail pixels. - // x, y, dw, dh are all < 2^16, so the sums cannot overflow; the - // product can (stride is any mp_int_t, and size_t is 32 bits on the - // MCU ports), so it is compared by division instead of computed -- - // a wrapped product must not slip under buf.len. - if (dw != 0 && dh != 0) { - size_t npix = buf.len / sizeof(uint16_t); - size_t tail = (size_t)x + dw; - size_t last_row = (size_t)y + dh - 1; - bool fits = tail <= npix && (last_row == 0 || stride <= (npix - tail) / last_row); - if (!fits) { - if (last_row == 0 || stride <= (SIZE_MAX / 2 - tail) / last_row) { - size_t need = (last_row * stride + tail) * sizeof(uint16_t); - mp_raise_msg_varg(&mp_type_ValueError, - MP_ERROR_TEXT("target too small: %dx%d at (%d, %d) with stride %lu needs %lu bytes, buffer has %lu"), - (int)dw, (int)dh, (int)x, (int)y, (unsigned long)stride, (unsigned long)need, (unsigned long)buf.len); - } - mp_raise_msg_varg(&mp_type_ValueError, - MP_ERROR_TEXT("target too small: %dx%d at (%d, %d) with stride %lu: the last pixel's offset overflows size_t; buffer has %lu bytes"), - (int)dw, (int)dh, (int)x, (int)y, (unsigned long)stride, (unsigned long)buf.len); - } - } - self->callback = MP_OBJ_NULL; - self->target = target; - self->pixels = buf.buf; - self->stride = stride; - outfunc = jpegio_buffer_output; - } - self->x = x; - self->y = y; - - // One-shot, like CircuitPython: the stream is consumed, so open() again - // before the next decode(). - self->ready = false; - JRESULT rc = JDR_OK; - nlr_buf_t nlr; - if (nlr_push(&nlr) == 0) { - rc = jd_decomp(&self->decoder, outfunc, (uint8_t)scale); - nlr_pop(); - } else { - jpegio_decode_done(self); - nlr_jump(nlr.ret_val); - } - jpegio_decode_done(self); - if (rc != JDR_OK && rc != JDR_INTR) { - jpegio_raise_jresult(rc); - } - return mp_const_none; -} -static MP_DEFINE_CONST_FUN_OBJ_KW(jpegio_jpegdecoder_decode_obj, 1, jpegio_jpegdecoder_decode); - -// width / height: read-only; track the last open() (valid after a successful -// one, through its decode(), until the next open(); 0 after a failed one). -static void jpegio_jpegdecoder_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) { - if (dest[0] != MP_OBJ_NULL) { - return; // store / delete: not supported (leaves dest[0] set -> AttributeError) - } - if (attr == MP_QSTR_width || attr == MP_QSTR_height) { - jpegio_jpegdecoder_obj_t *self = MP_OBJ_TO_PTR(self_in); - if (self->width == 0 && self->height == 0) { - mp_raise_msg_varg(&mp_type_RuntimeError, MP_ERROR_TEXT("%q needs a successful %q()"), attr, MP_QSTR_open); - } - dest[0] = MP_OBJ_NEW_SMALL_INT(attr == MP_QSTR_width ? self->width : self->height); - return; - } - dest[1] = MP_OBJ_SENTINEL; // continue the lookup in locals_dict (open, decode) -} - -static const mp_rom_map_elem_t jpegio_jpegdecoder_locals_dict_table[] = { - { MP_ROM_QSTR(MP_QSTR_open), MP_ROM_PTR(&jpegio_jpegdecoder_open_obj) }, - { MP_ROM_QSTR(MP_QSTR_decode), MP_ROM_PTR(&jpegio_jpegdecoder_decode_obj) }, -}; -static MP_DEFINE_CONST_DICT(jpegio_jpegdecoder_locals_dict, jpegio_jpegdecoder_locals_dict_table); - -static MP_DEFINE_CONST_OBJ_TYPE( - jpegio_jpegdecoder_type, - MP_QSTR_JpegDecoder, - MP_TYPE_FLAG_NONE, - make_new, jpegio_jpegdecoder_make_new, - print, jpegio_jpegdecoder_print, - attr, jpegio_jpegdecoder_attr, - locals_dict, &jpegio_jpegdecoder_locals_dict - ); - -// --- LVGL image decoder ------------------------------------------------------ -// -// register_lvgl_decoder() and lvgl_decoders() are module attributes on EVERY -// build; only the decoder itself (lvgl_decoder.c) is conditional. Two reasons: -// the API must not silently vanish when the sibling scan misses, and on the -// CMake ports (esp32, rp2, samd, mimxrt, stm32) a QSTR referenced only inside -// `#if JPEGIO_LVGL_DECODER` is never collected -- py/mkrules.cmake builds the -// QSTR preprocessor flags from the port target's own COMPILE_DEFINITIONS, so a -// usermod's INTERFACE define never reaches makeqstrdefs.py and the build fails -// with `MP_QSTR_register_lvgl_decoder undeclared`. See README.md. - -// register_lvgl_decoder() -> bool: register jpegio's decoder with the running -// LVGL. True when this call added it, False when it was already registered -// (a second registration never adds a second decoder). RuntimeError when -// LVGL is not initialised: call lv.init() first. Needed after `import jpegio` -// ran before lv.init(), and after every lv.deinit() / lv.init() cycle (LVGL -// clears its decoder list on deinit). Without the decoder compiled in -// (JPEGIO_LVGL_DECODER 0) it always raises RuntimeError, naming why. -static mp_obj_t jpegio_register_lvgl_decoder(void) { - #if JPEGIO_LVGL_DECODER - if (jpegio_lvgl_decoder_registered()) { - return mp_const_false; - } - if (!jpegio_lvgl_decoder_register()) { - mp_raise_msg(&mp_type_RuntimeError, MP_ERROR_TEXT("register_lvgl_decoder() needs an initialised LVGL: lv.init() first")); - } - return mp_const_true; - #else - mp_raise_msg(&mp_type_RuntimeError, MP_ERROR_TEXT("jpegio was built without its LVGL decoder: no lvgl-micropython sibling at build time (rebuild with JPEGIO_LVGL=1 to force it)")); - #endif -} -static MP_DEFINE_CONST_FUN_OBJ_0(jpegio_register_lvgl_decoder_obj, jpegio_register_lvgl_decoder); - -// lvgl_decoders() -> tuple of str: the names of LVGL's registered image -// decoders in the order LVGL consults them (`("jpegio", "LODEPNG", "BIN")` -// once registered); empty before lv.init(). A diagnostic: MicroPython's -// builtin-method self check keeps `lv.image_decoder_t.get_next(None)` from -// walking the list in Python, and this is how the test proves a second -// registration added nothing. Without the decoder compiled in -// (JPEGIO_LVGL_DECODER 0) it is always the empty tuple. -static mp_obj_t jpegio_lvgl_decoders(void) { - #if JPEGIO_LVGL_DECODER - size_t n = 0; - while (jpegio_lvgl_decoder_name_at(n) != NULL) { - n++; - } - mp_obj_tuple_t *names = MP_OBJ_TO_PTR(mp_obj_new_tuple(n, NULL)); - for (size_t i = 0; i < n; i++) { - const char *name = jpegio_lvgl_decoder_name_at(i); - names->items[i] = mp_obj_new_str(name, strlen(name)); - } - return MP_OBJ_FROM_PTR(names); - #else - return mp_const_empty_tuple; - #endif -} -static MP_DEFINE_CONST_FUN_OBJ_0(jpegio_lvgl_decoders_obj, jpegio_lvgl_decoders); - -#if JPEGIO_LVGL_DECODER -// Module __init__: MicroPython calls it on `import jpegio` (every import -// statement, on ports with MICROPY_MODULE_BUILTIN_INIT). If LVGL is already -// initialised, register now, so `import lvgl; lv.init(); import jpegio` -// needs nothing more. Idempotent, and silent when LVGL is not up yet. -static mp_obj_t jpegio___init__(void) { - jpegio_lvgl_decoder_register(); // no-op (false) until lv.init() has run - return mp_const_none; -} -static MP_DEFINE_CONST_FUN_OBJ_0(jpegio___init___obj, jpegio___init__); -#endif - -// --- module ----------------------------------------------------------------- - -static const mp_rom_map_elem_t jpegio_module_globals_table[] = { - { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_jpegio) }, - DISPLAYIF_REVISION_ENTRY, - { MP_ROM_QSTR(MP_QSTR_JpegDecoder), MP_ROM_PTR(&jpegio_jpegdecoder_type) }, - #if JPEGIO_LVGL_DECODER - { MP_ROM_QSTR(MP_QSTR___init__), MP_ROM_PTR(&jpegio___init___obj) }, - #endif - // Unconditional: see the LVGL section above (the API stays, and the CMake - // ports' QSTR pass cannot see a name that lives only inside the #if). - { MP_ROM_QSTR(MP_QSTR_register_lvgl_decoder), MP_ROM_PTR(&jpegio_register_lvgl_decoder_obj) }, - { MP_ROM_QSTR(MP_QSTR_lvgl_decoders), MP_ROM_PTR(&jpegio_lvgl_decoders_obj) }, -}; -static MP_DEFINE_CONST_DICT(jpegio_module_globals, jpegio_module_globals_table); - -const mp_obj_module_t jpegio_module = { - .base = { &mp_type_module }, - .globals = (mp_obj_dict_t *)&jpegio_module_globals, -}; - -MP_REGISTER_MODULE(MP_QSTR_jpegio, jpegio_module); diff --git a/src/jpegio/lvgl_decoder.c b/src/jpegio/lvgl_decoder.c deleted file mode 100644 index d72406c..0000000 --- a/src/jpegio/lvgl_decoder.c +++ /dev/null @@ -1,304 +0,0 @@ -// SPDX-License-Identifier: MIT -// jpegio's LVGL image decoder: baseline JPEG through jpegio's own TJpgDec, -// registered with LVGL's public lv_image_decoder_create API. Phase 2 of the -// org's docs/jpegio-vision.md: LVGL's built-in TJPGD is off (LV_USE_TJPGD 0 -// in lvgl-bindings' lv_conf.h on MicroPython), so a firmware with both -// usermods carries one TJpgDec -- jpegio's, vendored in tjpgd/ -- and this -// file is that object code's second user. -// -// Structure follows lvgl/src/libs/tjpgd/lv_tjpgd.c (read, never modified) -// for registration and the info/open/close split. The pixel path does not: -// lv_tjpgd.c decodes MCU by MCU from get_area_cb through LVGL-only exports -// (jd_mcu_load, jd_mcu_output, jd_restart, JDEC.rst/rsc/pool_original) that -// ChaN's R0.03 -- the copy jpegio ships -- does not have, and it hardcodes -// RGB888 (header.cf/stride at its lines 95-98, 117-120, 207-210, 264). This -// decoder does what LVGL's own full-decode decoders do (lv_lodepng.c at -// v9.5.0): open() decodes the whole image once with jd_decomp into a draw -// buffer from LVGL's image-cache handlers, hands it back as dsc->decoded, -// and close() destroys it unless the image cache kept it. No get_area_cb: -// LVGL only calls that when open() returns no pixels, and ChaN's jd_decomp -// is all-or-nothing, so an incremental path would re-decode the frame per -// area. Output is native-order RGB565 (JD_FORMAT 1 in tjpgd/tjpgdcnf.h, -// LV_COLOR_16_SWAP unset), labelled LV_COLOR_FORMAT_RGB565, stride w * 2. -// -// Source sniff is SOI only (FF D8), for a variable source's first two bytes -// or a file's -- UVC MJPEG frames are not JFIF-first and lv_tjpgd.c's -// 10-byte JFIF signature rejects every one of them. jd_prepare then judges: -// decoder_info parses the headers for both source kinds, so width/height are -// the stream's (not echoed from the caller's lv_image_dsc_t header) and a -// pixel buffer that happens to start FF D8 falls through to LVGL's built-in -// decoder instead of being claimed. - -#include "lvgl/lvgl.h" -#include "lvgl/src/draw/lv_image_decoder_private.h" // lv_image_decoder_t.info_cb, lv_image_cache_data_t -#include "lvgl/src/core/lv_global.h" // LV_GLOBAL_DEFAULT()->image_cache_draw_buf_handlers - -#include - -#include "tjpgd.h" -#include "lvgl_decoder.h" - -#if JD_FORMAT != 1 -#error "jpegio's LVGL decoder labels its output RGB565: TJpgDec must be built with JD_FORMAT 1" -#endif -#if defined(LV_COLOR_16_SWAP) && LV_COLOR_16_SWAP /* -Wundef clean: lv_conf.h does not define it */ -#error "jpegio's LVGL decoder emits native-order RGB565; LV_COLOR_16_SWAP is not supported" -#endif - -// CircuitPython's TJpgDec work-area size, the same 3500 bytes jpegio.c gives -// its JpegDecoder (TJPGD_WORKSPACE_SIZE there). Enough for the 512-byte -// stream buffer, the tables, and a 16x16 MCU; allocated per open, freed -// before it returns. -#define JPEGIO_LVGL_WORKSPACE_SIZE 3500 - -#define image_cache_draw_buf_handlers &(LV_GLOBAL_DEFAULT()->image_cache_draw_buf_handlers) - -// One decode session: the input TJpgDec pulls from and the RGB565 rows it -// pushes to. Exactly one of buf / file is live. -typedef struct { - const uint8_t *buf; // variable source: unread remainder - size_t len; - lv_fs_file_t *file; // file source, else NULL - uint8_t *dst; // decoded->data (NULL while only preparing) - uint32_t stride; // decoded->header.stride, bytes -} jpegio_lvgl_session_t; - -static lv_result_t decoder_info(lv_image_decoder_t *decoder, lv_image_decoder_dsc_t *dsc, lv_image_header_t *header); -static lv_result_t decoder_open(lv_image_decoder_t *decoder, lv_image_decoder_dsc_t *dsc); -static void decoder_close(lv_image_decoder_t *decoder, lv_image_decoder_dsc_t *dsc); - -// --- registration ----------------------------------------------------------- - -static lv_image_decoder_t *find_registered(void) { - lv_image_decoder_t *dec = NULL; - while ((dec = lv_image_decoder_get_next(dec)) != NULL) { - if (dec->info_cb == decoder_info) { - return dec; - } - } - return NULL; -} - -bool jpegio_lvgl_decoder_registered(void) { - return lv_is_initialized() && find_registered() != NULL; -} - -const char *jpegio_lvgl_decoder_name_at(size_t i) { - if (!lv_is_initialized()) { - return NULL; - } - lv_image_decoder_t *dec = NULL; - while ((dec = lv_image_decoder_get_next(dec)) != NULL) { - if (i-- == 0) { - return dec->name != NULL ? dec->name : ""; - } - } - return NULL; -} - -bool jpegio_lvgl_decoder_register(void) { - if (!lv_is_initialized()) { - return false; - } - if (find_registered() != NULL) { - return true; // registering twice adds nothing - } - // lv_image_decoder_create inserts at the head of LVGL's list, so this - // decoder is consulted before the built-in one (which claims any - // variable source with a known cf) -- same position lv_tjpgd_init took. - lv_image_decoder_t *dec = lv_image_decoder_create(); - if (dec == NULL) { - return false; - } - lv_image_decoder_set_info_cb(dec, decoder_info); - lv_image_decoder_set_open_cb(dec, decoder_open); - lv_image_decoder_set_close_cb(dec, decoder_close); - dec->name = JPEGIO_LVGL_DECODER_NAME; - return true; -} - -// --- TJpgDec input / output ------------------------------------------------- - -static size_t input_func(JDEC *jd, uint8_t *dest, size_t len) { - jpegio_lvgl_session_t *s = jd->device; - if (s->file != NULL) { - if (dest != NULL) { - uint32_t rn = 0; - if (lv_fs_read(s->file, dest, (uint32_t)len, &rn) != LV_FS_RES_OK) { - return 0; - } - return rn; - } - uint32_t pos = 0; - lv_fs_tell(s->file, &pos); - lv_fs_seek(s->file, (uint32_t)(pos + len), LV_FS_SEEK_SET); - return len; - } - size_t n = len < s->len ? len : s->len; - if (dest != NULL) { - memcpy(dest, s->buf, n); - } - s->buf += n; - s->len -= n; - return n; -} - -// TJpgDec hands MCU blocks in raster order, clipped to the image edge, as -// w*h native-order RGB565: copy each row into the draw buffer at its place. -static int output_func(JDEC *jd, void *bitmap, JRECT *rect) { - jpegio_lvgl_session_t *s = jd->device; - size_t w = (size_t)rect->right - rect->left + 1; - size_t h = (size_t)rect->bottom - rect->top + 1; - const uint8_t *src = bitmap; - uint8_t *dst = s->dst + (size_t)rect->top * s->stride + (size_t)rect->left * 2; - for (size_t row = 0; row < h; row++) { - memcpy(dst + row * s->stride, src + row * w * 2, w * 2); - } - return 1; -} - -static bool starts_with_soi(const uint8_t *p, size_t n) { - return n >= 2 && p[0] == 0xFF && p[1] == 0xD8; -} - -// Point the session at dsc's source. For a file the caller supplies the open -// handle (decoder_info gets dsc->file from LVGL, decoder_open opens its own). -static bool session_from_source(jpegio_lvgl_session_t *s, const lv_image_decoder_dsc_t *dsc, lv_fs_file_t *file) { - memset(s, 0, sizeof(*s)); - if (dsc->src_type == LV_IMAGE_SRC_VARIABLE) { - const lv_image_dsc_t *img = dsc->src; - if (!starts_with_soi(img->data, img->data_size)) { - return false; - } - s->buf = img->data; - s->len = img->data_size; - return true; - } - if (dsc->src_type == LV_IMAGE_SRC_FILE && file != NULL) { - uint8_t soi[2]; - uint32_t rn = 0; - if (lv_fs_seek(file, 0, LV_FS_SEEK_SET) != LV_FS_RES_OK - || lv_fs_read(file, soi, sizeof(soi), &rn) != LV_FS_RES_OK - || !starts_with_soi(soi, rn) - || lv_fs_seek(file, 0, LV_FS_SEEK_SET) != LV_FS_RES_OK) { - return false; - } - s->file = file; - return true; - } - return false; -} - -// --- LVGL decoder callbacks ------------------------------------------------- - -static lv_result_t decoder_info(lv_image_decoder_t *decoder, lv_image_decoder_dsc_t *dsc, lv_image_header_t *header) { - LV_UNUSED(decoder); - jpegio_lvgl_session_t s; - // LVGL opened dsc->file for a file source and seeks it to 0 before each - // decoder's info_cb; it closes it afterwards. - if (!session_from_source(&s, dsc, dsc->src_type == LV_IMAGE_SRC_FILE ? &dsc->file : NULL)) { - return LV_RESULT_INVALID; - } - uint8_t *work = lv_malloc(JPEGIO_LVGL_WORKSPACE_SIZE); - if (work == NULL) { - return LV_RESULT_INVALID; - } - JDEC jd; - JRESULT rc = jd_prepare(&jd, input_func, work, JPEGIO_LVGL_WORKSPACE_SIZE, &s); - lv_free(work); - if (rc != JDR_OK) { - LV_LOG_INFO("jpegio: jd_prepare error %d", rc); - return LV_RESULT_INVALID; - } - header->cf = LV_COLOR_FORMAT_RGB565; - header->w = jd.width; - header->h = jd.height; - header->stride = (uint32_t)jd.width * 2; - return LV_RESULT_OK; -} - -static lv_result_t decoder_open(lv_image_decoder_t *decoder, lv_image_decoder_dsc_t *dsc) { - lv_fs_file_t *f = NULL; - if (dsc->src_type == LV_IMAGE_SRC_FILE) { - f = lv_malloc(sizeof(lv_fs_file_t)); - if (f == NULL) { - return LV_RESULT_INVALID; - } - if (lv_fs_open(f, dsc->src, LV_FS_MODE_RD) != LV_FS_RES_OK) { - lv_free(f); - return LV_RESULT_INVALID; - } - } - jpegio_lvgl_session_t s; - lv_draw_buf_t *decoded = NULL; - uint8_t *work = NULL; - JRESULT rc = JDR_FMT1; - - if (session_from_source(&s, dsc, f)) { - work = lv_malloc(JPEGIO_LVGL_WORKSPACE_SIZE); - if (work != NULL) { - JDEC jd; - rc = jd_prepare(&jd, input_func, work, JPEGIO_LVGL_WORKSPACE_SIZE, &s); - if (rc == JDR_OK) { - decoded = lv_draw_buf_create_ex(image_cache_draw_buf_handlers, jd.width, jd.height, - LV_COLOR_FORMAT_RGB565, LV_STRIDE_AUTO); - if (decoded != NULL) { - s.dst = decoded->data; - s.stride = decoded->header.stride; - rc = jd_decomp(&jd, output_func, 0); - } else { - rc = JDR_MEM1; - } - } - lv_free(work); - } else { - rc = JDR_MEM1; - } - } - if (f != NULL) { - lv_fs_close(f); - lv_free(f); - } - if (rc != JDR_OK) { - LV_LOG_WARN("jpegio: decode error %d", rc); - if (decoded != NULL) { - lv_draw_buf_destroy(decoded); - } - return LV_RESULT_INVALID; - } - - // From here the same as lv_lodepng.c's decoder_open at v9.5.0. - lv_draw_buf_t *adjusted = lv_image_decoder_post_process(dsc, decoded); - if (adjusted == NULL) { - lv_draw_buf_destroy(decoded); - return LV_RESULT_INVALID; - } - if (adjusted != decoded) { - lv_draw_buf_destroy(decoded); - decoded = adjusted; - } - dsc->decoded = decoded; - - if (dsc->args.no_cache || !lv_image_cache_is_enabled()) { - return LV_RESULT_OK; - } - lv_image_cache_data_t search_key; - search_key.src_type = dsc->src_type; - search_key.src = dsc->src; - search_key.slot.size = decoded->data_size; - lv_cache_entry_t *entry = lv_image_decoder_add_to_cache(decoder, &search_key, decoded, NULL); - if (entry == NULL) { - lv_draw_buf_destroy(decoded); - dsc->decoded = NULL; - return LV_RESULT_INVALID; - } - dsc->cache_entry = entry; - return LV_RESULT_OK; -} - -static void decoder_close(lv_image_decoder_t *decoder, lv_image_decoder_dsc_t *dsc) { - LV_UNUSED(decoder); - if (dsc->args.no_cache || !lv_image_cache_is_enabled()) { - lv_draw_buf_destroy((lv_draw_buf_t *)dsc->decoded); - } -} diff --git a/src/jpegio/lvgl_decoder.h b/src/jpegio/lvgl_decoder.h deleted file mode 100644 index 73f4d6e..0000000 --- a/src/jpegio/lvgl_decoder.h +++ /dev/null @@ -1,27 +0,0 @@ -// SPDX-License-Identifier: MIT -// jpegio's LVGL image decoder (lvgl_decoder.c). Compiled only when the -// lvgl-micropython sibling usermod is on the same build (JPEGIO_LVGL_DECODER). -#ifndef DISPLAYIF_JPEGIO_LVGL_DECODER_H -#define DISPLAYIF_JPEGIO_LVGL_DECODER_H - -#include -#include - -// The name LVGL reports for it (lv_image_decoder_t.name). -#define JPEGIO_LVGL_DECODER_NAME "jpegio" - -// Register jpegio's decoder with the running LVGL (lv_image_decoder_create). -// Idempotent: a second call finds the first registration and adds nothing. -// Returns true when the decoder is registered after the call, false when LVGL -// is not initialised (lv_init() first) or the registration allocation failed. -bool jpegio_lvgl_decoder_register(void); - -// True when LVGL is initialised and jpegio's decoder is in its decoder list. -bool jpegio_lvgl_decoder_registered(void); - -// Name of the i-th decoder in LVGL's list (the order LVGL consults them), -// NULL past the end or when LVGL is not initialised. Diagnostic for -// jpegio.lvgl_decoders(): the binding cannot walk the list from Python. -const char *jpegio_lvgl_decoder_name_at(size_t i); - -#endif diff --git a/src/jpegio/micropython.cmake b/src/jpegio/micropython.cmake deleted file mode 100644 index 7c665f7..0000000 --- a/src/jpegio/micropython.cmake +++ /dev/null @@ -1,46 +0,0 @@ -# jpegio CMake glue (see micropython.mk for what the variables mean). - -set(JPEGIO_DIR ${DISPLAYIF_MOD_DIR}/src/jpegio) - -add_library(displayif_jpegio INTERFACE) -target_include_directories(displayif_jpegio INTERFACE - ${JPEGIO_DIR}/tjpgd -) -target_link_libraries(usermod INTERFACE displayif_jpegio) - -# The vendored TJpgDec is the firmware's only one (LVGL's is off by config). -target_sources(displayif_jpegio INTERFACE - ${JPEGIO_DIR}/jpegio.c - ${JPEGIO_DIR}/tjpgd/tjpgd.c -) - -# LVGL image decoder on this TJpgDec, only beside the lvgl-micropython usermod -# (D4: displayif self-detects). CMake has no sibling scan: the default is a -# lvgl-micropython/micropython.cmake in the directory this repo sits in (the -# textual parent, the same "workspace" py.mk globs -- not resolved through a -# symlink), or an lv_micropython target already defined by an lvgl-micropython -# listed earlier in a semicolon-separated USER_C_MODULES. Pass -# -DJPEGIO_LVGL=ON/OFF to decide by hand (the list form with lvgl-micropython -# elsewhere needs ON plus JPEGIO_LVGL_BINDINGS_DIR). -# The LVGL image decoder (lvgl_decoder.c) is built only when lvgl-micropython is -# in this build, decided from USER_C_MODULES the way micropython.mk decides it: -# what is on disk beside displayif says nothing about what this build selected, -# and jpegio has to build without LVGL. Override with -DJPEGIO_LVGL=ON/OFF. -# The decoder includes "lvgl/lvgl.h" and "lvgl/src/...", which resolve through -# the bindings directory lvgl-micropython itself puts on the include path, so -# nothing here needs to know where the bindings are. -if(NOT DEFINED JPEGIO_LVGL) - if("${USER_C_MODULES}" MATCHES "lvgl-micropython") - set(JPEGIO_LVGL ON) - else() - set(JPEGIO_LVGL OFF) - endif() -endif() - -if(JPEGIO_LVGL) - target_compile_definitions(displayif_jpegio INTERFACE JPEGIO_LVGL_DECODER=1) - # No qstrs of its own (the Python-visible names live in jpegio.c). - target_sources(displayif_jpegio INTERFACE - ${JPEGIO_DIR}/lvgl_decoder.c - ) -endif() diff --git a/src/jpegio/micropython.mk b/src/jpegio/micropython.mk deleted file mode 100644 index 68e1ca1..0000000 --- a/src/jpegio/micropython.mk +++ /dev/null @@ -1,40 +0,0 @@ -# jpegio — CircuitPython-compatible JPEG decoder (TJpgDec R0.03, CP's config). -# Platform-neutral C: included from the root micropython.mk on every port. -# -# The vendored tjpgd/tjpgd.c is the firmware's only TJpgDec. LVGL's own copy -# is off by config (LV_USE_TJPGD 0 in lvgl-bindings' lv_conf.h on MicroPython), -# so when the lvgl-micropython sibling is on the same USER_C_MODULES scan path -# jpegio also compiles lvgl_decoder.c: an LVGL image decoder on this TJpgDec, -# registered through LVGL's public lv_image_decoder_create API (Phase 2 of the -# org's docs/jpegio-vision.md, decision D4: displayif self-detects). Without -# the sibling nothing here changes and the build stays LVGL-less. - -JPEGIO_DIR := $(DISPLAYIF_MOD_DIR)/src/jpegio - -CFLAGS_USERMOD += -I$(JPEGIO_DIR)/tjpgd - -SRC_USERMOD_C += $(JPEGIO_DIR)/jpegio.c - -# Library source: no qstrs, so it stays out of the QSTR scan. -SRC_USERMOD_LIB_C += $(JPEGIO_DIR)/tjpgd/tjpgd.c - -# lvgl-micropython is in this build if USER_C_MODULES names it -- either as a -# module directory itself (MicroPython 1.29 c_module()) or as a parent -# directory one level above it (py.mk's glob). Override with JPEGIO_LVGL=0/1 -# on the make command line. -JPEGIO_LVMP_DIR := $(firstword $(filter %/lvgl-micropython,$(USER_C_MODULES:/=)) $(foreach d,$(USER_C_MODULES),$(wildcard $(d)/lvgl-micropython))) -JPEGIO_LVGL ?= $(if $(JPEGIO_LVMP_DIR),1,0) - -ifeq ($(JPEGIO_LVGL),1) -# lvgl_decoder.c includes "lvgl/lvgl.h" and "lvgl/src/...", which resolve -# through the -I$(BINDINGS_DIR) lvgl-micropython adds, wherever its bindings -# are (a sibling checkout or its own fetched .deps/), so nothing here derives -# that path. -CFLAGS_USERMOD += -DJPEGIO_LVGL_DECODER=1 -# No qstrs of its own (the Python-visible names live in jpegio.c). -SRC_USERMOD_LIB_C += $(JPEGIO_DIR)/lvgl_decoder.c -# lvgl.h with LV_USE_FLOAT trips -Werror=double-promotion / float-conversion -# on ports that append those after CFLAGS_USERMOD (unix, webassembly): the -# same per-object suppression lvgl-micropython puts on LVGL's own objects. -$(eval $(BUILD)/$(patsubst $(DISPLAYIF_MOD_DIR)/%,$(notdir $(DISPLAYIF_MOD_DIR))/%,$(JPEGIO_DIR)/lvgl_decoder.o): CFLAGS += -Wno-double-promotion -Wno-float-conversion) -endif diff --git a/src/jpegio/tjpgd/tjpgd.c b/src/jpegio/tjpgd/tjpgd.c deleted file mode 100644 index 576399c..0000000 --- a/src/jpegio/tjpgd/tjpgd.c +++ /dev/null @@ -1,1157 +0,0 @@ -/*----------------------------------------------------------------------------/ -/ TJpgDec - Tiny JPEG Decompressor R0.03 w/patch1 (C)ChaN, 2021 -/-----------------------------------------------------------------------------/ -/ The TJpgDec is a generic JPEG decompressor module for tiny embedded systems. -/ This is a free software that opened for education, research and commercial -/ developments under license policy of following terms. -/ -/ Copyright (C) 2021, ChaN, all right reserved. -/ -/ * The TJpgDec module is a free software and there is NO WARRANTY. -/ * No restriction on use. You can use, modify and redistribute it for -/ personal, non-profit or commercial products UNDER YOUR RESPONSIBILITY. -/ * Redistributions of source code must retain the above copyright notice. -/ -/-----------------------------------------------------------------------------/ -/ Oct 04, 2011 R0.01 First release. -/ Feb 19, 2012 R0.01a Fixed decompression fails when scan starts with an escape seq. -/ Sep 03, 2012 R0.01b Added JD_TBLCLIP option. -/ Mar 16, 2019 R0.01c Supprted stdint.h. -/ Jul 01, 2020 R0.01d Fixed wrong integer type usage. -/ May 08, 2021 R0.02 Supprted grayscale image. Separated configuration options. -/ Jun 11, 2021 R0.02a Some performance improvement. -/ Jul 01, 2021 R0.03 Added JD_FASTDECODE option. -/ Some performance improvement. -/----------------------------------------------------------------------------*/ - -#include "tjpgd.h" - - -#if JD_FASTDECODE == 2 -#define HUFF_BIT 10 /* Bit length to apply fast huffman decode */ -#define HUFF_LEN (1 << HUFF_BIT) -#define HUFF_MASK (HUFF_LEN - 1) -#endif - - -/*-----------------------------------------------*/ -/* Zigzag-order to raster-order conversion table */ -/*-----------------------------------------------*/ - -static const uint8_t Zig[64] = { /* Zigzag-order to raster-order conversion table */ - 0, 1, 8, 16, 9, 2, 3, 10, 17, 24, 32, 25, 18, 11, 4, 5, - 12, 19, 26, 33, 40, 48, 41, 34, 27, 20, 13, 6, 7, 14, 21, 28, - 35, 42, 49, 56, 57, 50, 43, 36, 29, 22, 15, 23, 30, 37, 44, 51, - 58, 59, 52, 45, 38, 31, 39, 46, 53, 60, 61, 54, 47, 55, 62, 63 -}; - - - -/*-------------------------------------------------*/ -/* Input scale factor of Arai algorithm */ -/* (scaled up 16 bits for fixed point operations) */ -/*-------------------------------------------------*/ - -static const uint16_t Ipsf[64] = { /* See also aa_idct.png */ - (uint16_t)(1.00000*8192), (uint16_t)(1.38704*8192), (uint16_t)(1.30656*8192), (uint16_t)(1.17588*8192), (uint16_t)(1.00000*8192), (uint16_t)(0.78570*8192), (uint16_t)(0.54120*8192), (uint16_t)(0.27590*8192), - (uint16_t)(1.38704*8192), (uint16_t)(1.92388*8192), (uint16_t)(1.81226*8192), (uint16_t)(1.63099*8192), (uint16_t)(1.38704*8192), (uint16_t)(1.08979*8192), (uint16_t)(0.75066*8192), (uint16_t)(0.38268*8192), - (uint16_t)(1.30656*8192), (uint16_t)(1.81226*8192), (uint16_t)(1.70711*8192), (uint16_t)(1.53636*8192), (uint16_t)(1.30656*8192), (uint16_t)(1.02656*8192), (uint16_t)(0.70711*8192), (uint16_t)(0.36048*8192), - (uint16_t)(1.17588*8192), (uint16_t)(1.63099*8192), (uint16_t)(1.53636*8192), (uint16_t)(1.38268*8192), (uint16_t)(1.17588*8192), (uint16_t)(0.92388*8192), (uint16_t)(0.63638*8192), (uint16_t)(0.32442*8192), - (uint16_t)(1.00000*8192), (uint16_t)(1.38704*8192), (uint16_t)(1.30656*8192), (uint16_t)(1.17588*8192), (uint16_t)(1.00000*8192), (uint16_t)(0.78570*8192), (uint16_t)(0.54120*8192), (uint16_t)(0.27590*8192), - (uint16_t)(0.78570*8192), (uint16_t)(1.08979*8192), (uint16_t)(1.02656*8192), (uint16_t)(0.92388*8192), (uint16_t)(0.78570*8192), (uint16_t)(0.61732*8192), (uint16_t)(0.42522*8192), (uint16_t)(0.21677*8192), - (uint16_t)(0.54120*8192), (uint16_t)(0.75066*8192), (uint16_t)(0.70711*8192), (uint16_t)(0.63638*8192), (uint16_t)(0.54120*8192), (uint16_t)(0.42522*8192), (uint16_t)(0.29290*8192), (uint16_t)(0.14932*8192), - (uint16_t)(0.27590*8192), (uint16_t)(0.38268*8192), (uint16_t)(0.36048*8192), (uint16_t)(0.32442*8192), (uint16_t)(0.27590*8192), (uint16_t)(0.21678*8192), (uint16_t)(0.14932*8192), (uint16_t)(0.07612*8192) -}; - - - -/*---------------------------------------------*/ -/* Conversion table for fast clipping process */ -/*---------------------------------------------*/ - -#if JD_TBLCLIP - -#define BYTECLIP(v) Clip8[(unsigned int)(v) & 0x3FF] - -static const uint8_t Clip8[1024] = { - /* 0..255 */ - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, - 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, - 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, - 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, - 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, - 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, - 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, - 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, - /* 256..511 */ - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, - /* -512..-257 */ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - /* -256..-1 */ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 -}; - -#else /* JD_TBLCLIP */ - -static uint8_t BYTECLIP (int val) -{ - if (val < 0) return 0; - if (val > 255) return 255; - return (uint8_t)val; -} - -#endif - - - -/*-----------------------------------------------------------------------*/ -/* Allocate a memory block from memory pool */ -/*-----------------------------------------------------------------------*/ - -static void* alloc_pool ( /* Pointer to allocated memory block (NULL:no memory available) */ - JDEC* jd, /* Pointer to the decompressor object */ - size_t ndata /* Number of bytes to allocate */ -) -{ - char *rp = 0; - - - ndata = (ndata + 3) & ~3; /* Align block size to the word boundary */ - - if (jd->sz_pool >= ndata) { - jd->sz_pool -= ndata; - rp = (char*)jd->pool; /* Get start of available memory pool */ - jd->pool = (void*)(rp + ndata); /* Allocate requierd bytes */ - } - - return (void*)rp; /* Return allocated memory block (NULL:no memory to allocate) */ -} - - - - -/*-----------------------------------------------------------------------*/ -/* Create de-quantization and prescaling tables with a DQT segment */ -/*-----------------------------------------------------------------------*/ - -static JRESULT create_qt_tbl ( /* 0:OK, !0:Failed */ - JDEC* jd, /* Pointer to the decompressor object */ - const uint8_t* data, /* Pointer to the quantizer tables */ - size_t ndata /* Size of input data */ -) -{ - unsigned int i, zi; - uint8_t d; - int32_t *pb; - - - while (ndata) { /* Process all tables in the segment */ - if (ndata < 65) return JDR_FMT1; /* Err: table size is unaligned */ - ndata -= 65; - d = *data++; /* Get table property */ - if (d & 0xF0) return JDR_FMT1; /* Err: not 8-bit resolution */ - i = d & 3; /* Get table ID */ - pb = alloc_pool(jd, 64 * sizeof (int32_t));/* Allocate a memory block for the table */ - if (!pb) return JDR_MEM1; /* Err: not enough memory */ - jd->qttbl[i] = pb; /* Register the table */ - for (i = 0; i < 64; i++) { /* Load the table */ - zi = Zig[i]; /* Zigzag-order to raster-order conversion */ - pb[zi] = (int32_t)((uint32_t)*data++ * Ipsf[zi]); /* Apply scale factor of Arai algorithm to the de-quantizers */ - } - } - - return JDR_OK; -} - - - - -/*-----------------------------------------------------------------------*/ -/* Create huffman code tables with a DHT segment */ -/*-----------------------------------------------------------------------*/ - -static JRESULT create_huffman_tbl ( /* 0:OK, !0:Failed */ - JDEC* jd, /* Pointer to the decompressor object */ - const uint8_t* data, /* Pointer to the packed huffman tables */ - size_t ndata /* Size of input data */ -) -{ - unsigned int i, j, b, cls, num; - size_t np; - uint8_t d, *pb, *pd; - uint16_t hc, *ph; - - - while (ndata) { /* Process all tables in the segment */ - if (ndata < 17) return JDR_FMT1; /* Err: wrong data size */ - ndata -= 17; - d = *data++; /* Get table number and class */ - if (d & 0xEE) return JDR_FMT1; /* Err: invalid class/number */ - cls = d >> 4; num = d & 0x0F; /* class = dc(0)/ac(1), table number = 0/1 */ - pb = alloc_pool(jd, 16); /* Allocate a memory block for the bit distribution table */ - if (!pb) return JDR_MEM1; /* Err: not enough memory */ - jd->huffbits[num][cls] = pb; - for (np = i = 0; i < 16; i++) { /* Load number of patterns for 1 to 16-bit code */ - np += (pb[i] = *data++); /* Get sum of code words for each code */ - } - ph = alloc_pool(jd, np * sizeof (uint16_t));/* Allocate a memory block for the code word table */ - if (!ph) return JDR_MEM1; /* Err: not enough memory */ - jd->huffcode[num][cls] = ph; - hc = 0; - for (j = i = 0; i < 16; i++) { /* Re-build huffman code word table */ - b = pb[i]; - while (b--) ph[j++] = hc++; - hc <<= 1; - } - - if (ndata < np) return JDR_FMT1; /* Err: wrong data size */ - ndata -= np; - pd = alloc_pool(jd, np); /* Allocate a memory block for the decoded data */ - if (!pd) return JDR_MEM1; /* Err: not enough memory */ - jd->huffdata[num][cls] = pd; - for (i = 0; i < np; i++) { /* Load decoded data corresponds to each code word */ - d = *data++; - if (!cls && d > 11) return JDR_FMT1; - pd[i] = d; - } -#if JD_FASTDECODE == 2 - { /* Create fast huffman decode table */ - unsigned int span, td, ti; - uint16_t *tbl_ac = 0; - uint8_t *tbl_dc = 0; - - if (cls) { - tbl_ac = alloc_pool(jd, HUFF_LEN * sizeof (uint16_t)); /* LUT for AC elements */ - if (!tbl_ac) return JDR_MEM1; /* Err: not enough memory */ - jd->hufflut_ac[num] = tbl_ac; - memset(tbl_ac, 0xFF, HUFF_LEN * sizeof (uint16_t)); /* Default value (0xFFFF: may be long code) */ - } else { - tbl_dc = alloc_pool(jd, HUFF_LEN * sizeof (uint8_t)); /* LUT for AC elements */ - if (!tbl_dc) return JDR_MEM1; /* Err: not enough memory */ - jd->hufflut_dc[num] = tbl_dc; - memset(tbl_dc, 0xFF, HUFF_LEN * sizeof (uint8_t)); /* Default value (0xFF: may be long code) */ - } - for (i = b = 0; b < HUFF_BIT; b++) { /* Create LUT */ - for (j = pb[b]; j; j--) { - ti = ph[i] << (HUFF_BIT - 1 - b) & HUFF_MASK; /* Index of input pattern for the code */ - if (cls) { - td = pd[i++] | ((b + 1) << 8); /* b15..b8: code length, b7..b0: zero run and data length */ - for (span = 1 << (HUFF_BIT - 1 - b); span; span--, tbl_ac[ti++] = (uint16_t)td) ; - } else { - td = pd[i++] | ((b + 1) << 4); /* b7..b4: code length, b3..b0: data length */ - for (span = 1 << (HUFF_BIT - 1 - b); span; span--, tbl_dc[ti++] = (uint8_t)td) ; - } - } - } - jd->longofs[num][cls] = i; /* Code table offset for long code */ - } -#endif - } - - return JDR_OK; -} - - - - -/*-----------------------------------------------------------------------*/ -/* Extract a huffman decoded data from input stream */ -/*-----------------------------------------------------------------------*/ - -static int huffext ( /* >=0: decoded data, <0: error code */ - JDEC* jd, /* Pointer to the decompressor object */ - unsigned int id, /* Table ID (0:Y, 1:C) */ - unsigned int cls /* Table class (0:DC, 1:AC) */ -) -{ - size_t dc = jd->dctr; - uint8_t *dp = jd->dptr; - unsigned int d, flg = 0; - -#if JD_FASTDECODE == 0 - uint8_t bm, nd, bl; - const uint8_t *hb = jd->huffbits[id][cls]; /* Bit distribution table */ - const uint16_t *hc = jd->huffcode[id][cls]; /* Code word table */ - const uint8_t *hd = jd->huffdata[id][cls]; /* Data table */ - - - bm = jd->dbit; /* Bit mask to extract */ - d = 0; bl = 16; /* Max code length */ - do { - if (!bm) { /* Next byte? */ - if (!dc) { /* No input data is available, re-fill input buffer */ - dp = jd->inbuf; /* Top of input buffer */ - dc = jd->infunc(jd, dp, JD_SZBUF); - if (!dc) return 0 - (int)JDR_INP; /* Err: read error or wrong stream termination */ - } else { - dp++; /* Next data ptr */ - } - dc--; /* Decrement number of available bytes */ - if (flg) { /* In flag sequence? */ - flg = 0; /* Exit flag sequence */ - if (*dp != 0) return 0 - (int)JDR_FMT1; /* Err: unexpected flag is detected (may be collapted data) */ - *dp = 0xFF; /* The flag is a data 0xFF */ - } else { - if (*dp == 0xFF) { /* Is start of flag sequence? */ - flg = 1; continue; /* Enter flag sequence, get trailing byte */ - } - } - bm = 0x80; /* Read from MSB */ - } - d <<= 1; /* Get a bit */ - if (*dp & bm) d++; - bm >>= 1; - - for (nd = *hb++; nd; nd--) { /* Search the code word in this bit length */ - if (d == *hc++) { /* Matched? */ - jd->dbit = bm; jd->dctr = dc; jd->dptr = dp; - return *hd; /* Return the decoded data */ - } - hd++; - } - bl--; - } while (bl); - -#else - const uint8_t *hb, *hd; - const uint16_t *hc; - unsigned int nc, bl, wbit = jd->dbit % 32; - uint32_t w = jd->wreg & ((1UL << wbit) - 1); - - - while (wbit < 16) { /* Prepare 16 bits into the working register */ - if (jd->marker) { - d = 0xFF; /* Input stream has stalled for a marker. Generate stuff bits */ - } else { - if (!dc) { /* Buffer empty, re-fill input buffer */ - dp = jd->inbuf; /* Top of input buffer */ - dc = jd->infunc(jd, dp, JD_SZBUF); - if (!dc) return 0 - (int)JDR_INP; /* Err: read error or wrong stream termination */ - } - d = *dp++; dc--; - if (flg) { /* In flag sequence? */ - flg = 0; /* Exit flag sequence */ - if (d != 0) jd->marker = d; /* Not an escape of 0xFF but a marker */ - d = 0xFF; - } else { - if (d == 0xFF) { /* Is start of flag sequence? */ - flg = 1; continue; /* Enter flag sequence, get trailing byte */ - } - } - } - w = w << 8 | d; /* Shift 8 bits in the working register */ - wbit += 8; - } - jd->dctr = dc; jd->dptr = dp; - jd->wreg = w; - -#if JD_FASTDECODE == 2 - /* Table serch for the short codes */ - d = (unsigned int)(w >> (wbit - HUFF_BIT)); /* Short code as table index */ - if (cls) { /* AC element */ - d = jd->hufflut_ac[id][d]; /* Table decode */ - if (d != 0xFFFF) { /* It is done if hit in short code */ - jd->dbit = wbit - (d >> 8); /* Snip the code length */ - return d & 0xFF; /* b7..0: zero run and following data bits */ - } - } else { /* DC element */ - d = jd->hufflut_dc[id][d]; /* Table decode */ - if (d != 0xFF) { /* It is done if hit in short code */ - jd->dbit = wbit - (d >> 4); /* Snip the code length */ - return d & 0xF; /* b3..0: following data bits */ - } - } - - /* Incremental serch for the codes longer than HUFF_BIT */ - hb = jd->huffbits[id][cls] + HUFF_BIT; /* Bit distribution table */ - hc = jd->huffcode[id][cls] + jd->longofs[id][cls]; /* Code word table */ - hd = jd->huffdata[id][cls] + jd->longofs[id][cls]; /* Data table */ - bl = HUFF_BIT + 1; -#else - /* Incremental serch for all codes */ - hb = jd->huffbits[id][cls]; /* Bit distribution table */ - hc = jd->huffcode[id][cls]; /* Code word table */ - hd = jd->huffdata[id][cls]; /* Data table */ - bl = 1; -#endif - for ( ; bl <= 16; bl++) { /* Incremental search */ - nc = *hb++; - if (nc) { - d = w >> (wbit - bl); - do { /* Search the code word in this bit length */ - if (d == *hc++) { /* Matched? */ - jd->dbit = wbit - bl; /* Snip the huffman code */ - return *hd; /* Return the decoded data */ - } - hd++; - } while (--nc); - } - } -#endif - - return 0 - (int)JDR_FMT1; /* Err: code not found (may be collapted data) */ -} - - - - -/*-----------------------------------------------------------------------*/ -/* Extract N bits from input stream */ -/*-----------------------------------------------------------------------*/ - -static int bitext ( /* >=0: extracted data, <0: error code */ - JDEC* jd, /* Pointer to the decompressor object */ - unsigned int nbit /* Number of bits to extract (1 to 16) */ -) -{ - size_t dc = jd->dctr; - uint8_t *dp = jd->dptr; - unsigned int d, flg = 0; - -#if JD_FASTDECODE == 0 - uint8_t mbit = jd->dbit; - - d = 0; - do { - if (!mbit) { /* Next byte? */ - if (!dc) { /* No input data is available, re-fill input buffer */ - dp = jd->inbuf; /* Top of input buffer */ - dc = jd->infunc(jd, dp, JD_SZBUF); - if (!dc) return 0 - (int)JDR_INP; /* Err: read error or wrong stream termination */ - } else { - dp++; /* Next data ptr */ - } - dc--; /* Decrement number of available bytes */ - if (flg) { /* In flag sequence? */ - flg = 0; /* Exit flag sequence */ - if (*dp != 0) return 0 - (int)JDR_FMT1; /* Err: unexpected flag is detected (may be collapted data) */ - *dp = 0xFF; /* The flag is a data 0xFF */ - } else { - if (*dp == 0xFF) { /* Is start of flag sequence? */ - flg = 1; continue; /* Enter flag sequence */ - } - } - mbit = 0x80; /* Read from MSB */ - } - d <<= 1; /* Get a bit */ - if (*dp & mbit) d |= 1; - mbit >>= 1; - nbit--; - } while (nbit); - - jd->dbit = mbit; jd->dctr = dc; jd->dptr = dp; - return (int)d; - -#else - unsigned int wbit = jd->dbit % 32; - uint32_t w = jd->wreg & ((1UL << wbit) - 1); - - - while (wbit < nbit) { /* Prepare nbit bits into the working register */ - if (jd->marker) { - d = 0xFF; /* Input stream stalled, generate stuff bits */ - } else { - if (!dc) { /* Buffer empty, re-fill input buffer */ - dp = jd->inbuf; /* Top of input buffer */ - dc = jd->infunc(jd, dp, JD_SZBUF); - if (!dc) return 0 - (int)JDR_INP; /* Err: read error or wrong stream termination */ - } - d = *dp++; dc--; - if (flg) { /* In flag sequence? */ - flg = 0; /* Exit flag sequence */ - if (d != 0) jd->marker = d; /* Not an escape of 0xFF but a marker */ - d = 0xFF; - } else { - if (d == 0xFF) { /* Is start of flag sequence? */ - flg = 1; continue; /* Enter flag sequence, get trailing byte */ - } - } - } - w = w << 8 | d; /* Get 8 bits into the working register */ - wbit += 8; - } - jd->wreg = w; jd->dbit = wbit - nbit; - jd->dctr = dc; jd->dptr = dp; - - return (int)(w >> ((wbit - nbit) % 32)); -#endif -} - - - - -/*-----------------------------------------------------------------------*/ -/* Process restart interval */ -/*-----------------------------------------------------------------------*/ - -static JRESULT restart ( - JDEC* jd, /* Pointer to the decompressor object */ - uint16_t rstn /* Expected restert sequense number */ -) -{ - unsigned int i; - uint8_t *dp = jd->dptr; - size_t dc = jd->dctr; - -#if JD_FASTDECODE == 0 - uint16_t d = 0; - - /* Get two bytes from the input stream */ - for (i = 0; i < 2; i++) { - if (!dc) { /* No input data is available, re-fill input buffer */ - dp = jd->inbuf; - dc = jd->infunc(jd, dp, JD_SZBUF); - if (!dc) return JDR_INP; - } else { - dp++; - } - dc--; - d = d << 8 | *dp; /* Get a byte */ - } - jd->dptr = dp; jd->dctr = dc; jd->dbit = 0; - - /* Check the marker */ - if ((d & 0xFFD8) != 0xFFD0 || (d & 7) != (rstn & 7)) { - return JDR_FMT1; /* Err: expected RSTn marker is not detected (may be collapted data) */ - } - -#else - uint16_t marker; - - - if (jd->marker) { /* Generate a maker if it has been detected */ - marker = 0xFF00 | jd->marker; - jd->marker = 0; - } else { - marker = 0; - for (i = 0; i < 2; i++) { /* Get a restart marker */ - if (!dc) { /* No input data is available, re-fill input buffer */ - dp = jd->inbuf; - dc = jd->infunc(jd, dp, JD_SZBUF); - if (!dc) return JDR_INP; - } - marker = (marker << 8) | *dp++; /* Get a byte */ - dc--; - } - jd->dptr = dp; jd->dctr = dc; - } - - /* Check the marker */ - if ((marker & 0xFFD8) != 0xFFD0 || (marker & 7) != (rstn & 7)) { - return JDR_FMT1; /* Err: expected RSTn marker was not detected (may be collapted data) */ - } - - jd->dbit = 0; /* Discard stuff bits */ -#endif - - jd->dcv[2] = jd->dcv[1] = jd->dcv[0] = 0; /* Reset DC offset */ - return JDR_OK; -} - - - - -/*-----------------------------------------------------------------------*/ -/* Apply Inverse-DCT in Arai Algorithm (see also aa_idct.png) */ -/*-----------------------------------------------------------------------*/ - -static void block_idct ( - int32_t* src, /* Input block data (de-quantized and pre-scaled for Arai Algorithm) */ - jd_yuv_t* dst /* Pointer to the destination to store the block as byte array */ -) -{ - const int32_t M13 = (int32_t)(1.41421*4096), M2 = (int32_t)(1.08239*4096), M4 = (int32_t)(2.61313*4096), M5 = (int32_t)(1.84776*4096); - int32_t v0, v1, v2, v3, v4, v5, v6, v7; - int32_t t10, t11, t12, t13; - int i; - - /* Process columns */ - for (i = 0; i < 8; i++) { - v0 = src[8 * 0]; /* Get even elements */ - v1 = src[8 * 2]; - v2 = src[8 * 4]; - v3 = src[8 * 6]; - - t10 = v0 + v2; /* Process the even elements */ - t12 = v0 - v2; - t11 = (v1 - v3) * M13 >> 12; - v3 += v1; - t11 -= v3; - v0 = t10 + v3; - v3 = t10 - v3; - v1 = t11 + t12; - v2 = t12 - t11; - - v4 = src[8 * 7]; /* Get odd elements */ - v5 = src[8 * 1]; - v6 = src[8 * 5]; - v7 = src[8 * 3]; - - t10 = v5 - v4; /* Process the odd elements */ - t11 = v5 + v4; - t12 = v6 - v7; - v7 += v6; - v5 = (t11 - v7) * M13 >> 12; - v7 += t11; - t13 = (t10 + t12) * M5 >> 12; - v4 = t13 - (t10 * M2 >> 12); - v6 = t13 - (t12 * M4 >> 12) - v7; - v5 -= v6; - v4 -= v5; - - src[8 * 0] = v0 + v7; /* Write-back transformed values */ - src[8 * 7] = v0 - v7; - src[8 * 1] = v1 + v6; - src[8 * 6] = v1 - v6; - src[8 * 2] = v2 + v5; - src[8 * 5] = v2 - v5; - src[8 * 3] = v3 + v4; - src[8 * 4] = v3 - v4; - - src++; /* Next column */ - } - - /* Process rows */ - src -= 8; - for (i = 0; i < 8; i++) { - v0 = src[0] + (128L << 8); /* Get even elements (remove DC offset (-128) here) */ - v1 = src[2]; - v2 = src[4]; - v3 = src[6]; - - t10 = v0 + v2; /* Process the even elements */ - t12 = v0 - v2; - t11 = (v1 - v3) * M13 >> 12; - v3 += v1; - t11 -= v3; - v0 = t10 + v3; - v3 = t10 - v3; - v1 = t11 + t12; - v2 = t12 - t11; - - v4 = src[7]; /* Get odd elements */ - v5 = src[1]; - v6 = src[5]; - v7 = src[3]; - - t10 = v5 - v4; /* Process the odd elements */ - t11 = v5 + v4; - t12 = v6 - v7; - v7 += v6; - v5 = (t11 - v7) * M13 >> 12; - v7 += t11; - t13 = (t10 + t12) * M5 >> 12; - v4 = t13 - (t10 * M2 >> 12); - v6 = t13 - (t12 * M4 >> 12) - v7; - v5 -= v6; - v4 -= v5; - - /* Descale the transformed values 8 bits and output a row */ -#if JD_FASTDECODE >= 1 - dst[0] = (int16_t)((v0 + v7) >> 8); - dst[7] = (int16_t)((v0 - v7) >> 8); - dst[1] = (int16_t)((v1 + v6) >> 8); - dst[6] = (int16_t)((v1 - v6) >> 8); - dst[2] = (int16_t)((v2 + v5) >> 8); - dst[5] = (int16_t)((v2 - v5) >> 8); - dst[3] = (int16_t)((v3 + v4) >> 8); - dst[4] = (int16_t)((v3 - v4) >> 8); -#else - dst[0] = BYTECLIP((v0 + v7) >> 8); - dst[7] = BYTECLIP((v0 - v7) >> 8); - dst[1] = BYTECLIP((v1 + v6) >> 8); - dst[6] = BYTECLIP((v1 - v6) >> 8); - dst[2] = BYTECLIP((v2 + v5) >> 8); - dst[5] = BYTECLIP((v2 - v5) >> 8); - dst[3] = BYTECLIP((v3 + v4) >> 8); - dst[4] = BYTECLIP((v3 - v4) >> 8); -#endif - - dst += 8; src += 8; /* Next row */ - } -} - - - - -/*-----------------------------------------------------------------------*/ -/* Load all blocks in an MCU into working buffer */ -/*-----------------------------------------------------------------------*/ - -static JRESULT mcu_load ( - JDEC* jd /* Pointer to the decompressor object */ -) -{ - int32_t *tmp = (int32_t*)jd->workbuf; /* Block working buffer for de-quantize and IDCT */ - int d, e; - unsigned int blk, nby, i, bc, z, id, cmp; - jd_yuv_t *bp; - const int32_t *dqf; - - - nby = jd->msx * jd->msy; /* Number of Y blocks (1, 2 or 4) */ - bp = jd->mcubuf; /* Pointer to the first block of MCU */ - - for (blk = 0; blk < nby + 2; blk++) { /* Get nby Y blocks and two C blocks */ - cmp = (blk < nby) ? 0 : blk - nby + 1; /* Component number 0:Y, 1:Cb, 2:Cr */ - - if (cmp && jd->ncomp != 3) { /* Clear C blocks if not exist (monochrome image) */ - for (i = 0; i < 64; bp[i++] = 128) ; - - } else { /* Load Y/C blocks from input stream */ - id = cmp ? 1 : 0; /* Huffman table ID of this component */ - - /* Extract a DC element from input stream */ - d = huffext(jd, id, 0); /* Extract a huffman coded data (bit length) */ - if (d < 0) return (JRESULT)(0 - d); /* Err: invalid code or input */ - bc = (unsigned int)d; - d = jd->dcv[cmp]; /* DC value of previous block */ - if (bc) { /* If there is any difference from previous block */ - e = bitext(jd, bc); /* Extract data bits */ - if (e < 0) return (JRESULT)(0 - e); /* Err: input */ - bc = 1 << (bc - 1); /* MSB position */ - if (!(e & bc)) e -= (bc << 1) - 1; /* Restore negative value if needed */ - d += e; /* Get current value */ - jd->dcv[cmp] = (int16_t)d; /* Save current DC value for next block */ - } - dqf = jd->qttbl[jd->qtid[cmp]]; /* De-quantizer table ID for this component */ - tmp[0] = d * dqf[0] >> 8; /* De-quantize, apply scale factor of Arai algorithm and descale 8 bits */ - - /* Extract following 63 AC elements from input stream */ - memset(&tmp[1], 0, 63 * sizeof (int32_t)); /* Initialize all AC elements */ - z = 1; /* Top of the AC elements (in zigzag-order) */ - do { - d = huffext(jd, id, 1); /* Extract a huffman coded value (zero runs and bit length) */ - if (d == 0) break; /* EOB? */ - if (d < 0) return (JRESULT)(0 - d); /* Err: invalid code or input error */ - bc = (unsigned int)d; - z += bc >> 4; /* Skip leading zero run */ - if (z >= 64) return JDR_FMT1; /* Too long zero run */ - if (bc &= 0x0F) { /* Bit length? */ - d = bitext(jd, bc); /* Extract data bits */ - if (d < 0) return (JRESULT)(0 - d); /* Err: input device */ - bc = 1 << (bc - 1); /* MSB position */ - if (!(d & bc)) d -= (bc << 1) - 1; /* Restore negative value if needed */ - i = Zig[z]; /* Get raster-order index */ - tmp[i] = d * dqf[i] >> 8; /* De-quantize, apply scale factor of Arai algorithm and descale 8 bits */ - } - } while (++z < 64); /* Next AC element */ - - if (JD_FORMAT != 2 || !cmp) { /* C components may not be processed if in grayscale output */ - if (z == 1 || (JD_USE_SCALE && jd->scale == 3)) { /* If no AC element or scale ratio is 1/8, IDCT can be ommited and the block is filled with DC value */ - d = (jd_yuv_t)((*tmp / 256) + 128); - if (JD_FASTDECODE >= 1) { - for (i = 0; i < 64; bp[i++] = d) ; - } else { - memset(bp, d, 64); - } - } else { - block_idct(tmp, bp); /* Apply IDCT and store the block to the MCU buffer */ - } - } - } - - bp += 64; /* Next block */ - } - - return JDR_OK; /* All blocks have been loaded successfully */ -} - - - - -/*-----------------------------------------------------------------------*/ -/* Output an MCU: Convert YCrCb to RGB and output it in RGB form */ -/*-----------------------------------------------------------------------*/ - -static JRESULT mcu_output ( - JDEC* jd, /* Pointer to the decompressor object */ - int (*outfunc)(JDEC*, void*, JRECT*), /* RGB output function */ - unsigned int x, /* MCU location in the image */ - unsigned int y /* MCU location in the image */ -) -{ - const int CVACC = (sizeof (int) > 2) ? 1024 : 128; /* Adaptive accuracy for both 16-/32-bit systems */ - unsigned int ix, iy, mx, my, rx, ry; - int yy, cb, cr; - jd_yuv_t *py, *pc; - uint8_t *pix; - JRECT rect; - - - mx = jd->msx * 8; my = jd->msy * 8; /* MCU size (pixel) */ - rx = (x + mx <= jd->width) ? mx : jd->width - x; /* Output rectangular size (it may be clipped at right/bottom end of image) */ - ry = (y + my <= jd->height) ? my : jd->height - y; - if (JD_USE_SCALE) { - rx >>= jd->scale; ry >>= jd->scale; - if (!rx || !ry) return JDR_OK; /* Skip this MCU if all pixel is to be rounded off */ - x >>= jd->scale; y >>= jd->scale; - } - rect.left = x; rect.right = x + rx - 1; /* Rectangular area in the frame buffer */ - rect.top = y; rect.bottom = y + ry - 1; - - - if (!JD_USE_SCALE || jd->scale != 3) { /* Not for 1/8 scaling */ - pix = (uint8_t*)jd->workbuf; - - if (JD_FORMAT != 2) { /* RGB output (build an RGB MCU from Y/C component) */ - for (iy = 0; iy < my; iy++) { - pc = py = jd->mcubuf; - if (my == 16) { /* Double block height? */ - pc += 64 * 4 + (iy >> 1) * 8; - if (iy >= 8) py += 64; - } else { /* Single block height */ - pc += mx * 8 + iy * 8; - } - py += iy * 8; - for (ix = 0; ix < mx; ix++) { - cb = pc[0] - 128; /* Get Cb/Cr component and remove offset */ - cr = pc[64] - 128; - if (mx == 16) { /* Double block width? */ - if (ix == 8) py += 64 - 8; /* Jump to next block if double block heigt */ - pc += ix & 1; /* Step forward chroma pointer every two pixels */ - } else { /* Single block width */ - pc++; /* Step forward chroma pointer every pixel */ - } - yy = *py++; /* Get Y component */ - *pix++ = /*R*/ BYTECLIP(yy + ((int)(1.402 * CVACC) * cr) / CVACC); - *pix++ = /*G*/ BYTECLIP(yy - ((int)(0.344 * CVACC) * cb + (int)(0.714 * CVACC) * cr) / CVACC); - *pix++ = /*B*/ BYTECLIP(yy + ((int)(1.772 * CVACC) * cb) / CVACC); - } - } - } else { /* Monochrome output (build a grayscale MCU from Y comopnent) */ - for (iy = 0; iy < my; iy++) { - py = jd->mcubuf + iy * 8; - if (my == 16) { /* Double block height? */ - if (iy >= 8) py += 64; - } - for (ix = 0; ix < mx; ix++) { - if (mx == 16) { /* Double block width? */ - if (ix == 8) py += 64 - 8; /* Jump to next block if double block height */ - } - if (JD_FASTDECODE >= 1) { - *pix++ = BYTECLIP(*py++); /* Get and store a Y value as grayscale */ - } else { - *pix++ = *py++; /* Get and store a Y value as grayscale */ - } - } - } - } - - /* Descale the MCU rectangular if needed */ - if (JD_USE_SCALE && jd->scale) { - unsigned int x, y, r, g, b, s, w, a; - uint8_t *op; - - /* Get averaged RGB value of each square correcponds to a pixel */ - s = jd->scale * 2; /* Number of shifts for averaging */ - w = 1 << jd->scale; /* Width of square */ - a = (mx - w) * (JD_FORMAT != 2 ? 3 : 1); /* Bytes to skip for next line in the square */ - op = (uint8_t*)jd->workbuf; - for (iy = 0; iy < my; iy += w) { - for (ix = 0; ix < mx; ix += w) { - pix = (uint8_t*)jd->workbuf + (iy * mx + ix) * (JD_FORMAT != 2 ? 3 : 1); - r = g = b = 0; - for (y = 0; y < w; y++) { /* Accumulate RGB value in the square */ - for (x = 0; x < w; x++) { - r += *pix++; /* Accumulate R or Y (monochrome output) */ - if (JD_FORMAT != 2) { /* RGB output? */ - g += *pix++; /* Accumulate G */ - b += *pix++; /* Accumulate B */ - } - } - pix += a; - } /* Put the averaged pixel value */ - *op++ = (uint8_t)(r >> s); /* Put R or Y (monochrome output) */ - if (JD_FORMAT != 2) { /* RGB output? */ - *op++ = (uint8_t)(g >> s); /* Put G */ - *op++ = (uint8_t)(b >> s); /* Put B */ - } - } - } - } - - } else { /* For only 1/8 scaling (left-top pixel in each block are the DC value of the block) */ - - /* Build a 1/8 descaled RGB MCU from discrete comopnents */ - pix = (uint8_t*)jd->workbuf; - pc = jd->mcubuf + mx * my; - cb = pc[0] - 128; /* Get Cb/Cr component and restore right level */ - cr = pc[64] - 128; - for (iy = 0; iy < my; iy += 8) { - py = jd->mcubuf; - if (iy == 8) py += 64 * 2; - for (ix = 0; ix < mx; ix += 8) { - yy = *py; /* Get Y component */ - py += 64; - if (JD_FORMAT != 2) { - *pix++ = /*R*/ BYTECLIP(yy + ((int)(1.402 * CVACC) * cr / CVACC)); - *pix++ = /*G*/ BYTECLIP(yy - ((int)(0.344 * CVACC) * cb + (int)(0.714 * CVACC) * cr) / CVACC); - *pix++ = /*B*/ BYTECLIP(yy + ((int)(1.772 * CVACC) * cb / CVACC)); - } else { - *pix++ = yy; - } - } - } - } - - /* Squeeze up pixel table if a part of MCU is to be truncated */ - mx >>= jd->scale; - if (rx < mx) { /* Is the MCU spans rigit edge? */ - uint8_t *s, *d; - unsigned int x, y; - - s = d = (uint8_t*)jd->workbuf; - for (y = 0; y < ry; y++) { - for (x = 0; x < rx; x++) { /* Copy effective pixels */ - *d++ = *s++; - if (JD_FORMAT != 2) { - *d++ = *s++; - *d++ = *s++; - } - } - s += (mx - rx) * (JD_FORMAT != 2 ? 3 : 1); /* Skip truncated pixels */ - } - } - - /* Convert RGB888 to RGB565 if needed */ - if (JD_FORMAT == 1) { - uint8_t *s = (uint8_t*)jd->workbuf; - uint16_t w, *d = (uint16_t*)s; - unsigned int n = rx * ry; - - do { - w = (*s++ & 0xF8) << 8; /* RRRRR----------- */ - w |= (*s++ & 0xFC) << 3; /* -----GGGGGG----- */ - w |= *s++ >> 3; /* -----------BBBBB */ - *d++ = w; /* displayif: native byte order (ChaN R0.03 as released; CircuitPython byte-swaps here) */ - } while (--n); - } - - /* Output the rectangular */ - return outfunc(jd, jd->workbuf, &rect) ? JDR_OK : JDR_INTR; -} - - - - -/*-----------------------------------------------------------------------*/ -/* Analyze the JPEG image and Initialize decompressor object */ -/*-----------------------------------------------------------------------*/ - -#define LDB_WORD(ptr) (uint16_t)(((uint16_t)*((uint8_t*)(ptr))<<8)|(uint16_t)*(uint8_t*)((ptr)+1)) - - -JRESULT jd_prepare ( - JDEC* jd, /* Blank decompressor object */ - size_t (*infunc)(JDEC*, uint8_t*, size_t), /* JPEG strem input function */ - void* pool, /* Working buffer for the decompression session */ - size_t sz_pool, /* Size of working buffer */ - void* dev /* I/O device identifier for the session */ -) -{ - uint8_t *seg, b; - uint16_t marker; - unsigned int n, i, ofs; - size_t len; - JRESULT rc; - - - memset(jd, 0, sizeof (JDEC)); /* Clear decompression object (this might be a problem if machine's null pointer is not all bits zero) */ - jd->pool = pool; /* Work memroy */ - jd->sz_pool = sz_pool; /* Size of given work memory */ - jd->infunc = infunc; /* Stream input function */ - jd->device = dev; /* I/O device identifier */ - - jd->inbuf = seg = alloc_pool(jd, JD_SZBUF); /* Allocate stream input buffer */ - if (!seg) return JDR_MEM1; - - ofs = marker = 0; /* Find SOI marker */ - do { - if (jd->infunc(jd, seg, 1) != 1) return JDR_INP; /* Err: SOI was not detected */ - ofs++; - marker = marker << 8 | seg[0]; - } while (marker != 0xFFD8); - - for (;;) { /* Parse JPEG segments */ - /* Get a JPEG marker */ - if (jd->infunc(jd, seg, 4) != 4) return JDR_INP; - marker = LDB_WORD(seg); /* Marker */ - len = LDB_WORD(seg + 2); /* Length field */ - if (len <= 2 || (marker >> 8) != 0xFF) return JDR_FMT1; - len -= 2; /* Segent content size */ - ofs += 4 + len; /* Number of bytes loaded */ - - switch (marker & 0xFF) { - case 0xC0: /* SOF0 (baseline JPEG) */ - if (len > JD_SZBUF) return JDR_MEM2; - if (jd->infunc(jd, seg, len) != len) return JDR_INP; /* Load segment data */ - - jd->width = LDB_WORD(&seg[3]); /* Image width in unit of pixel */ - jd->height = LDB_WORD(&seg[1]); /* Image height in unit of pixel */ - jd->ncomp = seg[5]; /* Number of color components */ - if (jd->ncomp != 3 && jd->ncomp != 1) return JDR_FMT3; /* Err: Supports only Grayscale and Y/Cb/Cr */ - - /* Check each image component */ - for (i = 0; i < jd->ncomp; i++) { - b = seg[7 + 3 * i]; /* Get sampling factor */ - if (i == 0) { /* Y component */ - if (b != 0x11 && b != 0x22 && b != 0x21) { /* Check sampling factor */ - return JDR_FMT3; /* Err: Supports only 4:4:4, 4:2:0 or 4:2:2 */ - } - jd->msx = b >> 4; jd->msy = b & 15; /* Size of MCU [blocks] */ - } else { /* Cb/Cr component */ - if (b != 0x11) return JDR_FMT3; /* Err: Sampling factor of Cb/Cr must be 1 */ - } - jd->qtid[i] = seg[8 + 3 * i]; /* Get dequantizer table ID for this component */ - if (jd->qtid[i] > 3) return JDR_FMT3; /* Err: Invalid ID */ - } - break; - - case 0xDD: /* DRI - Define Restart Interval */ - if (len > JD_SZBUF) return JDR_MEM2; - if (jd->infunc(jd, seg, len) != len) return JDR_INP; /* Load segment data */ - - jd->nrst = LDB_WORD(seg); /* Get restart interval (MCUs) */ - break; - - case 0xC4: /* DHT - Define Huffman Tables */ - if (len > JD_SZBUF) return JDR_MEM2; - if (jd->infunc(jd, seg, len) != len) return JDR_INP; /* Load segment data */ - - rc = create_huffman_tbl(jd, seg, len); /* Create huffman tables */ - if (rc) return rc; - break; - - case 0xDB: /* DQT - Define Quaitizer Tables */ - if (len > JD_SZBUF) return JDR_MEM2; - if (jd->infunc(jd, seg, len) != len) return JDR_INP; /* Load segment data */ - - rc = create_qt_tbl(jd, seg, len); /* Create de-quantizer tables */ - if (rc) return rc; - break; - - case 0xDA: /* SOS - Start of Scan */ - if (len > JD_SZBUF) return JDR_MEM2; - if (jd->infunc(jd, seg, len) != len) return JDR_INP; /* Load segment data */ - - if (!jd->width || !jd->height) return JDR_FMT1; /* Err: Invalid image size */ - if (seg[0] != jd->ncomp) return JDR_FMT3; /* Err: Wrong color components */ - - /* Check if all tables corresponding to each components have been loaded */ - for (i = 0; i < jd->ncomp; i++) { - b = seg[2 + 2 * i]; /* Get huffman table ID */ - if (b != 0x00 && b != 0x11) return JDR_FMT3; /* Err: Different table number for DC/AC element */ - n = i ? 1 : 0; /* Component class */ - if (!jd->huffbits[n][0] || !jd->huffbits[n][1]) { /* Check huffman table for this component */ - return JDR_FMT1; /* Err: Nnot loaded */ - } - if (!jd->qttbl[jd->qtid[i]]) { /* Check dequantizer table for this component */ - return JDR_FMT1; /* Err: Not loaded */ - } - } - - /* Allocate working buffer for MCU and pixel output */ - n = jd->msy * jd->msx; /* Number of Y blocks in the MCU */ - if (!n) return JDR_FMT1; /* Err: SOF0 has not been loaded */ - len = n * 64 * 2 + 64; /* Allocate buffer for IDCT and RGB output */ - if (len < 256) len = 256; /* but at least 256 byte is required for IDCT */ - jd->workbuf = alloc_pool(jd, len); /* and it may occupy a part of following MCU working buffer for RGB output */ - if (!jd->workbuf) return JDR_MEM1; /* Err: not enough memory */ - jd->mcubuf = alloc_pool(jd, (n + 2) * 64 * sizeof (jd_yuv_t)); /* Allocate MCU working buffer */ - if (!jd->mcubuf) return JDR_MEM1; /* Err: not enough memory */ - - /* Align stream read offset to JD_SZBUF */ - if (ofs %= JD_SZBUF) { - jd->dctr = jd->infunc(jd, seg + ofs, (size_t)(JD_SZBUF - ofs)); - } - jd->dptr = seg + ofs - (JD_FASTDECODE ? 0 : 1); - - return JDR_OK; /* Initialization succeeded. Ready to decompress the JPEG image. */ - - case 0xC1: /* SOF1 */ - case 0xC2: /* SOF2 */ - case 0xC3: /* SOF3 */ - case 0xC5: /* SOF5 */ - case 0xC6: /* SOF6 */ - case 0xC7: /* SOF7 */ - case 0xC9: /* SOF9 */ - case 0xCA: /* SOF10 */ - case 0xCB: /* SOF11 */ - case 0xCD: /* SOF13 */ - case 0xCE: /* SOF14 */ - case 0xCF: /* SOF15 */ - case 0xD9: /* EOI */ - return JDR_FMT3; /* Unsuppoted JPEG standard (may be progressive JPEG) */ - - default: /* Unknown segment (comment, exif or etc..) */ - /* Skip segment data (null pointer specifies to remove data from the stream) */ - if (jd->infunc(jd, 0, len) != len) return JDR_INP; - } - } -} - - - - -/*-----------------------------------------------------------------------*/ -/* Start to decompress the JPEG picture */ -/*-----------------------------------------------------------------------*/ - -JRESULT jd_decomp ( - JDEC* jd, /* Initialized decompression object */ - int (*outfunc)(JDEC*, void*, JRECT*), /* RGB output function */ - uint8_t scale /* Output de-scaling factor (0 to 3) */ -) -{ - unsigned int x, y, mx, my; - uint16_t rst, rsc; - JRESULT rc; - - - if (scale > (JD_USE_SCALE ? 3 : 0)) return JDR_PAR; - jd->scale = scale; - - mx = jd->msx * 8; my = jd->msy * 8; /* Size of the MCU (pixel) */ - - jd->dcv[2] = jd->dcv[1] = jd->dcv[0] = 0; /* Initialize DC values */ - rst = rsc = 0; - - rc = JDR_OK; - for (y = 0; y < jd->height; y += my) { /* Vertical loop of MCUs */ - for (x = 0; x < jd->width; x += mx) { /* Horizontal loop of MCUs */ - if (jd->nrst && rst++ == jd->nrst) { /* Process restart interval if enabled */ - rc = restart(jd, rsc++); - if (rc != JDR_OK) return rc; - rst = 1; - } - rc = mcu_load(jd); /* Load an MCU (decompress huffman coded stream, dequantize and apply IDCT) */ - if (rc != JDR_OK) return rc; - rc = mcu_output(jd, outfunc, x, y); /* Output the MCU (YCbCr to RGB, scaling and output) */ - if (rc != JDR_OK) return rc; - } - } - - return rc; -} diff --git a/src/jpegio/tjpgd/tjpgd.h b/src/jpegio/tjpgd/tjpgd.h deleted file mode 100644 index 0831e22..0000000 --- a/src/jpegio/tjpgd/tjpgd.h +++ /dev/null @@ -1,123 +0,0 @@ -/*----------------------------------------------------------------------------/ -/ TJpgDec - Tiny JPEG Decompressor R0.03 include file (C)ChaN, 2021 -/----------------------------------------------------------------------------*/ -#ifndef DEF_TJPGDEC -#define DEF_TJPGDEC - -#ifdef __cplusplus -extern "C" { -#endif - -#include "tjpgdcnf.h" -#include - -/* Deviation from ChaN R0.03 (see src/jpegio/README.md): upstream selects the -/ fallback typedefs with `#if defined(_WIN32)`, while its own comment says the -/ test is meant to be "a compiler without stdint.h". mingw is both at once -- -/ it defines _WIN32 and ships stdint.h, where uint32_t is `unsigned int`, not -/ `unsigned long` -- so every mingw cross-build failed with conflicting types -/ for uint32_t. Test for the header, and keep the fallback for the compiler -/ the comment actually meant (MSVC before 2010 shipped no stdint.h). */ -#ifndef JD_HAS_STDINT /* -DJD_HAS_STDINT=0/1 overrides the detection */ -# if defined(__has_include) -# if __has_include() -# define JD_HAS_STDINT 1 -# else -# define JD_HAS_STDINT 0 -# endif -# elif defined(_MSC_VER) && _MSC_VER < 1600 -# define JD_HAS_STDINT 0 -# else -# define JD_HAS_STDINT 1 -# endif -#endif - -#if JD_HAS_STDINT -#include -#else /* VC++ before 2010, or another compiler without stdint.h */ -typedef unsigned char uint8_t; -typedef unsigned short uint16_t; -typedef short int16_t; -typedef unsigned long uint32_t; -typedef long int32_t; -#endif - -#if JD_FASTDECODE >= 1 -typedef int16_t jd_yuv_t; -#else -typedef uint8_t jd_yuv_t; -#endif - - -/* Error code */ -typedef enum { - JDR_OK = 0, /* 0: Succeeded */ - JDR_INTR, /* 1: Interrupted by output function */ - JDR_INP, /* 2: Device error or wrong termination of input stream */ - JDR_MEM1, /* 3: Insufficient memory pool for the image */ - JDR_MEM2, /* 4: Insufficient stream input buffer */ - JDR_PAR, /* 5: Parameter error */ - JDR_FMT1, /* 6: Data format error (may be broken data) */ - JDR_FMT2, /* 7: Right format but not supported */ - JDR_FMT3 /* 8: Not supported JPEG standard */ -} JRESULT; - - - -/* Rectangular region in the output image */ -typedef struct { - uint16_t left; /* Left end */ - uint16_t right; /* Right end */ - uint16_t top; /* Top end */ - uint16_t bottom; /* Bottom end */ -} JRECT; - - - -/* Decompressor object structure */ -typedef struct JDEC JDEC; -struct JDEC { - size_t dctr; /* Number of bytes available in the input buffer */ - uint8_t* dptr; /* Current data read ptr */ - uint8_t* inbuf; /* Bit stream input buffer */ - uint8_t dbit; /* Number of bits availavble in wreg or reading bit mask */ - uint8_t scale; /* Output scaling ratio */ - uint8_t msx, msy; /* MCU size in unit of block (width, height) */ - uint8_t qtid[3]; /* Quantization table ID of each component, Y, Cb, Cr */ - uint8_t ncomp; /* Number of color components 1:grayscale, 3:color */ - int16_t dcv[3]; /* Previous DC element of each component */ - uint16_t nrst; /* Restart inverval */ - uint16_t width, height; /* Size of the input image (pixel) */ - uint8_t* huffbits[2][2]; /* Huffman bit distribution tables [id][dcac] */ - uint16_t* huffcode[2][2]; /* Huffman code word tables [id][dcac] */ - uint8_t* huffdata[2][2]; /* Huffman decoded data tables [id][dcac] */ - int32_t* qttbl[4]; /* Dequantizer tables [id] */ -#if JD_FASTDECODE >= 1 - uint32_t wreg; /* Working shift register */ - uint8_t marker; /* Detected marker (0:None) */ -#if JD_FASTDECODE == 2 - uint8_t longofs[2][2]; /* Table offset of long code [id][dcac] */ - uint16_t* hufflut_ac[2]; /* Fast huffman decode tables for AC short code [id] */ - uint8_t* hufflut_dc[2]; /* Fast huffman decode tables for DC short code [id] */ -#endif -#endif - void* workbuf; /* Working buffer for IDCT and RGB output */ - jd_yuv_t* mcubuf; /* Working buffer for the MCU */ - void* pool; /* Pointer to available memory pool */ - size_t sz_pool; /* Size of momory pool (bytes available) */ - size_t (*infunc)(JDEC*, uint8_t*, size_t); /* Pointer to jpeg stream input function */ - void* device; /* Pointer to I/O device identifiler for the session */ -}; - - - -/* TJpgDec API functions */ -JRESULT jd_prepare (JDEC* jd, size_t (*infunc)(JDEC*,uint8_t*,size_t), void* pool, size_t sz_pool, void* dev); -JRESULT jd_decomp (JDEC* jd, int (*outfunc)(JDEC*,void*,JRECT*), uint8_t scale); - - -#ifdef __cplusplus -} -#endif - -#endif /* _TJPGDEC */ diff --git a/src/jpegio/tjpgd/tjpgdcnf.h b/src/jpegio/tjpgd/tjpgdcnf.h deleted file mode 100644 index d3e52ba..0000000 --- a/src/jpegio/tjpgd/tjpgdcnf.h +++ /dev/null @@ -1,32 +0,0 @@ -/*----------------------------------------------*/ -/* TJpgDec System Configurations R0.03 */ -/*----------------------------------------------*/ - -#define JD_SZBUF 512 -/* Specifies size of stream input buffer */ - -#define JD_FORMAT 1 -/* Specifies output pixel format. -/ 0: RGB888 (24-bit/pix) -/ 1: RGB565 (16-bit/pix) -/ 2: Grayscale (8-bit/pix) -*/ - -#define JD_USE_SCALE 1 -/* Switches output descaling feature. -/ 0: Disable -/ 1: Enable -*/ - -#define JD_TBLCLIP 1 -/* Use table conversion for saturation arithmetic. A bit faster, but increases 1 KB of code size. -/ 0: Disable -/ 1: Enable -*/ - -#define JD_FASTDECODE 1 -/* Optimization level -/ 0: Basic optimization. Suitable for 8/16-bit MCUs. -/ 1: + 32-bit barrel shifter. Suitable for 32-bit MCUs. -/ 2: + Table conversion for huffman decoding (wants 6 << HUFF_BIT bytes of RAM) -*/ diff --git a/tests/README.md b/tests/README.md index f07cd2c..6551664 100644 --- a/tests/README.md +++ b/tests/README.md @@ -3,6 +3,5 @@ | Script | Notes | |--------|-------| | `test_lifecycle_api.py` | Import-only: types expose `deinit` / `__del__`. Hardware soft-reset / idempotent ctor needs a board — contract in [`docs/idempotent-lifecycle.md`](../docs/idempotent-lifecycle.md); P4 `mipidsi` + Qualia `dotclockframebuffer.DotClockFramebuffer` bring-up in [`docs/soft-reset-and-bring-up.md`](../docs/soft-reset-and-bring-up.md). | -| `jpegio/test_jpegio.py` | The `jpegio` module against its corpus under unix MicroPython: ` tests/jpegio/test_jpegio.py` (`--record` rewrites `frames/golden_tjpgd.json`, TJpgDec's own per-frame/scale digests). Corpus + Pillow fidelity reference beside it (`make_corpus.py`, `reference.py`, `jpegwalk.py`; frames, `reference.json` and the golden under `frames/`, see its README). | Hardware smoke scripts live under [`tools/`](../tools/) (see `tools/README.md`). diff --git a/tests/jpegio/frames/README.md b/tests/jpegio/frames/README.md deleted file mode 100644 index 9f670d7..0000000 --- a/tests/jpegio/frames/README.md +++ /dev/null @@ -1,17 +0,0 @@ -# jpegio test corpus - -Generated by [`../make_corpus.py`](../make_corpus.py) (Pillow 12.3.0); marker sequences and sizes below come from [`../jpegwalk.py`](../jpegwalk.py). `reference.json` is the Pillow fidelity reference written by [`../reference.py`](../reference.py) -- per baseline frame and scale 0..3: RGB565 buffer sha256 and per-channel means. It is a tolerance reference, not a bit-exact golden (TJpgDec and libjpeg differ in IDCT rounding and chroma upsampling). `golden_tjpgd.json` is the bit-exact one: TJpgDec's own sha256 per frame and scale, written by [`../test_jpegio.py`](../test_jpegio.py) `--record` and asserted on every run. - -Synthetic frames share one procedural pattern: colour bars (top 40 %), an x/y/diagonal RGB gradient (40-70 %), a 1-px black/white checker (bottom left) and a red|blue split (bottom right). Marker sequences omit the RSTn markers inside the scan; their count is given with the DRI interval (a DRI of 0 means the segment is present but restart intervals are disabled). `TRAILING` = bytes after EOI. - -| file | what / how made | markers | size | sampling | bytes | DRI | notes | -|------|-----------------|---------|------|----------|-------|-----|-------| -| `baseline_jfif_320x240.jpg` | baseline JFIF still of the test pattern at 320x240; Pillow save(quality=90, subsampling='4:2:0') | `SOI APP0 DQT DQT SOF0 DHT DHT DHT DHT SOS EOI` | 320x240 SOF0 | 4:2:0 | 11172 | - | 2 DQT tables in 2 segments; APP0=JFIF | -| `baseline_jfif_64x48.jpg` | baseline JFIF still of the test pattern; Pillow save(quality=90, subsampling='4:2:0') | `SOI APP0 DQT DQT SOF0 DHT DHT DHT DHT SOS EOI` | 64x48 SOF0 | 4:2:0 | 1540 | - | 2 DQT tables in 2 segments; APP0=JFIF | -| `c920e_320x240_dri.jpg` | real camera frame: Logitech C920e over usbif UVC on ESP32-S3, captured by the usbif session 2026-09-03; DHT present; DRI present | `SOI DQT SOF0 DHT DHT DHT DHT COM APP0 DRI SOS EOI TRAILING` | 320x240 SOF0 | 4:2:2 | 8064 | 0 (0 RST) | 3 DQT tables in 1 segment; COM 8 bytes (all zero); APP0 payload is not JFIF (14 bytes, all zero); 22 bytes after EOI (ff001415..) | -| `dri_first_mjpeg_style_320x240.jpg` | restart_dri_320x240 with its segments re-ordered by jpegwalk to SOI DQT DQT SOF0 DHT DHT DHT DHT APP0 DRI SOS (APP0/JFIF after the tables, as a Logitech C920e emits UVC MJPEG); Pillow decodes it pixel-identical to restart_dri_320x240 | `SOI DQT DQT SOF0 DHT DHT DHT DHT APP0 DRI SOS EOI` | 320x240 SOF0 | 4:2:0 | 11348 | 4 (74 RST) | 2 DQT tables in 2 segments; APP0=JFIF | -| `grayscale_64x48.jpg` | baseline grayscale (one component): pattern(64,48).convert('L'); Pillow save(quality=90) | `SOI APP0 DQT SOF0 DHT DHT SOS EOI` | 64x48 SOF0 | gray | 960 | - | 1 DQT table in 1 segment; APP0=JFIF | -| `nodht_320x240.jpg` | baseline_jfif_320x240 with every DHT (FFC4) segment removed by jpegwalk -- the 'MJPEG without Huffman tables' case; a decoder must either inject the standard tables (ITU T.81 K.3) or refuse it; Pillow/libjpeg-turbo decodes it by injecting the standard tables (pixels identical to baseline_jfif_320x240) | `SOI APP0 DQT DQT SOF0 SOS EOI` | 320x240 SOF0 | 4:2:0 | 10740 | - | 2 DQT tables in 2 segments; APP0=JFIF | -| `odd_size_37x29.jpg` | baseline, odd dimensions so the right column and bottom row of MCUs are partial (37 = 2x16 + 5, 29 = 1x16 + 13); Pillow save(quality=90, subsampling='4:2:0') | `SOI APP0 DQT DQT SOF0 DHT DHT DHT DHT SOS EOI` | 37x29 SOF0 | 4:2:0 | 1427 | - | 2 DQT tables in 2 segments; APP0=JFIF | -| `progressive_320x240.jpg` | the 320x240 pattern saved progressive (SOF2): Pillow save(progressive=True, subsampling='4:2:0'); TJpgDec is baseline-only and must refuse it | `SOI APP0 DQT DQT SOF2 DHT DHT SOS DHT SOS DHT SOS DHT SOS DHT SOS DHT SOS SOS DHT SOS DHT SOS DHT SOS EOI` | 320x240 SOF2 | 4:2:0 | 6074 | - | 2 DQT tables in 2 segments; APP0=JFIF | -| `restart_dri_320x240.jpg` | as baseline_jfif_320x240 but with restart markers: Pillow save(..., restart_marker_blocks=4) -> DRI segment + RSTn every 4 MCUs | `SOI APP0 DQT DQT SOF0 DHT DHT DHT DHT DRI SOS EOI` | 320x240 SOF0 | 4:2:0 | 11348 | 4 (74 RST) | 2 DQT tables in 2 segments; APP0=JFIF | diff --git a/tests/jpegio/frames/baseline_jfif_320x240.jpg b/tests/jpegio/frames/baseline_jfif_320x240.jpg deleted file mode 100644 index 38fe65d466ae8a70f23f836018638b34ecd6aa26..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 11172 zcmeHN3sh5A7QG>S42Tg4YV9ai?TFx~sMJEM@iVAp5Cy5}N6e_BsGSaepo$`|)>=C_ ziaJHa@=>fHbQY;P_@RywQR#q+Hlkv!pcpVJ3PA%Q$-6!G<>e6)x=3eSU9L5WT+GTn z@1C>wIcMLO`@lDF4eA*(_3f#M$3ut*{}BEWO+kDI2l4|iNBD8-=H%q)=;Z3+;@qvf zYxnMMu5NDbJ$m(Y@8QwI&8=teo*teeFE6j|!ajX_i~9Bwd5Op(JU(3G=p=A*5{TU0 z+(p0og-;`oZm5wrk-G$`an+Z!Myy#NB&iK^w{weMQ2LRo;zRq{eQ}SP+YCN zcKt@x%{zB%?y-bK85mat;c9|wQdVW$CFnL*HNRc`eY~3bH<;BW%Fg(~xpTBN?uxOUc26#`` zde`m^4;CRwnk46XnnW$zMm3KB4|3fXEu$-rsST5pd}PXnx5%pE{3Nor+4_-93U2+n z_uALrz^#AJyLP*+>%TPse6=8q_U|=kD&}_>nW$<|$~mfzwo~O#2iZVH3#S)F7sfZA*JftC6_a*hWEy>ud=gvBEUYOI)FxDfWRAN~*JlRtq&jDs zkxekIwu$we3%(A(I6ic1^Y|c`O(g;Ji`E4w3ep-PMy55)OK3V3ePHIxb*tQR*3G^i zswvnPTd`~hj2dw9R8^aBX?|=q^MUHT*(JHgOLUQ2a$@gtNC})DOmGN35&!{wQVT(c zF+qoFa<`5#?0I11$s7*M6ad&bhy<2hC&>c%D#dwi`We-!t4+j@2^UA(CdGP&;tqa{ z^&tu?U#8Hk#!%6NmBBdOzf<}UxuxN8OCmrUTSq&nB0$oNOfgFPdn5Xg=mTZ?hE4G4GFa^z)4WpmB z08_2AlGoqd9g&=kAbvz!!!qAAAs|ZG;WCRT$z?)t`|WYkbS|ryHa5zn_d)C80W`wY9_rM17J%U((B0vz@-w<8VH3?%aVo1&WRp#S!BJLn-nh-{0&26=Juu20br_=`8tGkyQB#O5HW;lg_jsLhm{y!uN-q{`|A>t)14st}yD9_ZR4Ub*WcM&%U@pdPv2+P^b^PCQ-H z4(KmkM{5fp!6MI8{lE3%nhgIMK`FfeXrD>nYUB16S(^A>f2c0$BEU%<(Mnt0mGRV{ zpwgv&Ix#(0d<3JrDYn?u?+Om2%Ae>{X+h-x=X9#h52GO%HB{s>+Ya1}=>pVG^c&@> z@<(#b6AcCPypys~kG!8gRFbkT-wXvFk=v!kN%ftoa$1VfJ-OkKVt_li8ftWvoY%g? zX=l@us-G$~Qr&8uI0d6mZc9=%!nQcI91tl>0Nb#xE#}29KdZ!O6tE_xZ>W+5m&DB; z812MJQ(9SW+7%P$Uy){od|7={FMRlh;U&mtI=B$hj}rHiO%rbQbOGA9;8o>UlOY~WAw2&LK14czXhx4;Q7N(t%Op_+N}4?5l;&QiGTv0g3)*0`Gec- zB(R$AS70XxyL|DqG_b2SntkI3alyT!VUK=)_6g-e@h^BL5I-@xvqwuvhDR1_Y>9Rwjb``bOEg+z~B81<3LI<(jUd>$;O<( z^jZz&*-~h_OOc&^;3rt$qYln9_JYe8x;1EjMwobE6L6oXBi_c725Rh=RG)hJ8erT5 z=toex8dY-7FtPdxV@Go?^%#ZERk!sGlQxG_N*P888UA;dmeNtwz3N8cW$HP!Vb_h? zwMAkAfPD_iV?!_7Vglz&_Fy^{e>*e3v@P11u`}~4?~@|=_3!&+%e#v!Uo#yyv;Xr? z!vQp$$R{^lh68+LJ`64|kT^;7|C~@`%WchXX@h=CXSVpV2h`P_v7I41L;veHrx&k= zwAbKaWr^@vsJVWV_l>?F`?nVAW0?n~rU$0v@#!S^KK6XRNZKMyw%B0gCp<2ELB1zU zv0iSr@{FbMI)GLi&WlSY PvZo`=ALhY(KCb#1V~pw8 diff --git a/tests/jpegio/frames/baseline_jfif_64x48.jpg b/tests/jpegio/frames/baseline_jfif_64x48.jpg deleted file mode 100644 index a56c3bdaa63ceed2a25aec989872e1883ba4eb57..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1540 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 A%>V!Z diff --git a/tests/jpegio/frames/dri_first_mjpeg_style_320x240.jpg b/tests/jpegio/frames/dri_first_mjpeg_style_320x240.jpg deleted file mode 100644 index dbd2d69f24a0f735357c6a4877c9428ac79be464..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 11348 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 PsOIOjt`Az;|8D{SUw_lX diff --git a/tests/jpegio/frames/nodht_320x240.jpg b/tests/jpegio/frames/nodht_320x240.jpg deleted file mode 100644 index b8ee573ed4e86f0c4d40d9399d3ca76a45ec3cd7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10740 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 diff --git a/tests/jpegio/frames/progressive_320x240.jpg b/tests/jpegio/frames/progressive_320x240.jpg deleted file mode 100644 index 3084c960d5316706d37833ddfbf872e45993db5d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6074 zcmeHL3piET9$)*sy^eD_ClR98T!)4uQOe_}5akGY)vl}-+a0A-PT&)+W)owuiyW__gZ`J zHQGGd1{jWZ4t4-R06<^`qj$gxKqL^*1`7!`GL1|ok;sZvDut%3sI07{sHCJqpT2hY6Kxd9THiAOjcm4D5oQsim^APV;SrHJ zfWSnEOl0&QKodg63m7W|GKEG(6d+R@asUw!(P(6aM5NNl6a@k@dK)Mc5kO!N88Flr zt3bH6wXt}+ihHOQ?FoMgB8eEO5*M5LmWKuwTcyNFm&OFT`^*$yU&6e*JA#;_q_9`JZC;6UTpp;&wt#;-a^NFrM{>E)KViPFaxMw z)~S+Pr`QwqPy-%{GT%4|Xvu&)=38z9CA>K8y{;FEW{ep?5%@#Qay@+^4Cn7|6Mf@) zGT#_@G>TKxA!ZCb76lB)LvdH^&))Y+{l=8XQsBkGA}{;d8w(%JLj~Wx2S)8t=*;(f zf_HfQr;GSq`TFNVggZ}q{vteM9l3>U?p_eykmfD2^sx*p?92JW@xE98piZgDtPTCe zY8KhNqO9*bhqXsK_Z!_bkQP-rANQ1a^}k***eMaj6!>uhYhMTll$y>roiM7~C$L=o zs#DZqB#zxJt`;?xU3Lkq?Nxdf{&|m%z%!w_|JChM`pD+N)~9jJ{a*~1Wj_)`Q!HYi z<7-Uyh^lSR0RZTvC>?D$B~;#dArog2yH?x~9s>Yh3joYY)GF+ft<0h8&o>l6y3IbU z?>Yd~oN5C>InJ|+u$&?00f4PNxLV=TR%c+V9_Qmd%Y+vPS2Yc;c7#kABsDlK`q;)Y zV79g8OwhHA375{dMksIh#dk!4>O~V2aP@xayLwhW@0t#-o9GDB8w$tO$30{l{wpMS zV?SX6gh(V%(XCrn5CV~;YmDziv1(E4~F8Xv#(ik`ccu*tavfnC+bI8Mzi*a(f)Wy zR#5W&h!!`=jZ%K5SRh>d*M=XQjqLi&5x1*1iXGBO1?$Sw#ObUr^<(FRnL2m14(Y)B zE?@!zLP8KSjVRAAk+L$OQkO(s{_W8Y0-0fB>*CtJ&K=J(0nPGDPmh)@RTt0KZ7{ud z-n;L+ZKs=(Bexwc-IB5OT!+t{%|$b%cRtr?KWO zciotf$~z$2vMAPs*Q+W7E)Phz>^DUe8|hob{q4&~DI#*G{Q;JX_SZMtN>B6?Lgt=c z>N6v47SCzr=k}`;`j6HqoJgtZ4&AU$fS58{v<;Q6?$`Y!ciG&$4{gft;O_9V3 z^FNu>Nsvx*r-!&xfA@bK(n+4l2b0YGULK^6kNi~aNFN{hDcj}Y>r4~>_MY%QLqGr{ z%zymz+j{n!HcomTXUz{BN7tbw*F$-n)lyEn6cQ#H*pMG=@{ObGq5OrA1+Q2qbKy)NOx|3kXQHvMD$qDX5(J5RfyU_=0cYMK1gK29Bsf*3gj&cKpl&fU zZYYv*pZN>rFeI2u$8ecTd<10&Ft0KnJKlDtQTn#~;BK16swt>3cjFIDK@~l&nSx+f zX~dcnIj|ayo$J7J9{?Bw^_O3An+98>UyXuFg&h{Z^|*dbc%9G2diIy^QN=J~Z33Hs0gYmczQuUvG4ZQSYUT z_BveYbL+8&fVG%#Gl55S>Z-`Jwd3XvC_3v&0xV&Cm3DtPiK{}lxR$ZTVVE|=#o zKWeaMylLD2XSR!dWfDD$X{)A}o1nLJmo3}tXuHjkT{e($)y0O*v}L<&%B{6cP14Iv z(mU+}hvQAnQ4g}<3~B8q$jwcn<-qUWrrd&4v^y;@Yf1cJlOv~{aF(#E*7Vj+YO@hm}ap$iH#Cy9oELQ%7Q zE#^SC7)A&)b=*HR)sMPcFmw!cyMA~Gb$@v2@ef?{ANMsHPeHZbky0=f^_MVtu-2n3 F{{%y)RP+D< diff --git a/tests/jpegio/frames/reference.json b/tests/jpegio/frames/reference.json deleted file mode 100644 index 76a20d4..0000000 --- a/tests/jpegio/frames/reference.json +++ /dev/null @@ -1,603 +0,0 @@ -{ - "_about": [ - "Pillow fidelity reference for tests/jpegio/frames; written by tests/jpegio/reference.py.", - "scales[k]: decode at scale k (1/2**k) -> width x height RGB565 buffer, little-endian", - "uint16, row-major; sha256_rgb565 is the digest of that buffer; mean_rgb are per-channel", - "means of the 8-bit reference, mean_rgb565 the means of r&0xF8, g&0xFC, b&0xF8 (what a", - "decoder's RGB565 output unpacks to). Downscaling is a truncating integer box average of", - "the full libjpeg decode (as TJpgDec's mcu_output does), NOT draft()/reduce().", - "Compare with a tolerance: TJpgDec's IDCT, chroma replication and DC-only 1/8 differ." - ], - "pillow": "12.3.0", - "rgb565": "((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3), little-endian uint16 (native order on all PyDevices targets)", - "scale_dims": "width = source_width >> scale, height = source_height >> scale (partial-MCU remainder dropped, as TJpgDec does)", - "frames": { - "baseline_jfif_320x240.jpg": { - "sha256_file": "e94699a7290e129f8e1cfb24e3beef441aa9e0155fcfd3618807e0d044aed841", - "bytes": 11172, - "width": 320, - "height": 240, - "sof": "SOF0", - "sampling": "4:2:0", - "components": 3, - "dri": null, - "markers": "SOI APP0 DQT DQT SOF0 DHT DHT DHT DHT SOS EOI", - "scales": { - "0": { - "width": 320, - "height": 240, - "sha256_rgb565": "7080526506e0c21eb112b46ea79e2bbb6d6b0e730ed37f4d5c3cdd59a5324ef4", - "mean_rgb": [ - 127.2433, - 108.2792, - 125.872 - ], - "mean_rgb565": [ - 123.8593, - 106.9648, - 122.5144 - ] - }, - "1": { - "width": 160, - "height": 120, - "sha256_rgb565": "9f57b34a205cf1f884a20c156ba5b92dffba66e9684e1a4032eaf311fa472ee1", - "mean_rgb": [ - 127.0509, - 108.102, - 125.6853 - ], - "mean_rgb565": [ - 123.3308, - 106.6765, - 121.9887 - ] - }, - "2": { - "width": 80, - "height": 60, - "sha256_rgb565": "5b661b9fef344f03d4c9aaf193a999263f7f04c0cee291b7f46f00ff747054fb", - "mean_rgb": [ - 126.9865, - 108.0429, - 125.5894 - ], - "mean_rgb565": [ - 123.1633, - 106.5925, - 121.64 - ] - }, - "3": { - "width": 40, - "height": 30, - "sha256_rgb565": "13dd202945e4b105bc0897b096362448c11add633446f61cfa602044bb7ca8bf", - "mean_rgb": [ - 126.9417, - 108.0033, - 125.5508 - ], - "mean_rgb565": [ - 123.0333, - 106.6033, - 121.6333 - ] - } - } - }, - "baseline_jfif_64x48.jpg": { - "sha256_file": "15015df784e5947c294367f0eb2d2cca3d8d909fb5082b57c5aa18afdf68efb6", - "bytes": 1540, - "width": 64, - "height": 48, - "sof": "SOF0", - "sampling": "4:2:0", - "components": 3, - "dri": null, - "markers": "SOI APP0 DQT DQT SOF0 DHT DHT DHT DHT SOS EOI", - "scales": { - "0": { - "width": 64, - "height": 48, - "sha256_rgb565": "af9b927c88efdb1ca2b4e042f80dea2cc778fa13c829e71960dc2aae6814fb7f", - "mean_rgb": [ - 127.4395, - 107.7321, - 126.0452 - ], - "mean_rgb565": [ - 124.0443, - 106.3477, - 122.7474 - ] - }, - "1": { - "width": 32, - "height": 24, - "sha256_rgb565": "00462f0ae49c146bcdad4288283d29c245814249945b2068631a0c5e38e6f992", - "mean_rgb": [ - 127.1458, - 107.4505, - 125.7721 - ], - "mean_rgb565": [ - 123.7292, - 106.0365, - 122.3542 - ] - }, - "2": { - "width": 16, - "height": 12, - "sha256_rgb565": "c2de4bd14598e9ecfc06f8d977df5b812abc8c4676c530b0003c4b956aca3ca6", - "mean_rgb": [ - 127.0312, - 107.3802, - 125.6094 - ], - "mean_rgb565": [ - 123.5417, - 105.8542, - 121.625 - ] - }, - "3": { - "width": 8, - "height": 6, - "sha256_rgb565": "082f5afec0ee17965ccdc3e90bbf05d667a19cdfcd578bfa04bd877a27480c73", - "mean_rgb": [ - 126.9375, - 107.2917, - 125.5208 - ], - "mean_rgb565": [ - 123.0, - 106.0, - 121.3333 - ] - } - } - }, - "c920e_320x240_dri.jpg": { - "sha256_file": "31968f6632c73dd72703e18260bf684ab5adacc9e99529d7b21e331f3137f535", - "bytes": 8064, - "width": 320, - "height": 240, - "sof": "SOF0", - "sampling": "4:2:2", - "components": 3, - "dri": 0, - "markers": "SOI DQT SOF0 DHT DHT DHT DHT COM APP0 DRI SOS EOI TRAILING", - "scales": { - "0": { - "width": 320, - "height": 240, - "sha256_rgb565": "be7607923e546ad79e385ff86a96d7bdb8c0058a4d0603cccb8e97191d837772", - "mean_rgb": [ - 177.6693, - 132.6757, - 133.5783 - ], - "mean_rgb565": [ - 173.9996, - 131.185, - 130.1168 - ] - }, - "1": { - "width": 160, - "height": 120, - "sha256_rgb565": "b25b3713700f792500ee2b359c9dd88412ace9bc8dfa7c9b38373f7abd4bdf7b", - "mean_rgb": [ - 177.3574, - 132.3479, - 133.2605 - ], - "mean_rgb565": [ - 173.7704, - 130.849, - 129.8004 - ] - }, - "2": { - "width": 80, - "height": 60, - "sha256_rgb565": "505e9d03211b1aa89ab9c0e85f3a31f7cb53c629d01d734df64466aa358102b1", - "mean_rgb": [ - 177.2421, - 132.2442, - 133.159 - ], - "mean_rgb565": [ - 173.705, - 130.7358, - 129.705 - ] - }, - "3": { - "width": 40, - "height": 30, - "sha256_rgb565": "1bdd157b7988cc7cbbe4a95eb2c24c834472c7f426d37ca5a20b10ba0bc35b3b", - "mean_rgb": [ - 177.21, - 132.2125, - 133.1092 - ], - "mean_rgb565": [ - 173.7867, - 130.6667, - 129.6267 - ] - } - } - }, - "dri_first_mjpeg_style_320x240.jpg": { - "sha256_file": "fce7f537a5bacca2fd8dcebee4b3e6304cabd8f50fa5c442635d9dfb803dc0e8", - "bytes": 11348, - "width": 320, - "height": 240, - "sof": "SOF0", - "sampling": "4:2:0", - "components": 3, - "dri": 4, - "markers": "SOI DQT DQT SOF0 DHT DHT DHT DHT APP0 DRI SOS EOI", - "scales": { - "0": { - "width": 320, - "height": 240, - "sha256_rgb565": "7080526506e0c21eb112b46ea79e2bbb6d6b0e730ed37f4d5c3cdd59a5324ef4", - "mean_rgb": [ - 127.2433, - 108.2792, - 125.872 - ], - "mean_rgb565": [ - 123.8593, - 106.9648, - 122.5144 - ] - }, - "1": { - "width": 160, - "height": 120, - "sha256_rgb565": "9f57b34a205cf1f884a20c156ba5b92dffba66e9684e1a4032eaf311fa472ee1", - "mean_rgb": [ - 127.0509, - 108.102, - 125.6853 - ], - "mean_rgb565": [ - 123.3308, - 106.6765, - 121.9887 - ] - }, - "2": { - "width": 80, - "height": 60, - "sha256_rgb565": "5b661b9fef344f03d4c9aaf193a999263f7f04c0cee291b7f46f00ff747054fb", - "mean_rgb": [ - 126.9865, - 108.0429, - 125.5894 - ], - "mean_rgb565": [ - 123.1633, - 106.5925, - 121.64 - ] - }, - "3": { - "width": 40, - "height": 30, - "sha256_rgb565": "13dd202945e4b105bc0897b096362448c11add633446f61cfa602044bb7ca8bf", - "mean_rgb": [ - 126.9417, - 108.0033, - 125.5508 - ], - "mean_rgb565": [ - 123.0333, - 106.6033, - 121.6333 - ] - } - } - }, - "grayscale_64x48.jpg": { - "sha256_file": "5da348ab045a05fce417a9012e23752bdc763a58356d7d03b144829557179f27", - "bytes": 960, - "width": 64, - "height": 48, - "sof": "SOF0", - "sampling": "gray", - "components": 1, - "dri": null, - "markers": "SOI APP0 DQT SOF0 DHT DHT SOS EOI", - "scales": { - "0": { - "width": 64, - "height": 48, - "sha256_rgb565": "8eba70fc8596e08e44def1ed58ed51cc2009d070481d8ef6ef3bbb3653abec8e", - "mean_rgb": [ - 115.5286, - 115.5286, - 115.5286 - ], - "mean_rgb565": [ - 111.875, - 114.1367, - 111.875 - ] - }, - "1": { - "width": 32, - "height": 24, - "sha256_rgb565": "eb9fbd807d5b66d6eb6272d698bd417dfd6bb00a10e67abe1c028eb983ec23ae", - "mean_rgb": [ - 115.3594, - 115.3594, - 115.3594 - ], - "mean_rgb565": [ - 111.4583, - 113.8802, - 111.4583 - ] - }, - "2": { - "width": 16, - "height": 12, - "sha256_rgb565": "77953436e19b7eaec7b5edd796b794d81ad132f14dcb11a9c484c9d9be9a734c", - "mean_rgb": [ - 115.2865, - 115.2865, - 115.2865 - ], - "mean_rgb565": [ - 111.375, - 113.8542, - 111.375 - ] - }, - "3": { - "width": 8, - "height": 6, - "sha256_rgb565": "5c66e9361a9ad52f285084c3c3efeca001bc28397bd7ea589eda71f9712f7a79", - "mean_rgb": [ - 115.25, - 115.25, - 115.25 - ], - "mean_rgb565": [ - 111.6667, - 113.9167, - 111.6667 - ] - } - } - }, - "nodht_320x240.jpg": { - "sha256_file": "d87e5e2e2129fcdea4add8ac09b44ed1c3b0e58964fb31ebefc751bd8c988146", - "bytes": 10740, - "width": 320, - "height": 240, - "sof": "SOF0", - "sampling": "4:2:0", - "components": 3, - "dri": null, - "markers": "SOI APP0 DQT DQT SOF0 SOS EOI", - "scales": { - "0": { - "width": 320, - "height": 240, - "sha256_rgb565": "7080526506e0c21eb112b46ea79e2bbb6d6b0e730ed37f4d5c3cdd59a5324ef4", - "mean_rgb": [ - 127.2433, - 108.2792, - 125.872 - ], - "mean_rgb565": [ - 123.8593, - 106.9648, - 122.5144 - ] - }, - "1": { - "width": 160, - "height": 120, - "sha256_rgb565": "9f57b34a205cf1f884a20c156ba5b92dffba66e9684e1a4032eaf311fa472ee1", - "mean_rgb": [ - 127.0509, - 108.102, - 125.6853 - ], - "mean_rgb565": [ - 123.3308, - 106.6765, - 121.9887 - ] - }, - "2": { - "width": 80, - "height": 60, - "sha256_rgb565": "5b661b9fef344f03d4c9aaf193a999263f7f04c0cee291b7f46f00ff747054fb", - "mean_rgb": [ - 126.9865, - 108.0429, - 125.5894 - ], - "mean_rgb565": [ - 123.1633, - 106.5925, - 121.64 - ] - }, - "3": { - "width": 40, - "height": 30, - "sha256_rgb565": "13dd202945e4b105bc0897b096362448c11add633446f61cfa602044bb7ca8bf", - "mean_rgb": [ - 126.9417, - 108.0033, - 125.5508 - ], - "mean_rgb565": [ - 123.0333, - 106.6033, - 121.6333 - ] - } - } - }, - "odd_size_37x29.jpg": { - "sha256_file": "9efd41499254db78e4b92868095537b4f0f624c8d3fbea2f3a7d683286ef5c01", - "bytes": 1427, - "width": 37, - "height": 29, - "sof": "SOF0", - "sampling": "4:2:0", - "components": 3, - "dri": null, - "markers": "SOI APP0 DQT DQT SOF0 DHT DHT DHT DHT SOS EOI", - "scales": { - "0": { - "width": 37, - "height": 29, - "sha256_rgb565": "5f7b9495686f03d91250e8acb3eb0e75579cf61e8348e60d93a2eb968507af73", - "mean_rgb": [ - 127.4529, - 109.0522, - 128.205 - ], - "mean_rgb565": [ - 124.1454, - 107.5415, - 124.7046 - ] - }, - "1": { - "width": 18, - "height": 14, - "sha256_rgb565": "0c9846c9bc3b267e0bb0b91dd06cda6ad903ec741002180277561506ade15996", - "mean_rgb": [ - 128.5437, - 112.2262, - 128.7857 - ], - "mean_rgb565": [ - 125.0476, - 110.6508, - 125.1746 - ] - }, - "2": { - "width": 9, - "height": 7, - "sha256_rgb565": "d8c2e4957c329082965f2bf8258805ae3c1ee120034960b524404fd6c42bd68d", - "mean_rgb": [ - 128.4444, - 112.127, - 128.6508 - ], - "mean_rgb565": [ - 124.5714, - 110.7302, - 125.4603 - ] - }, - "3": { - "width": 4, - "height": 3, - "sha256_rgb565": "e61c57b332eb7bbce8dfdf2809b28c7fcd0dda2845d6380eac2c9eb98b9b1cd3", - "mean_rgb": [ - 133.6667, - 128.6667, - 132.9167 - ], - "mean_rgb565": [ - 130.0, - 127.0, - 128.6667 - ] - } - } - }, - "restart_dri_320x240.jpg": { - "sha256_file": "bd75b35a1555213b58d493e10738c5fc342572452a88a6e2279765b61fc2c1e9", - "bytes": 11348, - "width": 320, - "height": 240, - "sof": "SOF0", - "sampling": "4:2:0", - "components": 3, - "dri": 4, - "markers": "SOI APP0 DQT DQT SOF0 DHT DHT DHT DHT DRI SOS EOI", - "scales": { - "0": { - "width": 320, - "height": 240, - "sha256_rgb565": "7080526506e0c21eb112b46ea79e2bbb6d6b0e730ed37f4d5c3cdd59a5324ef4", - "mean_rgb": [ - 127.2433, - 108.2792, - 125.872 - ], - "mean_rgb565": [ - 123.8593, - 106.9648, - 122.5144 - ] - }, - "1": { - "width": 160, - "height": 120, - "sha256_rgb565": "9f57b34a205cf1f884a20c156ba5b92dffba66e9684e1a4032eaf311fa472ee1", - "mean_rgb": [ - 127.0509, - 108.102, - 125.6853 - ], - "mean_rgb565": [ - 123.3308, - 106.6765, - 121.9887 - ] - }, - "2": { - "width": 80, - "height": 60, - "sha256_rgb565": "5b661b9fef344f03d4c9aaf193a999263f7f04c0cee291b7f46f00ff747054fb", - "mean_rgb": [ - 126.9865, - 108.0429, - 125.5894 - ], - "mean_rgb565": [ - 123.1633, - 106.5925, - 121.64 - ] - }, - "3": { - "width": 40, - "height": 30, - "sha256_rgb565": "13dd202945e4b105bc0897b096362448c11add633446f61cfa602044bb7ca8bf", - "mean_rgb": [ - 126.9417, - 108.0033, - 125.5508 - ], - "mean_rgb565": [ - 123.0333, - 106.6033, - 121.6333 - ] - } - } - } - }, - "not_referenced": { - "progressive_320x240.jpg": "SOF2: TJpgDec is baseline-only and must refuse it" - } -} diff --git a/tests/jpegio/frames/restart_dri_320x240.jpg b/tests/jpegio/frames/restart_dri_320x240.jpg deleted file mode 100644 index 4641809584ccdcdb783b6b5eb829c7912c082208..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 11348 zcmeHMeOOc189yNb1XM(Wc7Cy1I}!TmEVh1Bjn+}CQxMRluEoqc%EWE>fvYHRtJb>F z+0|J=Dw07fAKJ%E9j)cDrWBPHkN`=Dpe-N`6n0{BAsHd={<4i4lSULyE*9OdXJ5;?j$J3EaU z?K*n2o2#4Kn6cx>jT!4P*3E6)#Bm-I#Gam>qunPxHBtQ3c(JFL1Q7^fj>yr)(a}Xb z#%+w)<`@1Nd5l6Wg5^TN4CLS;5PAskHRJ_(Itthqvb+QikfW3HC>K{Z7?3>J`#C2j(>XI3a1G{TbyU?6hD99Sn8-}UcQj)8C=>j)9=k)2VGnzPI~HR zlb`*?EbrMq^Iv#z!NNt0SN_WXl~u2w{)cT5yQB8}Fet6H#}%lp2%Iz1kR$s_gM zhs8m%f{e=)E-^W##rqo1E~{?w@lI3TIb_`B>YetEYr9WJWxEfvu~o%{DLW^@-05|j z42&!bmp;0m7SDJ|ihm9(2y@{NjCQzpFDBC@xUBkI(RMXlO~&0AB}JO9V{~U#_s#^9 zGVy^p$e;2_xfSFu7F2J5-!JEP-KVhH*a<0^LY7VSEjp*(UUZI@=1b@Vm?tw`+n*GE zn~qEM7fahNJaQBVE%(A2TkvUZ##cTlKAr9ON^HbeY74$HoAId!#iy|ypVmfv<+k9{ z*^IAbP<*Af<14cfpV}6D8k_NH2gO%zJ3gI__)2WSS86l9vO)2wZO5mv5uet`g!)x} z8tP~IxIwqpkTq$oLD3vt8>3Rqu8k?)5~Yeb1{Zrvy4G;s1?~i-EGW`j#-2~!mZF`} zkfN>GR%Dw2Fwf6}?72 z)|pfO*T7muW?N5DECV9cKm=@U^lO&U9PKjQ?i;jRr7_o-o7Zri0FG|IL8qL6ED9|( z3Vj(A&`%eUEH_aP)As}W`=ufXM9KksB3%K++6%mAPBs%5*8{Oy#V}$HL|+3JKTQsI z8q_!F1=Q-&9&H>A?W+;U`zoYdBQYq5-R&BsejWQRp->qK3kfs8Q=~Xn}__(B2O4ee{iBGg- z7Vzkr@d0U8$i@j&zXyDPmlQFET@TZ#OF+KWEi2i`8#V94eXcNCj%7J6y-9qqPc~

KFMv?2w>My!-v6!jAR!In}?!IZBUTE#T z16U!ATgH=OE)kLc3ck^%kw$x%$P!B2lxrK=K^6F-ZV=XfES(gdHR!|MOs_=o-WZVb zQW4gi+?Jwwtd2dG1u6s+&yG6`k~Rv>tC@?&bt2nuWS?LS-&(b#--s)*_H5NT8J-rN ze+b9ghQxktPzJN;Io55(HAtV%PRWVP-s1}bI%H+)mQJia!%K_Pj~thGgju!?uI;Z* zgr!;|;70FI*c0JH&k2Dy}j94b$SH$6#g8w}ree7h}-pt0RGi>o@pMvsnMIuD%>9QV9%SKc-jql~#1 zF}kg+Fe;2rZC8~4rqqfPCOJlPyecqiUGpSk+6#8a24r@vScp+k9!9gN&Xu=yq?c02 zGBrxOE#sG1xp-^Ml3I-BVMGUFlr#MiZxaWpZ(m}PuNGqTXNYB>po8~#`oRK09j-UMhBzvzp2P)wkq3zjwsmmJN3N9UyZ@&!?#U~ViTF~8I_)@ zUT{KhI>elgQ8YqkI~Wy4@f(I7A}vU8jcj7PS_1PvsYxcdlmyln%ibeidxAKBeIV^j zsS_)xrm99p$_S%3%Wg>;bOmIIM0Z%N>&(z2qV)+kK{l#9#zpNlHp7elJ4OYR>C2A2 zy@>uebh=N1$R%t-qVX%Er26!{YL|TGO^n(CnLFM54xvbr+?xhfk%a+H>JlyM8l#V< zeg{I7`SwVdHzb!Zx_jbD#?&YHFQN*YjfsqlcDhqCRR?V{6Qd>-R(Hdx)3A+fbh0@Z z*-00zyJ{*585u_Rl%@-G^4=Grt4_*|$1(bR863e-sU zWESbHRm`uUQN5&O=VtqtKH?u2>k~q4Z;^af+wm3<0MA)IlJs2Yeup%L4>@h(#1>(TvUv7{YB6?mC$VNEy9bCeJzZBM>P7c!gUK#KRhO;hW==u;T!-I>O?OmYZe zPw@Is2zEA-*$jdW$TUq4hd~rO<}+d{ONm_DK8UYRSiJzFxm261Yu4Hr1bJT=BD1MGHSf&KVekmykU1%eq)@Z#l0#F#0_@M+>8?jz0uY^ zxmeb|9zZEDikUX=E{HRfA;WqPZc6zB-xS$OmqI3Dt}z>uI-b5{9S~NiFrH!rxP0is z{eMO*cejHl*dx`EMEZO2atYSigIRmT7^%*H>I|F4k>C=0l-M3IhL22^?SaStEqSI8 z^Ct+UbXmN&BG3KtTJN-YL@x6u6I+esG{!&6<8G3FvDzMt*dxa9Vby(Pv*>$X29MU* zqr~=zF(Q-U?9p0#l-M3IhL248d35<;9^LzSQXh{V+4+XA=G}+OW+B0)_Glb?#27x! zH*BgiY!ZQkpgl@#j~K&8CjC5GJ(x%LekR_>qephW5sx0~xig%jM;?id$m9uRGV(}l TfK2*%bje^IUHaqCIx7AP%7qb- diff --git a/tests/jpegio/jpegwalk.py b/tests/jpegio/jpegwalk.py deleted file mode 100644 index 811b6a3..0000000 --- a/tests/jpegio/jpegwalk.py +++ /dev/null @@ -1,198 +0,0 @@ -"""jpegwalk -- a tiny JPEG marker walker. - -Plain Python with no third-party imports, written so it also runs under -MicroPython (the jpegio unix tests can import it to assert the corpus's -marker sequences). No regexes: JPEG entropy-coded data is binary and the -only safe way to find the next marker is to walk it byte by byte honouring -byte stuffing (FF 00) and RSTn markers. - -walk(data) -> list of segments, each a tuple - - (marker, name, offset, raw) - -where ``raw`` is the exact bytes of that segment: the FF xx marker, plus the -two-byte length and payload for markers that carry one. The SOS segment's -``raw`` also carries the entropy-coded scan data that follows it, up to the -next non-RSTn marker, so RSTn markers stay inside the scan data. Therefore - - b"".join(s[3] for s in walk(data)) == data - -and a file can be rebuilt after dropping or re-ordering segments. -""" - -_NAMES = { - 0x01: "TEM", - 0xC0: "SOF0", 0xC1: "SOF1", 0xC2: "SOF2", 0xC3: "SOF3", - 0xC4: "DHT", - 0xC5: "SOF5", 0xC6: "SOF6", 0xC7: "SOF7", - 0xC8: "JPG", 0xC9: "SOF9", 0xCA: "SOF10", 0xCB: "SOF11", - 0xCC: "DAC", 0xCD: "SOF13", 0xCE: "SOF14", 0xCF: "SOF15", - 0xD8: "SOI", 0xD9: "EOI", 0xDA: "SOS", 0xDB: "DQT", 0xDC: "DNL", - 0xDD: "DRI", 0xDE: "DHP", 0xDF: "EXP", 0xFE: "COM", -} - -SOF_MARKERS = (0xC0, 0xC1, 0xC2, 0xC3, 0xC5, 0xC6, 0xC7, - 0xC9, 0xCA, 0xCB, 0xCD, 0xCE, 0xCF) - - -def marker_name(m): - if 0xD0 <= m <= 0xD7: - return "RST%d" % (m - 0xD0) - if 0xE0 <= m <= 0xEF: - return "APP%d" % (m - 0xE0) - if 0xF0 <= m <= 0xFD: - return "JPG%d" % (m - 0xF0) - return _NAMES.get(m, "0xFF%02X" % m) - - -def _standalone(m): - return m == 0xD8 or m == 0xD9 or m == 0x01 or (0xD0 <= m <= 0xD7) - - -def walk(data): - data = bytes(data) - n = len(data) - segs = [] - pos = 0 - while pos < n: - if data[pos] != 0xFF: - raise ValueError("expected a marker at offset %d, got 0x%02X" % (pos, data[pos])) - start = pos - while pos + 1 < n and data[pos + 1] == 0xFF: # fill bytes - pos += 1 - if pos + 1 >= n: - raise ValueError("truncated marker at offset %d" % start) - m = data[pos + 1] - if _standalone(m): - pos += 2 - segs.append((m, marker_name(m), start, data[start:pos])) - if m == 0xD9: - if pos != n: - segs.append((None, "TRAILING", pos, data[pos:])) - break - continue - if pos + 4 > n: - raise ValueError("truncated segment length at offset %d" % pos) - length = (data[pos + 2] << 8) | data[pos + 3] - if length < 2 or pos + 2 + length > n: - raise ValueError("bad segment length %d at offset %d" % (length, pos)) - pos += 2 + length - if m == 0xDA: - # Entropy-coded data: skip to the next marker that is not a - # stuffed zero (FF 00), an RSTn (FF D0..D7) or a fill byte. - p = pos - while True: - q = data.find(b"\xff", p) - if q < 0 or q + 1 >= n: - p = n - break - b = data[q + 1] - if b == 0x00 or (0xD0 <= b <= 0xD7): - p = q + 2 - continue - if b == 0xFF: - p = q + 1 - continue - p = q - break - pos = p - segs.append((m, marker_name(m), start, data[start:pos])) - return segs - - -def rebuild(segs): - return b"".join(s[3] for s in segs) - - -def sequence(segs): - """Marker names in file order; RSTn markers live inside SOS and are not listed.""" - return [s[1] for s in segs] - - -def sof_info(seg): - """Decode a SOFn payload -> dict(precision, height, width, components=[(id, h, v, tq), ...]).""" - raw = seg[3] - p = 4 - info = { - "precision": raw[p], - "height": (raw[p + 1] << 8) | raw[p + 2], - "width": (raw[p + 3] << 8) | raw[p + 4], - } - nc = raw[p + 5] - comps = [] - p += 6 - for _ in range(nc): - comps.append((raw[p], raw[p + 1] >> 4, raw[p + 1] & 0x0F, raw[p + 2])) - p += 3 - info["components"] = comps - return info - - -def sampling_name(comps): - """'4:4:4' / '4:2:2' / '4:2:0' / 'gray' / 'HxV' from SOF component sampling factors.""" - if len(comps) == 1: - return "gray" - h, v = comps[0][1], comps[0][2] - if (h, v) == (1, 1): - return "4:4:4" - if (h, v) == (2, 1): - return "4:2:2" - if (h, v) == (2, 2): - return "4:2:0" - return "%dx%d" % (h, v) - - -def dri_interval(seg): - raw = seg[3] - return (raw[4] << 8) | raw[5] - - -def count_rst(seg): - """Number of RSTn markers inside an SOS segment's scan data.""" - raw = seg[3] - n = 0 - p = 0 - while True: - q = raw.find(b"\xff", p) - if q < 0 or q + 1 >= len(raw): - return n - if 0xD0 <= raw[q + 1] <= 0xD7: - n += 1 - p = q + 1 - - -def describe(data): - """Summary dict for a whole file: sequence, dims, sampling, DRI, RST count, byte size.""" - segs = walk(data) - d = {"bytes": len(data), "sequence": sequence(segs)} - for s in segs: - if s[0] in SOF_MARKERS and "width" not in d: - i = sof_info(s) - d["width"] = i["width"] - d["height"] = i["height"] - d["precision"] = i["precision"] - d["components"] = len(i["components"]) - d["sampling"] = sampling_name(i["components"]) - d["sof"] = s[1] - elif s[0] == 0xDD: - d["dri"] = dri_interval(s) - elif s[0] == 0xDA: - d["rst_markers"] = d.get("rst_markers", 0) + count_rst(s) - return d - - -def format_description(d): - parts = ["%dx%d" % (d.get("width", 0), d.get("height", 0)), - d.get("sof", "?"), d.get("sampling", "?"), - "%d bytes" % d["bytes"]] - if "dri" in d: - parts.append("DRI=%d (%d RST)" % (d["dri"], d.get("rst_markers", 0))) - return " ".join(d["sequence"]) + " | " + ", ".join(parts) - - -if __name__ == "__main__": - import sys - for path in sys.argv[1:]: - with open(path, "rb") as f: - data = f.read() - print("%s: %s" % (path, format_description(describe(data)))) diff --git a/tests/jpegio/make_corpus.py b/tests/jpegio/make_corpus.py deleted file mode 100644 index 561ef2c..0000000 --- a/tests/jpegio/make_corpus.py +++ /dev/null @@ -1,244 +0,0 @@ -#!/usr/bin/env python3 -"""Build the jpegio test corpus in frames/ and write frames/README.md. - -Run: python3 tests/jpegio/make_corpus.py (needs Pillow; 12.3 used) - -Every synthetic frame is the same procedural test pattern, drawn in pure -Python and encoded by Pillow/libjpeg: - - * top 40 % -- eight 100 % colour bars (white, yellow, cyan, green, - magenta, red, blue, black): hard chroma edges every w/8 px - * 40 % .. 70 % -- gradient: R along x, G along y, B along the anti-diagonal - * bottom band -- left half a 1-px black/white checker (the highest - spatial frequency an 8x8 DCT can carry, exercises the - AC tables and 4:2:0 chroma against a luma edge); right - half a pure-red / pure-blue split (a saturated chroma - edge that chroma subsampling has to straddle) - -Files that are derived (nodht, dri_first) are made by re-writing the marker -segments with jpegwalk -- never by regex on the binary. Real camera frames -named c920e_*.jpg are never touched; they are only described in the README. -""" -import os -import sys - -from PIL import Image - -HERE = os.path.dirname(os.path.abspath(__file__)) -sys.path.insert(0, HERE) -import jpegwalk # noqa: E402 - -FRAMES = os.path.join(HERE, "frames") -QUALITY = 90 -RESTART_BLOCKS = 4 # DRI interval in MCUs for the restart frames - -C920E_PROVENANCE = ("Logitech C920e over usbif UVC on ESP32-S3, captured by the usbif " - "session 2026-09-03; DHT present; DRI present") - -BARS = [(255, 255, 255), (255, 255, 0), (0, 255, 255), (0, 255, 0), - (255, 0, 255), (255, 0, 0), (0, 0, 255), (0, 0, 0)] - - -def pattern(w, h): - """The synthetic test pattern as a Pillow RGB image (see module docstring).""" - y_bars = max(1, (h * 2) // 5) - y_grad = max(y_bars + 1, (h * 7) // 10) - buf = bytearray(w * h * 3) - i = 0 - for y in range(h): - for x in range(w): - if y < y_bars: - r, g, b = BARS[x * 8 // w] - elif y < y_grad: - r = x * 255 // max(1, w - 1) - g = (y - y_bars) * 255 // max(1, y_grad - y_bars - 1) - b = 255 - (x + y) * 255 // max(1, w + h - 2) - elif x < w // 2: - r = g = b = 255 if (x + y) & 1 else 0 - else: - r, g, b = (255, 0, 0) if x < (w * 3) // 4 else (0, 0, 255) - buf[i] = r - buf[i + 1] = g - buf[i + 2] = b - i += 3 - return Image.frombytes("RGB", (w, h), bytes(buf)) - - -def write(name, data): - with open(os.path.join(FRAMES, name), "wb") as f: - f.write(data) - return data - - -def read(name): - with open(os.path.join(FRAMES, name), "rb") as f: - return f.read() - - -def save(name, im, **kw): - kw.setdefault("quality", QUALITY) - im.save(os.path.join(FRAMES, name), "JPEG", **kw) - return read(name) - - -def decoded(name): - return Image.open(os.path.join(FRAMES, name)).convert("RGB").tobytes() - - -def strip_dht(data): - segs = [s for s in jpegwalk.walk(data) if s[0] != 0xC4] - out = jpegwalk.rebuild(segs) - seq = jpegwalk.sequence(jpegwalk.walk(out)) - assert "DHT" not in seq, seq - for must in ("SOI", "SOF0", "SOS", "EOI"): - assert must in seq, (must, seq) - return out - - -def dri_first(data): - """Re-order a JFIF file's segments the way a Logitech C920e emits UVC MJPEG: - SOI DQT.. SOF0 DHT.. APP0 DRI SOS (i.e. NOT JFIF-first).""" - order = ["SOI", "DQT", "SOF0", "DHT", "APP0", "DRI", "SOS", "EOI"] - by = {} - for s in jpegwalk.walk(data): - by.setdefault(s[1], []).append(s) - segs = [] - for name in order: - segs.extend(by.pop(name)) - assert not by, "unexpected segments: %s" % sorted(by) - return jpegwalk.rebuild(segs) - - -# name -> (how it is made) -DESCRIPTIONS = {} - - -def notes(data): - """Walker-derived facts a marker sequence alone does not show.""" - out = [] - dqt_tables = dqt_segs = 0 - for m, name, _off, raw in jpegwalk.walk(data): - if name == "APP0": - ident = raw[4:9] - out.append("APP0=JFIF" if ident == b"JFIF\0" else - "APP0 payload is not JFIF (%d bytes, %s)" % ( - len(raw) - 4, "all zero" if not any(raw[4:]) else raw[4:12].hex())) - elif m == 0xDB: - dqt_segs += 1 - p = 4 - while p < len(raw): - dqt_tables += 1 - p += 1 + (128 if raw[p] >> 4 else 64) - elif name == "COM": - out.append("COM %d bytes%s" % (len(raw) - 4, " (all zero)" if not any(raw[4:]) else "")) - elif name == "TRAILING": - out.append("%d bytes after EOI (%s..)" % (len(raw), raw[:4].hex())) - out.insert(0, "%d DQT table%s in %d segment%s" % ( - dqt_tables, "s" if dqt_tables != 1 else "", dqt_segs, "s" if dqt_segs != 1 else "")) - return "; ".join(out) - - -def build(): - os.makedirs(FRAMES, exist_ok=True) - p64 = pattern(64, 48) - p320 = pattern(320, 240) - - DESCRIPTIONS["baseline_jfif_64x48.jpg"] = ( - "baseline JFIF still of the test pattern; Pillow save(quality=%d, subsampling='4:2:0')" % QUALITY) - save("baseline_jfif_64x48.jpg", p64, subsampling="4:2:0") - - DESCRIPTIONS["baseline_jfif_320x240.jpg"] = ( - "baseline JFIF still of the test pattern at 320x240; Pillow save(quality=%d, " - "subsampling='4:2:0')" % QUALITY) - base320 = save("baseline_jfif_320x240.jpg", p320, subsampling="4:2:0") - - DESCRIPTIONS["restart_dri_320x240.jpg"] = ( - "as baseline_jfif_320x240 but with restart markers: Pillow " - "save(..., restart_marker_blocks=%d) -> DRI segment + RSTn every %d MCUs" - % (RESTART_BLOCKS, RESTART_BLOCKS)) - restart = save("restart_dri_320x240.jpg", p320, subsampling="4:2:0", - restart_marker_blocks=RESTART_BLOCKS) - assert decoded("restart_dri_320x240.jpg") == decoded("baseline_jfif_320x240.jpg") - - DESCRIPTIONS["nodht_320x240.jpg"] = ( - "baseline_jfif_320x240 with every DHT (FFC4) segment removed by jpegwalk -- the " - "'MJPEG without Huffman tables' case; a decoder must either inject the standard " - "tables (ITU T.81 K.3) or refuse it") - write("nodht_320x240.jpg", strip_dht(base320)) - try: - same = decoded("nodht_320x240.jpg") == decoded("baseline_jfif_320x240.jpg") - DESCRIPTIONS["nodht_320x240.jpg"] += ( - "; Pillow/libjpeg-turbo decodes it by injecting the standard tables (pixels %s " - "baseline_jfif_320x240)" % ("identical to" if same else "DIFFER from")) - except Exception as e: # noqa: BLE001 - DESCRIPTIONS["nodht_320x240.jpg"] += "; Pillow refuses it (%s: %s)" % (type(e).__name__, e) - - DESCRIPTIONS["progressive_320x240.jpg"] = ( - "the 320x240 pattern saved progressive (SOF2): Pillow save(progressive=True, " - "subsampling='4:2:0'); TJpgDec is baseline-only and must refuse it") - save("progressive_320x240.jpg", p320, subsampling="4:2:0", progressive=True) - - DESCRIPTIONS["odd_size_37x29.jpg"] = ( - "baseline, odd dimensions so the right column and bottom row of MCUs are partial " - "(37 = 2x16 + 5, 29 = 1x16 + 13); Pillow save(quality=%d, subsampling='4:2:0')" % QUALITY) - save("odd_size_37x29.jpg", pattern(37, 29), subsampling="4:2:0") - - DESCRIPTIONS["grayscale_64x48.jpg"] = ( - "baseline grayscale (one component): pattern(64,48).convert('L'); " - "Pillow save(quality=%d)" % QUALITY) - save("grayscale_64x48.jpg", p64.convert("L")) - - DESCRIPTIONS["dri_first_mjpeg_style_320x240.jpg"] = ( - "restart_dri_320x240 with its segments re-ordered by jpegwalk to SOI DQT DQT SOF0 " - "DHT DHT DHT DHT APP0 DRI SOS (APP0/JFIF after the tables, as a Logitech C920e " - "emits UVC MJPEG); Pillow decodes it pixel-identical to restart_dri_320x240") - write("dri_first_mjpeg_style_320x240.jpg", dri_first(restart)) - assert decoded("dri_first_mjpeg_style_320x240.jpg") == decoded("restart_dri_320x240.jpg") - seq = jpegwalk.describe(read("dri_first_mjpeg_style_320x240.jpg"))["sequence"] - assert seq[:3] == ["SOI", "DQT", "DQT"] and seq[3] == "SOF0" and seq[-3:] == ["DRI", "SOS", "EOI"], seq - assert seq.index("APP0") > seq.index("DHT"), seq - - -def readme(): - names = sorted(n for n in os.listdir(FRAMES) if n.lower().endswith(".jpg")) - lines = [ - "# jpegio test corpus", - "", - "Generated by [`../make_corpus.py`](../make_corpus.py) (Pillow %s); marker sequences and" - " sizes below come from [`../jpegwalk.py`](../jpegwalk.py). `reference.json` is the" - " Pillow fidelity reference written by [`../reference.py`](../reference.py) -- per" - " baseline frame and scale 0..3: RGB565 buffer sha256 and per-channel means. It is a" - " tolerance reference, not a bit-exact golden (TJpgDec and libjpeg differ in IDCT" - " rounding and chroma upsampling). `golden_tjpgd.json` is the bit-exact one: TJpgDec's" - " own sha256 per frame and scale, written by [`../test_jpegio.py`](../test_jpegio.py)" - " `--record` and asserted on every run." % Image.__version__, - "", - "Synthetic frames share one procedural pattern: colour bars (top 40 %), an x/y/diagonal" - " RGB gradient (40-70 %), a 1-px black/white checker (bottom left) and a red|blue" - " split (bottom right). Marker sequences omit the RSTn markers inside the scan; their" - " count is given with the DRI interval (a DRI of 0 means the segment is present but restart" - " intervals are disabled). `TRAILING` = bytes after EOI.", - "", - "| file | what / how made | markers | size | sampling | bytes | DRI | notes |", - "|------|-----------------|---------|------|----------|-------|-----|-------|", - ] - for name in names: - d = jpegwalk.describe(read(name)) - if name.startswith("c920e_"): - what = "real camera frame: %s" % C920E_PROVENANCE - else: - what = DESCRIPTIONS.get(name, "(not produced by make_corpus.py)") - dri = "%d (%d RST)" % (d["dri"], d.get("rst_markers", 0)) if "dri" in d else "-" - lines.append("| `%s` | %s | `%s` | %dx%d %s | %s | %d | %s | %s |" % ( - name, what, " ".join(d["sequence"]), d["width"], d["height"], d.get("sof", "?"), - d.get("sampling", "?"), d["bytes"], dri, notes(read(name)))) - lines.append("") - with open(os.path.join(FRAMES, "README.md"), "w") as f: - f.write("\n".join(lines)) - return names - - -if __name__ == "__main__": - build() - for name in readme(): - print("%-36s %s" % (name, jpegwalk.format_description(jpegwalk.describe(read(name))))) diff --git a/tests/jpegio/reference.py b/tests/jpegio/reference.py deleted file mode 100644 index d563192..0000000 --- a/tests/jpegio/reference.py +++ /dev/null @@ -1,170 +0,0 @@ -#!/usr/bin/env python3 -"""Pillow fidelity reference for the jpegio corpus -> frames/reference.json. - -Run: python3 tests/jpegio/reference.py (needs Pillow; 12.3 used) - -For every baseline (SOF0) frame in frames/ that Pillow can decode: - - 1. full decode with Pillow to 8-bit RGB (libjpeg-turbo: islow IDCT, "fancy" - triangle chroma upsampling; grayscale is expanded to R=G=B); - 2. for scale 0..3, integer box averaging of that full decode in pure Python: - each output pixel is the sum of a (1<> scale) x floor(h >> scale). - That mirrors what TJpgDec R0.03 does in mcu_output(): it averages the same - squares with the same truncating shift and clips per MCU so the remainder - columns/rows of a partial MCU are dropped. Pillow's draft() (libjpeg's - DCT-domain scaling) and reduce() (rounds to nearest) are deliberately NOT - used: neither matches TJpgDec's arithmetic. - 3. pack to RGB565 exactly as tjpgd.c does under JD_FORMAT 1, - ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3), stored as little-endian - uint16 (native order on every PyDevices target), row-major, no stride; - 4. record width, height, sha256 of the packed buffer, and per-channel means - of the 8-bit image (mean_rgb) and of the channels recoverable from the - RGB565 buffer, i.e. r&0xF8, g&0xFC, b&0xF8 (mean_rgb565). - -This is a FIDELITY reference, not a bit-exact golden: TJpgDec's fixed-point -IDCT, nearest-neighbour chroma replication and (at scale 3) DC-only blocks all -differ from libjpeg by a little, so a test compares means (and, if it wants, -per-pixel error against a decode of the same frame) with a tolerance. The -sha256 is there so an exact match, should one occur, can be recognised. -""" -import hashlib -import json -import os -import sys - -from PIL import Image - -HERE = os.path.dirname(os.path.abspath(__file__)) -sys.path.insert(0, HERE) -import jpegwalk # noqa: E402 - -FRAMES = os.path.join(HERE, "frames") -OUT = os.path.join(FRAMES, "reference.json") -SCALES = (0, 1, 2, 3) - - -def box_down(rgb, w, h, scale): - """Truncating integer box average of packed 8-bit RGB by 2**scale (TJpgDec style).""" - if scale == 0: - return bytes(rgb), w, h - f = 1 << scale - sh = 2 * scale - ow, oh = w >> scale, h >> scale - out = bytearray(ow * oh * 3) - o = 0 - for oy in range(oh): - y0 = oy * f - for ox in range(ow): - r = g = b = 0 - for yy in range(y0, y0 + f): - base = (yy * w + ox * f) * 3 - row = rgb[base:base + f * 3] - r += sum(row[0::3]) - g += sum(row[1::3]) - b += sum(row[2::3]) - out[o] = r >> sh - out[o + 1] = g >> sh - out[o + 2] = b >> sh - o += 3 - return bytes(out), ow, oh - - -def pack_rgb565(rgb): - n = len(rgb) // 3 - out = bytearray(n * 2) - for i in range(n): - j = 3 * i - v = ((rgb[j] & 0xF8) << 8) | ((rgb[j + 1] & 0xFC) << 3) | (rgb[j + 2] >> 3) - out[2 * i] = v & 0xFF - out[2 * i + 1] = v >> 8 - return bytes(out) - - -def means(rgb): - n = len(rgb) // 3 - return [round(sum(rgb[c::3]) / n, 4) for c in range(3)] - - -def means_565(rgb): - n = len(rgb) // 3 - return [round(sum(v & 0xF8 for v in rgb[0::3]) / n, 4), - round(sum(v & 0xFC for v in rgb[1::3]) / n, 4), - round(sum(v & 0xF8 for v in rgb[2::3]) / n, 4)] - - -def reference_for(path): - im = Image.open(path).convert("RGB") - w, h = im.size - full = im.tobytes() - scales = {} - for s in SCALES: - rgb, ow, oh = box_down(full, w, h, s) - packed = pack_rgb565(rgb) - scales[str(s)] = { - "width": ow, - "height": oh, - "sha256_rgb565": hashlib.sha256(packed).hexdigest(), - "mean_rgb": means(rgb), - "mean_rgb565": means_565(rgb), - } - return scales - - -def main(): - doc = { - "_about": [ - "Pillow fidelity reference for tests/jpegio/frames; written by tests/jpegio/reference.py.", - "scales[k]: decode at scale k (1/2**k) -> width x height RGB565 buffer, little-endian", - "uint16, row-major; sha256_rgb565 is the digest of that buffer; mean_rgb are per-channel", - "means of the 8-bit reference, mean_rgb565 the means of r&0xF8, g&0xFC, b&0xF8 (what a", - "decoder's RGB565 output unpacks to). Downscaling is a truncating integer box average of", - "the full libjpeg decode (as TJpgDec's mcu_output does), NOT draft()/reduce().", - "Compare with a tolerance: TJpgDec's IDCT, chroma replication and DC-only 1/8 differ.", - ], - "pillow": Image.__version__, - "rgb565": "((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3), little-endian uint16 (native order on all PyDevices targets)", - "scale_dims": "width = source_width >> scale, height = source_height >> scale (partial-MCU remainder dropped, as TJpgDec does)", - "frames": {}, - "not_referenced": {}, - } - for name in sorted(os.listdir(FRAMES)): - if not name.lower().endswith(".jpg"): - continue - path = os.path.join(FRAMES, name) - with open(path, "rb") as f: - data = f.read() - d = jpegwalk.describe(data) - entry = { - "sha256_file": hashlib.sha256(data).hexdigest(), - "bytes": len(data), - "width": d["width"], - "height": d["height"], - "sof": d["sof"], - "sampling": d["sampling"], - "components": d["components"], - "dri": d.get("dri"), - "markers": " ".join(d["sequence"]), - } - if d["sof"] != "SOF0": - doc["not_referenced"][name] = "%s: TJpgDec is baseline-only and must refuse it" % d["sof"] - continue - try: - entry["scales"] = reference_for(path) - except Exception as e: # noqa: BLE001 - doc["not_referenced"][name] = "Pillow cannot decode it: %s: %s" % (type(e).__name__, e) - continue - doc["frames"][name] = entry - print("%-36s %dx%d %s" % (name, d["width"], d["height"], - " ".join("s%s=%dx%d" % (k, v["width"], v["height"]) - for k, v in entry["scales"].items()))) - for name, why in doc["not_referenced"].items(): - print("%-36s not referenced: %s" % (name, why)) - with open(OUT, "w") as f: - json.dump(doc, f, indent=1) - f.write("\n") - print("wrote", OUT) - - -if __name__ == "__main__": - main() diff --git a/tests/jpegio/test_jpegio.py b/tests/jpegio/test_jpegio.py deleted file mode 100644 index 7d6ed07..0000000 --- a/tests/jpegio/test_jpegio.py +++ /dev/null @@ -1,580 +0,0 @@ -"""jpegio -- unix MicroPython tests against the corpus and the Pillow reference. - -The Phase 1 gate of the org's docs/jpegio-vision.md, checked mechanically: - - 1. every baseline corpus frame opens to reference.json's size and decodes - at scales 0..3 into a tight RGB565 buffer whose per-channel means sit - within a tolerance of the Pillow reference (the measured error is - printed beside the tolerance); TJpgDec's own digest per frame/scale is - kept in frames/golden_tjpgd.json -- recorded on the first run (or with - --record), asserted on every later one; - 2. the callback target covers the decoded image exactly once at every - scale, in raster order, and reassembles to the buffer target's bytes; - 3. the buffer target honours x / y / stride, leaves the rest of a larger - buffer alone, and refuses bad geometry -- x / y outside 0..65535, a - stride / y product that wraps size_t -- with the numbers in the message; - 4. the DHT-less frame is refused with JDR_FMT1 -- the Phase 1 contract; - 5. the progressive frame, truncated frames, a bare SOI, and non-JPEG bytes - all raise (never crash), and open() rejects non-source objects; - 6. the restart-marker frames decode to the baseline frame's digests; - 7. real c920e_* camera frames open, decode at every scale and are digested; - 8. one decoder object re-opens across frames and source kinds, and - width / height follow the last open() (a failed one reports nothing); - 9. 50 decodes do not trend gc.mem_free() down. - -Run from the displayif root (or anywhere: paths come from __file__) with the -unix MicroPython built with this repo on USER_C_MODULES: - - ./micropython/ports/unix/build-standard/micropython tests/jpegio/test_jpegio.py [--record] - -Plain asserts; needs only the built interpreter and frames/. Pillow's side is -frozen in frames/reference.json by reference.py, so Pillow is not needed here. -""" -import binascii -import gc -import hashlib -import io -import json -import math -import os -import sys -import time - -import jpegio - -HERE = __file__.rsplit("/", 1)[0] if "/" in __file__ else "." -FRAMES = HERE + "/frames" -REFERENCE = FRAMES + "/reference.json" -GOLDEN = FRAMES + "/golden_tjpgd.json" -SCALES = (0, 1, 2, 3) -RECORD = "--record" in sys.argv[1:] - -# --- fidelity tolerance ------------------------------------------------------ -# -# reference.json holds per-channel means of Pillow's (libjpeg-turbo) decode, -# box-averaged the way TJpgDec's mcu_output() scales. TJpgDec is not -# bit-identical to libjpeg: its fixed-point IDCT rounds differently, it -# replicates chroma where libjpeg interpolates ("fancy upsampling"), and at -# scale 3 it emits each block's DC value (chroma from the whole 16x16 MCU at -# 4:2:0) instead of a box average. Per pixel those differences are large on -# the synthetic pattern's hard chroma edges (up to ~150 levels at scale 0, -# ~190 at scale 3, measured against Pillow while writing this test) but they -# alternate in sign, so the means stay close. Measured |mean error| on this -# corpus, TJpgDec R0.03 with CircuitPython's tjpgdcnf.h, 2026-09-03: -# -# scales 0..2, every frame: max 1.27 (odd_size_37x29 s2, blue) -# scale 3, 1200 decoded px (320x240 -> 40x30): max 1.59 (blue) -# scale 3, 48 decoded px (64x48 -> 8x6): max 1.67 (blue) -# scale 3, 12 decoded px (37x29 -> 4x3): max 9.33 (red) -# -# Tolerance: 2.0 levels at scales 0..2 (1.6x the measured worst; one RGB565 -# step is 4 levels in green, 8 in red/blue, so a systematic one-step shift in -# any channel trips it). At scale 3: 2.0 + 32 / sqrt(n), n = decoded pixels -# (2.9 at 40x30, 6.6 at 8x6, 11.2 at 4x3): DC-only chroma is wrong by a bounded -# amount per pixel and averages out as 1/sqrt(n), so a tiny image needs more -# room. Means alone are a weak witness (a scrambled block order keeps them; a -# swapped R/B on this near-symmetric pattern moves them by ~1.4), which is why -# they are paired with the golden digests and the positional checks below. -MEAN_TOL = 2.0 -MEAN_TOL_SCALE3_K = 32.0 - - -def mean_tolerance(scale, npixels): - if scale < 3: - return MEAN_TOL - return MEAN_TOL + MEAN_TOL_SCALE3_K / math.sqrt(npixels) - - -# --- positional checks on the synthetic pattern ----------------------------- -# -# make_corpus.py draws: top 40 % eight colour bars; 40-70 % a gradient; bottom -# band = 1-px checker (left half) and a red|blue split (right half). Sampling -# the centre of each bar (and of the red / blue halves) pins channel identity -# and block placement, which means cannot. Measured deviation at those -# samples: 0 on every frame/scale the rules below admit, 8 on the 64x48 split. -# A 4:2:0 chroma sample spans 2 source px (a 16x16 MCU at scale 3), so bars -# narrower than 8 source px (16 at scale 3) are not sampled: their centre -# chroma is a mix of two bars, by construction of the format, not by defect. -BARS = ((255, 255, 255), (255, 255, 0), (0, 255, 255), (0, 255, 0), - (255, 0, 255), (255, 0, 0), (0, 0, 255), (0, 0, 0)) -POS_TOL = 24 -BAR_MIN_SRC_PX = {0: 8, 1: 8, 2: 8, 3: 16} - - -def pattern_geometry(w, h): - y_bars = max(1, (h * 2) // 5) - y_grad = max(y_bars + 1, (h * 7) // 10) - return y_bars, y_grad - - -def rgb565_to_888(v): - return ((v >> 8) & 0xF8, (v >> 3) & 0xFC, (v << 3) & 0xF8) - - -def pixel(buf, stride, x, y): - i = 2 * (y * stride + x) - return rgb565_to_888(buf[i] | (buf[i + 1] << 8)) - - -def expect_rgb(rgb): - return (rgb[0] & 0xF8, rgb[1] & 0xFC, rgb[2] & 0xF8) - - -def luma(rgb): - return (rgb[0] * 299 + rgb[1] * 587 + rgb[2] * 114) // 1000 - - -# --- helpers ----------------------------------------------------------------- - -def sha256_hex(buf): - return binascii.hexlify(hashlib.sha256(buf).digest()).decode() - - -def means565(buf, n): - r = g = b = 0 - for i in range(0, 2 * n, 2): - lo = buf[i] - hi = buf[i + 1] - r += hi & 0xF8 - g += ((hi & 0x07) << 5) | ((lo >> 5) << 2) - b += (lo << 3) & 0xF8 - return (r / n, g / n, b / n) - - -def read_file(name): - with open(FRAMES + "/" + name, "rb") as f: - return f.read() - - -def expect_raises(exc, fn, *needles): - """fn() must raise exc (or a subclass) whose str() contains every needle.""" - try: - fn() - except exc as e: - msg = str(e) - for needle in needles: - assert needle in msg, "expected %r in %s: %s" % (needle, type(e).__name__, msg) - return e - except Exception as e: # noqa: BLE001 - raise AssertionError("expected %s, got %s: %s" % (exc.__name__, type(e).__name__, e)) - raise AssertionError("expected %s, nothing raised" % exc.__name__) - - -def decode_tight(dec, source, scale): - w, h = dec.open(source) - dw, dh = w >> scale, h >> scale - buf = bytearray(dw * dh * 2) - dec.decode(buf, scale) - return dw, dh, buf - - -def decode_via_callback(dec, source, scale): - """decode(callable): coverage mask + raster-order check; returns the reassembled buffer.""" - w, h = dec.open(source) - dw, dh = w >> scale, h >> scale - mask = bytearray(dw * dh) - out = bytearray(dw * dh * 2) - state = [0, -1, -1, None] # blocks, last y, last x, last view - - def cb(x, y, bw, bh, mv): - assert bw > 0 and bh > 0 and 0 <= x and 0 <= y, (x, y, bw, bh) - assert x + bw <= dw and y + bh <= dh, "block %dx%d at (%d, %d) outside %dx%d" % (bw, bh, x, y, dw, dh) - assert len(mv) == bw * bh, (len(mv), bw, bh) - raw = bytes(mv) - assert len(raw) == bw * bh * 2 - assert y > state[1] or (y == state[1] and x > state[2]), "not raster order: (%d, %d) after (%d, %d)" % (x, y, state[2], state[1]) - state[1], state[2] = y, x - for r in range(bh): - o = (y + r) * dw + x - assert not any(mask[o:o + bw]), "pixel written twice in row %d, x %d..%d" % (y + r, x, x + bw) - mask[o:o + bw] = b"\x01" * bw - out[2 * o:2 * (o + bw)] = raw[2 * r * bw:2 * (r + 1) * bw] - state[0] += 1 - state[3] = mv - - dec.decode(cb, scale) - gaps = mask.count(b"\x00") if hasattr(mask, "count") else sum(1 for m in mask if not m) - assert gaps == 0, "%d of %d pixels never written at scale %d" % (gaps, dw * dh, scale) - assert state[3] is not None and len(state[3]) == 0, "block view must be emptied after decode()" - return out, state[0] - - -def check_pattern(name, scale, buf, dw, dh, w, h): - """Positional checks on the synthetic frames; returns the number of samples checked.""" - y_bars, y_grad = pattern_geometry(w, h) - band = y_bars >> scale - gray = name.startswith("grayscale_") - checked = 0 - bar_src_px = w / 8 - if band >= 2 and dw >= 8 and (gray or bar_src_px >= BAR_MIN_SRC_PX[scale]): - for i, rgb in enumerate(BARS): - x = int((i + 0.5) * dw / 8) - got = pixel(buf, dw, x, band // 2) - if gray: - L = luma(rgb) - want = expect_rgb((L, L, L)) - else: - want = expect_rgb(rgb) - err = max(abs(got[c] - want[c]) for c in range(3)) - assert err <= POS_TOL, "%s s%d bar %d at (%d, %d): got %s want %s" % (name, scale, i, x, band // 2, got, want) - checked += 1 - if scale < 3 and w >= 64 and not gray: - y = ((y_grad >> scale) + dh) // 2 - for xf, rgb in ((5 / 8, (255, 0, 0)), (7 / 8, (0, 0, 255))): - x = int(dw * xf) - got = pixel(buf, dw, x, y) - want = expect_rgb(rgb) - err = max(abs(got[c] - want[c]) for c in range(3)) - assert err <= POS_TOL, "%s s%d split at (%d, %d): got %s want %s" % (name, scale, x, y, got, want) - checked += 1 - if gray: - # TJpgDec builds R=G=B=Y for a one-component image: after RGB565 - # packing r == b exactly and g - r is 0 or 4 on every pixel. - for i in range(0, 2 * dw * dh, 2): - r, g, b = rgb565_to_888(buf[i] | (buf[i + 1] << 8)) - assert r == b and 0 <= g - r <= 4, "%s s%d pixel %d not grey: %s" % (name, scale, i // 2, (r, g, b)) - checked += dw * dh - return checked - - -def load_golden(): - try: - with open(GOLDEN) as f: - return json.load(f)["frames"] - except OSError: - return None - - -def write_golden(digests): - lines = ['{', ' "_about": [', - ' "TJpgDec\'s own sha256 per corpus frame and scale: the RGB565 buffer decode() fills",', - ' "(native-order uint16, row-major, tight), written by test_jpegio.py --record.",', - ' "A parity digest, not a fidelity claim: its evidence is the Pillow mean/positional",', - ' "check test_jpegio.py prints beside it. Re-record deliberately after a TJpgDec or",', - ' "tjpgdcnf.h change, never to make a red run green."', - ' ],', - ' "decoder": "TJpgDec R0.03 patch1, CircuitPython tjpgdcnf.h (JD_FORMAT 1, JD_USE_SCALE 1, JD_TBLCLIP 1, JD_FASTDECODE 1)",', - ' "frames": {'] - names = sorted(digests) - for i, name in enumerate(names): - lines.append(' "%s": {' % name) - for s in SCALES: - lines.append(' "%d": "%s"%s' % (s, digests[name][s], "," if s != SCALES[-1] else "")) - lines.append(' }%s' % ("," if i != len(names) - 1 else "")) - lines += [' }', '}', ''] - with open(GOLDEN, "w") as f: - f.write("\n".join(lines)) - - -# --- the tests ----------------------------------------------------------------- - -def main(): - t0 = time.time() - with open(REFERENCE) as f: - ref = json.load(f) - frames = ref["frames"] - dec = jpegio.JpegDecoder() - golden = None if RECORD else load_golden() - digests = {} - refused = {} - worst = {} - samples = 0 - - # 1. sizes, tight decode at every scale, means vs Pillow, digests vs golden - print("== 1. corpus frames: size, tight decode, means vs Pillow (tolerance beside), digest") - for name in sorted(frames): - entry = frames[name] - try: - w, h = dec.open(FRAMES + "/" + name) - except ValueError as e: - refused[name] = str(e) - print("%-36s open() refused: %s" % (name, e)) - continue - assert (w, h) == (entry["width"], entry["height"]), (name, w, h) - assert (dec.width, dec.height) == (w, h) - digests[name] = {} - for s in SCALES: - r = entry["scales"][str(s)] - dw, dh, buf = decode_tight(dec, FRAMES + "/" + name, s) - assert (dw, dh) == (r["width"], r["height"]), (name, s, dw, dh) - assert len(buf) == dw * dh * 2 - m = means565(buf, dw * dh) - err = [m[c] - r["mean_rgb565"][c] for c in range(3)] - tol = mean_tolerance(s, dw * dh) - worst[s] = max(worst.get(s, 0.0), max(abs(e) for e in err)) - digest = sha256_hex(buf) - digests[name][s] = digest - status = "recorded" - if golden is not None: - g = golden.get(name, {}).get(str(s)) - status = "golden match" if g == digest else "GOLDEN MISMATCH (golden %s)" % (g[:16] if g else None) - print("%-36s s%d %4dx%-4d mean err r=%+6.3f g=%+6.3f b=%+6.3f tol %5.2f sha256 %s.. %s" % ( - name, s, dw, dh, err[0], err[1], err[2], tol, digest[:16], status)) - for c, e in enumerate(err): - assert abs(e) <= tol, "%s s%d channel %d mean error %.3f exceeds %.2f" % (name, s, c, e, tol) - if golden is not None: - assert status == "golden match", "%s s%d: TJpgDec digest changed" % (name, s) - if name.startswith("c920e_"): - print("%-36s s%d means r=%.2f g=%.2f b=%.2f (Pillow r=%.2f g=%.2f b=%.2f)" % ( - name, s, m[0], m[1], m[2], r["mean_rgb565"][0], r["mean_rgb565"][1], r["mean_rgb565"][2])) - else: - samples += check_pattern(name, s, buf, dw, dh, w, h) - print("worst |mean err| per scale: " + " ".join("s%d=%.3f" % (s, worst[s]) for s in SCALES) - + " (tolerance %.1f at s0..2, %.1f + %.0f/sqrt(n) at s3)" % (MEAN_TOL, MEAN_TOL, MEAN_TOL_SCALE3_K)) - print("positional samples checked on the synthetic frames: %d" % samples) - assert samples > 0 - if golden is None: - write_golden(digests) - print("golden: RECORDED %d digests to %s" % (sum(len(v) for v in digests.values()), GOLDEN)) - else: - extra = sorted(set(golden) - set(digests)) - assert not extra, "golden has frames this run did not decode: %s" % extra - print("golden: %d digests match %s" % (sum(len(v) for v in digests.values()), GOLDEN)) - - # 6. restart markers and marker order must not change a pixel - print("== 6. restart / marker-order frames vs baseline_jfif_320x240.jpg") - base = digests["baseline_jfif_320x240.jpg"] - for other in ("restart_dri_320x240.jpg", "dri_first_mjpeg_style_320x240.jpg"): - for s in SCALES: - same = digests[other][s] == base[s] - print("%-36s s%d %s" % (other, s, "== baseline" if same else "FINDING: differs %s vs baseline %s" % (digests[other][s], base[s]))) - assert same, "%s decodes differently from the baseline at scale %d" % (other, s) - - # 7. real camera frames: every c920e_*.jpg in frames/ (means printed above when referenced) - print("== 7. c920e camera frames") - cams = sorted(n for n in os.listdir(FRAMES) if n.startswith("c920e_") and n.endswith(".jpg")) - assert cams, "no c920e_*.jpg in %s" % FRAMES - for name in cams: - if name in digests: - print("%-36s referenced: decoded and digested at every scale (see section 1)" % name) - continue - w, h = dec.open(FRAMES + "/" + name) - digests[name] = {} - for s in SCALES: - dw, dh, buf = decode_tight(dec, FRAMES + "/" + name, s) - m = means565(buf, dw * dh) - digests[name][s] = sha256_hex(buf) - print("%-36s s%d %4dx%-4d means r=%.2f g=%.2f b=%.2f sha256 %s.. (not in reference.json)" % ( - name, s, dw, dh, m[0], m[1], m[2], digests[name][s][:16])) - - # 2. callback path: exactly-once coverage at every scale, same bytes as the buffer path - print("== 2. callback coverage") - for name in sorted(digests): - src = read_file(name) - for s in SCALES: - out, blocks = decode_via_callback(dec, src, s) - assert sha256_hex(out) == digests[name][s], "%s s%d: callback bytes differ from buffer path" % (name, s) - print("%-36s s%d %4d blocks, every pixel once, reassembled == buffer path" % (name, s, blocks)) - # the callback path allocates nothing per block (README): a fixed-arity - # Python function costs no heap per call, so the whole decode should too. - full = read_file("baseline_jfif_320x240.jpg") - - def sink(x, y, w, h, mv): - pass - - dec.open(full) - dec.decode(sink, 1) - dec.open(full) - gc.collect() - a0 = gc.mem_alloc() - dec.decode(sink, 0) - grew = gc.mem_alloc() - a0 - print("callback decode of 320x240 at scale 0 (300 blocks) allocated %d bytes" % grew) - assert grew < 300, "per-block allocation on the callback path" - # a raise inside the callback propagates and consumes the open() - dec.open(full) - - class Stop(Exception): - pass - - def stop(x, y, w, h, mv): - raise Stop("at (%d, %d)" % (x, y)) - - expect_raises(Stop, lambda: dec.decode(stop), "at (0, 0)") - expect_raises(RuntimeError, lambda: dec.decode(bytearray(8)), "decode() without open()") - expect_raises(TypeError, lambda: (dec.open(full), dec.decode(sink, stride=5)), "stride") - # x, y are bounded like the image size (0..65535) on this path too, and - # inside the bound the callback gets them whole: before the fix 2**62 - # went through MP_OBJ_NEW_SMALL_INT unchecked and arrived negative. - small = read_file("baseline_jfif_64x48.jpg") - dec.open(small) - expect_raises(ValueError, lambda: dec.decode(sink, 0, 2 ** 62, 0), "0..65535", str(2 ** 62)) - expect_raises(ValueError, lambda: dec.decode(sink, 0, 0, 65536), "0..65535", "65536") - first = [] - dec.decode(lambda x, y, w, h, mv: first.append((x, y)) if not first else None, 3, 65535, 65535) - assert first == [(65535, 65535)], first - print("callback x, y: 2**62 and 65536 refused (0..65535); at (65535, 65535) the first block reports (65535, 65535)") - - # 3. buffer path with x, y, stride; untouched region stays untouched; bad geometry - print("== 3. buffer target: offsets, stride, untouched region, errors") - for name, s in (("baseline_jfif_64x48.jpg", 0), ("odd_size_37x29.jpg", 0), ("baseline_jfif_320x240.jpg", 1), ("c920e_320x240_dri.jpg", 2)): - src = read_file(name) - dw, dh, tight = decode_tight(dec, src, s) - x, y, stride, rows = 5, 3, dw + 13, dh + 7 - big = bytearray(b"\xa5" * (stride * rows * 2)) - want = bytearray(big) - for r in range(dh): - o = ((y + r) * stride + x) * 2 - want[o:o + dw * 2] = tight[r * dw * 2:(r + 1) * dw * 2] - dec.open(src) - dec.decode(big, s, x, y, stride=stride) - assert big == want, "%s s%d: offset decode wrote outside or wrote wrong pixels" % (name, s) - # keyword form, and a memoryview slice as the target - big2 = bytearray(b"\xa5" * (stride * rows * 2 + 6)) - dec.open(src) - dec.decode(memoryview(big2)[6:], scale=s, x=x, y=y, stride=stride) - assert big2[6:] == want and big2[:6] == b"\xa5" * 6 - print("%-36s s%d %dx%d at (%d, %d) stride %d into %d rows: region exact, %d other bytes untouched" % ( - name, s, dw, dh, x, y, stride, rows, len(big) - dw * dh * 2)) - w, h = dec.open(full) - n = w * h * 2 - expect_raises(ValueError, lambda: dec.decode(bytearray(1000)), "320x240", "153600", "1000") - # x > 0 with the default stride can never fit; the message says what to pass - expect_raises(ValueError, lambda: dec.decode(bytearray(n), 0, 1, 0), "1 + 320", "default stride 320", "pass stride=") - e = expect_raises(ValueError, lambda: dec.decode(bytearray(n), 0, 1, 0, stride=320), "1 + 320", "stride 320") - assert "default" not in str(e), e - expect_raises(ValueError, lambda: dec.decode(bytearray(n), 0, 0, 1), "(0, 1)", "154240", "153600") - expect_raises(ValueError, lambda: dec.decode(bytearray(n), 0, 0, 0, stride=319), "320", "319") - expect_raises(ValueError, lambda: dec.decode(bytearray(n), 0, 0, 0, stride=-5), "-5") - expect_raises(ValueError, lambda: dec.decode(bytearray(n), 0, -1, 0), "-1") - expect_raises(ValueError, lambda: dec.decode(bytearray(n), 0, 0, -2), "-2") - expect_raises(ValueError, lambda: dec.decode(bytearray(n), 4), "4") - expect_raises(ValueError, lambda: dec.decode(bytearray(n), -1), "-1") - expect_raises(TypeError, lambda: dec.decode(b"\x00" * n)) - # x, y are bounded like the image size (0..65535); the message keeps the - # value whole rather than printing it through an int - for bad in (65536, 2 ** 40, 2 ** 62): - expect_raises(ValueError, lambda: dec.decode(bytearray(n), 0, bad, 0), "0..65535", str(bad)) - expect_raises(ValueError, lambda: dec.decode(bytearray(n), 0, 0, bad), "0..65535", str(bad)) - # the buffer-fit check must not wrap: (y + h - 1) * stride is 2**bits - # here, i.e. 0 in size_t, so an unchecked product sees only the last - # row's 320 pixels (640 bytes), passes a 1000-byte buffer, and the decode walks off - # it (SIGSEGV before the fix). Then the product wrapping to a small - # non-zero count, which the 153600-byte buffer would also have passed. - bits = 64 if sys.maxsize > 2 ** 32 else 32 - y_wrap = 2 ** 15 - (h - 1) - e = expect_raises(ValueError, lambda: dec.decode(bytearray(1000), 0, 0, y_wrap, stride=2 ** (bits - 15)), - "target too small", "overflows size_t", "buffer has 1000") - print("wrapping geometry refused: %s" % e) - expect_raises(ValueError, lambda: dec.decode(bytearray(n), 0, 0, y_wrap, stride=2 ** (bits - 15) + 1), - "target too small", "overflows size_t") - # the widest legal placement is arithmetic the check must get right, not refuse - expect_raises(ValueError, lambda: dec.decode(bytearray(n), 0, 65535, 0, stride=65535 + 320), - "target too small", "needs %d bytes" % ((239 * (65535 + 320) + 65535 + 320) * 2)) - # a rejected decode() does not consume the open(): retry without re-opening - buf = bytearray(n) - dec.decode(buf) - assert sha256_hex(buf) == digests["baseline_jfif_320x240.jpg"][0] - print("bad geometry: 21 rejections named their numbers; the open() survived them and decoded") - - # 4. the DHT-less frame: Phase 1 refuses it (JDR_FMT1); nothing is injected - print("== 4. nodht_320x240.jpg") - # The contract Phase 1 chose (src/jpegio/README.md, "Errors"): a frame - # without Huffman tables fails open() with JDR_FMT1; injecting the T.81 - # K.3 tables is the open decision in docs/jpegio-vision.md, not something - # this module does silently. If a later phase injects them, this section - # becomes: open() -> (320, 240) and digests == baseline_jfif_320x240.jpg. - assert "nodht_320x240.jpg" in refused, "nodht frame decoded -- the module injected tables? update section 4" - assert sorted(refused) == ["nodht_320x240.jpg"], "unexpected refusals: %s" % refused - e = expect_raises(ValueError, lambda: dec.open(FRAMES + "/nodht_320x240.jpg"), "JDR_FMT1", "unsupported or malformed") - expect_raises(RuntimeError, lambda: dec.decode(bytearray(8)), "decode() without open()") - print("refused at open(): %s" % e) - - # 5. refusals that must raise, never crash - print("== 5. progressive, truncated, bare SOI, non-JPEG, non-source objects") - e = expect_raises(ValueError, lambda: dec.open(FRAMES + "/progressive_320x240.jpg"), "JDR_FMT3", "progressive") - print("progressive: %s" % e) - rst = read_file("restart_dri_320x240.jpg") - half = rst[:len(rst) // 2] - assert dec.open(half) == (320, 240) # headers are intact, the scan is not - e = expect_raises(ValueError, lambda: dec.decode(bytearray(n)), "JDR_INP") - print("truncated scan (bytes, %d of %d): decode() -> %s" % (len(half), len(rst), e)) - assert dec.open(io.BytesIO(half)) == (320, 240) - expect_raises(ValueError, lambda: dec.decode(lambda *a: None), "JDR_INP") - expect_raises(ValueError, lambda: dec.open(rst[:100]), "JDR_INP") - print("truncated headers (100 bytes): open() refused") - for label, src in (("bare SOI", b"\xff\xd8"), ("b'hello'", b"hello"), ("empty", b"")): - e = expect_raises(ValueError, lambda: dec.open(src), "JDR_INP") - expect_raises(RuntimeError, lambda: dec.decode(bytearray(8)), "decode() without open()") - print("%s: open() -> %s; no decode possible" % (label, e)) - expect_raises(OSError, lambda: dec.open(FRAMES + "/does_not_exist.jpg")) - - class Readable: - def read(self, n): - return b"" - - for label, src in (("int", 5), ("None", None), ("list", [1, 2]), ("float", 1.5), - ("object()", object()), ("Python read()", Readable()), - ("text file", open(FRAMES + "/README.md"))): - expect_raises(TypeError, lambda: dec.open(src), "source must be") - print("non-source objects (int, None, list, float, object, a Python read(), a text file): TypeError") - expect_raises(RuntimeError, lambda: jpegio.JpegDecoder().width, "width needs a successful open()") - - # 8. one decoder, many frames and source kinds - print("== 8. re-open: frames in sequence, path / bytes / file / BytesIO / memoryview") - order = ("baseline_jfif_64x48.jpg", "baseline_jfif_320x240.jpg", "odd_size_37x29.jpg", "grayscale_64x48.jpg", "c920e_320x240_dri.jpg") - for name in order: - for s in (0, 2): - dw, dh, buf = decode_tight(dec, FRAMES + "/" + name, s) - assert sha256_hex(buf) == digests[name][s], "%s s%d after other frames: digest changed" % (name, s) - assert (dec.width, dec.height) == (frames[name]["width"], frames[name]["height"]) - print("sequence %s: digests unchanged" % " -> ".join(order)) - with open(FRAMES + "/baseline_jfif_320x240.jpg", "rb") as fobj: - for label, src in (("path", FRAMES + "/baseline_jfif_320x240.jpg"), ("bytes", full), ("bytearray", bytearray(full)), - ("memoryview", memoryview(full)), ("file", fobj), ("BytesIO", io.BytesIO(full))): - dw, dh, buf = decode_tight(dec, src, 1) - assert sha256_hex(buf) == digests["baseline_jfif_320x240.jpg"][1], label - print("%-10s -> same digest at scale 1" % label) - # open() twice: the second image wins; a failed open() clears the first - dec.open(FRAMES + "/baseline_jfif_64x48.jpg") - assert dec.open(full) == (320, 240) - buf = bytearray(n) - dec.decode(buf) - assert sha256_hex(buf) == digests["baseline_jfif_320x240.jpg"][0] - dec.open(full) - expect_raises(ValueError, lambda: dec.open(b"hello")) - expect_raises(RuntimeError, lambda: dec.decode(buf), "decode() without open()") - # ... and width / height do not report the superseded image either - # (before the fix they still read 320x240 here) - expect_raises(RuntimeError, lambda: dec.width, "width needs a successful open()") - expect_raises(RuntimeError, lambda: dec.height, "height needs a successful open()") - for exc, fail in ((TypeError, lambda: dec.open(5)), - (OSError, lambda: dec.open(FRAMES + "/does_not_exist.jpg")), - (ValueError, lambda: dec.open(FRAMES + "/progressive_320x240.jpg"))): - assert dec.open(full) == (320, 240) - expect_raises(exc, fail) - expect_raises(RuntimeError, lambda: dec.width, "successful open()") - # after decode() they still read: blit_rect(buf, 0, 0, dec.width, dec.height) - dec.open(full) - dec.decode(buf) - assert (dec.width, dec.height) == (320, 240) - print("second open() supersedes the first; a failed open() leaves nothing to decode or report") - - # 9. memory: 50 decodes into the same buffer must not trend mem_free down - print("== 9. memory over 50 decodes of 320x240 at scale 0") - buf = bytearray(n) - dec.open(full) - dec.decode(buf) - gc.collect() - f0 = gc.mem_free() - for _ in range(50): - dec.open(full) - dec.decode(buf) - f1 = gc.mem_free() - gc.collect() - f2 = gc.mem_free() - for _ in range(50): - dec.open(full) - dec.decode(buf) - gc.collect() - f3 = gc.mem_free() - print("mem_free before %d, after 50 (uncollected) %d, collected %d, after 100 collected %d" % (f0, f1, f2, f3)) - print("uncollected garbage per open()+decode(): %d bytes" % ((f0 - f1) // 50)) - assert f0 - f1 < 50 * 512, "more than 512 bytes of garbage per decode" - assert f2 >= f0 - 1024 and f3 >= f2 - 1024, "mem_free trends down across decodes" - - print("jpegio tests passed: %d frames, %d digests, %.2f s" % ( - len(digests), sum(len(v) for v in digests.values()), time.time() - t0)) - - -main() diff --git a/tests/jpegio/test_jpegio_lvgl.py b/tests/jpegio/test_jpegio_lvgl.py deleted file mode 100644 index df129b9..0000000 --- a/tests/jpegio/test_jpegio_lvgl.py +++ /dev/null @@ -1,416 +0,0 @@ -"""jpegio -- its LVGL image decoder, on unix MicroPython built beside lvgl-micropython. - -The LVGL half of the Phase 2 gate in the org's docs/jpegio-vision.md, checked -mechanically: - - 1. registration: `import jpegio` before lv.init() registers nothing; after - lv.init() the decoder appears once -- by the module's import-time hook - where the port has MICROPY_MODULE_BUILTIN_INIT, and by - jpegio.register_lvgl_decoder() everywhere -- and registering again adds - no second decoder; it sits at the head of LVGL's list (counted with - jpegio.lvgl_decoders(): the binding cannot walk LVGL's decoder list - from Python -- MicroPython's builtin-method self check refuses - lv.image_decoder_t.get_next(None)); - 2. lv.image on three corpus frames (a JFIF still, the restart-marker frame, - a real non-JFIF C920e frame -- the SOI-only sniff through LVGL) flushed - to a 320x240 RGB565 frame buffer gives EXACTLY jpegio's own golden - digests from frames/golden_tjpgd.json at scale 0. Exact equality is the - gate (RGB565 into RGB565 at opa 255 is a row copy): a digest that - differs FAILS the test, after printing both digests and, as - diagnostics only, the BARS positional pixels and the Pillow means of - reference.json -- those tell a gross fault (wrong colours, wrong - stride) from a subtle one (pixels shuffled) but never pass anything. - "Something was drawn" is witnessed against the digest of the screen - background alone, not against a zero buffer: LVGL paints the - background into every flush, so the buffer is never zero; - 3. decoder_info tells the truth, read back through lv.image's source size: - the size LVGL reports is the stream's own (a wrong w/h in the - lv.image_dsc_t header is not echoed); non-JPEG bytes and a bare SOI are - not claimed by jpegio -- they fall through to LVGL's built-in decoder, - which echoes the header (jpegio never does); and an RGB565 pixel buffer - that happens to start FF D8 is drawn by that built-in decoder as pixels; - 4. a truncated scan draws nothing (the frame is the background alone) and - does not crash; - 5. a file source through fs_driver.py (when frozen in) decodes the same; - 6. 20 refreshes (each an open/decode/close) do not trend gc.mem_free() down; - 7. lv.deinit() / lv.init() drops the decoder; registering again restores it - and decodes. Last on purpose, with no gc.collect() after the re-init: - on this build lv.init() -> display -> lv.deinit() -> lv.init() -> display - -> one refresh -> gc.collect() dies with SIGBUS in MicroPython's - gc_sweep_run_finalisers -> mp_load_method_maybe (a finaliser-flagged GC - block whose type pointer is garbage) with no jpegio involved at all: - reproduced with a label on a build without displayif (lvgl-micropython - glue, not this decoder). - -Skips (exit 0, prints SKIP) when `import lvgl` fails: the interpreter was -built without the lvgl-micropython usermod, and displayif's LVGL-less -clean-build CI runs this file too. Before skipping it asserts the other -half of the contract, the one that only a decoder-less build can check: -jpegio.register_lvgl_decoder() and jpegio.lvgl_decoders() are module -attributes on EVERY build, the first raising RuntimeError that names the -missing sibling, the second returning (). - - ./micropython/ports/unix/build-standard/micropython tests/jpegio/test_jpegio_lvgl.py -""" -import sys - -try: - import lvgl as lv -except ImportError: - # No lvgl-micropython in this build, so lvgl_decoder.c was not compiled -- - # but the two module attributes are unconditional (jpegio.c keeps them out - # of the #if, so the CMake ports' QSTR pass sees their names and the API - # never silently disappears). This is the only build that can check it. - import jpegio - - try: - jpegio.register_lvgl_decoder() - except RuntimeError as e: - assert "built without its LVGL decoder" in str(e), e - print("no LVGL decoder compiled in: register_lvgl_decoder() -> RuntimeError: %s" % e) - else: - raise AssertionError("register_lvgl_decoder() returned without the decoder compiled in") - assert jpegio.lvgl_decoders() == (), jpegio.lvgl_decoders() - print("no LVGL decoder compiled in: lvgl_decoders() == ()") - - print("SKIP: no `lvgl` module in this interpreter (build with the lvgl-micropython usermod)") - sys.exit(0) - -import binascii -import gc -import hashlib -import json - -import jpegio - -HERE = __file__.rsplit("/", 1)[0] if "/" in __file__ else "." -FRAMES = HERE + "/frames" -GOLDEN = FRAMES + "/golden_tjpgd.json" -REFERENCE = FRAMES + "/reference.json" -W, H = 320, 240 -DECODER = "jpegio" -# Platforms whose interpreters run a built-in module's __init__ on import -# (MICROPY_MODULE_BUILTIN_INIT), so `import jpegio` after lv.init() registers. -DESKTOP_WITH_BUILTIN_INIT = ("linux", "darwin", "win32") -CORPUS = ("baseline_jfif_320x240.jpg", "restart_dri_320x240.jpg", "c920e_320x240_dri.jpg") - -# The synthetic pattern (make_corpus.py): eight colour bars in the top 40 %. -# Same positional check as test_jpegio.py's check_pattern(), scale 0 only. -BARS = ((255, 255, 255), (255, 255, 0), (0, 255, 255), (0, 255, 0), - (255, 0, 255), (255, 0, 0), (0, 0, 255), (0, 0, 0)) -POS_TOL = 24 -MEAN_TOL = 2.0 - - -def sha256_hex(buf): - return binascii.hexlify(hashlib.sha256(buf).digest()).decode() - - -def read_file(name): - with open(FRAMES + "/" + name, "rb") as f: - return f.read() - - -def pixel(buf, stride, x, y): - i = 2 * (y * stride + x) - v = buf[i] | (buf[i + 1] << 8) - return ((v >> 8) & 0xF8, (v >> 3) & 0xFC, (v << 3) & 0xF8) - - -def means565(buf, n): - r = g = b = 0 - for i in range(0, 2 * n, 2): - lo = buf[i] - hi = buf[i + 1] - r += hi & 0xF8 - g += ((hi & 0x07) << 5) | ((lo >> 5) << 2) - b += (lo << 3) & 0xF8 - return (r / n, g / n, b / n) - - -def check_bars(name, buf): - y = (H * 2) // 5 // 2 - for i, rgb in enumerate(BARS): - x = int((i + 0.5) * W / 8) - got = pixel(buf, W, x, y) - want = (rgb[0] & 0xF8, rgb[1] & 0xFC, rgb[2] & 0xF8) - err = max(abs(got[c] - want[c]) for c in range(3)) - assert err <= POS_TOL, "%s bar %d at (%d, %d): got %s want %s" % (name, i, x, y, got, want) - return len(BARS) - - -def check_means(name, buf, reference): - want = reference["frames"][name]["scales"]["0"]["mean_rgb565"] - got = means565(buf, W * H) - err = [got[c] - want[c] for c in range(3)] - for c in range(3): - assert abs(err[c]) <= MEAN_TOL, "%s channel %d mean error %.3f exceeds %.1f" % (name, c, err[c], MEAN_TOL) - return err - - -def decoders(): - """Names of LVGL's registered image decoders, in the order LVGL consults them.""" - return list(jpegio.lvgl_decoders()) - - -def expect_raises(exc, fn, *needles): - try: - fn() - except exc as e: - msg = str(e) - for needle in needles: - assert needle in msg, "expected %r in %s: %s" % (needle, type(e).__name__, msg) - return e - except Exception as e: # noqa: BLE001 - raise AssertionError("expected %s, got %s: %s" % (exc.__name__, type(e).__name__, e)) - raise AssertionError("expected %s, nothing raised" % exc.__name__) - - -class Frame: - """The display's flush target: a zeroed W x H native RGB565 buffer.""" - - def __init__(self): - self.buf = bytearray(W * H * 2) - self.flushes = 0 - - def zero(self): - for i in range(len(self.buf)): - self.buf[i] = 0 - self.flushes = 0 - - def flush_cb(self, disp, area, color_p): - aw = area.x2 - area.x1 + 1 - ah = area.y2 - area.y1 + 1 - data = color_p.__dereference__(aw * ah * 2) - for row in range(ah): - o = ((area.y1 + row) * W + area.x1) * 2 - self.buf[o:o + aw * 2] = data[row * aw * 2:(row + 1) * aw * 2] - self.flushes += 1 - disp.flush_ready() - -def setup_display(frame): - """320x240 RGB565 display like lvgl-bindings/tools/test_lvgl_smoke.py's _setup_display.""" - disp = lv.display_create(W, H) - disp.set_color_format(lv.COLOR_FORMAT.RGB565) - buf = lv.draw_buf_create(W, H, lv.COLOR_FORMAT.RGB565, 0) - disp.set_draw_buffers(buf, None) - disp.set_render_mode(lv.DISPLAY_RENDER_MODE.PARTIAL) - disp.set_flush_cb(frame.flush_cb) - return disp, buf - - -def image_dsc(jpeg, w=W, h=H, cf=None): - if cf is None: - cf = lv.COLOR_FORMAT.RAW - return lv.image_dsc_t({"header": {"w": w, "h": h, "cf": cf}, "data_size": len(jpeg), "data": jpeg}) - - -def show(disp, frame, src): - """Put `src` on a fresh screen as the only widget and render it into frame.""" - scr = lv.screen_active() - scr.clean() - img = lv.image(scr) - img.set_src(src) - img.set_pos(0, 0) - frame.zero() - scr.invalidate() - lv.refr_now(disp) - return img - - -def background_digest(disp, frame): - """Digest of a refresh with nothing on the screen: what "nothing drawn" looks like.""" - scr = lv.screen_active() - scr.clean() - frame.zero() - scr.invalidate() - lv.refr_now(disp) - assert frame.flushes > 0 - return sha256_hex(frame.buf) - - -def src_size(img): - """The size lv_image took from lv_image_decoder_get_info (0x0 when no decoder claimed the source).""" - return (img.get_src_width(), img.get_src_height()) - - -def main(): - with open(GOLDEN) as f: - golden = json.load(f)["frames"] - with open(REFERENCE) as f: - reference = json.load(f) - jpegs = {name: read_file(name) for name in CORPUS} - - # 1. registration - print("== 1. registration") - assert not lv.is_initialized() - assert jpegio.lvgl_decoders() == (), jpegio.lvgl_decoders() - expect_raises(RuntimeError, jpegio.register_lvgl_decoder, "lv.init() first") - lv.init() - names = decoders() - assert DECODER not in names, "decoder present before any registration: %s" % names - print("after lv.init(): decoders %s (no %s yet)" % (names, DECODER)) - import jpegio as jpegio_again # noqa: F401 -- a second import statement re-runs the module's __init__ hook - auto = decoders().count(DECODER) - assert auto <= 1, decoders() - # The desktop interpreters all have the hook: unix at its extra-features - # ROM level, windows through the PyDevices variant (displayif#41). - if sys.platform in DESKTOP_WITH_BUILTIN_INIT: - assert auto == 1, ( - "%s: `import jpegio` after lv.init() registered nothing -- this build lacks " - "MICROPY_MODULE_BUILTIN_INIT (decoders %s)" % (sys.platform, decoders()) - ) - print("import-time registration (MICROPY_MODULE_BUILTIN_INIT): %s" % ("yes" if auto == 1 else "no -- explicit call needed")) - added = jpegio.register_lvgl_decoder() - assert added is (auto == 0), (added, auto) - assert decoders().count(DECODER) == 1, decoders() - assert jpegio.register_lvgl_decoder() is False - assert jpegio.register_lvgl_decoder() is False - names = decoders() - assert names.count(DECODER) == 1, "registering three times left %s" % names - assert names[0] == DECODER, "%s must be consulted first (head of LVGL's list): %s" % (DECODER, names) - print("register_lvgl_decoder() x3: one decoder, first in line: %s" % names) - - # 2. lv.image on the corpus -> flush buffer -> jpegio's own golden digests - print("== 2. lv.image through the flush buffer vs golden_tjpgd.json (scale 0)") - frame = Frame() - disp, draw_buf = setup_display(frame) - bg = background_digest(disp, frame) - for name in CORPUS: - assert bg != golden[name]["0"], "the empty screen already hashes to %s's golden" % name - print("background-only refresh: sha256 %s.. (what 'nothing drawn' looks like)" % bg[:16]) - exact = 0 - mismatched = [] - keep = [] - for name in CORPUS: - dsc = image_dsc(jpegs[name]) - keep.append(dsc) - img = show(disp, frame, dsc) - assert frame.flushes > 0, "no flush during refr_now" - assert (img.get_width(), img.get_height()) == (W, H), (name, img.get_width(), img.get_height()) - digest = sha256_hex(frame.buf) - assert digest != bg, "%s: the frame is the background alone -- nothing was drawn" % name - want = golden[name]["0"] - if digest == want: - exact += 1 - print("%-28s %d flush(es) sha256 %s.. == golden: EXACT" % (name, frame.flushes, digest[:16])) - continue - mismatched.append(name) - print("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!") - print("!! %s: DIGEST MISMATCH through lv.image -- this fails the test" % name) - print("!! got %s" % digest) - print("!! golden %s" % want) - print("!! diagnostics (never a pass): positional / mean checks") - print("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!") - try: - err = check_means(name, frame.buf, reference) - checked = 0 if name.startswith("c920e_") else check_bars(name, frame.buf) - print("%-28s diagnostics: mean err r=%+.3f g=%+.3f b=%+.3f within %.1f, %d bar samples within %d" - " -- colours and stride are right, so the pixels are shuffled (a subtle fault)" - % (name, err[0], err[1], err[2], MEAN_TOL, checked, POS_TOL)) - except AssertionError as e: - print("%-28s diagnostics: %s -- a gross fault (colours or stride)" % (name, e)) - print("digests exact through lv.image: %d of %d" % (exact, len(CORPUS))) - assert exact == len(CORPUS), "not the exact gate: %s differ from golden_tjpgd.json through lv.image" % mismatched - - # 3. decoder_info tells the truth (read back through the widget's source size) - print("== 3. decoder_info: stream size wins, non-JPEG not claimed, FF D8 pixels not claimed") - wrong = image_dsc(jpegs["c920e_320x240_dri.jpg"], 1, 1) - img = show(disp, frame, wrong) - assert src_size(img) == (W, H), "header 1x1 was echoed: %s" % (src_size(img),) - assert (img.get_width(), img.get_height()) == (W, H) - assert sha256_hex(frame.buf) == golden["c920e_320x240_dri.jpg"]["0"], "header 1x1: digest differs from golden" - print("dsc header 1x1 -> lv.image sees %dx%d from jd_prepare, digest EXACT" % src_size(img)) - # LVGL's built-in BIN decoder claims any variable source whose header cf - # is not UNKNOWN (RAW included) and echoes the header; jpegio's decoder - # sits ahead of it and never echoes (its size comes from jd_prepare or - # not at all), so a header-sized result means jpegio did not claim it. - junk = b"hello, not a jpeg at all " * 8 - img = show(disp, frame, image_dsc(junk, 8, 8)) - assert src_size(img) == (8, 8), src_size(img) - soi_only = b"\xff\xd8" + b"\x00" * 62 - img = show(disp, frame, image_dsc(soi_only, 8, 8)) - assert src_size(img) == (8, 8), src_size(img) - print("non-JPEG bytes and a bare SOI with cf RAW: not claimed by jpegio (LVGL's built-in decoder echoes the 8x8 header)") - pixels = b"\xff\xd8" + b"\x00\x00" * 7 # 8x1 RGB565 whose first pixel is 0xD8FF - px_dsc = image_dsc(pixels, 8, 1, lv.COLOR_FORMAT.RGB565) - img = show(disp, frame, px_dsc) - assert src_size(img) == (8, 1), src_size(img) - assert (img.get_width(), img.get_height()) == (8, 1) - assert pixel(frame.buf, W, 0, 0) == (0xD8, 0x1C, 0xF8), pixel(frame.buf, W, 0, 0) - assert pixel(frame.buf, W, 1, 0) == (0, 0, 0) - print("an RGB565 pixel buffer starting FF D8 falls through to LVGL's built-in decoder (8x1, first pixel 0xD8FF)") - - # 4. a truncated scan: headers parse (info OK), the decode fails, nothing crashes - print("== 4. truncated scan") - rst = jpegs["restart_dri_320x240.jpg"] - half = rst[:len(rst) // 2] - half_dsc = image_dsc(half) - img = show(disp, frame, half_dsc) - assert src_size(img) == (W, H), src_size(img) # headers parse: info OK - assert sha256_hex(frame.buf) == bg, "half a scan drew something: %s" % sha256_hex(frame.buf) - print("half a scan (%d of %d bytes): info OK, open refused, nothing drawn, refr_now survived" % (len(half), len(rst))) - - # 5. a file source through fs_driver.py (frozen in by lvgl-micropython's manifest) - print("== 5. file source") - try: - import fs_driver - except ImportError: - fs_driver = None - if fs_driver is None: - print("fs_driver not frozen in: file-source path not exercised here") - else: - fs_driver.register("S") - path = "S:" + FRAMES + "/c920e_320x240_dri.jpg" - img = show(disp, frame, path) - assert src_size(img) == (W, H), src_size(img) - digest = sha256_hex(frame.buf) - want = golden["c920e_320x240_dri.jpg"]["0"] - assert digest == want, "file source DIGEST MISMATCH: got %s golden %s" % (digest, want) - print("%s: sha256 %s.. == golden: EXACT" % (path, digest[:16])) - img = show(disp, frame, "S:" + FRAMES + "/README.md") - assert src_size(img) == (0, 0), src_size(img) - assert sha256_hex(frame.buf) == bg - print("a non-JPEG file (README.md) is not claimed, nothing drawn") - - # 6. memory over repeated refreshes (each: open -> decode -> close) - print("== 6. memory over 20 refreshes of the C920e frame") - dsc = image_dsc(jpegs["c920e_320x240_dri.jpg"]) - img = show(disp, frame, dsc) - assert sha256_hex(frame.buf) == golden["c920e_320x240_dri.jpg"]["0"] - for _ in range(3): - img.invalidate() - lv.refr_now(disp) - gc.collect() - f0 = gc.mem_free() - for _ in range(20): - img.invalidate() - lv.refr_now(disp) - f1 = gc.mem_free() - gc.collect() - f2 = gc.mem_free() - print("mem_free before %d, after 20 (uncollected) %d, collected %d" % (f0, f1, f2)) - assert f2 >= f0 - 4096, "mem_free trends down across refreshes (a %d-byte decode buffer per refresh?)" % (W * H * 2) - - # 7. deinit / init drops the decoder; register again and decode once more - print("== 7. lv.deinit() / lv.init()") - lv.deinit() - assert jpegio.lvgl_decoders() == () - expect_raises(RuntimeError, jpegio.register_lvgl_decoder, "lv.init() first") - lv.init() - assert DECODER not in decoders() - assert jpegio.register_lvgl_decoder() is True - assert decoders().count(DECODER) == 1 - print("decoder gone after deinit, back after register_lvgl_decoder(): %s" % decoders()) - frame = Frame() - disp, draw_buf = setup_display(frame) - keep.append(image_dsc(jpegs["baseline_jfif_320x240.jpg"])) - img = show(disp, frame, keep[-1]) - assert sha256_hex(frame.buf) == golden["baseline_jfif_320x240.jpg"]["0"], "digest changed after the deinit/init cycle" - print("baseline_jfif_320x240.jpg after the cycle: digest EXACT") - - lv.deinit() - print("jpegio LVGL tests passed: %d corpus frames through lv.image, %d digests exact" % (len(CORPUS), exact)) - - -main() diff --git a/tools/check_qstr_reachability.py b/tools/check_qstr_reachability.py index 65ac400..4d370b3 100644 --- a/tools/check_qstr_reachability.py +++ b/tools/check_qstr_reachability.py @@ -49,8 +49,8 @@ #: exist on every build whether anything here collects them or not. Keep this #: list short and add to it only with the reason: a name is in the pool because #: MicroPython's own core uses it, and that can change between versions. -#: ``__init__`` is here for ``src/jpegio/jpegio.c``'s LVGL decoder registration. -CORE_POOL = frozenset({"__init__"}) +#: Empty since jpegio (which needed ``__init__``) moved to micropython-pydevices. +CORE_POOL = frozenset() QSTR = re.compile(r"MP_QSTR_(\w+)") IFACE_BLOCK = re.compile(