use crate::{
error::Error,
protocol::{caps::Capabilities, decode::Image, window::Window},
scan::profile::Film,
};
#[derive(Debug, Clone, Copy)]
pub struct Metering {
pub target: f32,
pub percentile: f32,
pub lock_white_balance: bool,
pub max_passes: usize,
}
impl Default for Metering {
fn default() -> Self {
Self {
target: 0.97,
percentile: 0.999,
lock_white_balance: false,
max_passes: 3,
}
}
}
impl Metering {
pub fn locks_white_balance(film: Film) -> bool {
match film {
Film::Negative => false,
Film::Positive | Film::Kodachrome | Film::MonochromeNegative => true,
}
}
pub fn apply(
&self,
caps: &Capabilities,
image: &Image,
windows: &[Window],
) -> Result<Vec<u32>, Error> {
let carried = image.colors.len() + usize::from(!image.ir.is_empty());
if carried != windows.len() {
return Err(Error::Unsupported {
op: "metering",
reason: format!(
"the pass carried {carried} channels and there are {} windows",
windows.len()
),
});
}
let limit = &caps.set_window.exposure;
let ceiling = ceiling(image.bits);
let target = (f32::from(ceiling) * self.target.clamp(0.0, 1.0)) as u16;
let steps: Vec<Option<f64>> = self
.measure(image, windows)
.into_iter()
.map(|level| level.and_then(|l| step(l, target, ceiling)))
.collect();
let locked = self.lock_white_balance.then(|| {
steps
.iter()
.zip(windows)
.filter(|(_, w)| w.channel().is_color())
.filter_map(|(s, _)| *s)
.fold(f64::INFINITY, |a, b| a.min(b))
});
Ok(windows
.iter()
.zip(&steps)
.map(|(w, own)| {
let scale = match locked {
Some(f) if w.channel().is_color() && f.is_finite() => Some(f),
_ => *own,
};
match scale {
Some(s) => (f64::from(w.exposure) * s)
.round()
.clamp(f64::from(limit.start), f64::from(limit.last))
as u32,
None => w.exposure,
}
})
.collect())
}
}
impl Metering {
pub fn measured(&self, image: &Image, windows: &[Window]) -> bool {
let ceiling = ceiling(image.bits);
self.measure(image, windows)
.into_iter()
.flatten()
.all(|level| level < ceiling)
}
pub fn measure(&self, image: &Image, windows: &[Window]) -> Vec<Option<u16>> {
let mut color = 0usize;
windows
.iter()
.map(|w| {
let plane = if w.channel().is_color() {
let p = image.colors[color];
color += 1;
p
} else {
image.ir
};
tail(plane, self.percentile)
})
.collect()
}
}
pub(crate) fn ceiling(bits: u8) -> u16 {
match bits {
0 | 16.. => u16::MAX,
b => (1u16 << b) - 1,
}
}
fn step(level: u16, target: u16, ceiling: u16) -> Option<f64> {
match level {
l if l >= ceiling => Some(0.5),
0 => None,
l => Some(f64::from(target) / f64::from(l)),
}
}
fn tail(plane: &[u16], percentile: f32) -> Option<u16> {
let mut counts = vec![0u32; usize::from(u16::MAX) + 1];
let mut total = 0usize;
for &sample in plane {
counts[usize::from(sample)] += 1;
total += 1;
}
if total == 0 {
return None;
}
let at = ((total - 1) as f32 * percentile.clamp(0.0, 1.0)) as usize;
let mut seen = 0usize;
for (value, count) in counts.iter().enumerate() {
seen += *count as usize;
if seen > at {
return Some(value as u16);
}
}
None
}
#[cfg(test)]
mod tests {
use super::*;
use crate::protocol::{
caps::{
Page,
address::Address,
identity::Identity,
other::Features,
set_window::{ColorInterleaving, SetWindowFunction},
},
decode::{Decoder, Samples},
image::Layout,
window::{Channel, Composition, LENGTH},
};
fn caps() -> Capabilities {
let mut p = vec![0u8; 91];
p[1] = Address::PAGE_CODE;
p[3] = 87;
p[18..20].copy_from_slice(&4000u16.to_be_bytes());
p[20..22].copy_from_slice(&4000u16.to_be_bytes());
let address = Address::try_from(&Page::new(Address::PAGE_CODE, p).unwrap()).unwrap();
let mut d = vec![0u8; 28];
d[1] = SetWindowFunction::PAGE_CODE;
d[3] = 24;
d[15] = 9; d[16] = 4;
d[17..21].copy_from_slice(&1u32.to_be_bytes());
d[21..25].copy_from_slice(&0x3FFFFFFu32.to_be_bytes());
let set_window =
SetWindowFunction::try_from(&Page::new(SetWindowFunction::PAGE_CODE, d).unwrap())
.unwrap();
let mut e = vec![0u8; 39];
e[1] = Features::PAGE_CODE;
e[3] = 35;
let features = Features::try_from(&Page::new(Features::PAGE_CODE, e).unwrap()).unwrap();
let mut i = vec![0u8; 36];
i[4] = 31;
Capabilities {
identity: Identity::parse(&i).unwrap(),
address,
features,
set_window,
ccd: None,
frames: None,
}
}
fn image<'a>(layout: &'a Layout, samples: &'a Samples) -> Image<'a> {
Image::new(layout, samples).unwrap()
}
const PIXELS: u32 = 4;
const LINES: u32 = 2;
fn windows(ids: &[u8], exposure: u32) -> Vec<Window> {
let visible = ids
.iter()
.filter(|&&id| Channel::from(id).is_color())
.count();
ids.iter()
.map(|&id| {
let mut w = Window::try_from(&[0u8; LENGTH][..]).unwrap();
w.id = id;
w.exposure = exposure;
w.resolution = (4000, 4000);
w.size = (PIXELS, LINES);
w.bpp = 16;
w.color_interleaving = ColorInterleaving::LINE_WITHOUT_DISTANCE;
w.composition = match visible {
1 => Composition::MultilevelBW,
_ => Composition::MultilevelRGB,
};
w
})
.collect()
}
fn decoded(windows: &[Window], levels: &[u16]) -> (Layout, Samples) {
let mut raw = Vec::new();
for _ in 0..LINES {
for &level in levels {
for _ in 0..PIXELS {
raw.extend_from_slice(&level.to_be_bytes());
}
}
}
let layout = Layout::new(&caps(), windows, 4000, None).unwrap();
let mut decoder = Decoder::new(&layout).unwrap();
let mut samples = Samples::default();
samples.resize_for(&decoder);
decoder.push(&raw, &mut samples).unwrap();
(layout, samples)
}
#[test]
fn each_channel_lands_on_the_target() {
let m = Metering {
target: 1.0,
..Default::default()
};
let w = windows(&[1, 2, 3], 1000);
let (l, s) = decoded(&w, &[21845, 32767, 16383]);
let got = m.apply(&caps(), &image(&l, &s), &w).unwrap();
assert_eq!(got, vec![3000, 2000, 4000]);
}
#[test]
fn locking_scales_the_set_by_its_most_constrained_channel() {
let m = Metering {
target: 1.0,
lock_white_balance: true,
..Default::default()
};
let w = windows(&[1, 2, 3], 1000);
let (l, s) = decoded(&w, &[21845, 32767, 16383]);
let got = m.apply(&caps(), &image(&l, &s), &w).unwrap();
assert_eq!(got, vec![2000, 2000, 2000]);
}
#[test]
fn a_clipped_channel_comes_down_instead_of_scaling() {
let m = Metering {
target: 1.0,
..Default::default()
};
let w = windows(&[1, 2, 3], 1000);
let (l, s) = decoded(&w, &[65535, 32767, 32767]);
let got = m.apply(&caps(), &image(&l, &s), &w).unwrap();
assert_eq!(got, vec![500, 2000, 2000]);
}
#[test]
fn infrared_meters_on_its_own_even_when_locked() {
let m = Metering {
target: 1.0,
lock_white_balance: true,
..Default::default()
};
let w = windows(&[1, 2, 3, Channel::Infrared.id()], 1000);
let (l, s) = decoded(&w, &[21845, 32767, 32767, 16383]);
let got = m.apply(&caps(), &image(&l, &s), &w).unwrap();
assert_eq!(got, vec![2000, 2000, 2000, 4000]);
}
#[test]
fn only_a_clipped_channel_needs_measuring_again() {
let m = Metering {
target: 1.0,
..Default::default()
};
let w = windows(&[1, 2, 3], 1000);
let measured = |levels: &[u16]| {
let (l, s) = decoded(&w, levels);
m.measured(&image(&l, &s), &w)
};
assert!(measured(&[65000, 65000, 65000]));
assert!(!measured(&[65535, 65000, 65000]));
assert!(measured(&[32767, 65000, 65000]));
assert!(measured(&[0, 65000, 65000]));
}
#[test]
fn a_dark_channel_keeps_what_it_had() {
let m = Metering::default();
let w = windows(&[1, 2, 3], 1000);
let (l, s) = decoded(&w, &[0, 32767, 32767]);
let got = m.apply(&caps(), &image(&l, &s), &w).unwrap();
assert_eq!(got[0], 1000);
}
}