Refactor ImageState + add highlight-recovery, fix exposure, smooth hi…#7
Merged
Refactor ImageState + add highlight-recovery, fix exposure, smooth hi…#7
Conversation
…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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
…stogram
Large cleanup branch that rewrites the RAW-processing glue and fixes several rendering issues uncovered along the way.
Cleanup & dedup
lib/services/isolate_processor.dart,lib/ffi/raw/raw_processor.dart, and the adjustment math inlib/services/image_processor.dart(nothing called it).scripts/build_test_libs.sh,dev.myyc.aks.yaml, andlinux/CMakeLists.txtall compileraw_processor_wrapper.cwhich includeslib/ffi/raw/raw_processor_common.c. Previously there were three drifting copies of the same source.RawPixelDatato its own file; extractapplyCropToImageintocrop_service.dart; extractgenerateCurveLookupTableintoprocessors/curve_lut.dart(was copy-pasted 3x).ImageState refactor
lib/models/image_state.dartfrom 546 → ~370 LOC.ImageCacheBundlecentralises disposal of the 4ui.Imagerefs.ProcessingPipelineowns pixel buffers, timers, processor dispatch; fixes the line-406 unawaited-timer leak.Highlight recovery
params.highlight = 2(blend) so the one-channel 255 pile-up (sky / window highlights) is redistributed instead of clipped. Plumbing supports clip/blend/reconstruct viaHighlightModeenum; a settings panel can expose the selector later — see CLAUDE.md.exp_shift = 2.0(+1.0 EV) withexp_preser = 1.0so 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
generateExposureLUTand the Vulkan shader'sapplyExposureboth previously didbyte * 2^EVon sRGB-encoded values, massively over- amplifying midtones (at +1 EV, sRGB 128 → 256 clipped instead of ~179).Histogram smoothing
sqrt(total/target)instead oftotal/target, so a 24MP image actually samples ~50k pixels (was ~96).Tests & lints
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_image_{1,2,3,4}.{arw,raf}gitignored — they're populated from local photo libraries and tests skip gracefully when absent.Numbers