Files
photocraft/crates/algo/examples/bench_redeye.rs
869ecf3cff Red Eye tool in the J-group flyout (#1084)
* Add the Red Eye tool to the J-group

Click a red pupil to neutralize the flash reflection. Pupil Size
sets the search window around the click; Darken Amount controls how
far corrected pixels go toward a darker, desaturated pupil.
Catchlights stay white. A miss reports an error instead of a silent
no-op. The kernel only reads a window plus a 5 px mask-filter halo.

* paint.redEye: write back only changed pixels, so CMYK/Lab stay untouched

write_rgba re-encoded every pixel of the click window through the naive
RGBA round trip, which regenerates K in CMYK and shifts Lab values. Now
untouched pixels keep their original channel data; only pixels whose RGB
changed are re-encoded. Adds a CMYK test checking untouched pixels inside
the window stay bit-exact.

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

* algo::redeye: cargo fmt

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

* Polish catalog rows for the Red Eye tool; regenerate docs/scorecard.md

pl.tsv landed on main and the coverage tests need every catalog to translate new UI strings.

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

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 19:57:14 -04:00

96 lines
3.6 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.
//! 24 MP timing of the red-eye kernel on a window around one click (not a full-image scan).
//! `cargo run --release -p photocraft-algo --example bench_redeye`
use std::time::Instant;
use photocraft_algo::redeye;
fn main() {
let (doc_w, doc_h) = (6016usize, 4000usize);
let click = (doc_w as i32 / 2, doc_h as i32 / 2);
let pupil = 50.0f32;
let radius = redeye::search_radius(pupil);
let halo = redeye::halo_radius();
let ext = radius.ceil() as i32 + halo;
let x0 = click.0.saturating_sub(ext);
let y0 = click.1.saturating_sub(ext);
let area_w = (2 * ext + 1) as usize;
let area_h = area_w;
let window_px = area_w.saturating_mul(area_h);
let doc_px = doc_w.saturating_mul(doc_h);
if window_px == 0 || window_px > (redeye::MAX_WINDOW as usize).saturating_pow(2) {
eprintln!("window {area_w}×{area_h} exceeds the 512² cap");
std::process::exit(1);
}
if window_px >= doc_px {
eprintln!("window {window_px} px is not smaller than the {doc_px} px document (full-image scan)");
std::process::exit(1);
}
// Full 24.1 MP RGBA8 document; the kernel is given only the search window + halo.
let mut doc = vec![0u8; doc_px.saturating_mul(4)];
for y in 0..area_h {
for x in 0..area_w {
let dx = x0 + x as i32 - click.0;
let dy = y0 + y as i32 - click.1;
let d = (dx as f32).hypot(dy as f32);
let gx = (x0 as usize).saturating_add(x);
let gy = (y0 as usize).saturating_add(y);
if gx >= doc_w || gy >= doc_h {
continue;
}
let i = gy.saturating_mul(doc_w).saturating_add(gx).saturating_mul(4);
let rgb = if d <= 1.5 {
[255u8, 255, 255]
} else if d <= 18.0 {
[242, 26, 26]
} else {
[97, 71, 41]
};
if let Some(px) = doc.get_mut(i..i.saturating_add(4)) {
px[0] = rgb[0];
px[1] = rgb[1];
px[2] = rgb[2];
px[3] = 255;
}
}
}
let mut px = vec![[0.0f32; 4]; window_px];
let origin = (x0, y0);
for y in 0..area_h {
for x in 0..area_w {
let gx = (x0 as usize).saturating_add(x);
let gy = (y0 as usize).saturating_add(y);
let slot = match px.get_mut(y.saturating_mul(area_w).saturating_add(x)) {
Some(s) => s,
None => continue,
};
if gx >= doc_w || gy >= doc_h {
*slot = [0.38, 0.28, 0.16, 1.0];
continue;
}
let i = gy.saturating_mul(doc_w).saturating_add(gx).saturating_mul(4);
let r = doc.get(i).copied().unwrap_or(0) as f32 / 255.0;
let g = doc.get(i + 1).copied().unwrap_or(0) as f32 / 255.0;
let b = doc.get(i + 2).copied().unwrap_or(0) as f32 / 255.0;
let a = doc.get(i + 3).copied().unwrap_or(255) as f32 / 255.0;
*slot = [r, g, b, a];
}
}
let mut warm = px.clone();
let _ = redeye::apply(&mut warm, area_w, area_h, click, origin, pupil, 50.0);
let t = Instant::now();
let n = redeye::apply(&mut px, area_w, area_h, click, origin, pupil, 50.0);
let ms = t.elapsed().as_secs_f64() * 1000.0;
println!(
"redeye document {doc_w}×{doc_h} ({:.2} MP) window {area_w}×{area_h} (+{halo} px halo, {window_px} px) pixels {n} {ms:.3} ms",
doc_px as f64 / 1_000_000.0
);
if n == 0 {
eprintln!("expected to correct the synthetic pupil");
std::process::exit(1);
}
}