Skip to main content

flashraw_codecs/
vc5.rs

1//! SMPTE ST 2073 (VC-5) wavelet codec as used by GoPro `.GPR` files (DNG compression 9).
2//!
3//! A GPR tile is one VC-5 bitstream ("VC-5" magic, big-endian 16-bit tag/value
4//! pairs) describing a Bayer image as four half-resolution channels (G sum, R−G,
5//! B−G, G difference). Each channel is a 3-level 2-D wavelet: one 16-bit lowpass
6//! band for the coarsest level plus three run-length/VLC coded, quantised and
7//! companded highpass bands per level. Reconstruction runs the vertical then
8//! horizontal inverse transform per level (with the per-level prescale), clamps the
9//! finest level to 14 bits, then recombines the four channels into the Bayer mosaic
10//! through the VC-5 inverse-log curve rescaled to the DNG white level.
11//!
12//! The 264-entry run/value code book (table 17 of the standard) is GoPro's
13//! published table (`gopro/gpr`, Apache-2.0 OR MIT).
14//!
15//! Parallelism: the 40 coded bands decode concurrently, channels reconstruct
16//! concurrently and the final recombination is row-parallel.
17
18use crate::x3f::PrefixCode;
19use flashraw_core::bits::{BitPumpMsb, BitRead};
20use flashraw_core::{Error, Result};
21
22/// Bayer phase of the output mosaic (colours of the top-left 2x2 block).
23#[derive(Debug, Clone, Copy, PartialEq, Eq)]
24pub enum CfaPhase {
25    Rggb,
26    Grbg,
27    Gbrg,
28    Bggr,
29}
30
31impl CfaPhase {
32    /// The phase of a 2x2 Bayer pattern anchored at the image origin (None for other CFAs).
33    pub fn from_pattern(p: &flashraw_core::CfaPattern) -> Option<Self> {
34        use flashraw_core::CfaColor::{Blue, Green, Red};
35        if !p.is_bayer() {
36            return None;
37        }
38        Some(match (p.color_at(0, 0), p.color_at(1, 0)) {
39            (Red, Green) => CfaPhase::Rggb,
40            (Green, Red) => CfaPhase::Grbg,
41            (Green, Blue) => CfaPhase::Gbrg,
42            (Blue, Green) => CfaPhase::Bggr,
43            _ => return None,
44        })
45    }
46}
47
48const NUM_CHANNELS: usize = 4;
49const NUM_LEVELS: usize = 3;
50const NUM_SUBBANDS: usize = 1 + 3 * NUM_LEVELS;
51const RUN_BITS: u32 = 9;
52const LOG_TABLE_BITS: u32 = 12;
53const MARKER_BAND_END: i16 = 1;
54
55/// Table 17: `(code length, code bits, run count, magnitude)`.
56#[rustfmt::skip]
57const TABLE17: [(u8, u32, u16, u16); 264] = [
58    (1, 0x0, 1, 0), (2, 0x2, 1, 1), (3, 0x7, 1, 2), (5, 0x19, 1, 3), (6, 0x30, 1, 4), (6, 0x36, 1, 5),
59    (7, 0x6F, 1, 8), (7, 0x63, 1, 6), (7, 0x69, 12, 0), (7, 0x6B, 1, 7), (8, 0xD1, 20, 0), (8, 0xD4, 1, 9),
60    (8, 0xDC, 1, 10), (9, 0x189, 1, 11), (9, 0x18A, 32, 0), (9, 0x1A0, 1, 12), (9, 0x1AB, 1, 13), (10, 0x377, 1, 18),
61    (10, 0x310, 1, 14), (10, 0x316, 1, 15), (10, 0x343, 60, 0), (10, 0x354, 1, 16), (10, 0x375, 1, 17),
62    (11, 0x623, 1, 19), (11, 0x684, 1, 20), (11, 0x685, 100, 0), (11, 0x6AB, 1, 21), (11, 0x6EC, 1, 22),
63    (12, 0xDDB, 1, 29), (12, 0xC5C, 1, 24), (12, 0xC5E, 1, 25), (12, 0xC44, 1, 23), (12, 0xD55, 1, 26),
64    (12, 0xDD1, 1, 27), (12, 0xDD3, 1, 28), (13, 0x1BB5, 1, 35), (13, 0x188B, 1, 30), (13, 0x18BB, 1, 31),
65    (13, 0x18BF, 180, 0), (13, 0x1AA8, 1, 32), (13, 0x1BA0, 1, 33), (13, 0x1BA5, 320, 0), (13, 0x1BA4, 1, 34),
66    (14, 0x3115, 1, 36), (14, 0x3175, 1, 37), (14, 0x317D, 1, 38), (14, 0x3553, 1, 39), (14, 0x3768, 1, 40),
67    (15, 0x6E87, 1, 46), (15, 0x6ED3, 1, 47), (15, 0x62E8, 1, 42), (15, 0x62F8, 1, 43), (15, 0x6228, 1, 41),
68    (15, 0x6AA4, 1, 44), (15, 0x6E85, 1, 45), (16, 0xC453, 1, 48), (16, 0xC5D3, 1, 49), (16, 0xC5F3, 1, 50),
69    (16, 0xDDA4, 1, 53), (16, 0xDD08, 1, 51), (16, 0xDD0C, 1, 52), (17, 0x1BB4B, 1, 61), (17, 0x1BB4A, 1, 60),
70    (17, 0x18BA5, 1, 55), (17, 0x18BE5, 1, 56), (17, 0x1AA95, 1, 57), (17, 0x1AA97, 1, 58), (17, 0x188A4, 1, 54),
71    (17, 0x1BA13, 1, 59), (18, 0x31748, 1, 62), (18, 0x317C8, 1, 63), (18, 0x35528, 1, 64), (18, 0x3552C, 1, 65),
72    (18, 0x37424, 1, 66), (18, 0x37434, 1, 67), (18, 0x37436, 1, 68), (19, 0x62294, 1, 69), (19, 0x62E92, 1, 70),
73    (19, 0x62F92, 1, 71), (19, 0x6AA52, 1, 72), (19, 0x6AA5A, 1, 73), (19, 0x6E86A, 1, 75), (19, 0x6E86E, 1, 76),
74    (19, 0x6E84A, 1, 74), (20, 0xC452A, 1, 77), (20, 0xC5D27, 1, 78), (20, 0xC5F26, 1, 79), (20, 0xD54A6, 1, 80),
75    (20, 0xD54B6, 1, 81), (20, 0xDD096, 1, 82), (20, 0xDD0D6, 1, 83), (20, 0xDD0DE, 1, 84), (21, 0x188A56, 1, 85),
76    (21, 0x18BA4D, 1, 86), (21, 0x18BE4E, 1, 87), (21, 0x18BE4F, 1, 88), (21, 0x1AA96E, 1, 89),
77    (21, 0x1BA12E, 1, 90), (21, 0x1BA12F, 1, 91), (21, 0x1BA1AF, 1, 92), (21, 0x1BA1BF, 1, 93),
78    (22, 0x37435D, 1, 99), (22, 0x37437D, 1, 100), (22, 0x317498, 1, 94), (22, 0x35529C, 1, 95),
79    (22, 0x35529D, 1, 96), (22, 0x3552DE, 1, 97), (22, 0x3552DF, 1, 98), (23, 0x62E933, 1, 102),
80    (23, 0x62295D, 1, 101), (23, 0x6AA53D, 1, 103), (23, 0x6AA53F, 1, 105), (23, 0x6AA53E, 1, 104),
81    (23, 0x6E86B9, 1, 106), (23, 0x6E86F8, 1, 107), (24, 0xD54A79, 1, 111), (24, 0xC5D265, 1, 109),
82    (24, 0xC452B8, 1, 108), (24, 0xDD0D71, 1, 113), (24, 0xD54A78, 1, 110), (24, 0xDD0D70, 1, 112),
83    (24, 0xDD0DF2, 1, 114), (24, 0xDD0DF3, 1, 115), (25, 0x188A5F6, 1, 225), (25, 0x188A5F5, 1, 189),
84    (25, 0x188A5F4, 1, 188), (25, 0x188A5F3, 1, 203), (25, 0x188A5F2, 1, 202), (25, 0x188A5F1, 1, 197),
85    (25, 0x188A5F0, 1, 207), (25, 0x188A5EF, 1, 169), (25, 0x188A5EE, 1, 223), (25, 0x188A5ED, 1, 159),
86    (25, 0x188A5AA, 1, 235), (25, 0x188A5E3, 1, 152), (25, 0x188A5DF, 1, 192), (25, 0x188A589, 1, 179),
87    (25, 0x188A5DD, 1, 201), (25, 0x188A578, 1, 172), (25, 0x188A5E0, 1, 149), (25, 0x188A588, 1, 178),
88    (25, 0x188A5D6, 1, 120), (25, 0x188A5DB, 1, 219), (25, 0x188A5E1, 1, 150), (25, 0x188A587, 1, 127),
89    (25, 0x188A59A, 1, 211), (25, 0x188A5C4, 1, 125), (25, 0x188A5EC, 1, 158), (25, 0x188A586, 1, 247),
90    (25, 0x188A573, 1, 238), (25, 0x188A59C, 1, 163), (25, 0x188A5C8, 1, 228), (25, 0x188A5FB, 1, 183),
91    (25, 0x188A5A1, 1, 217), (25, 0x188A5EB, 1, 168), (25, 0x188A5A8, 1, 122), (25, 0x188A584, 1, 128),
92    (25, 0x188A5D2, 1, 249), (25, 0x188A599, 1, 187), (25, 0x188A598, 1, 186), (25, 0x188A583, 1, 136),
93    (25, 0x18BA4C9, 1, 181), (25, 0x188A5D0, 1, 255), (25, 0x188A594, 1, 230), (25, 0x188A582, 1, 135),
94    (25, 0x188A5CB, 1, 233), (25, 0x188A5D8, 1, 222), (25, 0x188A5E7, 1, 145), (25, 0x188A581, 1, 134),
95    (25, 0x188A5EA, 1, 167), (25, 0x188A5A9, 1, 248), (25, 0x188A5A6, 1, 209), (25, 0x188A580, 1, 243),
96    (25, 0x188A5A0, 1, 216), (25, 0x188A59D, 1, 164), (25, 0x188A5C3, 1, 140), (25, 0x188A57F, 1, 157),
97    (25, 0x188A5C0, 1, 239), (25, 0x188A5DE, 1, 191), (25, 0x188A5D4, 1, 251), (25, 0x188A57E, 1, 156),
98    (25, 0x188A5C2, 1, 139), (25, 0x188A592, 1, 242), (25, 0x188A5CD, 1, 133), (25, 0x188A57D, 1, 162),
99    (25, 0x188A5A3, 1, 213), (25, 0x188A5E8, 1, 165), (25, 0x188A5A2, 1, 212), (25, 0x188A57C, 1, 227),
100    (25, 0x188A58E, 1, 198), (25, 0x188A5B3, 1, 236), (25, 0x188A5B2, 1, 234), (25, 0x188A5B1, 1, 117),
101    (25, 0x188A5B0, 1, 215), (25, 0x188A5AF, 1, 124), (25, 0x188A5AE, 1, 123), (25, 0x188A5AD, 1, 254),
102    (25, 0x188A5AC, 1, 253), (25, 0x188A5AB, 1, 148), (25, 0x188A5DA, 1, 218), (25, 0x188A5E4, 1, 146),
103    (25, 0x188A5E5, 1, 147), (25, 0x188A5D9, 1, 224), (25, 0x188A5B5, 1, 143), (25, 0x188A5BC, 1, 184),
104    (25, 0x188A5BD, 1, 185), (25, 0x188A5E9, 1, 166), (25, 0x188A5CC, 1, 132), (25, 0x188A585, 1, 129),
105    (25, 0x188A5D3, 1, 250), (25, 0x188A5E2, 1, 151), (25, 0x188A595, 1, 119), (25, 0x188A596, 1, 193),
106    (25, 0x188A5B8, 1, 176), (25, 0x188A590, 1, 245), (25, 0x188A5C9, 1, 229), (25, 0x188A5A4, 1, 206),
107    (25, 0x188A5E6, 1, 144), (25, 0x188A5A5, 1, 208), (25, 0x188A5CE, 1, 137), (25, 0x188A5BF, 1, 241),
108    (25, 0x188A572, 1, 237), (25, 0x188A59B, 1, 190), (25, 0x188A5BE, 1, 240), (25, 0x188A5C7, 1, 131),
109    (25, 0x188A5CA, 1, 232), (25, 0x188A5D5, 1, 252), (25, 0x188A57B, 1, 171), (25, 0x188A58D, 1, 205),
110    (25, 0x188A58C, 1, 204), (25, 0x188A58B, 1, 118), (25, 0x188A58A, 1, 214), (25, 0x18BA4C8, 1, 180),
111    (25, 0x188A5C5, 1, 126), (25, 0x188A5FA, 1, 182), (25, 0x188A5BB, 1, 175), (25, 0x188A5C1, 1, 141),
112    (25, 0x188A5CF, 1, 138), (25, 0x188A5B9, 1, 177), (25, 0x188A5B6, 1, 153), (25, 0x188A597, 1, 194),
113    (25, 0x188A5FE, 1, 160), (25, 0x188A5D7, 1, 121), (25, 0x188A5BA, 1, 174), (25, 0x188A591, 1, 246),
114    (25, 0x188A5C6, 1, 130), (25, 0x188A5DC, 1, 200), (25, 0x188A57A, 1, 170), (25, 0x188A59F, 1, 221),
115    (25, 0x188A5F9, 1, 196), (25, 0x188A5B4, 1, 142), (25, 0x188A5A7, 1, 210), (25, 0x188A58F, 1, 199),
116    (25, 0x188A5FD, 1, 155), (25, 0x188A5B7, 1, 154), (25, 0x188A593, 1, 244), (25, 0x188A59E, 1, 220),
117    (25, 0x188A5F8, 1, 195), (25, 0x188A5FF, 1, 161), (25, 0x188A5FC, 1, 231), (25, 0x188A579, 1, 173),
118    (25, 0x188A5F7, 1, 226), (26, 0x3114BA2, 1, 116), (26, 0x3114BA3, 0, 1),
119];
120
121/// Invert the VC-5 companding curve.
122fn decompand(v: i16) -> i16 {
123    let c = v as f64;
124    let c = c + (c * c * c * 768.0) / (255.0 * 255.0 * 255.0);
125    c.clamp(i16::MIN as f64, i16::MAX as f64) as i16
126}
127
128/// The run/value code book; decoded values are `decompanded magnitude << 9 | run`.
129fn code_book() -> Result<PrefixCode> {
130    let codes: Vec<(u32, u32, u32)> = TABLE17
131        .iter()
132        .map(|&(len, bits, count, value)| {
133            let v = ((decompand(value as i16) as u16 as u32) << RUN_BITS) | count as u32;
134            (bits, len as u32, v)
135        })
136        .collect();
137    PrefixCode::new(&codes)
138}
139
140/// The inverse-log output curve for a given output bit depth.
141fn log_table(output_bits: u32) -> Vec<u16> {
142    let n = 1usize << LOG_TABLE_BITS;
143    (0..n)
144        .map(|i| {
145            let x = i as f64 / (n as f64 - 1.0);
146            let y = (113f64.powf(x) - 1.0) / 112.0;
147            let y = (u16::MAX as f64 * y) as u32;
148            (y >> (16 - output_bits)) as u16
149        })
150        .collect()
151}
152
153/// A decoded/reconstructed band: `width x height` samples with row pitch `stride`.
154struct Band {
155    width: usize,
156    height: usize,
157    stride: usize,
158    data: Vec<i16>,
159}
160
161impl Band {
162    fn new(width: usize, height: usize) -> Result<Self> {
163        let n = width.checked_mul(height).ok_or_else(|| Error::corrupt("VC-5 band size overflow"))?;
164        Ok(Band { width, height, stride: width, data: vec![0; n] })
165    }
166    #[inline(always)]
167    fn at(&self, row: usize, col: usize) -> i32 {
168        self.data[row * self.stride + col] as i32
169    }
170}
171
172#[derive(Clone, Copy)]
173enum Coded<'a> {
174    Lowpass { precision: u32, data: &'a [u8] },
175    Highpass { quant: i16, data: &'a [u8] },
176}
177
178/// Parsed bitstream: per channel, per wavelet level (0 = finest), per band (0 = lowpass).
179struct Stream<'a> {
180    bands: [[[Option<Coded<'a>>; 4]; NUM_LEVELS]; NUM_CHANNELS],
181    prescale: [[u16; NUM_LEVELS]; NUM_CHANNELS],
182}
183
184/// Band dimensions per level: index 0 is the channel size, `i` the bands of level `i`.
185fn level_dims(width: usize, height: usize) -> [(usize, usize); NUM_LEVELS + 1] {
186    let mut d = [(0, 0); NUM_LEVELS + 1];
187    let (mut w, mut h) = (width, height);
188    for e in d.iter_mut() {
189        w = w.div_ceil(2);
190        h = h.div_ceil(2);
191        *e = (w, h);
192    }
193    d
194}
195
196fn parse(data: &[u8], width: usize, height: usize) -> Result<Stream<'_>> {
197    if data.get(..4) != Some(b"VC-5") {
198        return Err(Error::corrupt("missing VC-5 signature"));
199    }
200    let mut st = Stream { bands: Default::default(), prescale: [[0; NUM_LEVELS]; NUM_CHANNELS] };
201    let dims = level_dims(width, height);
202    let mut pos = 4usize;
203    let mut channel = 0usize;
204    let mut subband: Option<usize> = None;
205    let mut precision: Option<u32> = None;
206    let mut quant: Option<i16> = None;
207    let mut complete = [false; NUM_CHANNELS];
208    while !complete.iter().all(|&c| c) {
209        let b = data.get(pos..pos + 4).ok_or_else(|| Error::corrupt("truncated VC-5 bitstream"))?;
210        pos += 4;
211        let mut tag = i16::from_be_bytes([b[0], b[1]]);
212        let val = u16::from_be_bytes([b[2], b[3]]);
213        let mut optional = tag < 0;
214        if optional {
215            tag = tag.wrapping_neg();
216        }
217        let expect = |ok: bool, what: &str| {
218            if ok { Ok(()) } else { Err(Error::corrupt(format!("VC-5: unexpected {what} {val}"))) }
219        };
220        match tag {
221            0x000c => expect(val as usize == NUM_CHANNELS, "channel count")?,
222            0x0014 => expect(val as usize == width, "image width")?,
223            0x0015 => expect(val as usize == height, "image height")?,
224            0x000e => expect(val as usize == NUM_SUBBANDS, "subband count")?,
225            0x0054 => expect(val == 4, "image format")?,
226            0x0066 => expect(val as u32 == LOG_TABLE_BITS, "bits per component")?,
227            0x006a | 0x006b => expect(val == 2, "pattern size")?,
228            0x006c => expect(val == 1, "components per sample")?,
229            0x0023 => {
230                expect((8..=16).contains(&val), "lowpass precision")?;
231                precision = Some(val as u32);
232            }
233            0x003e => {
234                expect((val as usize) < NUM_CHANNELS, "channel number")?;
235                channel = val as usize;
236            }
237            0x0030 => {
238                expect((val as usize) < NUM_SUBBANDS, "subband number")?;
239                subband = Some(val as usize);
240            }
241            0x0035 => quant = Some(val as i16),
242            0x006d => {
243                // Two bits per level in a 14-bit field, finest level in the top bits.
244                for (i, p) in st.prescale[channel].iter_mut().enumerate() {
245                    *p = (val >> (14 - 2 * i as u32)) & 3;
246                }
247            }
248            _ => {
249                let t = tag as u16;
250                let mut size = 0usize;
251                if t & 0x2000 != 0 {
252                    size = (((t & 0xFF) as usize) << 16) | val as usize;
253                } else if t & 0x4000 != 0 {
254                    size = val as usize;
255                }
256                if t & 0x6000 == 0x6000 {
257                    let len = size * 4;
258                    let chunk = data
259                        .get(pos..pos.saturating_add(len))
260                        .ok_or_else(|| Error::corrupt("truncated VC-5 codeblock"))?;
261                    pos += len;
262                    let sb = subband.take().ok_or_else(|| Error::corrupt("VC-5 codeblock without subband"))?;
263                    // Subband 0: lowpass of the coarsest level; then 3 highpass bands per
264                    // level from coarse to fine.
265                    let (level, band) =
266                        if sb == 0 { (NUM_LEVELS - 1, 0) } else { (NUM_LEVELS - 1 - (sb - 1) / 3, 1 + (sb - 1) % 3) };
267                    let slot = &mut st.bands[channel][level][band];
268                    if slot.is_some() {
269                        return Err(Error::corrupt("VC-5 band specified twice"));
270                    }
271                    *slot = Some(if sb == 0 {
272                        let p = precision.take().ok_or_else(|| Error::corrupt("VC-5 lowpass without precision"))?;
273                        let (w, h) = dims[NUM_LEVELS];
274                        let bytes = ((w * h) as u64 * p as u64).div_ceil(64) as usize * 8;
275                        let d = chunk.get(..bytes).ok_or_else(|| Error::corrupt("truncated VC-5 lowpass band"))?;
276                        Coded::Lowpass { precision: p, data: d }
277                    } else {
278                        let q = quant.take().ok_or_else(|| Error::corrupt("VC-5 highpass without quantisation"))?;
279                        Coded::Highpass { quant: q, data: chunk }
280                    });
281                    let bands = &st.bands[channel];
282                    complete[channel] =
283                        bands[NUM_LEVELS - 1][0].is_some() && bands.iter().all(|l| l[1..].iter().all(|b| b.is_some()));
284                    continue;
285                }
286                if t & 0x2000 != 0 {
287                    // Other large chunks are containers whose payload follows as tags.
288                    optional = true;
289                    size = 0;
290                }
291                if !optional {
292                    return Err(Error::corrupt(format!("VC-5: unknown required tag 0x{t:04x}")));
293                }
294                pos = pos.saturating_add(size * 4);
295            }
296        }
297    }
298    Ok(st)
299}
300
301fn decode_lowpass(data: &[u8], precision: u32, w: usize, h: usize) -> Result<Band> {
302    let mut band = Band::new(w, h)?;
303    let mut bits = BitPumpMsb::new(data);
304    for v in band.data.iter_mut() {
305        *v = bits.get(precision) as i16;
306    }
307    Ok(band)
308}
309
310#[inline(always)]
311fn read_rlv(code: &PrefixCode, bits: &mut BitPumpMsb) -> Result<(i16, usize)> {
312    let v = code.decode(bits)?;
313    let count = (v & ((1 << RUN_BITS) - 1)) as usize;
314    let mut value = (v >> RUN_BITS) as u16 as i16;
315    if value != 0 && bits.get(1) != 0 {
316        value = -value;
317    }
318    Ok((value, count))
319}
320
321fn decode_highpass(code: &PrefixCode, data: &[u8], quant: i16, w: usize, h: usize) -> Result<Band> {
322    let mut band = Band::new(w, h)?;
323    let mut bits = BitPumpMsb::new(data);
324    let out = &mut band.data[..];
325    let mut i = 0usize;
326    while i < out.len() {
327        let (value, count) = read_rlv(code, &mut bits)?;
328        if count == 0 {
329            return Err(Error::corrupt("VC-5 end-of-band marker inside band"));
330        }
331        let value = value.checked_mul(quant).ok_or_else(|| Error::corrupt("VC-5 dequantisation overflow"))?;
332        let end =
333            i.checked_add(count).filter(|&e| e <= out.len()).ok_or_else(|| Error::corrupt("VC-5 run past band end"))?;
334        out[i..end].fill(value);
335        i = end;
336        if bits.exhausted() {
337            return Err(Error::corrupt("truncated VC-5 highpass band"));
338        }
339    }
340    let (value, count) = read_rlv(code, &mut bits)?;
341    if value != MARKER_BAND_END || count != 0 {
342        return Err(Error::corrupt("missing VC-5 end-of-band marker"));
343    }
344    Ok(band)
345}
346
347/// Inverse filter taps: (even, odd) multipliers for (high, low[k+s], low[k+s+1], low[k+s+2])
348/// and the start offset `s` at the first, interior and last position.
349type Taps = ([i32; 4], [i32; 4], isize);
350const FIRST: Taps = ([1, 11, -4, 1], [-1, 5, 4, -1], 0);
351const MIDDLE: Taps = ([1, 1, 8, -1], [-1, -1, 8, 1], -1);
352const LAST: Taps = ([1, -1, 4, 5], [-1, 1, -4, 11], -2);
353
354#[inline(always)]
355fn taps_for(i: usize, n: usize) -> Taps {
356    if i == 0 {
357        FIRST
358    } else if i + 1 < n {
359        MIDDLE
360    } else {
361        LAST
362    }
363}
364
365#[inline(always)]
366fn tap(m: &[i32; 4], high: i32, l: [i32; 3], descale: u32) -> i32 {
367    let lows = (m[1] * l[0] + m[2] * l[1] + m[3] * l[2] + 4) >> 3;
368    ((m[0] * high + lows) << descale) >> 1
369}
370
371fn check_pair(high: &Band, low: &Band) -> Result<()> {
372    if high.width < 3 || high.height < 3 || low.width < high.width || low.height < high.height {
373        return Err(Error::corrupt("VC-5 band geometry too small"));
374    }
375    Ok(())
376}
377
378/// Vertical inverse step: `high`/`low` (w x h) → (w x 2h).
379fn vertical(high: &Band, low: &Band) -> Result<Band> {
380    check_pair(high, low)?;
381    let (w, h) = (high.width, high.height);
382    let mut out = Band::new(w, 2 * h)?;
383    for row in 0..h {
384        let (even, odd, shift) = taps_for(row, h);
385        let base = (row as isize + shift) as usize;
386        let (top, bottom) = out.data[2 * row * w..(2 * row + 2) * w].split_at_mut(w);
387        for col in 0..w {
388            let l = [low.at(base, col), low.at(base + 1, col), low.at(base + 2, col)];
389            let hv = high.at(row, col);
390            top[col] = tap(&even, hv, l, 0) as i16;
391            bottom[col] = tap(&odd, hv, l, 0) as i16;
392        }
393    }
394    Ok(out)
395}
396
397/// Horizontal inverse step: `low`/`high` (w x h) → (2w x h), optionally clamped to 14 bits.
398fn horizontal(low: &Band, high: &Band, descale: u32, clamp: bool) -> Result<Band> {
399    check_pair(high, low)?;
400    let (w, h) = (high.width, high.height);
401    let mut out = Band::new(2 * w, h)?;
402    let fin = |v: i32| if clamp { v.clamp(0, (1 << 14) - 1) as i16 } else { v as i16 };
403    for row in 0..h {
404        let o = &mut out.data[row * 2 * w..(row + 1) * 2 * w];
405        for col in 0..w {
406            let (even, odd, shift) = taps_for(col, w);
407            let base = (col as isize + shift) as usize;
408            let l = [low.at(row, base), low.at(row, base + 1), low.at(row, base + 2)];
409            let hv = high.at(row, col);
410            o[2 * col] = fin(tap(&even, hv, l, descale));
411            o[2 * col + 1] = fin(tap(&odd, hv, l, descale));
412        }
413    }
414    Ok(out)
415}
416
417/// Decode a VC-5 bitstream (starting at the "VC-5" signature) into a `width x height`
418/// Bayer mosaic (`out`, row-major). `white_level` selects the output bit depth of the
419/// inverse-log curve (e.g. 4095 → 12-bit samples).
420pub fn decode(
421    data: &[u8],
422    width: usize,
423    height: usize,
424    white_level: u32,
425    phase: CfaPhase,
426    out: &mut [u16],
427    parallel: bool,
428) -> Result<()> {
429    if width == 0 || height == 0 || width % 2 != 0 || height % 2 != 0 || width > 65535 || height > 65535 {
430        return Err(Error::corrupt(format!("invalid VC-5 image size {width}x{height}")));
431    }
432    if out.len() < width * height {
433        return Err(Error::InvalidArgument("VC-5: output buffer too small".into()));
434    }
435    if white_level == 0 || white_level > 0xFFFF {
436        return Err(Error::corrupt(format!("invalid VC-5 white level {white_level}")));
437    }
438    let output_bits = 32 - white_level.leading_zeros();
439    let st = parse(data, width, height)?;
440    let code = code_book()?;
441    let dims = level_dims(width, height);
442
443    // Decode every coded band (index = (channel * levels + level) * 4 + band).
444    let coded: Vec<(usize, Coded)> = (0..NUM_CHANNELS * NUM_LEVELS * 4)
445        .filter_map(|i| st.bands[i / (NUM_LEVELS * 4)][(i / 4) % NUM_LEVELS][i % 4].map(|c| (i, c)))
446        .collect();
447    let decoded = flashraw_core::par::map(coded, parallel, |(i, cb)| -> Result<(usize, Band)> {
448        let (w, h) = dims[(i / 4) % NUM_LEVELS + 1];
449        Ok((
450            i,
451            match cb {
452                Coded::Lowpass { precision, data } => decode_lowpass(data, precision, w, h)?,
453                Coded::Highpass { quant, data } => decode_highpass(&code, data, quant, w, h)?,
454            },
455        ))
456    });
457    let mut bands: Vec<Option<Band>> = (0..NUM_CHANNELS * NUM_LEVELS * 4).map(|_| None).collect();
458    for r in decoded {
459        let (i, b) = r?;
460        bands[i] = Some(b);
461    }
462
463    // Reconstruct each channel from the coarsest level to the finest.
464    let mut per_channel: Vec<(usize, Vec<Option<Band>>)> = Vec::with_capacity(NUM_CHANNELS);
465    let mut it = bands.into_iter();
466    for c in 0..NUM_CHANNELS {
467        per_channel.push((c, it.by_ref().take(NUM_LEVELS * 4).collect()));
468    }
469    let prescale = st.prescale;
470    let finals = flashraw_core::par::map(per_channel, parallel, |(c, mut v)| -> Result<Band> {
471        let mut low = v[(NUM_LEVELS - 1) * 4].take().ok_or_else(|| Error::corrupt("missing VC-5 lowpass band"))?;
472        for level in (0..NUM_LEVELS).rev() {
473            let mut take = |b: usize| v[level * 4 + b].take().ok_or_else(|| Error::corrupt("missing VC-5 band"));
474            let (lh, hl, hh) = (take(1)?, take(2)?, take(3)?);
475            let lowpass = vertical(&hl, &low)?;
476            let highpass = vertical(&hh, &lh)?;
477            let descale = if prescale[c][level] == 2 { 2 } else { 0 };
478            low = horizontal(&lowpass, &highpass, descale, level == 0)?;
479        }
480        Ok(low)
481    });
482    let mut ch = Vec::with_capacity(NUM_CHANNELS);
483    for f in finals {
484        ch.push(f?);
485    }
486    let (hw, hh) = (width / 2, height / 2);
487    if ch.iter().any(|b| b.width < hw || b.height < hh) {
488        return Err(Error::corrupt("VC-5 reconstructed channel too small"));
489    }
490
491    // Recombine into the mosaic through the output curve.
492    let lut = log_table(output_bits);
493    let curve = |v: i32| lut[v.clamp(0, (1 << LOG_TABLE_BITS) - 1) as usize];
494    flashraw_core::par::for_each_row_chunk(&mut out[..width * height], width * 2, 16, parallel, |first, chunk| {
495        for (i, rows) in chunk.chunks_exact_mut(width * 2).enumerate() {
496            let row = first + i;
497            let (r0, r1) = rows.split_at_mut(width);
498            for col in 0..hw {
499                let gs = ch[0].at(row, col);
500                let rg = ch[1].at(row, col) - 2048;
501                let bg = ch[2].at(row, col) - 2048;
502                let gd = ch[3].at(row, col) - 2048;
503                let (r, b, g1, g2) = (curve(gs + 2 * rg), curve(gs + 2 * bg), curve(gs + gd), curve(gs - gd));
504                // The first green of the coded pattern stays first when the phase swaps rows.
505                let q = match phase {
506                    CfaPhase::Rggb => [r, g1, g2, b],
507                    CfaPhase::Grbg => [g1, r, b, g2],
508                    CfaPhase::Gbrg => [g1, b, r, g2],
509                    CfaPhase::Bggr => [b, g1, g2, r],
510                };
511                r0[2 * col] = q[0];
512                r0[2 * col + 1] = q[1];
513                r1[2 * col] = q[2];
514                r1[2 * col + 1] = q[3];
515            }
516        }
517    });
518    Ok(())
519}
520
521#[cfg(test)]
522mod tests {
523    use super::*;
524
525    #[test]
526    fn decompand_is_odd_and_monotonic() {
527        let mut prev = i16::MIN;
528        for v in 0..=255i16 {
529            assert_eq!(decompand(-v), -decompand(v));
530            assert!(decompand(v) >= prev);
531            prev = decompand(v);
532        }
533        assert_eq!(decompand(1), 1);
534        assert_eq!(decompand(255), 1023);
535    }
536
537    #[test]
538    fn code_book_round_trips() {
539        let code = code_book().unwrap();
540        for &(len, bits, count, value) in TABLE17.iter() {
541            let buf = ((bits as u64) << (64 - len as u32)).to_be_bytes();
542            let mut b = BitPumpMsb::new(&buf);
543            let got = code.decode(&mut b).unwrap();
544            assert_eq!(got & 0x1FF, count as u32);
545            assert_eq!((got >> 9) as u16 as i16, decompand(value as i16));
546        }
547    }
548
549    #[test]
550    fn log_curve_endpoints() {
551        let t = log_table(12);
552        assert_eq!(t[0], 0);
553        assert_eq!(t[4095], 4095);
554        assert!(t.windows(2).all(|w| w[0] <= w[1]));
555        assert_eq!(log_table(14)[4095], 16383);
556    }
557
558    #[test]
559    fn inverse_transform_of_flat_lowpass_is_flat() {
560        let mut low = Band::new(4, 4).unwrap();
561        low.data.fill(100);
562        let zero = Band::new(4, 4).unwrap();
563        let v = vertical(&zero, &low).unwrap();
564        assert!(v.data.iter().all(|&x| x == 50));
565        let lp = Band { width: 4, height: 8, stride: 4, data: vec![100; 32] };
566        let hz = Band::new(4, 8).unwrap();
567        let h = horizontal(&lp, &hz, 0, true).unwrap();
568        assert_eq!((h.width, h.height), (8, 8));
569        assert!(h.data.iter().all(|&x| x == 50));
570    }
571
572    #[test]
573    fn malformed_streams_error() {
574        let mut out = vec![0u16; 64 * 64];
575        assert!(decode(b"nope", 64, 64, 4095, CfaPhase::Rggb, &mut out, false).is_err());
576        assert!(decode(b"VC-5", 64, 64, 4095, CfaPhase::Rggb, &mut out, false).is_err());
577        let mut s = b"VC-5".to_vec();
578        s.extend_from_slice(&[0x00, 0x0c, 0x00, 0x04, 0x00, 0x14, 0x00, 0x40, 0x00, 0x15, 0x00, 0x41]);
579        assert!(decode(&s, 64, 64, 4095, CfaPhase::Rggb, &mut out, false).is_err());
580        let mut s = b"VC-5".to_vec();
581        s.extend_from_slice(&[0x00, 0x77, 0x00, 0x00]);
582        assert!(decode(&s, 64, 64, 4095, CfaPhase::Rggb, &mut out, false).is_err());
583        assert!(decode(b"VC-5", 63, 64, 4095, CfaPhase::Rggb, &mut out, false).is_err());
584        assert!(decode(b"VC-5", 64, 64, 0, CfaPhase::Rggb, &mut out, false).is_err());
585    }
586}