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, Comment, DecodeCapability, Decoder, Density, Format, ImageDescriptor, Limits,
35 Orientation, PixelFormat, PixelsError, ResolutionUnit, Result, Source, SourceInfo,
36};
37
38const XMP_ID: &[u8] = b"http://ns.adobe.com/xap/1.0/\0";
40
41fn jfif_density(payload: &[u8]) -> Option<Density> {
43 let rest = payload.strip_prefix(b"JFIF\0")?;
44 let (&unit, rest) = rest.get(2..)?.split_first()?;
45 let x = u16::from_be_bytes([*rest.first()?, *rest.get(1)?]);
46 let y = u16::from_be_bytes([*rest.get(2)?, *rest.get(3)?]);
47 let unit = match unit {
48 1 => ResolutionUnit::Inch,
49 2 => ResolutionUnit::Centimetre,
50 _ => return None,
52 };
53 Some(Density {
54 x: f64::from(x),
55 y: f64::from(y),
56 unit,
57 })
58}
59
60fn chroma_name(frame: &Frame) -> Option<&'static str> {
63 let [luma, chroma, ..] = &frame.components[..] else {
64 return None;
65 };
66 let (h, v) = (luma.h / chroma.h.max(1), luma.v / chroma.v.max(1));
67 let base = match (h, v) {
68 (1, 1) => "4:4:4",
69 (2, 1) => "4:2:2",
70 (2, 2) => "4:2:0",
71 (1, 2) => "4:4:0",
72 (4, 1) => "4:1:1",
73 (4, 2) => "4:1:0",
74 _ => return None,
75 };
76 Some(if frame.components.len() == 4 {
77 match base {
78 "4:4:4" => "4:4:4:4",
79 "4:2:2" => "4:2:2:4",
80 "4:2:0" => "4:2:0:4",
81 "4:4:0" => "4:4:0:4",
82 "4:1:1" => "4:1:1:4",
83 _ => "4:1:0:4",
84 }
85 } else {
86 base
87 })
88}
89
90const MAX_BLOCKS_PER_MCU: u32 = 10;
96
97#[derive(Debug, Clone, Copy, PartialEq, Eq)]
99enum Colour {
100 Grayscale,
102 YCbCr,
104 Rgb,
106}
107
108#[derive(Debug)]
110struct Plane {
111 stride: usize,
113 samples: Vec<u8>,
115 columns: Vec<u32>,
118 rows: Vec<u32>,
120}
121
122#[derive(Debug)]
128pub struct JpegDecoder<S: Source> {
129 inner: Box<Engine<S>>,
130 icc: Option<Vec<u8>>,
132 exif: Option<Vec<u8>>,
134 xmp: Option<Vec<u8>>,
136 info: SourceInfo,
138}
139
140impl<S: Source> JpegDecoder<S> {
141 pub fn new(source: S, limits: Limits) -> Result<Self> {
154 Self::with_scale(source, limits, Scale::Full)
155 }
156
157 pub fn with_scale(source: S, limits: Limits, scale: Scale) -> Result<Self> {
172 let mut inner = Box::new(Engine::with_scale(source, limits, scale)?);
173 let icc = inner.icc.take();
174 let exif = inner.exif.take();
175 let xmp = inner.xmp.take();
176 let info = std::mem::take(&mut inner.info);
177 Ok(Self {
178 inner,
179 icc,
180 exif,
181 xmp,
182 info,
183 })
184 }
185
186 #[must_use]
188 pub const fn scale(&self) -> Scale {
189 self.inner.scale
190 }
191
192 #[must_use]
194 pub const fn is_progressive(&self) -> bool {
195 self.inner.progressive
196 }
197}
198
199impl<S: Source + std::fmt::Debug> Decoder for JpegDecoder<S> {
200 fn descriptor(&self) -> ImageDescriptor {
201 self.inner.descriptor()
202 }
203
204 fn icc_profile(&self) -> Option<&[u8]> {
206 self.icc.as_deref()
207 }
208
209 fn exif(&self) -> Option<&[u8]> {
211 self.exif.as_deref()
212 }
213
214 fn xmp(&self) -> Option<&[u8]> {
217 self.xmp.as_deref()
218 }
219
220 fn source_info(&self) -> SourceInfo {
223 self.info.clone()
224 }
225
226 fn orientation(&self) -> Orientation {
228 self.inner.orientation.unwrap_or_default()
229 }
230
231 fn capability(&self) -> DecodeCapability {
232 self.inner.capability()
233 }
234
235 fn reduced_descriptor(&self, target: (u32, u32)) -> Option<ImageDescriptor> {
236 self.inner.reduced_descriptor(target)
237 }
238
239 fn reduce_to(&mut self, descriptor: ImageDescriptor) -> Result<()> {
240 self.inner.reduce_to(descriptor)
241 }
242
243 fn read_row(&mut self, out: &mut [u8]) -> Result<()> {
244 self.inner.read_row(out)
245 }
246}
247
248struct Engine<S: Source> {
250 reader: Reader<S>,
251 descriptor: ImageDescriptor,
252 frame: Frame,
253 scan: Scan,
254 colour: Colour,
255 quant: [[u16; 64]; 4],
257 dc_tables: [Option<HuffmanTable>; 4],
259 ac_tables: [Option<HuffmanTable>; 4],
260 restart_interval: u16,
262 restarts_left: u32,
264 orientation: Option<Orientation>,
266 icc: Option<Vec<u8>>,
268 exif: Option<Vec<u8>>,
270 xmp: Option<Vec<u8>>,
272 info: SourceInfo,
274 planes: Vec<Plane>,
275 expanded: Vec<Vec<u8>>,
278 predictors: Vec<i32>,
280 band: Vec<u8>,
282 band_row: u32,
284 band_height: u32,
286 mcus_per_line: u32,
287 mcu_rows: u32,
288 mcu_row: u32,
290 scale: Scale,
292 limits: Limits,
294 row: u32,
296 progressive: bool,
298 coefficients: Option<Coefficients>,
300 latched: Vec<Option<[u16; 64]>>,
303}
304
305impl<S: Source> std::fmt::Debug for Engine<S> {
306 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
307 f.debug_struct("Engine")
308 .field("descriptor", &self.descriptor)
309 .field("colour", &self.colour)
310 .field("components", &self.frame.components)
311 .field("scale", &self.scale)
312 .field("restart_interval", &self.restart_interval)
313 .field("orientation", &self.orientation)
314 .field("row", &self.row)
315 .finish_non_exhaustive()
316 }
317}
318
319impl<S: Source> Engine<S> {
320 fn with_scale(source: S, limits: Limits, scale: Scale) -> Result<Self> {
347 let mut reader = Reader::new(source);
348 let mut progressive = false;
349 let mut quant = [[0_u16; 64]; 4];
350 let mut dc_tables: [Option<HuffmanTable>; 4] = [None, None, None, None];
351 let mut ac_tables: [Option<HuffmanTable>; 4] = [None, None, None, None];
352 let mut restart_interval = 0_u16;
353 let mut orientation = None;
354 let mut exif: Option<Vec<u8>> = None;
355 let mut xmp: Option<Vec<u8>> = None;
356 let mut jfif = None;
357 let mut comments = Vec::new();
358 let mut icc = IccChunks::default();
359 let mut adobe = None;
360 let mut frame: Option<Frame> = None;
361
362 let first = reader.next_marker()?;
363 if first != marker::SOI {
364 return Err(PixelsError::malformed(
365 "jpeg",
366 format!("stream begins with marker {first:#04x}, not SOI"),
367 ));
368 }
369
370 let scan = loop {
371 let code = reader.next_marker()?;
372 match code {
373 marker::SOF0 | marker::SOF1 | marker::SOF2 => {
374 if frame.is_some() {
375 return Err(PixelsError::malformed(
376 "jpeg",
377 "stream declares more than one frame",
378 ));
379 }
380 progressive = code == marker::SOF2;
381 let parsed = Frame::parse(&reader.read_segment()?)?;
382 limits.check(u32::from(parsed.width), u32::from(parsed.height))?;
386 frame = Some(parsed);
387 }
388 marker::DAC => {
389 return Err(PixelsError::unsupported(
390 "jpeg: arithmetic coding; baseline JPEG is Huffman coded",
391 ));
392 }
393 code if marker::is_frame(code) => {
394 return Err(PixelsError::unsupported(format!(
395 "jpeg: frame type {code:#04x} is not baseline"
396 )));
397 }
398 marker::DQT => read_quantization_tables(&reader.read_segment()?, &mut quant)?,
399 marker::DHT => {
400 read_huffman_tables(&reader.read_segment()?, &mut dc_tables, &mut ac_tables)?;
401 }
402 marker::DRI => {
403 let payload = reader.read_segment()?;
404 let (Some(&hi), Some(&lo)) = (payload.first(), payload.get(1)) else {
405 return Err(PixelsError::malformed(
406 "jpeg",
407 "DRI segment carries no interval",
408 ));
409 };
410 restart_interval = u16::from_be_bytes([hi, lo]);
411 }
412 marker::APP1 => {
413 let payload = reader.read_segment()?;
414 orientation = orientation.or_else(|| exif_orientation(&payload));
416 if exif.is_none() {
417 exif = payload.strip_prefix(b"Exif\0\0").map(<[u8]>::to_vec);
418 }
419 if xmp.is_none() {
420 xmp = payload.strip_prefix(XMP_ID).map(<[u8]>::to_vec);
421 }
422 }
423 marker::APP0 => {
424 let payload = reader.read_segment()?;
425 jfif = jfif.or_else(|| jfif_density(&payload));
426 }
427 marker::COM => {
428 let payload = reader.read_segment()?;
429 let text = String::from_utf8(payload.clone())
430 .unwrap_or_else(|_| payload.iter().map(|&c| char::from(c)).collect());
431 comments.push(Comment {
432 keyword: String::new(),
433 text: text.trim_end_matches('\0').to_owned(),
434 });
435 }
436 marker::APP2 => icc.push(&reader.read_segment()?),
437 marker::APP14 => {
438 let payload = reader.read_segment()?;
439 adobe = adobe_transform(&payload).or(adobe);
440 }
441 marker::SOS => {
442 let Some(ref frame) = frame else {
443 return Err(PixelsError::malformed(
444 "jpeg",
445 "scan begins before any frame header",
446 ));
447 };
448 break Scan::parse(&reader.read_segment()?, frame)?;
449 }
450 marker::EOI => {
451 return Err(PixelsError::malformed(
452 "jpeg",
453 "stream ends before any scan",
454 ));
455 }
456 0xE3..=0xED | marker::APP15 => reader.skip_segment()?,
462 code if marker::is_standalone(code) => {}
463 _ => reader.skip_segment()?,
464 }
465 };
466
467 let Some(frame) = frame else {
468 return Err(PixelsError::malformed("jpeg", "stream has no frame header"));
469 };
470 if !progressive {
473 check_baseline_scan(&scan)?;
474 }
475
476 let mut info = SourceInfo::default();
477 info.density = jfif.or_else(|| {
478 exif.as_deref()
479 .and_then(otf_pixels_core::Density::from_exif_block)
480 });
481 info.progressive = progressive;
482 info.bits_per_sample = Some(frame.precision);
483 info.chroma_subsampling = chroma_name(&frame);
484 info.comments = comments;
485 let colour = colour_model(&frame, adobe)?;
486 let pixel = match colour {
487 Colour::Grayscale => PixelFormat::Gray8,
488 Colour::YCbCr | Colour::Rgb => PixelFormat::Rgb8,
489 };
490 let (full_width, full_height) = (u32::from(frame.width), u32::from(frame.height));
494 let descriptor = ImageDescriptor::with_limits(
495 scale.apply(full_width),
496 scale.apply(full_height),
497 pixel,
498 &limits,
499 )?;
500
501 if !progressive && scan.components.len() != frame.components.len() {
502 return Err(PixelsError::unsupported(
507 "jpeg: non-interleaved baseline scan",
508 ));
509 }
510 for component in scan.components.iter().filter(|_| !progressive) {
511 let slot = frame
512 .components
513 .get(component.index)
514 .map_or(0, |c| c.quant as usize);
515 if quant
516 .get(slot)
517 .is_none_or(|table| table.iter().all(|&q| q == 0))
518 {
519 return Err(PixelsError::malformed(
520 "jpeg",
521 format!("scan uses quantization table {slot}, which was never defined"),
522 ));
523 }
524 if dc_tables
525 .get(component.dc as usize)
526 .is_none_or(Option::is_none)
527 || ac_tables
528 .get(component.ac as usize)
529 .is_none_or(Option::is_none)
530 {
531 return Err(PixelsError::malformed(
532 "jpeg",
533 format!(
534 "scan uses Huffman tables {}/{}, which were never defined",
535 component.dc, component.ac
536 ),
537 ));
538 }
539 }
540
541 let (h_max, v_max) = (u32::from(frame.h_max()), u32::from(frame.v_max()));
542 let blocks: u32 = frame
543 .components
544 .iter()
545 .map(|c| u32::from(c.h) * u32::from(c.v))
546 .sum();
547 if blocks > MAX_BLOCKS_PER_MCU {
548 return Err(PixelsError::malformed(
549 "jpeg",
550 format!(
551 "an MCU would hold {blocks} blocks; the format allows {MAX_BLOCKS_PER_MCU}"
552 ),
553 ));
554 }
555
556 let mcus_per_line = full_width.div_ceil(h_max * 8);
557 let mcu_rows = full_height.div_ceil(v_max * 8);
558
559 let mut decoder = Self {
563 reader,
564 descriptor,
565 predictors: vec![0; frame.components.len()],
566 frame,
567 scan,
568 colour,
569 quant,
570 dc_tables,
571 ac_tables,
572 restart_interval,
573 restarts_left: u32::from(restart_interval),
574 orientation,
575 icc: icc.assemble(),
576 exif,
577 xmp,
578 info,
579 limits,
580 planes: Vec::new(),
581 expanded: Vec::new(),
582 band: Vec::new(),
583 band_row: 0,
584 band_height: 0,
585 mcus_per_line,
586 mcu_rows,
587 mcu_row: 0,
588 scale: Scale::Full,
589 row: 0,
590 progressive,
591 coefficients: None,
592 latched: Vec::new(),
593 };
594 decoder.apply_scale(scale)?;
595 Ok(decoder)
596 }
597
598 fn apply_scale(&mut self, scale: Scale) -> Result<()> {
610 if self.row > 0 || self.mcu_row > 0 {
611 return Err(PixelsError::invalid_argument(
612 "scale",
613 format!("{} rows have already been decoded", self.row),
614 ));
615 }
616 let full_width = u32::from(self.frame.width);
617 let full_height = u32::from(self.frame.height);
618 let (h_max, v_max) = (u32::from(self.frame.h_max()), u32::from(self.frame.v_max()));
619 let descriptor = ImageDescriptor::with_limits(
620 scale.apply(full_width),
621 scale.apply(full_height),
622 self.descriptor.pixel,
623 &self.limits,
624 )?;
625
626 let sample = scale.block_size();
629 let band_height = v_max * sample;
630
631 let mut planes = Vec::with_capacity(self.frame.components.len());
632 for component in &self.frame.components {
633 let (h, v) = (u32::from(component.h), u32::from(component.v));
634 let stride = usize::try_from(self.mcus_per_line * h * sample)
635 .map_err(|_| PixelsError::malformed("jpeg", "component plane overflows"))?;
636 let height = usize::try_from(v * sample).unwrap_or(32);
637 let samples = stride
638 .checked_mul(height)
639 .ok_or_else(|| PixelsError::malformed("jpeg", "component plane overflows"))?;
640 planes.push(Plane {
641 stride,
642 samples: vec![0_u8; samples],
643 columns: (0..descriptor.width).map(|x| x * h / h_max).collect(),
649 rows: (0..band_height).map(|y| y * v / v_max).collect(),
650 });
651 }
652
653 let band = descriptor
654 .row_bytes()
655 .checked_mul(band_height as usize)
656 .ok_or_else(|| PixelsError::malformed("jpeg", "MCU row band overflows"))?;
657
658 self.descriptor = descriptor;
659 self.expanded = vec![vec![0_u8; descriptor.width as usize]; planes.len()];
660 self.planes = planes;
661 self.band = vec![0_u8; band];
662 self.band_row = band_height;
665 self.band_height = band_height;
666 self.scale = scale;
667 Ok(())
668 }
669
670 fn fill_band(&mut self) -> Result<()> {
673 if self.mcu_row >= self.mcu_rows {
674 return Err(PixelsError::malformed(
675 "jpeg",
676 "more rows were requested than the frame declares",
677 ));
678 }
679
680 if self.progressive {
681 return self.fill_band_progressive();
682 }
683
684 let mut coefficients = [0_i32; 64];
685 let scale = self.scale;
686 let sample = scale.block_size() as usize;
687 for mcu in 0..self.mcus_per_line {
688 if self.restart_interval > 0 && self.restarts_left == 0 {
689 if !self.reader.restart()? {
690 let met = self.reader.pending_marker();
691 return Err(PixelsError::malformed(
692 "jpeg",
693 match met {
694 Some(code) => format!(
695 "expected a restart marker between MCU intervals, met {code:#04x}"
696 ),
697 None => "expected a restart marker between MCU intervals".to_owned(),
698 },
699 ));
700 }
701 self.predictors.iter_mut().for_each(|p| *p = 0);
705 self.restarts_left = u32::from(self.restart_interval);
706 }
707
708 for scanned in &self.scan.components {
709 let Some(component) = self.frame.components.get(scanned.index) else {
710 continue;
711 };
712 let (h, v) = (u32::from(component.h), u32::from(component.v));
713 let (Some(dc), Some(ac)) = (
714 self.dc_tables
715 .get(scanned.dc as usize)
716 .and_then(Option::as_ref),
717 self.ac_tables
718 .get(scanned.ac as usize)
719 .and_then(Option::as_ref),
720 ) else {
721 return Err(PixelsError::malformed(
722 "jpeg",
723 "scan names a Huffman table that was never defined",
724 ));
725 };
726 let quant = self
727 .quant
728 .get(component.quant as usize)
729 .ok_or_else(|| PixelsError::malformed("jpeg", "component names no table"))?;
730 let Some(predictor) = self.predictors.get_mut(scanned.index) else {
731 continue;
732 };
733 let Some(plane) = self.planes.get_mut(scanned.index) else {
734 continue;
735 };
736
737 for block_y in 0..v {
738 for block_x in 0..h {
739 decode_block(
740 &mut self.reader,
741 dc,
742 ac,
743 quant,
744 predictor,
745 &mut coefficients,
746 )?;
747 let x = ((mcu * h) + block_x) as usize * sample;
748 let y = block_y as usize * sample;
749 idct::scaled_block(
750 &coefficients,
751 scale,
752 &mut plane.samples,
753 y * plane.stride + x,
754 plane.stride,
755 );
756 }
757 }
758 }
759
760 if self.restart_interval > 0 {
761 self.restarts_left = self.restarts_left.saturating_sub(1);
762 }
763 }
764
765 self.mcu_row += 1;
766 self.convert_band();
767 self.band_row = 0;
768 Ok(())
769 }
770
771 fn fill_band_progressive(&mut self) -> Result<()> {
774 if self.coefficients.is_none() {
775 self.decode_scans()?;
776 }
777 let Some(stored) = self.coefficients.as_ref() else {
778 return Err(PixelsError::graph(
779 "jpeg coefficients vanished after decoding",
780 ));
781 };
782
783 let mut natural = [0_i32; 64];
784 let scale = self.scale;
785 let sample = scale.block_size() as usize;
786 let mcu_row = self.mcu_row as usize;
787 for (index, component) in self.frame.components.iter().enumerate() {
788 let (h, v) = (usize::from(component.h), usize::from(component.v));
789 let quant = self
792 .latched
793 .get(index)
794 .copied()
795 .flatten()
796 .or_else(|| self.quant.get(component.quant as usize).copied())
797 .unwrap_or([0; 64]);
798 let Some(plane) = self.planes.get_mut(index) else {
799 continue;
800 };
801 for block_y in 0..v {
802 for block_x in 0..h * self.mcus_per_line as usize {
803 let Some(block) = stored.block(index, block_x, mcu_row * v + block_y) else {
804 continue;
805 };
806 for ((&at, &value), &step) in ZIGZAG.iter().zip(block).zip(&quant) {
807 if let Some(slot) = natural.get_mut(at) {
808 *slot = i32::from(value) * i32::from(step);
809 }
810 }
811 idct::scaled_block(
812 &natural,
813 scale,
814 &mut plane.samples,
815 block_y * sample * plane.stride + block_x * sample,
816 plane.stride,
817 );
818 }
819 }
820 }
821
822 self.mcu_row += 1;
823 self.convert_band();
824 self.band_row = 0;
825 Ok(())
826 }
827
828 fn decode_scans(&mut self) -> Result<()> {
835 let mut coefficients = Coefficients::new(&self.frame, self.mcus_per_line, self.mcu_rows)?;
836 let mut latched: Vec<Option<[u16; 64]>> = vec![None; self.frame.components.len()];
837 let mut scan = self.scan.clone();
838 let mut scans = 0;
839 'scans: loop {
840 scans += 1;
841 if scans > MAX_SCANS {
842 return Err(PixelsError::malformed(
843 "jpeg",
844 format!("progressive frame has more than {MAX_SCANS} scans"),
845 ));
846 }
847 for scanned in &scan.components {
849 let Some(component) = self.frame.components.get(scanned.index) else {
850 continue;
851 };
852 if let Some(slot) = latched.get_mut(scanned.index) {
853 if slot.is_none() {
854 *slot = self.quant.get(component.quant as usize).copied();
855 }
856 }
857 }
858 let tables = Tables {
859 dc: &self.dc_tables,
860 ac: &self.ac_tables,
861 restart_interval: self.restart_interval,
862 };
863 crate::progressive::decode_scan(
864 &mut self.reader,
865 &self.frame,
866 &scan,
867 &tables,
868 &mut coefficients,
869 )?;
870 self.reader.align();
871
872 loop {
873 let Ok(code) = self.reader.next_marker() else {
876 break 'scans;
877 };
878 match code {
879 marker::SOS => {
880 scan = Scan::parse(&self.reader.read_segment()?, &self.frame)?;
881 continue 'scans;
882 }
883 marker::EOI => break 'scans,
884 marker::DHT => read_huffman_tables(
885 &self.reader.read_segment()?,
886 &mut self.dc_tables,
887 &mut self.ac_tables,
888 )?,
889 marker::DQT => {
890 read_quantization_tables(&self.reader.read_segment()?, &mut self.quant)?;
891 }
892 marker::DRI => {
893 let payload = self.reader.read_segment()?;
894 let (Some(&hi), Some(&lo)) = (payload.first(), payload.get(1)) else {
895 return Err(PixelsError::malformed(
896 "jpeg",
897 "DRI segment carries no interval",
898 ));
899 };
900 self.restart_interval = u16::from_be_bytes([hi, lo]);
901 }
902 code if marker::is_frame(code) => {
903 return Err(PixelsError::malformed(
904 "jpeg",
905 "stream declares more than one frame",
906 ));
907 }
908 code if marker::is_standalone(code) => {}
910 _ => self.reader.skip_segment()?,
911 }
912 }
913 }
914 self.coefficients = Some(coefficients);
915 self.latched = latched;
916 Ok(())
917 }
918
919 fn convert_band(&mut self) {
927 let width = self.descriptor.width as usize;
928 let row_bytes = self.descriptor.row_bytes();
929 let h_max = usize::from(self.frame.h_max());
930
931 for y in 0..self.band_height as usize {
932 let Some(out) = self
933 .band
934 .get_mut(y * row_bytes..)
935 .and_then(|rest| rest.get_mut(..row_bytes))
936 else {
937 continue;
938 };
939 for ((plane, component), expanded) in self
940 .planes
941 .iter()
942 .zip(&self.frame.components)
943 .zip(self.expanded.iter_mut())
944 {
945 let h = usize::from(component.h.max(1));
947 let ratio = if h_max % h == 0 { h_max / h } else { 0 };
948 plane.expand_row(y, ratio, expanded);
949 }
950
951 match self.colour {
952 Colour::Grayscale => {
953 if let Some(grey) = self.expanded.first() {
954 for (slot, &value) in out.iter_mut().zip(grey).take(width) {
955 *slot = value;
956 }
957 }
958 }
959 Colour::Rgb => {
960 let [a, b, c] =
961 [0, 1, 2].map(|i| self.expanded.get(i).map_or(&[][..], Vec::as_slice));
962 for (pixel, ((&r, &g), &b)) in
963 out.chunks_exact_mut(3).zip(a.iter().zip(b).zip(c))
964 {
965 pixel.copy_from_slice(&[r, g, b]);
966 }
967 }
968 Colour::YCbCr => {
969 let [luma, cb, cr] =
970 [0, 1, 2].map(|i| self.expanded.get(i).map_or(&[][..], Vec::as_slice));
971 for (pixel, ((&y, &cb), &cr)) in
972 out.chunks_exact_mut(3).zip(luma.iter().zip(cb).zip(cr))
973 {
974 pixel.copy_from_slice(&ycbcr_to_rgb([y, cb, cr]));
975 }
976 }
977 }
978 }
979 }
980}
981
982impl Plane {
983 fn expand_row(&self, y: usize, ratio: usize, out: &mut [u8]) {
989 let source = self.row(y);
990 match ratio {
991 1 => {
992 let count = out.len().min(source.len());
993 if let (Some(to), Some(from)) = (out.get_mut(..count), source.get(..count)) {
994 to.copy_from_slice(from);
995 }
996 }
997 2 => {
998 let half = out.len() / 2;
1001 let mut pairs = out.chunks_exact_mut(2);
1002 for (pair, &value) in (&mut pairs).zip(source) {
1003 pair.copy_from_slice(&[value, value]);
1004 }
1005 if let ([last], Some(&value)) = (pairs.into_remainder(), source.get(half)) {
1007 *last = value;
1008 }
1009 }
1010 _ => {
1011 for (x, slot) in out.iter_mut().enumerate() {
1012 *slot = self.sample(source, x);
1013 }
1014 }
1015 }
1016 }
1017
1018 fn row(&self, y: usize) -> &[u8] {
1020 let row = self.rows.get(y).copied().unwrap_or(0) as usize;
1021 self.samples
1022 .get(row * self.stride..)
1023 .and_then(|rest| rest.get(..self.stride))
1024 .unwrap_or(&[])
1025 }
1026
1027 fn sample(&self, row: &[u8], x: usize) -> u8 {
1029 let column = self.columns.get(x).copied().unwrap_or(0) as usize;
1030 row.get(column).copied().unwrap_or(0)
1031 }
1032}
1033
1034fn decode_block<S: Source>(
1036 reader: &mut Reader<S>,
1037 dc: &HuffmanTable,
1038 ac: &HuffmanTable,
1039 quant: &[u16; 64],
1040 predictor: &mut i32,
1041 out: &mut [i32; 64],
1042) -> Result<()> {
1043 out.fill(0);
1044
1045 let magnitude = reader.decode(dc)?;
1046 if magnitude > 15 {
1047 return Err(PixelsError::malformed(
1048 "jpeg",
1049 format!("DC coefficient claims {magnitude} bits; 15 is the maximum"),
1050 ));
1051 }
1052 let difference = reader.receive_extend(u32::from(magnitude))?;
1053 *predictor = predictor.wrapping_add(difference);
1058 if let (Some(slot), Some(&step)) = (out.first_mut(), quant.first()) {
1059 *slot = predictor.saturating_mul(i32::from(step));
1060 }
1061
1062 let mut index = 1_usize;
1063 while index < 64 {
1064 let symbol = reader.decode(ac)?;
1065 let (run, size) = ((symbol >> 4) as usize, u32::from(symbol & 0x0F));
1066 if size == 0 {
1067 if run != 15 {
1068 break;
1071 }
1072 index += 16;
1075 continue;
1076 }
1077 index += run;
1078 if index > 63 {
1079 return Err(PixelsError::malformed(
1080 "jpeg",
1081 "coefficient run passes the end of the block",
1082 ));
1083 }
1084 let value = reader.receive_extend(size)?;
1085 let step = quant.get(index).copied().unwrap_or(0);
1086 if let Some(slot) = ZIGZAG.get(index).and_then(|&at| out.get_mut(at)) {
1087 *slot = value.saturating_mul(i32::from(step));
1088 }
1089 index += 1;
1090 }
1091 Ok(())
1092}
1093
1094#[inline]
1111pub(crate) fn ycbcr_to_rgb([y, cb, cr]: [u8; 3]) -> [u8; 3] {
1112 const HALF: i32 = 1 << 15;
1113 let luma = (i32::from(y) << 16) + HALF;
1114 let blue = i32::from(i16::from(cb) - 128);
1115 let red = i32::from(i16::from(cr) - 128);
1116
1117 let r = (luma + (red << 16) + 26_345 * red) >> 16;
1118 let g = (luma - 22_554 * blue - (red << 16) + 18_734 * red) >> 16;
1119 let b = (luma + (blue << 17) - 14_942 * blue) >> 16;
1120 [
1121 r.clamp(0, 255) as u8,
1122 g.clamp(0, 255) as u8,
1123 b.clamp(0, 255) as u8,
1124 ]
1125}
1126
1127fn colour_model(frame: &Frame, adobe: Option<AdobeTransform>) -> Result<Colour> {
1129 match frame.components.len() {
1130 1 => Ok(Colour::Grayscale),
1131 3 => {
1132 let labelled_rgb = frame.components.iter().map(|c| c.id).eq(*b"RGB");
1135 Ok(if adobe == Some(AdobeTransform::None) || labelled_rgb {
1136 Colour::Rgb
1137 } else {
1138 Colour::YCbCr
1139 })
1140 }
1141 count => Err(PixelsError::unsupported(format!(
1145 "jpeg: {count}-component images (CMYK/YCCK)"
1146 ))),
1147 }
1148}
1149
1150fn check_baseline_scan(scan: &Scan) -> Result<()> {
1153 if scan.spectral_start != 0 || scan.spectral_end != 63 {
1154 return Err(PixelsError::malformed(
1155 "jpeg",
1156 format!(
1157 "baseline scan selects coefficients {}..={}; it must select all 64",
1158 scan.spectral_start, scan.spectral_end
1159 ),
1160 ));
1161 }
1162 if scan.approx_high != 0 || scan.approx_low != 0 {
1163 return Err(PixelsError::malformed(
1164 "jpeg",
1165 "baseline scan uses successive approximation, which is progressive only",
1166 ));
1167 }
1168 Ok(())
1169}
1170
1171fn read_quantization_tables(payload: &[u8], quant: &mut [[u16; 64]; 4]) -> Result<()> {
1173 let mut at = 0_usize;
1174 while at < payload.len() {
1175 let Some(&header) = payload.get(at) else {
1176 break;
1177 };
1178 at += 1;
1179 let (precision, slot) = (header >> 4, (header & 0x0F) as usize);
1180 if slot > 3 {
1181 return Err(PixelsError::malformed(
1182 "jpeg",
1183 format!("DQT names table {slot}; only 0..=3 exist"),
1184 ));
1185 }
1186 let wide = match precision {
1187 0 => false,
1188 1 => true,
1189 other => {
1190 return Err(PixelsError::malformed(
1191 "jpeg",
1192 format!("DQT declares precision {other}; only 0 and 1 exist"),
1193 ));
1194 }
1195 };
1196
1197 let Some(table) = quant.get_mut(slot) else {
1198 break;
1199 };
1200 for entry in table.iter_mut() {
1201 let value = if wide {
1202 let (Some(&hi), Some(&lo)) = (payload.get(at), payload.get(at + 1)) else {
1203 return Err(PixelsError::malformed("jpeg", "DQT segment is truncated"));
1204 };
1205 at += 2;
1206 u16::from_be_bytes([hi, lo])
1207 } else {
1208 let Some(&value) = payload.get(at) else {
1209 return Err(PixelsError::malformed("jpeg", "DQT segment is truncated"));
1210 };
1211 at += 1;
1212 u16::from(value)
1213 };
1214 *entry = value;
1215 }
1216 }
1217 Ok(())
1218}
1219
1220fn read_huffman_tables(
1222 payload: &[u8],
1223 dc_tables: &mut [Option<HuffmanTable>; 4],
1224 ac_tables: &mut [Option<HuffmanTable>; 4],
1225) -> Result<()> {
1226 let mut at = 0_usize;
1227 while at < payload.len() {
1228 let Some(&header) = payload.get(at) else {
1229 break;
1230 };
1231 at += 1;
1232 let (class, slot) = (header >> 4, (header & 0x0F) as usize);
1233 if slot > 3 || class > 1 {
1234 return Err(PixelsError::malformed(
1235 "jpeg",
1236 format!("DHT names class {class} table {slot}; classes are 0..=1, slots 0..=3"),
1237 ));
1238 }
1239
1240 let Some(counts) = payload.get(at..at + 16) else {
1241 return Err(PixelsError::malformed(
1242 "jpeg",
1243 "DHT segment ends inside its code-length counts",
1244 ));
1245 };
1246 let mut lengths = [0_u8; 16];
1247 lengths.copy_from_slice(counts);
1248 at += 16;
1249
1250 let total: usize = lengths.iter().map(|&c| c as usize).sum();
1251 let Some(values) = payload.get(at..at + total) else {
1252 return Err(PixelsError::malformed(
1253 "jpeg",
1254 "DHT segment ends inside its symbol list",
1255 ));
1256 };
1257 at += total;
1258
1259 let table = HuffmanTable::new(&lengths, values.to_vec())?;
1260 let slots = if class == 0 {
1261 &mut *dc_tables
1262 } else {
1263 &mut *ac_tables
1264 };
1265 if let Some(entry) = slots.get_mut(slot) {
1266 *entry = Some(table);
1267 }
1268 }
1269 Ok(())
1270}
1271
1272impl<S: Source + std::fmt::Debug> Decoder for Engine<S> {
1273 fn descriptor(&self) -> ImageDescriptor {
1274 self.descriptor
1275 }
1276
1277 fn capability(&self) -> DecodeCapability {
1278 DecodeCapability::Sequential
1279 }
1280
1281 fn reduced_descriptor(&self, target: (u32, u32)) -> Option<ImageDescriptor> {
1282 if self.row > 0 || self.mcu_row > 0 {
1284 return None;
1285 }
1286 let full = (u32::from(self.frame.width), u32::from(self.frame.height));
1287 let scale = Scale::fitting(full, target);
1288 if scale == self.scale {
1289 return None;
1290 }
1291 ImageDescriptor::new(
1292 scale.apply(full.0),
1293 scale.apply(full.1),
1294 self.descriptor.pixel,
1295 )
1296 .ok()
1297 }
1298
1299 fn reduce_to(&mut self, descriptor: ImageDescriptor) -> Result<()> {
1300 let full = (u32::from(self.frame.width), u32::from(self.frame.height));
1301 let scale = Scale::ALL
1305 .into_iter()
1306 .find(|scale| {
1307 scale.apply(full.0) == descriptor.width && scale.apply(full.1) == descriptor.height
1308 })
1309 .ok_or_else(|| {
1310 PixelsError::invalid_argument(
1311 "descriptor",
1312 format!(
1313 "{}x{} is not an M/8 scale of {}x{}",
1314 descriptor.width, descriptor.height, full.0, full.1
1315 ),
1316 )
1317 })?;
1318 self.apply_scale(scale)
1319 }
1320
1321 fn read_row(&mut self, out: &mut [u8]) -> Result<()> {
1322 if self.row >= self.descriptor.height {
1323 return Err(PixelsError::invalid_argument(
1324 "out",
1325 format!("all {} rows have already been read", self.descriptor.height),
1326 ));
1327 }
1328 let row_bytes = self.descriptor.row_bytes();
1329 if out.len() != row_bytes {
1330 return Err(PixelsError::invalid_argument(
1331 "out",
1332 format!("row buffer is {} bytes, expected {row_bytes}", out.len()),
1333 ));
1334 }
1335
1336 if self.band_row >= self.band_height {
1337 self.fill_band()?;
1338 }
1339 let start = self.band_row as usize * row_bytes;
1340 let row = self
1341 .band
1342 .get(start..)
1343 .and_then(|rest| rest.get(..row_bytes))
1344 .ok_or_else(|| PixelsError::malformed("jpeg", "MCU row band is short"))?;
1345 out.copy_from_slice(row);
1346 self.band_row += 1;
1347 self.row += 1;
1348 Ok(())
1349 }
1350}
1351
1352#[must_use]
1356pub fn probe(prefix: &[u8]) -> bool {
1357 prefix.get(..3) == Some(&crate::format::SIGNATURE[..])
1358}
1359
1360#[derive(Debug, Clone, Copy, Default)]
1362pub struct JpegCodec;
1363
1364impl Codec for JpegCodec {
1365 fn format(&self) -> Format {
1366 Format::Jpeg
1367 }
1368
1369 fn magic_len(&self) -> usize {
1370 3
1371 }
1372
1373 fn probe(&self, prefix: &[u8]) -> bool {
1374 probe(prefix)
1375 }
1376}
1377
1378#[cfg(test)]
1379mod tests {
1380 use super::*;
1381
1382 #[test]
1385 fn colour_conversion_matches_the_direct_formula_everywhere() {
1386 fn direct([y, cb, cr]: [u8; 3]) -> [u8; 3] {
1387 const HALF: i32 = 1 << 15;
1388 let luma = i32::from(y) << 16;
1389 let blue = i32::from(cb) - 128;
1390 let red = i32::from(cr) - 128;
1391 let r = (luma + 91_881 * red + HALF) >> 16;
1392 let g = (luma - 22_554 * blue - 46_802 * red + HALF) >> 16;
1393 let b = (luma + 116_130 * blue + HALF) >> 16;
1394 [r, g, b].map(|v| v.clamp(0, 255) as u8)
1395 }
1396 for y in 0..=255 {
1397 for cb in 0..=255 {
1398 for cr in 0..=255 {
1399 assert_eq!(ycbcr_to_rgb([y, cb, cr]), direct([y, cb, cr]));
1400 }
1401 }
1402 }
1403 }
1404}