Skip to content
Merged
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
77 changes: 77 additions & 0 deletions .agents/skills/rust-wasm/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,83 @@ Minimize `unsafe`. Document and test any required `unsafe` block.

Return explicit error/status values rather than using panics as the normal JS-facing error path.

## Validation

Build every changed artifact before testing it:

```sh
npm run wasm:build:jpeg
npm run wasm:build:png
```

Run the reference JPEG and PNG validation corpora into ignored directories so historical reports
remain unchanged:

```sh
npm run corpus:imazen -- --corpus ../codec-corpus --format jpeg --output benchmark/.tmp/imazen-reference-jpeg --baseline benchmark/results --timeout-ms 30000 --memory-mb 512 --concurrency 2
npm run corpus:imazen -- --corpus ../codec-corpus --format png --output benchmark/.tmp/imazen-reference-png --baseline benchmark/results --timeout-ms 30000 --memory-mb 512 --concurrency 2
```

These commands currently register the plain TypeScript codecs in
`scripts/validate-imazen-worker.ts`; they do not exercise a WASM accelerator. Use them to protect
the reference codec and fallback behavior, never as evidence that scalar or SIMD WASM ran. The
Imazen workflow verifies decode, PNG encode and reopen, dimensions, and process safety. It does not
prove exact pixel parity.

Run the forced scalar and SIMD lanes for every accelerated web codec:

```sh
for format in jpeg png webp; do
for variant in scalar simd; do
npm run corpus:imazen:wasm -- --corpus ../codec-corpus --format "$format" --variant "$variant" --output "benchmark/.tmp/imazen-$format-wasm-$variant" --timeout-ms 30000 --memory-mb 512 --concurrency 2
done
done
```

The JPEG and PNG lanes require the selected WASM kernel for each input inside that accelerator's
documented subset and record expected reference fallback outside it. Every successful input is
decoded exactly against TypeScript and encoded through the forced WASM encoder. Scalar JPEG and
PNG encoding require deterministic byte parity. SIMD JPEG encoding uses the benchmark's AAN gate:
decoded PSNR must stay within 0.05 dB and output size within 1% of the TypeScript encoder. The WebP
lane requires the selected WASM decoder and encoder for each supported still image.

A WASM corpus lane must:

* explicitly register scalar and SIMD loaders in separate runs;
* set `minimumPixels: 1` and `minimumEncodePixels: 1` so small corpus files do not silently skip
WASM;
* count or otherwise assert loader and accelerator use for every eligible operation;
* compare exact decoded pixels or hashes with the TypeScript reference for supported valid inputs;
* compare metadata, dimensions, output bytes where deterministic, structured errors, and strict and
tolerant decoding behavior;
* preserve isolated per-file time and memory limits; and
* fail on unexpected fallback, traps, crashes, timeouts, out-of-memory failures, raw exceptions,
invalid output, or a valid-file behavior change.

Do not rely on the public accelerator's automatic selection for this gate because it may choose
SIMD or transparently fall back. If the repository has no reusable corpus runner with explicit
engine selection and accelerator-use assertions, add one or report the missing coverage. Do not
claim full WASM corpus validation from an ad hoc run that cannot prove which path executed.

Pass `--baseline benchmark/results` to make the Imazen CLI fail when a generated report differs
from the checked-in baseline per file. The comparison includes outcome, last completed stage,
structured error code, diagnostic, child exit code, and signal. Without `--baseline`, the CLI only
writes reports. Do not rely only on aggregate totals. Compare current WASM with the previous WASM
artifact as well as the TypeScript reference: safe acceptance differences for invalid or flexible
inputs may be established tolerant behavior, but must remain explicit.

If every isolated corpus record reports `process-crash` at `start`, check whether the sandbox blocked
child Node processes. Rerun with child-process permission before attributing the result to a codec.

After corpus validation, run focused WASM tests, real-browser scalar/SIMD selection and fallback
coverage, then the full repository gate:

