Commit Graph
15 Commits
Author SHA1 Message Date
FredApps a68db8e1f8 Merge approved PhotoCraft PR #8905: fix(plugins): bound a run's total plug-in memory, not just each instance
Port behavior and regression coverage into current modules; preserve current upstream behavior.
Bound aggregate parallel plugin memory and preserve serial/parallel equality and total-budget regressions.
2026-10-29 04:47:32 +01:00
FredApps 537f206dc3 Merge approved PhotoCraft PR #9509: fix: an edit at full coverage keeps its result over huge 32-bit values
Port unchanged PR hunks to their unique current module locations after upstream file splits.
2026-10-29 03:36:56 +01:00
David MannandClaude Opus 5.5 68c0f034d5 Plug-ins: refuse a code section with no functions but many bytes instead of panicking (#9679)
wasmi 2.0's strict limits divide the code section's size by its function
count before validating it, so a module whose code section declares 0
functions but more than 1000 bytes (crafted, truncated inside the section,
or a duplicate code section) panicked in Plugin::load. Check every code
section for that one case before handing the bytes to wasmi and return
Error::Module; everything else is still left to wasmi.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-26 14:34:41 +01:00
Hector VillarrealandClaude Opus 5.5 d78d7493d3 fix: an edit at full coverage keeps its result over huge 32-bit values (#9483)
Adjustment layers (compose blend_back), destructive edits
(mix_through_selection) and plug-in filters mixed their result back as
o + (r - o) * k. In f32 the difference had already rounded a small
result away against a huge backdrop, so a Color Lookup's 0.1 over 2^20
came out 0.125, and 0.5 over 1e8 came out 0. They now compute
o * (1 - k) + r * k, which is exactly r at full coverage.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 12:03:01 -06:00
David MannandClaude Opus 5.5 ab54aee120 Plug-ins: refuse a manifest that declares a parameter twice (#9441) (#9442)
* Plug-ins: refuse a manifest that declares a parameter twice (#9441)

`ordered_params` kept every entry of the `params` object, so a repeated
key gave one value two dialog controls and the last spec won in
`resolve_params`. Refuse the repeated key with an error naming it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Plug-ins: quote the repeated parameter name in the error (#9441)

The duplicate check runs before validate() checks the name, so a key
with control or bidi characters must not reach the message unescaped.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Address CodeRabbit review: a repeated parameter is named before its spec is read

The key was checked after next_entry had parsed the value and after the
64-parameter limit, so a repeated key with a broken second spec, or one
past the limit, reported that error instead of naming the key.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 10:19:07 -04:00
Dylan M. Taylor 530e0b6b70 security(plugins): reject host routing keys, bound bands and start fuel, open without blocking on FIFOs 2026-10-23 14:41:52 -04:00
John Turnbach 0fdc625919 fix(plugins): bound a run's total plug-in memory, not just each instance
A filter run gives each band a fresh wasmi instance capped at 512 MiB of
linear memory, and ran one band per rayon worker. The cap was therefore
per instance times the thread count: one run on a 16-thread machine could
hold about 8 GiB, and a plug-in smart filter repeated that on every
re-render.

Limits gains max_total_memory_bytes (default 2 GiB), the budget for all
of one run's instances together, and Limits::parallel_bands(threads),
which allows only as many concurrent bands as max_memory_bytes fits into
that budget (at least one). Plugin::apply uses it to size its groups of
parallel bands, so with the defaults a run holds at most four instances
at the cap however many cores there are. Plug-ins configured with a
smaller per-instance cap keep every worker busy. The limit is listed in
docs/plugins.md.

Tests (crates/plugins/tests/abi.rs):
- parallel_bands_fit_the_total_memory_budget: 16 workers with the
  default limits run 4 bands at once, not 16 (failed before: 16 != 4).
- one_band_at_a_time_matches_parallel_bands: a budget that holds one
  instance still covers every band on an 8-thread pool, with the same
  pixels as parallel bands.

Fixes #8603
2026-10-23 11:43:32 -04:00
Michael Higginsandcoderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> d781772ed8 Update crates/plugins/src/manifest.rs
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2026-10-21 08:36:06 -04:00
Higgs32584andClaude Opus 5.5 20c9dec174 Plug-ins: an unknown "unit" is unitless, not a manifest error (#7524)
Unknown manifest fields were always ignored, so a plug-in may already
carry any "unit" value; failing its install would break it. Anything but
"px" (an unknown string, a number, null...) now reads as "none", as when
the field is absent. The plug-in loader has no warning channel, so this
stays silent.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 08:10:16 -04:00
Higgs32584andClaude Opus 5.5 65f38bf3ed Plug-ins: scale "unit": "px" params on a reduced live preview (#7524)
A filter plug-in's live preview runs on a reduced copy of a large
document, but its params only went through the built-in name list, so a
pixel length with another name (Chromatic Aberration's Amount) previewed
2-4 times too large, and a unitless `size` or `radius` was divided.

number and int params now take an optional "unit" ("px" or "none", the
default; anything else is a manifest error). The preview divides px
params by the reduction (defaults included, ints rounded, kept in range)
and limits the factor so a px param with a positive minimum never drops
below it. Unitless params are never scaled.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 08:00:57 -04:00
Ivan · VanTan 10fdcbbefc chore: fix workspace Clippy collapsible_match warnings (#5190)
Strict workspace Clippy stopped on collapsible_match in affinity, raw, plugins, engine, ui-egui and xtask. Pure conditions become guarded match arms (each falling through to an existing empty arm), and the ruler drag state and the measurement command result are bound to locals, preserving evaluation order, error messages and fallbacks.

The same lint fixes also appear as a side commit in #5157 (David2766), which can drop it on rebase.
2026-10-16 17:42:36 -07:00
midasdfandClaude Opus 5.5 40f14d25e8 Plug-ins: keep the originating error across parallel bands (#1654)
When one band failed, its siblings stopped with a generic "another band failed"
error, which could win the collection order and hide the real cause. The abort
flag now carries the first error, so every band reports it; tests cover traps,
error codes and the instruction budget across parallel bands.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 09:11:00 -04:00
MarekSpodBiedry 746f2530e6 Plug-in names reject bidi controls, zero-width spaces and other invisible format characters, and need a visible character (#1011) (#1444) 2026-10-09 11:12:27 +07:00
MarekSpodBiedry 9ce06090b1 Plug-ins: a missing parameter's default is clamped to the declared min..max (#1012) (#1443) 2026-10-09 11:12:24 +07:00
Brandon ThomasandClaude Opus 5.5 7a5c1930b3 Sandboxed WebAssembly plug-ins: photocraft-plugins (wasmi), plugin.* commands, Filter › Plug-ins (#67)
* photocraft-plugins: sandboxed WebAssembly filter plug-ins (wasmi host, ABI v1)

A new L4 crate runs plug-ins in wasmi (pure Rust, MIT/Apache) with no host
imports, a fuel budget metered in slices with a wall-clock deadline, a memory
cap, a recursion cap and wasmi's strict module limits. Every band gets a fresh
instance; traps, budgets, bad manifests and bad pointers are all Errors.
Pixels go over as interleaved normalised f32 in tile-aligned bands, in
parallel on native, blended by the selection. Includes the example Invert
plug-in (no_std Rust, examples/plugins/invert-rs) built into a test fixture,
hostile-module tests, and docs/plugins.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Engine: plugin.* commands (install, list, run, remove, reload)

plugin.run filters the active pixel layer, a targeted channel or a smart
object (recorded as a smart filter) as one undo step; Preferences >
Plug-ins gains Use Additional Plug-ins Folder, loaded when prefs apply.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* UI: Filter > Plug-ins lists installed plug-ins with generated dialogs

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Plug-ins: bands never outgrow the memory cap; regenerate parity

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Bench: example plug-in Invert vs built-in Invert on 24 MP

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 16:08:28 -04:00