Files
photocraft/crates/engine/examples/remove_bench.rs
JohnnYoruandBrandon Thomas 897f9f37ad Add the Remove tool with non-local patch completion (#2193)
* Add the Remove tool with non-local patch completion

Tool::Remove (J flyout) paints a hole that paint.remove fills from its
surroundings as a background job, in one undo step. A stroke around an
object also removes what it encloses; the selection limits the hole.

The fill (photocraft_algo::nonlocal) follows Newson, Almansa, Gousseau and
Pérez (IPOL 2017): texture features in the patch distance, onion-peel
initialisation, match fields upsampled between levels and a best-patch
final copy, plus gradient-domain hiding of edge and interior seams.
Sparse holes are filled in tiles. inpaint::complete (Content-Aware Fill,
Spot Healing, Content-Aware Move) is unchanged.

On 20 holes in public-domain photos (remove_quality) the fill keeps 0.89
of the texture it replaces, against 0.54 with the Wexler/PatchMatch
completion. 24 MP (remove_bench): 100 px ring 120 ms, 1000 px ring 2.5 s.

* Translate the Remove tool's label and tighten comments

Adds "Remove Tool" to every UI catalog, following each one's pattern for
tool names and its existing word for Remove. Corrects comments that no
longer matched the code: the benchmark's description, the tile and margin
minimums, and the reasons given for two parameters.

* Put the Remove tool first in the J group

The J flyout's slot now shows Remove until another J tool is picked, and
J from another group selects it; repeated J goes on to Spot Healing Brush,
Healing Brush, Patch, Content-Aware Move and Red Eye.

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-10 05:19:11 -04:00

36 lines
1.7 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.
//! Timing: the Remove Tool (`paint.remove`) on a 6000×4000 (24 MP) document, through the full
//! command path (`Session::execute`: window read, hole, fill, write, undo snapshot).
//! `cargo run --release -p photocraft-engine --example remove_bench [depth]`
//!
//! Rings around objects of 100, 300 and 1000 px (one completion each) and a 3000 px thin line
//! (completed tile by tile), each undone before the next.
use std::time::Instant;
use photocraft_engine::Session;
use serde_json::json;
fn ring(cx: f64, cy: f64, r: f64) -> Vec<[f64; 2]> {
(0..=96).map(|i| f64::from(i) / 96.0 * std::f64::consts::TAU).map(|t| [cx + r * t.cos(), cy + r * t.sin()]).collect()
}
fn main() {
let depth: u64 = std::env::args().nth(1).and_then(|v| v.parse().ok()).unwrap_or(8);
let mut s = Session::new();
s.execute("file.new", json!({"width": 6000, "height": 4000, "depth": depth})).unwrap();
s.execute("filter.render.clouds", json!({"seed": 3})).unwrap();
s.execute("filter.noise.addNoise", json!({"amount": 8, "seed": 1})).unwrap();
println!("document 6000x4000, {depth}-bit RGB");
let cases: Vec<(String, serde_json::Value)> = vec![
("ring 100 px".into(), json!({"points": ring(3000.0, 2000.0, 60.0), "size": 16})),
("ring 300 px".into(), json!({"points": ring(3000.0, 2000.0, 160.0), "size": 24})),
("ring 1000 px".into(), json!({"points": ring(3000.0, 2000.0, 510.0), "size": 32})),
("line 3000 px, 4 px wide".into(), json!({"points": [[1500, 1000], [4500, 3000]], "size": 2})),
];
for (name, p) in cases {
let t = Instant::now();
s.execute("paint.remove", p).unwrap();
println!("{name:<26} {:>8.1?}", t.elapsed());
s.execute("edit.undo", json!({})).unwrap();
}
}