mirror of
https://github.com/storytold/lightcraft.git
synced 2026-10-14 19:27:01 -04:00
LR-IMP-FORMATS: ARW6 sensor-anchored frame geometry and tile reconstruction
This commit is contained in:
2 files changed
+371
-10
No files matched your search
Vendored
+323
-9
@@ -8,7 +8,11 @@
|
||||
// Consumed by the geometry and `decode` tasks that follow.
|
||||
#![allow(dead_code)]
|
||||
|
||||
use crate::llvc::{Bands3, Plane, band_rows, dequant, reconstruct3, vld_decode_line};
|
||||
use crate::vendor::pef::Bits;
|
||||
use crate::{RawError, Result};
|
||||
use rayon::prelude::*;
|
||||
use std::ops::Range;
|
||||
|
||||
fn corrupt(why: &str) -> RawError {
|
||||
RawError::Corrupt(format!("ARW6: {why}"))
|
||||
@@ -116,7 +120,6 @@ pub(crate) struct StreamRef {
|
||||
pub(crate) struct TileHeader {
|
||||
pub hs: usize,
|
||||
pub vs: usize,
|
||||
pub ntu: usize,
|
||||
/// File order: g0 c0..2, g1 c0..2, g2 c0..2, g3 c0..2, g4 c0.
|
||||
pub streams: Vec<StreamRef>,
|
||||
}
|
||||
@@ -167,7 +170,7 @@ pub(crate) fn parse_tile_header(tile: &[u8], tw: usize, th: usize) -> Result<Til
|
||||
word = end;
|
||||
}
|
||||
}
|
||||
Ok(TileHeader { hs, vs, ntu: vs.div_ceil(16), streams })
|
||||
Ok(TileHeader { hs, vs, streams })
|
||||
}
|
||||
|
||||
/// One half-TU of a stream: `len` bytes at byte `start` of the tile, quantiser index per band
|
||||
@@ -217,6 +220,233 @@ pub(crate) fn stream_halves(tile: &[u8], s: &StreamRef, ntu: usize) -> Result<Ve
|
||||
Ok(halves)
|
||||
}
|
||||
|
||||
// ---- sensor-anchored frame geometry and tile reconstruction (spec *Frame geometry*) ----
|
||||
|
||||
/// Halves of `vs` plane rows are anchored to the sensor: half `n` covers plane rows `8n - 6 + s .. 8n + 1 + s`, so
|
||||
/// `ceil((vs - 2 - s) / 8) + 1` halves are needed, two per TU (controller ruling, spec *Frame geometry*).
|
||||
pub(crate) fn tu_count(vs: usize, s: u8) -> usize {
|
||||
let halves = vs.saturating_sub(2usize.saturating_add(usize::from(s))).div_ceil(8).saturating_add(1);
|
||||
halves.div_ceil(2)
|
||||
}
|
||||
|
||||
/// Lifting phases of levels 1..=3 for anchor offset `s` (`plan/arw6/scratch/assemble.py::geometry`):
|
||||
/// FF (s = 0) gives [0, 1, 1], the APS-C crop (s = 3) [1, 0, 0].
|
||||
pub(crate) fn phases(s: u8) -> [u8; 3] {
|
||||
let s = i32::from(s);
|
||||
let p1 = s % 2;
|
||||
let p2 = ((2 + s - p1) / 2) % 2;
|
||||
let k3 = ((6 + s) % 8 - p1).div_euclid(2);
|
||||
let p3 = (k3 - p2).div_euclid(2).rem_euclid(2);
|
||||
[p1 as u8, p2 as u8, p3 as u8]
|
||||
}
|
||||
|
||||
/// Plane rows of half `n`: `8n - 6 + s .. 8n + 2 + s`, clipped to `0..rows` (empty when disjoint).
|
||||
pub(crate) fn half_rows(n: usize, s: u8, rows: usize) -> Range<usize> {
|
||||
let base = n.saturating_mul(8).saturating_add(usize::from(s));
|
||||
base.saturating_sub(6).min(rows)..base.saturating_add(2).min(rows)
|
||||
}
|
||||
|
||||
/// First half's frame rows `lines - 2`, read before the TU count is known from g4's first index entry alone
|
||||
/// (index table at word `g4.word + 1`, entry 0's `A` = big-endian u16; its bytes start after the index table).
|
||||
/// Residual lines are decoded while at least 8 bits remain and fewer than 8 lines were read (spec *Frame geometry*).
|
||||
pub(crate) fn first_half_shift(tile: &[u8], g4: &StreamRef, width2: usize) -> Result<u8> {
|
||||
let idx = g4.word.checked_add(1).and_then(|w| w.checked_mul(16)).ok_or_else(|| corrupt("stream position"))?;
|
||||
let data = g4.index_words.checked_mul(16).and_then(|l| l.checked_add(idx)).ok_or_else(|| corrupt("stream position"))?;
|
||||
let a = u16_be(tile, idx).ok_or_else(|| corrupt("index table outside the tile"))?;
|
||||
let bytes = tile.get(data..data.checked_add(a).ok_or_else(|| corrupt("stream position"))?).ok_or_else(|| corrupt("half outside the tile"))?;
|
||||
let total = bytes.len() * 8;
|
||||
let mut bits = Bits::new(bytes);
|
||||
let mut lines = 0usize;
|
||||
while lines < 8 && total.saturating_sub(bits.consumed_bits()) >= 8 {
|
||||
vld_decode_line(&mut bits, total, width2)?;
|
||||
lines += 1;
|
||||
}
|
||||
match lines.checked_sub(2) {
|
||||
Some(s @ 0..=5) => Ok(s as u8),
|
||||
_ => Err(unsupported("first-half row count outside 2..=7")),
|
||||
}
|
||||
}
|
||||
|
||||
/// Band rows of one half: plane rows `t` and the rows they land on at each level (`split`).
|
||||
struct HalfRows {
|
||||
t: Range<usize>,
|
||||
l1: Vec<usize>,
|
||||
h1: Vec<usize>,
|
||||
l2: Vec<usize>,
|
||||
h2: Vec<usize>,
|
||||
l3: Vec<usize>,
|
||||
h3: Vec<usize>,
|
||||
}
|
||||
|
||||
/// Rows with `t % 2 == p` go to the low band at `(t - p) / 2`, the others to the high band at `(t - (1 - p)) / 2`.
|
||||
fn split(rows: &[usize], p: u8) -> (Vec<usize>, Vec<usize>) {
|
||||
let p = usize::from(p);
|
||||
let (mut lo, mut hi) = (Vec::new(), Vec::new());
|
||||
for &t in rows {
|
||||
if t % 2 == p {
|
||||
lo.push(t.saturating_sub(p) / 2);
|
||||
} else {
|
||||
hi.push(t.saturating_sub(1 - p) / 2);
|
||||
}
|
||||
}
|
||||
(lo, hi)
|
||||
}
|
||||
|
||||
fn half_layout(n: usize, s: u8, vs: usize, ph: [u8; 3]) -> HalfRows {
|
||||
let t = half_rows(n, s, vs);
|
||||
let rows: Vec<usize> = t.clone().collect();
|
||||
let (l1, h1) = split(&rows, ph[0]);
|
||||
let (l2, h2) = split(&l1, ph[1]);
|
||||
let (l3, h3) = split(&l2, ph[2]);
|
||||
HalfRows { t, l1, h1, l2, h2, l3, h3 }
|
||||
}
|
||||
|
||||
/// Lines a half holds in group `g`: g0 LL3 rows; g1 HL3 + LH3 + HH3; g2 HL2 + LH2 + HH2; g3 HL1 + LH1 + HH1; g4 residual.
|
||||
fn line_count(g: u8, r: &HalfRows) -> usize {
|
||||
match g {
|
||||
0 => r.l3.len(),
|
||||
1 => r.l3.len() + 2 * r.h3.len(),
|
||||
2 => r.l2.len() + 2 * r.h2.len(),
|
||||
3 => r.l1.len() + 2 * r.h1.len(),
|
||||
_ => r.t.len(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Line width of group `g`: level-`l` bands are `(tw / 2) >> l` wide (g0, g1: level 3; g2: 2; g3: 1; g4: the plane).
|
||||
fn line_width(g: u8, w2: usize) -> usize {
|
||||
match g {
|
||||
0 | 1 => w2 >> 3,
|
||||
2 => w2 >> 2,
|
||||
3 => w2 >> 1,
|
||||
_ => w2,
|
||||
}
|
||||
}
|
||||
|
||||
/// A half with its decoded (still quantised) lines.
|
||||
type HalfLines = (Half, Vec<Vec<i32>>);
|
||||
|
||||
/// Index the stream's halves and VLD-decode exactly the lines each holds; a line running past its half is `Corrupt`.
|
||||
fn decode_stream(tile: &[u8], st: &StreamRef, ntu: usize, w2: usize, layouts: &[HalfRows]) -> Result<Vec<HalfLines>> {
|
||||
let halves = stream_halves(tile, st, ntu)?;
|
||||
let width = line_width(st.group, w2);
|
||||
halves
|
||||
.into_iter()
|
||||
.zip(layouts)
|
||||
.map(|(half, rows)| {
|
||||
let end = half.start.checked_add(half.len).ok_or_else(|| corrupt("half position"))?;
|
||||
let bytes = tile.get(half.start..end).ok_or_else(|| corrupt("half outside the tile"))?;
|
||||
let total = bytes.len() * 8;
|
||||
let mut bits = Bits::new(bytes);
|
||||
let mut lines = Vec::with_capacity(line_count(st.group, rows));
|
||||
for _ in 0..line_count(st.group, rows) {
|
||||
lines.push(vld_decode_line(&mut bits, total, width)?);
|
||||
if bits.consumed_bits() > total {
|
||||
return Err(corrupt("line runs past its half"));
|
||||
}
|
||||
}
|
||||
Ok((half, lines)) // bytes beyond the last line are ignored
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Dequantise `line` into band row `row`.
|
||||
fn put(plane: &mut Plane, row: usize, line: &[i32], mut f: impl FnMut(i32) -> i32) -> Result<()> {
|
||||
let start = row.checked_mul(plane.width).ok_or_else(|| corrupt("band row"))?;
|
||||
let dst = plane.data.get_mut(start..start.saturating_add(plane.width)).ok_or_else(|| corrupt("band row outside its band"))?;
|
||||
for (d, &v) in dst.iter_mut().zip(line) {
|
||||
*d = f(v);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Lay a half's lines into (band, rows, QI) in order: the natural clipped assignment of spec *Frame geometry*.
|
||||
fn fill(set: [(&mut Plane, &[usize], u32); 3], lines: &[Vec<i32>]) -> Result<()> {
|
||||
let mut it = lines.iter();
|
||||
for (plane, rows, qi) in set {
|
||||
for &r in rows {
|
||||
put(plane, r, it.next().ok_or_else(|| corrupt("half holds fewer lines than rows"))?, |q| dequant(q, qi))?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// One colour component: its ten bands from streams g0..g3 (component `c`), then the 3-level inverse.
|
||||
fn component(c: usize, streams: &[Vec<HalfLines>], layouts: &[HalfRows], vs: usize, w2: usize, ph: [u8; 3]) -> Result<Plane> {
|
||||
let (n1l, n1h) = band_rows(vs, ph[0]);
|
||||
let (n2l, n2h) = band_rows(n1l, ph[1]);
|
||||
let (n3l, n3h) = band_rows(n2l, ph[2]);
|
||||
let (w1, w2b, w3) = (w2 >> 1, w2 >> 2, w2 >> 3);
|
||||
let z = Plane::zeros;
|
||||
let mut b = Bands3 {
|
||||
ll3: z(w3, n3l),
|
||||
hl3: z(w3, n3l),
|
||||
lh3: z(w3, n3h),
|
||||
hh3: z(w3, n3h),
|
||||
hl2: z(w2b, n2l),
|
||||
lh2: z(w2b, n2h),
|
||||
hh2: z(w2b, n2h),
|
||||
hl1: z(w1, n1l),
|
||||
lh1: z(w1, n1h),
|
||||
hh1: z(w1, n1h),
|
||||
};
|
||||
let grp = |g: usize, n: usize| streams.get(3 * g + c).and_then(|s| s.get(n)).ok_or_else(|| corrupt("missing half"));
|
||||
for (n, r) in layouts.iter().enumerate() {
|
||||
let (h0, l0) = grp(0, n)?;
|
||||
// LL3: the nibble must be 0 (Phase 0: never seen otherwise); lines are DPCM from 2048 (spec *Container*).
|
||||
if h0.qi[0] != 0 {
|
||||
return Err(unsupported("LL3 quantiser index"));
|
||||
}
|
||||
for (&row, line) in r.l3.iter().zip(l0) {
|
||||
let mut acc = 2048i32;
|
||||
put(&mut b.ll3, row, line, |d| {
|
||||
acc = acc.saturating_add(d);
|
||||
acc
|
||||
})?;
|
||||
}
|
||||
let (h1, l1) = grp(1, n)?;
|
||||
fill([(&mut b.hl3, &r.l3, h1.qi[0]), (&mut b.lh3, &r.h3, h1.qi[1]), (&mut b.hh3, &r.h3, h1.qi[2])], l1)?;
|
||||
let (h2, l2) = grp(2, n)?;
|
||||
fill([(&mut b.hl2, &r.l2, h2.qi[0]), (&mut b.lh2, &r.h2, h2.qi[1]), (&mut b.hh2, &r.h2, h2.qi[2])], l2)?;
|
||||
let (h3, l3) = grp(3, n)?;
|
||||
fill([(&mut b.hl1, &r.l1, h3.qi[0]), (&mut b.lh1, &r.h1, h3.qi[1]), (&mut b.hh1, &r.h1, h3.qi[2])], l3)?;
|
||||
}
|
||||
reconstruct3(&b, ph)
|
||||
}
|
||||
|
||||
/// A decoded tile: the three wavelet planes (green mean, two chroma) and the green residual, each `tw/2 x th/2`.
|
||||
pub(crate) struct TilePlanes {
|
||||
pub m: Plane,
|
||||
pub c1: Plane,
|
||||
pub c2: Plane,
|
||||
pub res: Plane,
|
||||
}
|
||||
|
||||
/// Decode a tile to its planes: header, `s` from g4's first half, TU count, all 13 streams (in parallel), band
|
||||
/// assembly and the 3-level inverse per component (in parallel).
|
||||
pub(crate) fn decode_tile_planes(tile: &[u8], tw: usize, th: usize) -> Result<TilePlanes> {
|
||||
let h = parse_tile_header(tile, tw, th)?;
|
||||
let (w2, vs) = (tw / 2, h.vs);
|
||||
if vs == 0 || !tw.is_multiple_of(2) || !w2.is_multiple_of(8) || w2.checked_mul(vs).is_none_or(|a| a > crate::MAX_SAMPLES) {
|
||||
return Err(unsupported("tile geometry"));
|
||||
}
|
||||
let g4 = h.streams.get(12).ok_or_else(|| corrupt("missing residual stream"))?;
|
||||
let s = first_half_shift(tile, g4, w2)?;
|
||||
let (ntu, ph) = (tu_count(vs, s), phases(s));
|
||||
let layouts: Vec<HalfRows> = (0..2 * ntu).map(|n| half_layout(n, s, vs, ph)).collect();
|
||||
let streams = h.streams.par_iter().map(|st| decode_stream(tile, st, ntu, w2, &layouts)).collect::<Result<Vec<_>>>()?;
|
||||
let comps = (0..3).into_par_iter().map(|c| component(c, &streams, &layouts, vs, w2, ph)).collect::<Result<Vec<_>>>()?;
|
||||
// Residual: g4's lines are the half's plane rows, dequantised with the half's nibble.
|
||||
let mut res = Plane::zeros(w2, vs);
|
||||
for ((half, lines), r) in streams.get(12).ok_or_else(|| corrupt("missing residual stream"))?.iter().zip(&layouts) {
|
||||
for (row, line) in r.t.clone().zip(lines) {
|
||||
put(&mut res, row, line, |q| dequant(q, half.qi[0]))?;
|
||||
}
|
||||
}
|
||||
let mut it = comps.into_iter();
|
||||
let (Some(m), Some(c1), Some(c2)) = (it.next(), it.next(), it.next()) else { return Err(corrupt("missing component")) };
|
||||
Ok(TilePlanes { m, c1, c2, res })
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -287,15 +517,17 @@ mod tests {
|
||||
fn tile_header_and_streams() {
|
||||
let (tile, _) = one_tile(64, 48, 3, Qis::REAL);
|
||||
let h = parse_tile_header(&tile, 64, 48).unwrap();
|
||||
assert_eq!((h.hs, h.vs, h.ntu, h.streams.len()), (64, 24, 2, 13));
|
||||
assert_eq!((h.hs, h.vs, h.streams.len()), (64, 24, 13));
|
||||
let ntu = tu_count(24, 3);
|
||||
assert_eq!(ntu, 2);
|
||||
assert_eq!(
|
||||
h.streams.iter().map(|s| (s.group, s.comp)).collect::<Vec<_>>(),
|
||||
[(0, 0), (0, 1), (0, 2), (1, 0), (1, 1), (1, 2), (2, 0), (2, 1), (2, 2), (3, 0), (3, 1), (3, 2), (4, 0)]
|
||||
);
|
||||
let g3 = stream_halves(&tile, &h.streams[9], h.ntu).unwrap();
|
||||
let g3 = stream_halves(&tile, &h.streams[9], ntu).unwrap();
|
||||
assert_eq!(g3.len(), 4);
|
||||
assert_eq!(g3[0].qi, [1, 1, 2]);
|
||||
let g4 = stream_halves(&tile, &h.streams[12], h.ntu).unwrap();
|
||||
let g4 = stream_halves(&tile, &h.streams[12], ntu).unwrap();
|
||||
assert_eq!(g4[1].qi, [2, 2, 2]);
|
||||
assert!(g4[3].start + g4[3].len <= tile.len());
|
||||
let mut bad = tile.clone();
|
||||
@@ -373,9 +605,9 @@ mod tests {
|
||||
let mut bad_half = tile.clone();
|
||||
let idx_start = (s.word + 1) * 16;
|
||||
bad_half[idx_start..idx_start + 2].copy_from_slice(&[0xff, 0xff]); // A past the data area
|
||||
assert!(stream_halves(&bad_half, s, h.ntu).is_err());
|
||||
assert!(stream_halves(&bad_half, s, tu_count(24, 0)).is_err());
|
||||
let outside = StreamRef { index_words: 0xffff, ..s.clone() };
|
||||
assert!(stream_halves(&tile, &outside, h.ntu).is_err());
|
||||
assert!(stream_halves(&tile, &outside, tu_count(24, 0)).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -384,11 +616,12 @@ mod tests {
|
||||
let (tile, spec) = one_tile(64, 48, s, Qis::REAL);
|
||||
assert_eq!(tile.len() % 4096, 0);
|
||||
let h = parse_tile_header(&tile, 64, 48).unwrap();
|
||||
let ntu = tu_count(24, s);
|
||||
for st in &h.streams {
|
||||
assert_eq!(stream_halves(&tile, st, h.ntu).unwrap().len(), 2 * h.ntu);
|
||||
assert_eq!(stream_halves(&tile, st, ntu).unwrap().len(), 2 * ntu);
|
||||
}
|
||||
// the residual stream's halves decode to the clipped, quantised rows 8n-6+s ..= 8n+1+s
|
||||
let g4 = stream_halves(&tile, &h.streams[12], h.ntu).unwrap();
|
||||
let g4 = stream_halves(&tile, &h.streams[12], ntu).unwrap();
|
||||
for (n, half) in g4.iter().enumerate() {
|
||||
let bytes = &tile[half.start..half.start + half.len];
|
||||
let mut bits = Bits::new(bytes);
|
||||
@@ -412,4 +645,85 @@ mod tests {
|
||||
let mut b = Bits::new(&bytes);
|
||||
assert_eq!(crate::llvc::vld_decode_line(&mut b, bytes.len() * 8, 100).unwrap(), vals);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn phases_and_half_rows_match_the_spec() {
|
||||
assert_eq!(phases(0), [0, 1, 1]);
|
||||
assert_eq!(phases(3), [1, 0, 0]);
|
||||
assert_eq!(half_rows(0, 0, 1668), 0..2);
|
||||
assert_eq!(half_rows(209, 0, 1668), 1666..1668);
|
||||
assert_eq!(half_rows(1, 0, 1668), 2..10);
|
||||
assert_eq!(half_rows(0, 3, 2186), 0..5);
|
||||
assert_eq!(half_rows(273, 3, 2186), 2181..2186);
|
||||
assert_eq!(half_rows(300, 3, 2186), 2186..2186);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tu_count_covers_every_row() {
|
||||
assert_eq!((tu_count(1668, 0), tu_count(2186, 3), tu_count(24, 3), tu_count(48, 0)), (105, 137, 2, 4));
|
||||
for s in 0u8..=5 {
|
||||
for vs in 1usize..200 {
|
||||
let last = half_rows(2 * tu_count(vs, s) - 1, s, vs);
|
||||
assert!(last.end == vs || (2 * tu_count(vs, s) - 1) * 8 + usize::from(s) + 2 >= vs, "s {s} vs {vs}");
|
||||
assert_eq!((0..2 * tu_count(vs, s)).map(|n| half_rows(n, s, vs).len()).sum::<usize>(), vs, "s {s} vs {vs}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn first_half_shift_is_read_from_the_stream() {
|
||||
for s in [0u8, 3, 5] {
|
||||
let (tile, _) = one_tile(64, 48, s, Qis::REAL);
|
||||
let h = parse_tile_header(&tile, 64, 48).unwrap();
|
||||
assert_eq!(first_half_shift(&tile, &h.streams[12], 32).unwrap(), s);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn round_trip_every_shift() {
|
||||
for s in 0u8..=5 {
|
||||
for (w, h) in [(64usize, 48usize), (80, 96), (64, 36)] {
|
||||
let (tile, spec) = one_tile(w, h, s, Qis::ZERO); // QI 0: lossless, planes must come back exactly
|
||||
let p = decode_tile_planes(&tile, w, h).unwrap_or_else(|e| panic!("s={s} {w}x{h}: {e}"));
|
||||
assert_eq!(p.m, spec.m, "s={s} {w}x{h} m");
|
||||
assert_eq!(p.c1, spec.c1);
|
||||
assert_eq!(p.c2, spec.c2);
|
||||
assert_eq!(p.res, spec.res);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn round_trip_with_real_quantisers() {
|
||||
// bands drawn as quantised integers q, planes = reconstruct3(dequant(q)); the encoder must reproduce them bit for bit
|
||||
for s in [0u8, 3] {
|
||||
let (tile, spec) = one_tile_quantised(96, 64, s, Qis::REAL);
|
||||
let p = decode_tile_planes(&tile, 96, 64).unwrap();
|
||||
assert_eq!(p.m, spec.m);
|
||||
assert_eq!(p.c1, spec.c1);
|
||||
assert_eq!(p.c2, spec.c2);
|
||||
assert_eq!(p.res, spec.res);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn half_too_short_is_corrupt() {
|
||||
let (mut tile, _) = one_tile(64, 48, 0, Qis::ZERO);
|
||||
let h = parse_tile_header(&tile, 64, 48).unwrap();
|
||||
let w = h.streams[9].word * 16 + 16; // first index entry of g3 c0: shrink A by 1 byte
|
||||
let a = u16::from_be_bytes([tile[w], tile[w + 1]]) - 1;
|
||||
tile[w..w + 2].copy_from_slice(&a.to_be_bytes());
|
||||
assert!(matches!(decode_tile_planes(&tile, 64, 48), Err(RawError::Corrupt(_))));
|
||||
}
|
||||
|
||||
proptest::proptest! {
|
||||
#[test]
|
||||
fn mutated_tiles_never_panic(flips in proptest::collection::vec((proptest::num::usize::ANY, proptest::num::u8::ANY), 1..12), cut in proptest::num::usize::ANY) {
|
||||
let (mut tile, _) = one_tile(64, 48, 3, Qis::REAL);
|
||||
for (i, b) in flips { let i = i % tile.len(); tile[i] ^= b; }
|
||||
let n = cut % (tile.len() + 1);
|
||||
let _ = decode_tile_planes(&tile[..n], 64, 48);
|
||||
let _ = decode_tile_planes(&tile, 64, 48);
|
||||
}
|
||||
}
|
||||
}
|
||||
+48
-1
@@ -290,7 +290,7 @@ fn build_stream(halves: &[(Vec<u8>, Vec<u32>)]) -> Vec<u8> {
|
||||
/// clipped order (spec *Frame geometry*), then add index tables, stream headers and the tile header.
|
||||
pub(crate) fn encode_tile(t: &TileSpec) -> Vec<u8> {
|
||||
let (vs, s) = (t.m.height, i32::from(t.s));
|
||||
let ntu = vs.div_ceil(16);
|
||||
let ntu = crate::vendor::arw6::tu_count(vs, t.s);
|
||||
let p1 = s % 2;
|
||||
let p2 = ((2 + s - p1) / 2) % 2;
|
||||
let k3 = ((6 + s) % 8 - p1) / 2;
|
||||
@@ -411,3 +411,50 @@ pub(crate) fn arw6_file(tiles: &[(usize, usize, usize, usize, Vec<u8>)], width:
|
||||
ifd0.add_sub_ifd(raw);
|
||||
TiffWriter::new(ByteOrder::Little, false).write(&[ifd0]).unwrap()
|
||||
}
|
||||
|
||||
/// A tile whose bands are random quantised integers `q` (level 1 wider), `v = dequant(q, qi)`, planes by
|
||||
/// `reconstruct3` (so `quantize` in the encoder is exact on them); LL3 is random around 2048, the residual
|
||||
/// `dequant(q, qi.res)`.
|
||||
pub(crate) fn one_tile_quantised(w: usize, h: usize, s: u8, qi: Qis) -> (Vec<u8>, TileSpec) {
|
||||
use crate::llvc::{dequant, reconstruct3};
|
||||
let (w2, vs) = (w / 2, h / 2);
|
||||
let ph = crate::vendor::arw6::phases(s);
|
||||
let mut x = 0x9e37_79b9_7f4a_7c15u64;
|
||||
let mut rnd = |span: i32, centre: i32, q: u32| -> i32 {
|
||||
x ^= x << 13;
|
||||
x ^= x >> 7;
|
||||
x ^= x << 17;
|
||||
let v = (x % (2 * span as u64 + 1)) as i32 - span;
|
||||
if centre == 0 { dequant(v, q) } else { centre + v }
|
||||
};
|
||||
let mut plane = |wd: usize, ht: usize, span: i32, centre: i32, q: u32| Plane {
|
||||
width: wd,
|
||||
height: ht,
|
||||
data: (0..wd * ht).map(|_| rnd(span, centre, q)).collect(),
|
||||
};
|
||||
let mut comps = Vec::new();
|
||||
for c in 0..3 {
|
||||
let (n1l, n1h) = band_rows(vs, ph[0]);
|
||||
let (n2l, n2h) = band_rows(n1l, ph[1]);
|
||||
let (n3l, n3h) = band_rows(n2l, ph[2]);
|
||||
let (w1, w2b, w3) = (w2 / 2, w2 / 4, w2 / 8);
|
||||
let (a, b, d) = (qi.l1[c], qi.l2[c], qi.l3[c]);
|
||||
let bands = Bands3 {
|
||||
ll3: plane(w3, n3l, 60, 2048, 0),
|
||||
hl3: plane(w3, n3l, 7, 0, d[0]),
|
||||
lh3: plane(w3, n3h, 7, 0, d[1]),
|
||||
hh3: plane(w3, n3h, 7, 0, d[2]),
|
||||
hl2: plane(w2b, n2l, 7, 0, b[0]),
|
||||
lh2: plane(w2b, n2h, 7, 0, b[1]),
|
||||
hh2: plane(w2b, n2h, 7, 0, b[2]),
|
||||
hl1: plane(w1, n1l, 15, 0, a[0]),
|
||||
lh1: plane(w1, n1h, 15, 0, a[1]),
|
||||
hh1: plane(w1, n1h, 15, 0, a[2]),
|
||||
};
|
||||
comps.push(reconstruct3(&bands, ph).unwrap());
|
||||
}
|
||||
let res = plane(w2, vs, 7, 0, qi.res);
|
||||
let (m, c1, c2) = (comps.remove(0), comps.remove(0), comps.remove(0));
|
||||
let t = TileSpec { s, qi, m, c1, c2, res };
|
||||
(encode_tile(&t), t)
|
||||
}
|
||||
Reference in new issue
Block a user