Skip to main content

docling_core/
jpeg.rs

1//! A JPEG decoder that reproduces libjpeg(-turbo)'s output byte for byte —
2//! what pdfium's `DCTDecode` produces (`core/fxcodec/jpeg`, libjpeg at its
3//! defaults: the `JDCT_ISLOW` integer IDCT, "fancy" triangle-filter chroma
4//! upsampling, the 16.16 fixed-point YCbCr→RGB tables of `jdcolor.c`).
5//!
6//! Neither pure-Rust decoder in the dependency tree matches those bytes:
7//! `zune-jpeg` (the `image` crate's) rounds its upsampler and colour
8//! conversion differently, `jpeg-decoder` carries stb_image's `+8/+8` h2v2
9//! filter where libjpeg alternates `+8/+7` and a float-derived colour
10//! conversion. The pixel differences are ±1, invisible — and exactly what a
11//! byte-for-byte oracle against pdfium's raster cannot tolerate. So this is a
12//! deliberately small decoder: baseline and progressive Huffman, 8-bit,
13//! 1 or 3 components, restart intervals, and libjpeg's `1/2`, `1/4`, `1/8`
14//! DCT-scaled output (`jidctred.c`'s 4×4/2×2/1×1 transforms with
15//! `jdmaster.c`'s per-component scaling and the scaled upsampler rules);
16//! everything else (arithmetic coding, 12-bit, lossless, CMYK/YCCK, DNL) is
17//! reported as unsupported and the page stays with pdfium. Checked against
18//! Pillow's libjpeg-turbo on docling-pdf's `tests/data/jpeg/` fixtures
19//! (`raster::jpeg_tests::matches_libjpeg_on_the_fixtures`).
20//!
21//! It lives in docling-core because Pillow decodes with the same libjpeg:
22//! besides docling-pdf's raster/renderer (which re-export it), the DocLang
23//! serializer hashes a JPEG picture's decoded pixels to name its asset the
24//! way docling does (`pixel_digest.rs`).
25
26/// A decoded image: `channels` is 1 (gray) or 3 (RGB, or the raw component
27/// triplets when no colour transform applies), rows of `width` pixels.
28#[derive(Debug, Clone)]
29pub struct Image {
30    pub width: usize,
31    pub height: usize,
32    pub channels: usize,
33    pub data: Vec<u8>,
34    /// A four-component image carried an Adobe APP14 marker: its CMYK
35    /// samples are stored inverted (Adobe's convention), as libjpeg hands
36    /// them out.
37    pub adobe_inverted: bool,
38}
39
40#[derive(Debug, Clone, PartialEq, Eq)]
41pub enum Error {
42    Unsupported(&'static str),
43    Corrupt(&'static str),
44}
45
46/// Header facts the caller needs before decoding.
47#[derive(Debug, Clone, Copy)]
48pub struct Info {
49    pub width: usize,
50    pub height: usize,
51    pub components: usize,
52}
53
54const ZIGZAG: [usize; 64] = [
55    0, 1, 8, 16, 9, 2, 3, 10, 17, 24, 32, 25, 18, 11, 4, 5, 12, 19, 26, 33, 40, 48, 41, 34, 27, 20,
56    13, 6, 7, 14, 21, 28, 35, 42, 49, 56, 57, 50, 43, 36, 29, 22, 15, 23, 30, 37, 44, 51, 58, 59,
57    52, 45, 38, 31, 39, 46, 53, 60, 61, 54, 47, 55, 62, 63,
58];
59
60#[derive(Clone, Default)]
61struct Huffman {
62    /// `maxcode[l]` for code length `l` (1..=16), -1 where no code has that
63    /// length; `valptr[l]` the index of the first value of that length and
64    /// `mincode[l]` its code — libjpeg's slow-path decode (`jpeg_huff_decode`).
65    maxcode: [i32; 18],
66    valptr: [i32; 17],
67    mincode: [i32; 17],
68    values: Vec<u8>,
69    present: bool,
70}
71
72impl Huffman {
73    fn build(bits: &[u8; 16], values: Vec<u8>) -> Result<Self, Error> {
74        let mut h = Huffman {
75            values,
76            present: true,
77            ..Default::default()
78        };
79        let mut code: i32 = 0;
80        let mut k: i32 = 0;
81        for l in 1..=16usize {
82            let n = i32::from(bits[l - 1]);
83            if n == 0 {
84                h.maxcode[l] = -1;
85            } else {
86                h.valptr[l] = k;
87                h.mincode[l] = code;
88                code += n;
89                k += n;
90                h.maxcode[l] = code - 1;
91            }
92            code <<= 1;
93        }
94        h.maxcode[17] = i32::MAX;
95        if k as usize != h.values.len() {
96            return Err(Error::Corrupt("DHT value count"));
97        }
98        Ok(h)
99    }
100}
101
102struct Reader<'a> {
103    data: &'a [u8],
104    pos: usize,
105    acc: u64,
106    nbits: u32,
107    /// A marker met inside the entropy-coded data: bits after it read as
108    /// zeros, as libjpeg's `jpeg_fill_bit_buffer` fills them.
109    marker: Option<u8>,
110}
111
112impl<'a> Reader<'a> {
113    fn new(data: &'a [u8], pos: usize) -> Self {
114        Reader {
115            data,
116            pos,
117            acc: 0,
118            nbits: 0,
119            marker: None,
120        }
121    }
122
123    fn fill(&mut self) {
124        while self.nbits <= 56 {
125            let byte = if self.marker.is_some() {
126                0
127            } else {
128                match self.data.get(self.pos) {
129                    None => {
130                        self.marker = Some(0xD9);
131                        0
132                    }
133                    Some(&0xFF) => {
134                        let next = self.data.get(self.pos + 1).copied().unwrap_or(0xD9);
135                        if next == 0 {
136                            self.pos += 2;
137                            0xFF
138                        } else if next == 0xFF {
139                            // Fill byte before a marker.
140                            self.pos += 1;
141                            continue;
142                        } else {
143                            self.marker = Some(next);
144                            0
145                        }
146                    }
147                    Some(&b) => {
148                        self.pos += 1;
149                        b
150                    }
151                }
152            };
153            self.acc |= u64::from(byte) << (56 - self.nbits);
154            self.nbits += 8;
155        }
156    }
157
158    fn bits(&mut self, n: u32) -> u32 {
159        if n == 0 {
160            return 0;
161        }
162        if self.nbits < n {
163            self.fill();
164        }
165        let v = (self.acc >> (64 - n)) as u32;
166        self.acc <<= n;
167        self.nbits -= n;
168        v
169    }
170
171    fn bit(&mut self) -> u32 {
172        self.bits(1)
173    }
174
175    /// `HUFF_EXTEND`: the `s`-bit magnitude category to a signed value.
176    fn receive_extend(&mut self, s: u32) -> i32 {
177        if s == 0 {
178            return 0;
179        }
180        let v = self.bits(s) as i32;
181        if v < (1 << (s - 1)) {
182            v - (1 << s) + 1
183        } else {
184            v
185        }
186    }
187
188    fn decode(&mut self, h: &Huffman) -> Result<u8, Error> {
189        if !h.present {
190            return Err(Error::Corrupt("missing Huffman table"));
191        }
192        let mut code = self.bit() as i32;
193        let mut l = 1usize;
194        while code > h.maxcode[l] {
195            code = (code << 1) | self.bit() as i32;
196            l += 1;
197            if l > 16 {
198                // libjpeg warns and yields 0 here.
199                return Ok(0);
200            }
201        }
202        let idx = h.valptr[l] + code - h.mincode[l];
203        Ok(h.values.get(idx as usize).copied().unwrap_or(0))
204    }
205
206    /// Drop the buffered bits and the marker note (restart / end of scan).
207    fn reset(&mut self) {
208        self.acc = 0;
209        self.nbits = 0;
210        self.marker = None;
211    }
212
213    /// Position of the next marker's `0xFF` after the entropy-coded segment.
214    fn marker_position(&self) -> usize {
215        // Bytes still in the accumulator were consumed from `pos` already,
216        // so the marker (if seen) sits exactly at `pos`.
217        if self.marker.is_some() {
218            return self.pos;
219        }
220        let mut p = self.pos;
221        while p + 1 < self.data.len() {
222            if self.data[p] == 0xFF && self.data[p + 1] != 0 && self.data[p + 1] != 0xFF {
223                return p;
224            }
225            p += 1;
226        }
227        self.data.len()
228    }
229}
230
231struct Component {
232    id: u8,
233    h: usize,
234    v: usize,
235    tq: usize,
236    /// Blocks per line / rows of the padded (whole-MCU) grid.
237    bw: usize,
238    bh: usize,
239    /// `downsampled_width/height` after IDCT scaling:
240    /// `ceil(X·h·dct/(hmax·8))`, `ceil(Y·v·dct/(vmax·8))`.
241    dw: usize,
242    dh: usize,
243    /// `DCT_scaled_size`: the IDCT output size per block (8, 4, 2 or 1) —
244    /// libjpeg scales chroma up through the IDCT rather than the upsampler
245    /// where the sampling ratios allow (`jpeg_calc_output_dimensions`).
246    dct: usize,
247    /// Coefficients in natural order, `bw·bh` blocks of 64 (progressive
248    /// accumulates here; baseline reconstructs each block on the spot).
249    coefs: Vec<i16>,
250    /// Reconstructed samples, `bw·dct` × `bh·dct`.
251    samples: Vec<u8>,
252    dc_tbl: usize,
253    ac_tbl: usize,
254    dc_pred: i32,
255}
256
257#[derive(Clone, Copy, PartialEq, Eq, Debug)]
258enum ColorSpace {
259    Gray,
260    YCbCr,
261    Rgb,
262}
263
264struct Decoder<'a> {
265    data: &'a [u8],
266    qt: [[u16; 64]; 4],
267    qt_present: [bool; 4],
268    dc: [Huffman; 4],
269    ac: [Huffman; 4],
270    comps: Vec<Component>,
271    width: usize,
272    height: usize,
273    hmax: usize,
274    vmax: usize,
275    mcux: usize,
276    mcuy: usize,
277    progressive: bool,
278    restart_interval: usize,
279    saw_jfif: bool,
280    adobe_transform: Option<u8>,
281    eobrun: u32,
282    /// `min_DCT_scaled_size` for the requested `1/scale_denom`: 8, 4, 2 or 1.
283    min_dct: usize,
284}
285
286/// Read the frame header only.
287pub fn info(data: &[u8]) -> Result<Info, Error> {
288    let mut d = Decoder::new(data, 1)?;
289    d.run(true)?;
290    Ok(Info {
291        width: d.width,
292        height: d.height,
293        components: d.comps.len(),
294    })
295}
296
297/// Decode `data` at `1/scale_denom` (1, 2, 4 or 8 — libjpeg's DCT scaling,
298/// which pdfium requests for an image at least twice the bitmap's size:
299/// `resolution_levels_to_skip`); the output is `ceil(w/scale_denom)` ×
300/// `ceil(h/scale_denom)`. `color_transform` is the PDF's `/ColorTransform`
301/// decode parameter (default 1), which pdfium forces on when an Adobe marker
302/// is present and otherwise uses to decide whether a 3-component image is
303/// converted from YCbCr or handed over raw.
304pub fn decode(data: &[u8], color_transform: bool, scale_denom: u32) -> Result<Image, Error> {
305    let mut d = Decoder::new(data, scale_denom)?;
306    d.run(false)?;
307    d.finish(color_transform)
308}
309
310impl<'a> Decoder<'a> {
311    fn new(data: &'a [u8], scale_denom: u32) -> Result<Self, Error> {
312        // `jpeg_core_output_dimensions` for `scale_num = 1`.
313        let min_dct = match scale_denom {
314            1 => 8,
315            2 => 4,
316            4 => 2,
317            8 => 1,
318            _ => return Err(Error::Unsupported("DCT scale")),
319        };
320        Ok(Decoder {
321            data,
322            qt: [[0; 64]; 4],
323            qt_present: [false; 4],
324            dc: Default::default(),
325            ac: Default::default(),
326            comps: Vec::new(),
327            width: 0,
328            height: 0,
329            hmax: 1,
330            vmax: 1,
331            mcux: 0,
332            mcuy: 0,
333            progressive: false,
334            restart_interval: 0,
335            saw_jfif: false,
336            adobe_transform: None,
337            eobrun: 0,
338            min_dct,
339        })
340    }
341
342    fn u16_at(&self, p: usize) -> Result<usize, Error> {
343        match (self.data.get(p), self.data.get(p + 1)) {
344            (Some(&a), Some(&b)) => Ok(usize::from(a) << 8 | usize::from(b)),
345            _ => Err(Error::Corrupt("truncated")),
346        }
347    }
348
349    /// Walk the marker segments; with `header_only`, stop at the first SOS.
350    fn run(&mut self, header_only: bool) -> Result<(), Error> {
351        let mut p = 0usize;
352        // Tolerate leading garbage before SOI, as libjpeg does not but pdfium's
353        // stream boundaries do.
354        while p + 1 < self.data.len() && !(self.data[p] == 0xFF && self.data[p + 1] == 0xD8) {
355            p += 1;
356        }
357        if p + 1 >= self.data.len() {
358            return Err(Error::Corrupt("no SOI"));
359        }
360        p += 2;
361        loop {
362            // Skip fill bytes to the next marker.
363            while p < self.data.len() && self.data[p] != 0xFF {
364                p += 1;
365            }
366            while p < self.data.len() && self.data[p] == 0xFF {
367                p += 1;
368            }
369            let Some(&marker) = self.data.get(p) else {
370                return if self.comps.is_empty() {
371                    Err(Error::Corrupt("no frame"))
372                } else {
373                    Ok(())
374                };
375            };
376            p += 1;
377            match marker {
378                0xD8 | 0x01 | 0xD0..=0xD7 => continue, // standalone markers
379                0xD9 => return Ok(()),                 // EOI
380                _ => {}
381            }
382            let len = self.u16_at(p)?;
383            if len < 2 {
384                return Err(Error::Corrupt("segment length"));
385            }
386            let seg = self
387                .data
388                .get(p + 2..p + len)
389                .ok_or(Error::Corrupt("truncated segment"))?;
390            match marker {
391                0xC0..=0xC2 => {
392                    self.progressive = marker == 0xC2;
393                    self.frame(seg)?;
394                }
395                0xC3 | 0xC5..=0xC7 | 0xC9..=0xCB | 0xCD..=0xCF => {
396                    return Err(Error::Unsupported(
397                        "lossless, hierarchical or arithmetic JPEG",
398                    ));
399                }
400                0xC4 => self.dht(seg)?,
401                0xDB => self.dqt(seg)?,
402                0xDD => {
403                    if seg.len() < 2 {
404                        return Err(Error::Corrupt("DRI"));
405                    }
406                    self.restart_interval = usize::from(seg[0]) << 8 | usize::from(seg[1]);
407                }
408                0xDC => return Err(Error::Unsupported("DNL")),
409                0xE0 => {
410                    if seg.starts_with(b"JFIF\0") {
411                        self.saw_jfif = true;
412                    }
413                }
414                0xEE => {
415                    if seg.starts_with(b"Adobe") && seg.len() >= 12 {
416                        self.adobe_transform = Some(seg[11]);
417                    }
418                }
419                0xDA => {
420                    if header_only {
421                        return if self.comps.is_empty() {
422                            Err(Error::Corrupt("SOS before SOF"))
423                        } else {
424                            Ok(())
425                        };
426                    }
427                    let end = self.scan(seg, p + len)?;
428                    p = end;
429                    continue;
430                }
431                _ => {}
432            }
433            p += len;
434        }
435    }
436
437    fn frame(&mut self, seg: &[u8]) -> Result<(), Error> {
438        if seg.len() < 6 {
439            return Err(Error::Corrupt("SOF"));
440        }
441        if seg[0] != 8 {
442            return Err(Error::Unsupported("sample precision other than 8"));
443        }
444        self.height = usize::from(seg[1]) << 8 | usize::from(seg[2]);
445        self.width = usize::from(seg[3]) << 8 | usize::from(seg[4]);
446        let n = usize::from(seg[5]);
447        if self.height == 0 {
448            return Err(Error::Unsupported("DNL-defined height"));
449        }
450        if self.width == 0 || !(n == 1 || n == 3 || n == 4) {
451            return Err(Error::Unsupported("component count"));
452        }
453        if seg.len() < 6 + 3 * n {
454            return Err(Error::Corrupt("SOF components"));
455        }
456        self.comps.clear();
457        for i in 0..n {
458            let c = &seg[6 + 3 * i..9 + 3 * i];
459            let (h, v) = (usize::from(c[1] >> 4), usize::from(c[1] & 15));
460            if !(1..=4).contains(&h) || !(1..=4).contains(&v) || c[2] > 3 {
461                return Err(Error::Corrupt("sampling factors"));
462            }
463            self.comps.push(Component {
464                id: c[0],
465                h,
466                v,
467                tq: usize::from(c[2]),
468                bw: 0,
469                bh: 0,
470                dw: 0,
471                dh: 0,
472                dct: 8,
473                coefs: Vec::new(),
474                samples: Vec::new(),
475                dc_tbl: 0,
476                ac_tbl: 0,
477                dc_pred: 0,
478            });
479        }
480        self.hmax = self.comps.iter().map(|c| c.h).max().unwrap_or(1);
481        self.vmax = self.comps.iter().map(|c| c.v).max().unwrap_or(1);
482        self.mcux = self.width.div_ceil(8 * self.hmax);
483        self.mcuy = self.height.div_ceil(8 * self.vmax);
484        let (w, h, hmax, vmax, mcux, mcuy, progressive, min_dct) = (
485            self.width,
486            self.height,
487            self.hmax,
488            self.vmax,
489            self.mcux,
490            self.mcuy,
491            self.progressive,
492            self.min_dct,
493        );
494        for c in &mut self.comps {
495            c.bw = mcux * c.h;
496            c.bh = mcuy * c.v;
497            // `jpeg_calc_output_dimensions`: scale a subsampled component up
498            // through the IDCT while the ratios stay integral. (`%`, not
499            // `is_multiple_of`: docling-core's MSRV is 1.85; the sampling
500            // factors are validated 1..=4, so no divisor is zero.)
501            let mut ssize = min_dct;
502            while ssize < 8
503                && (hmax * min_dct) % (c.h * ssize * 2) == 0
504                && (vmax * min_dct) % (c.v * ssize * 2) == 0
505            {
506                ssize *= 2;
507            }
508            c.dct = ssize;
509            c.dw = (w * c.h * c.dct).div_ceil(hmax * 8);
510            c.dh = (h * c.v * c.dct).div_ceil(vmax * 8);
511            let blocks = c.bw * c.bh;
512            if blocks > (1usize << 26) {
513                return Err(Error::Unsupported("image too large"));
514            }
515            if progressive {
516                c.coefs = vec![0; blocks * 64];
517            }
518            c.samples = vec![0; blocks * c.dct * c.dct];
519        }
520        Ok(())
521    }
522
523    fn dht(&mut self, mut seg: &[u8]) -> Result<(), Error> {
524        while !seg.is_empty() {
525            if seg.len() < 17 {
526                return Err(Error::Corrupt("DHT"));
527            }
528            let class = seg[0] >> 4;
529            let id = usize::from(seg[0] & 15);
530            if id > 3 || class > 1 {
531                return Err(Error::Corrupt("DHT id"));
532            }
533            let mut bits = [0u8; 16];
534            bits.copy_from_slice(&seg[1..17]);
535            let total: usize = bits.iter().map(|&b| usize::from(b)).sum();
536            if total > 256 || seg.len() < 17 + total {
537                return Err(Error::Corrupt("DHT counts"));
538            }
539            let table = Huffman::build(&bits, seg[17..17 + total].to_vec())?;
540            if class == 0 {
541                self.dc[id] = table;
542            } else {
543                self.ac[id] = table;
544            }
545            seg = &seg[17 + total..];
546        }
547        Ok(())
548    }
549
550    fn dqt(&mut self, mut seg: &[u8]) -> Result<(), Error> {
551        while !seg.is_empty() {
552            let pq = seg[0] >> 4;
553            let tq = usize::from(seg[0] & 15);
554            if tq > 3 || pq > 1 {
555                return Err(Error::Corrupt("DQT id"));
556            }
557            let n = if pq == 0 { 64 } else { 128 };
558            if seg.len() < 1 + n {
559                return Err(Error::Corrupt("DQT"));
560            }
561            for k in 0..64 {
562                let v = if pq == 0 {
563                    u16::from(seg[1 + k])
564                } else {
565                    u16::from(seg[1 + 2 * k]) << 8 | u16::from(seg[2 + 2 * k])
566                };
567                self.qt[tq][ZIGZAG[k]] = v;
568            }
569            self.qt_present[tq] = true;
570            seg = &seg[1 + n..];
571        }
572        Ok(())
573    }
574
575    /// Decode one scan whose entropy-coded data starts at `start`; returns
576    /// the position of the marker that ends it.
577    fn scan(&mut self, seg: &[u8], start: usize) -> Result<usize, Error> {
578        if self.comps.is_empty() {
579            return Err(Error::Corrupt("SOS before SOF"));
580        }
581        let ns = usize::from(*seg.first().ok_or(Error::Corrupt("SOS"))?);
582        if ns == 0 || ns > 4 || seg.len() < 1 + 2 * ns + 3 {
583            return Err(Error::Corrupt("SOS"));
584        }
585        let mut in_scan = Vec::with_capacity(ns);
586        for i in 0..ns {
587            let cs = seg[1 + 2 * i];
588            let t = seg[2 + 2 * i];
589            let ci = self
590                .comps
591                .iter()
592                .position(|c| c.id == cs)
593                .ok_or(Error::Corrupt("SOS component"))?;
594            self.comps[ci].dc_tbl = usize::from(t >> 4).min(3);
595            self.comps[ci].ac_tbl = usize::from(t & 15).min(3);
596            in_scan.push(ci);
597        }
598        let ss = usize::from(seg[1 + 2 * ns]);
599        let se = usize::from(seg[2 + 2 * ns]);
600        let ah = u32::from(seg[3 + 2 * ns] >> 4);
601        let al = u32::from(seg[3 + 2 * ns] & 15);
602        if self.progressive {
603            if ss > se || se > 63 || (ss == 0 && se != 0) || (ss > 0 && ns != 1) || al > 13 {
604                return Err(Error::Corrupt("progressive scan parameters"));
605            }
606        } else if ss != 0 || se != 63 || ah != 0 || al != 0 {
607            return Err(Error::Corrupt("sequential scan parameters"));
608        }
609        for c in &mut self.comps {
610            c.dc_pred = 0;
611        }
612        self.eobrun = 0;
613        let mut rd = Reader::new(self.data, start);
614
615        // MCU geometry: interleaved scans walk whole MCUs, a single-component
616        // scan walks that component's own block grid (A.2.2) —
617        // `width_in_blocks = ceil(image_width · h / (hmax · 8))`, the
618        // *coded* size, whatever DCT scaling the output is asked for (the
619        // scaled `dw`/`dh` would walk a fraction of the blocks and leave the
620        // rest of a reduced grayscale or progressive decode black).
621        let (mcus_x, mcus_y) = if ns == 1 {
622            let c = &self.comps[in_scan[0]];
623            (
624                (self.width * c.h).div_ceil(self.hmax * 8),
625                (self.height * c.v).div_ceil(self.vmax * 8),
626            )
627        } else {
628            (self.mcux, self.mcuy)
629        };
630        let total = mcus_x * mcus_y;
631        let mut count = 0usize;
632        for my in 0..mcus_y {
633            for mx in 0..mcus_x {
634                if self.restart_interval > 0 && count > 0 && count % self.restart_interval == 0 {
635                    self.restart(&mut rd);
636                }
637                if ns == 1 {
638                    let ci = in_scan[0];
639                    self.block(&mut rd, ci, mx, my, ss, se, ah, al)?;
640                } else {
641                    for &ci in &in_scan {
642                        let (h, v) = (self.comps[ci].h, self.comps[ci].v);
643                        for by in 0..v {
644                            for bx in 0..h {
645                                self.block(&mut rd, ci, mx * h + bx, my * v + by, ss, se, ah, al)?;
646                            }
647                        }
648                    }
649                }
650                count += 1;
651                if count == total {
652                    break;
653                }
654            }
655        }
656        Ok(rd.marker_position())
657    }
658
659    /// Consume the RSTn marker between restart intervals and reset the
660    /// predictors, as `read_restart_marker` does.
661    fn restart(&mut self, rd: &mut Reader<'_>) {
662        let mut p = rd.marker_position();
663        // p points at 0xFF of the marker; skip 0xFF fill and the marker byte.
664        while p < self.data.len() && self.data[p] == 0xFF {
665            p += 1;
666        }
667        if p < self.data.len() && (0xD0..=0xD7).contains(&self.data[p]) {
668            p += 1;
669        }
670        rd.reset();
671        rd.pos = p;
672        for c in &mut self.comps {
673            c.dc_pred = 0;
674        }
675        self.eobrun = 0;
676    }
677
678    #[allow(clippy::too_many_arguments)]
679    fn block(
680        &mut self,
681        rd: &mut Reader<'_>,
682        ci: usize,
683        bx: usize,
684        by: usize,
685        ss: usize,
686        se: usize,
687        ah: u32,
688        al: u32,
689    ) -> Result<(), Error> {
690        let (bw, bh) = (self.comps[ci].bw, self.comps[ci].bh);
691        if bx >= bw || by >= bh {
692            return Err(Error::Corrupt("block outside the grid"));
693        }
694        let bi = by * bw + bx;
695        if !self.progressive {
696            let mut coef = [0i16; 64];
697            let dc = &self.dc[self.comps[ci].dc_tbl];
698            let ac = &self.ac[self.comps[ci].ac_tbl];
699            let t = rd.decode(dc)?;
700            let diff = rd.receive_extend(u32::from(t));
701            let c = &mut self.comps[ci];
702            c.dc_pred = c.dc_pred.wrapping_add(diff);
703            coef[0] = c.dc_pred as i16;
704            let mut k = 1usize;
705            while k < 64 {
706                let rs = rd.decode(ac)?;
707                let r = usize::from(rs >> 4);
708                let s = u32::from(rs & 15);
709                if s == 0 {
710                    if r == 15 {
711                        k += 16;
712                        continue;
713                    }
714                    break;
715                }
716                k += r;
717                if k > 63 {
718                    break;
719                }
720                coef[ZIGZAG[k]] = rd.receive_extend(s) as i16;
721                k += 1;
722            }
723            let q = &self.qt[self.comps[ci].tq.min(3)];
724            let c = &mut self.comps[ci];
725            idct(c.dct, &coef, q, &mut c.samples, bi, bw);
726            return Ok(());
727        }
728
729        // Progressive.
730        let dc = &self.dc[self.comps[ci].dc_tbl];
731        let ac = &self.ac[self.comps[ci].ac_tbl];
732        let c = &mut self.comps[ci];
733        let coef = &mut c.coefs[bi * 64..bi * 64 + 64];
734        if ss == 0 {
735            if ah == 0 {
736                // DC first.
737                let t = rd.decode(dc)?;
738                let diff = rd.receive_extend(u32::from(t));
739                c.dc_pred = c.dc_pred.wrapping_add(diff);
740                coef[0] = (c.dc_pred << al) as i16;
741            } else if rd.bit() == 1 {
742                // DC refine.
743                coef[0] |= (1i32 << al) as i16;
744            }
745            return Ok(());
746        }
747        if ah == 0 {
748            // AC first (jdphuff.c decode_mcu_AC_first).
749            if self.eobrun > 0 {
750                self.eobrun -= 1;
751                return Ok(());
752            }
753            let mut k = ss;
754            while k <= se {
755                let rs = rd.decode(ac)?;
756                let r = u32::from(rs >> 4);
757                let s = u32::from(rs & 15);
758                if s != 0 {
759                    k += r as usize;
760                    if k > 63 {
761                        break;
762                    }
763                    let v = rd.receive_extend(s);
764                    coef[ZIGZAG[k]] = (v << al) as i16;
765                } else {
766                    if r != 15 {
767                        self.eobrun = 1 << r;
768                        if r > 0 {
769                            self.eobrun += rd.bits(r);
770                        }
771                        self.eobrun -= 1;
772                        break;
773                    }
774                    k += 15;
775                }
776                k += 1;
777            }
778            return Ok(());
779        }
780        // AC refine (jdphuff.c decode_mcu_AC_refine).
781        let p1: i16 = (1i32 << al) as i16;
782        let m1: i16 = (-1i32 << al) as i16;
783        let mut k = ss;
784        if self.eobrun == 0 {
785            while k <= se {
786                let rs = rd.decode(ac)?;
787                let mut r = i32::from(rs >> 4);
788                let mut s = i32::from(rs & 15);
789                if s != 0 {
790                    // Newly nonzero coefficient: sign bit.
791                    s = if rd.bit() == 1 {
792                        i32::from(p1)
793                    } else {
794                        i32::from(m1)
795                    };
796                } else if r != 15 {
797                    self.eobrun = 1 << r;
798                    if r > 0 {
799                        self.eobrun += rd.bits(r as u32);
800                    }
801                    break;
802                }
803                // Advance over already-nonzero coefficients and `r` zero ones,
804                // appending correction bits to the nonzero ones.
805                loop {
806                    let pos = ZIGZAG[k];
807                    if coef[pos] != 0 {
808                        if rd.bit() == 1 && (coef[pos] & p1) == 0 {
809                            coef[pos] = if coef[pos] >= 0 {
810                                coef[pos].wrapping_add(p1)
811                            } else {
812                                coef[pos].wrapping_add(m1)
813                            };
814                        }
815                    } else {
816                        r -= 1;
817                        if r < 0 {
818                            break;
819                        }
820                    }
821                    k += 1;
822                    if k > se {
823                        break;
824                    }
825                }
826                if s != 0 && k <= se {
827                    coef[ZIGZAG[k]] = s as i16;
828                }
829                k += 1;
830            }
831        }
832        if self.eobrun > 0 {
833            while k <= se {
834                let pos = ZIGZAG[k];
835                if coef[pos] != 0 && rd.bit() == 1 && (coef[pos] & p1) == 0 {
836                    coef[pos] = if coef[pos] >= 0 {
837                        coef[pos].wrapping_add(p1)
838                    } else {
839                        coef[pos].wrapping_add(m1)
840                    };
841                }
842                k += 1;
843            }
844            self.eobrun -= 1;
845        }
846        Ok(())
847    }
848
849    /// Reconstruct the progressive planes, upsample, convert.
850    fn finish(mut self, color_transform: bool) -> Result<Image, Error> {
851        if self.comps.is_empty() {
852            return Err(Error::Corrupt("no frame"));
853        }
854        for c in &self.comps {
855            if !self.qt_present[c.tq.min(3)] {
856                return Err(Error::Corrupt("missing quantization table"));
857            }
858        }
859        if self.progressive {
860            for ci in 0..self.comps.len() {
861                let q = self.qt[self.comps[ci].tq.min(3)];
862                let c = &mut self.comps[ci];
863                let bw = c.bw;
864                for bi in 0..c.bw * c.bh {
865                    let mut coef = [0i16; 64];
866                    coef.copy_from_slice(&c.coefs[bi * 64..bi * 64 + 64]);
867                    idct(c.dct, &coef, &q, &mut c.samples, bi, bw);
868                }
869                c.coefs = Vec::new();
870            }
871        }
872        // `output_width/height`: `ceil(dim · min_DCT_scaled_size / 8)`.
873        let (w, h) = (
874            (self.width * self.min_dct).div_ceil(8),
875            (self.height * self.min_dct).div_ceil(8),
876        );
877        let planes: Vec<Vec<u8>> = self.comps.iter().map(|c| self.upsample(c, w, h)).collect();
878        let n = self.comps.len();
879        let space = self.color_space();
880        // pdfium: the /ColorTransform parameter, forced on by an Adobe marker;
881        // off, a 3-component image is handed over in its own space.
882        let convert = n == 3
883            && space == ColorSpace::YCbCr
884            && (color_transform || self.adobe_transform.is_some());
885        let mut out = vec![0u8; w * h * n];
886        if n == 1 {
887            out.copy_from_slice(&planes[0][..w * h]);
888        } else if n == 4 {
889            // `ycck_cmyk_convert` (jdcolor.c) for Adobe transform 2, else
890            // the four planes as stored (transform 0 = CMYK).
891            let ycck = self.adobe_transform == Some(2);
892            let t = ycc_tables();
893            for i in 0..w * h {
894                if ycck {
895                    let y = i32::from(planes[0][i]);
896                    let cb = usize::from(planes[1][i]);
897                    let cr = usize::from(planes[2][i]);
898                    out[4 * i] = range_limit(255 - (y + t.cr_r[cr]));
899                    out[4 * i + 1] = range_limit(255 - (y + ((t.cb_g[cb] + t.cr_g[cr]) >> 16)));
900                    out[4 * i + 2] = range_limit(255 - (y + t.cb_b[cb]));
901                } else {
902                    out[4 * i] = planes[0][i];
903                    out[4 * i + 1] = planes[1][i];
904                    out[4 * i + 2] = planes[2][i];
905                }
906                out[4 * i + 3] = planes[3][i];
907            }
908        } else if convert {
909            let t = ycc_tables();
910            for i in 0..w * h {
911                let y = i32::from(planes[0][i]);
912                let cb = usize::from(planes[1][i]);
913                let cr = usize::from(planes[2][i]);
914                out[3 * i] = range_limit(y + t.cr_r[cr]);
915                out[3 * i + 1] = range_limit(y + ((t.cb_g[cb] + t.cr_g[cr]) >> 16));
916                out[3 * i + 2] = range_limit(y + t.cb_b[cb]);
917            }
918        } else {
919            for i in 0..w * h {
920                out[3 * i] = planes[0][i];
921                out[3 * i + 1] = planes[1][i];
922                out[3 * i + 2] = planes[2][i];
923            }
924        }
925        Ok(Image {
926            width: w,
927            height: h,
928            channels: n,
929            data: out,
930            adobe_inverted: n == 4 && self.adobe_transform.is_some(),
931        })
932    }
933
934    /// `default_decompress_parms`: what the components are.
935    fn color_space(&self) -> ColorSpace {
936        if self.comps.len() == 1 {
937            return ColorSpace::Gray;
938        }
939        if self.saw_jfif {
940            return ColorSpace::YCbCr;
941        }
942        if let Some(t) = self.adobe_transform {
943            return if t == 0 {
944                ColorSpace::Rgb
945            } else {
946                ColorSpace::YCbCr
947            };
948        }
949        let ids = [self.comps[0].id, self.comps[1].id, self.comps[2].id];
950        if ids == [b'R', b'G', b'B'] {
951            ColorSpace::Rgb
952        } else {
953            ColorSpace::YCbCr
954        }
955    }
956
957    /// One component to the output size (`jinit_upsampler`): input groups
958    /// of `h·dct/min_dct` × `v·dct/min_dct` samples become `hmax` × `vmax`
959    /// — the triangle filters for 2:1 (only while `do_fancy`, i.e. the IDCT
960    /// still produces more than one sample per block, and the row is wider
961    /// than 2), replication otherwise, with libjpeg's edge rules (the row
962    /// above the first / below the last is the edge row itself; the last
963    /// column repeats).
964    fn upsample(&self, c: &Component, w: usize, h: usize) -> Vec<u8> {
965        let stride = c.bw * c.dct;
966        let (dw, dh) = (c.dw, c.dh);
967        let row = |r: usize| -> &[u8] {
968            let r = r.min(dh.saturating_sub(1));
969            &c.samples[r * stride..r * stride + dw]
970        };
971        let h_in = c.h * c.dct / self.min_dct;
972        let v_in = c.v * c.dct / self.min_dct;
973        let (h_out, v_out) = (self.hmax, self.vmax);
974        let do_fancy = self.min_dct > 1;
975        let mut out = vec![0u8; w * h];
976        let replicate = |out: &mut Vec<u8>, h_exp: usize, v_exp: usize| {
977            for y in 0..h {
978                let src = row(y / v_exp);
979                for x in 0..w {
980                    out[y * w + x] = src[(x / h_exp).min(dw - 1)];
981                }
982            }
983        };
984        if h_in == h_out && v_in == v_out {
985            for y in 0..h {
986                out[y * w..y * w + w].copy_from_slice(&row(y)[..w]);
987            }
988        } else if h_in * 2 == h_out && v_in == v_out {
989            if do_fancy && dw > 2 {
990                let mut line = vec![0u8; dw * 2];
991                for y in 0..h {
992                    h2v1_fancy(row(y), &mut line);
993                    out[y * w..y * w + w].copy_from_slice(&line[..w]);
994                }
995            } else {
996                replicate(&mut out, 2, 1);
997            }
998        } else if h_in == h_out && v_in * 2 == v_out && do_fancy {
999            for r in 0..dh {
1000                for v in 0..2 {
1001                    let y = 2 * r + v;
1002                    if y >= h {
1003                        break;
1004                    }
1005                    let near = row(r);
1006                    let far = if v == 0 {
1007                        row(r.saturating_sub(1))
1008                    } else {
1009                        row(r + 1)
1010                    };
1011                    let bias: u32 = if v == 0 { 1 } else { 2 };
1012                    for x in 0..w {
1013                        out[y * w + x] =
1014                            ((3 * u32::from(near[x]) + u32::from(far[x]) + bias) >> 2) as u8;
1015                    }
1016                }
1017            }
1018        } else if h_in * 2 == h_out && v_in * 2 == v_out {
1019            if do_fancy && dw > 2 {
1020                let mut line = vec![0u8; dw * 2];
1021                for r in 0..dh {
1022                    for v in 0..2 {
1023                        let y = 2 * r + v;
1024                        if y >= h {
1025                            break;
1026                        }
1027                        let near = row(r);
1028                        let far = if v == 0 {
1029                            row(r.saturating_sub(1))
1030                        } else {
1031                            row(r + 1)
1032                        };
1033                        h2v2_fancy(near, far, &mut line);
1034                        out[y * w..y * w + w].copy_from_slice(&line[..w]);
1035                    }
1036                }
1037            } else {
1038                replicate(&mut out, 2, 2);
1039            }
1040        } else if h_in > 0 && v_in > 0 && h_out % h_in == 0 && v_out % v_in == 0 {
1041            // `int_upsample`: plain replication (any integral ratio).
1042            replicate(&mut out, h_out / h_in, v_out / v_in);
1043        } else {
1044            // `JERR_FRACT_SAMPLE_NOTIMPL`: libjpeg refuses; leave the plane
1045            // black rather than guess.
1046        }
1047        out
1048    }
1049}
1050
1051/// `h2v1_fancy_upsample`: `out[2i] = (3·in[i] + in[i−1] + 1) >> 2`,
1052/// `out[2i+1] = (3·in[i] + in[i+1] + 2) >> 2`, edges replicated.
1053fn h2v1_fancy(input: &[u8], out: &mut [u8]) {
1054    let n = input.len();
1055    let at = |i: usize| u32::from(input[i]);
1056    out[0] = input[0];
1057    out[1] = ((at(0) * 3 + at(1) + 2) >> 2) as u8;
1058    for i in 1..n - 1 {
1059        let v = at(i) * 3;
1060        out[2 * i] = ((v + at(i - 1) + 1) >> 2) as u8;
1061        out[2 * i + 1] = ((v + at(i + 1) + 2) >> 2) as u8;
1062    }
1063    out[2 * (n - 1)] = ((at(n - 1) * 3 + at(n - 2) + 1) >> 2) as u8;
1064    out[2 * (n - 1) + 1] = input[n - 1];
1065}
1066
1067/// `h2v2_fancy_upsample` for one output row: column sums `3·near + far`,
1068/// then `(3·this + neighbour + 8|7) >> 4` alternating, edges replicated.
1069fn h2v2_fancy(near: &[u8], far: &[u8], out: &mut [u8]) {
1070    let n = near.len();
1071    let colsum = |i: usize| u32::from(near[i]) * 3 + u32::from(far[i]);
1072    let mut this = colsum(0);
1073    let mut next = colsum(1);
1074    out[0] = ((this * 4 + 8) >> 4) as u8;
1075    out[1] = ((this * 3 + next + 7) >> 4) as u8;
1076    let mut last = this;
1077    this = next;
1078    for i in 1..n - 1 {
1079        next = colsum(i + 1);
1080        out[2 * i] = ((this * 3 + last + 8) >> 4) as u8;
1081        out[2 * i + 1] = ((this * 3 + next + 7) >> 4) as u8;
1082        last = this;
1083        this = next;
1084    }
1085    out[2 * (n - 1)] = ((this * 3 + last + 8) >> 4) as u8;
1086    out[2 * (n - 1) + 1] = ((this * 4 + 7) >> 4) as u8;
1087}
1088
1089struct YccTables {
1090    cr_r: [i32; 256],
1091    cb_b: [i32; 256],
1092    cr_g: [i32; 256],
1093    cb_g: [i32; 256],
1094}
1095
1096/// `jdcolor.c build_ycc_rgb_table`: 16.16 fixed point, `ONE_HALF` folded
1097/// into the red/blue tables and the green Cb term.
1098fn ycc_tables() -> YccTables {
1099    const SCALEBITS: i32 = 16;
1100    const ONE_HALF: i32 = 1 << (SCALEBITS - 1);
1101    let fix = |x: f64| (x * f64::from(1i32 << SCALEBITS) + 0.5) as i32;
1102    let mut t = YccTables {
1103        cr_r: [0; 256],
1104        cb_b: [0; 256],
1105        cr_g: [0; 256],
1106        cb_g: [0; 256],
1107    };
1108    for i in 0..256i32 {
1109        let x = i - 128;
1110        t.cr_r[i as usize] = (fix(1.402) * x + ONE_HALF) >> SCALEBITS;
1111        t.cb_b[i as usize] = (fix(1.772) * x + ONE_HALF) >> SCALEBITS;
1112        t.cr_g[i as usize] = -fix(0.71414) * x;
1113        t.cb_g[i as usize] = -fix(0.34414) * x + ONE_HALF;
1114    }
1115    t
1116}
1117
1118/// libjpeg's `range_limit` for the colour converter: clamp to 0..=255.
1119fn range_limit(v: i32) -> u8 {
1120    v.clamp(0, 255) as u8
1121}
1122
1123/// The post-IDCT range-limit table (`prepare_range_limit_table`), indexed by
1124/// the descaled value `& 1023`: identity around zero (shifted by 128), 255
1125/// above, 0 below, and the wrap-around segments for out-of-range garbage.
1126fn idct_range_limit(x: i32) -> u8 {
1127    let i = (x & 1023) as usize;
1128    match i {
1129        0..=127 => (128 + i) as u8,
1130        128..=511 => 255,
1131        512..=895 => 0,
1132        _ => (i - 896) as u8,
1133    }
1134}
1135
1136/// The IDCT for a block at `DCT_scaled_size` `dct` (`jddctmgr.c`: 8 →
1137/// `jpeg_idct_islow`, 4/2/1 → the `jidctred.c` reduced-size transforms,
1138/// which always use the islow-style dequantization), writing the `dct` ×
1139/// `dct` output of block `bi` into a plane `bw` blocks wide.
1140fn idct(dct: usize, coef: &[i16; 64], q: &[u16; 64], samples: &mut [u8], bi: usize, bw: usize) {
1141    match dct {
1142        8 => idct_islow(coef, q, samples, bi, bw),
1143        4 => idct_4x4(coef, q, samples, bi, bw),
1144        2 => idct_2x2(coef, q, samples, bi, bw),
1145        _ => idct_1x1(coef, q, samples, bi, bw),
1146    }
1147}
1148
1149const CONST_BITS: i32 = 13;
1150const PASS1_BITS: i32 = 2;
1151
1152#[inline(always)]
1153fn descale(x: i32, n: i32) -> i32 {
1154    x.wrapping_add(1 << (n - 1)) >> n
1155}
1156
1157#[inline(always)]
1158fn mul(a: i32, b: i32) -> i32 {
1159    a.wrapping_mul(b)
1160}
1161
1162/// `jpeg_idct_4x4` (jidctred.c): a 4×4 output from the 8×8 block, column 4
1163/// never examined.
1164fn idct_4x4(coef: &[i16; 64], q: &[u16; 64], samples: &mut [u8], bi: usize, bw: usize) {
1165    const FIX_0_211164243: i32 = 1730;
1166    const FIX_0_509795579: i32 = 4176;
1167    const FIX_0_601344887: i32 = 4926;
1168    const FIX_0_765366865: i32 = 6270;
1169    const FIX_0_899976223: i32 = 7373;
1170    const FIX_1_061594337: i32 = 8697;
1171    const FIX_1_451774981: i32 = 11893;
1172    const FIX_1_847759065: i32 = 15137;
1173    const FIX_2_172734803: i32 = 17799;
1174    const FIX_2_562915447: i32 = 20995;
1175    let dq = |k: usize| i32::from(coef[k]).wrapping_mul(i32::from(q[k]));
1176    let mut ws = [0i32; 32]; // 4 rows × 8 columns
1177    for col in 0..8 {
1178        if col == 4 {
1179            continue;
1180        }
1181        if [1, 2, 3, 5, 6, 7].iter().all(|&r| coef[r * 8 + col] == 0) {
1182            let dc = dq(col) << PASS1_BITS;
1183            for r in 0..4 {
1184                ws[r * 8 + col] = dc;
1185            }
1186            continue;
1187        }
1188        let tmp0 = dq(col) << (CONST_BITS + 1);
1189        let z2 = dq(2 * 8 + col);
1190        let z3 = dq(6 * 8 + col);
1191        let tmp2 = mul(z2, FIX_1_847759065).wrapping_add(mul(z3, -FIX_0_765366865));
1192        let tmp10 = tmp0.wrapping_add(tmp2);
1193        let tmp12 = tmp0.wrapping_sub(tmp2);
1194        let z1 = dq(7 * 8 + col);
1195        let z2 = dq(5 * 8 + col);
1196        let z3 = dq(3 * 8 + col);
1197        let z4 = dq(8 + col);
1198        let tmp0 = mul(z1, -FIX_0_211164243)
1199            .wrapping_add(mul(z2, FIX_1_451774981))
1200            .wrapping_add(mul(z3, -FIX_2_172734803))
1201            .wrapping_add(mul(z4, FIX_1_061594337));
1202        let tmp2 = mul(z1, -FIX_0_509795579)
1203            .wrapping_add(mul(z2, -FIX_0_601344887))
1204            .wrapping_add(mul(z3, FIX_0_899976223))
1205            .wrapping_add(mul(z4, FIX_2_562915447));
1206        let n = CONST_BITS - PASS1_BITS + 1;
1207        ws[col] = descale(tmp10.wrapping_add(tmp2), n);
1208        ws[3 * 8 + col] = descale(tmp10.wrapping_sub(tmp2), n);
1209        ws[8 + col] = descale(tmp12.wrapping_add(tmp0), n);
1210        ws[2 * 8 + col] = descale(tmp12.wrapping_sub(tmp0), n);
1211    }
1212    let stride = bw * 4;
1213    let (bx, by) = (bi % bw, bi / bw);
1214    for row in 0..4 {
1215        let w = &ws[row * 8..row * 8 + 8];
1216        let off = (by * 4 + row) * stride + bx * 4;
1217        let out = &mut samples[off..off + 4];
1218        if [1, 2, 3, 5, 6, 7].iter().all(|&k| w[k] == 0) {
1219            out.fill(idct_range_limit(descale(w[0], PASS1_BITS + 3)));
1220            continue;
1221        }
1222        let tmp0 = w[0] << (CONST_BITS + 1);
1223        let tmp2 = mul(w[2], FIX_1_847759065).wrapping_add(mul(w[6], -FIX_0_765366865));
1224        let tmp10 = tmp0.wrapping_add(tmp2);
1225        let tmp12 = tmp0.wrapping_sub(tmp2);
1226        let (z1, z2, z3, z4) = (w[7], w[5], w[3], w[1]);
1227        let tmp0 = mul(z1, -FIX_0_211164243)
1228            .wrapping_add(mul(z2, FIX_1_451774981))
1229            .wrapping_add(mul(z3, -FIX_2_172734803))
1230            .wrapping_add(mul(z4, FIX_1_061594337));
1231        let tmp2 = mul(z1, -FIX_0_509795579)
1232            .wrapping_add(mul(z2, -FIX_0_601344887))
1233            .wrapping_add(mul(z3, FIX_0_899976223))
1234            .wrapping_add(mul(z4, FIX_2_562915447));
1235        let n = CONST_BITS + PASS1_BITS + 3 + 1;
1236        out[0] = idct_range_limit(descale(tmp10.wrapping_add(tmp2), n));
1237        out[3] = idct_range_limit(descale(tmp10.wrapping_sub(tmp2), n));
1238        out[1] = idct_range_limit(descale(tmp12.wrapping_add(tmp0), n));
1239        out[2] = idct_range_limit(descale(tmp12.wrapping_sub(tmp0), n));
1240    }
1241}
1242
1243/// `jpeg_idct_2x2` (jidctred.c): columns 2, 4, 6 never examined.
1244fn idct_2x2(coef: &[i16; 64], q: &[u16; 64], samples: &mut [u8], bi: usize, bw: usize) {
1245    const FIX_0_720959822: i32 = 5906;
1246    const FIX_0_850430095: i32 = 6967;
1247    const FIX_1_272758580: i32 = 10426;
1248    const FIX_3_624509785: i32 = 29692;
1249    let dq = |k: usize| i32::from(coef[k]).wrapping_mul(i32::from(q[k]));
1250    let mut ws = [0i32; 16]; // 2 rows × 8 columns
1251    for col in [0usize, 1, 3, 5, 7] {
1252        if [1, 3, 5, 7].iter().all(|&r| coef[r * 8 + col] == 0) {
1253            let dc = dq(col) << PASS1_BITS;
1254            ws[col] = dc;
1255            ws[8 + col] = dc;
1256            continue;
1257        }
1258        let tmp10 = dq(col) << (CONST_BITS + 2);
1259        let tmp0 = mul(dq(7 * 8 + col), -FIX_0_720959822)
1260            .wrapping_add(mul(dq(5 * 8 + col), FIX_0_850430095))
1261            .wrapping_add(mul(dq(3 * 8 + col), -FIX_1_272758580))
1262            .wrapping_add(mul(dq(8 + col), FIX_3_624509785));
1263        let n = CONST_BITS - PASS1_BITS + 2;
1264        ws[col] = descale(tmp10.wrapping_add(tmp0), n);
1265        ws[8 + col] = descale(tmp10.wrapping_sub(tmp0), n);
1266    }
1267    let stride = bw * 2;
1268    let (bx, by) = (bi % bw, bi / bw);
1269    for row in 0..2 {
1270        let w = &ws[row * 8..row * 8 + 8];
1271        let off = (by * 2 + row) * stride + bx * 2;
1272        let out = &mut samples[off..off + 2];
1273        if [1, 3, 5, 7].iter().all(|&k| w[k] == 0) {
1274            out.fill(idct_range_limit(descale(w[0], PASS1_BITS + 3)));
1275            continue;
1276        }
1277        let tmp10 = w[0] << (CONST_BITS + 2);
1278        let tmp0 = mul(w[7], -FIX_0_720959822)
1279            .wrapping_add(mul(w[5], FIX_0_850430095))
1280            .wrapping_add(mul(w[3], -FIX_1_272758580))
1281            .wrapping_add(mul(w[1], FIX_3_624509785));
1282        let n = CONST_BITS + PASS1_BITS + 3 + 2;
1283        out[0] = idct_range_limit(descale(tmp10.wrapping_add(tmp0), n));
1284        out[1] = idct_range_limit(descale(tmp10.wrapping_sub(tmp0), n));
1285    }
1286}
1287
1288/// `jpeg_idct_1x1`: the DC term, one eighth.
1289fn idct_1x1(coef: &[i16; 64], q: &[u16; 64], samples: &mut [u8], bi: usize, bw: usize) {
1290    let dc = descale(i32::from(coef[0]).wrapping_mul(i32::from(q[0])), 3);
1291    let (bx, by) = (bi % bw, bi / bw);
1292    samples[by * bw + bx] = idct_range_limit(dc);
1293}
1294
1295/// `jpeg_idct_islow` (jidctint.c): the accurate integer inverse DCT with its
1296/// exact fixed-point constants and descaling, writing the dequantized block
1297/// `bi` of a `bw`-blocks-wide plane.
1298fn idct_islow(coef: &[i16; 64], q: &[u16; 64], samples: &mut [u8], bi: usize, bw: usize) {
1299    const FIX_0_298631336: i32 = 2446;
1300    const FIX_0_390180644: i32 = 3196;
1301    const FIX_0_541196100: i32 = 4433;
1302    const FIX_0_765366865: i32 = 6270;
1303    const FIX_0_899976223: i32 = 7373;
1304    const FIX_1_175875602: i32 = 9633;
1305    const FIX_1_501321110: i32 = 12299;
1306    const FIX_1_847759065: i32 = 15137;
1307    const FIX_1_961570560: i32 = 16069;
1308    const FIX_2_053119869: i32 = 16819;
1309    const FIX_2_562915447: i32 = 20995;
1310    const FIX_3_072711026: i32 = 25172;
1311
1312    let dq = |k: usize| i32::from(coef[k]).wrapping_mul(i32::from(q[k]));
1313    let mut ws = [0i32; 64];
1314
1315    // Pass 1: columns.
1316    for col in 0..8 {
1317        if (1..8).all(|r| coef[r * 8 + col] == 0) {
1318            let dc = dq(col) << PASS1_BITS;
1319            for r in 0..8 {
1320                ws[r * 8 + col] = dc;
1321            }
1322            continue;
1323        }
1324        let z2 = dq(2 * 8 + col);
1325        let z3 = dq(6 * 8 + col);
1326        let z1 = mul(z2.wrapping_add(z3), FIX_0_541196100);
1327        let tmp2 = z1.wrapping_add(mul(z3, -FIX_1_847759065));
1328        let tmp3 = z1.wrapping_add(mul(z2, FIX_0_765366865));
1329        let z2 = dq(col);
1330        let z3 = dq(4 * 8 + col);
1331        let tmp0 = z2.wrapping_add(z3) << CONST_BITS;
1332        let tmp1 = z2.wrapping_sub(z3) << CONST_BITS;
1333        let tmp10 = tmp0.wrapping_add(tmp3);
1334        let tmp13 = tmp0.wrapping_sub(tmp3);
1335        let tmp11 = tmp1.wrapping_add(tmp2);
1336        let tmp12 = tmp1.wrapping_sub(tmp2);
1337
1338        let mut tmp0 = dq(7 * 8 + col);
1339        let mut tmp1 = dq(5 * 8 + col);
1340        let mut tmp2 = dq(3 * 8 + col);
1341        let mut tmp3 = dq(8 + col);
1342        let z1 = tmp0.wrapping_add(tmp3);
1343        let z2 = tmp1.wrapping_add(tmp2);
1344        let z3 = tmp0.wrapping_add(tmp2);
1345        let z4 = tmp1.wrapping_add(tmp3);
1346        let z5 = mul(z3.wrapping_add(z4), FIX_1_175875602);
1347        tmp0 = mul(tmp0, FIX_0_298631336);
1348        tmp1 = mul(tmp1, FIX_2_053119869);
1349        tmp2 = mul(tmp2, FIX_3_072711026);
1350        tmp3 = mul(tmp3, FIX_1_501321110);
1351        let z1 = mul(z1, -FIX_0_899976223);
1352        let z2 = mul(z2, -FIX_2_562915447);
1353        let z3 = mul(z3, -FIX_1_961570560).wrapping_add(z5);
1354        let z4 = mul(z4, -FIX_0_390180644).wrapping_add(z5);
1355        tmp0 = tmp0.wrapping_add(z1).wrapping_add(z3);
1356        tmp1 = tmp1.wrapping_add(z2).wrapping_add(z4);
1357        tmp2 = tmp2.wrapping_add(z2).wrapping_add(z3);
1358        tmp3 = tmp3.wrapping_add(z1).wrapping_add(z4);
1359
1360        let n = CONST_BITS - PASS1_BITS;
1361        ws[col] = descale(tmp10.wrapping_add(tmp3), n);
1362        ws[7 * 8 + col] = descale(tmp10.wrapping_sub(tmp3), n);
1363        ws[8 + col] = descale(tmp11.wrapping_add(tmp2), n);
1364        ws[6 * 8 + col] = descale(tmp11.wrapping_sub(tmp2), n);
1365        ws[2 * 8 + col] = descale(tmp12.wrapping_add(tmp1), n);
1366        ws[5 * 8 + col] = descale(tmp12.wrapping_sub(tmp1), n);
1367        ws[3 * 8 + col] = descale(tmp13.wrapping_add(tmp0), n);
1368        ws[4 * 8 + col] = descale(tmp13.wrapping_sub(tmp0), n);
1369    }
1370
1371    // Pass 2: rows.
1372    let stride = bw * 8;
1373    let (bx, by) = (bi % bw, bi / bw);
1374    for row in 0..8 {
1375        let w = &ws[row * 8..row * 8 + 8];
1376        let out_off = (by * 8 + row) * stride + bx * 8;
1377        let out = &mut samples[out_off..out_off + 8];
1378        let n = CONST_BITS + PASS1_BITS + 3;
1379        if w[1..].iter().all(|&v| v == 0) {
1380            let dc = idct_range_limit(descale(w[0], PASS1_BITS + 3));
1381            out.fill(dc);
1382            continue;
1383        }
1384        let z2 = w[2];
1385        let z3 = w[6];
1386        let z1 = mul(z2.wrapping_add(z3), FIX_0_541196100);
1387        let tmp2 = z1.wrapping_add(mul(z3, -FIX_1_847759065));
1388        let tmp3 = z1.wrapping_add(mul(z2, FIX_0_765366865));
1389        let tmp0 = w[0].wrapping_add(w[4]) << CONST_BITS;
1390        let tmp1 = w[0].wrapping_sub(w[4]) << CONST_BITS;
1391        let tmp10 = tmp0.wrapping_add(tmp3);
1392        let tmp13 = tmp0.wrapping_sub(tmp3);
1393        let tmp11 = tmp1.wrapping_add(tmp2);
1394        let tmp12 = tmp1.wrapping_sub(tmp2);
1395
1396        let mut tmp0 = w[7];
1397        let mut tmp1 = w[5];
1398        let mut tmp2 = w[3];
1399        let mut tmp3 = w[1];
1400        let z1 = tmp0.wrapping_add(tmp3);
1401        let z2 = tmp1.wrapping_add(tmp2);
1402        let z3 = tmp0.wrapping_add(tmp2);
1403        let z4 = tmp1.wrapping_add(tmp3);
1404        let z5 = mul(z3.wrapping_add(z4), FIX_1_175875602);
1405        tmp0 = mul(tmp0, FIX_0_298631336);
1406        tmp1 = mul(tmp1, FIX_2_053119869);
1407        tmp2 = mul(tmp2, FIX_3_072711026);
1408        tmp3 = mul(tmp3, FIX_1_501321110);
1409        let z1 = mul(z1, -FIX_0_899976223);
1410        let z2 = mul(z2, -FIX_2_562915447);
1411        let z3 = mul(z3, -FIX_1_961570560).wrapping_add(z5);
1412        let z4 = mul(z4, -FIX_0_390180644).wrapping_add(z5);
1413        tmp0 = tmp0.wrapping_add(z1).wrapping_add(z3);
1414        tmp1 = tmp1.wrapping_add(z2).wrapping_add(z4);
1415        tmp2 = tmp2.wrapping_add(z2).wrapping_add(z3);
1416        tmp3 = tmp3.wrapping_add(z1).wrapping_add(z4);
1417
1418        out[0] = idct_range_limit(descale(tmp10.wrapping_add(tmp3), n));
1419        out[7] = idct_range_limit(descale(tmp10.wrapping_sub(tmp3), n));
1420        out[1] = idct_range_limit(descale(tmp11.wrapping_add(tmp2), n));
1421        out[6] = idct_range_limit(descale(tmp11.wrapping_sub(tmp2), n));
1422        out[2] = idct_range_limit(descale(tmp12.wrapping_add(tmp1), n));
1423        out[5] = idct_range_limit(descale(tmp12.wrapping_sub(tmp1), n));
1424        out[3] = idct_range_limit(descale(tmp13.wrapping_add(tmp0), n));
1425        out[4] = idct_range_limit(descale(tmp13.wrapping_sub(tmp0), n));
1426    }
1427}
1428
1429#[cfg(test)]
1430mod tests {
1431    use super::*;
1432
1433    #[test]
1434    fn a_dc_only_block_is_flat() {
1435        let mut coef = [0i16; 64];
1436        coef[0] = 8; // × q 1 → 8/8 = +1 over mid-gray
1437        let q = [1u16; 64];
1438        let mut samples = vec![0u8; 64];
1439        idct_islow(&coef, &q, &mut samples, 0, 1);
1440        assert!(samples.iter().all(|&v| v == 129), "{samples:?}");
1441    }
1442
1443    #[test]
1444    fn colour_tables_match_libjpeg_constants() {
1445        let t = ycc_tables();
1446        // Cr = 255 → x = 127 → round(1.402 · 127) = 178; Cb = 0 → x = −128 → round(1.772 · −128) = −227.
1447        assert_eq!(t.cr_r[255], 178);
1448        assert_eq!(t.cb_b[0], -227);
1449        assert_eq!(range_limit(300), 255);
1450        assert_eq!(idct_range_limit(-5), 123);
1451        assert_eq!(idct_range_limit(200), 255);
1452        assert_eq!(idct_range_limit(-300), 0);
1453    }
1454}