1use flashraw_core::bits::{BitPumpJpeg, BitRead, extend_diff};
12use flashraw_core::huffman::HuffTable;
13use flashraw_core::{Error, ReadAt, Result};
14
15const ZIGZAG: [u8; 80] = [
18 0, 1, 8, 16, 9, 2, 3, 10, 17, 24, 32, 25, 18, 11, 4, 5, 12, 19, 26, 33, 40, 48, 41, 34, 27, 20, 13, 6, 7, 14, 21,
19 28, 35, 42, 49, 56, 57, 50, 43, 36, 29, 22, 15, 23, 30, 37, 44, 51, 58, 59, 52, 45, 38, 31, 39, 46, 53, 60, 61, 54,
20 47, 55, 62, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63,
21];
22
23pub struct Dct12 {
25 pub width: u32,
26 pub height: u32,
27 pub precision: u32,
28 quant: [i32; 64],
29 dc: HuffTable,
30 ac: HuffTable,
31 scan_offset: usize,
32}
33
34impl Dct12 {
35 pub fn parse(data: &[u8], dng_bug16: bool) -> Result<Self> {
36 if data.len() < 4 || data[0] != 0xFF || data[1] != 0xD8 {
37 return Err(Error::corrupt("DCT tile: missing SOI"));
38 }
39 let mut pos = 2usize;
40 let mut quant = [0i32; 64];
41 let mut dc: Option<HuffTable> = None;
42 let mut ac: Option<HuffTable> = None;
43 let mut frame = None;
44 loop {
45 while pos < data.len() && data[pos] != 0xFF {
46 pos += 1;
47 }
48 while pos < data.len() && data[pos] == 0xFF {
49 pos += 1;
50 }
51 let marker = *data.get(pos).ok_or_else(|| Error::corrupt("DCT tile: truncated header"))?;
52 pos += 1;
53 if marker == 0xD8 || marker == 0x01 || (0xD0..=0xD7).contains(&marker) {
54 continue;
55 }
56 let len = data.u16be(pos)? as usize;
57 if len < 2 {
58 return Err(Error::corrupt("DCT tile: invalid segment length"));
59 }
60 let seg = flashraw_core::bytes::slice_at(data, pos + 2, len - 2)?;
61 match marker {
62 0xDB => {
63 for (i, q) in quant.iter_mut().enumerate() {
65 *q = seg.u16be(1 + 2 * i).unwrap_or(0) as i32;
66 }
67 }
68 0xC4 => {
69 let mut p = 0usize;
70 while p + 17 <= seg.len() {
71 let class = seg[p] >> 4;
72 let counts = &seg[p + 1..p + 17];
73 let total: usize = counts.iter().map(|&c| c as usize).sum();
74 let vals = flashraw_core::bytes::slice_at(seg, p + 17, total)?;
75 if class == 0 {
76 let mut t = HuffTable::from_counts(counts, vals, true)?;
77 t.dng_bug16 = dng_bug16;
78 dc.get_or_insert(t);
79 } else {
80 ac.get_or_insert(HuffTable::from_counts(counts, vals, false)?);
81 }
82 p += 17 + total;
83 }
84 }
85 0xC0 | 0xC1 => {
86 if seg.len() < 6 {
87 return Err(Error::corrupt("DCT tile: short SOF"));
88 }
89 if seg[5] != 1 {
90 return Err(Error::unsupported_compression("DNG", format!("{}-component DCT tile", seg[5])));
91 }
92 frame = Some((seg[0] as u32, seg.u16be(3)? as u32, seg.u16be(1)? as u32));
93 }
94 0xDA => {
95 let (precision, width, height) = frame.ok_or_else(|| Error::corrupt("DCT tile: SOS before SOF"))?;
96 let dc = dc.ok_or_else(|| Error::corrupt("DCT tile: missing DC table"))?;
97 let ac = ac.ok_or_else(|| Error::corrupt("DCT tile: missing AC table"))?;
98 return Ok(Dct12 { width, height, precision, quant, dc, ac, scan_offset: pos + len });
99 }
100 _ => {}
101 }
102 pos += len;
103 }
104 }
105
106 pub fn decode_blocks<F: FnMut(u32, u32, &[u16; 64])>(&self, data: &[u8], mut sink: F) -> Result<()> {
109 let scan = data.get(self.scan_offset..).ok_or_else(|| Error::corrupt("DCT tile: scan beyond data"))?;
110 let mut bits = BitPumpJpeg::new(scan);
111 let cs = cos_table();
112 let mut dc_pred: i32 = 1 << 14;
113 let mut out = [0u16; 64];
114 let mut jrow = 0;
115 while jrow + 7 < self.height {
116 let mut jcol = 0;
117 while jcol + 7 < self.width {
118 let mut work = [[[0f32; 8]; 8]; 3];
119 let diff = self.dc.decode_diff(&mut bits)?;
120 dc_pred = dc_pred.wrapping_add(diff.wrapping_mul(self.quant[0]));
121 work[0][0][0] = dc_pred as f32;
122 let mut i = 1usize;
123 while i < 64 {
124 let sym = self.ac.decode_symbol(&mut bits)?;
125 let skip = (sym >> 4) as usize;
126 let len = sym & 15;
127 i += skip;
128 if len == 0 && skip < 15 {
129 break;
130 }
131 let coef = if len == 0 { 0 } else { extend_diff(bits.get(len), len) };
132 let z = ZIGZAG[i.min(79)] as usize;
133 work[0][z / 8][z % 8] = (coef * self.quant[i.min(63)]) as f32;
134 i += 1;
135 }
136 let s = std::f64::consts::FRAC_1_SQRT_2 as f32;
137 for c in 0..8 {
138 work[0][0][c] *= s;
139 }
140 for c in 0..8 {
141 work[0][c][0] *= s;
142 }
143 for i in 0..8 {
144 for j in 0..8 {
145 let mut acc = work[1][i][j];
146 for c in 0..8 {
147 acc += work[0][i][c] * cs[(j * 2 + 1) * c];
148 }
149 work[1][i][j] = acc;
150 }
151 }
152 for i in 0..8 {
153 for j in 0..8 {
154 let mut acc = work[2][i][j];
155 for c in 0..8 {
156 acc += work[1][c][j] * cs[(i * 2 + 1) * c];
157 }
158 work[2][i][j] = acc;
159 }
160 }
161 for (k, o) in out.iter_mut().enumerate() {
162 let v = (work[2][k / 8][k % 8] as f64 + 0.5) as i64;
163 *o = v.clamp(0, 65535) as u16;
164 }
165 if bits.overrun() > 64 {
166 return Err(Error::corrupt("DCT tile: data ends early"));
167 }
168 sink(jrow, jcol, &out);
169 jcol += 8;
170 }
171 jrow += 8;
172 }
173 Ok(())
174 }
175}
176
177fn cos_table() -> &'static [f32; 106] {
178 use std::sync::OnceLock;
179 static T: OnceLock<[f32; 106]> = OnceLock::new();
180 T.get_or_init(|| {
181 let mut t = [0f32; 106];
182 for (c, v) in t.iter_mut().enumerate() {
183 *v = ((((c & 31) as f64) * std::f64::consts::PI / 16.0).cos() / 2.0) as f32;
184 }
185 t
186 })
187}
188
189#[cfg(test)]
190mod tests {
191 use super::*;
192
193 #[test]
194 fn cosine_table() {
195 let t = cos_table();
196 assert!((t[0] - 0.5).abs() < 1e-7);
197 assert!((t[8]).abs() < 1e-7);
198 assert!((t[16] + 0.5).abs() < 1e-7);
199 }
200
201 #[test]
202 fn rejects_garbage() {
203 assert!(Dct12::parse(&[0, 1, 2, 3], true).is_err());
204 assert!(Dct12::parse(&[0xFF, 0xD8, 0xFF, 0xDA, 0, 2], true).is_err());
205 }
206}