Skip to content

Wavelet fixes (CDF 9/7, fair bit estimates), DCT edge fixes, and viewer UX - #3

Merged
thundermage117 merged 4 commits into
mainfrom
feature/codec-ux-improvements
Oct 10, 2026
Merged

thundermage117 merged 4 commits into
mainfrom
feature/codec-ux-improvements

Conversation

@thundermage117

Copy link
Copy Markdown
Owner

Summary

Fixes the DWT path, makes the DCT/DWT bitrate comparison fair, fixes DCT edge-case bugs, and adds several viewer features.

Codec (C++)

  • CDF 9/7 wavelet (the JPEG 2000 lossy wavelet), implemented with lifting and symmetric extension, alongside Haar. transform_mode is now 0 = DCT, 1 = Haar, 2 = CDF 9/7, so existing callers that pass 1 still get Haar.
  • Bit estimate. Bits are now estimated per subband as the zeroth-order entropy of the quantization indices. Previously the estimator measured dequantized values, charged 0.5 bit for every zero (so every curve bottomed out near 0.75 bpp), and added a 400-bit header to DWT only. At equal bitrate, CDF 9/7 now beats DCT by about 1.5 dB on the test images, in line with JPEG 2000 vs. JPEG.
  • DWT quantization shrinks the step by 0.75 per coarser level instead of halving it. 0.75 was the best value measured on the bundled images. Chroma steps are 1.5× coarser, matching the ratio the DCT path already uses.
  • DCT fixes:
    • Partial edge blocks are padded and coded instead of copied through unchanged, which inflated PSNR and left their bits uncounted.
    • Output is clamped to 0–255.
    • Quality is clamped to [1, 100]; quality ≤ 0 used to divide by zero.
  • Chroma upsampling is bilinear instead of nearest-neighbour.

WASM bindings

  • The reconstructed image is cached, so switching views no longer redoes colour conversion.
  • New rd_encode for RD sweeps. It skips SSIM and leaves the displayed result untouched.
  • New Subbands view (view mode 7).
  • The Makefile builds EXPORTED_FUNCTIONS from a single WEB_EXPORTS list. _set_artifact_gain was missing from the old list.

