Files
photocraft/crates/plugins/examples/bench_invert.rs
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

45 lines
2.3 KiB
Rust
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! Times the example Invert plug-in on a large image:
//! `cargo run -p photocraft-plugins --release --example bench_invert -- [width] [height]`
//! (default 6000 × 4000 = 24 MP, RGBA 8-bit).
use std::time::Instant;
use photocraft_color::PixelFormat;
use photocraft_geom::Rect;
use photocraft_plugins::{Limits, Plugin};
use photocraft_raster::Surface;
fn main() {
let mut args = std::env::args().skip(1).map(|a| a.parse::<i32>().unwrap_or(0));
let w = args.next().filter(|v| *v > 0).unwrap_or(6000);
let h = args.next().filter(|v| *v > 0).unwrap_or(4000);
let mut s = Surface::new(PixelFormat::RGBA8);
let row: Vec<f32> = (0..w).flat_map(|x| [(x % 256) as f32 / 255.0, 0.5, 0.25, 1.0]).collect();
for y in 0..h {
s.write_region(Rect::new(0, y, w, y + 1), &row);
}
let p = Plugin::load(include_bytes!("../tests/fixtures/invert.wasm"), Limits::default()).expect("load");
if std::env::var("NOOP").is_ok() {
let noop = wat::parse_str(NOOP).unwrap();
let p = Plugin::load(&noop, Limits::default()).unwrap();
let t = Instant::now();
p.apply(&s, Rect::new(0, 0, w, h), None, &serde_json::json!({})).unwrap();
println!("noop: {:.0} ms", t.elapsed().as_secs_f64() * 1e3);
}
let canvas = Rect::new(0, 0, w, h);
for run in 0..3 {
let t = Instant::now();
let out = p.apply(&s, canvas, None, &serde_json::json!({})).expect("apply");
println!("run {run}: {w}x{h} ({:.1} MP) in {:.0} ms", (w as f64 * h as f64) / 1e6, t.elapsed().as_secs_f64() * 1e3);
assert!((out.pixel(1, 1)[0] - (1.0 - 1.0 / 255.0)).abs() < 1e-6);
}
}
const NOOP: &str = r#"(module (memory (export "memory") 1) (data (i32.const 16) "{\"id\":\"n\",\"name\":\"n\",\"kind\":\"filter\"}")
(func (export "pc_abi_version") (result i32) (i32.const 1))
(func (export "pc_manifest") (result i64) (i64.const 0x0000002500000010))
(func (export "pc_alloc") (param $n i32) (result i32) (local $old i32)
(local.set $old (memory.grow (i32.shr_u (i32.add (local.get $n) (i32.const 65535)) (i32.const 16))))
(if (result i32) (i32.eq (local.get $old) (i32.const -1)) (then (i32.const 0)) (else (i32.mul (local.get $old) (i32.const 65536)))))
(func (export "pc_filter") (param i32 i32 i32 i32 i32 i32 i32 i32) (result i32) (i32.const 0)))"#;