1use core::fmt;
4
5pub mod lossless;
7
8#[rustfmt::skip]
10pub const ZIGZAG: [u8; 64] = [
11 0, 1, 8, 16, 9, 2, 3, 10,
12 17, 24, 32, 25, 18, 11, 4, 5,
13 12, 19, 26, 33, 40, 48, 41, 34,
14 27, 20, 13, 6, 7, 14, 21, 28,
15 35, 42, 49, 56, 57, 50, 43, 36,
16 29, 22, 15, 23, 30, 37, 44, 51,
17 58, 59, 52, 45, 38, 31, 39, 46,
18 53, 60, 61, 54, 47, 55, 62, 63,
19];
20
21#[derive(Debug, Clone, Copy, PartialEq, Eq)]
23pub struct CanonicalHuffmanDerivation {
24 pub min_code: [i32; 17],
26 pub max_code: [i32; 17],
28 pub val_offset: [i32; 17],
30 pub huffcode: [u16; 256],
32 pub huffsize: [u8; 256],
34 pub huffsize_len: usize,
36}
37
38#[derive(Debug, Clone, Copy, PartialEq, Eq)]
40pub enum CanonicalHuffmanError {
41 BitsExceedTableCapacity,
43 BitsValuesLenMismatch,
45 CodeOverflow,
47}
48
49impl fmt::Display for CanonicalHuffmanError {
50 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
51 match self {
52 Self::BitsExceedTableCapacity => f.write_str("BITS exceed table capacity"),
53 Self::BitsValuesLenMismatch => f.write_str("BITS do not match HUFFVAL length"),
54 Self::CodeOverflow => f.write_str("canonical code overflow"),
55 }
56 }
57}
58
59#[expect(
65 clippy::cast_possible_truncation,
66 clippy::cast_possible_wrap,
67 reason = "JPEG Huffman lengths and value counts are bounded to 16 and 256, which fit every converted integer type"
68)]
69pub const fn derive_canonical_huffman(
70 bits: &[u8; 16],
71 values_len: usize,
72) -> Result<CanonicalHuffmanDerivation, CanonicalHuffmanError> {
73 if values_len > 256 {
74 return Err(CanonicalHuffmanError::BitsExceedTableCapacity);
75 }
76
77 let mut huffsize = [0u8; 256];
78 let mut huffsize_len = 0usize;
79 let mut len_minus_1 = 0usize;
80 while len_minus_1 < bits.len() {
81 let len = (len_minus_1 + 1) as u8;
82 let mut remaining = bits[len_minus_1];
83 while remaining != 0 {
84 if huffsize_len >= values_len || huffsize_len >= huffsize.len() {
85 return Err(CanonicalHuffmanError::BitsExceedTableCapacity);
86 }
87 huffsize[huffsize_len] = len;
88 huffsize_len += 1;
89 remaining -= 1;
90 }
91 len_minus_1 += 1;
92 }
93 if huffsize_len != values_len {
94 return Err(CanonicalHuffmanError::BitsValuesLenMismatch);
95 }
96
97 let mut huffcode = [0u16; 256];
98 let mut code = 0u32;
99 let mut si = if huffsize_len == 0 { 0 } else { huffsize[0] };
100 let mut idx = 0usize;
101 while idx < huffsize_len {
102 let size = huffsize[idx];
103 while size != si {
104 code <<= 1;
105 si = si.saturating_add(1);
106 }
107 if si > 16 || code >= (1u32 << si) {
108 return Err(CanonicalHuffmanError::CodeOverflow);
109 }
110 huffcode[idx] = code as u16;
111 code = match code.checked_add(1) {
112 Some(next) => next,
113 None => return Err(CanonicalHuffmanError::CodeOverflow),
114 };
115 idx += 1;
116 }
117
118 let mut min_code = [i32::MAX; 17];
119 let mut max_code = [-1i32; 17];
120 let mut val_offset = [0i32; 17];
121 let mut cursor = 0usize;
122 let mut len_minus_1 = 0usize;
123 while len_minus_1 < bits.len() {
124 let len = len_minus_1 + 1;
125 let count = bits[len_minus_1] as usize;
126 if count == 0 {
127 len_minus_1 += 1;
128 continue;
129 }
130 min_code[len] = huffcode[cursor] as i32;
131 max_code[len] = huffcode[cursor + count - 1] as i32;
132 val_offset[len] = cursor as i32 - min_code[len];
133 cursor += count;
134 len_minus_1 += 1;
135 }
136
137 Ok(CanonicalHuffmanDerivation {
138 min_code,
139 max_code,
140 val_offset,
141 huffcode,
142 huffsize,
143 huffsize_len,
144 })
145}
146
147pub mod idct {
149 pub const CONST_BITS: usize = 13;
151 pub const PASS1_BITS: usize = 2;
153
154 pub const FIX_0_298631336: i32 = 2_446;
156 pub const FIX_0_390180644: i32 = 3_196;
158 pub const FIX_0_541196100: i32 = 4_433;
160 pub const FIX_0_765366865: i32 = 6_270;
162 pub const FIX_0_899976223: i32 = 7_373;
164 pub const FIX_1_175875602: i32 = 9_633;
166 pub const FIX_1_501321110: i32 = 12_299;
168 pub const FIX_1_847759065: i32 = 15_137;
170 pub const FIX_1_961570560: i32 = 16_069;
172 pub const FIX_2_053119869: i32 = 16_819;
174 pub const FIX_2_562915447: i32 = 20_995;
176 pub const FIX_3_072711026: i32 = 25_172;
178}
179
180pub mod ycbcr {
182 pub const FIX_1_40200: i32 = 91_881;
184 pub const FIX_0_34414: i32 = 22_554;
186 pub const FIX_0_71414: i32 = 46_802;
188 pub const FIX_1_77200: i32 = 116_130;
190 pub const ROUND: i32 = 1 << 15;
192}
193
194#[cfg(test)]
195mod tests {
196 extern crate alloc;
197
198 use super::*;
199 use alloc::string::ToString;
200
201 #[test]
202 fn zigzag_is_a_permutation_of_one_block() {
203 let mut seen = [false; 64];
204 for &idx in &ZIGZAG {
205 assert!(idx < 64);
206 assert!(!seen[idx as usize], "duplicate zigzag index {idx}");
207 seen[idx as usize] = true;
208 }
209 assert!(seen.into_iter().all(|entry| entry));
210 }
211
212 #[test]
213 fn canonical_huffman_derivation_matches_t81_luma_dc_table() {
214 let bits = [0, 1, 5, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0];
215 let Ok(canonical) = derive_canonical_huffman(&bits, 12) else {
216 panic!("canonical table should derive");
217 };
218
219 assert_eq!(canonical.huffsize_len, 12);
220 assert_eq!(
221 &canonical.huffsize[..12],
222 &[2, 3, 3, 3, 3, 3, 4, 5, 6, 7, 8, 9]
223 );
224 assert_eq!(
225 &canonical.huffcode[..12],
226 &[0, 2, 3, 4, 5, 6, 14, 30, 62, 126, 254, 510]
227 );
228 assert_eq!(canonical.min_code[2], 0);
229 assert_eq!(canonical.max_code[3], 6);
230 assert_eq!(canonical.val_offset[3], -1);
231 }
232
233 #[test]
234 fn canonical_huffman_derivation_rejects_mismatched_value_count() {
235 let bits = [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
236
237 assert_eq!(
238 derive_canonical_huffman(&bits, 0),
239 Err(CanonicalHuffmanError::BitsExceedTableCapacity)
240 );
241 assert_eq!(
242 derive_canonical_huffman(&bits, 2),
243 Err(CanonicalHuffmanError::BitsValuesLenMismatch)
244 );
245 }
246
247 #[test]
248 fn canonical_huffman_error_display_is_stable() {
249 let errors = [
250 CanonicalHuffmanError::BitsExceedTableCapacity,
251 CanonicalHuffmanError::BitsValuesLenMismatch,
252 CanonicalHuffmanError::CodeOverflow,
253 ];
254 assert_eq!(
255 errors.map(|error| error.to_string()),
256 [
257 "BITS exceed table capacity",
258 "BITS do not match HUFFVAL length",
259 "canonical code overflow",
260 ]
261 );
262 }
263
264 #[test]
265 fn canonical_huffman_derivation_accepts_complete_decoder_table() {
266 let bits = [2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
267
268 let derived = derive_canonical_huffman(&bits, 2).expect("complete prefix table derives");
269 assert_eq!(&derived.huffcode[..2], &[0, 1]);
270 }
271
272 #[test]
273 fn idct_constants_match_existing_integer_backend() {
274 assert_eq!(idct::CONST_BITS, 13);
275 assert_eq!(idct::PASS1_BITS, 2);
276 assert_eq!(idct::FIX_0_298631336, 2_446);
277 assert_eq!(idct::FIX_0_390180644, 3_196);
278 assert_eq!(idct::FIX_0_541196100, 4_433);
279 assert_eq!(idct::FIX_0_765366865, 6_270);
280 assert_eq!(idct::FIX_0_899976223, 7_373);
281 assert_eq!(idct::FIX_1_175875602, 9_633);
282 assert_eq!(idct::FIX_1_501321110, 12_299);
283 assert_eq!(idct::FIX_1_847759065, 15_137);
284 assert_eq!(idct::FIX_1_961570560, 16_069);
285 assert_eq!(idct::FIX_2_053119869, 16_819);
286 assert_eq!(idct::FIX_2_562915447, 20_995);
287 assert_eq!(idct::FIX_3_072711026, 25_172);
288 }
289}