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 exif: Option<Vec<u8>>,
82}
83
84impl<S: Source> JpegDecoder<S> {
85 pub fn new(source: S, limits: Limits) -> Result<Self> {
98 Self::with_scale(source, limits, Scale::Full)
99 }
100
101 pub fn with_scale(source: S, limits: Limits, scale: Scale) -> Result<Self> {
116 let mut inner = Box::new(Engine::with_scale(source, limits, scale)?);
117 let icc = inner.icc.take();
118 let exif = inner.exif.take();
119 Ok(Self { inner, icc, exif })
120 }
121
122 #[must_use]
124 pub const fn scale(&self) -> Scale {
125 self.inner.scale
126 }
127
128 #[must_use]
130 pub const fn is_progressive(&self) -> bool {
131 self.inner.progressive
132 }
133}
134
135impl<S: Source + std::fmt::Debug> Decoder for JpegDecoder<S> {
136 fn descriptor(&self) -> ImageDescriptor {
137 self.inner.descriptor()
138 }
139
140 fn icc_profile(&self) -> Option<&[u8]> {
142 self.icc.as_deref()
143 }
144
145 fn exif(&self) -> Option<&[u8]> {
147 self.exif.as_deref()
148 }
149
150 fn orientation(&self) -> Orientation {
152 self.inner.orientation.unwrap_or_default()
153 }
154
155 fn capability(&self) -> DecodeCapability {
156 self.inner.capability()
157 }
158
159 fn reduced_descriptor(&self, target: (u32, u32)) -> Option<ImageDescriptor> {
160 self.inner.reduced_descriptor(target)
161 }
162
163 fn reduce_to(&mut self, descriptor: ImageDescriptor) -> Result<()> {
164 self.inner.reduce_to(descriptor)
165 }
166
167 fn read_row(&mut self, out: &mut [u8]) -> Result<()> {
168 self.inner.read_row(out)
169 }
170}
171
172struct Engine<S: Source> {
174 reader: Reader<S>,
175 descriptor: ImageDescriptor,
176 frame: Frame,
177 scan: Scan,
178 colour: Colour,
179 quant: [[u16; 64]; 4],
181 dc_tables: [Option<HuffmanTable>; 4],
183 ac_tables: [Option<HuffmanTable>; 4],
184 restart_interval: u16,
186 restarts_left: u32,
188 orientation: Option<Orientation>,
190 icc: Option<Vec<u8>>,
192 exif: Option<Vec<u8>>,
194 planes: Vec<Plane>,
195 expanded: Vec<Vec<u8>>,
198 predictors: Vec<i32>,
200 band: Vec<u8>,
202 band_row: u32,
204 band_height: u32,
206 mcus_per_line: u32,
207 mcu_rows: u32,
208 mcu_row: u32,
210 scale: Scale,
212 limits: Limits,
214 row: u32,
216 progressive: bool,
218 coefficients: Option<Coefficients>,
220 latched: Vec<Option<[u16; 64]>>,
223}
224
225impl<S: Source> std::fmt::Debug for Engine<S> {
226 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
227 f.debug_struct("Engine")
228 .field("descriptor", &self.descriptor)
229 .field("colour", &self.colour)
230 .field("components", &self.frame.components)
231 .field("scale", &self.scale)
232 .field("restart_interval", &self.restart_interval)
233 .field("orientation", &self.orientation)
234 .field("row", &self.row)
235 .finish_non_exhaustive()
236 }
237}
238
239impl<S: Source> Engine<S> {
240 fn with_scale(source: S, limits: Limits, scale: Scale) -> Result<Self> {
267 let mut reader = Reader::new(source);
268 let mut progressive = false;
269 let mut quant = [[0_u16; 64]; 4];
270 let mut dc_tables: [Option<HuffmanTable>; 4] = [None, None, None, None];
271 let mut ac_tables: [Option<HuffmanTable>; 4] = [None, None, None, None];
272 let mut restart_interval = 0_u16;
273 let mut orientation = None;
274 let mut exif: Option<Vec<u8>> = None;
275 let mut icc = IccChunks::default();
276 let mut adobe = None;
277 let mut frame: Option<Frame> = None;
278
279 let first = reader.next_marker()?;
280 if first != marker::SOI {
281 return Err(PixelsError::malformed(
282 "jpeg",
283 format!("stream begins with marker {first:#04x}, not SOI"),
284 ));
285 }
286
287 let scan = loop {
288 let code = reader.next_marker()?;
289 match code {
290 marker::SOF0 | marker::SOF1 | marker::SOF2 => {
291 if frame.is_some() {
292 return Err(PixelsError::malformed(
293 "jpeg",
294 "stream declares more than one frame",
295 ));
296 }
297 progressive = code == marker::SOF2;
298 let parsed = Frame::parse(&reader.read_segment()?)?;
299 limits.check(u32::from(parsed.width), u32::from(parsed.height))?;
303 frame = Some(parsed);
304 }
305 marker::DAC => {
306 return Err(PixelsError::unsupported(
307 "jpeg: arithmetic coding; baseline JPEG is Huffman coded",
308 ));
309 }
310 code if marker::is_frame(code) => {
311 return Err(PixelsError::unsupported(format!(
312 "jpeg: frame type {code:#04x} is not baseline"
313 )));
314 }
315 marker::DQT => read_quantization_tables(&reader.read_segment()?, &mut quant)?,
316 marker::DHT => {
317 read_huffman_tables(&reader.read_segment()?, &mut dc_tables, &mut ac_tables)?;
318 }
319 marker::DRI => {
320 let payload = reader.read_segment()?;
321 let (Some(&hi), Some(&lo)) = (payload.first(), payload.get(1)) else {
322 return Err(PixelsError::malformed(
323 "jpeg",
324 "DRI segment carries no interval",
325 ));
326 };
327 restart_interval = u16::from_be_bytes([hi, lo]);
328 }
329 marker::APP1 => {
330 let payload = reader.read_segment()?;
331 orientation = orientation.or_else(|| exif_orientation(&payload));
333 if exif.is_none() {
334 exif = payload.strip_prefix(b"Exif\0\0").map(<[u8]>::to_vec);
335 }
336 }
337 marker::APP2 => icc.push(&reader.read_segment()?),
338 marker::APP14 => {
339 let payload = reader.read_segment()?;
340 adobe = adobe_transform(&payload).or(adobe);
341 }
342 marker::SOS => {
343 let Some(ref frame) = frame else {
344 return Err(PixelsError::malformed(
345 "jpeg",
346 "scan begins before any frame header",
347 ));
348 };
349 break Scan::parse(&reader.read_segment()?, frame)?;
350 }
351 marker::EOI => {
352 return Err(PixelsError::malformed(
353 "jpeg",
354 "stream ends before any scan",
355 ));
356 }
357 marker::APP0..=marker::APP15 | marker::COM => reader.skip_segment()?,
362 code if marker::is_standalone(code) => {}
363 _ => reader.skip_segment()?,
364 }
365 };
366
367 let Some(frame) = frame else {
368 return Err(PixelsError::malformed("jpeg", "stream has no frame header"));
369 };
370 if !progressive {
373 check_baseline_scan(&scan)?;
374 }
375
376 let colour = colour_model(&frame, adobe)?;
377 let pixel = match colour {
378 Colour::Grayscale => PixelFormat::Gray8,
379 Colour::YCbCr | Colour::Rgb => PixelFormat::Rgb8,
380 };
381 let (full_width, full_height) = (u32::from(frame.width), u32::from(frame.height));
385 let descriptor = ImageDescriptor::with_limits(
386 scale.apply(full_width),
387 scale.apply(full_height),
388 pixel,
389 &limits,
390 )?;
391
392 if !progressive && scan.components.len() != frame.components.len() {
393 return Err(PixelsError::unsupported(
398 "jpeg: non-interleaved baseline scan",
399 ));
400 }
401 for component in scan.components.iter().filter(|_| !progressive) {
402 let slot = frame
403 .components
404 .get(component.index)
405 .map_or(0, |c| c.quant as usize);
406 if quant
407 .get(slot)
408 .is_none_or(|table| table.iter().all(|&q| q == 0))
409 {
410 return Err(PixelsError::malformed(
411 "jpeg",
412 format!("scan uses quantization table {slot}, which was never defined"),
413 ));
414 }
415 if dc_tables
416 .get(component.dc as usize)
417 .is_none_or(Option::is_none)
418 || ac_tables
419 .get(component.ac as usize)
420 .is_none_or(Option::is_none)
421 {
422 return Err(PixelsError::malformed(
423 "jpeg",
424 format!(
425 "scan uses Huffman tables {}/{}, which were never defined",
426 component.dc, component.ac
427 ),
428 ));
429 }
430 }
431
432 let (h_max, v_max) = (u32::from(frame.h_max()), u32::from(frame.v_max()));
433 let blocks: u32 = frame
434 .components
435 .iter()
436 .map(|c| u32::from(c.h) * u32::from(c.v))
437 .sum();
438 if blocks > MAX_BLOCKS_PER_MCU {
439 return Err(PixelsError::malformed(
440 "jpeg",
441 format!(
442 "an MCU would hold {blocks} blocks; the format allows {MAX_BLOCKS_PER_MCU}"
443 ),
444 ));
445 }
446
447 let mcus_per_line = full_width.div_ceil(h_max * 8);
448 let mcu_rows = full_height.div_ceil(v_max * 8);
449
450 let mut decoder = Self {
454 reader,
455 descriptor,
456 predictors: vec![0; frame.components.len()],
457 frame,
458 scan,
459 colour,
460 quant,
461 dc_tables,
462 ac_tables,
463 restart_interval,
464 restarts_left: u32::from(restart_interval),
465 orientation,
466 icc: icc.assemble(),
467 exif,
468 limits,
469 planes: Vec::new(),
470 expanded: Vec::new(),
471 band: Vec::new(),
472 band_row: 0,
473 band_height: 0,
474 mcus_per_line,
475 mcu_rows,
476 mcu_row: 0,
477 scale: Scale::Full,
478 row: 0,
479 progressive,
480 coefficients: None,
481 latched: Vec::new(),
482 };
483 decoder.apply_scale(scale)?;
484 Ok(decoder)
485 }
486
487 fn apply_scale(&mut self, scale: Scale) -> Result<()> {
499 if self.row > 0 || self.mcu_row > 0 {
500 return Err(PixelsError::invalid_argument(
501 "scale",
502 format!("{} rows have already been decoded", self.row),
503 ));
504 }
505 let full_width = u32::from(self.frame.width);
506 let full_height = u32::from(self.frame.height);
507 let (h_max, v_max) = (u32::from(self.frame.h_max()), u32::from(self.frame.v_max()));
508 let descriptor = ImageDescriptor::with_limits(
509 scale.apply(full_width),
510 scale.apply(full_height),
511 self.descriptor.pixel,
512 &self.limits,
513 )?;
514
515 let sample = scale.block_size();
518 let band_height = v_max * sample;
519
520 let mut planes = Vec::with_capacity(self.frame.components.len());
521 for component in &self.frame.components {
522 let (h, v) = (u32::from(component.h), u32::from(component.v));
523 let stride = usize::try_from(self.mcus_per_line * h * sample)
524 .map_err(|_| PixelsError::malformed("jpeg", "component plane overflows"))?;
525 let height = usize::try_from(v * sample).unwrap_or(32);
526 let samples = stride
527 .checked_mul(height)
528 .ok_or_else(|| PixelsError::malformed("jpeg", "component plane overflows"))?;
529 planes.push(Plane {
530 stride,
531 samples: vec![0_u8; samples],
532 columns: (0..descriptor.width).map(|x| x * h / h_max).collect(),
538 rows: (0..band_height).map(|y| y * v / v_max).collect(),
539 });
540 }
541
542 let band = descriptor
543 .row_bytes()
544 .checked_mul(band_height as usize)
545 .ok_or_else(|| PixelsError::malformed("jpeg", "MCU row band overflows"))?;
546
547 self.descriptor = descriptor;
548 self.expanded = vec![vec![0_u8; descriptor.width as usize]; planes.len()];
549 self.planes = planes;
550 self.band = vec![0_u8; band];
551 self.band_row = band_height;
554 self.band_height = band_height;
555 self.scale = scale;
556 Ok(())
557 }
558
559 fn fill_band(&mut self) -> Result<()> {
562 if self.mcu_row >= self.mcu_rows {
563 return Err(PixelsError::malformed(
564 "jpeg",
565 "more rows were requested than the frame declares",
566 ));
567 }
568
569 if self.progressive {
570 return self.fill_band_progressive();
571 }
572
573 let mut coefficients = [0_i32; 64];
574 let scale = self.scale;
575 let sample = scale.block_size() as usize;
576 for mcu in 0..self.mcus_per_line {
577 if self.restart_interval > 0 && self.restarts_left == 0 {
578 if !self.reader.restart()? {
579 let met = self.reader.pending_marker();
580 return Err(PixelsError::malformed(
581 "jpeg",
582 match met {
583 Some(code) => format!(
584 "expected a restart marker between MCU intervals, met {code:#04x}"
585 ),
586 None => "expected a restart marker between MCU intervals".to_owned(),
587 },
588 ));
589 }
590 self.predictors.iter_mut().for_each(|p| *p = 0);
594 self.restarts_left = u32::from(self.restart_interval);
595 }
596
597 for scanned in &self.scan.components {
598 let Some(component) = self.frame.components.get(scanned.index) else {
599 continue;
600 };
601 let (h, v) = (u32::from(component.h), u32::from(component.v));
602 let (Some(dc), Some(ac)) = (
603 self.dc_tables
604 .get(scanned.dc as usize)
605 .and_then(Option::as_ref),
606 self.ac_tables
607 .get(scanned.ac as usize)
608 .and_then(Option::as_ref),
609 ) else {
610 return Err(PixelsError::malformed(
611 "jpeg",
612 "scan names a Huffman table that was never defined",
613 ));
614 };
615 let quant = self
616 .quant
617 .get(component.quant as usize)
618 .ok_or_else(|| PixelsError::malformed("jpeg", "component names no table"))?;
619 let Some(predictor) = self.predictors.get_mut(scanned.index) else {
620 continue;
621 };
622 let Some(plane) = self.planes.get_mut(scanned.index) else {
623 continue;
624 };
625
626 for block_y in 0..v {
627 for block_x in 0..h {
628 decode_block(
629 &mut self.reader,
630 dc,
631 ac,
632 quant,
633 predictor,
634 &mut coefficients,
635 )?;
636 let x = ((mcu * h) + block_x) as usize * sample;
637 let y = block_y as usize * sample;
638 idct::scaled_block(
639 &coefficients,
640 scale,
641 &mut plane.samples,
642 y * plane.stride + x,
643 plane.stride,
644 );
645 }
646 }
647 }
648
649 if self.restart_interval > 0 {
650 self.restarts_left = self.restarts_left.saturating_sub(1);
651 }
652 }
653
654 self.mcu_row += 1;
655 self.convert_band();
656 self.band_row = 0;
657 Ok(())
658 }
659
660 fn fill_band_progressive(&mut self) -> Result<()> {
663 if self.coefficients.is_none() {
664 self.decode_scans()?;
665 }
666 let Some(stored) = self.coefficients.as_ref() else {
667 return Err(PixelsError::graph(
668 "jpeg coefficients vanished after decoding",
669 ));
670 };
671
672 let mut natural = [0_i32; 64];
673 let scale = self.scale;
674 let sample = scale.block_size() as usize;
675 let mcu_row = self.mcu_row as usize;
676 for (index, component) in self.frame.components.iter().enumerate() {
677 let (h, v) = (usize::from(component.h), usize::from(component.v));
678 let quant = self
681 .latched
682 .get(index)
683 .copied()
684 .flatten()
685 .or_else(|| self.quant.get(component.quant as usize).copied())
686 .unwrap_or([0; 64]);
687 let Some(plane) = self.planes.get_mut(index) else {
688 continue;
689 };
690 for block_y in 0..v {
691 for block_x in 0..h * self.mcus_per_line as usize {
692 let Some(block) = stored.block(index, block_x, mcu_row * v + block_y) else {
693 continue;
694 };
695 for ((&at, &value), &step) in ZIGZAG.iter().zip(block).zip(&quant) {
696 if let Some(slot) = natural.get_mut(at) {
697 *slot = i32::from(value) * i32::from(step);
698 }
699 }
700 idct::scaled_block(
701 &natural,
702 scale,
703 &mut plane.samples,
704 block_y * sample * plane.stride + block_x * sample,
705 plane.stride,
706 );
707 }
708 }
709 }
710
711 self.mcu_row += 1;
712 self.convert_band();
713 self.band_row = 0;
714 Ok(())
715 }
716
717 fn decode_scans(&mut self) -> Result<()> {
724 let mut coefficients = Coefficients::new(&self.frame, self.mcus_per_line, self.mcu_rows)?;
725 let mut latched: Vec<Option<[u16; 64]>> = vec![None; self.frame.components.len()];
726 let mut scan = self.scan.clone();
727 let mut scans = 0;
728 'scans: loop {
729 scans += 1;
730 if scans > MAX_SCANS {
731 return Err(PixelsError::malformed(
732 "jpeg",
733 format!("progressive frame has more than {MAX_SCANS} scans"),
734 ));
735 }
736 for scanned in &scan.components {
738 let Some(component) = self.frame.components.get(scanned.index) else {
739 continue;
740 };
741 if let Some(slot) = latched.get_mut(scanned.index) {
742 if slot.is_none() {
743 *slot = self.quant.get(component.quant as usize).copied();
744 }
745 }
746 }
747 let tables = Tables {
748 dc: &self.dc_tables,
749 ac: &self.ac_tables,
750 restart_interval: self.restart_interval,
751 };
752 crate::progressive::decode_scan(
753 &mut self.reader,
754 &self.frame,
755 &scan,
756 &tables,
757 &mut coefficients,
758 )?;
759 self.reader.align();
760
761 loop {
762 let Ok(code) = self.reader.next_marker() else {
765 break 'scans;
766 };
767 match code {
768 marker::SOS => {
769 scan = Scan::parse(&self.reader.read_segment()?, &self.frame)?;
770 continue 'scans;
771 }
772 marker::EOI => break 'scans,
773 marker::DHT => read_huffman_tables(
774 &self.reader.read_segment()?,
775 &mut self.dc_tables,
776 &mut self.ac_tables,
777 )?,
778 marker::DQT => {
779 read_quantization_tables(&self.reader.read_segment()?, &mut self.quant)?;
780 }
781 marker::DRI => {
782 let payload = self.reader.read_segment()?;
783 let (Some(&hi), Some(&lo)) = (payload.first(), payload.get(1)) else {
784 return Err(PixelsError::malformed(
785 "jpeg",
786 "DRI segment carries no interval",
787 ));
788 };
789 self.restart_interval = u16::from_be_bytes([hi, lo]);
790 }
791 code if marker::is_frame(code) => {
792 return Err(PixelsError::malformed(
793 "jpeg",
794 "stream declares more than one frame",
795 ));
796 }
797 code if marker::is_standalone(code) => {}
799 _ => self.reader.skip_segment()?,
800 }
801 }
802 }
803 self.coefficients = Some(coefficients);
804 self.latched = latched;
805 Ok(())
806 }
807
808 fn convert_band(&mut self) {
816 let width = self.descriptor.width as usize;
817 let row_bytes = self.descriptor.row_bytes();
818 let h_max = usize::from(self.frame.h_max());
819
820 for y in 0..self.band_height as usize {
821 let Some(out) = self
822 .band
823 .get_mut(y * row_bytes..)
824 .and_then(|rest| rest.get_mut(..row_bytes))
825 else {
826 continue;
827 };
828 for ((plane, component), expanded) in self
829 .planes
830 .iter()
831 .zip(&self.frame.components)
832 .zip(self.expanded.iter_mut())
833 {
834 let h = usize::from(component.h.max(1));
836 let ratio = if h_max % h == 0 { h_max / h } else { 0 };
837 plane.expand_row(y, ratio, expanded);
838 }
839
840 match self.colour {
841 Colour::Grayscale => {
842 if let Some(grey) = self.expanded.first() {
843 for (slot, &value) in out.iter_mut().zip(grey).take(width) {
844 *slot = value;
845 }
846 }
847 }
848 Colour::Rgb => {
849 let [a, b, c] =
850 [0, 1, 2].map(|i| self.expanded.get(i).map_or(&[][..], Vec::as_slice));
851 for (pixel, ((&r, &g), &b)) in
852 out.chunks_exact_mut(3).zip(a.iter().zip(b).zip(c))
853 {
854 pixel.copy_from_slice(&[r, g, b]);
855 }
856 }
857 Colour::YCbCr => {
858 let [luma, cb, cr] =
859 [0, 1, 2].map(|i| self.expanded.get(i).map_or(&[][..], Vec::as_slice));
860 for (pixel, ((&y, &cb), &cr)) in
861 out.chunks_exact_mut(3).zip(luma.iter().zip(cb).zip(cr))
862 {
863 pixel.copy_from_slice(&ycbcr_to_rgb([y, cb, cr]));
864 }
865 }
866 }
867 }
868 }
869}
870
871impl Plane {
872 fn expand_row(&self, y: usize, ratio: usize, out: &mut [u8]) {
878 let source = self.row(y);
879 match ratio {
880 1 => {
881 let count = out.len().min(source.len());
882 if let (Some(to), Some(from)) = (out.get_mut(..count), source.get(..count)) {
883 to.copy_from_slice(from);
884 }
885 }
886 2 => {
887 let half = out.len() / 2;
890 let mut pairs = out.chunks_exact_mut(2);
891 for (pair, &value) in (&mut pairs).zip(source) {
892 pair.copy_from_slice(&[value, value]);
893 }
894 if let ([last], Some(&value)) = (pairs.into_remainder(), source.get(half)) {
896 *last = value;
897 }
898 }
899 _ => {
900 for (x, slot) in out.iter_mut().enumerate() {
901 *slot = self.sample(source, x);
902 }
903 }
904 }
905 }
906
907 fn row(&self, y: usize) -> &[u8] {
909 let row = self.rows.get(y).copied().unwrap_or(0) as usize;
910 self.samples
911 .get(row * self.stride..)
912 .and_then(|rest| rest.get(..self.stride))
913 .unwrap_or(&[])
914 }
915
916 fn sample(&self, row: &[u8], x: usize) -> u8 {
918 let column = self.columns.get(x).copied().unwrap_or(0) as usize;
919 row.get(column).copied().unwrap_or(0)
920 }
921}
922
923fn decode_block<S: Source>(
925 reader: &mut Reader<S>,
926 dc: &HuffmanTable,
927 ac: &HuffmanTable,
928 quant: &[u16; 64],
929 predictor: &mut i32,
930 out: &mut [i32; 64],
931) -> Result<()> {
932 out.fill(0);
933
934 let magnitude = reader.decode(dc)?;
935 if magnitude > 15 {
936 return Err(PixelsError::malformed(
937 "jpeg",
938 format!("DC coefficient claims {magnitude} bits; 15 is the maximum"),
939 ));
940 }
941 let difference = reader.receive_extend(u32::from(magnitude))?;
942 *predictor = predictor.wrapping_add(difference);
947 if let (Some(slot), Some(&step)) = (out.first_mut(), quant.first()) {
948 *slot = predictor.saturating_mul(i32::from(step));
949 }
950
951 let mut index = 1_usize;
952 while index < 64 {
953 let symbol = reader.decode(ac)?;
954 let (run, size) = ((symbol >> 4) as usize, u32::from(symbol & 0x0F));
955 if size == 0 {
956 if run != 15 {
957 break;
960 }
961 index += 16;
964 continue;
965 }
966 index += run;
967 if index > 63 {
968 return Err(PixelsError::malformed(
969 "jpeg",
970 "coefficient run passes the end of the block",
971 ));
972 }
973 let value = reader.receive_extend(size)?;
974 let step = quant.get(index).copied().unwrap_or(0);
975 if let Some(slot) = ZIGZAG.get(index).and_then(|&at| out.get_mut(at)) {
976 *slot = value.saturating_mul(i32::from(step));
977 }
978 index += 1;
979 }
980 Ok(())
981}
982
983#[inline]
1000pub(crate) fn ycbcr_to_rgb([y, cb, cr]: [u8; 3]) -> [u8; 3] {
1001 const HALF: i32 = 1 << 15;
1002 let luma = (i32::from(y) << 16) + HALF;
1003 let blue = i32::from(i16::from(cb) - 128);
1004 let red = i32::from(i16::from(cr) - 128);
1005
1006 let r = (luma + (red << 16) + 26_345 * red) >> 16;
1007 let g = (luma - 22_554 * blue - (red << 16) + 18_734 * red) >> 16;
1008 let b = (luma + (blue << 17) - 14_942 * blue) >> 16;
1009 [
1010 r.clamp(0, 255) as u8,
1011 g.clamp(0, 255) as u8,
1012 b.clamp(0, 255) as u8,
1013 ]
1014}
1015
1016fn colour_model(frame: &Frame, adobe: Option<AdobeTransform>) -> Result<Colour> {
1018 match frame.components.len() {
1019 1 => Ok(Colour::Grayscale),
1020 3 => {
1021 let labelled_rgb = frame.components.iter().map(|c| c.id).eq(*b"RGB");
1024 Ok(if adobe == Some(AdobeTransform::None) || labelled_rgb {
1025 Colour::Rgb
1026 } else {
1027 Colour::YCbCr
1028 })
1029 }
1030 count => Err(PixelsError::unsupported(format!(
1034 "jpeg: {count}-component images (CMYK/YCCK)"
1035 ))),
1036 }
1037}
1038
1039fn check_baseline_scan(scan: &Scan) -> Result<()> {
1042 if scan.spectral_start != 0 || scan.spectral_end != 63 {
1043 return Err(PixelsError::malformed(
1044 "jpeg",
1045 format!(
1046 "baseline scan selects coefficients {}..={}; it must select all 64",
1047 scan.spectral_start, scan.spectral_end
1048 ),
1049 ));
1050 }
1051 if scan.approx_high != 0 || scan.approx_low != 0 {
1052 return Err(PixelsError::malformed(
1053 "jpeg",
1054 "baseline scan uses successive approximation, which is progressive only",
1055 ));
1056 }
1057 Ok(())
1058}
1059
1060fn read_quantization_tables(payload: &[u8], quant: &mut [[u16; 64]; 4]) -> Result<()> {
1062 let mut at = 0_usize;
1063 while at < payload.len() {
1064 let Some(&header) = payload.get(at) else {
1065 break;
1066 };
1067 at += 1;
1068 let (precision, slot) = (header >> 4, (header & 0x0F) as usize);
1069 if slot > 3 {
1070 return Err(PixelsError::malformed(
1071 "jpeg",
1072 format!("DQT names table {slot}; only 0..=3 exist"),
1073 ));
1074 }
1075 let wide = match precision {
1076 0 => false,
1077 1 => true,
1078 other => {
1079 return Err(PixelsError::malformed(
1080 "jpeg",
1081 format!("DQT declares precision {other}; only 0 and 1 exist"),
1082 ));
1083 }
1084 };
1085
1086 let Some(table) = quant.get_mut(slot) else {
1087 break;
1088 };
1089 for entry in table.iter_mut() {
1090 let value = if wide {
1091 let (Some(&hi), Some(&lo)) = (payload.get(at), payload.get(at + 1)) else {
1092 return Err(PixelsError::malformed("jpeg", "DQT segment is truncated"));
1093 };
1094 at += 2;
1095 u16::from_be_bytes([hi, lo])
1096 } else {
1097 let Some(&value) = payload.get(at) else {
1098 return Err(PixelsError::malformed("jpeg", "DQT segment is truncated"));
1099 };
1100 at += 1;
1101 u16::from(value)
1102 };
1103 *entry = value;
1104 }
1105 }
1106 Ok(())
1107}
1108
1109fn read_huffman_tables(
1111 payload: &[u8],
1112 dc_tables: &mut [Option<HuffmanTable>; 4],
1113 ac_tables: &mut [Option<HuffmanTable>; 4],
1114) -> Result<()> {
1115 let mut at = 0_usize;
1116 while at < payload.len() {
1117 let Some(&header) = payload.get(at) else {
1118 break;
1119 };
1120 at += 1;
1121 let (class, slot) = (header >> 4, (header & 0x0F) as usize);
1122 if slot > 3 || class > 1 {
1123 return Err(PixelsError::malformed(
1124 "jpeg",
1125 format!("DHT names class {class} table {slot}; classes are 0..=1, slots 0..=3"),
1126 ));
1127 }
1128
1129 let Some(counts) = payload.get(at..at + 16) else {
1130 return Err(PixelsError::malformed(
1131 "jpeg",
1132 "DHT segment ends inside its code-length counts",
1133 ));
1134 };
1135 let mut lengths = [0_u8; 16];
1136 lengths.copy_from_slice(counts);
1137 at += 16;
1138
1139 let total: usize = lengths.iter().map(|&c| c as usize).sum();
1140 let Some(values) = payload.get(at..at + total) else {
1141 return Err(PixelsError::malformed(
1142 "jpeg",
1143 "DHT segment ends inside its symbol list",
1144 ));
1145 };
1146 at += total;
1147
1148 let table = HuffmanTable::new(&lengths, values.to_vec())?;
1149 let slots = if class == 0 {
1150 &mut *dc_tables
1151 } else {
1152 &mut *ac_tables
1153 };
1154 if let Some(entry) = slots.get_mut(slot) {
1155 *entry = Some(table);
1156 }
1157 }
1158 Ok(())
1159}
1160
1161impl<S: Source + std::fmt::Debug> Decoder for Engine<S> {
1162 fn descriptor(&self) -> ImageDescriptor {
1163 self.descriptor
1164 }
1165
1166 fn capability(&self) -> DecodeCapability {
1167 DecodeCapability::Sequential
1168 }
1169
1170 fn reduced_descriptor(&self, target: (u32, u32)) -> Option<ImageDescriptor> {
1171 if self.row > 0 || self.mcu_row > 0 {
1173 return None;
1174 }
1175 let full = (u32::from(self.frame.width), u32::from(self.frame.height));
1176 let scale = Scale::fitting(full, target);
1177 if scale == self.scale {
1178 return None;
1179 }
1180 ImageDescriptor::new(
1181 scale.apply(full.0),
1182 scale.apply(full.1),
1183 self.descriptor.pixel,
1184 )
1185 .ok()
1186 }
1187
1188 fn reduce_to(&mut self, descriptor: ImageDescriptor) -> Result<()> {
1189 let full = (u32::from(self.frame.width), u32::from(self.frame.height));
1190 let scale = Scale::ALL
1194 .into_iter()
1195 .find(|scale| {
1196 scale.apply(full.0) == descriptor.width && scale.apply(full.1) == descriptor.height
1197 })
1198 .ok_or_else(|| {
1199 PixelsError::invalid_argument(
1200 "descriptor",
1201 format!(
1202 "{}x{} is not an M/8 scale of {}x{}",
1203 descriptor.width, descriptor.height, full.0, full.1
1204 ),
1205 )
1206 })?;
1207 self.apply_scale(scale)
1208 }
1209
1210 fn read_row(&mut self, out: &mut [u8]) -> Result<()> {
1211 if self.row >= self.descriptor.height {
1212 return Err(PixelsError::invalid_argument(
1213 "out",
1214 format!("all {} rows have already been read", self.descriptor.height),
1215 ));
1216 }
1217 let row_bytes = self.descriptor.row_bytes();
1218 if out.len() != row_bytes {
1219 return Err(PixelsError::invalid_argument(
1220 "out",
1221 format!("row buffer is {} bytes, expected {row_bytes}", out.len()),
1222 ));
1223 }
1224
1225 if self.band_row >= self.band_height {
1226 self.fill_band()?;
1227 }
1228 let start = self.band_row as usize * row_bytes;
1229 let row = self
1230 .band
1231 .get(start..)
1232 .and_then(|rest| rest.get(..row_bytes))
1233 .ok_or_else(|| PixelsError::malformed("jpeg", "MCU row band is short"))?;
1234 out.copy_from_slice(row);
1235 self.band_row += 1;
1236 self.row += 1;
1237 Ok(())
1238 }
1239}
1240
1241#[must_use]
1245pub fn probe(prefix: &[u8]) -> bool {
1246 prefix.get(..3) == Some(&crate::format::SIGNATURE[..])
1247}
1248
1249#[derive(Debug, Clone, Copy, Default)]
1251pub struct JpegCodec;
1252
1253impl Codec for JpegCodec {
1254 fn format(&self) -> Format {
1255 Format::Jpeg
1256 }
1257
1258 fn magic_len(&self) -> usize {
1259 3
1260 }
1261
1262 fn probe(&self, prefix: &[u8]) -> bool {
1263 probe(prefix)
1264 }
1265}
1266
1267#[cfg(test)]
1268mod tests {
1269 use super::*;
1270
1271 #[test]
1274 fn colour_conversion_matches_the_direct_formula_everywhere() {
1275 fn direct([y, cb, cr]: [u8; 3]) -> [u8; 3] {
1276 const HALF: i32 = 1 << 15;
1277 let luma = i32::from(y) << 16;
1278 let blue = i32::from(cb) - 128;
1279 let red = i32::from(cr) - 128;
1280 let r = (luma + 91_881 * red + HALF) >> 16;
1281 let g = (luma - 22_554 * blue - 46_802 * red + HALF) >> 16;
1282 let b = (luma + 116_130 * blue + HALF) >> 16;
1283 [r, g, b].map(|v| v.clamp(0, 255) as u8)
1284 }
1285 for y in 0..=255 {
1286 for cb in 0..=255 {
1287 for cr in 0..=255 {
1288 assert_eq!(ycbcr_to_rgb([y, cb, cr]), direct([y, cb, cr]));
1289 }
1290 }
1291 }
1292 }
1293}