use super::{
boundaries::{self, Polarity},
framing,
pass::Pass,
window,
};
use crate::{
error::Error,
protocol::{
caps::{
Capabilities,
set_window::{ColorInterleaving, ScanKind, ScanMode},
},
data::{Boundary, BoundaryType2, FramePosition, PerfInformation, Rect},
decode::{Image, Samples},
model::Model,
window::{Flags, Window},
},
};
use tracing::*;
const MARGIN: u32 = 50;
pub fn available(caps: &Capabilities) -> bool {
caps.set_window.kind.contains(ScanKind::THUMBNAIL)
&& caps.address.thumbnail_resolution.start > 0
}
pub fn frames(
caps: &Capabilities,
pass: &Pass,
samples: &Samples,
length: u32,
polarity: Polarity,
) -> Result<Boundary, Error> {
let format = window::reachable_blocks(caps, length);
framing::reachable(caps, format)?;
let image = Image::new(&pass.layout, samples)?;
let pitch = pass.layout.line_pitch.max(1);
let origin = caps.address.y_axis.address_range.start;
let end = caps.address.y_axis.address_range.last;
let (left, width) = opening(caps);
let found = boundaries::detect(&image, (format / pitch) as usize, polarity);
let format = window::reachable_blocks(caps, found.length as u32 * pitch);
framing::reachable(caps, format)?;
let bleed = margin(format, found.pitch as u32 * pitch);
let length = window::reachable_blocks(caps, format + 2 * window::whole_blocks(caps, bleed));
framing::reachable(caps, length)?;
let margin = (length - format) / 2;
let frames: Vec<Rect> = found
.frames
.iter()
.map(|&col| {
(origin + col as u32 * pitch)
.saturating_sub(margin)
.max(origin)
})
.filter(|top| top + length <= end)
.map(|top| Rect {
top,
left,
bottom: top + length,
right: left + width,
})
.collect();
info!(
frames = frames.len(),
?polarity,
pitch = found.pitch as u32 * pitch,
"measured the loaded strip"
);
for (n, rect) in frames.iter().enumerate() {
debug!(frame = n + 1, ?rect, "frame rect");
}
Ok(Boundary { frames })
}
pub fn frames_type2(
caps: &Capabilities,
pass: &Pass,
samples: &Samples,
perf_info: &PerfInformation,
length: u32,
polarity: Polarity,
) -> Result<(BoundaryType2, u32), Error> {
let length = window::reachable_blocks(caps, length);
framing::reachable(caps, length)?;
let image = Image::new(&pass.layout, samples)?;
let pitch = pass.layout.line_pitch.max(1);
let origin = caps.address.y_axis.address_range.start;
let end = caps.address.y_axis.address_range.last;
let found = boundaries::detect(&image, (length / pitch) as usize, polarity);
let length = window::reachable_blocks(caps, found.length as u32 * pitch);
framing::reachable(caps, length)?;
if perf_info.perfs.len() != image.cols {
debug!(
perfs = perf_info.perfs.len(),
columns = image.cols,
"the perforation table does not run the length of the thumbnail pass"
);
}
let frames: Vec<FramePosition> = found
.frames
.iter()
.filter_map(|&col| {
let top = origin + col as u32 * pitch;
let perf = perf_info.at(col);
debug!(col, top, ?perf, "detected column");
if top + length > end {
return None;
}
match perf {
Some(perf) => Some(FramePosition::new(top, perf)),
None => {
warn!(col, top, "no perforation reading for this frame, dropped");
None
}
}
})
.collect();
info!(
frames = frames.len(),
?polarity,
pitch = found.pitch as u32 * pitch,
"measured the loaded strip"
);
for (n, frame) in frames.iter().enumerate() {
debug!(frame = n + 1, ?frame, "frame position");
}
Ok((BoundaryType2 { frames }, length))
}
fn margin(format: u32, wound: u32) -> u32 {
let most = format / MARGIN;
match wound {
0 => most,
wound => most.min(wound.saturating_sub(format) / 2),
}
}
fn opening(caps: &Capabilities) -> (u32, u32) {
let x = &caps.address.x_axis;
match caps.frames.as_ref().and_then(|f| f.images.first()) {
Some(opening) => (opening.left, opening.width),
None => (x.address_range.start, x.boundary),
}
}
pub(crate) fn windows(caps: &Capabilities) -> Result<Vec<Window>, Error> {
let y = &caps.address.y_axis;
let unsupported = |reason: String| Error::Unsupported {
op: "thumbnail window",
reason,
};
let offered = caps.set_window.interleaving;
if !offered.contains(ColorInterleaving::LINE_WITHOUT_DISTANCE) {
return Err(unsupported(format!(
"a thumbnail needs line ordering and this unit offers {offered:?}"
)));
}
let flags = match caps.identity.model() {
Some(Model::Ls8000 | Model::Ls9000) => Flags::empty(),
Some(_) => Flags::POSITIVE | Flags::AVERAGING,
None => {
return Err(Error::Unsupported {
op: "thumbnail window",
reason: "unrecognized model".into(),
});
}
};
let (left, width) = opening(caps);
let mut windows = window::blank(caps, &window::color_channels(caps))?;
for w in &mut windows {
w.resolution = (
caps.address.thumbnail_resolution.start,
caps.address.thumbnail_resolution.start,
);
w.origin = (left, y.address_range.start);
w.size = (width, y.address_range.last);
w.scanning_kind = ScanKind::THUMBNAIL;
w.scanning_mode = ScanMode::NORMAL_QUALITY;
w.flags = flags;
w.color_interleaving = ColorInterleaving::LINE_WITHOUT_DISTANCE;
}
Ok(windows)
}