```sh
npx vitest run tests/wasm-jpeg.test.ts tests/wasm-png.test.ts
npx playwright test browser-tests/compatibility.pw.ts --grep 'JPEG|PNG'
npm run check
```

## Acceptance rule

Do not introduce WASM merely because a kernel benchmark is faster.
Expand Down
128 changes: 128 additions & 0 deletions .github/workflows/imazen-corpus.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
name: Imazen codec corpus

on:
pull_request:

permissions:
contents: read

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
validate:
name: ${{ matrix.format }} corpus
runs-on: ubuntu-latest
timeout-minutes: 20
strategy:
fail-fast: false
matrix:
format:
- jpeg
- png
- webp
- tiff
- gif
- bmp

steps:
- name: Checkout PureJsImage
uses: actions/checkout@v7
with:
persist-credentials: false

- name: Checkout pinned Imazen codec corpus
uses: actions/checkout@v7
with:
repository: imazen/codec-corpus
ref: 28205bbc5cf40364d012c462240ba28143373d67
path: codec-corpus
persist-credentials: false

- name: Set up Node.js
uses: actions/setup-node@v7
with:
node-version: 22
cache: npm

- name: Install dependencies
run: npm ci

- name: Validate corpus behavior against checked-in baseline
run: >-
npm run corpus:imazen --
--corpus codec-corpus
--format "${{ matrix.format }}"
--output "$RUNNER_TEMP/imazen-${{ matrix.format }}"
--baseline benchmark/results
--timeout-ms 30000
--memory-mb 512
--concurrency 2

- name: Upload corpus report
if: always()
uses: actions/upload-artifact@v7
with:
name: imazen-${{ matrix.format }}-corpus-report
path: ${{ runner.temp }}/imazen-${{ matrix.format }}/
if-no-files-found: warn
retention-days: 30

validate-wasm:
name: ${{ matrix.format }} ${{ matrix.variant }} WASM corpus
runs-on: ubuntu-latest
timeout-minutes: 20
strategy:
fail-fast: false
matrix:
format:
- jpeg
- png
- webp
variant:
- scalar
- simd

steps:
- name: Checkout PureJsImage
uses: actions/checkout@v7
with:
persist-credentials: false

- name: Checkout pinned Imazen codec corpus
uses: actions/checkout@v7
with:
repository: imazen/codec-corpus
ref: 28205bbc5cf40364d012c462240ba28143373d67
path: codec-corpus
persist-credentials: false

- name: Set up Node.js
uses: actions/setup-node@v7
with:
node-version: 22
cache: npm

- name: Install dependencies
run: npm ci

- name: Validate WASM parity, quality, and accelerator use
run: >-
npm run corpus:imazen:wasm --
--corpus codec-corpus
--format "${{ matrix.format }}"
--variant "${{ matrix.variant }}"
--output "$RUNNER_TEMP/imazen-${{ matrix.format }}-wasm-${{ matrix.variant }}"
--timeout-ms 30000
--memory-mb 512
--concurrency 2

- name: Upload WASM corpus report
if: always()
uses: actions/upload-artifact@v7
with:
name: imazen-${{ matrix.format }}-${{ matrix.variant }}-wasm-corpus-report
path: ${{ runner.temp }}/imazen-${{ matrix.format }}-wasm-${{ matrix.variant }}/
if-no-files-found: warn
retention-days: 30
6 changes: 6 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,12 @@
browser File/Blob, Uint8Array/Blob output, CompressionStream, and origin-private storage behavior
behind the browser adapters. Do not duplicate codec or pixel-processing implementations between
runtimes.
- Temporary-file use must be off by default and require an explicit public opt-in. Document the
memory, process-RSS, storage-capacity, and performance tradeoffs wherever the option is exposed.
Before consuming input rows, probe file creation, writing, reading, and truncation. If opt-in
setup or writing fails, continue with the memory implementation and preserve the same output.
Report failures that prevent recovery of already-written bytes as structured `ImageError`s, and
clean up every created file or directory.
- Browsers cannot open arbitrary local path strings. Browser inputs should use File/Blob,
ArrayBuffer, Uint8Array, fetched bytes, or an explicit `ImageSource`; browser outputs should use
Uint8Array, Blob, or an explicit `ImageSink`.
Expand Down
48 changes: 48 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,54 @@ All notable changes to PureJsImage are documented in this file.

