1use crate::entropy::Reader;
27use crate::format::{
28 AdobeTransform, Frame, IccChunks, Scan, ZIGZAG, adobe_transform, exif_orientation, marker,
29};
30use crate::huffman::HuffmanTable;
31use crate::idct::{self, Scale};
32use crate::progressive::{Coefficients, MAX_SCANS, Tables};
33use otf_pixels_core::{
34 Codec, DecodeCapability, Decoder, Format, ImageDescriptor, Limits, Orientation, PixelFormat,
35 PixelsError, Result, Source,
36};
37
38const MAX_BLOCKS_PER_MCU: u32 = 10;
44
45#[derive(Debug, Clone, Copy, PartialEq, Eq)]
47enum Colour {
48 Grayscale,
50 YCbCr,
52 Rgb,
54}
55
56#[derive(Debug)]
58struct Plane {
59 stride: usize,
61 samples: Vec<u8>,
63 columns: Vec<u32>,
66 rows: Vec<u32>,
68}
69
70#[derive(Debug)]
76pub struct JpegDecoder<S: Source> {
77 inner: Box<Engine<S>>,
78 icc: Option<Vec<u8>>,
80}
81
82impl<S: Source> JpegDecoder<S> {
83 pub fn new(source: S, limits: Limits) -> Result<Self> {
96 Self::with_scale(source, limits, Scale::Full)
97 }
98
99 pub fn with_scale(source: S, limits: Limits, scale: Scale) -> Result<Self> {
114 let mut inner = Box::new(Engine::with_scale(source, limits, scale)?);
115 let icc = inner.icc.take();
116 Ok(Self { inner, icc })
117 }
118
119 #[must_use]
121 pub const fn scale(&self) -> Scale {
122 self.inner.scale
123 }
124
125 #[must_use]
127 pub const fn is_progressive(&self) -> bool {
128 self.inner.progressive
129 }
130}
131
132impl<S: Source + std::fmt::Debug> Decoder for JpegDecoder<S> {
133 fn descriptor(&self) -> ImageDescriptor {
134 self.inner.descriptor()
135 }
136
137 fn icc_profile(&self) -> Option<&[u8]> {
139 self.icc.as_deref()
140 }
141
142 fn orientation(&self) -> Orientation {
144 self.inner.orientation.unwrap_or_default()
145 }
146
147 fn capability(&self) -> DecodeCapability {
148 self.inner.capability()
149 }
150
151 fn reduced_descriptor(&self, target: (u32, u32)) -> Option<ImageDescriptor> {
152 self.inner.reduced_descriptor(target)
153 }
154
155 fn reduce_to(&mut self, descriptor: ImageDescriptor) -> Result<()> {
156 self.inner.reduce_to(descriptor)
157 }
158
159 fn read_row(&mut self, out: &mut [u8]) -> Result<()> {
160 self.inner.read_row(out)
161 }
162}
163
164struct Engine<S: Source> {
166 reader: Reader<S>,
167 descriptor: ImageDescriptor,
168 frame: Frame,
169 scan: Scan,
170 colour: Colour,
171 quant: [[u16; 64]; 4],
173 dc_tables: [Option<HuffmanTable>; 4],
175 ac_tables: [Option<HuffmanTable>; 4],
176 restart_interval: u16,
178 restarts_left: u32,
180 orientation: Option<Orientation>,
182 icc: Option<Vec<u8>>,
184 planes: Vec<Plane>,
185 expanded: Vec<Vec<u8>>,
188 predictors: Vec<i32>,
190 band: Vec<u8>,
192 band_row: u32,
194 band_height: u32,
196 mcus_per_line: u32,
197 mcu_rows: u32,
198 mcu_row: u32,
200 scale: Scale,
202 limits: Limits,
204 row: u32,
206 progressive: bool,
208 coefficients: Option<Coefficients>,
210 latched: Vec<Option<[u16; 64]>>,
213}
214
215impl<S: Source> std::fmt::Debug for Engine<S> {
216 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
217 f.debug_struct("Engine")
218 .field("descriptor", &self.descriptor)
219 .field("colour", &self.colour)
220 .field("components", &self.frame.components)
221 .field("scale", &self.scale)
222 .field("restart_interval", &self.restart_interval)
223 .field("orientation", &self.orientation)
224 .field("row", &self.row)
225 .finish_non_exhaustive()
226 }
227}
228
229impl<S: Source> Engine<S> {
230 fn with_scale(source: S, limits: Limits, scale: Scale) -> Result<Self> {
257 let mut reader = Reader::new(source);
258 let mut progressive = false;
259 let mut quant = [[0_u16; 64]; 4];
260 let mut dc_tables: [Option<HuffmanTable>; 4] = [None, None, None, None];
261 let mut ac_tables: [Option<HuffmanTable>; 4] = [None, None, None, None];
262 let mut restart_interval = 0_u16;
263 let mut orientation = None;
264 let mut icc = IccChunks::default();
265 let mut adobe = None;
266 let mut frame: Option<Frame> = None;
267
268 let first = reader.next_marker()?;
269 if first != marker::SOI {
270 return Err(PixelsError::malformed(
271 "jpeg",
272 format!("stream begins with marker {first:#04x}, not SOI"),
273 ));
274 }
275
276 let scan = loop {
277 let code = reader.next_marker()?;
278 match code {
279 marker::SOF0 | marker::SOF1 | marker::SOF2 => {
280 if frame.is_some() {
281 return Err(PixelsError::malformed(
282 "jpeg",
283 "stream declares more than one frame",
284 ));
285 }
286 progressive = code == marker::SOF2;
287 let parsed = Frame::parse(&reader.read_segment()?)?;
288 limits.check(u32::from(parsed.width), u32::from(parsed.height))?;
292 frame = Some(parsed);
293 }
294 marker::DAC => {
295 return Err(PixelsError::unsupported(
296 "jpeg: arithmetic coding; baseline JPEG is Huffman coded",
297 ));
298 }
299 code if marker::is_frame(code) => {
300 return Err(PixelsError::unsupported(format!(
301 "jpeg: frame type {code:#04x} is not baseline"
302 )));
303 }
304 marker::DQT => read_quantization_tables(&reader.read_segment()?, &mut quant)?,
305 marker::DHT => {
306 read_huffman_tables(&reader.read_segment()?, &mut dc_tables, &mut ac_tables)?;
307 }
308 marker::DRI => {
309 let payload = reader.read_segment()?;
310 let (Some(&hi), Some(&lo)) = (payload.first(), payload.get(1)) else {
311 return Err(PixelsError::malformed(
312 "jpeg",
313 "DRI segment carries no interval",
314 ));
315 };
316 restart_interval = u16::from_be_bytes([hi, lo]);
317 }
318 marker::APP1 => {
319 let payload = reader.read_segment()?;
320 orientation = orientation.or_else(|| exif_orientation(&payload));
322 }
323 marker::APP2 => icc.push(&reader.read_segment()?),
324 marker::APP14 => {
325 let payload = reader.read_segment()?;
326 adobe = adobe_transform(&payload).or(adobe);
327 }
328 marker::SOS => {
329 let Some(ref frame) = frame else {
330 return Err(PixelsError::malformed(
331 "jpeg",
332 "scan begins before any frame header",
333 ));
334 };
335 break Scan::parse(&reader.read_segment()?, frame)?;
336 }
337 marker::EOI => {
338 return Err(PixelsError::malformed(
339 "jpeg",
340 "stream ends before any scan",
341 ));
342 }
343 marker::APP0..=marker::APP15 | marker::COM => reader.skip_segment()?,
348 code if marker::is_standalone(code) => {}
349 _ => reader.skip_segment()?,
350 }
351 };
352
353 let Some(frame) = frame else {
354 return Err(PixelsError::malformed("jpeg", "stream has no frame header"));
355 };
356 if !progressive {
359 check_baseline_scan(&scan)?;
360 }
361
362 let colour = colour_model(&frame, adobe)?;
363 let pixel = match colour {
364 Colour::Grayscale => PixelFormat::Gray8,
365 Colour::YCbCr | Colour::Rgb => PixelFormat::Rgb8,
366 };
367 let (full_width, full_height) = (u32::from(frame.width), u32::from(frame.height));
371 let descriptor = ImageDescriptor::with_limits(
372 scale.apply(full_width),
373 scale.apply(full_height),
374 pixel,
375 &limits,
376 )?;
377
378 if !progressive && scan.components.len() != frame.components.len() {
379 return Err(PixelsError::unsupported(
384 "jpeg: non-interleaved baseline scan",
385 ));
386 }
387 for component in scan.components.iter().filter(|_| !progressive) {
388 let slot = frame
389 .components
390 .get(component.index)
391 .map_or(0, |c| c.quant as usize);
392 if quant
393 .get(slot)
394 .is_none_or(|table| table.iter().all(|&q| q == 0))
395 {
396 return Err(PixelsError::malformed(
397 "jpeg",
398 format!("scan uses quantization table {slot}, which was never defined"),
399 ));
400 }
401 if dc_tables
402 .get(component.dc as usize)
403 .is_none_or(Option::is_none)
404 || ac_tables
405 .get(component.ac as usize)
406 .is_none_or(Option::is_none)
407 {
408 return Err(PixelsError::malformed(
409 "jpeg",
410 format!(
411 "scan uses Huffman tables {}/{}, which were never defined",
412 component.dc, component.ac
413 ),
414 ));
415 }
416 }
417
418 let (h_max, v_max) = (u32::from(frame.h_max()), u32::from(frame.v_max()));
419 let blocks: u32 = frame
420 .components
421 .iter()
422 .map(|c| u32::from(c.h) * u32::from(c.v))
423 .sum();
424 if blocks > MAX_BLOCKS_PER_MCU {
425 return Err(PixelsError::malformed(
426 "jpeg",
427 format!(
428 "an MCU would hold {blocks} blocks; the format allows {MAX_BLOCKS_PER_MCU}"
429 ),
430 ));
431 }
432
433 let mcus_per_line = full_width.div_ceil(h_max * 8);
434 let mcu_rows = full_height.div_ceil(v_max * 8);
435
436 let mut decoder = Self {
440 reader,
441 descriptor,
442 predictors: vec![0; frame.components.len()],
443 frame,
444 scan,
445 colour,
446 quant,
447 dc_tables,
448 ac_tables,
449 restart_interval,
450 restarts_left: u32::from(restart_interval),
451 orientation,
452 icc: icc.assemble(),
453 limits,
454 planes: Vec::new(),
455 expanded: Vec::new(),
456 band: Vec::new(),
457 band_row: 0,
458 band_height: 0,
459 mcus_per_line,
460 mcu_rows,
461 mcu_row: 0,
462 scale: Scale::Full,
463 row: 0,
464 progressive,
465 coefficients: None,
466 latched: Vec::new(),
467 };
468 decoder.apply_scale(scale)?;
469 Ok(decoder)
470 }
471
472 fn apply_scale(&mut self, scale: Scale) -> Result<()> {
484 if self.row > 0 || self.mcu_row > 0 {
485 return Err(PixelsError::invalid_argument(
486 "scale",
487 format!("{} rows have already been decoded", self.row),
488 ));
489 }
490 let full_width = u32::from(self.frame.width);
491 let full_height = u32::from(self.frame.height);
492 let (h_max, v_max) = (u32::from(self.frame.h_max()), u32::from(self.frame.v_max()));
493 let descriptor = ImageDescriptor::with_limits(
494 scale.apply(full_width),
495 scale.apply(full_height),
496 self.descriptor.pixel,
497 &self.limits,
498 )?;
499
500 let sample = scale.block_size();
503 let band_height = v_max * sample;
504
505 let mut planes = Vec::with_capacity(self.frame.components.len());
506 for component in &self.frame.components {
507 let (h, v) = (u32::from(component.h), u32::from(component.v));
508 let stride = usize::try_from(self.mcus_per_line * h * sample)
509 .map_err(|_| PixelsError::malformed("jpeg", "component plane overflows"))?;
510 let height = usize::try_from(v * sample).unwrap_or(32);
511 let samples = stride
512 .checked_mul(height)
513 .ok_or_else(|| PixelsError::malformed("jpeg", "component plane overflows"))?;
514 planes.push(Plane {
515 stride,
516 samples: vec![0_u8; samples],
517 columns: (0..descriptor.width).map(|x| x * h / h_max).collect(),
523 rows: (0..band_height).map(|y| y * v / v_max).collect(),
524 });
525 }
526
527 let band = descriptor
528 .row_bytes()
529 .checked_mul(band_height as usize)
530 .ok_or_else(|| PixelsError::malformed("jpeg", "MCU row band overflows"))?;
531
532 self.descriptor = descriptor;
533 self.expanded = vec![vec![0_u8; descriptor.width as usize]; planes.len()];
534 self.planes = planes;
535 self.band = vec![0_u8; band];
536 self.band_row = band_height;
539 self.band_height = band_height;
540 self.scale = scale;
541 Ok(())
542 }
543
544 fn fill_band(&mut self) -> Result<()> {
547 if self.mcu_row >= self.mcu_rows {
548 return Err(PixelsError::malformed(
549 "jpeg",
550 "more rows were requested than the frame declares",
551 ));
552 }
553
554 if self.progressive {
555 return self.fill_band_progressive();
556 }
557
558 let mut coefficients = [0_i32; 64];
559 let scale = self.scale;
560 let sample = scale.block_size() as usize;
561 for mcu in 0..self.mcus_per_line {
562 if self.restart_interval > 0 && self.restarts_left == 0 {
563 if !self.reader.restart()? {
564 let met = self.reader.pending_marker();
565 return Err(PixelsError::malformed(
566 "jpeg",
567 match met {
568 Some(code) => format!(
569 "expected a restart marker between MCU intervals, met {code:#04x}"
570 ),
571 None => "expected a restart marker between MCU intervals".to_owned(),
572 },
573 ));
574 }
575 self.predictors.iter_mut().for_each(|p| *p = 0);
579 self.restarts_left = u32::from(self.restart_interval);
580 }
581
582 for scanned in &self.scan.components {
583 let Some(component) = self.frame.components.get(scanned.index) else {
584 continue;
585 };
586 let (h, v) = (u32::from(component.h), u32::from(component.v));
587 let (Some(dc), Some(ac)) = (
588 self.dc_tables
589 .get(scanned.dc as usize)
590 .and_then(Option::as_ref),
591 self.ac_tables
592 .get(scanned.ac as usize)
593 .and_then(Option::as_ref),
594 ) else {
595 return Err(PixelsError::malformed(
596 "jpeg",
597 "scan names a Huffman table that was never defined",
598 ));
599 };
600 let quant = self
601 .quant
602 .get(component.quant as usize)
603 .ok_or_else(|| PixelsError::malformed("jpeg", "component names no table"))?;
604 let Some(predictor) = self.predictors.get_mut(scanned.index) else {
605 continue;
606 };
607 let Some(plane) = self.planes.get_mut(scanned.index) else {
608 continue;
609 };
610
611 for block_y in 0..v {
612 for block_x in 0..h {
613 decode_block(
614 &mut self.reader,
615 dc,
616 ac,
617 quant,
618 predictor,
619 &mut coefficients,
620 )?;
621 let x = ((mcu * h) + block_x) as usize * sample;
622 let y = block_y as usize * sample;
623 idct::scaled_block(
624 &coefficients,
625 scale,
626 &mut plane.samples,
627 y * plane.stride + x,
628 plane.stride,
629 );
630 }
631 }
632 }
633
634 if self.restart_interval > 0 {
635 self.restarts_left = self.restarts_left.saturating_sub(1);
636 }
637 }
638
639 self.mcu_row += 1;
640 self.convert_band();
641 self.band_row = 0;
642 Ok(())
643 }
644
645 fn fill_band_progressive(&mut self) -> Result<()> {
648 if self.coefficients.is_none() {
649 self.decode_scans()?;
650 }
651 let Some(stored) = self.coefficients.as_ref() else {
652 return Err(PixelsError::graph(
653 "jpeg coefficients vanished after decoding",
654 ));
655 };
656
657 let mut natural = [0_i32; 64];
658 let scale = self.scale;
659 let sample = scale.block_size() as usize;
660 let mcu_row = self.mcu_row as usize;
661 for (index, component) in self.frame.components.iter().enumerate() {
662 let (h, v) = (usize::from(component.h), usize::from(component.v));
663 let quant = self
666 .latched
667 .get(index)
668 .copied()
669 .flatten()
670 .or_else(|| self.quant.get(component.quant as usize).copied())
671 .unwrap_or([0; 64]);
672 let Some(plane) = self.planes.get_mut(index) else {
673 continue;
674 };
675 for block_y in 0..v {
676 for block_x in 0..h * self.mcus_per_line as usize {
677 let Some(block) = stored.block(index, block_x, mcu_row * v + block_y) else {
678 continue;
679 };
680 for ((&at, &value), &step) in ZIGZAG.iter().zip(block).zip(&quant) {
681 if let Some(slot) = natural.get_mut(at) {
682 *slot = i32::from(value) * i32::from(step);
683 }
684 }
685 idct::scaled_block(
686 &natural,
687 scale,
688 &mut plane.samples,
689 block_y * sample * plane.stride + block_x * sample,
690 plane.stride,
691 );
692 }
693 }
694 }
695
696 self.mcu_row += 1;
697 self.convert_band();
698 self.band_row = 0;
699 Ok(())
700 }
701
702 fn decode_scans(&mut self) -> Result<()> {
709 let mut coefficients = Coefficients::new(&self.frame, self.mcus_per_line, self.mcu_rows)?;
710 let mut latched: Vec<Option<[u16; 64]>> = vec![None; self.frame.components.len()];
711 let mut scan = self.scan.clone();
712 let mut scans = 0;
713 'scans: loop {
714 scans += 1;
715 if scans > MAX_SCANS {
716 return Err(PixelsError::malformed(
717 "jpeg",
718 format!("progressive frame has more than {MAX_SCANS} scans"),
719 ));
720 }
721 for scanned in &scan.components {
723 let Some(component) = self.frame.components.get(scanned.index) else {
724 continue;
725 };
726 if let Some(slot) = latched.get_mut(scanned.index) {
727 if slot.is_none() {
728 *slot = self.quant.get(component.quant as usize).copied();
729 }
730 }
731 }
732 let tables = Tables {
733 dc: &self.dc_tables,
734 ac: &self.ac_tables,
735 restart_interval: self.restart_interval,
736 };
737 crate::progressive::decode_scan(
738 &mut self.reader,
739 &self.frame,
740 &scan,
741 &tables,
742 &mut coefficients,
743 )?;
744 self.reader.align();
745
746 loop {
747 let Ok(code) = self.reader.next_marker() else {
750 break 'scans;
751 };
752 match code {
753 marker::SOS => {
754 scan = Scan::parse(&self.reader.read_segment()?, &self.frame)?;
755 continue 'scans;
756 }
757 marker::EOI => break 'scans,
758 marker::DHT => read_huffman_tables(
759 &self.reader.read_segment()?,
760 &mut self.dc_tables,
761 &mut self.ac_tables,
762 )?,
763 marker::DQT => {
764 read_quantization_tables(&self.reader.read_segment()?, &mut self.quant)?;
765 }
766 marker::DRI => {
767 let payload = self.reader.read_segment()?;
768 let (Some(&hi), Some(&lo)) = (payload.first(), payload.get(1)) else {
769 return Err(PixelsError::malformed(
770 "jpeg",
771 "DRI segment carries no interval",
772 ));
773 };
774 self.restart_interval = u16::from_be_bytes([hi, lo]);
775 }
776 code if marker::is_frame(code) => {
777 return Err(PixelsError::malformed(
778 "jpeg",
779 "stream declares more than one frame",
780 ));
781 }
782 code if marker::is_standalone(code) => {}
784 _ => self.reader.skip_segment()?,
785 }
786 }
787 }
788 self.coefficients = Some(coefficients);
789 self.latched = latched;
790 Ok(())
791 }
792
793 fn convert_band(&mut self) {
801 let width = self.descriptor.width as usize;
802 let row_bytes = self.descriptor.row_bytes();
803 let h_max = usize::from(self.frame.h_max());
804
805 for y in 0..self.band_height as usize {
806 let Some(out) = self
807 .band
808 .get_mut(y * row_bytes..)
809 .and_then(|rest| rest.get_mut(..row_bytes))
810 else {
811 continue;
812 };
813 for ((plane, component), expanded) in self
814 .planes
815 .iter()
816 .zip(&self.frame.components)
817 .zip(self.expanded.iter_mut())
818 {
819 let h = usize::from(component.h.max(1));
821 let ratio = if h_max % h == 0 { h_max / h } else { 0 };
822 plane.expand_row(y, ratio, expanded);
823 }
824
825 match self.colour {
826 Colour::Grayscale => {
827 if let Some(grey) = self.expanded.first() {
828 for (slot, &value) in out.iter_mut().zip(grey).take(width) {
829 *slot = value;
830 }
831 }
832 }
833 Colour::Rgb => {
834 let [a, b, c] =
835 [0, 1, 2].map(|i| self.expanded.get(i).map_or(&[][..], Vec::as_slice));
836 for (pixel, ((&r, &g), &b)) in
837 out.chunks_exact_mut(3).zip(a.iter().zip(b).zip(c))
838 {
839 pixel.copy_from_slice(&[r, g, b]);
840 }
841 }
842 Colour::YCbCr => {
843 let [luma, cb, cr] =
844 [0, 1, 2].map(|i| self.expanded.get(i).map_or(&[][..], Vec::as_slice));
845 for (pixel, ((&y, &cb), &cr)) in
846 out.chunks_exact_mut(3).zip(luma.iter().zip(cb).zip(cr))
847 {
848 pixel.copy_from_slice(&ycbcr_to_rgb([y, cb, cr]));
849 }
850 }
851 }
852 }
853 }
854}
855
856impl Plane {
857 fn expand_row(&self, y: usize, ratio: usize, out: &mut [u8]) {
863 let source = self.row(y);
864 match ratio {
865 1 => {
866 let count = out.len().min(source.len());
867 if let (Some(to), Some(from)) = (out.get_mut(..count), source.get(..count)) {
868 to.copy_from_slice(from);
869 }
870 }
871 2 => {
872 let half = out.len() / 2;
875 let mut pairs = out.chunks_exact_mut(2);
876 for (pair, &value) in (&mut pairs).zip(source) {
877 pair.copy_from_slice(&[value, value]);
878 }
879 if let ([last], Some(&value)) = (pairs.into_remainder(), source.get(half)) {
881 *last = value;
882 }
883 }
884 _ => {
885 for (x, slot) in out.iter_mut().enumerate() {
886 *slot = self.sample(source, x);
887 }
888 }
889 }
890 }
891
892 fn row(&self, y: usize) -> &[u8] {
894 let row = self.rows.get(y).copied().unwrap_or(0) as usize;
895 self.samples
896 .get(row * self.stride..)
897 .and_then(|rest| rest.get(..self.stride))
898 .unwrap_or(&[])
899 }
900
901 fn sample(&self, row: &[u8], x: usize) -> u8 {
903 let column = self.columns.get(x).copied().unwrap_or(0) as usize;
904 row.get(column).copied().unwrap_or(0)
905 }
906}
907
908fn decode_block<S: Source>(
910 reader: &mut Reader<S>,
911 dc: &HuffmanTable,
912 ac: &HuffmanTable,
913 quant: &[u16; 64],
914 predictor: &mut i32,
915 out: &mut [i32; 64],
916) -> Result<()> {
917 out.fill(0);
918
919 let magnitude = reader.decode(dc)?;
920 if magnitude > 15 {
921 return Err(PixelsError::malformed(
922 "jpeg",
923 format!("DC coefficient claims {magnitude} bits; 15 is the maximum"),
924 ));
925 }
926 let difference = reader.receive_extend(u32::from(magnitude))?;
927 *predictor = predictor.wrapping_add(difference);
932 if let (Some(slot), Some(&step)) = (out.first_mut(), quant.first()) {
933 *slot = predictor.saturating_mul(i32::from(step));
934 }
935
936 let mut index = 1_usize;
937 while index < 64 {
938 let symbol = reader.decode(ac)?;
939 let (run, size) = ((symbol >> 4) as usize, u32::from(symbol & 0x0F));
940 if size == 0 {
941 if run != 15 {
942 break;
945 }
946 index += 16;
949 continue;
950 }
951 index += run;
952 if index > 63 {
953 return Err(PixelsError::malformed(
954 "jpeg",
955 "coefficient run passes the end of the block",
956 ));
957 }
958 let value = reader.receive_extend(size)?;
959 let step = quant.get(index).copied().unwrap_or(0);
960 if let Some(slot) = ZIGZAG.get(index).and_then(|&at| out.get_mut(at)) {
961 *slot = value.saturating_mul(i32::from(step));
962 }
963 index += 1;
964 }
965 Ok(())
966}
967
968#[inline]
985pub(crate) fn ycbcr_to_rgb([y, cb, cr]: [u8; 3]) -> [u8; 3] {
986 const HALF: i32 = 1 << 15;
987 let luma = (i32::from(y) << 16) + HALF;
988 let blue = i32::from(i16::from(cb) - 128);
989 let red = i32::from(i16::from(cr) - 128);
990
991 let r = (luma + (red << 16) + 26_345 * red) >> 16;
992 let g = (luma - 22_554 * blue - (red << 16) + 18_734 * red) >> 16;
993 let b = (luma + (blue << 17) - 14_942 * blue) >> 16;
994 [
995 r.clamp(0, 255) as u8,
996 g.clamp(0, 255) as u8,
997 b.clamp(0, 255) as u8,
998 ]
999}
1000
1001fn colour_model(frame: &Frame, adobe: Option<AdobeTransform>) -> Result<Colour> {
1003 match frame.components.len() {
1004 1 => Ok(Colour::Grayscale),
1005 3 => {
1006 let labelled_rgb = frame.components.iter().map(|c| c.id).eq(*b"RGB");
1009 Ok(if adobe == Some(AdobeTransform::None) || labelled_rgb {
1010 Colour::Rgb
1011 } else {
1012 Colour::YCbCr
1013 })
1014 }
1015 count => Err(PixelsError::unsupported(format!(
1019 "jpeg: {count}-component images (CMYK/YCCK)"
1020 ))),
1021 }
1022}
1023
1024fn check_baseline_scan(scan: &Scan) -> Result<()> {
1027 if scan.spectral_start != 0 || scan.spectral_end != 63 {
1028 return Err(PixelsError::malformed(
1029 "jpeg",
1030 format!(
1031 "baseline scan selects coefficients {}..={}; it must select all 64",
1032 scan.spectral_start, scan.spectral_end
1033 ),
1034 ));
1035 }
1036 if scan.approx_high != 0 || scan.approx_low != 0 {
1037 return Err(PixelsError::malformed(
1038 "jpeg",
1039 "baseline scan uses successive approximation, which is progressive only",
1040 ));
1041 }
1042 Ok(())
1043}
1044
1045fn read_quantization_tables(payload: &[u8], quant: &mut [[u16; 64]; 4]) -> Result<()> {
1047 let mut at = 0_usize;
1048 while at < payload.len() {
1049 let Some(&header) = payload.get(at) else {
1050 break;
1051 };
1052 at += 1;
1053 let (precision, slot) = (header >> 4, (header & 0x0F) as usize);
1054 if slot > 3 {
1055 return Err(PixelsError::malformed(
1056 "jpeg",
1057 format!("DQT names table {slot}; only 0..=3 exist"),
1058 ));
1059 }
1060 let wide = match precision {
1061 0 => false,
1062 1 => true,
1063 other => {
1064 return Err(PixelsError::malformed(
1065 "jpeg",
1066 format!("DQT declares precision {other}; only 0 and 1 exist"),
1067 ));
1068 }
1069 };
1070
1071 let Some(table) = quant.get_mut(slot) else {
1072 break;
1073 };
1074 for entry in table.iter_mut() {
1075 let value = if wide {
1076 let (Some(&hi), Some(&lo)) = (payload.get(at), payload.get(at + 1)) else {
1077 return Err(PixelsError::malformed("jpeg", "DQT segment is truncated"));
1078 };
1079 at += 2;
1080 u16::from_be_bytes([hi, lo])
1081 } else {
1082 let Some(&value) = payload.get(at) else {
1083 return Err(PixelsError::malformed("jpeg", "DQT segment is truncated"));
1084 };
1085 at += 1;
1086 u16::from(value)
1087 };
1088 *entry = value;
1089 }
1090 }
1091 Ok(())
1092}
1093
1094fn read_huffman_tables(
1096 payload: &[u8],
1097 dc_tables: &mut [Option<HuffmanTable>; 4],
1098 ac_tables: &mut [Option<HuffmanTable>; 4],
1099) -> Result<()> {
1100 let mut at = 0_usize;
1101 while at < payload.len() {
1102 let Some(&header) = payload.get(at) else {
1103 break;
1104 };
1105 at += 1;
1106 let (class, slot) = (header >> 4, (header & 0x0F) as usize);
1107 if slot > 3 || class > 1 {
1108 return Err(PixelsError::malformed(
1109 "jpeg",
1110 format!("DHT names class {class} table {slot}; classes are 0..=1, slots 0..=3"),
1111 ));
1112 }
1113
1114 let Some(counts) = payload.get(at..at + 16) else {
1115 return Err(PixelsError::malformed(
1116 "jpeg",
1117 "DHT segment ends inside its code-length counts",
1118 ));
1119 };
1120 let mut lengths = [0_u8; 16];
1121 lengths.copy_from_slice(counts);
1122 at += 16;
1123
1124 let total: usize = lengths.iter().map(|&c| c as usize).sum();
1125 let Some(values) = payload.get(at..at + total) else {
1126 return Err(PixelsError::malformed(
1127 "jpeg",
1128 "DHT segment ends inside its symbol list",
1129 ));
1130 };
1131 at += total;
1132
1133 let table = HuffmanTable::new(&lengths, values.to_vec())?;
1134 let slots = if class == 0 {
1135 &mut *dc_tables
1136 } else {
1137 &mut *ac_tables
1138 };
1139 if let Some(entry) = slots.get_mut(slot) {
1140 *entry = Some(table);
1141 }
1142 }
1143 Ok(())
1144}
1145
1146impl<S: Source + std::fmt::Debug> Decoder for Engine<S> {
1147 fn descriptor(&self) -> ImageDescriptor {
1148 self.descriptor
1149 }
1150
1151 fn capability(&self) -> DecodeCapability {
1152 DecodeCapability::Sequential
1153 }
1154
1155 fn reduced_descriptor(&self, target: (u32, u32)) -> Option<ImageDescriptor> {
1156 if self.row > 0 || self.mcu_row > 0 {
1158 return None;
1159 }
1160 let full = (u32::from(self.frame.width), u32::from(self.frame.height));
1161 let scale = Scale::fitting(full, target);
1162 if scale == self.scale {
1163 return None;
1164 }
1165 ImageDescriptor::new(
1166 scale.apply(full.0),
1167 scale.apply(full.1),
1168 self.descriptor.pixel,
1169 )
1170 .ok()
1171 }
1172
1173 fn reduce_to(&mut self, descriptor: ImageDescriptor) -> Result<()> {
1174 let full = (u32::from(self.frame.width), u32::from(self.frame.height));
1175 let scale = Scale::ALL
1179 .into_iter()
1180 .find(|scale| {
1181 scale.apply(full.0) == descriptor.width && scale.apply(full.1) == descriptor.height
1182 })
1183 .ok_or_else(|| {
1184 PixelsError::invalid_argument(
1185 "descriptor",
1186 format!(
1187 "{}x{} is not an M/8 scale of {}x{}",
1188 descriptor.width, descriptor.height, full.0, full.1
1189 ),
1190 )
1191 })?;
1192 self.apply_scale(scale)
1193 }
1194
1195 fn read_row(&mut self, out: &mut [u8]) -> Result<()> {
1196 if self.row >= self.descriptor.height {
1197 return Err(PixelsError::invalid_argument(
1198 "out",
1199 format!("all {} rows have already been read", self.descriptor.height),
1200 ));
1201 }
1202 let row_bytes = self.descriptor.row_bytes();
1203 if out.len() != row_bytes {
1204 return Err(PixelsError::invalid_argument(
1205 "out",
1206 format!("row buffer is {} bytes, expected {row_bytes}", out.len()),
1207 ));
1208 }
1209
1210 if self.band_row >= self.band_height {
1211 self.fill_band()?;
1212 }
1213 let start = self.band_row as usize * row_bytes;
1214 let row = self
1215 .band
1216 .get(start..)
1217 .and_then(|rest| rest.get(..row_bytes))
1218 .ok_or_else(|| PixelsError::malformed("jpeg", "MCU row band is short"))?;
1219 out.copy_from_slice(row);
1220 self.band_row += 1;
1221 self.row += 1;
1222 Ok(())
1223 }
1224}
1225
1226#[must_use]
1230pub fn probe(prefix: &[u8]) -> bool {
1231 prefix.get(..3) == Some(&crate::format::SIGNATURE[..])
1232}
1233
1234#[derive(Debug, Clone, Copy, Default)]
1236pub struct JpegCodec;
1237
1238impl Codec for JpegCodec {
1239 fn format(&self) -> Format {
1240 Format::Jpeg
1241 }
1242
1243 fn magic_len(&self) -> usize {
1244 3
1245 }
1246
1247 fn probe(&self, prefix: &[u8]) -> bool {
1248 probe(prefix)
1249 }
1250}
1251
1252#[cfg(test)]
1253mod tests {
1254 use super::*;
1255
1256 #[test]
1259 fn colour_conversion_matches_the_direct_formula_everywhere() {
1260 fn direct([y, cb, cr]: [u8; 3]) -> [u8; 3] {
1261 const HALF: i32 = 1 << 15;
1262 let luma = i32::from(y) << 16;
1263 let blue = i32::from(cb) - 128;
1264 let red = i32::from(cr) - 128;
1265 let r = (luma + 91_881 * red + HALF) >> 16;
1266 let g = (luma - 22_554 * blue - 46_802 * red + HALF) >> 16;
1267 let b = (luma + 116_130 * blue + HALF) >> 16;
1268 [r, g, b].map(|v| v.clamp(0, 255) as u8)
1269 }
1270 for y in 0..=255 {
1271 for cb in 0..=255 {
1272 for cr in 0..=255 {
1273 assert_eq!(ycbcr_to_rgb([y, cb, cr]), direct([y, cb, cr]));
1274 }
1275 }
1276 }
1277 }
1278}