Skip to main content

flashraw_codecs/
fuji_compressed.rs

1//! Fujifilm compressed RAF codec (lossless and lossy, X-Trans and Bayer).
2//!
3//! ## Stream layout
4//! The payload starts with a 16-byte big-endian header (signature `IS`), followed by
5//! one big-endian `u32` byte size per vertical *block* (padded to 16 bytes), then —
6//! for lossy files only — one quantisation base per block and 6-row *line*
7//! (`blocks × align16(lines)` bytes), and finally the entropy-coded blocks back to back.
8//!
9//! The image is split into vertical stripes of `block_size` (768) columns. Every
10//! stripe is an independent bit stream, which is what makes this codec embarrassingly
11//! parallel: [`FujiCompressed::decode`] decodes all stripes concurrently straight
12//! into the output buffer.
13//!
14//! ## Coding
15//! A stripe is coded line by line, a line being six sensor rows. Samples of each
16//! colour are gathered into per-colour line buffers (3 red, 6 green, 3 blue per line,
17//! plus the two previous lines of each colour as context). Each sample is predicted
18//! from its neighbours (a median-edge-like rule that differs for even and odd
19//! positions), and the residual is coded with an adaptive Golomb-Rice-like code: a
20//! unary prefix (zero run) followed by `k` raw bits, `k` derived from running
21//! statistics selected by a quantised local gradient. Lossy files additionally
22//! quantise residuals with a per-line step `2 * q_base + 1`. X-Trans streams leave
23//! some positions to pure interpolation (no bits are spent on them).
24//!
25//! Decoding is bit-exact, including tolerance of a one-byte over-read at the end of
26//! a stripe.
27
28use flashraw_core::bits::{BitPumpMsb, BitRead};
29use flashraw_core::par::{self, GridWriter};
30use flashraw_core::{Error, Rect, Result};
31
32/// Size of the stream header in bytes.
33pub const HEADER_SIZE: usize = 16;
34
35/// Parsed stream header.
36#[derive(Debug, Clone, Copy, PartialEq, Eq)]
37pub struct FujiHeader {
38    pub lossless: bool,
39    /// X-Trans (raw type 16) or Bayer (raw type 0).
40    pub xtrans: bool,
41    pub bits: u32,
42    pub height: u32,
43    pub rounded_width: u32,
44    pub width: u32,
45    pub block_size: u32,
46    pub blocks: u32,
47    /// Number of 6-row lines.
48    pub lines: u32,
49}
50
51impl FujiHeader {
52    /// Parse and validate the 16-byte header.
53    pub fn parse(d: &[u8]) -> Result<Self> {
54        let h = d.get(..HEADER_SIZE).ok_or_else(|| Error::corrupt("truncated Fuji compressed header"))?;
55        let be16 = |i: usize| u16::from_be_bytes([h[i], h[i + 1]]) as u32;
56        let signature = be16(0);
57        let lossless = h[2];
58        let raw_type = h[3];
59        let bits = h[4] as u32;
60        let height = be16(5);
61        let rounded_width = be16(7);
62        let width = be16(9);
63        let block_size = be16(11);
64        let blocks = h[13] as u32;
65        let lines = be16(14);
66        let ok = signature == 0x4953
67            && lossless <= 1
68            && (6..=0x4002).contains(&height)
69            && height % 6 == 0
70            && block_size == 0x300
71            && (0x300..=0x4200).contains(&width)
72            && width % 24 == 0
73            && rounded_width <= 0x4200
74            && rounded_width >= block_size
75            && rounded_width % block_size == 0
76            && rounded_width - width < block_size
77            && blocks > 0
78            && blocks <= 0x10
79            && blocks == rounded_width / block_size
80            && lines > 0
81            && lines <= 0xAAB
82            && lines == height / 6
83            && matches!(bits, 12 | 14 | 16)
84            && matches!(raw_type, 0 | 16);
85        if !ok {
86            return Err(Error::corrupt("invalid Fuji compressed header"));
87        }
88        Ok(FujiHeader {
89            lossless: lossless == 1,
90            xtrans: raw_type == 16,
91            bits,
92            height,
93            rounded_width,
94            width,
95            block_size,
96            blocks,
97            lines,
98        })
99    }
100
101    /// Samples per colour line buffer.
102    fn line_width(&self) -> usize {
103        if self.xtrans { self.block_size as usize * 2 / 3 } else { self.block_size as usize / 2 }
104    }
105
106    /// Width in pixels of block `i` (the last block may be narrower).
107    pub fn block_width(&self, i: u32) -> u32 {
108        if i + 1 == self.blocks { self.width - self.block_size * i } else { self.block_size }
109    }
110}
111
112/// Colour (0 = red, 1 = green, 2 = blue) of every position of a 6×6 tile anchored at
113/// the buffer origin. Bayer patterns are repeated to fill the tile.
114pub type ColorTile = [[u8; 6]; 6];
115
116/// A parsed compressed payload, ready to decode.
117pub struct FujiCompressed<'a> {
118    pub header: FujiHeader,
119    /// Entropy-coded data of each block.
120    blocks: Vec<&'a [u8]>,
121    /// Lossy only: per block, the quantisation base of each line.
122    q_bases: Vec<&'a [u8]>,
123}
124
125impl<'a> FujiCompressed<'a> {
126    /// `data` starts at the stream header.
127    pub fn new(data: &'a [u8]) -> Result<Self> {
128        let header = FujiHeader::parse(data)?;
129        let body = &data[HEADER_SIZE..];
130        let nb = header.blocks as usize;
131        let sizes_len = 4 * nb;
132        let sizes = body.get(..sizes_len).ok_or_else(|| Error::corrupt("truncated Fuji block size table"))?;
133        let mut pos = (sizes_len + 15) & !15;
134        let mut q_bases = Vec::new();
135        if !header.lossless {
136            let step = (header.lines as usize + 15) & !15;
137            for b in 0..nb {
138                let start = pos + b * step;
139                let q = body.get(start..start + header.lines as usize);
140                // A short table means missing data.
141                q_bases.push(q.ok_or_else(|| Error::corrupt("truncated Fuji lossy quantisation table"))?);
142            }
143            pos += nb * step;
144        }
145        let mut blocks = Vec::with_capacity(nb);
146        for b in 0..nb {
147            let size =
148                u32::from_be_bytes([sizes[4 * b], sizes[4 * b + 1], sizes[4 * b + 2], sizes[4 * b + 3]]) as usize;
149            // A block past the end of the file is empty: its decode fails (and is
150            // reported as truncation by the container), while opening still succeeds.
151            let start = pos.min(body.len());
152            let end = pos.saturating_add(size).min(body.len());
153            blocks.push(&body[start..end]);
154            pos = pos.saturating_add(size);
155        }
156        Ok(FujiCompressed { header, blocks, q_bases })
157    }
158
159    /// Byte ranges (relative to the header start) of each block's entropy data.
160    pub fn block_ranges(&self, base: &'a [u8]) -> Vec<(usize, usize)> {
161        self.blocks.iter().map(|b| (b.as_ptr() as usize - base.as_ptr() as usize, b.len())).collect()
162    }
163
164    /// Decode into `out`, a row-major `width × height` buffer that must match the
165    /// stream's image size. Stripes decode in parallel when `parallel`.
166    pub fn decode(&self, out: &mut [u16], width: u32, height: u32, colors: &ColorTile, parallel: bool) -> Result<()> {
167        let h = &self.header;
168        if width != h.width || height != h.height {
169            return Err(Error::InvalidArgument("Fuji compressed output size does not match the stream".into()));
170        }
171        self.decode_region(out, Rect::new(0, 0, width, height), colors, parallel)
172    }
173
174    /// Decode only `region` (image coordinates) into `out` (`region.width × region.height`).
175    /// Only the stripes intersecting the region are decoded, and each stops after the
176    /// last 6-row line the region needs.
177    pub fn decode_region(&self, out: &mut [u16], region: Rect, colors: &ColorTile, parallel: bool) -> Result<()> {
178        let h = &self.header;
179        let (w, ht) = (region.width as usize, region.height as usize);
180        if out.len() < w * ht || region.right() > h.width || region.bottom() > h.height {
181            return Err(Error::InvalidArgument("Fuji compressed region outside the image or buffer too small".into()));
182        }
183        if region.is_empty() {
184            return Ok(());
185        }
186        let common = Common::new(h, &self.q_bases)?;
187        let map = OutputMap::new(h, colors);
188        let grid = GridWriter::new(
189            out,
190            region.width,
191            region.height,
192            1,
193            (region.x, region.y),
194            h.block_size,
195            h.height,
196            h.width,
197            h.height,
198        )
199        .ok_or_else(|| Error::InvalidArgument("invalid Fuji output grid".into()))?;
200        let needed_lines = (region.bottom() as usize).div_ceil(6).min(h.lines as usize);
201        let errors = std::sync::Mutex::new(None);
202        par::for_each_index(self.blocks.len(), parallel, |b| {
203            // Stripes outside the region are not claimed (claim returns None).
204            let Some(mut tile) = grid.claim(b as u32, 0) else { return };
205            let (tw, th) = tile.size();
206            let (sx, sy) = (tile.skip_x as usize, tile.skip_y as usize);
207            let q = self.q_bases.get(b).copied().unwrap_or(&[]);
208            let emit = |line: usize, lines: &[u16]| {
209                for r in 0..6 {
210                    let y = line * 6 + r;
211                    if y < sy || y - sy >= th {
212                        continue;
213                    }
214                    let row = tile.row(y - sy);
215                    let idx = map.idx[r].get(sx..sx + tw).unwrap_or(&[]);
216                    for (dst, &src) in row.iter_mut().zip(idx.iter()) {
217                        *dst = lines.get(src as usize).copied().unwrap_or(0);
218                    }
219                }
220            };
221            let res = if h.lossless {
222                decode_block::<false>(&common, self.blocks[b], q, needed_lines, emit)
223            } else {
224                decode_block::<true>(&common, self.blocks[b], q, needed_lines, emit)
225            };
226            if let Err(e) = res {
227                errors.lock().unwrap().get_or_insert(e);
228            }
229        });
230        match errors.into_inner().unwrap() {
231            Some(e) => Err(e),
232            None => Ok(()),
233        }
234    }
235}
236
237// Line buffer indices.
238const R0: usize = 0;
239const R2: usize = 2;
240const R3: usize = 3;
241const R4: usize = 4;
242const G0: usize = 5;
243const G2: usize = 7;
244const G3: usize = 8;
245const G4: usize = 9;
246const G5: usize = 10;
247const G6: usize = 11;
248const G7: usize = 12;
249const B0: usize = 13;
250const B2: usize = 15;
251const B3: usize = 16;
252const B4: usize = 17;
253const LINES: usize = 18;
254
255/// Maps each of the 6 output rows × block columns to an index in the line buffers.
256struct OutputMap {
257    idx: [Vec<u32>; 6],
258}
259
260impl OutputMap {
261    fn new(h: &FujiHeader, colors: &ColorTile) -> Self {
262        let stride = h.line_width() + 2;
263        let bs = h.block_size as usize;
264        let idx = std::array::from_fn(|r| {
265            (0..bs)
266                .map(|p| {
267                    let c = colors[r][p % 6];
268                    let line = match c {
269                        0 => R2 + r / 2,
270                        2 => B2 + r / 2,
271                        _ => G2 + r,
272                    };
273                    let i = if h.xtrans { (((p * 2 / 3) & !1) | ((p % 3) & 1)) + ((p % 3) >> 1) } else { p >> 1 };
274                    (line * stride + 1 + i) as u32
275                })
276                .collect()
277        });
278        OutputMap { idx }
279    }
280}
281
282/// Size of a level table: a power of two above every difference of 16-bit samples
283/// (`-65535..=65535`), so a difference wraps to a unique index.
284const LUT_LEN: usize = 1 << 17;
285type Lut = Box<[i8; LUT_LEN]>;
286
287/// A quantisation table: maps a neighbour difference to a level in `-4..=4`.
288#[derive(Clone)]
289struct QTable {
290    lut: Lut,
291    q_base: i32,
292    max_grad: i32,
293    grad_mult: i32,
294    total_values: i32,
295    raw_bits: u32,
296    /// Longest unary prefix of a Rice code; longer prefixes escape to a raw value.
297    max_zeros: i32,
298}
299
300fn log2ceil(v: i32) -> u32 {
301    if v <= 1 { 0 } else { 32 - ((v - 1) as u32).leading_zeros() }
302}
303
304impl QTable {
305    /// Level lookup for differences in `-max..=max` with thresholds `qp[0..4]`, indexed
306    /// by the difference modulo the table size.
307    fn lut(qp: [i32; 5]) -> Lut {
308        let max = qp[4];
309        let mut t: Lut = vec![0; LUT_LEN].into_boxed_slice().try_into().unwrap();
310        for v in -max..=max {
311            t[v as usize & (LUT_LEN - 1)] = {
312                if v <= -qp[3] {
313                    -4
314                } else if v <= -qp[2] {
315                    -3
316                } else if v <= -qp[1] {
317                    -2
318                } else if v < -qp[0] {
319                    -1
320                } else if v <= qp[0] {
321                    0
322                } else if v < qp[1] {
323                    1
324                } else if v < qp[2] {
325                    2
326                } else if v < qp[3] {
327                    3
328                } else {
329                    4
330                }
331            };
332        }
333        t
334    }
335
336    fn new(lut: Lut, q_base: i32, max_grad: i32, grad_mult: i32, total_values: i32, max_value: i32) -> Self {
337        let raw_bits = log2ceil(total_values);
338        let max_bits = 4 * log2ceil(max_value + 1) as i32;
339        QTable { lut, q_base, max_grad, grad_mult, total_values, raw_bits, max_zeros: max_bits - raw_bits as i32 - 1 }
340    }
341
342    /// The main table for a line quantisation base.
343    fn main(q_base: i32, max_value: i32) -> QTable {
344        let max_val = max_value + 1;
345        let mut qp = [q_base, 3 * q_base + 0x12, 5 * q_base + 0x43, 7 * q_base + 0x114, max_value];
346        if qp[1] >= max_val || qp[1] < q_base + 1 {
347            qp[1] = q_base + 1;
348        }
349        if qp[2] < qp[1] || qp[2] >= max_val {
350            qp[2] = qp[1];
351        }
352        if qp[3] < qp[2] || qp[3] >= max_val {
353            qp[3] = qp[2];
354        }
355        let total_values = (qp[4] + 2 * q_base) / (2 * q_base + 1) + 1;
356        Self::new(Self::lut(qp), q_base, 0, 9, total_values, max_value)
357    }
358
359    #[inline(always)]
360    fn level(&self, v: i32) -> i32 {
361        // The mask keeps the index in range without a bounds check.
362        self.lut[v as usize & (LUT_LEN - 1)] as i32
363    }
364
365    /// The three fixed tables used by lossy streams for flat areas.
366    fn lossy_extra(max_value: i32) -> [QTable; 3] {
367        let m = max_value;
368        let pick = |limit: i32, fallback: i32| if m >= limit { limit } else { fallback };
369        let mk = |q_base: i32, max_grad: i32, total_values: i32, t: [i32; 3]| {
370            let q1 = pick(t[0], q_base + 1);
371            let q2 = pick(t[1], q1);
372            let q3 = pick(t[2], q2);
373            Self::new(Self::lut([q_base, q1, q2, q3, m]), q_base, max_grad, 3, total_values, m)
374        };
375        [
376            mk(0, 5, m + 1, [0x12, 0x43, 0x114]),
377            mk(1, 6, (m + 2) / 3 + 1, [0x15, 0x48, 0x11B]),
378            mk(2, 7, (m + 4) / 5 + 1, [0x18, 0x4D, 0x122]),
379        ]
380    }
381}
382
383/// Parameters shared by all blocks.
384struct Common {
385    xtrans: bool,
386    line_width: usize,
387    max_value: i32,
388    /// Main tables indexed by q_base (lossless: only index 0).
389    main: Vec<Option<QTable>>,
390    extra: [QTable; 3],
391}
392
393impl Common {
394    fn new(h: &FujiHeader, q_bases: &[&[u8]]) -> Result<Self> {
395        let max_value = (1i32 << h.bits) - 1;
396        let mut main: Vec<Option<QTable>> = vec![None; 256];
397        if h.lossless {
398            main[0] = Some(QTable::main(0, max_value));
399        } else {
400            // Precompute the (few) distinct line quantisers once for all blocks.
401            let mut seen = [false; 256];
402            for q in q_bases.iter().flat_map(|q| q.iter()) {
403                seen[*q as usize] = true;
404            }
405            for (q, _) in seen.iter().enumerate().filter(|(_, s)| **s) {
406                main[q] = Some(QTable::main(q as i32, max_value));
407            }
408        }
409        Ok(Common {
410            xtrans: h.xtrans,
411            line_width: h.line_width(),
412            max_value,
413            main,
414            extra: QTable::lossy_extra(max_value),
415        })
416    }
417}
418
419/// Adaptive statistics for one gradient context: (accumulated magnitude, count).
420/// The Rice parameter `bit_diff(sum, n)` is kept alongside, computed when the
421/// statistics change rather than on every use.
422/// Packed in 8 bytes so one load fetches all three.
423#[derive(Clone, Copy)]
424#[repr(C, align(8))]
425struct Stat {
426    sum: i32,
427    n: u8,
428    k: u8,
429    /// `(1 << k) - 1`, the mask of the Rice remainder.
430    mask: u16,
431}
432
433#[inline(always)]
434fn new_stat(sum: i32, n: i32) -> Stat {
435    // `n` stays within 1..=MIN_VALUE and `k` within 0..=15.
436    let k = bit_diff(sum, n);
437    Stat { sum, n: n as u8, k: k as u8, mask: ((1u32 << k) - 1) as u16 }
438}
439
440/// Per-context statistics: 41 main gradient contexts and, for lossy streams, 5
441/// contexts for each of the three flat-area tables.
442#[derive(Clone, Copy)]
443struct Grads {
444    main: [Stat; 41],
445    lossy: [[Stat; 5]; 3],
446}
447
448const MIN_VALUE: i32 = 0x40;
449
450/// Smallest `k` (capped at 15) such that `n << k >= sum`: the Rice parameter.
451#[inline(always)]
452fn bit_diff(sum: i32, n: i32) -> u32 {
453    // Branch-free: start from the bit-length difference and correct by one.
454    let (s, n) = (sum.max(1) as u32, n.max(1) as u32);
455    let k = n.leading_zeros().saturating_sub(s.leading_zeros());
456    let k = k + (((n as u64) << k) < s as u64) as u32;
457    k.min(15)
458}
459
460/// Decode all lines of one stripe, handing each finished line's buffers to `emit`.
461fn decode_block<const LOSSY: bool>(
462    c: &Common,
463    data: &[u8],
464    q_bases: &[u8],
465    lines: usize,
466    mut emit: impl FnMut(usize, &[u16]),
467) -> Result<()> {
468    let mut d = BlockDecoder::<LOSSY>::new(c, data, q_bases)?;
469    for line in 0..lines {
470        d.decode_line(line)?;
471        emit(line, &d.lines);
472        d.advance_line();
473    }
474    Ok(())
475}
476
477struct BlockDecoder<'c, 'a, const LOSSY: bool> {
478    c: &'c Common,
479    bits: BitPumpMsb<'a>,
480    data_len: usize,
481    /// 18 colour lines of `line_width + 2` samples (one guard sample on each side).
482    lines: Vec<u16>,
483    stride: usize,
484    /// Statistics for even (0) and odd (1) positions, three sets each (chosen per pass).
485    grads: [[Grads; 3]; 2],
486    q_bases: &'a [u8],
487    /// Current main table.
488    qt: &'c QTable,
489    cur_q: i32,
490    /// Set when the bit stream ran dry inside a zero run (checked once per line).
491    overrun: bool,
492}
493
494#[derive(Clone, Copy, PartialEq, Eq)]
495enum Even {
496    Decode,
497    Interp,
498}
499
500impl<'c, 'a, const LOSSY: bool> BlockDecoder<'c, 'a, LOSSY> {
501    fn new(c: &'c Common, data: &'a [u8], q_bases: &'a [u8]) -> Result<Self> {
502        let stride = c.line_width + 2;
503        let init = Grads { main: [new_stat(0, 1); 41], lossy: [[new_stat(0, 1); 5]; 3] };
504        let first_q = if LOSSY { q_bases.first().copied().unwrap_or(0) as usize } else { 0 };
505        let qt = c.main[first_q].as_ref().ok_or_else(|| Error::corrupt("missing Fuji quantisation table"))?;
506        let mut d = BlockDecoder {
507            c,
508            bits: BitPumpMsb::new(data),
509            data_len: data.len(),
510            lines: vec![0; LINES * stride],
511            stride,
512            grads: [[init; 3]; 2],
513            q_bases,
514            qt,
515            cur_q: -1,
516            overrun: false,
517        };
518        if LOSSY {
519            for k in 0..3 {
520                let max_diff = 2.max((c.extra[k].total_values + 0x20) >> 6);
521                for g in d.grads.iter_mut().flatten() {
522                    g.lossy[k] = [new_stat(max_diff, 1); 5];
523                }
524            }
525        } else {
526            d.cur_q = 0;
527            d.init_main_grads();
528        }
529        Ok(d)
530    }
531
532    fn init_main_grads(&mut self) {
533        let max_diff = 2.max((self.qt.total_values + 0x20) >> 6);
534        for g in self.grads.iter_mut().flatten() {
535            g.main = [new_stat(max_diff, 1); 41];
536        }
537    }
538
539    fn decode_line(&mut self, line: usize) -> Result<()> {
540        if LOSSY {
541            let q = self.q_bases.get(line).copied().unwrap_or(0) as i32;
542            if line == 0 || q != self.cur_q {
543                self.qt = self.c.main[q as usize]
544                    .as_ref()
545                    .ok_or_else(|| Error::corrupt("missing Fuji quantisation table"))?;
546                self.cur_q = q;
547                self.init_main_grads();
548            }
549        }
550        if self.c.xtrans {
551            let rule_a = |p: usize| if p & 3 != 0 { Even::Decode } else { Even::Interp };
552            let rule_b = |p: usize| if p & 3 == 2 { Even::Interp } else { Even::Decode };
553            self.pass(R2, G2, 0, |_| Even::Interp, |_| Even::Decode);
554            self.extend(R2, R4);
555            self.extend(G2, G7);
556            self.pass(G3, B2, 1, |_| Even::Decode, |_| Even::Interp);
557            self.extend(G2, G7);
558            self.extend(B2, B4);
559            self.pass(R3, G4, 2, rule_a, |_| Even::Interp);
560            self.extend(R2, R4);
561            self.extend(G2, G7);
562            self.pass(G5, B3, 0, |_| Even::Decode, rule_b);
563            self.extend(G2, G7);
564            self.extend(B2, B4);
565            self.pass(R4, G6, 1, rule_b, |_| Even::Decode);
566            self.extend(R2, R4);
567            self.extend(G2, G7);
568            self.pass(G7, B4, 2, |_| Even::Interp, rule_a);
569            self.extend(G2, G7);
570            self.extend(B2, B4);
571        } else {
572            let d = |_: usize| Even::Decode;
573            self.pass(R2, G2, 0, d, d);
574            self.extend(R2, R4);
575            self.extend(G2, G7);
576            self.pass(G3, B2, 1, d, d);
577            self.extend(G2, G7);
578            self.extend(B2, B4);
579            self.pass(R3, G4, 2, d, d);
580            self.extend(R2, R4);
581            self.extend(G2, G7);
582            self.pass(G5, B3, 0, d, d);
583            self.extend(G2, G7);
584            self.extend(B2, B4);
585            self.pass(R4, G6, 1, d, d);
586            self.extend(R2, R4);
587            self.extend(G2, G7);
588            self.pass(G7, B4, 2, d, d);
589            self.extend(G2, G7);
590            self.extend(B2, B4);
591        }
592        // A stripe is padded with a single zero byte and any further read fails:
593        // consuming more than that means the stripe is truncated or corrupt.
594        if self.overrun || self.bits_consumed() > 8 * self.data_len + 7 {
595            return Err(Error::corrupt("truncated Fuji compressed block"));
596        }
597        Ok(())
598    }
599
600    fn bits_consumed(&self) -> usize {
601        // byte_position() excludes whole buffered bytes; the partially consumed byte's
602        // remaining bits are still buffered.
603        (self.bits.byte_position() * 8).saturating_sub((self.bits.buffered_bits() % 8) as usize)
604    }
605
606    /// One coding pass over two colour lines `a` and `b`: even positions of both lines
607    /// (decoded or interpolated per `rule_a`/`rule_b`), with odd positions lagging five
608    /// even steps behind (they need the even neighbours on both sides).
609    #[inline(never)]
610    fn pass(&mut self, a: usize, b: usize, g: usize, rule_a: impl Fn(usize) -> Even, rule_b: impl Fn(usize) -> Even) {
611        let s = self.stride;
612        // Everything the samples touch is held in locals for the pass: the bit pump stays
613        // in registers, and the line windows and statistics are borrowed once.
614        let mut lines = std::mem::take(&mut self.lines);
615        let mut st = Sampler { c: self.c, qt: self.qt, bits: self.bits, overrun: false };
616        let [even, odd] = &mut self.grads;
617        let (ge, go) = (&mut even[g], &mut odd[g]);
618        // `a` lies below `b` in the buffer and their three-line windows do not overlap.
619        let (lo, hi) = lines.split_at_mut((b - 2) * s);
620        let mut la = Lines::new(&mut lo[(a - 2) * s..], s);
621        let mut lb = Lines::new(hi, s);
622        let lw = la.width();
623        let (mut ep, mut op) = (0usize, 1usize);
624        while ep < lw || op < lw {
625            if ep < lw {
626                match rule_a(ep) {
627                    Even::Decode => la.decode_even::<LOSSY>(&mut st, ge, ep),
628                    Even::Interp => la.interp_even(ep),
629                }
630                match rule_b(ep) {
631                    Even::Decode => lb.decode_even::<LOSSY>(&mut st, ge, ep),
632                    Even::Interp => lb.interp_even(ep),
633                }
634                ep += 2;
635            }
636            if ep > 8 {
637                la.decode_odd::<LOSSY>(&mut st, go, op);
638                lb.decode_odd::<LOSSY>(&mut st, go, op);
639                op += 2;
640            }
641        }
642        self.bits = st.bits;
643        self.overrun |= st.overrun;
644        self.lines = lines;
645    }
646
647    /// Copy edge samples from the previous line into the guard positions.
648    fn extend(&mut self, first: usize, last: usize) {
649        let (s, lw) = (self.stride, self.c.line_width);
650        for l in first..=last {
651            self.lines[l * s] = self.lines[(l - 1) * s + 1];
652            self.lines[l * s + lw + 1] = self.lines[(l - 1) * s + lw];
653        }
654    }
655
656    /// Rotate line context after a decoded line: the last two lines of each colour
657    /// become the first two, the rest is cleared.
658    fn advance_line(&mut self) {
659        let s = self.stride;
660        let lw = self.c.line_width;
661        for (dst, src) in [(R0, R3), (R0 + 1, R4), (G0, G6), (G0 + 1, G7), (B0, B3), (B0 + 1, B4)] {
662            self.lines.copy_within(src * s..(src + 1) * s, dst * s);
663        }
664        for (first, n) in [(R2, 3), (G2, 6), (B2, 3)] {
665            self.lines[first * s..(first + n) * s].fill(0);
666            self.lines[first * s] = self.lines[(first - 1) * s + 1];
667            self.lines[first * s + lw + 1] = self.lines[(first - 1) * s + lw];
668        }
669    }
670}
671
672/// Entropy-decoding state of one pass, held in locals.
673struct Sampler<'c, 'a> {
674    c: &'c Common,
675    /// Current main table.
676    qt: &'c QTable,
677    bits: BitPumpMsb<'a>,
678    /// Set when the bit stream ran dry inside a zero run.
679    overrun: bool,
680}
681
682impl Sampler<'_, '_> {
683    /// Count zero bits up to (and consuming) the next one bit.
684    #[inline(always)]
685    fn zero_run(&mut self) -> i32 {
686        let mut n = 0i32;
687        loop {
688            self.bits.fill();
689            let w = self.bits.peek_no_fill(32);
690            if w != 0 {
691                let lz = w.leading_zeros();
692                self.bits.skip_no_fill(lz + 1);
693                return n + lz as i32;
694            }
695            self.bits.skip_no_fill(32);
696            n += 32;
697            if self.bits.exhausted() {
698                self.overrun = true;
699                return n;
700            }
701        }
702    }
703
704    /// Decode one residual and reconstruct the sample from the prediction `interp`.
705    /// `grads` are the statistics of the sample's side (even or odd) and pass.
706    #[inline(always)]
707    fn decode_residual<const LOSSY: bool>(
708        &mut self,
709        grads: &mut Grads,
710        flat: i32,
711        v1: i32,
712        v2: i32,
713        interp: i32,
714    ) -> u16 {
715        let c = self.c;
716        let max = c.max_value;
717        let mut qt = self.qt;
718        let mut ctx = usize::MAX;
719        if LOSSY {
720            // Lossy streams code flat areas with coarser fixed tables.
721            let mut i = 1;
722            while qt.q_base >= i && i < 4 {
723                let t = &c.extra[(i - 1) as usize];
724                if flat <= t.max_grad {
725                    qt = t;
726                    ctx = (i - 1) as usize;
727                    break;
728                }
729                i += 1;
730            }
731        }
732        // The main table always weighs the first difference by 9.
733        let mult = if LOSSY { qt.grad_mult } else { 9 };
734        let grad = mult * qt.level(v1) + qt.level(v2);
735        let gi = grad.unsigned_abs() as usize;
736        let st = if LOSSY && ctx != usize::MAX { grads.lossy[ctx][gi.min(4)] } else { grads.main[gi.min(40)] };
737        let (sum, n, k, mask) = (st.sum, st.n as i32, st.k as u32, st.mask as u32);
738        // Fast path: the unary prefix and the Rice remainder fit in one 32-bit peek.
739        // The refill is unconditional: it is branch-free, while testing the fill level
740        // mispredicts on the irregular code lengths.
741        self.bits.refill_force();
742        let w = self.bits.peek_no_fill(32);
743        let lz = w.leading_zeros();
744        let code = if w != 0 && (lz as i32) < qt.max_zeros && lz + 1 + k <= 32 {
745            let total = lz + 1 + k;
746            // The remainder is the `k` bits after the prefix's terminating one bit.
747            let v = (w >> (32 - total)) & mask;
748            self.bits.skip_no_fill(total);
749            ((lz << k) | v) as i32
750        } else {
751            let sample = self.zero_run();
752            if sample < qt.max_zeros {
753                ((sample as u32) << k | self.bits.get(k)) as i32
754            } else {
755                self.bits.get(qt.raw_bits) as i32 + 1
756            }
757        };
758        // Out-of-range codes are tolerated rather than treated as fatal.
759        // Zig-zag decode (odd codes are negative): branch-free `code & 1 ? -1 - code/2 : code/2`.
760        let code = (code >> 1) ^ -(code & 1);
761        // Halve the statistics when the count reaches MIN_VALUE (branch-free: rare but
762        // irregular).
763        let h = (n == MIN_VALUE) as u32;
764        let (sum, n) = ((sum + code.abs()) >> h, n >> h);
765        let stat =
766            if LOSSY && ctx != usize::MAX { &mut grads.lossy[ctx][gi.min(4)] } else { &mut grads.main[gi.min(40)] };
767        *stat = new_stat(sum, n + 1);
768        // Lossless streams always quantise with base 0 (step 1).
769        let (q_base, step) = if LOSSY { (qt.q_base, 2 * qt.q_base + 1) } else { (0, 1) };
770        // The residual's sign follows the gradient's (branch-free: (grad >> 31) | 1 is ±1).
771        let mut v = interp + ((grad >> 31) | 1) * code * step;
772        if v < -q_base {
773            v += qt.total_values * step;
774        } else if v > q_base + max {
775            v -= qt.total_values * step;
776        }
777        v.clamp(0, max) as u16
778    }
779}
780
781/// The three-line window of one colour line in a pass: the two lines above (read) and
782/// the line being decoded. Each line is `width + 2` samples with one guard on each side.
783struct Lines<'l> {
784    up2: &'l [u16],
785    up1: &'l [u16],
786    cur: &'l mut [u16],
787}
788
789impl<'l> Lines<'l> {
790    /// `block` starts at the first of the three lines; `s` is the line stride.
791    #[inline(always)]
792    fn new(block: &'l mut [u16], s: usize) -> Self {
793        let (up2, rest) = block.split_at_mut(s);
794        let (up1, rest) = rest.split_at_mut(s);
795        Lines { up2, up1, cur: &mut rest[..s] }
796    }
797
798    /// Number of samples per line (positions `0..width`, stored at `1..=width`).
799    #[inline(always)]
800    fn width(&self) -> usize {
801        self.cur.len().saturating_sub(2)
802    }
803
804    /// Even-position neighbours of `pos`: `(rf, rc, rb, rd)`.
805    #[inline(always)]
806    fn even_ctx(&self, pos: usize) -> (i32, i32, i32, i32) {
807        let up = &self.up1[pos..pos + 3];
808        (self.up2[pos + 1] as i32, up[0] as i32, up[1] as i32, up[2] as i32)
809    }
810
811    #[inline(always)]
812    fn interp_even(&mut self, pos: usize) {
813        let (rf, rc, rb, rd) = self.even_ctx(pos);
814        self.cur[pos + 1] = (even_prediction(rf, rc, rb, rd) >> 2) as u16;
815    }
816
817    #[inline(always)]
818    fn decode_even<const LOSSY: bool>(&mut self, st: &mut Sampler, grads: &mut Grads, pos: usize) {
819        let (rf, rc, rb, rd) = self.even_ctx(pos);
820        let interp = even_prediction(rf, rc, rb, rd) >> 2;
821        let flat = (rf - rb).abs() + (rc - rb).abs();
822        self.cur[pos + 1] = st.decode_residual::<LOSSY>(grads, flat, rb - rf, rc - rb, interp);
823    }
824
825    #[inline(always)]
826    fn decode_odd<const LOSSY: bool>(&mut self, st: &mut Sampler, grads: &mut Grads, pos: usize) {
827        let up = &self.up1[pos..pos + 3];
828        let (rc, rb, rd) = (up[0] as i32, up[1] as i32, up[2] as i32);
829        let cur = &mut self.cur[pos..pos + 3];
830        let (ra, rg) = (cur[0] as i32, cur[2] as i32);
831        // `rb` is a local extremum among (rc, rb, rd) exactly when both differences share a
832        // sign (and neither is zero); then the prediction also weighs `rb`.
833        let (x, y) = (rb - rc, rb - rd);
834        let extremum = ((x ^ y) >= 0) & (x != 0) & (y != 0);
835        let e = -(extremum as i32);
836        let interp = (((rg + ra + 2 * rb) >> 2) & e) | (((ra + rg) >> 1) & !e);
837        let flat = (rb - rc).abs() + (rc - ra).abs();
838        cur[1] = st.decode_residual::<LOSSY>(grads, flat, rb - rc, rc - ra, interp);
839    }
840}
841
842/// Edge-directed prediction for even positions (four times the predicted value).
843#[inline(always)]
844fn even_prediction(rf: i32, rc: i32, rb: i32, rd: i32) -> i32 {
845    let (d_cb, d_fb, d_db) = ((rc - rb).abs(), (rf - rb).abs(), (rd - rb).abs());
846    // Drop the neighbour that deviates most from `rb`. Masks instead of selects: the
847    // choice is data-dependent and unpredictable, and branch-free arithmetic keeps
848    // compilers from turning it into branches.
849    let mc = -(((d_cb > d_fb) & (d_cb > d_db)) as i32);
850    let md = -(((d_db > d_cb) & (d_db > d_fb)) as i32) & !mc;
851    let dropped = (rc & mc) | (rd & md) | (rf & !(mc | md));
852    rf + rc + rd - dropped + 2 * rb
853}
854
855#[cfg(test)]
856mod tests {
857    use super::*;
858
859    #[test]
860    fn rice_parameter_matches_reference_loop() {
861        for v1 in [0, 1, 2, 3, 5, 64, 100, 1000, 65535, 1 << 20, i32::MAX / 4] {
862            for v2 in 1..=130 {
863                // Loop form: while (d <= 14 && (v2 << ++d) < v1);
864                let mut d = 0u32;
865                if v2 < v1 {
866                    loop {
867                        if d > 14 {
868                            break;
869                        }
870                        d += 1;
871                        if ((v2 as i64) << d) >= v1 as i64 {
872                            break;
873                        }
874                    }
875                }
876                assert_eq!(bit_diff(v1, v2), d, "v1 {v1} v2 {v2}");
877            }
878        }
879    }
880
881    fn header(lossless: u8, raw_type: u8, bits: u8, h: u16, rw: u16, w: u16, blocks: u8) -> Vec<u8> {
882        let mut v = vec![0x49, 0x53, lossless, raw_type, bits];
883        v.extend(h.to_be_bytes());
884        v.extend(rw.to_be_bytes());
885        v.extend(w.to_be_bytes());
886        v.extend(0x300u16.to_be_bytes());
887        v.push(blocks);
888        v.extend((h / 6).to_be_bytes());
889        v
890    }
891
892    #[test]
893    fn header_validation() {
894        let h = header(1, 16, 14, 4182, 6912, 6384, 9);
895        let p = FujiHeader::parse(&h).unwrap();
896        assert!(p.lossless && p.xtrans);
897        assert_eq!((p.width, p.height, p.blocks, p.lines), (6384, 4182, 9, 697));
898        assert_eq!(p.block_width(8), 6384 - 8 * 768);
899        assert!(FujiHeader::parse(&header(1, 16, 13, 4182, 6912, 6384, 9)).is_err());
900        assert!(FujiHeader::parse(&header(1, 16, 14, 4181, 6912, 6384, 9)).is_err());
901        assert!(FujiHeader::parse(&header(1, 16, 14, 4182, 6912, 6384, 8)).is_err());
902        assert!(FujiHeader::parse(&header(2, 16, 14, 4182, 6912, 6384, 9)).is_err());
903        assert!(FujiHeader::parse(&h[..10]).is_err());
904    }
905
906    #[test]
907    fn qtables() {
908        let t = QTable::main(0, 16383);
909        assert_eq!(t.total_values, 16384);
910        assert_eq!(t.raw_bits, 14);
911        assert_eq!(t.level(0), 0);
912        assert_eq!(t.level(1), 1);
913        assert_eq!(t.level(-1), -1);
914        assert_eq!(t.level(0x12), 2);
915        assert_eq!(t.level(16383), 4);
916        assert_eq!(t.level(-16383), -4);
917        let t = QTable::main(3, 16383);
918        assert_eq!(t.total_values, (16383 + 6) / 7 + 1);
919        let x = QTable::lossy_extra(4095);
920        assert_eq!(x[1].total_values, (4095 + 2) / 3 + 1);
921        assert_eq!(log2ceil(16384), 14);
922        assert_eq!(log2ceil(16385), 15);
923        assert_eq!(log2ceil(1), 0);
924    }
925
926    fn stream(lossless: bool, xtrans: bool, payload: &[u8]) -> Vec<u8> {
927        // Single 768-wide block, 12 rows.
928        let mut v = header(lossless as u8, if xtrans { 16 } else { 0 }, 14, 12, 768, 768, 1);
929        v.extend((payload.len() as u32).to_be_bytes());
930        v.extend([0u8; 12]);
931        if !lossless {
932            v.extend([0u8, 2]);
933            v.extend([0u8; 14]);
934        }
935        v.extend_from_slice(payload);
936        v
937    }
938
939    fn pseudo(n: usize, seed: u64) -> Vec<u8> {
940        let mut x = seed;
941        (0..n)
942            .map(|_| {
943                x ^= x << 13;
944                x ^= x >> 7;
945                x ^= x << 17;
946                (x >> 24) as u8 | 0x11
947            })
948            .collect()
949    }
950
951    const XT: ColorTile = [
952        [1, 1, 0, 1, 1, 2],
953        [1, 1, 2, 1, 1, 0],
954        [2, 0, 1, 0, 2, 1],
955        [1, 1, 2, 1, 1, 0],
956        [1, 1, 0, 1, 1, 2],
957        [0, 2, 1, 2, 0, 1],
958    ];
959
960    const BAYER: ColorTile = [
961        [0, 1, 0, 1, 0, 1],
962        [1, 2, 1, 2, 1, 2],
963        [0, 1, 0, 1, 0, 1],
964        [1, 2, 1, 2, 1, 2],
965        [0, 1, 0, 1, 0, 1],
966        [1, 2, 1, 2, 1, 2],
967    ];
968
969    #[test]
970    fn decodes_synthetic_streams_without_panicking() {
971        for (lossless, xtrans) in [(true, true), (true, false), (false, true), (false, false)] {
972            for seed in 1..6 {
973                let data = stream(lossless, xtrans, &pseudo(200_000, seed));
974                let c = FujiCompressed::new(&data).unwrap();
975                let mut out = vec![0u16; 768 * 12];
976                let tile = if xtrans { XT } else { BAYER };
977                // Random data either decodes (values within range) or reports corruption.
978                if c.decode(&mut out, 768, 12, &tile, false).is_ok() {
979                    assert!(out.iter().all(|&v| v < 1 << 14));
980                }
981            }
982        }
983    }
984
985    #[test]
986    fn region_decode_matches_full_decode() {
987        // Two stripes (1536 wide), 12 rows.
988        let mut v = header(1, 16, 14, 12, 1536, 1536, 2);
989        let payload = vec![0xFFu8; 300_000];
990        v.extend((payload.len() as u32).to_be_bytes());
991        v.extend((payload.len() as u32).to_be_bytes());
992        v.extend([0u8; 8]);
993        v.extend_from_slice(&payload);
994        v.extend_from_slice(&payload);
995        let c = FujiCompressed::new(&v).unwrap();
996        let mut full = vec![0u16; 1536 * 12];
997        c.decode(&mut full, 1536, 12, &XT, true).unwrap();
998        for r in [Rect::new(700, 3, 100, 5), Rect::new(0, 0, 10, 1), Rect::new(800, 6, 736, 6)] {
999            let mut part = vec![0u16; (r.width * r.height) as usize];
1000            c.decode_region(&mut part, r, &XT, true).unwrap();
1001            for y in 0..r.height as usize {
1002                let src = &full[(r.y as usize + y) * 1536 + r.x as usize..][..r.width as usize];
1003                assert_eq!(&part[y * r.width as usize..][..r.width as usize], src, "{r:?} row {y}");
1004            }
1005        }
1006        let mut small = vec![0u16; 4];
1007        assert!(c.decode_region(&mut small, Rect::new(1530, 0, 10, 1), &XT, true).is_err());
1008    }
1009
1010    #[test]
1011    fn truncated_stream_is_an_error() {
1012        let data = stream(true, true, &[0x00; 64]);
1013        let c = FujiCompressed::new(&data).unwrap();
1014        let mut out = vec![0u16; 768 * 12];
1015        assert!(c.decode(&mut out, 768, 12, &XT, true).is_err());
1016        // Missing block table.
1017        assert!(FujiCompressed::new(&data[..18]).is_err());
1018    }
1019
1020    #[test]
1021    fn all_ones_stream_is_deterministic() {
1022        // Every residual reads a zero-length prefix; the result must not depend on threading.
1023        let data = stream(true, true, &vec![0xFF; 400_000]);
1024        let c = FujiCompressed::new(&data).unwrap();
1025        let mut a = vec![0u16; 768 * 12];
1026        let mut b = vec![0u16; 768 * 12];
1027        c.decode(&mut a, 768, 12, &XT, false).unwrap();
1028        c.decode(&mut b, 768, 12, &XT, true).unwrap();
1029        assert_eq!(a, b);
1030    }
1031}