Skip to content

Refactor ImageState + add highlight-recovery, fix exposure, smooth hi…#7

Merged
myyc merged 1 commit intomasterfrom
refactor/cleanup-and-imagestate
Apr 15, 2026
Merged

Refactor ImageState + add highlight-recovery, fix exposure, smooth hi…#7
myyc merged 1 commit intomasterfrom
refactor/cleanup-and-imagestate

Conversation

@myyc
Copy link
Copy Markdown
Owner

@myyc myyc commented Apr 15, 2026

…stogram

Large cleanup branch that rewrites the RAW-processing glue and fixes several rendering issues uncovered along the way.

Cleanup & dedup

  • Delete dead code: lib/services/isolate_processor.dart, lib/ffi/raw/raw_processor.dart, and the adjustment math in lib/services/image_processor.dart (nothing called it).
  • Consolidate the native RAW build: scripts/build_test_libs.sh, dev.myyc.aks.yaml, and linux/CMakeLists.txt all compile raw_processor_wrapper.c which includes lib/ffi/raw/raw_processor_common.c. Previously there were three drifting copies of the same source.
  • Move RawPixelData to its own file; extract applyCropToImage into crop_service.dart; extract generateCurveLookupTable into processors/curve_lut.dart (was copy-pasted 3x).

ImageState refactor

  • Slim lib/models/image_state.dart from 546 → ~370 LOC.
  • New ImageCacheBundle centralises disposal of the 4 ui.Image refs.
  • New ProcessingPipeline owns pixel buffers, timers, processor dispatch; fixes the line-406 unawaited-timer leak.

Highlight recovery

  • Hardcode libraw params.highlight = 2 (blend) so the one-channel 255 pile-up (sky / window highlights) is redistributed instead of clipped. Plumbing supports clip/blend/reconstruct via HighlightMode enum; a settings panel can expose the selector later — see CLAUDE.md.
  • Compensate blend's systematic darkening with libraw's exposure-correction feature: exp_shift = 2.0 (+1.0 EV) with exp_preser = 1.0 so highlights are compressed rather than re-clipped. Lands blend within -15%/+7% of clip's mean brightness across 5 test fixtures, with zero re-introduced 255-pile-up.

Linear-light exposure

  • generateExposureLUT and the Vulkan shader's applyExposure both previously did byte * 2^EV on sRGB-encoded values, massively over- amplifying midtones (at +1 EV, sRGB 128 → 256 clipped instead of ~179).
  • Both paths now sRGB-decode → multiply by 2^EV → sRGB-encode. CPU and GPU outputs agree to within 1 LSB on a real 24MP RAW across five EV values (-1, -0.5, 0, +0.5, +1).

Histogram smoothing

  • Apply a 5-tap binomial blur twice (σ≈1.4) to the 256-bin counts before painting, matching Lightroom's display. Eliminates visible per-bin quantisation jitter and tapers legitimate spikes at 0/255.
  • 2D sampling stride is now sqrt(total/target) instead of total/target, so a 24MP image actually samples ~50k pixels (was ~96).

Tests & lints

  • 96 tests green (66 unit + 30 native), up from 23.
  • New: curve_lut_test, optimized_processor_test, edit_pipeline_test (JSON roundtrip), image_cache_bundle_test, processing_pipeline_test (line-406 regression), image_state_test, exposure_precision_test (CPU ≡ Vulkan across 5 EVs), fixtures_histogram_test (blend regression guard on 5 real fixtures).
  • analysis_options.yaml: excludes generated FFI bindings, enables unused_* lints, cleans up 14 pre-existing dead imports/variables.
  • Test fixtures test_image_{1,2,3,4}.{arw,raf} gitignored — they're populated from local photo libraries and tests skip gracefully when absent.

Numbers

  • 38 files changed, +544 / -2282 LOC net.

…stogram

Large cleanup branch that rewrites the RAW-processing glue and fixes
several rendering issues uncovered along the way.

## Cleanup & dedup
- Delete dead code: `lib/services/isolate_processor.dart`,
  `lib/ffi/raw/raw_processor.dart`, and the adjustment math in
  `lib/services/image_processor.dart` (nothing called it).
- Consolidate the native RAW build: `scripts/build_test_libs.sh`,
  `dev.myyc.aks.yaml`, and `linux/CMakeLists.txt` all compile
  `raw_processor_wrapper.c` which includes `lib/ffi/raw/raw_processor_common.c`.
  Previously there were three drifting copies of the same source.
- Move `RawPixelData` to its own file; extract `applyCropToImage` into
  `crop_service.dart`; extract `generateCurveLookupTable` into
  `processors/curve_lut.dart` (was copy-pasted 3x).

## ImageState refactor
- Slim `lib/models/image_state.dart` from 546 → ~370 LOC.
- New `ImageCacheBundle` centralises disposal of the 4 `ui.Image` refs.
- New `ProcessingPipeline` owns pixel buffers, timers, processor dispatch;
  fixes the line-406 unawaited-timer leak.

## Highlight recovery
- Hardcode libraw `params.highlight = 2` (blend) so the one-channel 255
  pile-up (sky / window highlights) is redistributed instead of clipped.
  Plumbing supports clip/blend/reconstruct via `HighlightMode` enum; a
  settings panel can expose the selector later — see CLAUDE.md.
- Compensate blend's systematic darkening with libraw's exposure-correction
  feature: `exp_shift = 2.0` (+1.0 EV) with `exp_preser = 1.0` so highlights
  are compressed rather than re-clipped. Lands blend within -15%/+7% of
  clip's mean brightness across 5 test fixtures, with zero re-introduced
  255-pile-up.

## Linear-light exposure
- `generateExposureLUT` and the Vulkan shader's `applyExposure` both
  previously did `byte * 2^EV` on sRGB-encoded values, massively over-
  amplifying midtones (at +1 EV, sRGB 128 → 256 clipped instead of ~179).
- Both paths now sRGB-decode → multiply by 2^EV → sRGB-encode. CPU and
  GPU outputs agree to within 1 LSB on a real 24MP RAW across five EV
  values (-1, -0.5, 0, +0.5, +1).

## Histogram smoothing
- Apply a 5-tap binomial blur twice (σ≈1.4) to the 256-bin counts before
  painting, matching Lightroom's display. Eliminates visible per-bin
  quantisation jitter and tapers legitimate spikes at 0/255.
- 2D sampling stride is now `sqrt(total/target)` instead of `total/target`,
  so a 24MP image actually samples ~50k pixels (was ~96).

## Tests & lints
- 96 tests green (66 unit + 30 native), up from 23.
- New: `curve_lut_test`, `optimized_processor_test`, `edit_pipeline_test`
  (JSON roundtrip), `image_cache_bundle_test`, `processing_pipeline_test`
  (line-406 regression), `image_state_test`, `exposure_precision_test`
  (CPU ≡ Vulkan across 5 EVs), `fixtures_histogram_test` (blend regression
  guard on 5 real fixtures).
- `analysis_options.yaml`: excludes generated FFI bindings, enables
  unused_* lints, cleans up 14 pre-existing dead imports/variables.
- Test fixtures `test_image_{1,2,3,4}.{arw,raf}` gitignored — they're
  populated from local photo libraries and tests skip gracefully when
  absent.

## Numbers
- 38 files changed, +544 / -2282 LOC net.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@myyc myyc merged commit 866c40c into master Apr 15, 2026
1 check failed
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