1use std::fmt;
18
19#[derive(Debug)]
23pub enum JpegError {
24 Invalid(String),
25 Unsupported(String),
26 Truncated,
27}
28
29impl fmt::Display for JpegError {
30 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
31 match self {
32 JpegError::Invalid(s) => write!(f, "Invalid JPEG: {}", s),
33 JpegError::Unsupported(s) => write!(f, "Unsupported JPEG feature: {}", s),
34 JpegError::Truncated => write!(f, "Truncated JPEG data"),
35 }
36 }
37}
38
39impl std::error::Error for JpegError {}
40
41const LUMA_QTABLE_BASE: [u16; 64] = [
45 16, 11, 10, 16, 24, 40, 51, 61, 12, 12, 14, 19, 26, 58, 60, 55, 14, 13, 16, 24, 40, 57, 69, 56,
46 14, 17, 22, 29, 51, 87, 80, 62, 18, 22, 37, 56, 68, 109, 103, 77, 24, 35, 55, 64, 81, 104, 113,
47 92, 49, 64, 78, 87, 103, 121, 120, 101, 72, 92, 95, 98, 112, 100, 103, 99,
48];
49
50const CHROMA_QTABLE_BASE: [u16; 64] = [
52 17, 18, 24, 47, 99, 99, 99, 99, 18, 21, 26, 66, 99, 99, 99, 99, 24, 26, 56, 99, 99, 99, 99, 99,
53 47, 66, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99,
54 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99,
55];
56
57fn make_qtable(base: &[u16; 64], quality: u8) -> [u16; 64] {
59 let q = quality.clamp(1, 100) as u32;
60 let scale = if q < 50 { 5000 / q } else { 200 - 2 * q };
61 let mut out = [0u16; 64];
62 for (i, &v) in base.iter().enumerate() {
63 let s = ((v as u32 * scale + 50) / 100).clamp(1, 255);
64 out[i] = s as u16;
65 }
66 out
67}
68
69const ZIGZAG_ORDER: [u8; 64] = [
73 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,
74 13, 6, 7, 14, 21, 28, 35, 42, 49, 56, 57, 50, 43, 36, 29, 22, 15, 23, 30, 37, 44, 51, 58, 59,
75 52, 45, 38, 31, 39, 46, 53, 60, 61, 54, 47, 55, 62, 63,
76];
77
78fn cos_table() -> [[f32; 8]; 8] {
82 use std::f32::consts::PI;
83 let mut t = [[0f32; 8]; 8];
84 for (u, row) in t.iter_mut().enumerate() {
85 for (n, cell) in row.iter_mut().enumerate() {
86 *cell = ((2 * n + 1) as f32 * u as f32 * PI / 16.0).cos();
87 }
88 }
89 t
90}
91
92fn dct8x8(block: &[f32; 64]) -> [f32; 64] {
100 let cos = cos_table();
101 let inv_sqrt2 = 1.0_f32 / std::f32::consts::SQRT_2;
102 let mut tmp = [0f32; 64];
103
104 for r in 0..8usize {
106 for u in 0..8usize {
107 let cu = if u == 0 { inv_sqrt2 } else { 1.0 };
108 let mut sum = 0.0f32;
109 for n in 0..8usize {
110 sum += block[r * 8 + n] * cos[u][n];
111 }
112 tmp[r * 8 + u] = 0.5 * cu * sum;
113 }
114 }
115
116 let mut out = [0f32; 64];
118 for c in 0..8usize {
119 for v in 0..8usize {
120 let cv = if v == 0 { inv_sqrt2 } else { 1.0 };
121 let mut sum = 0.0f32;
122 for n in 0..8usize {
123 sum += tmp[n * 8 + c] * cos[v][n];
124 }
125 out[v * 8 + c] = 0.5 * cv * sum;
126 }
127 }
128 out
129}
130
131fn idct8x8(coeffs: &[f32; 64]) -> [f32; 64] {
136 let cos = cos_table();
137 let inv_sqrt2 = 1.0_f32 / std::f32::consts::SQRT_2;
138
139 let mut tmp = [0f32; 64];
141 for c in 0..8usize {
142 for n in 0..8usize {
143 let mut sum = 0.0f32;
144 for v in 0..8usize {
145 let cv = if v == 0 { inv_sqrt2 } else { 1.0 };
146 sum += cv * coeffs[v * 8 + c] * cos[v][n];
147 }
148 tmp[n * 8 + c] = 0.5 * sum;
149 }
150 }
151
152 let mut out = [0f32; 64];
154 for r in 0..8usize {
155 for n in 0..8usize {
156 let mut sum = 0.0f32;
157 for u in 0..8usize {
158 let cu = if u == 0 { inv_sqrt2 } else { 1.0 };
159 sum += cu * tmp[r * 8 + u] * cos[u][n];
160 }
161 out[r * 8 + n] = 0.5 * sum;
162 }
163 }
164 out
165}
166
167type HuffEntry = (u8, u16);
171
172fn build_huffman_table(bits: &[u8; 16], huffval: &[u8]) -> [Option<HuffEntry>; 256] {
178 let mut table = [None; 256];
179 let mut code = 0u16;
180 let mut idx = 0usize;
181 for (bit_len, &count) in bits.iter().enumerate() {
182 let length = (bit_len + 1) as u8;
183 for _ in 0..count {
184 if idx < huffval.len() {
185 table[huffval[idx] as usize] = Some((length, code));
186 code += 1;
187 }
188 idx += 1;
189 }
190 code <<= 1;
191 }
192 table
193}
194
195const DC_LUMA_BITS: [u8; 16] = [0, 1, 5, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0];
197const DC_LUMA_HUFFVAL: [u8; 12] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11];
198
199const DC_CHROMA_BITS: [u8; 16] = [0, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0];
201const DC_CHROMA_HUFFVAL: [u8; 12] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11];
202
203const AC_LUMA_BITS: [u8; 16] = [0, 2, 1, 3, 3, 2, 4, 3, 5, 5, 4, 4, 0, 0, 1, 125];
205const AC_LUMA_HUFFVAL: &[u8] = &[
206 0x01, 0x02, 0x03, 0x00, 0x04, 0x11, 0x05, 0x12, 0x21, 0x31, 0x41, 0x06, 0x13, 0x51, 0x61, 0x07,
207 0x22, 0x71, 0x14, 0x32, 0x81, 0x91, 0xA1, 0x08, 0x23, 0x42, 0xB1, 0xC1, 0x15, 0x52, 0xD1, 0xF0,
208 0x24, 0x33, 0x62, 0x72, 0x82, 0x09, 0x0A, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x25, 0x26, 0x27, 0x28,
209 0x29, 0x2A, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49,
210 0x4A, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69,
211 0x6A, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89,
212 0x8A, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7,
213 0xA8, 0xA9, 0xAA, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xC2, 0xC3, 0xC4, 0xC5,
214 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xE1, 0xE2,
215 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8,
216 0xF9, 0xFA,
217];
218
219const AC_CHROMA_BITS: [u8; 16] = [0, 2, 1, 2, 4, 4, 3, 4, 7, 5, 4, 4, 0, 1, 2, 119];
221const AC_CHROMA_HUFFVAL: &[u8] = &[
222 0x00, 0x01, 0x02, 0x03, 0x11, 0x04, 0x05, 0x21, 0x31, 0x06, 0x12, 0x41, 0x51, 0x07, 0x61, 0x71,
223 0x13, 0x22, 0x32, 0x81, 0x08, 0x14, 0x42, 0x91, 0xA1, 0xB1, 0xC1, 0x09, 0x23, 0x33, 0x52, 0xF0,
224 0x15, 0x62, 0x72, 0xD1, 0x0A, 0x16, 0x24, 0x34, 0xE1, 0x25, 0xF1, 0x17, 0x18, 0x19, 0x1A, 0x26,
225 0x27, 0x28, 0x29, 0x2A, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48,
226 0x49, 0x4A, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68,
227 0x69, 0x6A, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87,
228 0x88, 0x89, 0x8A, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0xA2, 0xA3, 0xA4, 0xA5,
229 0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xC2, 0xC3,
230 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA,
231 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8,
232 0xF9, 0xFA,
233];
234
235struct BitWriter {
238 buffer: Vec<u8>,
239 accumulator: u32,
240 bits_pending: u8,
241}
242
243impl BitWriter {
244 fn new() -> Self {
245 Self {
246 buffer: Vec::new(),
247 accumulator: 0,
248 bits_pending: 0,
249 }
250 }
251
252 fn write_bits(&mut self, code: u16, length: u8) {
253 self.accumulator = (self.accumulator << length) | (code as u32);
254 self.bits_pending += length;
255 while self.bits_pending >= 8 {
256 self.bits_pending -= 8;
257 let byte = ((self.accumulator >> self.bits_pending) & 0xFF) as u8;
258 self.buffer.push(byte);
259 if byte == 0xFF {
261 self.buffer.push(0x00);
262 }
263 }
264 }
265
266 fn flush(&mut self) {
267 if self.bits_pending > 0 {
268 let shift = 8 - self.bits_pending;
270 let byte = (((self.accumulator << shift) | ((1u32 << shift) - 1)) & 0xFF) as u8;
271 self.buffer.push(byte);
272 if byte == 0xFF {
273 self.buffer.push(0x00);
274 }
275 self.bits_pending = 0;
276 }
277 }
278
279 fn into_bytes(self) -> Vec<u8> {
280 self.buffer
281 }
282}
283
284fn size_category(v: i16) -> u8 {
289 let abs = v.unsigned_abs();
290 if abs == 0 {
291 0
292 } else {
293 16 - abs.leading_zeros() as u8
294 }
295}
296
297fn amplitude_bits(v: i16, s: u8) -> u16 {
302 if v >= 0 {
303 v as u16
304 } else {
305 (v + ((1i16 << s) - 1)) as u16
307 }
308}
309
310pub fn jpeg_encode_rgb(
317 width: u32,
318 height: u32,
319 pixels: &[u8],
320 quality: u8,
321) -> Result<Vec<u8>, JpegError> {
322 let w = width as usize;
323 let h = height as usize;
324 if pixels.len() != w * h * 3 {
325 return Err(JpegError::Invalid(format!(
326 "expected {} bytes, got {}",
327 w * h * 3,
328 pixels.len()
329 )));
330 }
331 if w == 0 || h == 0 {
332 return Err(JpegError::Invalid("zero-dimension image".into()));
333 }
334
335 let luma_qt = make_qtable(&LUMA_QTABLE_BASE, quality);
336 let chroma_qt = make_qtable(&CHROMA_QTABLE_BASE, quality);
337
338 let mut y_plane = vec![0f32; w * h];
340 let mut cb_plane = vec![0f32; w * h];
341 let mut cr_plane = vec![0f32; w * h];
342 for i in 0..w * h {
343 let r = pixels[i * 3] as f32;
344 let g = pixels[i * 3 + 1] as f32;
345 let b = pixels[i * 3 + 2] as f32;
346 y_plane[i] = 0.299 * r + 0.587 * g + 0.114 * b;
347 cb_plane[i] = -0.168_736 * r - 0.331_264 * g + 0.5 * b + 128.0;
348 cr_plane[i] = 0.5 * r - 0.418_688 * g - 0.081_312 * b + 128.0;
349 }
350
351 let dc_luma_ht = build_huffman_table(&DC_LUMA_BITS, &DC_LUMA_HUFFVAL);
353 let dc_chroma_ht = build_huffman_table(&DC_CHROMA_BITS, &DC_CHROMA_HUFFVAL);
354 let ac_luma_ht = build_huffman_table(&AC_LUMA_BITS, AC_LUMA_HUFFVAL);
355 let ac_chroma_ht = build_huffman_table(&AC_CHROMA_BITS, AC_CHROMA_HUFFVAL);
356
357 let mut bw = BitWriter::new();
359 let mut prev_dc_y = 0i16;
360 let mut prev_dc_cb = 0i16;
361 let mut prev_dc_cr = 0i16;
362
363 let mcu_cols = w.div_ceil(8);
365 let mcu_rows = h.div_ceil(8);
366
367 for mcu_row in 0..mcu_rows {
368 for mcu_col in 0..mcu_cols {
369 for ch in 0..3 {
371 let plane = match ch {
372 0 => y_plane.as_slice(),
373 1 => cb_plane.as_slice(),
374 _ => cr_plane.as_slice(),
375 };
376 let qt = if ch == 0 { &luma_qt } else { &chroma_qt };
377 let dc_ht = if ch == 0 { &dc_luma_ht } else { &dc_chroma_ht };
378 let ac_ht = if ch == 0 { &ac_luma_ht } else { &ac_chroma_ht };
379 let prev_dc = match ch {
380 0 => &mut prev_dc_y,
381 1 => &mut prev_dc_cb,
382 _ => &mut prev_dc_cr,
383 };
384
385 let mut block = [0f32; 64];
387 for by in 0..8 {
388 for bx in 0..8 {
389 let px = (mcu_col * 8 + bx).min(w - 1);
390 let py = (mcu_row * 8 + by).min(h - 1);
391 block[by * 8 + bx] = plane[py * w + px] - 128.0;
392 }
393 }
394
395 let dct_coeffs = dct8x8(&block);
397
398 let mut quant = [0i16; 64];
400 for (zz, &pos) in ZIGZAG_ORDER.iter().enumerate() {
401 let coeff = dct_coeffs[pos as usize];
402 let q = qt[zz] as f32;
403 quant[zz] = (coeff / q).round() as i16;
404 }
405
406 let dc_diff = quant[0] - *prev_dc;
408 *prev_dc = quant[0];
409 let s = size_category(dc_diff);
410 let (dc_len, dc_code) = dc_ht[s as usize]
411 .ok_or_else(|| JpegError::Invalid("DC Huffman missing".into()))?;
412 bw.write_bits(dc_code, dc_len);
413 if s > 0 {
414 bw.write_bits(amplitude_bits(dc_diff, s), s);
415 }
416
417 let mut run = 0u8;
419 for (k, &ac) in quant[1..].iter().enumerate().map(|(i, v)| (i + 1, v)) {
420 if ac == 0 {
421 if k == 63 {
422 let (len, code) = ac_ht[0x00]
424 .ok_or_else(|| JpegError::Invalid("AC EOB missing".into()))?;
425 bw.write_bits(code, len);
426 } else {
427 run += 1;
428 if run == 16 {
429 let (len, code) = ac_ht[0xF0]
431 .ok_or_else(|| JpegError::Invalid("AC ZRL missing".into()))?;
432 bw.write_bits(code, len);
433 run = 0;
434 }
435 }
436 } else {
437 let s = size_category(ac);
438 let symbol = (run << 4) | s;
439 let (ac_len, ac_code) = ac_ht[symbol as usize]
440 .ok_or_else(|| JpegError::Invalid("AC Huffman missing".into()))?;
441 bw.write_bits(ac_code, ac_len);
442 bw.write_bits(amplitude_bits(ac, s), s);
443 run = 0;
444 }
445 }
446 }
447 }
448 }
449 bw.flush();
450 let scan_data = bw.into_bytes();
451
452 let mut out = Vec::new();
454
455 out.extend_from_slice(&[0xFF, 0xD8]);
457
458 let app0: &[u8] = &[
460 0xFF, 0xE0, 0x00, 0x10, 0x4A, 0x46, 0x49, 0x46, 0x00, 0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, ];
468 out.extend_from_slice(app0);
469
470 for (id, qt) in [(&luma_qt, 0u8), (&chroma_qt, 1u8)] {
472 let seg_len: u16 = 2 + 1 + 64;
474 out.extend_from_slice(&[0xFF, 0xDB]);
475 out.extend_from_slice(&seg_len.to_be_bytes());
476 out.push(qt); for &v in id.iter() {
478 out.push(v.min(255) as u8);
479 }
480 }
481
482 let sof0_len: u16 = 17;
485 out.extend_from_slice(&[0xFF, 0xC0]);
486 out.extend_from_slice(&sof0_len.to_be_bytes());
487 out.push(8); out.extend_from_slice(&(height as u16).to_be_bytes());
489 out.extend_from_slice(&(width as u16).to_be_bytes());
490 out.push(3); out.extend_from_slice(&[1, 0x11, 0]);
493 out.extend_from_slice(&[2, 0x11, 1]);
495 out.extend_from_slice(&[3, 0x11, 1]);
497
498 write_dht(&mut out, 0x00, &DC_LUMA_BITS, &DC_LUMA_HUFFVAL);
500 write_dht(&mut out, 0x01, &DC_CHROMA_BITS, &DC_CHROMA_HUFFVAL);
501 write_dht(&mut out, 0x10, &AC_LUMA_BITS, AC_LUMA_HUFFVAL);
502 write_dht(&mut out, 0x11, &AC_CHROMA_BITS, AC_CHROMA_HUFFVAL);
503
504 let sos_len: u16 = 12;
507 out.extend_from_slice(&[0xFF, 0xDA]);
508 out.extend_from_slice(&sos_len.to_be_bytes());
509 out.push(3); out.extend_from_slice(&[1, 0x00]); out.extend_from_slice(&[2, 0x11]); out.extend_from_slice(&[3, 0x11]); out.extend_from_slice(&[0, 63, 0]); out.extend_from_slice(&scan_data);
517
518 out.extend_from_slice(&[0xFF, 0xD9]);
520
521 Ok(out)
522}
523
524fn write_dht(out: &mut Vec<u8>, tc_th: u8, bits: &[u8; 16], huffval: &[u8]) {
526 let total_codes: u16 = bits.iter().map(|&b| b as u16).sum();
527 let seg_len = 2u16 + 1 + 16 + total_codes;
529 out.extend_from_slice(&[0xFF, 0xC4]);
530 out.extend_from_slice(&seg_len.to_be_bytes());
531 out.push(tc_th);
532 out.extend_from_slice(bits);
533 out.extend_from_slice(huffval);
534}
535
536pub fn jpeg_decode(bytes: &[u8]) -> Result<super::image_codec::RawDecodeResult, JpegError> {
542 if bytes.len() < 4 || bytes[0] != 0xFF || bytes[1] != 0xD8 {
544 return Err(JpegError::Invalid("missing SOI marker".into()));
545 }
546
547 let mut ctx = DecodeContext::new();
549 ctx.parse_markers(bytes)?;
550
551 let rgb_pixels = ctx.decode_scan()?;
553
554 Ok(super::image_codec::RawDecodeResult {
555 width: ctx.image_width,
556 height: ctx.image_height,
557 pixels: rgb_pixels,
558 })
559}
560
561#[derive(Clone)]
565struct HuffNode {
566 symbol: u8,
568 is_leaf: bool,
570 left: Option<Box<HuffNode>>,
571 right: Option<Box<HuffNode>>,
572}
573
574impl HuffNode {
575 fn internal() -> Self {
576 Self {
577 symbol: 0,
578 is_leaf: false,
579 left: None,
580 right: None,
581 }
582 }
583}
584
585fn build_huffman_decode_tree(bits: &[u8; 16], huffval: &[u8]) -> Result<HuffNode, JpegError> {
587 let mut root = HuffNode::internal();
588 let mut code = 0u16;
589 let mut idx = 0usize;
590
591 for (bit_len_0, &count) in bits.iter().enumerate() {
592 let depth = bit_len_0 + 1;
593 for _ in 0..count {
594 if idx >= huffval.len() {
595 break;
596 }
597 let sym = huffval[idx];
598 idx += 1;
599 insert_huffman_code(&mut root, code, depth as u8, sym)?;
601 code += 1;
602 }
603 code <<= 1;
604 }
605 Ok(root)
606}
607
608fn insert_huffman_code(
610 node: &mut HuffNode,
611 code: u16,
612 depth: u8,
613 sym: u8,
614) -> Result<(), JpegError> {
615 if depth == 0 {
616 node.symbol = sym;
617 node.is_leaf = true;
618 return Ok(());
619 }
620 let go_right = (code >> (depth - 1)) & 1 == 1;
621 if go_right {
622 if node.right.is_none() {
623 node.right = Some(Box::new(HuffNode::internal()));
624 }
625 if let Some(ref mut child) = node.right {
626 insert_huffman_code(child, code & ((1 << (depth - 1)) - 1), depth - 1, sym)?;
627 }
628 } else {
629 if node.left.is_none() {
630 node.left = Some(Box::new(HuffNode::internal()));
631 }
632 if let Some(ref mut child) = node.left {
633 insert_huffman_code(child, code & ((1 << (depth - 1)) - 1), depth - 1, sym)?;
634 }
635 }
636 Ok(())
637}
638
639#[derive(Clone, Default)]
642struct Component {
643 id: u8,
644 #[allow(dead_code)]
645 h_samp: u8,
646 #[allow(dead_code)]
647 v_samp: u8,
648 qt_id: u8,
649 dc_ht_id: u8,
650 ac_ht_id: u8,
651}
652
653struct DecodeContext {
655 image_width: usize,
656 image_height: usize,
657 #[allow(dead_code)]
659 precision: u8,
660 components: Vec<Component>,
661 qtables: [[u16; 64]; 4],
663 #[allow(dead_code)]
665 qtable_present: [bool; 4],
666 dc_trees: [Option<HuffNode>; 4],
668 ac_trees: [Option<HuffNode>; 4],
669 scan_data: Vec<u8>,
671}
672
673impl DecodeContext {
674 fn new() -> Self {
675 Self {
676 image_width: 0,
677 image_height: 0,
678 precision: 8,
679 components: Vec::new(),
680 qtables: [[0u16; 64]; 4],
681 qtable_present: [false; 4],
682 dc_trees: [None, None, None, None],
683 ac_trees: [None, None, None, None],
684 scan_data: Vec::new(),
685 }
686 }
687
688 fn parse_markers(&mut self, bytes: &[u8]) -> Result<(), JpegError> {
689 let mut pos = 2; while pos + 1 < bytes.len() {
691 if bytes[pos] != 0xFF {
692 return Err(JpegError::Invalid(format!(
693 "expected 0xFF marker at position {}",
694 pos
695 )));
696 }
697 let marker = bytes[pos + 1];
698 pos += 2;
699
700 match marker {
701 0xD8 => {} 0xD9 => break, 0xE0..=0xEF => {
704 let seg_len = read_u16_be(bytes, pos)? as usize;
706 pos += seg_len;
707 }
708 0xDB => {
709 let seg_len = read_u16_be(bytes, pos)? as usize;
711 let seg_end = pos + seg_len;
712 let mut cur = pos + 2;
713 while cur < seg_end {
714 let pq_tq = *bytes.get(cur).ok_or(JpegError::Truncated)?;
715 let precision = (pq_tq >> 4) & 0x0F;
716 let table_id = (pq_tq & 0x0F) as usize;
717 cur += 1;
718 if precision == 0 {
719 if cur + 64 > bytes.len() {
721 return Err(JpegError::Truncated);
722 }
723 for i in 0..64 {
724 self.qtables[table_id][i] = bytes[cur + i] as u16;
725 }
726 cur += 64;
727 } else {
728 if cur + 128 > bytes.len() {
730 return Err(JpegError::Truncated);
731 }
732 for i in 0..64 {
733 self.qtables[table_id][i] = u16::from_be_bytes([
734 bytes[cur + i * 2],
735 bytes[cur + i * 2 + 1],
736 ]);
737 }
738 cur += 128;
739 }
740 self.qtable_present[table_id] = true;
741 }
742 pos = seg_end;
743 }
744 0xC0 | 0xC1 => {
745 let seg_len = read_u16_be(bytes, pos)? as usize;
747 let seg = bytes.get(pos..pos + seg_len).ok_or(JpegError::Truncated)?;
748 self.precision = seg[2];
749 self.image_height = u16::from_be_bytes([seg[3], seg[4]]) as usize;
750 self.image_width = u16::from_be_bytes([seg[5], seg[6]]) as usize;
751 let ncomp = seg[7] as usize;
752 self.components = Vec::with_capacity(ncomp);
753 for i in 0..ncomp {
754 let base = 8 + i * 3;
755 let comp = Component {
756 id: seg[base],
757 h_samp: (seg[base + 1] >> 4) & 0x0F,
758 v_samp: seg[base + 1] & 0x0F,
759 qt_id: seg[base + 2],
760 dc_ht_id: 0,
761 ac_ht_id: 0,
762 };
763 self.components.push(comp);
764 }
765 pos += seg_len;
766 }
767 0xC4 => {
768 let seg_len = read_u16_be(bytes, pos)? as usize;
770 let seg_end = pos + seg_len;
771 let mut cur = pos + 2;
772 while cur < seg_end {
773 let tc_th = *bytes.get(cur).ok_or(JpegError::Truncated)?;
774 let tc = (tc_th >> 4) & 0x0F; let th = (tc_th & 0x0F) as usize;
776 cur += 1;
777 if cur + 16 > bytes.len() {
778 return Err(JpegError::Truncated);
779 }
780 let mut bits = [0u8; 16];
781 bits.copy_from_slice(&bytes[cur..cur + 16]);
782 cur += 16;
783 let total_codes: usize = bits.iter().map(|&b| b as usize).sum();
784 if cur + total_codes > bytes.len() {
785 return Err(JpegError::Truncated);
786 }
787 let huffval = &bytes[cur..cur + total_codes];
788 cur += total_codes;
789 let tree = build_huffman_decode_tree(&bits, huffval)?;
790 if tc == 0 {
791 self.dc_trees[th] = Some(tree);
792 } else {
793 self.ac_trees[th] = Some(tree);
794 }
795 }
796 pos = seg_end;
797 }
798 0xDA => {
799 let seg_len = read_u16_be(bytes, pos)? as usize;
801 let seg_end = pos + seg_len;
802 let seg = bytes.get(pos..seg_end).ok_or(JpegError::Truncated)?;
803 let ncomp = seg[2] as usize;
804 for i in 0..ncomp {
805 let comp_id = seg[3 + i * 2];
806 let ht_byte = seg[3 + i * 2 + 1];
807 let dc_id = ht_byte >> 4;
808 let ac_id = ht_byte & 0x0F;
809 for comp in &mut self.components {
811 if comp.id == comp_id {
812 comp.dc_ht_id = dc_id;
813 comp.ac_ht_id = ac_id;
814 }
815 }
816 }
817 pos = seg_end;
818 self.scan_data = collect_scan_data(bytes, pos)?;
820 break;
821 }
822 0xFE | 0xC2..=0xC3 | 0xC5..=0xCF => {
823 if pos + 2 > bytes.len() {
825 return Err(JpegError::Truncated);
826 }
827 let seg_len = read_u16_be(bytes, pos)? as usize;
828 if marker >= 0xC2 {
829 return Err(JpegError::Unsupported(format!(
830 "non-baseline SOF marker 0x{:02X}",
831 marker
832 )));
833 }
834 pos += seg_len;
835 }
836 0xDD => {
837 let seg_len = read_u16_be(bytes, pos)? as usize;
839 pos += seg_len;
840 }
841 0xD0..=0xD7 => {
842 }
844 _ => {
845 if pos + 2 <= bytes.len() {
847 let seg_len = read_u16_be(bytes, pos)? as usize;
848 pos += seg_len;
849 } else {
850 break;
851 }
852 }
853 }
854 }
855 Ok(())
856 }
857
858 fn decode_scan(&self) -> Result<Vec<u8>, JpegError> {
859 if self.image_width == 0 || self.image_height == 0 {
860 return Err(JpegError::Invalid("SOF0 not found".into()));
861 }
862 if self.components.len() != 3 {
863 return Err(JpegError::Unsupported(format!(
864 "{} components (only 3-component YCbCr supported)",
865 self.components.len()
866 )));
867 }
868
869 let w = self.image_width;
870 let h = self.image_height;
871 let mcu_cols = w.div_ceil(8);
872 let mcu_rows = h.div_ceil(8);
873
874 let mut y_plane = vec![0i32; mcu_cols * 8 * mcu_rows * 8];
876 let mut cb_plane = vec![0i32; mcu_cols * 8 * mcu_rows * 8];
877 let mut cr_plane = vec![0i32; mcu_cols * 8 * mcu_rows * 8];
878
879 let mut br = BitReader::new(&self.scan_data);
881 let stride = mcu_cols * 8;
882
883 let mut prev_dc = [0i32; 3];
884
885 for mcu_row in 0..mcu_rows {
886 for mcu_col in 0..mcu_cols {
887 for (ch, comp) in self.components.iter().enumerate() {
888 let dc_tree = self.dc_trees[comp.dc_ht_id as usize]
889 .as_ref()
890 .ok_or_else(|| JpegError::Invalid("DC Huffman tree missing".into()))?;
891 let ac_tree = self.ac_trees[comp.ac_ht_id as usize]
892 .as_ref()
893 .ok_or_else(|| JpegError::Invalid("AC Huffman tree missing".into()))?;
894 let qt = &self.qtables[comp.qt_id as usize];
895
896 let dc_size = decode_huffman_symbol(&mut br, dc_tree)? as usize;
898 let dc_diff = if dc_size == 0 {
899 0i32
900 } else {
901 let raw = br.read_bits(dc_size)?;
902 decode_signed_coeff(raw as i32, dc_size as u8)
903 };
904 prev_dc[ch] += dc_diff;
905 let dc_val = prev_dc[ch];
906
907 let mut zz_coeffs = [0i16; 64];
909 zz_coeffs[0] = dc_val as i16;
910 let mut k = 1usize;
911 while k < 64 {
912 let sym = decode_huffman_symbol(&mut br, ac_tree)?;
913 if sym == 0x00 {
914 break;
916 }
917 if sym == 0xF0 {
918 k += 16;
920 continue;
921 }
922 let run_len = ((sym >> 4) & 0x0F) as usize;
923 let ac_size = (sym & 0x0F) as usize;
924 k += run_len;
925 if k >= 64 {
926 break;
927 }
928 if ac_size > 0 {
929 let raw = br.read_bits(ac_size)?;
930 zz_coeffs[k] = decode_signed_coeff(raw as i32, ac_size as u8) as i16;
931 }
932 k += 1;
933 }
934
935 let mut coeffs = [0f32; 64];
937 for (zz, &pos) in ZIGZAG_ORDER.iter().enumerate() {
938 coeffs[pos as usize] = zz_coeffs[zz] as f32 * qt[zz] as f32;
939 }
940
941 let spatial = idct8x8(&coeffs);
943
944 let plane = match ch {
946 0 => &mut y_plane,
947 1 => &mut cb_plane,
948 _ => &mut cr_plane,
949 };
950 for by in 0..8 {
951 for bx in 0..8 {
952 let px = mcu_col * 8 + bx;
953 let py = mcu_row * 8 + by;
954 if px < stride && py < mcu_rows * 8 {
955 let val = (spatial[by * 8 + bx] + 128.0).round() as i32;
957 plane[py * stride + px] = val.clamp(0, 255);
958 }
959 }
960 }
961 }
962 }
963 }
964
965 let mut pixels = vec![0u8; w * h * 3];
967 for py in 0..h {
968 for px in 0..w {
969 let idx = py * stride + px;
970 let y = y_plane[idx] as f32;
971 let cb = cb_plane[idx] as f32 - 128.0;
972 let cr = cr_plane[idx] as f32 - 128.0;
973
974 let r = (y + 1.402 * cr).round() as i32;
975 let g = (y - 0.344_136 * cb - 0.714_136 * cr).round() as i32;
976 let b = (y + 1.772 * cb).round() as i32;
977
978 let out_idx = (py * w + px) * 3;
979 pixels[out_idx] = r.clamp(0, 255) as u8;
980 pixels[out_idx + 1] = g.clamp(0, 255) as u8;
981 pixels[out_idx + 2] = b.clamp(0, 255) as u8;
982 }
983 }
984
985 Ok(pixels)
986 }
987}
988
989struct BitReader<'a> {
992 data: &'a [u8],
993 pos: usize,
994 buffer: u32,
995 bits_avail: u8,
996}
997
998impl<'a> BitReader<'a> {
999 fn new(data: &'a [u8]) -> Self {
1000 Self {
1001 data,
1002 pos: 0,
1003 buffer: 0,
1004 bits_avail: 0,
1005 }
1006 }
1007
1008 fn read_bits(&mut self, n: usize) -> Result<u32, JpegError> {
1009 while self.bits_avail < n as u8 {
1010 let byte = self.next_byte()?;
1011 self.buffer = (self.buffer << 8) | byte as u32;
1012 self.bits_avail += 8;
1013 }
1014 self.bits_avail -= n as u8;
1015 let val = (self.buffer >> self.bits_avail) & ((1 << n) - 1);
1016 Ok(val)
1017 }
1018
1019 fn next_byte(&mut self) -> Result<u8, JpegError> {
1020 if self.pos >= self.data.len() {
1021 return Err(JpegError::Truncated);
1022 }
1023 let b = self.data[self.pos];
1024 self.pos += 1;
1025 Ok(b)
1026 }
1027}
1028
1029fn decode_huffman_symbol(br: &mut BitReader<'_>, tree: &HuffNode) -> Result<u8, JpegError> {
1031 let mut node = tree;
1032 loop {
1033 if node.is_leaf {
1034 return Ok(node.symbol);
1035 }
1036 let bit = br.read_bits(1)?;
1037 if bit == 0 {
1038 node = node
1039 .left
1040 .as_deref()
1041 .ok_or_else(|| JpegError::Invalid("Huffman tree: null left child".into()))?;
1042 } else {
1043 node = node
1044 .right
1045 .as_deref()
1046 .ok_or_else(|| JpegError::Invalid("Huffman tree: null right child".into()))?;
1047 }
1048 }
1049}
1050
1051fn decode_signed_coeff(raw: i32, size: u8) -> i32 {
1053 let threshold = 1 << (size - 1);
1056 if raw < threshold {
1057 raw - ((1 << size) - 1)
1058 } else {
1059 raw
1060 }
1061}
1062
1063fn collect_scan_data(bytes: &[u8], start: usize) -> Result<Vec<u8>, JpegError> {
1067 let mut data = Vec::new();
1068 let mut i = start;
1069 while i < bytes.len() {
1070 let b = bytes[i];
1071 if b == 0xFF {
1072 if i + 1 >= bytes.len() {
1073 break;
1074 }
1075 let next = bytes[i + 1];
1076 if next == 0x00 {
1077 data.push(0xFF);
1079 i += 2;
1080 } else if next == 0xD9 {
1081 break;
1083 } else if (0xD0..=0xD7).contains(&next) {
1084 i += 2;
1086 } else {
1087 break;
1089 }
1090 } else {
1091 data.push(b);
1092 i += 1;
1093 }
1094 }
1095 Ok(data)
1096}
1097
1098fn read_u16_be(bytes: &[u8], pos: usize) -> Result<u16, JpegError> {
1101 if pos + 2 > bytes.len() {
1102 return Err(JpegError::Truncated);
1103 }
1104 Ok(u16::from_be_bytes([bytes[pos], bytes[pos + 1]]))
1105}
1106
1107#[cfg(test)]
1110mod tests {
1111 use super::*;
1112
1113 fn solid_rgb(width: u32, height: u32, r: u8, g: u8, b: u8) -> Vec<u8> {
1114 let n = (width * height) as usize * 3;
1115 let mut buf = Vec::with_capacity(n);
1116 for _ in 0..(width * height) as usize {
1117 buf.push(r);
1118 buf.push(g);
1119 buf.push(b);
1120 }
1121 buf
1122 }
1123
1124 #[test]
1125 fn test_jpeg_encode_returns_jfif_magic() {
1126 let pixels = solid_rgb(8, 8, 100, 150, 200);
1127 let encoded = jpeg_encode_rgb(8, 8, &pixels, 90).expect("encode failed");
1128 assert_eq!(&encoded[..2], &[0xFF, 0xD8]);
1129 }
1130
1131 #[test]
1132 fn test_jpeg_encode_ends_with_eoi() {
1133 let pixels = solid_rgb(8, 8, 80, 80, 80);
1134 let encoded = jpeg_encode_rgb(8, 8, &pixels, 75).expect("encode failed");
1135 let n = encoded.len();
1136 assert!(n >= 2);
1137 assert_eq!(&encoded[n - 2..], &[0xFF, 0xD9]);
1138 }
1139
1140 #[test]
1141 fn test_jpeg_roundtrip_8x8() {
1142 let pixels = solid_rgb(8, 8, 120, 80, 200);
1143 let encoded = jpeg_encode_rgb(8, 8, &pixels, 90).expect("encode");
1144 let decoded = jpeg_decode(&encoded).expect("decode");
1145 assert_eq!(decoded.width, 8);
1146 assert_eq!(decoded.height, 8);
1147 assert_eq!(decoded.pixels.len(), 8 * 8 * 3);
1148 for i in 0..8 * 8 {
1149 let dr = (decoded.pixels[i * 3] as i16 - 120i16).abs();
1150 let dg = (decoded.pixels[i * 3 + 1] as i16 - 80i16).abs();
1151 let db = (decoded.pixels[i * 3 + 2] as i16 - 200i16).abs();
1152 assert!(dr <= 8, "R channel error {} at pixel {}", dr, i);
1153 assert!(dg <= 8, "G channel error {} at pixel {}", dg, i);
1154 assert!(db <= 8, "B channel error {} at pixel {}", db, i);
1155 }
1156 }
1157
1158 #[test]
1159 fn test_jpeg_roundtrip_gradient_16x16() {
1160 let w = 16usize;
1161 let h = 16usize;
1162 let mut pixels = vec![0u8; w * h * 3];
1163 for y in 0..h {
1164 for x in 0..w {
1165 let r = (x * 255 / (w - 1)) as u8;
1166 let idx = (y * w + x) * 3;
1167 pixels[idx] = r;
1168 pixels[idx + 1] = 100;
1169 pixels[idx + 2] = 50;
1170 }
1171 }
1172 let encoded = jpeg_encode_rgb(w as u32, h as u32, &pixels, 90).expect("encode");
1173 let decoded = jpeg_decode(&encoded).expect("decode");
1174 assert_eq!(decoded.width, w);
1175 assert_eq!(decoded.height, h);
1176 let left_r = decoded.pixels[0] as i16;
1178 let right_r = decoded.pixels[(w - 1) * 3] as i16;
1179 assert!(
1180 right_r > left_r,
1181 "gradient not preserved: left={} right={}",
1182 left_r,
1183 right_r
1184 );
1185 for y in 0..h {
1187 for x in 0..w {
1188 let orig_r = (x * 255 / (w - 1)) as i16;
1189 let decoded_r = decoded.pixels[(y * w + x) * 3] as i16;
1190 let err = (decoded_r - orig_r).abs();
1191 assert!(
1192 err <= 15,
1193 "R error {} at ({},{}) expected {}",
1194 err,
1195 x,
1196 y,
1197 orig_r
1198 );
1199 }
1200 }
1201 }
1202
1203 #[test]
1204 fn test_jpeg_invalid_magic_returns_error() {
1205 let result = jpeg_decode(&[0x00, 0x01, 0x02]);
1206 assert!(result.is_err());
1207 }
1208
1209 #[test]
1210 fn test_jpeg_quality_50_smaller_than_quality_90() {
1211 let pixels = solid_rgb(16, 16, 128, 64, 32);
1212 let enc50 = jpeg_encode_rgb(16, 16, &pixels, 50).expect("encode q50");
1213 let enc90 = jpeg_encode_rgb(16, 16, &pixels, 90).expect("encode q90");
1214 assert!(
1215 enc50.len() < enc90.len(),
1216 "q50 size {} not < q90 size {}",
1217 enc50.len(),
1218 enc90.len()
1219 );
1220 }
1221
1222 #[test]
1223 fn test_jpeg_decode_empty_returns_error() {
1224 let result = jpeg_decode(&[]);
1225 assert!(result.is_err());
1226 }
1227}