use std::path::PathBuf;
use kallo_core::FilmStock;
use kallo_io::resize_to_width;
use crate::contact::{ContactOptions, ContactOutcome, contact};
use crate::error::Error;
use crate::input::read_input;
use crate::render::{Exposure, RenderOptions, RenderOutcome, render};
use crate::selection::Selection;
#[derive(Debug, Clone)]
pub struct RollRequest {
pub input: PathBuf,
pub input_encoding: kallo_io::InputEncoding,
pub frames: Vec<PathBuf>,
pub contact: Option<PathBuf>,
pub record: Option<PathBuf>,
pub times: Vec<f64>,
pub size: u32,
pub dpi: f64,
pub ev: Exposure,
pub print_ev: f64,
pub frame_mm: f64,
pub grain: bool,
pub grain_um: Option<f64>,
pub grain_seed: u64,
pub halation: bool,
pub couplers: bool,
pub gpu: bool,
}
impl Default for RollRequest {
fn default() -> Self {
Self {
input: PathBuf::new(),
input_encoding: kallo_io::InputEncoding::Srgb,
frames: Vec::new(),
contact: None,
record: None,
times: Vec::new(),
size: 1200,
dpi: 600.0,
ev: Exposure::Auto,
print_ev: 0.0,
frame_mm: 36.0,
grain: true,
grain_um: None,
grain_seed: 0,
halation: true,
couplers: true,
gpu: true,
}
}
}
#[derive(Debug, Clone, Copy)]
pub enum Progress {
Frame {
index: usize,
total: usize,
},
Contact,
Done,
}
pub struct RollOutcome {
pub frames: Vec<RenderOutcome>,
pub contact: Option<ContactOutcome>,
pub chart: Vec<crate::chart::FrameChart>,
pub source: (u32, u32),
}
pub fn roll(
request: &RollRequest,
selection: &Selection,
film: Option<&'static FilmStock>,
progress: &mut dyn FnMut(Progress),
) -> Result<RollOutcome, Error> {
if request.frames.len() != request.times.len() {
return Err(Error::Contact {
message: format!(
"{} development times but {} output frames",
request.times.len(),
request.frames.len()
),
});
}
if request.size == 0 {
return Err(Error::InvalidNumber {
flag: "--size",
value: f64::from(request.size),
expected: "a positive width in pixels",
});
}
if let Some(bad) = request
.times
.iter()
.copied()
.find(|time| !(time.is_finite() && *time > 0.0))
{
return Err(Error::InvalidNumber {
flag: "--dev",
value: bad,
expected: "a positive number of minutes",
});
}
let source = read_input(&request.input, request.input_encoding)?;
let source_size = (source.width, source.height);
let bench = resize_to_width(&source, request.size);
let options = RenderOptions {
ev: request.ev,
print_ev: request.print_ev,
frame_mm: request.frame_mm,
grain: request.grain,
grain_um: request.grain_um,
grain_seed: request.grain_seed,
halation: request.halation,
couplers: request.couplers,
gpu: request.gpu,
..RenderOptions::default()
};
let total = request.times.len();
let mut frames = Vec::with_capacity(total);
for (index, output) in request.frames.iter().enumerate() {
let mut image = bench.clone();
let mut frame_selection = selection.clone();
frame_selection.development.time_min = request.times[index];
let outcome = render(&mut image, film, &frame_selection, &options, output)?;
frames.push(outcome);
progress(Progress::Frame {
index: index + 1,
total,
});
}
let proof = if request.contact.is_some() || request.record.is_some() {
let sheet = request.contact.clone().unwrap_or_else(|| {
let mut path = request.input.clone();
path.set_extension("contact.png");
path
});
let options = ContactOptions {
ev: request.ev,
print_ev: request.print_ev,
dpi: request.dpi,
grain: request.grain,
halation: request.halation,
couplers: request.couplers,
edge_codes: true,
record: request.record.clone(),
};
let outcome = contact(&source, film, selection, &request.times, &options, &sheet)?;
progress(Progress::Contact);
Some(outcome)
} else {
None
};
let chart = crate::chart::chart(
film.unwrap_or(selection.film),
&request.times,
selection.development.temp_c,
);
progress(Progress::Done);
Ok(RollOutcome {
frames,
contact: proof,
chart,
source: source_size,
})
}