Skip to main content

otf_pixels_codec_jpeg/
decoder.rs

1//! The baseline JPEG decoder.
2//!
3//! # Shape of the work
4//!
5//! A JPEG is a sequence of marker segments — quantization tables, Huffman
6//! tables, a frame header — followed by one scan of entropy-coded data.
7//! [`JpegDecoder::new`] reads up to and including the scan header, so
8//! everything the engine needs to plan a pipeline is known before a single
9//! coefficient is decoded.
10//!
11//! The scan is then consumed one **MCU row** at a time. An MCU (minimum coded
12//! unit) is the smallest group of blocks that covers the same rectangle in
13//! every component, which with 2x2 chroma subsampling is four luma blocks and
14//! one of each chroma: 16x16 pixels. Decoding a row of them fills a band of
15//! component planes, which is upsampled and colour-converted into interleaved
16//! output rows on the spot. Peak memory is that band, not the image.
17//!
18//! # Progressive frames
19//!
20//! A progressive frame (`SOF2`) is opened the same way, up to its first scan.
21//! On the first row request every scan is decoded into a whole-image buffer of
22//! coefficients (`progressive`), and bands are then built from that buffer
23//! through the same inverse DCT, upsampling and colour conversion — so a
24//! progressive file decodes at reduced scale too.
25
26use 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
38/// The identifier an XMP `APP1` segment starts with (XMP part 3, §1.1.3).
39const XMP_ID: &[u8] = b"http://ns.adobe.com/xap/1.0/\0";
40
41/// The density a JFIF `APP0` segment states: units, then X and Y.
42fn 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        // Aspect ratio only: no density.
51        _ => return None,
52    };
53    Some(Density {
54        x: f64::from(x),
55        y: f64::from(y),
56        unit,
57    })
58}
59
60/// sharp's spelling of a frame's chroma subsampling, from the first
61/// component's sampling factors against the others'.
62fn 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
90/// The most blocks a single MCU may contain (ITU-T T.81 §B.2.3).
91///
92/// Sampling factors are capped at 4x4 each, so without this a crafted frame
93/// could ask for 48 blocks per MCU across three components. The format says
94/// ten; holding it to that bounds the work one MCU can cost.
95const MAX_BLOCKS_PER_MCU: u32 = 10;
96
97/// How the frame's components map onto colour.
98#[derive(Debug, Clone, Copy, PartialEq, Eq)]
99enum Colour {
100    /// One component: luminance only.
101    Grayscale,
102    /// Three components, transformed.
103    YCbCr,
104    /// Three components already in RGB order.
105    Rgb,
106}
107
108/// One component's plane for the MCU row currently being decoded.
109#[derive(Debug)]
110struct Plane {
111    /// Samples per row, including the padding that rounds up to whole blocks.
112    stride: usize,
113    /// Decoded samples for this band.
114    samples: Vec<u8>,
115    /// Output column to plane column, precomputed to keep upsampling out of
116    /// the inner loop.
117    columns: Vec<u32>,
118    /// Output row within the band to plane row.
119    rows: Vec<u32>,
120}
121
122/// Decodes a JPEG stream.
123///
124/// Baseline frames stream one MCU row at a time. Progressive frames decode
125/// their scans into a coefficient buffer first and are then served the same
126/// way; the distinction is not visible through [`Decoder`].
127#[derive(Debug)]
128pub struct JpegDecoder<S: Source> {
129    inner: Box<Engine<S>>,
130    /// The ICC profile from the `APP2` segments before the scan.
131    icc: Option<Vec<u8>>,
132    /// The EXIF block from the first EXIF `APP1` segment.
133    exif: Option<Vec<u8>>,
134    /// The XMP packet from the first XMP `APP1` segment.
135    xmp: Option<Vec<u8>>,
136    /// Density, subsampling and the rest of what the header says.
137    info: SourceInfo,
138}
139
140impl<S: Source> JpegDecoder<S> {
141    /// Read every header up to and including the scan header.
142    ///
143    /// No coefficient is decoded here, baseline or progressive, so this is the
144    /// `probe()`/metadata path as well as the start of a decode (SPEC
145    /// §Guarantees 3).
146    ///
147    /// # Errors
148    ///
149    /// Returns [`PixelsError::Malformed`] for a stream that is not a
150    /// well-formed JPEG, [`PixelsError::Unsupported`] for a variant this crate
151    /// does not handle (arithmetic coding, lossless, 12-bit, CMYK), or
152    /// [`PixelsError::LimitExceeded`] if the frame exceeds `limits`.
153    pub fn new(source: S, limits: Limits) -> Result<Self> {
154        Self::with_scale(source, limits, Scale::Full)
155    }
156
157    /// Read every header, and decode at a reduced resolution.
158    ///
159    /// At `M/8` scale the decoder inverse-transforms only the low-frequency
160    /// corner of each block, so a thumbnail costs a fraction of the arithmetic
161    /// and — the point of it — the full-resolution image is never
162    /// materialized. Entropy decoding is unchanged: every coefficient is still
163    /// read, because the format gives no way to skip one.
164    ///
165    /// [`Decoder::descriptor`] reports the *scaled* size, so a caller that
166    /// asks for a reduced decode is told what it will actually receive.
167    ///
168    /// # Errors
169    ///
170    /// As [`JpegDecoder::new`].
171    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    /// The resolution this decoder produces, as eighths of full size.
187    #[must_use]
188    pub const fn scale(&self) -> Scale {
189        self.inner.scale
190    }
191
192    /// Whether this stream is a progressive frame.
193    #[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    /// The ICC profile, joined from its `APP2` segments.
205    fn icc_profile(&self) -> Option<&[u8]> {
206        self.icc.as_deref()
207    }
208
209    /// The EXIF block, from the first EXIF `APP1` segment.
210    fn exif(&self) -> Option<&[u8]> {
211        self.exif.as_deref()
212    }
213
214    /// The XMP packet, from the first XMP `APP1` segment. The extended XMP
215    /// a packet over 64 KB spills into is not joined.
216    fn xmp(&self) -> Option<&[u8]> {
217        self.xmp.as_deref()
218    }
219
220    /// Progressive, chroma subsampling, density (JFIF, else EXIF) and
221    /// `COM` comments before the first scan.
222    fn source_info(&self) -> SourceInfo {
223        self.info.clone()
224    }
225
226    /// The EXIF orientation, from the first EXIF `APP1` segment.
227    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
248/// The decoder proper, for baseline and progressive frames alike.
249struct Engine<S: Source> {
250    reader: Reader<S>,
251    descriptor: ImageDescriptor,
252    frame: Frame,
253    scan: Scan,
254    colour: Colour,
255    /// Quantization tables in zigzag order, by slot.
256    quant: [[u16; 64]; 4],
257    /// DC and AC Huffman tables, by slot.
258    dc_tables: [Option<HuffmanTable>; 4],
259    ac_tables: [Option<HuffmanTable>; 4],
260    /// MCUs between restart markers; zero means there are none.
261    restart_interval: u16,
262    /// MCUs left before the next restart marker is due.
263    restarts_left: u32,
264    /// The EXIF orientation tag, if the file carries one.
265    orientation: Option<Orientation>,
266    /// The ICC profile, until [`JpegDecoder`] takes it.
267    icc: Option<Vec<u8>>,
268    /// The EXIF block, until [`JpegDecoder`] takes it.
269    exif: Option<Vec<u8>>,
270    /// The XMP packet, until [`JpegDecoder`] takes it.
271    xmp: Option<Vec<u8>>,
272    /// What the header says, until [`JpegDecoder`] takes it.
273    info: SourceInfo,
274    planes: Vec<Plane>,
275    /// One output-width row per component, reused for every row: the
276    /// component's samples at output resolution, ready for colour conversion.
277    expanded: Vec<Vec<u8>>,
278    /// Per-component DC predictor, reset at every restart.
279    predictors: Vec<i32>,
280    /// Interleaved output rows for the MCU row just decoded.
281    band: Vec<u8>,
282    /// The next row of `band` to serve; equal to the band height when spent.
283    band_row: u32,
284    /// Pixel height of one MCU row, and so of `band`.
285    band_height: u32,
286    mcus_per_line: u32,
287    mcu_rows: u32,
288    /// The next MCU row to decode.
289    mcu_row: u32,
290    /// How much of full resolution this decode produces.
291    scale: Scale,
292    /// Kept so a later re-scale can be re-checked against the same bounds.
293    limits: Limits,
294    /// Output rows already served.
295    row: u32,
296    /// Whether the frame is progressive (`SOF2`).
297    progressive: bool,
298    /// A progressive frame's coefficients, once its scans are decoded.
299    coefficients: Option<Coefficients>,
300    /// Each component's quantization table, as it stood at the component's
301    /// first scan; a progressive file may redefine a slot between scans.
302    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    /// Read every header up to and including the scan header.
321    ///
322    /// No coefficient is decoded here, so this is the `probe()`/metadata path
323    /// as well as the start of a decode (SPEC §Guarantees 3).
324    ///
325    /// # Errors
326    ///
327    /// Returns [`PixelsError::Malformed`] for a stream that is not a
328    /// well-formed baseline JPEG, [`PixelsError::Unsupported`] for a JPEG
329    /// variant this crate does not own (progressive, arithmetic-coded, 12-bit,
330    /// CMYK), or [`PixelsError::LimitExceeded`] if the frame exceeds `limits`.
331    /// Read every header, and decode at a reduced resolution.
332    ///
333    /// At `M/8` scale the decoder inverse-transforms only the low-frequency
334    /// corner of each block, so a thumbnail costs a fraction of the arithmetic
335    /// and — the point of it — the full-resolution image is never
336    /// materialized for the rest of the pipeline to carry. Entropy decoding is
337    /// unchanged: every coefficient is still read, because the format gives no
338    /// way to skip one.
339    ///
340    /// [`Decoder::descriptor`] reports the *scaled* size, so a caller that
341    /// asks for a reduced decode is told what it will actually receive.
342    ///
343    /// # Errors
344    ///
345    /// As [`JpegDecoder::new`].
346    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                    // Checked here rather than once the scan header arrives,
383                    // so an enormous frame costs one segment parse and stops
384                    // (SPEC §Safety and limits).
385                    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                    // The first EXIF block wins; later ones are thumbnails.
415                    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                // Segments that carry no pixels are skipped, not rejected:
457                // JFIF density, colour profiles, comments and vendor
458                // extensions all live here, and refusing them would fail
459                // files every viewer opens.
460                // APP3 to APP13 and APP15; the others are read above.
461                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        // A progressive scan is checked when it is decoded, against the
471        // tables as they stand by then; a baseline one must be complete now.
472        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        // The frame's own size drives the MCU grid — that is a property of the
491        // encoded data, not of how much of it we intend to produce — while the
492        // descriptor reports what the caller will actually receive.
493        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            // Every component of a baseline frame is coded in one interleaved
503            // scan in practice. The alternative — a scan per component — needs
504            // whole-image component planes, which would trade the memory
505            // guarantee for a case no encoder in circulation produces.
506            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        // Geometry is set up by `apply_scale`, which the planner may call
560        // again later: shrink-on-load decides the scale only once the whole
561        // pipeline is known, by which point this decoder already exists.
562        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    /// Set the resolution this decoder will produce, rebuilding the geometry
599    /// that depends on it.
600    ///
601    /// Every plane dimension is in samples-per-block, so the scale is the only
602    /// thing the rest of the decoder has to know about.
603    ///
604    /// # Errors
605    ///
606    /// Returns [`PixelsError::InvalidArgument`] if any row has already been
607    /// read — a scaled decode cannot start halfway — or
608    /// [`PixelsError::LimitExceeded`] if the scaled size is outside `limits`.
609    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        // How many samples one block becomes: 8 at full resolution, fewer at a
627        // reduced scale.
628        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                // Nearest-neighbour upsampling: output column `x` reads the
644                // component sample covering it. Triangle ("fancy") upsampling
645                // is smoother and is what libjpeg defaults to, which is why
646                // reference comparisons are made against a decoder configured
647                // to match rather than against libjpeg's default.
648                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        // Nothing decoded yet, so the band is spent and the first `read_row`
663        // fills it.
664        self.band_row = band_height;
665        self.band_height = band_height;
666        self.scale = scale;
667        Ok(())
668    }
669
670    /// Decode one MCU row into the component planes, then convert it into the
671    /// interleaved output band.
672    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                // Predictors are differential *within* an interval; that is
702                // the whole point of restarts, and carrying them across one
703                // would corrupt every block after a resynchronization.
704                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    /// [`Engine::fill_band`] for a progressive frame: the band's blocks come
772    /// from the coefficient buffer, decoded in full on the first call.
773    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            // A component no scan ever reached has all-zero coefficients, so
790            // whichever table dequantizes them makes no difference.
791            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    /// Decode every scan of a progressive frame into the coefficient buffer.
829    ///
830    /// The first scan's header was read when the decoder was built. Tables
831    /// and restart intervals may be redefined between scans and take effect
832    /// for the scans after them; the frame ends at `EOI`, or where the data
833    /// runs out, which leaves a partial download showing what arrived.
834    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            // libjpeg's rule: a component's table is fixed by its first scan.
848            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                // A stream that ends between scans is a partial download:
874                // what arrived stands, as it does mid-scan.
875                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                    // A stray restart marker between scans carries nothing.
909                    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    /// Upsample the component planes and write interleaved pixels into the
920    /// output band.
921    ///
922    /// Each component's row is first expanded to output width in a buffer of
923    /// its own, so colour conversion is one straight loop over equal-length
924    /// slices — which the compiler vectorizes — rather than a per-pixel
925    /// lookup into each plane.
926    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                // Whole ratios only; anything else takes the general path.
946                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    /// Write this plane's samples for output row `y` of the band into
984    /// `out`, one per output column. `ratio` is how many output columns one
985    /// sample covers, or 0 if that is not a whole number; 1 and 2 — every
986    /// common layout — take fast paths, and anything else goes through the
987    /// general column table.
988    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                // A fixed two-byte copy, not `fill`: on a slice that short,
999                // `fill` is a `memset` call per pixel pair.
1000                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                // An odd width leaves one column, covered by the last sample.
1006                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    /// The plane row backing output row `y` of the band.
1019    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    /// The sample of `row` covering output column `x`.
1028    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
1034/// Decode one 8x8 block, dequantizing into natural order as it goes.
1035fn 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    // DC is coded as a difference from the previous block of the same
1054    // component. Wrapping keeps a crafted stream from panicking on overflow;
1055    // the result is nonsense pixels, which is the correct outcome for
1056    // nonsense input.
1057    *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                // End of block: every remaining coefficient is zero, which is
1069                // where JPEG gets most of its compression.
1070                break;
1071            }
1072            // A run of sixteen zeros, coded because the run length field
1073            // cannot express more than fifteen.
1074            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/// Convert one YCbCr triple to RGB in fixed point.
1095///
1096/// The coefficients are JFIF's, scaled by 2^16. Fixed point rather than float
1097/// for ADR-0011's reason: the same input has to produce the same byte on every
1098/// target.
1099///
1100/// Each constant above 2^15 is split into a shift and a multiplier below it,
1101/// so every product is 16 by 16 bits: the baseline x86-64 target (SSE2) has
1102/// no 32-bit vector multiply but does have 16-bit ones, so this form
1103/// vectorizes and the direct one does not. The results are identical.
1104///
1105/// | Constant | As |
1106/// |---|---|
1107/// | 91 881 | `(c << 16) + 26 345 c` |
1108/// | 116 130 | `(c << 17) − 14 942 c` |
1109/// | 46 802 | `(c << 16) − 18 734 c` |
1110#[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
1127/// Decide what the frame's components mean.
1128fn colour_model(frame: &Frame, adobe: Option<AdobeTransform>) -> Result<Colour> {
1129    match frame.components.len() {
1130        1 => Ok(Colour::Grayscale),
1131        3 => {
1132            // Component ids 'R', 'G', 'B' are the other way a JPEG says it is
1133            // not YCbCr, and predate Adobe's marker.
1134            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        // CMYK and YCCK need an ink model and usually an ICC profile to look
1142        // right; v1 is sRGB-assumed (SPEC §Pixel formats), so guessing here
1143        // would produce confidently wrong colour.
1144        count => Err(PixelsError::unsupported(format!(
1145            "jpeg: {count}-component images (CMYK/YCCK)"
1146        ))),
1147    }
1148}
1149
1150/// Reject a scan header that is not the single full-spectrum scan baseline
1151/// requires.
1152fn 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
1171/// Read one `DQT` segment, which may define several tables.
1172fn 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
1220/// Read one `DHT` segment, which may define several tables.
1221fn 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        // Past the first row the geometry is settled.
1283        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        // The descriptor has to be one this decoder could have offered, not an
1302        // arbitrary size: only the four M/8 scales are reachable without
1303        // resampling, which is the caller's job and not ours.
1304        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/// Whether `prefix` starts with a JPEG signature.
1353///
1354/// Detection is by magic bytes only (SPEC §Formats).
1355#[must_use]
1356pub fn probe(prefix: &[u8]) -> bool {
1357    prefix.get(..3) == Some(&crate::format::SIGNATURE[..])
1358}
1359
1360/// The JPEG entry in a sniffing registry.
1361#[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    /// The split-constant form must equal the direct JFIF formula for every
1383    /// one of the 16.7 million inputs, not merely for typical ones.
1384    #[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}