Skip to main content

flashraw_codecs/
canon_cr2.rs

1//! Canon CR2 lossless-JPEG payload with vertical slicing.
2//!
3//! CR2 bodies encode the sensor as a single lossless-JPEG stream but feed the
4//! encoder in vertical strips ("slices"): the decoded sample sequence fills slice 0
5//! top to bottom (each slice row is `slice_width` samples), then slice 1, and so on;
6//! the last slice may have a different width. The slice layout is stored in TIFF
7//! tag `0xC640` as `[count, width, last_width]` where `count` counts the full-width
8//! slices preceding the last one. Without the tag (or with `count == 0`) the samples
9//! simply fill the image row by row.
10//!
11//! Large frames are entropy-decoded on all threads ([`ljpeg::decode_frame_parallel`])
12//! into a frame buffer whose rows are then copied to their slice positions in
13//! parallel. Otherwise the decoder streams JPEG rows straight into the output image,
14//! splitting every row into runs copied to their final position.
15
16use crate::ljpeg::{self, LjpegHeader};
17use flashraw_core::{Error, Result};
18
19/// Slice layout from tag 0xC640.
20#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
21pub struct Slices {
22    pub count: u32,
23    pub width: u32,
24    pub last_width: u32,
25}
26
27impl Slices {
28    pub fn from_tag(v: &[u32]) -> Slices {
29        Slices {
30            count: v.first().copied().unwrap_or(0),
31            width: v.get(1).copied().unwrap_or(0),
32            last_width: v.get(2).copied().unwrap_or(0),
33        }
34    }
35    pub fn is_sliced(&self) -> bool {
36        self.count != 0
37    }
38}
39
40/// Where the decoded sample stream goes.
41struct Placement {
42    raw_width: usize,
43    raw_height: usize,
44    /// Slice widths in order (a single slice of `raw_width` when unsliced).
45    widths: Vec<usize>,
46    /// First column of each slice.
47    starts: Vec<usize>,
48    slice: usize,
49    row: usize,
50    col: usize,
51}
52
53impl Placement {
54    fn new(raw_width: usize, raw_height: usize, s: Slices) -> Result<Placement> {
55        let (widths, starts) = if s.is_sliced() {
56            if s.width == 0 || s.last_width == 0 {
57                return Err(Error::corrupt("CR2: zero slice width"));
58            }
59            if s.count > 64 * 1024 {
60                return Err(Error::corrupt("CR2: implausible slice count"));
61            }
62            let mut w = vec![s.width as usize; s.count as usize];
63            w.push(s.last_width as usize);
64            let starts = (0..w.len()).map(|i| i * s.width as usize).collect();
65            (w, starts)
66        } else {
67            (vec![raw_width], vec![0])
68        };
69        Ok(Placement { raw_width, raw_height, widths, starts, slice: 0, row: 0, col: 0 })
70    }
71
72    /// Copy `src` (consecutive stream samples) into `out`.
73    #[inline]
74    fn put(&mut self, mut src: &[u16], out: &mut [u16]) -> Result<()> {
75        while !src.is_empty() {
76            // Samples past the end keep following the last slice's geometry below the
77            // image; the stream is rejected once they run more than a row past the
78            // bottom.
79            if self.row > self.raw_height {
80                return Err(Error::corrupt("CR2: more samples than the slice layout holds"));
81            }
82            let sw = self.widths[self.slice];
83            let take = (sw - self.col).min(src.len());
84            let x = self.starts[self.slice] + self.col;
85            if self.row < self.raw_height && x < self.raw_width {
86                let n = take.min(self.raw_width - x);
87                let o = self.row * self.raw_width + x;
88                out[o..o + n].copy_from_slice(&src[..n]);
89            }
90            src = &src[take..];
91            self.col += take;
92            if self.col == sw {
93                self.col = 0;
94                self.row += 1;
95                if self.row == self.raw_height && self.slice + 1 < self.widths.len() {
96                    self.row = 0;
97                    self.slice += 1;
98                }
99            }
100        }
101        Ok(())
102    }
103}
104
105/// Decode the frame with [`ljpeg::decode_frame_parallel`] and place it. Returns
106/// `Ok(false)` when the stream has to be decoded sequentially.
107fn decode_parallel(
108    data: &[u8],
109    hdr: &LjpegHeader,
110    place: &Placement,
111    out: &mut [u16],
112    chunk: Option<usize>,
113) -> Result<bool> {
114    let frame_len = hdr.row_len() * hdr.height as usize;
115    if place.widths.len() == 1 && frame_len <= out.len() {
116        // Unsliced: the sample stream is the image in raster order.
117        return crate::ljpeg_par::decode_frame_chunked(data, hdr, hdr.height as usize, &mut out[..frame_len], chunk);
118    }
119    let mut frame = vec![0u16; frame_len];
120    if !crate::ljpeg_par::decode_frame_chunked(data, hdr, hdr.height as usize, &mut frame, chunk)? {
121        return Ok(false);
122    }
123    let (w, h) = (place.raw_width, place.raw_height);
124    let workers = flashraw_core::par::memory_bound_workers();
125    flashraw_core::par::for_each_row_chunk_bounded(out, w, workers, true, |y0, rows| {
126        for (dy, row) in rows.chunks_exact_mut(w).enumerate() {
127            let y = y0 + dy;
128            let mut base = 0usize;
129            for (&sw, &x0) in place.widths.iter().zip(&place.starts) {
130                let src = base + y * sw;
131                base += sw * h;
132                if x0 >= w || src >= frame.len() {
133                    continue;
134                }
135                let n = sw.min(w - x0).min(frame.len() - src);
136                row[x0..x0 + n].copy_from_slice(&frame[src..src + n]);
137            }
138        }
139    });
140    Ok(true)
141}
142
143/// Decode a CR2 lossless-JPEG stream into `out` (`raw_width * raw_height` samples).
144///
145/// `shift_left_2` reproduces the EOS-1D Mark III quirk where the stream is offset by
146/// two samples (every sample lands two positions earlier in raster order).
147#[allow(clippy::too_many_arguments)]
148pub fn decode(
149    data: &[u8],
150    hdr: &LjpegHeader,
151    raw_width: usize,
152    raw_height: usize,
153    slices: Slices,
154    shift_left_2: bool,
155    parallel: bool,
156    out: &mut [u16],
157) -> Result<()> {
158    let total = raw_width.checked_mul(raw_height).ok_or_else(|| Error::corrupt("CR2: size overflow"))?;
159    if out.len() < total || total == 0 {
160        return Err(Error::corrupt("CR2: output buffer too small"));
161    }
162    let out = &mut out[..total];
163    let mut place = Placement::new(raw_width, raw_height, slices)?;
164    if parallel
165        && total >= crate::ljpeg_par::LJPEG_PARALLEL_MIN_SAMPLES
166        && decode_parallel(data, hdr, &place, out, None)?
167    {
168        if shift_left_2 && total > 2 {
169            out.copy_within(2.., 0);
170            out[total - 2..].fill(0);
171        }
172        return Ok(());
173    }
174    let mut overflow = false;
175    ljpeg::decode_rows(data, hdr, |_, row| {
176        if overflow {
177            return Ok(());
178        }
179        if place.put(row, out).is_err() {
180            // Extra rows beyond the layout are ignored as long as they would land
181            // below the image (only rows far past the end are rejected).
182            overflow = true;
183        }
184        Ok(())
185    })?;
186    if shift_left_2 && total > 2 {
187        out.copy_within(2.., 0);
188        out[total - 2..].fill(0);
189    }
190    Ok(())
191}
192
193#[cfg(test)]
194mod tests {
195    use super::*;
196    use crate::ljpeg::encoder::encode;
197
198    /// Reference placement: the straightforward per-sample slice formula.
199    fn reference(stream: &[u16], w: usize, h: usize, s: Slices) -> Vec<u16> {
200        let mut out = vec![0u16; w * h];
201        let (mut row, mut col) = (0usize, 0usize);
202        for (jidx, &v) in stream.iter().enumerate() {
203            if s.is_sliced() {
204                let per = s.width as usize * h;
205                let mut i = jidx / per;
206                let last = i >= s.count as usize;
207                if last {
208                    i = s.count as usize;
209                }
210                let sw = if last { s.last_width } else { s.width } as usize;
211                let k = jidx - i * per;
212                row = k / sw;
213                col = k % sw + i * s.width as usize;
214            }
215            if row < h && col < w {
216                out[row * w + col] = v;
217            }
218            col += 1;
219            if col >= w {
220                col = 0;
221                row += 1;
222            }
223        }
224        out
225    }
226
227    fn pseudo(n: usize) -> Vec<u16> {
228        let mut x = 12345u32;
229        (0..n)
230            .map(|_| {
231                x = x.wrapping_mul(1103515245).wrapping_add(12345);
232                ((x >> 8) & 0x3fff) as u16
233            })
234            .collect()
235    }
236
237    #[test]
238    fn slices_match_layout_formula() {
239        // 14 x 6 image, JPEG frame of 2 components x 7 wide x 6 high; slices 2 x 5 + 4.
240        let (w, h) = (14usize, 6usize);
241        let samples = pseudo(w * h);
242        let jpg = encode(7, 6, 2, 14, 1, &samples);
243        let hdr = LjpegHeader::parse(&jpg, Default::default()).unwrap();
244        for s in [
245            Slices { count: 2, width: 5, last_width: 4 },
246            Slices { count: 1, width: 7, last_width: 7 },
247            Slices { count: 0, width: 0, last_width: 14 },
248        ] {
249            let mut out = vec![0u16; w * h];
250            decode(&jpg, &hdr, w, h, s, false, false, &mut out).unwrap();
251            let place = Placement::new(w, h, s).unwrap();
252            let mut par = vec![0u16; w * h];
253            assert!(decode_parallel(&jpg, &hdr, &place, &mut par, Some(9)).unwrap());
254            assert_eq!(par, out, "parallel {s:?}");
255            assert_eq!(out, reference(&samples, w, h, s), "{s:?}");
256        }
257    }
258
259    #[test]
260    fn shift_quirk() {
261        let (w, h) = (8usize, 2usize);
262        let samples = pseudo(w * h);
263        let jpg = encode(8, 2, 1, 12, 1, &samples);
264        let hdr = LjpegHeader::parse(&jpg, Default::default()).unwrap();
265        let mut out = vec![0u16; w * h];
266        decode(&jpg, &hdr, w, h, Slices::default(), true, false, &mut out).unwrap();
267        assert_eq!(&out[..w * h - 2], &samples[2..]);
268        assert_eq!(&out[w * h - 2..], &[0, 0]);
269    }
270
271    #[test]
272    fn bad_slices_rejected() {
273        assert!(Placement::new(10, 10, Slices { count: 1, width: 0, last_width: 3 }).is_err());
274        let mut p = Placement::new(4, 1, Slices { count: 1, width: 2, last_width: 2 }).unwrap();
275        let mut out = vec![0u16; 4];
276        p.put(&[1, 2, 3, 4], &mut out).unwrap();
277        assert_eq!(out, vec![1, 2, 3, 4]);
278        p.put(&[5, 6], &mut out).unwrap(); // one row below the image: dropped
279        assert!(p.put(&[7, 8, 9], &mut out).is_err());
280        assert_eq!(out, vec![1, 2, 3, 4]);
281    }
282}