Web

  • Transform selector: DCT / Haar / CDF 9/7. transforms.ts is the single registry for labels, URL slugs and chart colours.
  • RD chart draws one curve per transform.
  • Subbands view shows the wavelet pyramid full-frame; coefficients quantized to zero are drawn dim.
  • In wavelet mode, the block inspector entry is replaced by a "Wavelet Subbands" tool. Before, the inspector showed zeros for DWT.
  • Settings are saved in the URL hash (#q=30&t=cdf97&cs=420&v=subbands&img=3) and restored on load.
  • Download PNG button.
  • Shortcuts: [ / ] change quality by 5, T cycles the transform.

Build / CI

  • make sanitize builds in build-sanitize/. The old recipe tested -d on the cache file, so sanitizer flags stayed cached in build/ and the next make test hung.
  • dev, test and coverage now set their CMake flags explicitly, so one target's settings can't carry over to another.
  • Apple clang 17's ASan runtime deadlocks at startup on macOS 26. make sanitize detects this with a 10 s probe and falls back to UBSan only.
  • New sanitize-native CI job runs ASan + UBSan on Ubuntu and gates build-web.

Behaviour changes

  • File-size estimates and RD curves change everywhere, because the bit model changed.
  • Loading an image keeps the current quality instead of resetting it to 50. Shared links need this.

Test plan

  • make test: 52/52 GTest. New tests cover CDF 9/7 reconstruction and gain, subband indexing, coded edge blocks, pixel range, quality clamping, bits monotonic in quality, flat image ≈ 0 bits, 9/7 > Haar, subband map, and smooth chroma upsampling.
  • make sanitize locally: 52/52 under UBSan (ASan is unavailable on this toolchain, see above).
  • npm run check: 0 errors, 0 warnings.
  • npm run test: 193 unit tests. New: url-state, transforms, rdEncode.
  • npm run test:browser: 42 tests, including a new real-WASM suite covering all three transforms, rd_encode isolation and the Subbands output.
  • Manual check in Chromium via Playwright: shared-link restore, Subbands view, shortcuts, RD chart, PNG download; no console errors.
  • First run of the new sanitize-native CI job (ASan on Linux has not been run locally).

Note

feature/motion-estimation has this branch merged into it locally, with conflicts resolved, so it should merge cleanly after this PR lands.

🤖 Generated with Claude Code

thundermage117 and others added 4 commits October 9, 2026 22:22
Wavelets
- Add CDF 9/7 (JPEG 2000 lossy wavelet) via lifting with symmetric
  extension, alongside Haar. TransformType is now DCT / DWT_HAAR / DWT_97
  (WASM transform_mode 0 / 1 / 2; existing callers keep Haar for 1).
- Quant steps shrink by 0.75 per coarser level instead of halving, which
  over-spent bits on coarse bands of a near-orthonormal transform. 0.75
  gave the best PSNR at equal bitrate on the bundled test images.
- Chroma wavelet bands use a 1.5x coarser step, mirroring the JPEG
  chroma/luma table ratio the DCT path already uses.
- subbandMap() renders the quantized pyramid (zeroed coefficients dim).

Bit estimate
- Both paths now estimate bits from quantization indices (previously
  dequantized values) using per-subband zeroth-order entropy
  (EntropyEstimator). The old flat 0.5 bit/zero model floored every
  curve near 0.75 bpp, and DWT alone paid a 400-bit header, so the
  DCT-vs-DWT comparison was skewed.

DCT / pipeline correctness
- Partial edge blocks are padded by edge replication and coded instead
  of copied through untouched (inflated PSNR, uncounted bits).
- DCT output is clamped to [0, 255]; quality is clamped to [1, 100]
  (quality <= 0 divided by zero).
- Chroma upsampling is bilinear instead of nearest-neighbour.

WASM
- Cache the reconstructed BGR image so view switches skip colour
  conversion; add rd_encode/get_rd_bit_estimate for cheap RD sweeps that
  leave the displayed result untouched; add the Subbands view (7).
- Drop the duplicate chroma downsampler in favour of
  ImageCodec::downsampleChannel.
- Makefile builds EXPORTED_FUNCTIONS from a single WEB_EXPORTS list.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Transform selector offers DCT / Haar / CDF 9/7 everywhere (transforms.ts
  is the single registry: labels, URL slugs, chart colours).
- RD chart plots one curve per transform via rd_encode, so sampling no
  longer re-encodes and re-renders the displayed image afterwards.
- Subbands view for wavelets, shown full-frame rather than behind the
  comparison split. The DCT-only block inspector is swapped for a
  "Wavelet Subbands" tool in wavelet mode instead of showing zeros; the
  inspector's broken DWT toggle is replaced with an explanation.
- Settings sync to the URL hash (#q=30&t=cdf97&cs=420&v=subbands&img=3)
  and restore on load, including the bundled test image. Loading an
  image keeps the current quality instead of resetting to 50.
- Download PNG of the processed view; [ / ] change quality by 5, T
  cycles the transform.
- Tests: url-state, transforms, rdEncode unit tests; real-WASM browser
  test of all three transforms, rd_encode isolation and Subbands output.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- sanitize builds in build-sanitize/ instead of build/. The old recipe
  meant to drop the CMake cache but tested `-d` on a file, so
  ENABLE_SANITIZERS stayed cached and the next `make test` hung.
- dev/test/coverage pass ENABLE_COVERAGE/ENABLE_SANITIZERS explicitly
  for the same reason.
- Apple clang 17 on macOS 26 ships an ASan runtime that deadlocks
  before main (re-entrant malloc in InitializeShadowMemory). sanitize
  now runs a probe under a 10 s watchdog and falls back to UBSan only,
  with a warning. UBSan findings now fail the run.
- New CI job runs the C++ suite under ASan + UBSan on Ubuntu and gates
  the web build.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@thundermage117
thundermage117 merged commit ef8abcd into main Oct 10, 2026
4 of 5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant