use flashraw_core::bits::{BitPumpMsb, BitRead};
use flashraw_core::par::{self, GridWriter};
use flashraw_core::{Error, Rect, Result};
pub const HEADER_SIZE: usize = 16;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct FujiHeader {
pub lossless: bool,
pub xtrans: bool,
pub bits: u32,
pub height: u32,
pub rounded_width: u32,
pub width: u32,
pub block_size: u32,
pub blocks: u32,
pub lines: u32,
}
impl FujiHeader {
pub fn parse(d: &[u8]) -> Result<Self> {
let h = d.get(..HEADER_SIZE).ok_or_else(|| Error::corrupt("truncated Fuji compressed header"))?;
let be16 = |i: usize| u16::from_be_bytes([h[i], h[i + 1]]) as u32;
let signature = be16(0);
let lossless = h[2];
let raw_type = h[3];
let bits = h[4] as u32;
let height = be16(5);
let rounded_width = be16(7);
let width = be16(9);
let block_size = be16(11);
let blocks = h[13] as u32;
let lines = be16(14);
let ok = signature == 0x4953
&& lossless <= 1
&& (6..=0x4002).contains(&height)
&& height % 6 == 0
&& block_size == 0x300
&& (0x300..=0x4200).contains(&width)
&& width % 24 == 0
&& rounded_width <= 0x4200
&& rounded_width >= block_size
&& rounded_width % block_size == 0
&& rounded_width - width < block_size
&& blocks > 0
&& blocks <= 0x10
&& blocks == rounded_width / block_size
&& lines > 0
&& lines <= 0xAAB
&& lines == height / 6
&& matches!(bits, 12 | 14 | 16)
&& matches!(raw_type, 0 | 16);
if !ok {
return Err(Error::corrupt("invalid Fuji compressed header"));
}
Ok(FujiHeader {
lossless: lossless == 1,
xtrans: raw_type == 16,
bits,
height,
rounded_width,
width,
block_size,
blocks,
lines,
})
}
fn line_width(&self) -> usize {
if self.xtrans { self.block_size as usize * 2 / 3 } else { self.block_size as usize / 2 }
}
pub fn block_width(&self, i: u32) -> u32 {
if i + 1 == self.blocks { self.width - self.block_size * i } else { self.block_size }
}
}
pub type ColorTile = [[u8; 6]; 6];
pub struct FujiCompressed<'a> {
pub header: FujiHeader,
blocks: Vec<&'a [u8]>,
q_bases: Vec<&'a [u8]>,
}
impl<'a> FujiCompressed<'a> {
pub fn new(data: &'a [u8]) -> Result<Self> {
let header = FujiHeader::parse(data)?;
let body = &data[HEADER_SIZE..];
let nb = header.blocks as usize;
let sizes_len = 4 * nb;
let sizes = body.get(..sizes_len).ok_or_else(|| Error::corrupt("truncated Fuji block size table"))?;
let mut pos = (sizes_len + 15) & !15;
let mut q_bases = Vec::new();
if !header.lossless {
let step = (header.lines as usize + 15) & !15;
for b in 0..nb {
let start = pos + b * step;
let q = body.get(start..start + header.lines as usize);
q_bases.push(q.ok_or_else(|| Error::corrupt("truncated Fuji lossy quantisation table"))?);
}
pos += nb * step;
}
let mut blocks = Vec::with_capacity(nb);
for b in 0..nb {
let size =
u32::from_be_bytes([sizes[4 * b], sizes[4 * b + 1], sizes[4 * b + 2], sizes[4 * b + 3]]) as usize;
let start = pos.min(body.len());
let end = pos.saturating_add(size).min(body.len());
blocks.push(&body[start..end]);
pos = pos.saturating_add(size);
}
Ok(FujiCompressed { header, blocks, q_bases })
}
pub fn block_ranges(&self, base: &'a [u8]) -> Vec<(usize, usize)> {
self.blocks.iter().map(|b| (b.as_ptr() as usize - base.as_ptr() as usize, b.len())).collect()
}
pub fn decode(&self, out: &mut [u16], width: u32, height: u32, colors: &ColorTile, parallel: bool) -> Result<()> {
let h = &self.header;
if width != h.width || height != h.height {
return Err(Error::InvalidArgument("Fuji compressed output size does not match the stream".into()));
}
self.decode_region(out, Rect::new(0, 0, width, height), colors, parallel)
}
pub fn decode_region(&self, out: &mut [u16], region: Rect, colors: &ColorTile, parallel: bool) -> Result<()> {
let h = &self.header;
let (w, ht) = (region.width as usize, region.height as usize);
if out.len() < w * ht || region.right() > h.width || region.bottom() > h.height {
return Err(Error::InvalidArgument("Fuji compressed region outside the image or buffer too small".into()));
}
if region.is_empty() {
return Ok(());
}
let common = Common::new(h, &self.q_bases)?;
let map = OutputMap::new(h, colors);
let grid = GridWriter::new(
out,
region.width,
region.height,
1,
(region.x, region.y),
h.block_size,
h.height,
h.width,
h.height,
)
.ok_or_else(|| Error::InvalidArgument("invalid Fuji output grid".into()))?;
let needed_lines = (region.bottom() as usize).div_ceil(6).min(h.lines as usize);
let errors = std::sync::Mutex::new(None);
par::for_each_index(self.blocks.len(), parallel, |b| {
let Some(mut tile) = grid.claim(b as u32, 0) else { return };
let (tw, th) = tile.size();
let (sx, sy) = (tile.skip_x as usize, tile.skip_y as usize);
let q = self.q_bases.get(b).copied().unwrap_or(&[]);
let emit = |line: usize, lines: &[u16]| {
for r in 0..6 {
let y = line * 6 + r;
if y < sy || y - sy >= th {
continue;
}
let row = tile.row(y - sy);
let idx = map.idx[r].get(sx..sx + tw).unwrap_or(&[]);
for (dst, &src) in row.iter_mut().zip(idx.iter()) {
*dst = lines.get(src as usize).copied().unwrap_or(0);
}
}
};
let res = if h.lossless {
decode_block::<false>(&common, self.blocks[b], q, needed_lines, emit)
} else {
decode_block::<true>(&common, self.blocks[b], q, needed_lines, emit)
};
if let Err(e) = res {
errors.lock().unwrap().get_or_insert(e);
}
});
match errors.into_inner().unwrap() {
Some(e) => Err(e),
None => Ok(()),
}
}
}
const R0: usize = 0;
const R2: usize = 2;
const R3: usize = 3;
const R4: usize = 4;
const G0: usize = 5;
const G2: usize = 7;
const G3: usize = 8;
const G4: usize = 9;
const G5: usize = 10;
const G6: usize = 11;
const G7: usize = 12;
const B0: usize = 13;
const B2: usize = 15;
const B3: usize = 16;
const B4: usize = 17;
const LINES: usize = 18;
struct OutputMap {
idx: [Vec<u32>; 6],
}
impl OutputMap {
fn new(h: &FujiHeader, colors: &ColorTile) -> Self {
let stride = h.line_width() + 2;
let bs = h.block_size as usize;
let idx = std::array::from_fn(|r| {
(0..bs)
.map(|p| {
let c = colors[r][p % 6];
let line = match c {
0 => R2 + r / 2,
2 => B2 + r / 2,
_ => G2 + r,
};
let i = if h.xtrans { (((p * 2 / 3) & !1) | ((p % 3) & 1)) + ((p % 3) >> 1) } else { p >> 1 };
(line * stride + 1 + i) as u32
})
.collect()
});
OutputMap { idx }
}
}
const LUT_LEN: usize = 1 << 17;
type Lut = Box<[i8; LUT_LEN]>;
#[derive(Clone)]
struct QTable {
lut: Lut,
q_base: i32,
max_grad: i32,
grad_mult: i32,
total_values: i32,
raw_bits: u32,
max_zeros: i32,
}
fn log2ceil(v: i32) -> u32 {
if v <= 1 { 0 } else { 32 - ((v - 1) as u32).leading_zeros() }
}
impl QTable {
fn lut(qp: [i32; 5]) -> Lut {
let max = qp[4];
let mut t: Lut = vec![0; LUT_LEN].into_boxed_slice().try_into().unwrap();
for v in -max..=max {
t[v as usize & (LUT_LEN - 1)] = {
if v <= -qp[3] {
-4
} else if v <= -qp[2] {
-3
} else if v <= -qp[1] {
-2
} else if v < -qp[0] {
-1
} else if v <= qp[0] {
0
} else if v < qp[1] {
1
} else if v < qp[2] {
2
} else if v < qp[3] {
3
} else {
4
}
};
}
t
}
fn new(lut: Lut, q_base: i32, max_grad: i32, grad_mult: i32, total_values: i32, max_value: i32) -> Self {
let raw_bits = log2ceil(total_values);
let max_bits = 4 * log2ceil(max_value + 1) as i32;
QTable { lut, q_base, max_grad, grad_mult, total_values, raw_bits, max_zeros: max_bits - raw_bits as i32 - 1 }
}
fn main(q_base: i32, max_value: i32) -> QTable {
let max_val = max_value + 1;
let mut qp = [q_base, 3 * q_base + 0x12, 5 * q_base + 0x43, 7 * q_base + 0x114, max_value];
if qp[1] >= max_val || qp[1] < q_base + 1 {
qp[1] = q_base + 1;
}
if qp[2] < qp[1] || qp[2] >= max_val {
qp[2] = qp[1];
}
if qp[3] < qp[2] || qp[3] >= max_val {
qp[3] = qp[2];
}
let total_values = (qp[4] + 2 * q_base) / (2 * q_base + 1) + 1;
Self::new(Self::lut(qp), q_base, 0, 9, total_values, max_value)
}
#[inline(always)]
fn level(&self, v: i32) -> i32 {
self.lut[v as usize & (LUT_LEN - 1)] as i32
}
fn lossy_extra(max_value: i32) -> [QTable; 3] {
let m = max_value;
let pick = |limit: i32, fallback: i32| if m >= limit { limit } else { fallback };
let mk = |q_base: i32, max_grad: i32, total_values: i32, t: [i32; 3]| {
let q1 = pick(t[0], q_base + 1);
let q2 = pick(t[1], q1);
let q3 = pick(t[2], q2);
Self::new(Self::lut([q_base, q1, q2, q3, m]), q_base, max_grad, 3, total_values, m)
};
[
mk(0, 5, m + 1, [0x12, 0x43, 0x114]),
mk(1, 6, (m + 2) / 3 + 1, [0x15, 0x48, 0x11B]),
mk(2, 7, (m + 4) / 5 + 1, [0x18, 0x4D, 0x122]),
]
}
}
struct Common {
xtrans: bool,
line_width: usize,
max_value: i32,
main: Vec<Option<QTable>>,
extra: [QTable; 3],
}
impl Common {
fn new(h: &FujiHeader, q_bases: &[&[u8]]) -> Result<Self> {
let max_value = (1i32 << h.bits) - 1;
let mut main: Vec<Option<QTable>> = vec![None; 256];
if h.lossless {
main[0] = Some(QTable::main(0, max_value));
} else {
let mut seen = [false; 256];
for q in q_bases.iter().flat_map(|q| q.iter()) {
seen[*q as usize] = true;
}
for (q, _) in seen.iter().enumerate().filter(|(_, s)| **s) {
main[q] = Some(QTable::main(q as i32, max_value));
}
}
Ok(Common {
xtrans: h.xtrans,
line_width: h.line_width(),
max_value,
main,
extra: QTable::lossy_extra(max_value),
})
}
}
#[derive(Clone, Copy)]
#[repr(C, align(8))]
struct Stat {
sum: i32,
n: u8,
k: u8,
mask: u16,
}
#[inline(always)]
fn new_stat(sum: i32, n: i32) -> Stat {
let k = bit_diff(sum, n);
Stat { sum, n: n as u8, k: k as u8, mask: ((1u32 << k) - 1) as u16 }
}
#[derive(Clone, Copy)]
struct Grads {
main: [Stat; 41],
lossy: [[Stat; 5]; 3],
}
const MIN_VALUE: i32 = 0x40;
#[inline(always)]
fn bit_diff(sum: i32, n: i32) -> u32 {
let (s, n) = (sum.max(1) as u32, n.max(1) as u32);
let k = n.leading_zeros().saturating_sub(s.leading_zeros());
let k = k + (((n as u64) << k) < s as u64) as u32;
k.min(15)
}
fn decode_block<const LOSSY: bool>(
c: &Common,
data: &[u8],
q_bases: &[u8],
lines: usize,
mut emit: impl FnMut(usize, &[u16]),
) -> Result<()> {
let mut d = BlockDecoder::<LOSSY>::new(c, data, q_bases)?;
for line in 0..lines {
d.decode_line(line)?;
emit(line, &d.lines);
d.advance_line();
}
Ok(())
}
struct BlockDecoder<'c, 'a, const LOSSY: bool> {
c: &'c Common,
bits: BitPumpMsb<'a>,
data_len: usize,
lines: Vec<u16>,
stride: usize,
grads: [[Grads; 3]; 2],
q_bases: &'a [u8],
qt: &'c QTable,
cur_q: i32,
overrun: bool,
}
#[derive(Clone, Copy, PartialEq, Eq)]
enum Even {
Decode,
Interp,
}
impl<'c, 'a, const LOSSY: bool> BlockDecoder<'c, 'a, LOSSY> {
fn new(c: &'c Common, data: &'a [u8], q_bases: &'a [u8]) -> Result<Self> {
let stride = c.line_width + 2;
let init = Grads { main: [new_stat(0, 1); 41], lossy: [[new_stat(0, 1); 5]; 3] };
let first_q = if LOSSY { q_bases.first().copied().unwrap_or(0) as usize } else { 0 };
let qt = c.main[first_q].as_ref().ok_or_else(|| Error::corrupt("missing Fuji quantisation table"))?;
let mut d = BlockDecoder {
c,
bits: BitPumpMsb::new(data),
data_len: data.len(),
lines: vec![0; LINES * stride],
stride,
grads: [[init; 3]; 2],
q_bases,
qt,
cur_q: -1,
overrun: false,
};
if LOSSY {
for k in 0..3 {
let max_diff = 2.max((c.extra[k].total_values + 0x20) >> 6);
for g in d.grads.iter_mut().flatten() {
g.lossy[k] = [new_stat(max_diff, 1); 5];
}
}
} else {
d.cur_q = 0;
d.init_main_grads();
}
Ok(d)
}
fn init_main_grads(&mut self) {
let max_diff = 2.max((self.qt.total_values + 0x20) >> 6);
for g in self.grads.iter_mut().flatten() {
g.main = [new_stat(max_diff, 1); 41];
}
}
fn decode_line(&mut self, line: usize) -> Result<()> {
if LOSSY {
let q = self.q_bases.get(line).copied().unwrap_or(0) as i32;
if line == 0 || q != self.cur_q {
self.qt = self.c.main[q as usize]
.as_ref()
.ok_or_else(|| Error::corrupt("missing Fuji quantisation table"))?;
self.cur_q = q;
self.init_main_grads();
}
}
if self.c.xtrans {
let rule_a = |p: usize| if p & 3 != 0 { Even::Decode } else { Even::Interp };
let rule_b = |p: usize| if p & 3 == 2 { Even::Interp } else { Even::Decode };
self.pass(R2, G2, 0, |_| Even::Interp, |_| Even::Decode);
self.extend(R2, R4);
self.extend(G2, G7);
self.pass(G3, B2, 1, |_| Even::Decode, |_| Even::Interp);
self.extend(G2, G7);
self.extend(B2, B4);
self.pass(R3, G4, 2, rule_a, |_| Even::Interp);
self.extend(R2, R4);
self.extend(G2, G7);
self.pass(G5, B3, 0, |_| Even::Decode, rule_b);
self.extend(G2, G7);
self.extend(B2, B4);
self.pass(R4, G6, 1, rule_b, |_| Even::Decode);
self.extend(R2, R4);
self.extend(G2, G7);
self.pass(G7, B4, 2, |_| Even::Interp, rule_a);
self.extend(G2, G7);
self.extend(B2, B4);
} else {
let d = |_: usize| Even::Decode;
self.pass(R2, G2, 0, d, d);
self.extend(R2, R4);
self.extend(G2, G7);
self.pass(G3, B2, 1, d, d);
self.extend(G2, G7);
self.extend(B2, B4);
self.pass(R3, G4, 2, d, d);
self.extend(R2, R4);
self.extend(G2, G7);
self.pass(G5, B3, 0, d, d);
self.extend(G2, G7);
self.extend(B2, B4);
self.pass(R4, G6, 1, d, d);
self.extend(R2, R4);
self.extend(G2, G7);
self.pass(G7, B4, 2, d, d);
self.extend(G2, G7);
self.extend(B2, B4);
}
if self.overrun || self.bits_consumed() > 8 * self.data_len + 7 {
return Err(Error::corrupt("truncated Fuji compressed block"));
}
Ok(())
}
fn bits_consumed(&self) -> usize {
(self.bits.byte_position() * 8).saturating_sub((self.bits.buffered_bits() % 8) as usize)
}
#[inline(never)]
fn pass(&mut self, a: usize, b: usize, g: usize, rule_a: impl Fn(usize) -> Even, rule_b: impl Fn(usize) -> Even) {
let s = self.stride;
let mut lines = std::mem::take(&mut self.lines);
let mut st = Sampler { c: self.c, qt: self.qt, bits: self.bits, overrun: false };
let [even, odd] = &mut self.grads;
let (ge, go) = (&mut even[g], &mut odd[g]);
let (lo, hi) = lines.split_at_mut((b - 2) * s);
let mut la = Lines::new(&mut lo[(a - 2) * s..], s);
let mut lb = Lines::new(hi, s);
let lw = la.width();
let (mut ep, mut op) = (0usize, 1usize);
while ep < lw || op < lw {
if ep < lw {
match rule_a(ep) {
Even::Decode => la.decode_even::<LOSSY>(&mut st, ge, ep),
Even::Interp => la.interp_even(ep),
}
match rule_b(ep) {
Even::Decode => lb.decode_even::<LOSSY>(&mut st, ge, ep),
Even::Interp => lb.interp_even(ep),
}
ep += 2;
}
if ep > 8 {
la.decode_odd::<LOSSY>(&mut st, go, op);
lb.decode_odd::<LOSSY>(&mut st, go, op);
op += 2;
}
}
self.bits = st.bits;
self.overrun |= st.overrun;
self.lines = lines;
}
fn extend(&mut self, first: usize, last: usize) {
let (s, lw) = (self.stride, self.c.line_width);
for l in first..=last {
self.lines[l * s] = self.lines[(l - 1) * s + 1];
self.lines[l * s + lw + 1] = self.lines[(l - 1) * s + lw];
}
}
fn advance_line(&mut self) {
let s = self.stride;
let lw = self.c.line_width;
for (dst, src) in [(R0, R3), (R0 + 1, R4), (G0, G6), (G0 + 1, G7), (B0, B3), (B0 + 1, B4)] {
self.lines.copy_within(src * s..(src + 1) * s, dst * s);
}
for (first, n) in [(R2, 3), (G2, 6), (B2, 3)] {
self.lines[first * s..(first + n) * s].fill(0);
self.lines[first * s] = self.lines[(first - 1) * s + 1];
self.lines[first * s + lw + 1] = self.lines[(first - 1) * s + lw];
}
}
}
struct Sampler<'c, 'a> {
c: &'c Common,
qt: &'c QTable,
bits: BitPumpMsb<'a>,
overrun: bool,
}
impl Sampler<'_, '_> {
#[inline(always)]
fn zero_run(&mut self) -> i32 {
let mut n = 0i32;
loop {
self.bits.fill();
let w = self.bits.peek_no_fill(32);
if w != 0 {
let lz = w.leading_zeros();
self.bits.skip_no_fill(lz + 1);
return n + lz as i32;
}
self.bits.skip_no_fill(32);
n += 32;
if self.bits.exhausted() {
self.overrun = true;
return n;
}
}
}
#[inline(always)]
fn decode_residual<const LOSSY: bool>(
&mut self,
grads: &mut Grads,
flat: i32,
v1: i32,
v2: i32,
interp: i32,
) -> u16 {
let c = self.c;
let max = c.max_value;
let mut qt = self.qt;
let mut ctx = usize::MAX;
if LOSSY {
let mut i = 1;
while qt.q_base >= i && i < 4 {
let t = &c.extra[(i - 1) as usize];
if flat <= t.max_grad {
qt = t;
ctx = (i - 1) as usize;
break;
}
i += 1;
}
}
let mult = if LOSSY { qt.grad_mult } else { 9 };
let grad = mult * qt.level(v1) + qt.level(v2);
let gi = grad.unsigned_abs() as usize;
let st = if LOSSY && ctx != usize::MAX { grads.lossy[ctx][gi.min(4)] } else { grads.main[gi.min(40)] };
let (sum, n, k, mask) = (st.sum, st.n as i32, st.k as u32, st.mask as u32);
self.bits.refill_force();
let w = self.bits.peek_no_fill(32);
let lz = w.leading_zeros();
let code = if w != 0 && (lz as i32) < qt.max_zeros && lz + 1 + k <= 32 {
let total = lz + 1 + k;
let v = (w >> (32 - total)) & mask;
self.bits.skip_no_fill(total);
((lz << k) | v) as i32
} else {
let sample = self.zero_run();
if sample < qt.max_zeros {
((sample as u32) << k | self.bits.get(k)) as i32
} else {
self.bits.get(qt.raw_bits) as i32 + 1
}
};
let code = (code >> 1) ^ -(code & 1);
let h = (n == MIN_VALUE) as u32;
let (sum, n) = ((sum + code.abs()) >> h, n >> h);
let stat =
if LOSSY && ctx != usize::MAX { &mut grads.lossy[ctx][gi.min(4)] } else { &mut grads.main[gi.min(40)] };
*stat = new_stat(sum, n + 1);
let (q_base, step) = if LOSSY { (qt.q_base, 2 * qt.q_base + 1) } else { (0, 1) };
let mut v = interp + ((grad >> 31) | 1) * code * step;
if v < -q_base {
v += qt.total_values * step;
} else if v > q_base + max {
v -= qt.total_values * step;
}
v.clamp(0, max) as u16
}
}
struct Lines<'l> {
up2: &'l [u16],
up1: &'l [u16],
cur: &'l mut [u16],
}
impl<'l> Lines<'l> {
#[inline(always)]
fn new(block: &'l mut [u16], s: usize) -> Self {
let (up2, rest) = block.split_at_mut(s);
let (up1, rest) = rest.split_at_mut(s);
Lines { up2, up1, cur: &mut rest[..s] }
}
#[inline(always)]
fn width(&self) -> usize {
self.cur.len().saturating_sub(2)
}
#[inline(always)]
fn even_ctx(&self, pos: usize) -> (i32, i32, i32, i32) {
let up = &self.up1[pos..pos + 3];
(self.up2[pos + 1] as i32, up[0] as i32, up[1] as i32, up[2] as i32)
}
#[inline(always)]
fn interp_even(&mut self, pos: usize) {
let (rf, rc, rb, rd) = self.even_ctx(pos);
self.cur[pos + 1] = (even_prediction(rf, rc, rb, rd) >> 2) as u16;
}
#[inline(always)]
fn decode_even<const LOSSY: bool>(&mut self, st: &mut Sampler, grads: &mut Grads, pos: usize) {
let (rf, rc, rb, rd) = self.even_ctx(pos);
let interp = even_prediction(rf, rc, rb, rd) >> 2;
let flat = (rf - rb).abs() + (rc - rb).abs();
self.cur[pos + 1] = st.decode_residual::<LOSSY>(grads, flat, rb - rf, rc - rb, interp);
}
#[inline(always)]
fn decode_odd<const LOSSY: bool>(&mut self, st: &mut Sampler, grads: &mut Grads, pos: usize) {
let up = &self.up1[pos..pos + 3];
let (rc, rb, rd) = (up[0] as i32, up[1] as i32, up[2] as i32);
let cur = &mut self.cur[pos..pos + 3];
let (ra, rg) = (cur[0] as i32, cur[2] as i32);
let (x, y) = (rb - rc, rb - rd);
let extremum = ((x ^ y) >= 0) & (x != 0) & (y != 0);
let e = -(extremum as i32);
let interp = (((rg + ra + 2 * rb) >> 2) & e) | (((ra + rg) >> 1) & !e);
let flat = (rb - rc).abs() + (rc - ra).abs();
cur[1] = st.decode_residual::<LOSSY>(grads, flat, rb - rc, rc - ra, interp);
}
}
#[inline(always)]
fn even_prediction(rf: i32, rc: i32, rb: i32, rd: i32) -> i32 {
let (d_cb, d_fb, d_db) = ((rc - rb).abs(), (rf - rb).abs(), (rd - rb).abs());
let mc = -(((d_cb > d_fb) & (d_cb > d_db)) as i32);
let md = -(((d_db > d_cb) & (d_db > d_fb)) as i32) & !mc;
let dropped = (rc & mc) | (rd & md) | (rf & !(mc | md));
rf + rc + rd - dropped + 2 * rb
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn rice_parameter_matches_reference_loop() {
for v1 in [0, 1, 2, 3, 5, 64, 100, 1000, 65535, 1 << 20, i32::MAX / 4] {
for v2 in 1..=130 {
let mut d = 0u32;
if v2 < v1 {
loop {
if d > 14 {
break;
}
d += 1;
if ((v2 as i64) << d) >= v1 as i64 {
break;
}
}
}
assert_eq!(bit_diff(v1, v2), d, "v1 {v1} v2 {v2}");
}
}
}
fn header(lossless: u8, raw_type: u8, bits: u8, h: u16, rw: u16, w: u16, blocks: u8) -> Vec<u8> {
let mut v = vec![0x49, 0x53, lossless, raw_type, bits];
v.extend(h.to_be_bytes());
v.extend(rw.to_be_bytes());
v.extend(w.to_be_bytes());
v.extend(0x300u16.to_be_bytes());
v.push(blocks);
v.extend((h / 6).to_be_bytes());
v
}
#[test]
fn header_validation() {
let h = header(1, 16, 14, 4182, 6912, 6384, 9);
let p = FujiHeader::parse(&h).unwrap();
assert!(p.lossless && p.xtrans);
assert_eq!((p.width, p.height, p.blocks, p.lines), (6384, 4182, 9, 697));
assert_eq!(p.block_width(8), 6384 - 8 * 768);
assert!(FujiHeader::parse(&header(1, 16, 13, 4182, 6912, 6384, 9)).is_err());
assert!(FujiHeader::parse(&header(1, 16, 14, 4181, 6912, 6384, 9)).is_err());
assert!(FujiHeader::parse(&header(1, 16, 14, 4182, 6912, 6384, 8)).is_err());
assert!(FujiHeader::parse(&header(2, 16, 14, 4182, 6912, 6384, 9)).is_err());
assert!(FujiHeader::parse(&h[..10]).is_err());
}
#[test]
fn qtables() {
let t = QTable::main(0, 16383);
assert_eq!(t.total_values, 16384);
assert_eq!(t.raw_bits, 14);
assert_eq!(t.level(0), 0);
assert_eq!(t.level(1), 1);
assert_eq!(t.level(-1), -1);
assert_eq!(t.level(0x12), 2);
assert_eq!(t.level(16383), 4);
assert_eq!(t.level(-16383), -4);
let t = QTable::main(3, 16383);
assert_eq!(t.total_values, (16383 + 6) / 7 + 1);
let x = QTable::lossy_extra(4095);
assert_eq!(x[1].total_values, (4095 + 2) / 3 + 1);
assert_eq!(log2ceil(16384), 14);
assert_eq!(log2ceil(16385), 15);
assert_eq!(log2ceil(1), 0);
}
fn stream(lossless: bool, xtrans: bool, payload: &[u8]) -> Vec<u8> {
let mut v = header(lossless as u8, if xtrans { 16 } else { 0 }, 14, 12, 768, 768, 1);
v.extend((payload.len() as u32).to_be_bytes());
v.extend([0u8; 12]);
if !lossless {
v.extend([0u8, 2]);
v.extend([0u8; 14]);
}
v.extend_from_slice(payload);
v
}
fn pseudo(n: usize, seed: u64) -> Vec<u8> {
let mut x = seed;
(0..n)
.map(|_| {
x ^= x << 13;
x ^= x >> 7;
x ^= x << 17;
(x >> 24) as u8 | 0x11
})
.collect()
}
const XT: ColorTile = [
[1, 1, 0, 1, 1, 2],
[1, 1, 2, 1, 1, 0],
[2, 0, 1, 0, 2, 1],
[1, 1, 2, 1, 1, 0],
[1, 1, 0, 1, 1, 2],
[0, 2, 1, 2, 0, 1],
];
const BAYER: ColorTile = [
[0, 1, 0, 1, 0, 1],
[1, 2, 1, 2, 1, 2],
[0, 1, 0, 1, 0, 1],
[1, 2, 1, 2, 1, 2],
[0, 1, 0, 1, 0, 1],
[1, 2, 1, 2, 1, 2],
];
#[test]
fn decodes_synthetic_streams_without_panicking() {
for (lossless, xtrans) in [(true, true), (true, false), (false, true), (false, false)] {
for seed in 1..6 {
let data = stream(lossless, xtrans, &pseudo(200_000, seed));
let c = FujiCompressed::new(&data).unwrap();
let mut out = vec![0u16; 768 * 12];
let tile = if xtrans { XT } else { BAYER };
if c.decode(&mut out, 768, 12, &tile, false).is_ok() {
assert!(out.iter().all(|&v| v < 1 << 14));
}
}
}
}
#[test]
fn region_decode_matches_full_decode() {
let mut v = header(1, 16, 14, 12, 1536, 1536, 2);
let payload = vec![0xFFu8; 300_000];
v.extend((payload.len() as u32).to_be_bytes());
v.extend((payload.len() as u32).to_be_bytes());
v.extend([0u8; 8]);
v.extend_from_slice(&payload);
v.extend_from_slice(&payload);
let c = FujiCompressed::new(&v).unwrap();
let mut full = vec![0u16; 1536 * 12];
c.decode(&mut full, 1536, 12, &XT, true).unwrap();
for r in [Rect::new(700, 3, 100, 5), Rect::new(0, 0, 10, 1), Rect::new(800, 6, 736, 6)] {
let mut part = vec![0u16; (r.width * r.height) as usize];
c.decode_region(&mut part, r, &XT, true).unwrap();
for y in 0..r.height as usize {
let src = &full[(r.y as usize + y) * 1536 + r.x as usize..][..r.width as usize];
assert_eq!(&part[y * r.width as usize..][..r.width as usize], src, "{r:?} row {y}");
}
}
let mut small = vec![0u16; 4];
assert!(c.decode_region(&mut small, Rect::new(1530, 0, 10, 1), &XT, true).is_err());
}
#[test]
fn truncated_stream_is_an_error() {
let data = stream(true, true, &[0x00; 64]);
let c = FujiCompressed::new(&data).unwrap();
let mut out = vec![0u16; 768 * 12];
assert!(c.decode(&mut out, 768, 12, &XT, true).is_err());
assert!(FujiCompressed::new(&data[..18]).is_err());
}
#[test]
fn all_ones_stream_is_deterministic() {
let data = stream(true, true, &vec![0xFF; 400_000]);
let c = FujiCompressed::new(&data).unwrap();
let mut a = vec![0u16; 768 * 12];
let mut b = vec![0u16; 768 * 12];
c.decode(&mut a, 768, 12, &XT, false).unwrap();
c.decode(&mut b, 768, 12, &XT, true).unwrap();
assert_eq!(a, b);
}
}