Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
756d60f
feat(canvas): noise adjustment for film grain
joshistoast Oct 19, 2026
7752686
Merge branch 'main' into feat/canvas-noise-adjustment
joshistoast Oct 19, 2026
aa758fc
feat(canvas): add grain size, tonal response and chroma to noise
joshistoast Oct 23, 2026
689c96d
fix(canvas): write noise documents as canvas schema 5
joshistoast Oct 23, 2026
5c609c6
fix(canvas): bake adjustments in Merge Down and Rasterize
joshistoast Oct 23, 2026
4708c1a
docs(canvas): document noise grain options and adjustment baking
joshistoast Oct 23, 2026
7f3e020
Merge remote-tracking branch 'origin/main' into feat/canvas-noise-adj…
joshistoast Oct 23, 2026
fc490de
fix(projects): raise the canvas floor on whole-project saves
joshistoast Oct 23, 2026
2202893
fix(canvas): keep composite export within its memory reservation
joshistoast Oct 23, 2026
6f1f350
feat: add built-in image manipulation nodes
JPPhoto Oct 16, 2026
09ccc3f
feat(nodes): extend image adjustment strength range
JPPhoto Oct 16, 2026
456d0cc
fix(image-adjustments): keep saturated colours stable through the Okl…
joshistoast Oct 23, 2026
4982c76
feat(canvas): add Clarity, Texture and Dehaze kernels that match the …
joshistoast Oct 23, 2026
bc8fc76
feat(canvas): run spatial adjustments in a background worker
joshistoast Oct 23, 2026
b2dd3e4
feat(canvas): save Clarity, Texture and Dehaze adjustments in canvas …
joshistoast Oct 23, 2026
4486055
feat(canvas): display spatial adjustments on layers and groups from t…
joshistoast Oct 23, 2026
9fdef00
feat(canvas): keep generation, exports and bakes exact with spatial a…
joshistoast Oct 23, 2026
cc9e280
feat(layers): add Clarity, Texture and Dehaze to the layers panel
joshistoast Oct 23, 2026
13dd07a
perf(canvas): record the spatial adjustment modules and bytes
joshistoast Oct 23, 2026
2de8417
Merge remote-tracking branch 'origin/main' into feat/canvas-noise-adj…
joshistoast Oct 23, 2026
60b8474
perf(canvas): record the editor and canvas bytes after merging main
joshistoast Oct 23, 2026
178a1da
Merge branch 'feat/canvas-noise-adjustment' into feat/canvas-spatial-…
joshistoast Oct 23, 2026
66426b2
Merge remote-tracking branch 'origin/main' into feat/canvas-spatial-a…
joshistoast Oct 23, 2026
fb3b67f
Merge remote-tracking branch 'origin/main' into feat/canvas-spatial-a…
joshistoast Oct 23, 2026
f349719
Merge branch 'main' into feat/canvas-spatial-adjustments
joshistoast Oct 23, 2026
77b61b8
test(canvas): spatial adjustments must match the node output byte for…
joshistoast Oct 23, 2026
5e7e493
fix(canvas): converting or copying a raster layer to control keeps it…
joshistoast Oct 23, 2026
f15954a
Merge branch 'main' into feat/canvas-spatial-adjustments
JPPhoto Oct 24, 2026
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
8 changes: 6 additions & 2 deletions .github/workflows/python-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -144,12 +144,16 @@ jobs:
# Cross-stack generation contracts: the frontend writes these, Python validates them
# against the invocation and architecture registries. A frontend-only change that
# regenerates one would otherwise pass vitest while skipping the backend half.
# Shared Generation numerical fixtures are read by Python tests, so fixture-only changes
# must trigger this suite.
# `__fixtures__/` does not exist yet -- it arrives with the graph-builder fixtures.
# A pattern matching nothing costs nothing here, and listing it now means the branch
# that adds the directory does not also have to remember to add this line.
- 'invokeai/frontend/webv2/src/features/generation/core/__snapshots__/**'
- 'invokeai/frontend/webv2/src/features/generation/core/__fixtures__/**'
- 'invokeai/frontend/webv2/src/features/video/core/__snapshots__/**'
- 'invokeai/frontend/webv2/src/features/upscale/core/__snapshots__/**'
# test_shared_numerical_fixtures.py checks the Python image adjustments against the
# canvas spatial-adjustment kernels' shared fixtures.
- 'invokeai/frontend/webv2/src/workbench/canvas-engine/render/spatial/__fixtures__/**'
# test_ideogram4_frontend_bounds.py reads the Ideogram 4 control bounds from here.
- 'invokeai/frontend/webv2/src/features/generation/core/settings.ts'
- 'tests/**'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,6 @@ In the Workflow Library dialog, **This project** lists the project's own workflo
- **Projects belong to one account.** With [multiple users](/configuration/multi-user-mode/user-guide/) enabled, each person sees only their own projects, and there is no project sharing. Export and import a `.invk` file to pass a project to someone else.
- **Undo history is not saved** with the project; it is cleared when you reload or close it.
- **Invoke needs the server to open projects.** If it cannot reach the server, a **Projects are temporarily unavailable** screen offers to retry and to export any unsaved work your browser is holding.
- **Newer projects need a newer Invoke.** A project saved by a newer version of Invoke shows **Requires a newer Invoke** and cannot be opened until you upgrade. Some features raise this requirement for good: once a project has had a Noise adjustment on the canvas, versions of Invoke without Noise adjustments refuse to open it, even after you remove the adjustment.
- **Newer projects need a newer Invoke.** A project saved by a newer version of Invoke shows **Requires a newer Invoke** and cannot be opened until you upgrade. Some features raise this requirement for good: once a project has had a Noise, Texture, Clarity or Dehaze adjustment on the canvas, versions of Invoke without those adjustments refuse to open it, even after you remove the adjustment.
- **Size limits.** A project file can be up to 2 GB, with up to 20,000 files. Export reports a project that is too large to package.
- **Clear saved data…** under **Data & workspace** in Settings deletes *all* of your projects and settings at once, and cannot be undone.
25 changes: 23 additions & 2 deletions docs/src/content/docs/Users Guide/07.Canvas/03.layers-panel.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -86,11 +86,11 @@ The icons at the top **Arrange** the layer (move it to the front, forward, backw
* **Delete layer**.
* **Raster layers:**
* **Transform**.
* **Add adjustment** — Brightness/Contrast, Exposure, Levels, Curves, Saturation, Hue, Invert or Noise. Adjustments are non-destructive and appear as child rows. Noise adds film grain.
* **Add adjustment** — Brightness/Contrast, Exposure, Levels, Curves, Saturation, Hue, Invert, Texture, Clarity, Dehaze or Noise. Adjustments are non-destructive and appear as child rows. Texture, Clarity and Dehaze change detail and contrast; Noise adds film grain.
* **Add regenerate region** — treats the layer's own shape as an inpaint mask.
* **Filter**, **Select object** and **Run workflow** — process the layer; see the [Canvas guide](/users-guide/canvas/canvas-guide/#filters-and-object-selection).
* **Boolean operations** — combine the layer with the one below: intersect, cut out, cut away or exclude.
* **Convert to** — make it a control layer, inpaint mask or regional guidance layer. Text, shape and gradient layers can also be rasterized into plain pixels.
* **Convert to** — make it a control layer, inpaint mask or regional guidance layer. Text, shape and gradient layers can also be rasterized into plain pixels. Control layers have no adjustments, so converting or copying a layer with adjustments to a control layer bakes them, with the layer's scaling and rotation, into its pixels; undo restores the raster layer with its adjustments.
* **Control layers:** **Transform**, **Filter**, **Select object**, **Run workflow**, **Enable transparent preview**, and **Convert to raster layer**.
* **Inpaint masks:** **Add noise**, **Add denoise limit**, **Extract masked area** (copy the masked part of the image to a new layer), and **Hide layer**.
* **Regional guidance:** **Enable auto-negative**, **Add reference image**, and **Hide layer**.
Expand Down Expand Up @@ -137,6 +137,27 @@ Things to know about Noise:
* **Baking.** Rasterizing a text, shape or gradient layer whose Noise adjustment is enabled bakes its whole adjustment stack into the pixels and removes it, so the grain stays where it was. **Merge down** bakes each layer's adjustments the same way.
* **Older versions of Invoke.** Once a project has had a Noise adjustment, it needs a version of Invoke with Noise adjustments, even after you remove the adjustment; older versions refuse to open it.

**Texture**, **Clarity** and **Dehaze** work on detail and contrast across an area rather than pixel by pixel. They match the [Clarity, Texture and Contrast Dehaze workflow nodes](/users-guide/workflows/image-adjustments/):

* **Texture** — fine detail, such as skin, fabric or foliage. Positive amounts bring it out; negative amounts smooth it.
* **Clarity** — midtone contrast in larger shapes. Positive amounts add punch; negative amounts soften the image.
* **Dehaze** — broad contrast and color intensity. Positive amounts increase both, which can lift a flat, washed-out image; negative amounts reduce both. It is an aesthetic approximation, not physical haze removal: it does not estimate real haze or recover lost detail, and strong settings can clip tones or colors.

Their settings:

* **Amount** — from −200 to 200. The default, 0, changes nothing. Double-click the field or press **Backspace** in it to reset it.
* **Size** (Texture and Clarity only) — how large the detail is that the adjustment affects: 1 to 100 px for Texture (8 by default) and 4 to 1000 px for Clarity (40 by default). It is measured in the layer's own pixels, before the layer is scaled, so resizing a layer does not change which detail is affected. On a group, it is measured in canvas pixels.

Things to know about Texture, Clarity and Dehaze:

* **They are computed in the background.** Each one looks at the whole layer, so the canvas computes it in the background and stays responsive. Meanwhile, the canvas shows the previous result, or the layer without them after you paint on it. A small spinner appears on the layer's row when the work takes more than a moment.
* **Size limit.** They cannot run on content larger than 4096 × 4096 px (16.8 megapixels, measured over the area the layer's visible content spans, in the layer's own pixels before any scaling; for a group, at document resolution). A warning icon then appears on the layer's or group's row, the canvas leaves them out, and anything that uses the layer's pixels with them — generating, exporting, merging, rasterizing, filtering, copying or converting to a control layer, saving to PSD, painting on an image layer — is refused with a message naming the layer or group. Disable or remove them, or crop the content. The same warning appears when there is not enough canvas memory or the computation fails; hover over it for what to do, or select the adjustment to read it in Properties.
* **What you see is what you get.** Generating, exporting, merging, rasterizing and saving to PSD use the same result as the canvas, computing it first if the canvas is still working on it. One exception: PSD has no group adjustments, so a group's Texture, Clarity and Dehaze are applied to each of its layers separately, and the PSD can look different from the canvas.
* **While you drag a selection**, the dragged pixels show without them; once placed, the layer is computed again with them.
* **Thumbnails** of layers the canvas has not shown yet can leave them out.
* **Baking.** Rasterizing a text, shape or gradient layer with one of them enabled bakes its whole adjustment stack into the pixels and removes it. **Merge down** bakes each layer's adjustments the same way.
* **Older versions of Invoke.** Like Noise, once a project has had one of these adjustments, older versions of Invoke refuse to open it, even after you remove the adjustment.

### Control layer

* **Adapter type** — ControlNet, T2I Adapter, Control LoRA, Z-Image ControlNet or Anima ControlNet-LLLite, depending on the model selected in the Generate panel.
Expand Down
5 changes: 3 additions & 2 deletions docs/src/content/docs/development/Front End/project-files.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,9 @@ a maximum of v2 is refused before it can overwrite that document.

A canvas adopts a newer schema only when its content needs one, that is, when an older reader
would drop part of it on load and lose it on the next autosave. Schema 4 marks custom-font
typography (a text source with `fontRef`, `fontStyle` or `fontVariations`). Schema 5 marks a noise
adjustment, enabled or not, on any layer or group at any depth; an older reader would drop the
typography (a text source with `fontRef`, `fontStyle` or `fontVariations`). Schema 5 marks a noise,
clarity, texture or dehaze adjustment, enabled or not, on any layer or group at any depth; an older
reader would drop the
whole adjustment stack holding it. The live document and each saved canvas snapshot are versioned
this way, and the canvas state takes the newest of them. Adoption is sticky: removing the feature
does not lower the version, as the server floor cannot be lowered either. A canvas with neither
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ This record covers the image manipulation invocation and its shared numerical he
## Sources and implementation

The image color-space conversion follows the published [Oklab model and reference equations](https://bottosson.github.io/posts/oklab/).
The scalar edge-aware smoothing follows the guided-filter formulation of He, Sun, and Tang, [Guided Image Filtering (ECCV 2010)](https://doi.org/10.1007/978-3-642-15549-9_1), using normalized box means and reflected boundaries. The sRGB transfer function follows the [CSS Color Module Level 4 definition](https://www.w3.org/TR/css-color-4/#predefined-sRGB).
The scalar edge-aware smoothing follows the guided-filter formulation of He, Sun, and Tang, [Guided Image Filtering (ECCV 2010)](https://doi.org/10.1007/978-3-642-15549-9_1), using normalized box means and reflected boundaries. The sRGB transfer function follows the [CSS Color Module Level 4 definition](https://www.w3.org/TR/css-color-4/#predefined-sRGB). The canvas fill of fully transparent pixels follows the chamfer distance transform of Borgefors, [Distance transformations in digital images (1986)](https://doi.org/10.1016/S0734-189X(86)80047-0).

The clarity, texture, and contrast-dehaze controls are first-party image adjustments built from these documented equations and standard multiscale image-processing operations. Contrast Dehaze is an aesthetic approximation: it is not a physical haze estimator or scene-recovery method. The candidate contains no Dark Channel Prior (DCP) implementation or DCP control.

The references below identify mathematical and specification foundations only. They are not code sources.
The references below identify mathematical and specification foundations; apart from the gamut-clipping reference noted for `numerics.ts` below, they are not code sources.

| Implementation file | Purpose and algorithm/source basis | Libraries and owner |
| --- | --- | --- |
Expand All @@ -23,11 +23,18 @@ The references below identify mathematical and specification foundations only. T
| `invokeai/backend/image_util/guided_filter.py` | Scalar guided image filter; He, Sun, and Tang's guided-filter formulation, implemented with normalized OpenCV box filters. | NumPy, OpenCV core APIs (`cv2`). |
| `invokeai/backend/image_util/multiscale.py` | Difference between two self-guided smooths; built on the guided-filter formulation. | NumPy; delegates to `guided_filter.py`. |
| `invokeai/backend/image_util/texture.py` | Fine-scale detail adjustment with a local RMS gate; guided-filter bands and Oklab lightness. | NumPy, Pillow, OpenCV core APIs (`cv2`); delegates to the `color.py` and `multiscale.py` helpers. |
| `invokeai/frontend/webv2/src/features/generation/core/colorConversion.ts` | sRGB-to-Oklab conversion; published Oklab equations and sRGB transfer function. | TypeScript and standard ECMAScript numeric arrays/math. |
| `invokeai/frontend/webv2/src/features/generation/core/guidedFilter.ts` | Scalar guided filter with reflected boundaries; He, Sun, and Tang's guided-filter formulation. | TypeScript and standard ECMAScript numeric arrays/math. |
| `invokeai/frontend/webv2/src/features/generation/core/multiscale.ts` | Difference between guided-filter smooths; guided-filter formulation. | TypeScript and standard ECMAScript numeric arrays/math; delegates to `guidedFilter.ts`. |
| `invokeai/frontend/webv2/src/workbench/canvas-engine/render/spatial/alphaFill.ts` | Nearest-visible-pixel labels for hidden-pixel fill; reproduces the labels of OpenCV's 5×5 chamfer distance transform (`distanceTransformWithLabels` with `DIST_L2` and `DIST_LABEL_PIXEL`), a chamfer transform after Borgefors. | TypeScript and standard ECMAScript typed arrays. |
| `invokeai/frontend/webv2/src/workbench/canvas-engine/render/spatial/evaluateSpatialSteps.ts` | Canvas evaluation of Clarity, Texture, and Contrast Dehaze over a layer's alpha bounds; mirrors the first-party backend helpers above. | TypeScript and standard ECMAScript typed arrays/math; delegates to the other files in this directory and to the canvas engine's `alphaBounds.ts`. |
| `invokeai/frontend/webv2/src/workbench/canvas-engine/render/spatial/guidedFilter.ts` | Self-guided filter and edge-aware bands with reflected boundaries and float64 box sums; He, Sun, and Tang's guided-filter formulation. | TypeScript and standard ECMAScript typed arrays/math. |
| `invokeai/frontend/webv2/src/workbench/canvas-engine/render/spatial/numerics.ts` | sRGB transfer, Oklab conversion, Oklab gamut clipping, and Python-compatible half-to-even rounding; published Oklab equations, sRGB transfer function, and a scalar port of the backend's `gamut_clip_tensor` (`invokeai/backend/image_util/color_conversion.py`), which follows Björn Ottosson's [sRGB gamut clipping](https://bottosson.github.io/posts/gamutclipping/) reference implementation. | TypeScript and standard ECMAScript numeric arrays/math. Gamut-clipping portion `MIT`, Copyright (c) 2021 Björn Ottosson; the notice is reproduced in the file. |
| `invokeai/frontend/webv2/src/workbench/canvas-engine/render/spatial/spatialSteps.ts` | Step and result types shared with the canvas worker; no algorithm. | TypeScript. |

For **every file in the table**, the implementation was authored for InvokeAI, with no individual author asserted. No code was copied or adapted from the cited works, their reference implementations, or other external code. Each file is licensed under `Apache-2.0` (SPDX identifier in the source); no third-party code or third-party notices are included in these files. The Python modules call OpenCV core APIs only. The project already declares the `opencv-contrib-python` distribution, but this feature uses no contrib-only API and adds no contrib requirement or other dependency.
For every file in the table except the two noted below, the implementation was authored for InvokeAI, with no individual author asserted, and no code was copied or adapted from the cited works, their reference implementations, or other external code.

- `numerics.ts` adapts Björn Ottosson's MIT-licensed gamut-clipping reference code (maximum-saturation polynomial and its coefficients, cusp search, gamut intersection, and the adaptive lightness anchor) through the backend's `gamut_clip_tensor`. It carries Ottosson's MIT copyright and permission notice and the SPDX expression `Apache-2.0 AND MIT`. The backend's `color_conversion.py`, which predates this feature and is outside this manifest, implements the same reference but does not carry that notice; `composition.py` carries a one-line Ottosson attribution.
- `alphaFill.ts`: to return the same labels as the backend, its mask weights, neighbor visiting order, and tie rule were taken from OpenCV 4.11.0's `distanceTransformEx_5x5` (`modules/imgproc/src/distransform.cpp`; OpenCV is distributed under `Apache-2.0`, and that file keeps its earlier Intel and OpenCV Foundation BSD-style notice). The TypeScript is an independent implementation of that behavior; no OpenCV source text is reproduced.

Each file is licensed under `Apache-2.0` (SPDX identifier in the source), except the MIT-licensed gamut-clipping portion of `numerics.ts` described above; no other third-party code or notices are included in these files. The Python modules call OpenCV core APIs only. The project already declares the `opencv-contrib-python` distribution, but this feature uses no contrib-only API and adds no contrib requirement or other dependency.

## License and patent status

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -855,6 +855,7 @@
"source:src/workbench/canvas-engine/render/adjustments.ts",
"source:src/workbench/canvas-engine/render/compositor.ts",
"source:src/workbench/canvas-engine/render/controlTransparency.ts",
"source:src/workbench/canvas-engine/render/exactAdjustments.ts",
"source:src/workbench/canvas-engine/render/groupCompositeScopes.ts",
"source:src/workbench/canvas-engine/render/maskFill.ts",
"source:src/workbench/canvas-engine/render/rasterComposite.ts",
Expand Down
Loading
Loading