1use std::path::PathBuf;
11
12use kallo_core::FilmStock;
13use kallo_io::resize_to_width;
14
15use crate::contact::{ContactOptions, ContactOutcome, contact};
16use crate::error::Error;
17use crate::input::read_input;
18use crate::render::{Exposure, RenderOptions, RenderOutcome, render};
19use crate::selection::Selection;
20
21#[derive(Debug, Clone)]
23pub struct RollRequest {
24 pub input: PathBuf,
26 pub input_encoding: kallo_io::InputEncoding,
28 pub frames: Vec<PathBuf>,
30 pub contact: Option<PathBuf>,
32 pub record: Option<PathBuf>,
34 pub times: Vec<f64>,
36 pub size: u32,
38 pub dpi: f64,
40 pub ev: Exposure,
42 pub print_ev: f64,
44 pub frame_mm: f64,
46 pub grain: bool,
48 pub grain_um: Option<f64>,
50 pub grain_seed: u64,
52 pub halation: bool,
54 pub couplers: bool,
56 pub gpu: bool,
58}
59
60impl Default for RollRequest {
61 fn default() -> Self {
62 Self {
63 input: PathBuf::new(),
64 input_encoding: kallo_io::InputEncoding::Srgb,
65 frames: Vec::new(),
66 contact: None,
67 record: None,
68 times: Vec::new(),
69 size: 1200,
70 dpi: 600.0,
71 ev: Exposure::Auto,
72 print_ev: 0.0,
73 frame_mm: 36.0,
74 grain: true,
75 grain_um: None,
76 grain_seed: 0,
77 halation: true,
78 couplers: true,
79 gpu: true,
80 }
81 }
82}
83
84#[derive(Debug, Clone, Copy)]
86pub enum Progress {
87 Frame {
89 index: usize,
91 total: usize,
93 },
94 Contact,
96 Done,
98}
99
100pub struct RollOutcome {
102 pub frames: Vec<RenderOutcome>,
104 pub contact: Option<ContactOutcome>,
106 pub chart: Vec<crate::chart::FrameChart>,
108 pub source: (u32, u32),
110}
111
112pub fn roll(
118 request: &RollRequest,
119 selection: &Selection,
120 film: Option<&'static FilmStock>,
121 progress: &mut dyn FnMut(Progress),
122) -> Result<RollOutcome, Error> {
123 if request.frames.len() != request.times.len() {
124 return Err(Error::Contact {
125 message: format!(
126 "{} development times but {} output frames",
127 request.times.len(),
128 request.frames.len()
129 ),
130 });
131 }
132 if request.size == 0 {
133 return Err(Error::InvalidNumber {
134 flag: "--size",
135 value: f64::from(request.size),
136 expected: "a positive width in pixels",
137 });
138 }
139 if let Some(bad) = request
140 .times
141 .iter()
142 .copied()
143 .find(|time| !(time.is_finite() && *time > 0.0))
144 {
145 return Err(Error::InvalidNumber {
146 flag: "--dev",
147 value: bad,
148 expected: "a positive number of minutes",
149 });
150 }
151
152 let source = read_input(&request.input, request.input_encoding)?;
153 let source_size = (source.width, source.height);
154 let bench = resize_to_width(&source, request.size);
157
158 let options = RenderOptions {
159 ev: request.ev,
160 print_ev: request.print_ev,
161 frame_mm: request.frame_mm,
162 grain: request.grain,
163 grain_um: request.grain_um,
164 grain_seed: request.grain_seed,
165 halation: request.halation,
166 couplers: request.couplers,
167 gpu: request.gpu,
168 ..RenderOptions::default()
169 };
170
171 let total = request.times.len();
172 let mut frames = Vec::with_capacity(total);
173 for (index, output) in request.frames.iter().enumerate() {
174 let mut image = bench.clone();
179 let mut frame_selection = selection.clone();
180 frame_selection.development.time_min = request.times[index];
181 let outcome = render(&mut image, film, &frame_selection, &options, output)?;
182 frames.push(outcome);
183 progress(Progress::Frame {
184 index: index + 1,
185 total,
186 });
187 }
188
189 let proof = if request.contact.is_some() || request.record.is_some() {
190 let sheet = request.contact.clone().unwrap_or_else(|| {
191 let mut path = request.input.clone();
192 path.set_extension("contact.png");
193 path
194 });
195 let options = ContactOptions {
196 ev: request.ev,
197 print_ev: request.print_ev,
198 dpi: request.dpi,
199 grain: request.grain,
200 halation: request.halation,
201 couplers: request.couplers,
202 edge_codes: true,
203 record: request.record.clone(),
204 };
205 let outcome = contact(&source, film, selection, &request.times, &options, &sheet)?;
206 progress(Progress::Contact);
207 Some(outcome)
208 } else {
209 None
210 };
211
212 let chart = crate::chart::chart(
216 film.unwrap_or(selection.film),
217 &request.times,
218 selection.development.temp_c,
219 );
220
221 progress(Progress::Done);
222 Ok(RollOutcome {
223 frames,
224 contact: proof,
225 chart,
226 source: source_size,
227 })
228}