use super::{caps::other::DataTypes, sense::Coop};
use crate::error::Error;
use bitflags::bitflags;
pub const HEADER: usize = 6;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Row {
pub code: u8,
pub width: Option<u8>,
pub count: Option<u32>,
pub header: bool,
pub read: Option<DataTypes>,
pub write: Option<DataTypes>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DataType {
Image,
HalftoneMask,
Lut,
Histogram,
MaxValue,
Matrix,
Filter,
Shading,
DarkVoltage,
Magnetic,
Cooperation,
Boundary,
AnalogGamma,
AnalogGain,
DigitalGain,
WhiteBalanceExposure,
Setup,
Perforation,
Boundary2,
ShipmentWhiteBalance,
CcdData,
DriverVersion,
LeakVolume,
RamBuffer,
EepromBuffer,
}
impl DataType {
pub const fn row(self) -> Row {
use DataTypes as D;
const NONE: (Option<u8>, Option<u32>) = (None, None);
let (code, (width, count), header, read, write) = match self {
Self::Image => (0x00, (None, None), false, None, None),
Self::HalftoneMask => (
0x02,
NONE,
true,
Some(D::HALFTONE_READ),
Some(D::HALFTONE_WRITE),
),
Self::Lut => (
0x03,
(Some(2), Some(16384)),
false,
Some(D::GAMMA_READ),
Some(D::GAMMA_WRITE),
),
Self::Histogram => (0x80, NONE, true, Some(D::HISTOGRAM_READ), None),
Self::MaxValue => (
0x81,
(Some(2), Some(1)),
true,
Some(D::MAX_VALUE_READ),
None,
),
Self::Matrix => (
0x82,
NONE,
true,
Some(D::MATRIX_READ),
Some(D::MATRIX_WRITE),
),
Self::Filter => (
0x83,
NONE,
true,
Some(D::FILTER_READ),
Some(D::FILTER_WRITE),
),
Self::Shading => (
0x84,
(Some(2), Some(47352)),
true,
Some(D::SHADING_READ),
Some(D::SHADING_WRITE),
),
Self::DarkVoltage => (
0x85,
NONE,
true,
Some(D::DARK_VOLTAGE_READ),
Some(D::DARK_VOLTAGE_WRITE),
),
Self::Magnetic => (
0x86,
NONE,
true,
Some(D::MAGNETIC_READ),
Some(D::MAGNETIC_WRITE),
),
Self::Cooperation => (0x87, (Some(1), None), true, Some(D::COOP_PARAMS_READ), None),
Self::Boundary => (
0x88,
(Some(4), None),
true,
Some(D::BOUNDARY_READ),
Some(D::BOUNDARY_WRITE),
),
Self::AnalogGamma => (0x89, NONE, true, Some(D::ANALOG_GAMMA_READ), None),
Self::AnalogGain => (
0x8A,
(Some(4), Some(2)),
true,
Some(D::ANALOG_GAIN_READ),
None,
),
Self::DigitalGain => (0x8B, NONE, true, Some(D::DIGITAL_GAIN_READ), None),
Self::WhiteBalanceExposure => {
(0x8C, (Some(4), Some(1)), true, Some(D::EXPOSURE_READ), None)
}
Self::Setup => (
0x8D,
(Some(1), None),
true,
Some(D::SETUP_READ),
Some(D::SETUP_WRITE),
),
Self::Perforation => (0x8E, (None, None), true, Some(D::PERFORATION_READ), None),
Self::Boundary2 => (
0x8F,
(None, None),
true,
Some(D::BOUNDARY2_READ),
Some(D::BOUNDARY2_WRITE),
),
Self::ShipmentWhiteBalance => (0x90, NONE, true, Some(D::INITIAL_WB_READ), None),
Self::CcdData => (0x91, (Some(2), None), true, Some(D::CCD_DATA_READ), None),
Self::DriverVersion => (
0x92,
NONE,
true,
Some(D::DRIVER_VERSION_READ),
Some(D::DRIVER_VERSION_WRITE),
),
Self::LeakVolume => (0x93, (Some(2), Some(3)), true, Some(D::LEAK_READ), None),
Self::RamBuffer => (0xE0, (None, None), true, None, None),
Self::EepromBuffer => (0xE1, (None, None), true, None, None),
};
Row {
code,
width,
count,
header,
read,
write,
}
}
pub const fn per_color(self) -> bool {
matches!(
self,
Self::Lut
| Self::Histogram
| Self::MaxValue
| Self::Shading
| Self::DarkVoltage
| Self::WhiteBalanceExposure
| Self::Setup
)
}
pub const fn scalar(self) -> Scalar {
match self {
Self::AnalogGain => Scalar::F32,
Self::Boundary | Self::WhiteBalanceExposure => Scalar::U32,
_ => match self.row().width {
Some(1) => Scalar::U8,
Some(4) => Scalar::U32,
_ => Scalar::U16,
},
}
}
pub fn qualifier(self) -> Option<(u8, u8)> {
match self {
Self::Perforation => Some((0, 0x00)),
Self::Boundary2 => Some((0, 0x03)),
_ => self.row().width.map(|width| {
(
width,
width_code(width).expect("2-11-2 widths are all encodable"),
)
}),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Scalar {
U8,
U16,
U32,
F32,
}
#[derive(Debug, Clone, PartialEq)]
pub enum Values {
Bytes(Vec<u8>),
Words(Vec<u16>),
Longs(Vec<u32>),
Floats(Vec<f32>),
}
impl Values {
pub fn decode(scalar: Scalar, bytes: &[u8]) -> Self {
fn each<const N: usize, T>(bytes: &[u8], f: impl Fn([u8; N]) -> T) -> Vec<T> {
bytes
.chunks_exact(N)
.map(|c| f(c.try_into().expect("chunks_exact")))
.collect()
}
match scalar {
Scalar::U8 => Self::Bytes(bytes.to_vec()),
Scalar::U16 => Self::Words(each(bytes, u16::from_be_bytes)),
Scalar::U32 => Self::Longs(each(bytes, u32::from_be_bytes)),
Scalar::F32 => Self::Floats(each(bytes, f32::from_be_bytes)),
}
}
}
pub const fn width_code(width: u8) -> Option<u8> {
Some(match width {
1 => 0x00,
2 => 0x01,
4 => 0x03,
_ => return None,
})
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum FrameTable {
Boundary(Boundary),
BoundaryType2(BoundaryType2),
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct Rect {
pub top: u32,
pub left: u32,
pub bottom: u32,
pub right: u32,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct FramePosition {
pub top: u32,
pub perf_number: u16,
pub perf_decimal: u8,
pub pulse_number: u8,
}
impl FramePosition {
pub fn rect(self, x_start: u32, x_boundary: u32, length: u32) -> Rect {
Rect {
top: self.top,
left: x_start,
bottom: self.top + length - 1,
right: x_start + x_boundary - 1,
}
}
pub fn new(top: u32, perf: &PerforationInformation) -> Self {
FramePosition {
top,
perf_number: perf.perf_number,
perf_decimal: perf.perf_decimal,
pulse_number: perf.pulse_number,
}
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct Boundary {
pub frames: Vec<Rect>,
}
impl Boundary {
const HEAD: usize = 4;
const RECT: usize = 16;
pub fn at(&self, x: u32, y: u32) -> Option<Rect> {
self.frames
.iter()
.copied()
.find(|f| (f.left..f.right).contains(&x) && (f.top..f.bottom).contains(&y))
}
pub fn holding(&self, r: Rect) -> Option<Rect> {
self.frames.iter().copied().find(|f| {
f.left <= r.left && r.right <= f.right && f.top <= r.top && r.bottom <= f.bottom
})
}
pub fn from_bytes(b: &[u8]) -> Option<Self> {
let head: &[u8; Self::HEAD] = b.get(..Self::HEAD)?.try_into().ok()?;
let count = usize::from(head[2]);
let be32 = |s: &[u8], i: usize| u32::from_be_bytes([s[i], s[i + 1], s[i + 2], s[i + 3]]);
let mut frames = Vec::with_capacity(count);
for n in 0..count {
let at = Self::HEAD + n * Self::RECT;
let r = b.get(at..at + Self::RECT)?;
frames.push(Rect {
top: be32(r, 0),
left: be32(r, 4),
bottom: be32(r, 8),
right: be32(r, 12),
});
}
Some(Self { frames })
}
pub fn to_bytes(&self) -> Result<Vec<u8>, Error> {
if self.frames.is_empty() {
return Err(Error::Unsupported {
op: "frame table",
reason: "no frames were measured, so there is no table to send".into(),
});
}
if self.frames.len() > u8::MAX as usize {
return Err(Error::Unsupported {
op: "boundary",
reason: format!(
"{} frames cannot fit the one-byte count field",
self.frames.len()
),
});
}
let mut out = Vec::with_capacity(Self::HEAD + self.frames.len() * Self::RECT);
let length = (Self::HEAD + self.frames.len() * Self::RECT) as u16;
out.extend_from_slice(&length.to_be_bytes());
out.push(self.frames.len() as u8);
out.push(0);
for r in &self.frames {
for v in [r.top, r.left, r.bottom, r.right] {
out.extend_from_slice(&v.to_be_bytes());
}
}
Ok(out)
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct PerforationInformation {
pub perf_number: u16,
pub count_switching_flag: bool,
pub perf_decimal: u8,
pub pulse_number: u8,
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct PerfInformation {
pub perfs: Vec<PerforationInformation>,
}
impl PerfInformation {
const HEAD: usize = 4;
const PERFS: usize = 4;
pub fn from_bytes(b: &[u8]) -> Option<Self> {
let head: &[u8; Self::HEAD] = b.get(..Self::HEAD)?.try_into().ok()?;
let parameter_length =
((head[0] as usize) << 16) | ((head[1] as usize) << 8) | (head[2] as usize);
let bytes_per_parameter = usize::from(head[3]);
if bytes_per_parameter != Self::PERFS {
return None;
}
let total = parameter_length.checked_add(3)?;
if total < Self::HEAD || b.len() < total {
return None;
}
let payload_len = total - Self::HEAD;
if payload_len % Self::PERFS != 0 {
return None;
}
let num_records = payload_len / Self::PERFS;
let mut perfs = Vec::with_capacity(num_records);
for n in 0..num_records {
let at = Self::HEAD + n * Self::PERFS;
let r = b.get(at..at + Self::PERFS)?;
perfs.push(PerforationInformation {
perf_number: u16::from_be_bytes([r[0], r[1]]),
count_switching_flag: r[2] & 0x80 != 0,
perf_decimal: r[2] & 0x7f,
pulse_number: r[3],
});
}
Some(Self { perfs })
}
pub fn at(&self, line: usize) -> Option<&PerforationInformation> {
self.perfs.get(line)
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct BoundaryType2 {
pub frames: Vec<FramePosition>,
}
impl BoundaryType2 {
const HEAD: usize = 4;
const BOUNDARY: usize = 8;
pub fn from_bytes(b: &[u8]) -> Option<Self> {
let head: &[u8; Self::HEAD] = b.get(..Self::HEAD)?.try_into().ok()?;
let parameter_length = u16::from_be_bytes([head[0], head[1]]) as usize;
let count = usize::from(head[2]);
let total = parameter_length.checked_add(1)?;
let expected = Self::HEAD.checked_add(count.checked_mul(Self::BOUNDARY)?)?;
if total != expected || b.len() < total {
return None;
}
let mut frames = Vec::with_capacity(count);
for n in 0..count {
let at = Self::HEAD + n * Self::BOUNDARY;
let r = b.get(at..at + Self::BOUNDARY)?;
frames.push(FramePosition {
top: u32::from_be_bytes([r[0], r[1], r[2], r[3]]),
perf_number: u16::from_be_bytes([r[4], r[5]]),
perf_decimal: r[6],
pulse_number: r[7],
});
}
Some(Self { frames })
}
pub fn to_bytes(&self) -> Result<Vec<u8>, Error> {
if self.frames.is_empty() {
return Err(Error::Unsupported {
op: "frame table",
reason: "no frames were measured, so there is no table to send".into(),
});
}
if self.frames.len() > u8::MAX as usize {
return Err(Error::Unsupported {
op: "boundary_type2",
reason: format!(
"{} frames cannot fit the one-byte count field",
self.frames.len()
),
});
}
let total = Self::HEAD
.checked_add(self.frames.len() * Self::BOUNDARY)
.ok_or_else(|| Error::Unsupported {
op: "boundary_type2",
reason: "boundary information is too large".into(),
})?;
let parameter_length = total - 2;
if parameter_length > u16::MAX as usize {
return Err(Error::Unsupported {
op: "boundary_type2",
reason: "boundary information is too large".into(),
});
}
let mut out = Vec::with_capacity(total);
out.extend_from_slice(&(parameter_length as u16).to_be_bytes());
out.push(self.frames.len() as u8);
out.push(0);
for frame in &self.frames {
out.extend_from_slice(&frame.top.to_be_bytes());
out.extend_from_slice(&frame.perf_number.to_be_bytes());
out.push(frame.perf_decimal);
out.push(frame.pulse_number);
}
Ok(out)
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct SetupImage {
pub index: u8,
pub exposure: u32,
pub white_balance: u32,
pub min: u16,
pub max: u16,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Setup {
pub format: u8,
pub base_level: u16,
pub base_exposure: u32,
pub base_white_balance: u32,
pub images: Vec<SetupImage>,
}
impl Setup {
const HEAD: usize = 14;
const IMAGE: usize = 13;
pub fn from_bytes(b: &[u8]) -> Option<Self> {
let head: &[u8; Self::HEAD] = b.get(..Self::HEAD)?.try_into().ok()?;
let be16 = |s: &[u8], i: usize| u16::from_be_bytes([s[i], s[i + 1]]);
let be32 = |s: &[u8], i: usize| u32::from_be_bytes([s[i], s[i + 1], s[i + 2], s[i + 3]]);
let count = usize::from(head[13]);
let mut images = Vec::with_capacity(count);
for n in 0..count {
let at = Self::HEAD + n * Self::IMAGE;
let e = b.get(at..at + Self::IMAGE)?;
images.push(SetupImage {
index: e[0],
exposure: be32(e, 1),
white_balance: be32(e, 5),
min: be16(e, 9),
max: be16(e, 11),
});
}
Some(Self {
format: head[2],
base_level: be16(head, 3),
base_exposure: be32(head, 5),
base_white_balance: be32(head, 9),
images,
})
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Op {
Initialize,
ReturnToOrigin,
AutoAf,
AutoCalibration,
AutoFocus,
ColorAutoFocus,
SetupShading,
FocusMove,
Unload,
Load,
Other(u8),
}
impl Op {
pub const fn code(self) -> u8 {
match self {
Self::Initialize => 0x80,
Self::ReturnToOrigin => 0x81,
Self::AutoAf => 0x91,
Self::AutoCalibration => 0x92,
Self::AutoFocus => 0xA0,
Self::ColorAutoFocus => 0xA1,
Self::SetupShading => 0xB0,
Self::FocusMove => 0xC1,
Self::Unload => 0xD0,
Self::Load => 0xD1,
Self::Other(code) => code,
}
}
}
impl From<u8> for Op {
fn from(code: u8) -> Self {
match code {
0x80 => Self::Initialize,
0x81 => Self::ReturnToOrigin,
0x91 => Self::AutoAf,
0x92 => Self::AutoCalibration,
0xA0 => Self::AutoFocus,
0xA1 => Self::ColorAutoFocus,
0xB0 => Self::SetupShading,
0xC1 => Self::FocusMove,
0xD0 => Self::Unload,
0xD1 => Self::Load,
x => Self::Other(x),
}
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct Operation {
pub color: u8,
pub first: u32,
pub second: u32,
}
impl Operation {
pub const LENGTH: usize = 9;
pub fn from_bytes(b: &[u8]) -> Option<Self> {
let b: &[u8; Self::LENGTH] = b.get(..Self::LENGTH)?.try_into().ok()?;
let be32 = |i: usize| u32::from_be_bytes([b[i], b[i + 1], b[i + 2], b[i + 3]]);
Some(Self {
color: b[0],
first: be32(1),
second: be32(5),
})
}
pub fn to_bytes(&self) -> [u8; 9] {
let mut b = [0u8; 9];
b[0] = self.color;
b[1..5].copy_from_slice(&self.first.to_be_bytes());
b[5..9].copy_from_slice(&self.second.to_be_bytes());
b
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct Geometry {
pub bytes_per_line: u16,
pub entire_lines: u16,
pub bits_per_color: u8,
pub lines_per_image: u16,
pub readings_per_line: u8,
pub registration_gap: u16,
}
impl Geometry {
const LENGTH: usize = 15;
fn from_bytes(b: &[u8]) -> Option<Self> {
let b: &[u8; Self::LENGTH] = b.get(..Self::LENGTH)?.try_into().ok()?;
let be16 = |i: usize| u16::from_be_bytes([b[i], b[i + 1]]);
Some(Self {
bytes_per_line: be16(5),
entire_lines: be16(7),
bits_per_color: b[9],
lines_per_image: be16(10),
readings_per_line: b[12],
registration_gap: be16(13),
})
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct Truncation {
pub position: Position,
pub per_color: Edges,
pub all_colors: Edges,
pub lines: Edges,
pub frame: Edges,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct Edges {
pub first: u16,
pub last: u16,
}
bitflags! {
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct Position: u16 {
const COLOR_FIRST = 1 << 0;
const COLOR_LAST = 1 << 1;
const ALL_FIRST = 1 << 2;
const ALL_LAST = 1 << 3;
const LINE_FIRST = 1 << 6;
const LINE_LAST = 1 << 7;
const FRAME_FIRST = 1 << 8;
const FRAME_LAST = 1 << 9;
}
}
impl Truncation {
const LENGTH: usize = 27;
fn from_bytes(b: &[u8]) -> Option<Self> {
let b: &[u8; Self::LENGTH] = b.get(..Self::LENGTH)?.try_into().ok()?;
let be16 = |i: usize| u16::from_be_bytes([b[i], b[i + 1]]);
let edges = |i: usize| Edges {
first: be16(i),
last: be16(i + 2),
};
Some(Self {
position: Position::from_bits_truncate(u16::from(b[5]) | u16::from(b[6]) << 8),
per_color: edges(7),
all_colors: edges(11),
lines: edges(19),
frame: edges(23),
})
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum CooperativeAction {
Geometry(Coop, Geometry),
Truncate(Truncation),
CcdData([u8; 8]),
Unknown(u8, Vec<u8>),
}
impl CooperativeAction {
pub const LENGTH: usize = 18;
pub fn kind(&self) -> Coop {
match self {
Self::Geometry(kind, _) => *kind,
Self::Truncate(_) => Coop::Truncate,
Self::CcdData(_) => Coop::CcdData,
Self::Unknown(code, _) => Coop::from(*code),
}
}
pub fn any_multiline_registration(cooperations: &[Self]) -> bool {
cooperations
.iter()
.any(|c| matches!(c, Self::Geometry(Coop::MultiLineRegistration, _)))
}
pub fn from_bytes(b: &[u8]) -> Option<Self> {
let unknown = || Some(Self::Unknown(b[0], b.to_vec()));
let kind = Coop::from(*b.first()?);
match kind {
Coop::Thumbnail | Coop::Averaging | Coop::MultiLineRegistration => {
match Geometry::from_bytes(b) {
Some(g) => Some(Self::Geometry(kind, g)),
None => unknown(),
}
}
Coop::Truncate => match Truncation::from_bytes(b) {
Some(t) => Some(Self::Truncate(t)),
None => unknown(),
},
Coop::CcdData => match b.get(5..13).and_then(|s| s.try_into().ok()) {
Some(types) => Some(Self::CcdData(types)),
None => unknown(),
},
Coop::Unknown(_) => unknown(),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Header {
pub code: u8,
pub bits: u8,
pub length: u32,
}
impl Header {
pub fn from_bytes(b: &[u8]) -> Option<(Self, &[u8])> {
let head = b.get(..HEADER)?;
Some((
Self {
code: head[0],
bits: head[1],
length: u32::from_be_bytes([head[2], head[3], head[4], head[5]]),
},
&b[HEADER..],
))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_perforation_table_is_read_in_thumbnail_line_order() {
let mut b = vec![0x00, 0x00, 0x0d, 0x04];
for line in 0..3u8 {
b.extend_from_slice(&[0x00, line, 0x80 | line, line * 7]);
}
let perfs = PerfInformation::from_bytes(&b).expect("perforations");
assert_eq!(perfs.perfs.len(), 3);
let second = perfs.at(1).expect("line 1");
assert_eq!(second.perf_number, 1);
assert_eq!(second.perf_decimal, 1);
assert_eq!(second.pulse_number, 7);
assert!(second.count_switching_flag);
assert_eq!(perfs.at(3), None);
let frame = FramePosition::new(4321, second);
assert_eq!(frame.top, 4321);
assert_eq!(
(frame.perf_number, frame.perf_decimal, frame.pulse_number),
(1, 1, 7)
);
}
#[test]
fn the_multi_line_record_reads_as_geometry() {
let mut b = [0u8; CooperativeAction::LENGTH];
b[0] = 0x04;
b[1..5].copy_from_slice(&[0x09, 0x80, 0x04, 0x01]);
b[5..7].copy_from_slice(&60000u16.to_be_bytes());
b[7..9].copy_from_slice(&13860u16.to_be_bytes());
b[9] = 16;
b[13..15].copy_from_slice(&12u16.to_be_bytes());
let CooperativeAction::Geometry(kind, g) = CooperativeAction::from_bytes(&b).unwrap()
else {
panic!("not a geometry record");
};
assert_eq!(kind, Coop::MultiLineRegistration);
assert_eq!(g.bytes_per_line, 60000);
assert_eq!(g.entire_lines, 13860);
assert_eq!(g.bits_per_color, 16);
assert_eq!(g.registration_gap, 12);
assert_eq!((g.lines_per_image, g.readings_per_line), (0, 0));
}
#[test]
fn the_averaging_record_carries_only_the_reading_count() {
let mut b = [0u8; CooperativeAction::LENGTH];
b[0] = 0x02;
b[12] = 16;
let CooperativeAction::Geometry(kind, g) = CooperativeAction::from_bytes(&b).unwrap()
else {
panic!("not a geometry record");
};
assert_eq!(kind, Coop::Averaging);
assert_eq!(g.readings_per_line, 16);
}
#[test]
fn only_multiline_registration_says_the_pass_left_seams() {
let geometry = |ascq| {
let mut b = [0u8; CooperativeAction::LENGTH];
b[0] = ascq;
CooperativeAction::from_bytes(&b).unwrap()
};
assert!(!CooperativeAction::any_multiline_registration(&[]));
assert!(!CooperativeAction::any_multiline_registration(&[
geometry(0x01), geometry(0x02), ]));
assert!(CooperativeAction::any_multiline_registration(&[
geometry(0x01),
geometry(0x04), ]));
}
#[test]
fn the_ccd_record_is_per_color_types_not_geometry() {
let mut b = [0u8; CooperativeAction::LENGTH];
b[0] = 0x07;
b[5..13].copy_from_slice(&[1, 2, 3, 0, 0, 0, 0, 0]);
assert_eq!(
CooperativeAction::from_bytes(&b).unwrap(),
CooperativeAction::CcdData([1, 2, 3, 0, 0, 0, 0, 0])
);
}
#[test]
fn the_truncation_record_is_longer_than_the_others() {
let mut b = [0u8; 27];
b[0] = 0x06;
b[5] = 0b0000_0011; b[7..9].copy_from_slice(&8u16.to_be_bytes());
b[9..11].copy_from_slice(&4u16.to_be_bytes());
b[19..21].copy_from_slice(&2u16.to_be_bytes());
b[25..27].copy_from_slice(&6u16.to_be_bytes());
let CooperativeAction::Truncate(t) = CooperativeAction::from_bytes(&b).unwrap() else {
panic!("not a truncation record");
};
assert_eq!(t.position, Position::COLOR_FIRST | Position::COLOR_LAST);
assert_eq!(t.per_color, Edges { first: 8, last: 4 });
assert_eq!(t.lines, Edges { first: 2, last: 0 });
assert_eq!(t.frame, Edges { first: 0, last: 6 });
assert!(matches!(
CooperativeAction::from_bytes(&b[..CooperativeAction::LENGTH]),
Some(CooperativeAction::Unknown(0x06, _))
));
}
#[test]
fn setup_information_decodes_a_retained_image() {
let b = [
0x00, 0x18, 0x00, 0x71, 0xF9, 0x00, 0x04, 0xFC, 0x62, 0x00, 0x04, 0xFC, 0x62, 0x01,
0x01, 0x00, 0x04, 0xF0, 0x00, 0x00, 0x04, 0xF0, 0x00, 0x12, 0x34, 0x56, 0x78,
];
let setup = Setup::from_bytes(&b).unwrap();
assert_eq!(setup.format, 0);
assert_eq!(setup.base_level, 29177);
assert_eq!(setup.base_exposure, 326754);
assert_eq!(setup.base_white_balance, 326754);
assert_eq!(
setup.images,
vec![SetupImage {
index: 1,
exposure: 0x0004F000,
white_balance: 0x0004F000,
min: 0x1234,
max: 0x5678,
}]
);
}
#[test]
fn a_record_shorter_than_its_image_count_is_refused() {
let mut b = vec![0u8; 27];
b[13] = 2;
assert!(Setup::from_bytes(&b).is_none());
}
#[test]
fn boundary_information_round_trips() {
let b = Boundary {
frames: vec![Rect {
top: 0,
left: 0,
bottom: 13859,
right: 9999,
}],
};
let bytes = b.to_bytes().unwrap();
assert_eq!(bytes.len(), 20);
assert_eq!(&bytes[..4], &[0x00, 0x14, 0x01, 0x00]);
assert_eq!(Boundary::from_bytes(&bytes), Some(b));
}
#[test]
fn an_unknown_job_keeps_its_bytes() {
let b = [0x0Au8; CooperativeAction::LENGTH];
let action = CooperativeAction::from_bytes(&b).unwrap();
assert_eq!(action.kind(), Coop::Unknown(0x0A));
assert!(matches!(action, CooperativeAction::Unknown(0x0A, _)));
}
}