use crate::{
error::Error,
protocol::{
caps::{
Capabilities,
set_window::{AnalogControl, ScanKind, ScanMode},
},
window::{Channel, Flags, Window},
},
scan::meter::Metering,
};
use std::collections::BTreeMap;
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct Exposures(BTreeMap<Channel, u32>);
impl Exposures {
pub fn read(windows: &[Window]) -> Self {
Self(windows.iter().map(|w| (w.channel(), w.exposure)).collect())
}
pub fn get(&self, channel: Channel) -> Option<u32> {
self.0.get(&channel).copied()
}
pub fn set(&mut self, channel: Channel, exposure: u32) {
self.0.insert(channel, exposure);
}
pub fn apply(&self, windows: &mut [Window]) {
for w in windows {
if let Some(&exposure) = self.0.get(&w.channel()) {
w.exposure = exposure;
}
}
}
pub fn iter(&self) -> impl Iterator<Item = (Channel, u32)> + '_ {
self.0.iter().map(|(&c, &e)| (c, e))
}
}
#[derive(Debug, Clone, Copy)]
pub(crate) enum AutoExposure {
Unit(ScanKind),
Host(Metering),
}
impl AutoExposure {
pub(crate) fn choose(caps: &Capabilities, lock_white_balance: bool) -> Result<Self, Error> {
let kinds = caps.set_window.kind;
if lock_white_balance && kinds.contains(ScanKind::AE_WB) {
return Ok(Self::Unit(ScanKind::AE_WB));
}
if !lock_white_balance && kinds.contains(ScanKind::AE) {
return Ok(Self::Unit(ScanKind::AE));
}
let aic = caps.set_window.aic;
if !aic.intersects(AnalogControl::EXPOSURE_VALUE | AnalogControl::EXPOSURE_TIME) {
return Err(Error::Unsupported {
op: "exposure",
reason: format!(
"this unit runs no AE pass and offers no exposure control, only {aic:?}"
),
});
}
Ok(Self::Host(Metering {
lock_white_balance,
..Metering::default()
}))
}
}
pub(crate) fn prescan_windows(caps: &Capabilities, windows: &[Window]) -> Vec<Window> {
let dpi = caps.address.x_axis.dpi_range.start;
let fast = caps.set_window.mode.contains(ScanMode::HIGH_SPEED);
windows
.iter()
.map(|w| {
let mut w = w.clone();
w.resolution = if caps.identity.is_mf_scanner() {
(dpi, dpi / 2)
} else {
(285, 285)
};
w.flags.remove(Flags::AVERAGING);
if fast {
w.scanning_mode = ScanMode::HIGH_SPEED;
}
w.multiple_reading = 0;
w.scanning_mode.remove(ScanMode::MULTI_READING);
w
})
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
use crate::protocol::window::LENGTH;
fn window(id: u8, exposure: u32) -> Window {
let mut w = Window::try_from(&[0u8; LENGTH][..]).expect("a zeroed descriptor");
w.id = id;
w.exposure = exposure;
w
}
#[test]
fn exposures_go_back_where_they_came_from() {
let metered = [window(1, 50842), window(2, 60000), window(3, 71125)];
let exposures = Exposures::read(&metered);
let mut next = [window(1, 0), window(2, 0), window(3, 0)];
exposures.apply(&mut next);
assert_eq!(
next.map(|w| w.exposure),
metered.map(|w: Window| w.exposure)
);
}
#[test]
fn a_channel_nothing_was_measured_for_is_left_alone() {
let exposures = Exposures::read(&[window(1, 50842)]);
let mut windows = [window(1, 0), window(Channel::Infrared.id(), 93004)];
exposures.apply(&mut windows);
assert_eq!(windows[0].exposure, 50842);
assert_eq!(windows[1].exposure, 93004);
}
#[test]
fn an_exposure_can_be_written_by_hand() {
let mut exposures = Exposures::default();
exposures.set(Channel::Green, 60000);
assert_eq!(exposures.get(Channel::Green), Some(60000));
let mut windows = [window(Channel::Green.id(), 0)];
exposures.apply(&mut windows);
assert_eq!(windows[0].exposure, 60000);
}
}