Repository navigation
feat(canvas): Clarity, Texture and Dehaze adjustments on layers and groups - #10001
Merged
Merged
Conversation
Adds a Noise raster/group adjustment with Gaussian or Uniform distribution, monochromatic or color grain, amount, and a re-rollable seed. Noise is a deterministic function of position, so display, generation, export, merges and PSD share the same grain. Layer bakes and PSD now apply a layer's own adjustments before resampling, matching the display.
Noise gains a 1–8 px grain size (coordinate-pure Gaussian-correlated samples), a midtones response that fades grain to none at black and white, and a chroma grain on OKLab a/b that keeps lightness. Re-records the editor source-owner set and canvas route bytes for the larger kernel.
Any noise adjustment, enabled or not, raises the canvas and the project floor to 5 and never lowers it, so older builds refuse the project instead of dropping the adjustment stack and saving the loss.
Merge Down bakes both layers' stacks layer-local before compositing; Rasterize bakes a noise stack before the transform and drops it. Composites reserve measured text scratch past its estimate or refuse as over budget.
Covers grain size, tonal response and chroma, how Merge Down and Rasterize bake adjustments, the noise kernel's cost and region-independence invariants, and that older versions refuse projects with noise.
…ustment # Conflicts: # invokeai/frontend/webv2/performance/browser-baseline.json # invokeai/frontend/webv2/src/workbench/projects/projectFile.test.ts
A legacy project kept whole because its workflow repeats an item id now sends the raised canvas floor like split saves do, so saving Noise into it stores floor 5 and older clients refuse it.
Export reuses the display's adjusted copy only when it is current and never builds a layer-sized one, so a cache miss adjusts just the reserved sampled region.
Add Clarity, Texture, and Contrast Dehaze approximation invocations with shared numerical primitives, schemas, tests, and docs.
Allow Clarity, Texture, and Contrast Dehaze amounts from -200 to 200.
…ab restore Restore Oklab lightness in float64 per tile and gamut-clip only pixels more than 1e-5 out of range, so float32 round-trip noise no longer shifts colours with a 0 or 255 channel by up to 45 levels.
…backend nodes Port the nodes' numerics into the canvas engine (alpha-bounds domain, OpenCV chamfer fill, guided-filter bands, Oklab with gamut clip) and replace the unused Generation primitives. A Python-generated parity fixture pins both sides byte for byte; provenance records the OpenCV and Oklab sources.
Add a lazily created module worker and a job service with priorities, same-slot replacement, abort, memory reservations and crash recovery. Nothing renders through it yet.
…schema v5 Adds the three adjustment entries to raster layer and group stacks. Documents holding any of them, enabled or not, write schema v5 so older builds refuse them instead of dropping the stack.
…he worker Layers and groups show a per-pixel interim while a job runs and install only exact results. A keyed status store reports computing, too-large, over-budget and error per owner.
…djustments Every pixel-writing operation evaluates spatial stacks exactly in the worker or refuses with a message naming the layer and the cause. PSD export approximates spatial group stacks per layer.
Adds the menu entries, Amount and Size controls, and a row indicator for pending or failed adjustments, with user docs.
Main's growth and the Noise adjustment together exceed the 1% byte allowance on the editor build and on canvas activation.
JPPhoto
requested changes
Oct 23, 2026
JPPhoto
left a comment
Collaborator
There was a problem hiding this comment.
Merge blockers:
- Must fix - Raster-to-control conversion and copy drop enabled spatial adjustments.
invokeai/frontend/webv2/src/workbench/widgets/layers/layerOps.ts:483clones the raw source for control layers. The context menu reaches this path for both conversion and copy atinvokeai/frontend/webv2/src/workbench/widgets/layers/LayerContextMenu.tsx:473and:730. A converted layer loses its adjustment stack; a copied control layer receives unadjusted pixels. Bake the exact result or refuse the action when evaluation fails. If skipped, control inference can use pixels different from the displayed layer. This happens whenever an affected layer has an enabled spatial adjustment. Conversion can be undone, but generated results from the copied pixels must be rerun. Add coverage that asserts exact pixels or refusal for both actions.
Other findings/issues:
- Should fix - The parity test does not enforce the byte-identical claim.
invokeai/frontend/webv2/src/workbench/canvas-engine/render/spatial/evaluateSpatialSteps.test.ts:51allows up to 1 byte of difference and up to 1% differing visible channels at lines 57-58. Tighten the assertion to exact bytes, or revise the claim to state the accepted tolerance. Otherwise small output drift can pass while the PR still claims exact backend parity. Add a test that enforces the chosen contract.
… byte The parity test now requires exact visible bytes; every fixture case already matched.
…s adjustments Copy and convert to control bake the exact adjusted pixels, or refuse naming the layer when the stack cannot run. Convert is one undo step that restores the raster layer, its stack and its pixels; unadjusted and empty layers clone as before.
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.
Summary
Adds Clarity, Texture and Dehaze as non-destructive adjustments on raster layers and raster groups, using the same kernels as the backend nodes from #9891.
Shares canvas schema v5 with the Noise adjustment (#9933).
Related Issues / Discussions
Closes #9813
Builds on #9891 (backend nodes) and #9933 (Noise adjustment, schema v5).
QA Instructions
Automated (Node 22.14, pnpm 10):
pnpm check:releasepasses: lint, 11,179 unit tests, 3,345 browser tests, fixtures, both performance gates, the project-file journey and accessibility journeys.pytest tests/backend/image_utilpasses, including the shared parity fixture that pins the TS kernels to the Python output.Manual, on the mock backend:
Not yet done: a real-backend comparison of a canvas result with the node on the same alpha-trimmed image.
Screenshots, on the mock backend:
Add adjustment menu on a raster layer:
Texture +80, Clarity +120 and Dehaze +60 on one layer, with Clarity selected:
Dehaze settings and hint:
Before and after, with the three adjustments off, then on:
Row status: the spinner while a job runs on a 3072² layer, and the warning with its tooltip on a 4100² layer:
Review
Resolved findings:
Known limits:
Compatibility / Rollout
Schema: documents holding any of the three adjustments, enabled or not, save as canvas schema v5. This is shared with Noise (feat(canvas): noise adjustment for film grain #9933), so older builds refuse those documents instead of silently dropping the adjustment stack. Documents without them keep their current version.
Bundle size: measured against feat(canvas): noise adjustment for film grain #9933's build:
The browser baseline re-records only byte figures and startup-module lists; timings are unchanged. The kernels load lazily in a 9 KB worker chunk.
Memory: worker scratch (at most about 400 MB, one job at a time per engine) is outside the raster memory budget. The pixel buffer each job uses is reserved.
Checklist
What's Newcopy (if doing a release after this PR)