use std::io::{Read, Write};
use axon::idpe::RasterTile;
use axon::idpe_frontend::{perona_malik, PeronaMalik};
const MAX_INPUT_BYTES: usize = 64 * 1024 * 1024;
const MAX_SIDE: u32 = 20_000;
const MAX_MEGAPIXELS: u32 = 256;
fn fail(reason: &str) -> ! {
let _ = writeln!(std::io::stderr(), "idpe-sidecar refused: {reason}");
std::process::exit(2);
}
fn main() {
let mut buf = Vec::new();
let mut handle = std::io::stdin().lock().take((MAX_INPUT_BYTES as u64) + 1);
if handle.read_to_end(&mut buf).is_err() {
fail("could not read stdin");
}
if buf.len() > MAX_INPUT_BYTES {
fail(&format!("input exceeds {MAX_INPUT_BYTES} bytes — refused before decode"));
}
let tile = if buf.starts_with(b"P5") || buf.starts_with(b"P4") {
match RasterTile::from_netpbm(&buf, &axon::extraction::ExtractionBounds::default()) {
Ok(t) => t,
Err(e) => fail(&format!("netpbm decode failed: {e}")),
}
} else {
decode_hostile(&buf)
};
let cleaned = perona_malik(&tile, &PeronaMalik::default());
let mut out = std::io::stdout().lock();
let header = format!("P5\n{} {}\n255\n", cleaned.width, cleaned.height);
if out.write_all(header.as_bytes()).and_then(|_| out.write_all(&cleaned.gray)).is_err() {
fail("could not write stdout");
}
let _ = out.flush();
}
fn decode_hostile(bytes: &[u8]) -> RasterTile {
use image::ImageReader;
let mut limits = image::Limits::default();
limits.max_image_width = Some(MAX_SIDE);
limits.max_image_height = Some(MAX_SIDE);
let cursor = std::io::Cursor::new(bytes);
let mut reader = match ImageReader::new(cursor).with_guessed_format() {
Ok(r) => r,
Err(e) => fail(&format!("format detection failed: {e}")),
};
reader.limits(limits);
let (w, h) = match reader.into_dimensions() {
Ok(d) => d,
Err(e) => fail(&format!("dimension read failed: {e}")),
};
let mp = (w as u64 * h as u64) / 1_000_000;
if mp as u32 > MAX_MEGAPIXELS {
fail(&format!("{mp} megapixels exceeds the {MAX_MEGAPIXELS} cap — refused before full decode"));
}
let mut limits2 = image::Limits::default();
limits2.max_image_width = Some(MAX_SIDE);
limits2.max_image_height = Some(MAX_SIDE);
let cursor = std::io::Cursor::new(bytes);
let mut reader = match ImageReader::new(cursor).with_guessed_format() {
Ok(r) => r,
Err(e) => fail(&format!("format detection failed: {e}")),
};
reader.limits(limits2);
let img = match reader.decode() {
Ok(i) => i,
Err(e) => fail(&format!("decode failed: {e}")),
};
let gray = img.to_luma8();
RasterTile {
width: gray.width() as usize,
height: gray.height() as usize,
gray: gray.into_raw(),
}
}