Skip to main content

flashraw_codecs/
hasselblad.rs

1//! Hasselblad lossless JPEG variant (3FR/FFF, Imacon/Flexcolor, H/X/CFV backs).
2//!
3//! The stream starts with ordinary JPEG markers (SOF3, DHT, SOS) but the scan is not
4//! standard lossless JPEG:
5//!
6//! - bits are read MSB-first from 32-bit **little-endian** words, with no `FF 00`
7//!   byte stuffing;
8//! - pixels are coded in pairs: two Huffman length symbols, then the two difference
9//!   values (a length-16 code of all ones means -32768);
10//! - prediction is from the same-colour neighbour two columns to the left, starting
11//!   each row at `0x8000 + base_offset`; predictor 11 additionally adds half the
12//!   horizontal gradient of the row two above.
13//!
14//! The stream is a single sequential bit stream (no restart markers or row index).
15
16use flashraw_core::bits::{BitPumpMsb32, BitRead};
17use flashraw_core::huffman::HuffTable;
18use flashraw_core::{Error, ReadAt, Result};
19
20/// Parsed header of a Hasselblad stream.
21#[derive(Debug, Clone)]
22pub struct HasselbladHeader {
23    pub table: HuffTable,
24    /// Predictor selector from the SOS header (1 or 11 in practice).
25    pub predictor: u32,
26    /// Frame dimensions as declared by SOF3 (informational; the container decides).
27    pub width: u32,
28    pub height: u32,
29    /// Offset of the scan data.
30    pub scan_offset: usize,
31}
32
33impl HasselbladHeader {
34    /// Parse markers up to the start of the scan.
35    pub fn parse(data: &[u8]) -> Result<Self> {
36        if data.len() < 4 || data[0] != 0xFF || data[1] != 0xD8 {
37            return Err(Error::corrupt("Hasselblad: missing SOI"));
38        }
39        let mut pos = 2usize;
40        let mut table: Option<HuffTable> = None;
41        let (mut width, mut height) = (0u32, 0u32);
42        loop {
43            while pos < data.len() && data[pos] != 0xFF {
44                pos += 1;
45            }
46            while pos < data.len() && data[pos] == 0xFF {
47                pos += 1;
48            }
49            let marker = *data.get(pos).ok_or_else(|| Error::corrupt("Hasselblad: truncated header"))?;
50            pos += 1;
51            if marker == 0xD8 || (0xD0..=0xD7).contains(&marker) {
52                continue;
53            }
54            if marker == 0xD9 {
55                return Err(Error::corrupt("Hasselblad: EOI before SOS"));
56            }
57            let len = data.u16be(pos)? as usize;
58            if len < 2 {
59                return Err(Error::corrupt("Hasselblad: invalid segment length"));
60            }
61            let seg = flashraw_core::bytes::slice_at(data, pos + 2, len - 2)?;
62            match marker {
63                0xC3 if seg.len() >= 5 => {
64                    height = seg.u16be(1)? as u32;
65                    width = seg.u16be(3)? as u32;
66                }
67                0xC4 => {
68                    let mut p = 0usize;
69                    while p + 17 <= seg.len() {
70                        let id = seg[p] & 0x0F;
71                        let counts = &seg[p + 1..p + 17];
72                        let total: usize = counts.iter().map(|&c| c as usize).sum();
73                        let vals = flashraw_core::bytes::slice_at(seg, p + 17, total)?;
74                        if id == 0 || table.is_none() {
75                            table = Some(HuffTable::from_counts(counts, vals, false)?);
76                        }
77                        p += 17 + total;
78                    }
79                }
80                0xDA => {
81                    let ns = *seg.first().ok_or_else(|| Error::corrupt("Hasselblad: short SOS"))? as usize;
82                    let predictor = *seg.get(1 + 2 * ns).ok_or_else(|| Error::corrupt("Hasselblad: short SOS"))? as u32;
83                    let table = table.ok_or_else(|| Error::corrupt("Hasselblad: missing Huffman table"))?;
84                    return Ok(HasselbladHeader { table, predictor, width, height, scan_offset: pos + len });
85                }
86                _ => {}
87            }
88            pos += len;
89        }
90    }
91}
92
93/// Read a difference of `len` bits (JPEG-style sign extension, with the
94/// Hasselblad all-ones length-16 escape).
95#[inline(always)]
96fn diff<B: BitRead>(bits: &mut B, len: u32) -> i32 {
97    if len == 0 {
98        return 0;
99    }
100    let mut d = bits.get(len) as i32;
101    if d & (1 << (len - 1)) == 0 {
102        d -= (1 << len) - 1;
103    }
104    if d == 65535 { -32768 } else { d }
105}
106
107/// Decode a single-shot stream into `out` (`width x height`, row-major).
108/// `base` is added to the row-start predictor (0 except for some H4D-60 3FR files).
109pub fn decode(
110    data: &[u8],
111    hdr: &HasselbladHeader,
112    out: &mut [u16],
113    width: usize,
114    height: usize,
115    base: i32,
116) -> Result<()> {
117    if width % 2 != 0 {
118        return Err(Error::unsupported_compression("Hasselblad", "odd raster width"));
119    }
120    if out.len() < width * height {
121        return Err(Error::InvalidArgument("Hasselblad: output buffer too small".into()));
122    }
123    let scan = data.get(hdr.scan_offset..).ok_or_else(|| Error::corrupt("Hasselblad: scan beyond data"))?;
124    let mut bits = BitPumpMsb32::new(scan);
125    let t = &hdr.table;
126    let grad = hdr.predictor == 11;
127    if !grad {
128        return decode_left(&mut bits, t, out, width, height, 0x8000 + base);
129    }
130    // Predictor history: current row and the row two above (same CFA colours).
131    let mut rows: [Vec<i32>; 3] = [vec![0; width], vec![0; width], vec![0; width]];
132    let start = 0x8000 + base;
133    for row in 0..height {
134        rows.rotate_left(1); // rows[2] := buffer of row-3 (reused for the current row)
135        let (prev2, rest) = rows.split_at_mut(1);
136        let up2 = &prev2[0];
137        let cur = &mut rest[1];
138        let dst = &mut out[row * width..(row + 1) * width];
139        for col in (0..width).step_by(2) {
140            let l0 = t.decode_symbol(&mut bits)?;
141            let l1 = t.decode_symbol(&mut bits)?;
142            if l0 > 16 || l1 > 16 {
143                return Err(Error::corrupt("Hasselblad: difference length > 16"));
144            }
145            let d = [diff(&mut bits, l0), diff(&mut bits, l1)];
146            for (k, &dk) in d.iter().enumerate() {
147                let s = col + k;
148                let mut pred = if col > 0 { cur[s - 2] } else { start };
149                if grad && col > 0 && row > 1 {
150                    pred += up2[s] / 2 - up2[s - 2] / 2;
151                }
152                pred = pred.wrapping_add(dk);
153                dst[s] = pred as u16;
154                cur[s] = pred;
155            }
156        }
157        if bits.exhausted() {
158            return Err(Error::corrupt(format!("Hasselblad: data ends at row {row}/{height}")));
159        }
160    }
161    Ok(())
162}
163
164/// [`decode`] that entropy-decodes large predictor-1 streams on all threads
165/// (`par_entropy`) when `parallel` is set. Output and errors are identical to
166/// [`decode`]: anything speculation cannot prove takes the sequential path.
167pub fn decode_par(
168    data: &[u8],
169    hdr: &HasselbladHeader,
170    out: &mut [u16],
171    width: usize,
172    height: usize,
173    base: i32,
174    parallel: bool,
175) -> Result<()> {
176    if parallel
177        && hdr.predictor != 11
178        && width % 2 == 0
179        && width > 0
180        && out.len() >= width * height
181        && width * height >= crate::ljpeg_par::PARALLEL_MIN_SAMPLES
182        && decode_parallel(data, hdr, &mut out[..width * height], width, base, None)
183    {
184        return Ok(());
185    }
186    decode(data, hdr, out, width, height, base)
187}
188
189/// The scan as a stream of pixel pairs (two length symbols, then two differences).
190struct PairScan<'a> {
191    scan: &'a [u8],
192    table: &'a HuffTable,
193}
194
195impl crate::par_entropy::SymbolStream for PairScan<'_> {
196    type Pump<'p>
197        = BitPumpMsb32<'p>
198    where
199        Self: 'p;
200    const WIDTH: usize = 2;
201    fn cycle(&self) -> usize {
202        1
203    }
204    fn period(&self) -> usize {
205        1
206    }
207    fn total_bits(&self) -> u64 {
208        self.scan.len() as u64 * 8
209    }
210    fn end_check(&self, pos: u64) -> crate::par_entropy::EndCheck {
211        use crate::par_entropy::EndCheck;
212        // The sequential decoder rejects a row once more than 8 bytes of padding were
213        // fetched; that never happens before the data is consumed, and always happens
214        // 8 bytes after. In between it depends on the refill history.
215        let end = self.total_bits();
216        if pos <= end {
217            EndCheck::Valid
218        } else if pos <= end + 64 {
219            EndCheck::Unknown
220        } else {
221            EndCheck::Invalid
222        }
223    }
224    fn pump_at(&self, bit: u64) -> BitPumpMsb32<'_> {
225        BitPumpMsb32::at_bit(self.scan, bit)
226    }
227    fn position(p: &BitPumpMsb32<'_>) -> u64 {
228        p.bit_position()
229    }
230    #[inline(always)]
231    fn decode(&self, p: &mut BitPumpMsb32<'_>, _phase: usize, out: &mut [u16]) -> bool {
232        let before = p.bit_position();
233        let lens = (self.table.decode_symbol(p), self.table.decode_symbol(p));
234        let (Ok(l0), Ok(l1)) = lens else {
235            if p.bit_position() == before {
236                p.skip(1);
237            }
238            return false;
239        };
240        if l0 > 16 || l1 > 16 {
241            return false;
242        }
243        out[0] = diff(p, l0) as u16;
244        out[1] = diff(p, l1) as u16;
245        true
246    }
247}
248
249/// Parallel predictor-1 decode: pairs are entropy-decoded speculatively, then every row
250/// (which restarts from `0x8000 + base`) is rebuilt independently.
251fn decode_parallel(
252    data: &[u8],
253    hdr: &HasselbladHeader,
254    out: &mut [u16],
255    width: usize,
256    base: i32,
257    chunk: Option<usize>,
258) -> bool {
259    let Some(scan) = data.get(hdr.scan_offset..) else { return false };
260    let Some(chunk) = chunk.or_else(|| crate::par_entropy::default_chunk(scan.len())) else { return false };
261    let src = PairScan { scan, table: &hdr.table };
262    let Some(values) = crate::par_entropy::decode(&src, out.len() / 2, chunk) else { return false };
263    let start = (0x8000 + base) as u16;
264    flashraw_core::par::for_each_row_chunk(out, width, 16, true, |r0, rows| {
265        for (i, row) in rows.chunks_exact_mut(width).enumerate() {
266            values.copy_to((r0 + i) * width, row);
267            let (mut p0, mut p1) = (start, start);
268            for px in row.chunks_exact_mut(2) {
269                p0 = p0.wrapping_add(px[0]);
270                p1 = p1.wrapping_add(px[1]);
271                px[0] = p0;
272                px[1] = p1;
273            }
274        }
275    });
276    true
277}
278
279/// Fast path for the plain left predictor: the two running predictions live in
280/// registers and no row history is kept.
281fn decode_left(
282    bits: &mut BitPumpMsb32,
283    t: &HuffTable,
284    out: &mut [u16],
285    width: usize,
286    height: usize,
287    start: i32,
288) -> Result<()> {
289    for (row, dst) in out.chunks_exact_mut(width).take(height).enumerate() {
290        let (mut p0, mut p1) = (start, start);
291        for px in dst.chunks_exact_mut(2) {
292            let l0 = t.decode_symbol(bits)?;
293            let l1 = t.decode_symbol(bits)?;
294            if l0 > 16 || l1 > 16 {
295                return Err(Error::corrupt("Hasselblad: difference length > 16"));
296            }
297            p0 = p0.wrapping_add(diff(bits, l0));
298            p1 = p1.wrapping_add(diff(bits, l1));
299            px[0] = p0 as u16;
300            px[1] = p1 as u16;
301        }
302        if bits.exhausted() {
303            return Err(Error::corrupt(format!("Hasselblad: data ends at row {row}/{height}")));
304        }
305    }
306    Ok(())
307}
308
309#[cfg(test)]
310mod tests {
311    use super::*;
312
313    /// Minimal encoder for predictor 1 (two lengths, two diffs per pixel pair).
314    fn encode(width: usize, height: usize, samples: &[u16]) -> Vec<u8> {
315        // Table: lengths 0..=16, codes of length 5 for all 17 symbols (fits in 32 codes).
316        let counts: [u8; 16] = [0, 0, 0, 0, 17, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
317        let values: Vec<u8> = (0..=16).collect();
318        let mut out = vec![0xFF, 0xD8];
319        out.extend([
320            0xFF,
321            0xC3,
322            0,
323            11,
324            16,
325            (height >> 8) as u8,
326            height as u8,
327            (width >> 8) as u8,
328            width as u8,
329            1,
330            1,
331            0x11,
332            0,
333        ]);
334        out.extend([0xFF, 0xC4, 0, (2 + 17 + values.len()) as u8, 0x00]);
335        out.extend(counts);
336        out.extend(&values);
337        out.extend([0xFF, 0xDA, 0, 8, 1, 1, 0, 1, 0, 0]);
338        let mut words: Vec<u32> = vec![];
339        let (mut acc, mut n) = (0u64, 0u32);
340        let mut put = |v: u32, b: u32, words: &mut Vec<u32>| {
341            if b == 0 {
342                return;
343            }
344            acc = (acc << b) | (v as u64 & ((1u64 << b) - 1));
345            n += b;
346            while n >= 32 {
347                words.push((acc >> (n - 32)) as u32);
348                n -= 32;
349            }
350        };
351        for row in 0..height {
352            for col in (0..width).step_by(2) {
353                let mut ds = [0i32; 2];
354                for (k, dk) in ds.iter_mut().enumerate() {
355                    let s = col + k;
356                    let pred = if col > 0 { samples[row * width + s - 2] as i32 } else { 0x8000 };
357                    *dk = samples[row * width + s] as i32 - pred;
358                }
359                let lens: Vec<u32> =
360                    ds.iter().map(|&d| if d == 0 { 0 } else { 32 - (d.unsigned_abs()).leading_zeros() }).collect();
361                for &l in &lens {
362                    put(l, 5, &mut words); // symbol l has code l (5 bits)
363                }
364                for k in 0..2 {
365                    let (d, l) = (ds[k], lens[k]);
366                    let v = if d >= 0 { d as u32 } else { (d + (1 << l) - 1) as u32 };
367                    put(v, l, &mut words);
368                }
369            }
370        }
371        put(0, 31, &mut words);
372        put(0, 32, &mut words);
373        for w in words {
374            out.extend(w.to_le_bytes());
375        }
376        out
377    }
378
379    #[test]
380    fn round_trip() {
381        let (w, h) = (16usize, 5usize);
382        let samples: Vec<u16> = (0..w * h).map(|i| ((i * 2654435761usize) >> 7) as u16 & 0x3FFF).collect();
383        let data = encode(w, h, &samples);
384        let hdr = HasselbladHeader::parse(&data).unwrap();
385        assert_eq!((hdr.width, hdr.height, hdr.predictor), (w as u32, h as u32, 1));
386        let mut out = vec![0u16; w * h];
387        decode(&data, &hdr, &mut out, w, h, 0).unwrap();
388        assert_eq!(out, samples);
389    }
390
391    #[test]
392    fn truncated_and_garbage() {
393        let (w, h) = (16usize, 40usize);
394        let samples: Vec<u16> = (0..w * h).map(|i| (i * 977 % 60000) as u16).collect();
395        let data = encode(w, h, &samples);
396        let hdr = HasselbladHeader::parse(&data).unwrap();
397        let mut out = vec![0u16; w * h];
398        assert!(decode(&data[..hdr.scan_offset + 20], &hdr, &mut out, w, h, 0).is_err());
399        for n in 0..hdr.scan_offset {
400            let _ = HasselbladHeader::parse(&data[..n]);
401        }
402        let mut g = data.clone();
403        for b in g[hdr.scan_offset..].iter_mut() {
404            *b = b.wrapping_mul(31).wrapping_add(7);
405        }
406        let _ = decode(&g, &hdr, &mut out, w, h, 0);
407    }
408
409    #[test]
410    fn parallel_matches_sequential() {
411        let (w, h) = (64usize, 40usize);
412        let mut x = 7u32;
413        let samples: Vec<u16> = (0..w * h)
414            .map(|i| {
415                x = x.wrapping_mul(1103515245).wrapping_add(12345);
416                (0x8000 + (i % w) as i32 * 3 + ((x >> 20) & 63) as i32) as u16
417            })
418            .collect();
419        let data = encode(w, h, &samples);
420        let hdr = HasselbladHeader::parse(&data).unwrap();
421        let mut seq = vec![0u16; w * h];
422        decode(&data, &hdr, &mut seq, w, h, 0).unwrap();
423        for chunk in [5usize, 37, 200, 1 << 20] {
424            let mut par = vec![0u16; w * h];
425            assert!(decode_parallel(&data, &hdr, &mut par, w, 0, Some(chunk)), "chunk {chunk}");
426            assert_eq!(par, seq, "chunk {chunk}");
427        }
428    }
429}