mirror of
https://github.com/storytold/photocraft.git
synced 2026-11-02 18:13:39 +00:00
b07b7fab55
* perf(seam): drop the original-column map from the shrink proxy, same pixels (-15 % at 24 MP) Content-Aware Scale's proxy seam search tracked each panel column's original column in an idx map, one usize per proxy pixel, shifted by every seam. The original column is x plus the number of columns already taken at or left of it, so a per-row list of the taken columns answers the same question without a buffer the size of the energy map. The recorded P28 budget number predates the proxies: the 2026-10-05 baseline commit carves at full height and recomputes energy per seam, and measures 266 s for the 24 MP case on the machine this was timed on, against 2.6 s now. Documented in docs/seam-carving-performance.md; enforce stays off until a budgeted-class run can vouch for the whole row. * review: generate exactly ch samples per pixel in bench_seam, reject ch > 4 The fixture flattened four samples per pixel and then truncated to w*h*ch, so a three-channel run grouped samples across pixel boundaries and any ch above four built a buffer shorter than the rows carve_width indexes. Generate ch samples inside the per-pixel closure and refuse ch outside 1..=4. The three-channel row is dropped from the perf note rather than re-measured here: it moved inside the timer noise either way, and the other agents' builds on this machine make a fresh number worthless. The four-channel hashes are unchanged, which is the check that this edit touched only the fixture. * perf(seam): find the removed column by binary search, not a linear scan Recording a seam recovered each removed original column by scanning the row's list of already-taken columns, so a deep shrink paid a comparison per removed column on every row and every seam: quadratic in the seam count, and worse than the index map this PR replaced once a shrink got deep enough. `o - i` never decreases along the row's sorted list, so that count is a binary search, and the original column belongs where the search stopped; the rows are independent and update in parallel, like the pixel shift. What is left per removed column is one insert. Output is unchanged: same columns, same order, same hashes (10 % and 50 % width cases below), and `counted_removed_columns_match_the_tracked_map` now runs a tracking and a counting carver side by side to one column and compares every row's list after every seam. docs/seam-carving-performance.md gains those cases, states that the ~104x figure is the two example runs rather than the baseline row, and names perf/baseline.json as the source of the 954 s. --------- Co-authored-by: ebosido444-ai <ebosido444-ai@users.noreply.github.com>
33 lines
1.6 KiB
Rust
33 lines
1.6 KiB
Rust
//! Timing and output hash for Content-Aware Scale's seam carving core (P28).
|
|
//!
|
|
//! `cargo run --release -p photocraft-algo --example bench_seam [width height new_width channels]`
|
|
//!
|
|
//! Prints the carve time and an FNV-1a hash of the output's raw f32 bits, so two builds can be
|
|
//! compared with `cmp`-level strictness without writing tens of megabytes of pixels to disk.
|
|
use std::time::Instant;
|
|
|
|
fn main() {
|
|
let a: Vec<String> = std::env::args().skip(1).collect();
|
|
let num = |i: usize, d: usize| a.get(i).and_then(|s| s.parse().ok()).unwrap_or(d);
|
|
let (w, h, new_w, ch) = (num(0, 6000), num(1, 4000), num(2, 5400), num(3, 4));
|
|
if !(1..=4).contains(&ch) {
|
|
eprintln!("channels must be 1..=4");
|
|
std::process::exit(2);
|
|
}
|
|
// Deterministic photo-ish content: gradients with fine detail, so the energy map carries
|
|
// structure instead of one flat value per row.
|
|
let img: Vec<f32> = (0..w * h)
|
|
.flat_map(|i| {
|
|
let (x, y) = ((i % w) as f32, (i / w) as f32);
|
|
let d = ((x * 0.13).sin() * (y * 0.07).cos()).abs() * 0.3;
|
|
[(x / w as f32) * 0.6 + d, (y / h as f32) * 0.9 - d, d, 1.0].into_iter().take(ch)
|
|
})
|
|
.collect();
|
|
let t = Instant::now();
|
|
let out = photocraft_algo::seam::carve_width(w, h, ch, &img, None, new_w);
|
|
let ms = t.elapsed().as_secs_f64() * 1e3;
|
|
let hash =
|
|
out.iter().fold(0xcbf2_9ce4_8422_2325u64, |h, v| v.to_bits().to_le_bytes().iter().fold(h, |h, b| (h ^ u64::from(*b)).wrapping_mul(0x100_0000_01b3)));
|
|
println!("{w}x{h}x{ch} -> {new_w}: {ms:.1} ms, {} samples, fnv {hash:016x}", out.len());
|
|
}
|