## [Unreleased]

### Added

- Added an explicit optional Rust/WASM WebP accelerator with scalar and SIMD modules for VP8 row
color conversion, VP8L predictor and color transforms, lossless encode transforms, and lossy
RGBA or gray to YUV420 conversion. Measured RGB input keeps the existing paired-row TypeScript
conversion because it is faster end to end. The TypeScript codec still owns RIFF parsing, entropy
coding, limits, metadata, and bounded row orchestration. Module or kernel failures fall back to
the TypeScript path. The pull-request corpus workflow now forces scalar and SIMD WebP runs and
checks exact decode pixels, deterministic encode bytes, and actual kernel use.
- Added a pull-request-only Imazen corpus workflow for JPEG, PNG, WebP, TIFF, GIF, and BMP. The
workflow checks out the corpus at a pinned commit, runs every file in an isolated process, fails
on per-file behavior changes, and uploads the generated reports for review.
- Added forced scalar and SIMD Imazen lanes for JPEG, PNG, and WebP. Eligible inputs must execute
the selected WASM kernels. JPEG and PNG decode pixels remain exact, deterministic scalar and PNG
encoders require byte parity, and the SIMD JPEG AAN encoder keeps its existing PSNR and size gate.

### Changed

- Refreshed the stable-codec, cross-library web-codec, JPEG, PNG, and WebP WASM benchmarks, package
metrics, result index, README charts, website charts, and accelerator benchmark pages after the
current codec and runtime work.
- Node orientation and rotation now use lazy chunked memory by default. Pass
`{ temporaryFiles: true }` as the second argument to `createImageLibrary()` to opt into a lower
process-RSS file spool. The opt-in path probes file creation, writing, reading, and truncation
before consuming image rows. Failed setup or later file writes fall back to memory. The previous
64 MiB Node memory fallback ceiling has been removed; normal image limits still apply.
- The optional WebP WASM accelerator now uses four-pixel SIMD for VP8L color transforms, fuses the
common inverse color, predictor, and subtract-green row sequence into one call, and specializes
uniform mode-11 prediction. Seven paired 4000x3000 lossless WebP trials reduced the median from
617.90 ms to 518.50 ms, with exact output and a 2.00% peak RSS reduction.
- The optional Rust/WASM JPEG encoder now uses a separable f32 AAN transform, precomputed
reciprocal quantization factors, and four-pixel SIMD RGB and RGBA compositing and color
conversion. Unused SIMD scratch arrays were removed. Seven paired end-to-end trials reduced the
`png-to-jpeg` workflow median by 24.58% with the output quality, size, and correctness gates
unchanged.
- The optional Rust/WASM JPEG decoder now skips the full IDCT for DC-only blocks. Seven paired
end-to-end trials reduced the `jpeg-to-png` workflow median by 5.64% with exact output
correctness and a 0.46% peak RSS reduction.
- JPEG WASM exactness now covers DC-only inverse-transform half-integer boundaries and scalar
encoder values immediately below a positive half-integer. The Rust decoder now validates the
end-of-scan marker after its final MCU while still allowing legal bytes after EOI.
- Rust/WASM JPEG and PNG builds now reserve a bounded 128 KiB stack, trap on unexpected panics,
and reject out-of-bounds or overlapping JPEG buffers before reading or writing memory. Initial
accelerator memory remains within four WebAssembly pages.
- The Imazen corpus command now accepts `--baseline <directory>` and exits with an error when a
file changes outcome, completed stage, structured error, diagnostic, child exit code, or signal.
Timing and machine metadata remain outside the comparison.

## [0.16.0] - 2026-08-21

### Added
Expand Down
Loading