Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# SPDX-License-Identifier: Apache-2.0
# Copyright 2026 Seth Morrow
# Part of xtQRdecoder, an xTalk port of the ZXing QR decoder.
#
# Static CI gates for the xTalk sources. These are the checks docs/ARCHITECTURE.md
# §5 and §7 describe as the CI gates:
# * tools/lint_lcs.py — the heuristic xTalk syntax linter (front-runs the
# engine for block matching, reserved words, bare
# return, case collisions, loop/undeclared vars, SPDX)
# * build_livecodescript.py --check — fails if lib/xtQRdecoder.livecodescript is
# stale, i.e. a qr/*.lc module changed without
# rebuilding the combined script-only library.
#
# Both tools are pure Python 3 stdlib (no dependencies). They cannot run the xTalk
# engine itself, so qr/qr_tester.lc (399 unit tests) and qr/qr_golden.lc (5 golden
# fixtures) are still verified on a real engine before a release — see the docs.
name: CI

on:
push:
branches: [ main ]
pull_request:

permissions:
contents: read

jobs:
static-checks:
name: xTalk lint + combined-build sync
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.x'
- name: Lint the xTalk modules and the combined library
run: python3 tools/lint_lcs.py qr lib/xtQRdecoder.livecodescript
- name: Verify the combined library is in sync with the modules
run: python3 tools/build_livecodescript.py --check
50 changes: 50 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,56 @@ All notable changes to xtQRdecoder are documented here. The format is based on
[Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and the project aims to
follow [Semantic Versioning](https://semver.org/).

## [Unreleased]

### Performance
- **Further reduced the interpreted hot-loop cost (the documented decode cost
centre, `docs/spec.md` §11) with no change to decode output.** Each change is
bit-identical — verified by simulating the old vs. new logic against the exact
packing model over thousands of random pixel buffers, images, and bit-matrices
— so the 399 unit tests and 5 golden fixtures are unaffected:
- **Luminance (per-pixel).** `luminanceSource_newFromImageData` now walks the
raw pixel plane with a `repeat for each byte` **sequential iterator** and a
4-phase counter, instead of three indexed `byte (o+k) of pRaw` reads per
pixel. Indexed chunk access re-resolves the chunk on every read; `repeat for
each` advances an internal pointer and hands each byte over directly — the
single biggest interpreted-loop lever in xTalk.
- **Global binarizer (per-pixel → per-word).** `globalHistogramBinarizer`'s
whole-image threshold now builds each 32-bit `BitMatrix` word from up to 32
pixels and writes it **once per word** (skipping all-white words), instead of
a `bitMatrix_set` **command** call — and an array read+write — per black
pixel on the O(W·H) loop.
- **Hybrid binarizer (per-pixel).** `hb_thresholdBlock` inlines the black-pixel
set instead of calling `bitMatrix_set` per pixel, removing the per-pixel
handler dispatch and the `shl` call (`shl(1, k)` is exactly `2 ^ k` for k in
0..31) across the thousands of 8×8 blocks.
- **Detector row scan (per-pixel).** `finderPatternFinder`'s main row scan now
loads one 32-bit row word per 32 columns and shifts it one bit per pixel, so
each pixel test is a `bitAnd`/`div 2` instead of a `bitMatrix_get` function
call plus a `uShr` call. Bit-identical for the sequential row walk.
- **Robust path (per scale).** `qrDecodeResultRobust` now builds the
downsampled luminance source **once per scale** and reuses it across
binarizers, instead of recomputing the downsample+greyscale (the cost centre)
for each `(binarizer, scale)` strategy — halving that work on photos that
need the global fallback. The source is read-only downstream, so reuse is
behaviour-preserving.

### Added
- **`ENGINE_RESAMPLE` decode hint (opt-in fast path).** On `qrDecodeResultRobust`,
downsampling large images is the dominant interpreted cost; with this hint the
image is resampled by the engine's **compiled** `resizeImage` and the already-
small `imageData` is read, skipping the per-pixel downsample loop entirely.
Because the engine's resampling filter differs from the interpreted nearest-
neighbour sampler, it changes decode behaviour and is therefore **off by
default** (the default path stays bit-identical); enable it per-call and
re-verify your images decode. Requires a build whose image object supports
`resizeImage` (desktop/mobile).
- **Continuous-integration workflow** (`.github/workflows/ci.yml`) running the two
static gates the docs describe — `tools/lint_lcs.py` over the modules and the
combined library, and `build_livecodescript.py --check` for combined-build sync
— on every push to `main` and every pull request. (Pure Python stdlib; this is
the workflow the README's CI badge already points at.)

## [0.1.0] — 2026-06-03

Second public release. Builds on `0.0.1` with a modern interactive scanner, the
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,7 @@ a **comma-separated string** of `"KEY"` / `"KEY=VALUE"` tokens
| `NR_ALLOW_SKIP_ROWS` | int | Override the finder's row-skip heuristic. `0` forces every row to be scanned (slowest, most thorough). |
| `ALLOWED_DEVIATION` | float | Module-size deviation tolerance when selecting finder candidates (default `0.05`). |
| `MAX_VARIANCE` | float | Tolerance for the 1:1:3:1:1 finder-pattern ratio test (default `0.5`). |
| `ENGINE_RESAMPLE` | flag | *(robust path)* Downsample large images with the engine's **compiled** `resizeImage` instead of the interpreted per-pixel loop — much faster on big photos. Changes the resampling filter, so it's **off by default**; re-verify your images decode. Needs a build whose image object supports `resizeImage` (desktop/mobile). |

---

Expand Down
Loading
Loading