use crate::{
error::Error,
protocol::{
caps::set_window::ColorInterleaving, curves::Curves, image::Layout, window::Channel,
},
};
fn bad(reason: String) -> Error {
Error::Unsupported {
op: "decode",
reason,
}
}
#[derive(Debug, Clone, Default)]
pub struct Samples {
pub colors: Vec<Vec<u16>>,
pub ir: Option<Vec<u16>>,
}
impl Samples {
pub(crate) fn resize_for(&mut self, decoder: &Decoder) {
let (rows, cols) = decoder.shape();
let len = rows * cols;
self.colors.resize_with(decoder.colors(), Vec::new);
for plane in &mut self.colors {
plane.clear();
plane.resize(len, 0);
}
match decoder.ir_samples() {
0 => self.ir = None,
n => {
let ir = self.ir.get_or_insert_with(Vec::new);
ir.clear();
ir.resize(n, 0);
}
}
}
pub fn to_full_scale(&mut self, bits: u8) {
let Some(table) = full_scale_table(bits) else {
return;
};
for plane in self.colors.iter_mut().chain(self.ir.as_mut()) {
for v in plane {
*v = table[usize::from(*v)];
}
}
}
}
fn full_scale_table(bits: u8) -> Option<Vec<u16>> {
if bits == 0 || bits >= 16 {
return None;
}
let top = u32::from(u16::MAX >> (16 - bits));
Some(
(0..=u32::from(u16::MAX))
.map(|v| ((v.min(top) * 65535 + top / 2) / top) as u16)
.collect(),
)
}
#[derive(Debug, Clone)]
pub struct Image<'a> {
pub colors: Vec<&'a [u16]>,
pub ir: &'a [u16],
pub rows: usize,
pub cols: usize,
pub bits: u8,
}
impl<'a> Image<'a> {
pub fn new(layout: &Layout, samples: &'a Samples) -> Result<Self, Error> {
let decoder = Decoder::new(layout)?;
if samples.colors.len() != decoder.colors() {
return Err(bad(format!(
"{} color planes is not the {} this layout describes",
samples.colors.len(),
decoder.colors()
)));
}
let (rows, cols) = decoder.shape();
let plane_len = rows * cols;
for (i, plane) in samples.colors.iter().enumerate() {
if plane.len() < plane_len {
return Err(bad(format!(
"color plane {i} is {} samples, short of the {plane_len} this layout describes",
plane.len()
)));
}
}
let ir: &'a [u16] = samples.ir.as_deref().unwrap_or(&[]);
if ir.len() < decoder.ir_samples() {
return Err(bad(format!(
"{} infrared samples is short of the {} this layout describes",
ir.len(),
decoder.ir_samples()
)));
}
Ok(Self {
colors: samples.colors.iter().map(Vec::as_slice).collect(),
ir,
rows,
cols,
bits: layout.bits_per_sample,
})
}
}
struct Transposed<'a> {
rows: usize,
cols: usize,
ccd_lines: usize,
gap: usize,
slots: Vec<Slot>,
colors: usize,
ir: usize,
readout: usize,
stage: usize,
bytes_per_sample: usize,
curves: Option<&'a Curves>,
}
#[derive(Debug, Clone, Copy)]
struct Slot {
first: usize,
next: usize,
stride: usize,
readings: usize,
out: usize,
color: bool,
}
impl Slot {
fn nth(&self, reading: usize) -> usize {
match reading {
0 => self.first,
r => self.next + (r - 1) * self.stride,
}
}
fn every(channels: &[u8], readings: usize) -> Vec<Self> {
let colors = channels
.iter()
.filter(|id| Channel::from(**id).is_color())
.count();
let once = channels.len() - colors;
let (mut color, mut ir) = (0, 0);
channels
.iter()
.map(|id| match Channel::from(*id).is_color() {
true => {
color += 1;
Slot {
first: color - 1,
next: colors + once + color - 1,
stride: colors,
readings,
out: color - 1,
color: true,
}
}
false => {
ir += 1;
Slot {
first: colors + ir - 1,
next: 0,
stride: 0,
readings: 1,
out: ir - 1,
color: false,
}
}
})
.collect()
}
}
impl Transposed<'_> {
fn block_bytes(&self) -> usize {
self.gap * self.stage * self.bytes_per_sample
}
fn blocks(&self) -> usize {
self.cols / (self.gap * self.ccd_lines)
}
fn shape(&self) -> (usize, usize) {
(self.rows, self.cols)
}
fn ir_samples(&self) -> usize {
self.rows * self.cols * self.ir
}
fn emit(&self, n: usize, block: &[u8], colors_out: &mut [&mut [u16]], ir_out: &mut [u16]) {
let first = n * self.gap * self.ccd_lines;
for col in 0..self.gap * self.ccd_lines {
let (position, line) = (col % self.gap, col / self.gap);
let x = first + col;
let base = position * self.stage + line;
for p in 0..self.rows {
let y = self.rows - 1 - p;
let sample = base + p * self.ccd_lines;
let pixel = y * self.cols + x;
for slot in &self.slots {
let mut sum = 0u32;
for r in 0..slot.readings {
let off = (sample + slot.nth(r) * self.readout) * self.bytes_per_sample;
let raw = sample_at(&block[off..off + self.bytes_per_sample]);
sum += u32::from(match self.curves {
Some(curves) => curves.correct(line, raw),
None => raw,
});
}
let n = slot.readings as u32;
let value = ((sum + n / 2) / n) as u16;
match slot.color {
true => colors_out[slot.out][pixel] = value,
false => ir_out[pixel] = value,
}
}
}
}
}
}
#[inline]
fn sample_at(sample: &[u8]) -> u16 {
match sample {
[b] => u16::from(*b),
[hi, lo] => u16::from_be_bytes([*hi, *lo]),
_ => unreachable!("the width is checked when the decoder is built"),
}
}
pub struct Decoder<'a> {
ordering: Transposed<'a>,
carry: Vec<u8>,
done: usize,
}
impl<'a> Decoder<'a> {
pub fn new(layout: &Layout) -> Result<Self, Error> {
if !matches!(layout.bytes_per_sample, 1 | 2) {
return Err(bad(format!(
"{} bytes a sample is neither of the widths 2-11-3 defines",
layout.bytes_per_sample
)));
}
let bytes_per_sample = usize::from(layout.bytes_per_sample);
let (rows, cols) = (layout.pixels as usize, layout.lines as usize);
if !layout.interleaving.intersects(
ColorInterleaving::MULTILINE_SIMULTANEOUS | ColorInterleaving::LINE_WITHOUT_DISTANCE,
) {
return Err(bad(format!(
"{:?} is not an ordering this decodes yet",
layout.interleaving
)));
}
let ccd_lines = if layout
.interleaving
.contains(ColorInterleaving::MULTILINE_SIMULTANEOUS)
{
usize::from(layout.ccd_lines).max(1)
} else {
1
};
let gap = match ccd_lines {
1 => 1,
_ => layout.registration_gap as usize,
};
if gap == 0 {
return Err(bad(format!(
"a scanning pitch of {} puts all the CCD rows on one output line, so the decoder cannot separate a multi-line read",
layout.line_pitch
)));
}
let strip = gap * ccd_lines;
if !cols.is_multiple_of(strip) {
return Err(bad(format!(
"{cols} columns is not a whole number of {strip}-column blocks"
)));
}
let readout = rows * ccd_lines;
let slots = Slot::every(
&layout.channels,
usize::from(layout.readings_per_line).max(1),
);
let colors = slots.iter().filter(|s| s.color).count();
let ir = slots.len() - colors;
let ordering = Transposed {
rows,
cols,
ccd_lines,
gap,
slots,
colors,
ir,
readout,
stage: layout.readouts() as usize * readout,
bytes_per_sample,
curves: None,
};
Ok(Self {
carry: Vec::with_capacity(ordering.block_bytes()),
ordering,
done: 0,
})
}
pub fn correcting(mut self, curves: &'a Curves) -> Self {
if self.ordering.ccd_lines > 1 {
self.ordering.curves = Some(curves);
}
self
}
pub fn shape(&self) -> (usize, usize) {
self.ordering.shape()
}
pub fn colors(&self) -> usize {
self.ordering.colors
}
pub fn ir_samples(&self) -> usize {
self.ordering.ir_samples()
}
pub fn decoded(&self) -> usize {
self.done
}
pub fn complete(&self) -> bool {
self.done == self.ordering.blocks()
}
pub fn push(&mut self, chunk: &[u8], samples: &mut Samples) -> Result<(), Error> {
if samples.colors.len() != self.colors() {
return Err(bad(format!(
"the output holds {} color planes and this stream needs {}",
samples.colors.len(),
self.colors()
)));
}
let (rows, cols) = self.shape();
let plane_len = rows * cols;
for (i, plane) in samples.colors.iter().enumerate() {
if plane.len() < plane_len {
return Err(bad(format!(
"color plane {i} holds {} samples and this stream needs {plane_len}",
plane.len()
)));
}
}
let needed = self.ir_samples();
let have = samples.ir.as_ref().map_or(0, Vec::len);
if have < needed {
return Err(bad(format!(
"the infrared output holds {have} samples and this stream needs {needed}"
)));
}
let mut none = Vec::new();
let ir_out = samples.ir.as_deref_mut().unwrap_or(&mut none[..]);
let mut colors_out: Vec<&mut [u16]> =
samples.colors.iter_mut().map(Vec::as_mut_slice).collect();
self.push_into(chunk, &mut colors_out, ir_out)
}
fn push_into(
&mut self,
chunk: &[u8],
colors_out: &mut [&mut [u16]],
ir_out: &mut [u16],
) -> Result<(), Error> {
let width = self.ordering.block_bytes();
let mut rest = chunk;
if !self.carry.is_empty() {
let take = (width - self.carry.len()).min(rest.len());
self.carry.extend_from_slice(&rest[..take]);
rest = &rest[take..];
if self.carry.len() < width {
return Ok(());
}
let block = std::mem::take(&mut self.carry);
self.take(&block, colors_out, ir_out);
self.carry = block;
self.carry.clear();
}
let mut blocks = rest.chunks_exact(width);
for block in &mut blocks {
self.take(block, colors_out, ir_out);
}
self.carry.extend_from_slice(blocks.remainder());
Ok(())
}
fn take(&mut self, block: &[u8], colors_out: &mut [&mut [u16]], ir_out: &mut [u16]) {
if self.done >= self.ordering.blocks() {
return;
}
self.ordering.emit(self.done, block, colors_out, ir_out);
self.done += 1;
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::protocol::image::Layout;
fn layout(pixels: u32, lines: u32, channels: Vec<u8>) -> Layout {
Layout::single_line(pixels, lines, channels)
}
fn stream(pixels: usize, lines: usize, channels: usize) -> Vec<u8> {
let mut out = Vec::new();
for y in 0..lines {
for c in 0..channels {
for x in 0..pixels {
let v = (y * 1000 + x * 10 + c) as u16;
out.extend_from_slice(&v.to_be_bytes());
}
}
}
out
}
fn buffers(l: &Layout) -> (Decoder<'_>, Samples) {
let d = Decoder::new(l).unwrap();
let mut samples = Samples::default();
samples.resize_for(&d);
(d, samples)
}
#[test]
fn a_line_of_planes_lands_in_its_own_plane() {
let l = layout(4, 3, vec![1, 2, 3]);
let (mut d, mut samples) = buffers(&l);
d.push(&stream(4, 3, 3), &mut samples).unwrap();
assert!(d.complete());
assert!(samples.ir.is_none());
assert_eq!(d.shape(), (4, 3));
for s in 0..4usize {
for f in 0..3usize {
for c in 0..3usize {
let got = samples.colors[c][(4 - 1 - s) * 3 + f];
assert_eq!(got, (f * 1000 + s * 10 + c) as u16, "{s},{f},{c}");
}
}
}
}
#[test]
fn single_line_and_three_line_read_one_geometry() {
let single = layout(4, 3, vec![1, 2, 3]);
let mut three = single.clone();
three.interleaving = ColorInterleaving::MULTILINE_SIMULTANEOUS;
three.ccd_lines = 1;
three.registration_gap = 0;
let raw = stream(4, 3, 3);
let (mut a, mut a_samples) = buffers(&single);
let (mut b, mut b_samples) = buffers(&three);
a.push(&raw, &mut a_samples).unwrap();
b.push(&raw, &mut b_samples).unwrap();
assert_eq!(a_samples.colors, b_samples.colors);
}
#[test]
fn a_stream_split_anywhere_decodes_the_same() {
let l = layout(4, 3, vec![1, 2, 3]);
let whole = stream(4, 3, 3);
let (mut d, mut want) = buffers(&l);
d.push(&whole, &mut want).unwrap();
for split in [1usize, 3, 7, 16, 23, 48] {
let (mut d, mut got) = buffers(&l);
for piece in whole.chunks(split) {
d.push(piece, &mut got).unwrap();
}
assert!(d.complete(), "split {split} left {} lines", d.decoded());
assert_eq!(got.colors, want.colors, "split {split}");
}
}
#[test]
fn a_short_stream_writes_only_what_arrived() {
let l = layout(4, 3, vec![1, 2, 3]);
let (mut d, mut samples) = buffers(&l);
let whole = stream(4, 3, 3);
d.push(&whole[..whole.len() / 3], &mut samples).unwrap();
assert!(!d.complete());
assert_eq!(d.decoded(), 1);
let cols = 3;
for (y, s) in [(0usize, 3usize), (1, 2), (2, 1), (3, 0)] {
for c in 0..3usize {
assert_eq!(
samples.colors[c][y * cols],
(s * 10 + c) as u16,
"row {y} ch {c}"
);
}
}
for y in 0..4usize {
for x in 1..3usize {
for c in 0..3usize {
assert_eq!(samples.colors[c][y * cols + x], 0, "row {y} col {x} ch {c}");
}
}
}
}
#[test]
fn padding_past_the_last_line_is_dropped() {
let l = layout(4, 2, vec![1, 2, 3]);
let (mut d, mut samples) = buffers(&l);
d.push(&stream(4, 4, 3), &mut samples).unwrap();
assert_eq!(d.decoded(), 2);
assert!(d.complete());
}
#[test]
fn a_real_pass_decodes_into_a_photograph() {
let Ok(raw) = std::fs::read("scan.raw") else {
eprintln!("no scan.raw, skipping");
return;
};
let (w, h) = (1494usize, 1494usize);
let l = layout(w as u32, h as u32, vec![1, 2, 3]);
let (mut d, mut samples) = buffers(&l);
let expected = samples.colors.iter().map(Vec::len).sum::<usize>() * 2;
if raw.len() != expected {
eprintln!(
"scan.raw is {} bytes, not the {expected} of a single-line {w}x{h} pass, skipping",
raw.len(),
);
return;
}
for piece in raw.chunks(262_144) {
d.push(piece, &mut samples).unwrap();
}
assert!(d.complete());
let at = |y: usize, x: usize, c: usize| f64::from(samples.colors[c][y * w + x]);
let (mut near, mut far, mut n) = (0.0, 0.0, 0.0);
for y in (10..h - 10).step_by(37) {
for x in (10..w - 800).step_by(37) {
near += (at(y, x, 1) - at(y, x + 1, 1)).abs();
far += (at(y, x, 1) - at(y, x + 700, 1)).abs();
n += 1.0;
}
}
eprintln!("neighbour {:.0}, distant {:.0}", near / n, far / n);
assert!(near * 4.0 < far, "near {} far {}", near / n, far / n);
}
#[test]
fn an_output_too_small_is_refused() {
let l = layout(4, 3, vec![1, 2, 3]);
let mut d = Decoder::new(&l).unwrap();
let mut samples = Samples {
colors: vec![vec![0u16; 4]; 3],
ir: None,
};
assert!(d.push(&[], &mut samples).is_err());
}
}
#[cfg(test)]
mod transposed {
use super::*;
fn layout(rows: u32, stages: u32, gap: u32, channels: Vec<u8>, readings: u8) -> Layout {
Layout {
pixels: rows,
lines: stages * 3,
pitch: 1,
line_pitch: 1,
dpi: 4000,
bytes_per_sample: 2,
bits_per_sample: 16,
channels,
interleaving: ColorInterleaving::MULTILINE_SIMULTANEOUS,
readings_per_line: readings,
ccd_lines: 3,
packed_rows: 1,
registration_gap: gap,
granule: 1,
truncated_bytes_line: (0, 0),
truncated_lines_frame: (0, 0),
multiline_registered: false,
}
}
fn buffers(l: &Layout) -> (Decoder<'_>, Samples) {
let d = Decoder::new(l).unwrap();
let mut samples = Samples::default();
samples.resize_for(&d);
(d, samples)
}
fn tag(stage: usize, slot: usize, pixel: usize, line: usize) -> u16 {
(stage * 1000 + slot * 100 + pixel * 10 + line) as u16
}
fn stream(stages: usize, readouts: usize, rows: usize, lines: usize) -> Vec<u8> {
let mut out = Vec::new();
for stage in 0..stages {
for slot in 0..readouts {
for pixel in 0..rows {
for line in 0..lines {
out.extend_from_slice(&tag(stage, slot, pixel, line).to_be_bytes());
}
}
}
}
out
}
#[test]
fn a_block_tiles_stage_positions_against_ccd_rows() {
let (rows, stages, gap) = (4usize, 2usize, 2u32);
let l = layout(rows as u32, stages as u32, gap, vec![1, 2, 3], 1);
let (mut d, mut samples) = buffers(&l);
assert_eq!(d.shape(), (rows, stages * 3));
d.push(&stream(stages, 3, rows, 3), &mut samples).unwrap();
assert!(d.complete());
let (_, cols) = d.shape();
for stage in 0..stages {
for line in 0..3 {
let x = line * gap as usize + stage;
for pixel in 0..rows {
let y = rows - 1 - pixel;
for channel in 0..3 {
assert_eq!(
samples.colors[channel][y * cols + x],
tag(stage, channel, pixel, line),
"stage {stage} line {line} pixel {pixel} channel {channel}"
);
}
}
}
}
}
#[test]
fn multi_sampling_averages_the_colors_and_leaves_infrared_apart() {
let l = layout(2, 1, 1, vec![9, 1, 2, 3], 2);
let (mut d, mut samples) = buffers(&l);
d.push(&stream(1, 7, 2, 3), &mut samples).unwrap();
assert!(d.complete());
let (_, cols) = d.shape();
let ir = samples.ir.as_ref().unwrap();
for line in 0..3 {
for pixel in 0..2 {
let (y, x) = (2 - 1 - pixel, line);
let pixel_at = y * cols + x;
assert_eq!(ir[pixel_at], tag(0, 3, pixel, line));
for c in 0..3 {
let first = tag(0, c, pixel, line);
let second = tag(0, 4 + c, pixel, line);
assert_eq!(samples.colors[c][pixel_at], (first + second) / 2);
}
}
}
}
#[test]
fn a_gap_of_zero_is_refused() {
let mut l = layout(4, 2, 4, vec![1, 2, 3], 1);
l.registration_gap = 0;
assert!(Decoder::new(&l).is_err());
}
#[test]
fn a_ragged_column_count_is_refused() {
let mut l = layout(4, 2, 4, vec![1, 2, 3], 1);
l.lines = 7;
assert!(Decoder::new(&l).is_err());
}
fn single(rows: u32, stages: u32, channels: Vec<u8>, readings: u8) -> Layout {
Layout {
pixels: rows,
lines: stages,
pitch: 1,
line_pitch: 1,
dpi: 4000,
bytes_per_sample: 2,
bits_per_sample: 16,
channels,
interleaving: ColorInterleaving::LINE_WITHOUT_DISTANCE,
readings_per_line: readings,
ccd_lines: 1,
packed_rows: 1,
registration_gap: 1,
granule: 1,
truncated_bytes_line: (0, 0),
truncated_lines_frame: (0, 0),
multiline_registered: false,
}
}
#[test]
fn a_single_line_averages_multi_pass_and_leaves_infrared_apart() {
let (rows, stages, readings) = (2usize, 2usize, 4u8);
let l = single(rows as u32, stages as u32, vec![9, 1, 2, 3], readings);
let readouts = 13usize;
let (mut d, mut samples) = buffers(&l);
d.push(&stream(stages, readouts, rows, 1), &mut samples)
.unwrap();
assert!(d.complete());
let (_, cols) = d.shape();
let ir = samples.ir.as_ref().unwrap();
for stage in 0..stages {
for pixel in 0..rows {
let (y, x) = (rows - 1 - pixel, stage);
let pixel_at = y * cols + x;
assert_eq!(
ir[pixel_at],
tag(stage, 3, pixel, 0),
"stage {stage} pixel {pixel} IR"
);
for c in 0..3 {
let slots = [c, 4 + c, 7 + c, 10 + c];
let reads: Vec<u16> = slots.iter().map(|&s| tag(stage, s, pixel, 0)).collect();
let want = reads.iter().map(|&s| u32::from(s)).sum::<u32>() / 4;
assert_eq!(
samples.colors[c][pixel_at], want as u16,
"stage {stage} pixel {pixel} color {c}"
);
}
}
}
}
#[test]
fn a_single_line_reads_once_read_channels_once() {
let (rows, stages) = (2usize, 2usize);
let l = single(rows as u32, stages as u32, vec![9], 4);
let (mut d, mut samples) = buffers(&l);
assert_eq!(d.shape(), (rows, stages));
assert!(samples.colors.is_empty());
d.push(&stream(stages, 1, rows, 1), &mut samples).unwrap();
assert!(d.complete());
let (_, cols) = d.shape();
let ir = samples.ir.as_ref().unwrap();
for stage in 0..stages {
for pixel in 0..rows {
let (y, x) = (rows - 1 - pixel, stage);
assert_eq!(ir[y * cols + x], tag(stage, 0, pixel, 0));
}
}
}
}