Skip to main content

zune_jpeg/
mcu_prog.rs

1/*
2 * Copyright (c) 2023.
3 *
4 * This software is free software;
5 *
6 * You can redistribute it or modify it under terms of the MIT, Apache License or Zlib license
7 */
8
9//!Routines for progressive decoding
10/*
11This file is needlessly complicated,
12
13It is that way to ensure we don't burn memory anyhow
14
15Memory is a scarce resource in some environments, I would like this to be viable
16in such environments
17
18Half of the complexity comes from the jpeg spec, because progressive decoding,
19is one hell of a ride.
20
21*/
22use alloc::string::ToString;
23use alloc::vec::Vec;
24use alloc::{format, vec};
25use core::cmp::min;
26
27use zune_core::bytestream::{ZByteReaderTrait, ZReader};
28use zune_core::colorspace::ColorSpace;
29use zune_core::log::{debug, error, warn};
30
31use crate::bitstream::BitStream;
32use crate::components::SampleRatios;
33use crate::decoder::{JpegDecoder, MAX_COMPONENTS};
34use crate::errors::DecodeErrors;
35use crate::headers::parse_sos;
36use crate::marker::Marker;
37use crate::mcu::DCT_BLOCK;
38use crate::misc::{calculate_padded_width, setup_component_params};
39
40impl<T: ZByteReaderTrait> JpegDecoder<T> {
41    /// Decode a progressive image
42    ///
43    /// This routine decodes a progressive image, stopping if it finds any error.
44    #[allow(
45        clippy::needless_range_loop,
46        clippy::cast_sign_loss,
47        clippy::redundant_else,
48        clippy::too_many_lines
49    )]
50    #[inline(never)]
51    pub(crate) fn decode_mcu_ycbcr_progressive(
52        &mut self, pixels: &mut [u8]
53    ) -> Result<(), DecodeErrors> {
54        setup_component_params(self)?;
55
56        let mut mcu_height;
57
58        // memory location for decoded pixels for components
59        let mut block: [Vec<i16>; MAX_COMPONENTS] = [vec![], vec![], vec![], vec![]];
60        let mut mcu_width;
61
62        let mut seen_scans = 1;
63
64        if self.input_colorspace == ColorSpace::Luma && self.is_interleaved {
65            warn!("Grayscale image with down-sampled component, resetting component details");
66            self.reset_params();
67        }
68
69        if self.is_interleaved {
70            // this helps us catch component errors.
71            self.set_upsampling()?;
72        }
73        if self.is_interleaved {
74            mcu_width = self.mcu_x;
75            mcu_height = self.mcu_y;
76        } else {
77            mcu_width = (self.info.width as usize + 7) / 8;
78            mcu_height = (self.info.height as usize + 7) / 8;
79        }
80        if self.is_interleaved
81            && self.input_colorspace.num_components() > 1
82            && self.options.jpeg_get_out_colorspace().num_components() == 1
83            && (self.info.sample_ratio == SampleRatios::V
84                || self.info.sample_ratio == SampleRatios::HV)
85        {
86            // For a specific set of images, e.g interleaved,
87            // when converting from YcbCr to grayscale, we need to
88            // take into account mcu height since the MCU decoding needs to take
89            // it into account for padding purposes and the post processor
90            // parses two rows per mcu width.
91            //
92            // set coeff to be 2 to ensure that we increment two rows
93            // for every mcu processed also
94            mcu_height *= self.v_max;
95            mcu_height /= self.h_max;
96            self.coeff = 2;
97        }
98
99        mcu_width *= 64;
100
101        for i in 0..self.input_colorspace.num_components() {
102            let comp = &self.components[i];
103            let len = mcu_width * comp.vertical_sample * comp.horizontal_sample * mcu_height;
104
105            block[i] = vec![0; len];
106        }
107
108        let mut stream = BitStream::new_progressive(self.succ_low, self.spec_start, self.spec_end);
109
110        // there are multiple scans in the stream, this should resolve the first scan
111        let result = self.parse_entropy_coded_data(&mut stream, &mut block);
112
113        if result.is_err() {
114            return if self.options.strict_mode() {
115                Err(result.err().unwrap())
116            } else {
117                error!("{}", result.err().unwrap());
118                // Go process it and return as much as we can, exiting here
119                return self.finish_progressive_decoding(&block, pixels);
120            };
121        }
122
123        // extract marker
124        let mut marker = stream
125            .marker
126            .take()
127            .ok_or(DecodeErrors::FormatStatic("Marker missing where expected"))?;
128
129        // if marker is EOI, we are done, otherwise continue scanning.
130        //
131        // In case we have a premature image, we print a warning or return
132        // an error, depending on the strictness of the decoder, so there
133        // is that logic to handle too
134        'eoi: while marker != Marker::EOI {
135            match marker {
136                Marker::SOS => {
137                    parse_sos(self)?;
138
139                    stream.update_progressive_params(
140                        self.succ_high,
141                        self.succ_low,
142                        self.spec_start,
143                        self.spec_end
144                    );
145                    // after every SOS, marker, parse data for that scan.
146                    let result = self.parse_entropy_coded_data(&mut stream, &mut block);
147
148                    // Do not error out too fast, allows the decoder to continue as much as possible
149                    // even after errors
150                    if result.is_err() {
151                        return if self.options.strict_mode() {
152                            Err(result.err().unwrap())
153                        } else {
154                            error!("{}", result.err().unwrap());
155                            break 'eoi;
156                        };
157                    }
158                    // extract marker, might either indicate end of image or we continue
159                    // scanning(hence the continue statement to determine).
160                    match get_marker(&mut self.stream, &mut stream) {
161                        Ok(marker_n) => {
162                            marker = marker_n;
163                            seen_scans += 1;
164                            if seen_scans > self.options.jpeg_get_max_scans() {
165                                return Err(DecodeErrors::Format(format!(
166                                    "Too many scans, exceeded limit of {}",
167                                    self.options.jpeg_get_max_scans()
168                                )));
169                            }
170
171                            stream.reset();
172                            continue 'eoi;
173                        }
174                        Err(msg) => {
175                            if self.options.strict_mode() {
176                                return Err(msg);
177                            }
178                            error!("{:?}", msg);
179                            break 'eoi;
180                        }
181                    }
182                }
183                Marker::RST(_n) => {
184                    self.handle_rst(&mut stream)?;
185                }
186                _ => {
187                    self.parse_marker_inner(marker)?;
188                }
189            }
190
191            match get_marker(&mut self.stream, &mut stream) {
192                Ok(marker_n) => {
193                    marker = marker_n;
194                }
195                Err(e) => {
196                    if self.options.strict_mode() {
197                        return Err(e);
198                    }
199                    error!("{}", e);
200                    // If we can't get the marker, just break away
201                    // allows us to decode some corrupt images
202                    // e.g https://github.com/etemesi254/zune-image/issues/294
203                    break 'eoi;
204                }
205            }
206        }
207
208        self.finish_progressive_decoding(&block, pixels)
209    }
210
211    /// Reset progressive parameters
212    fn reset_prog_params(&mut self, stream: &mut BitStream) {
213        stream.reset();
214        self.components.iter_mut().for_each(|x| x.dc_pred = 0);
215
216        // Also reset JPEG restart intervals
217        self.todo = if self.restart_interval != 0 { self.restart_interval } else { usize::MAX };
218    }
219
220    #[allow(clippy::too_many_lines, clippy::cast_sign_loss)]
221    fn parse_entropy_coded_data(
222        &mut self, stream: &mut BitStream, buffer: &mut [Vec<i16>; MAX_COMPONENTS]
223    ) -> Result<(), DecodeErrors> {
224        self.reset_prog_params(stream);
225
226        if usize::from(self.num_scans) > self.input_colorspace.num_components() {
227            return Err(DecodeErrors::Format(format!(
228                "Number of scans {} cannot be greater than number of components, {}",
229                self.num_scans,
230                self.input_colorspace.num_components()
231            )));
232        }
233        if self.num_scans == 1 {
234            // Safety checks
235            if self.spec_end != 0 && self.spec_start == 0 {
236                return Err(DecodeErrors::FormatStatic(
237                    "Can't merge DC and AC corrupt jpeg"
238                ));
239            }
240            // non interleaved data, process one block at a time in trivial scanline order
241
242            let k = self.z_order[0];
243
244            if k >= self.components.len() {
245                return Err(DecodeErrors::Format(format!(
246                    "Cannot find component {k}, corrupt image"
247                )));
248            }
249            // For non-interleaved scans, iterate over the component's actual data-unit grid.
250            let component = &self.components[k];
251
252            let mcu_width = (self.info.width as usize * component.horizontal_sample).div_ceil(self.h_max * 8);
253            let mcu_height = (self.info.height as usize * component.vertical_sample).div_ceil(self.v_max * 8);
254
255            for i in 0..mcu_height {
256                for j in 0..mcu_width {
257                    if self.spec_start != 0 && self.succ_high == 0 && stream.eob_run > 0 {
258                        // handle EOB runs here.
259                        stream.eob_run -= 1;
260                    } else {
261                        let start = 64 * (j + i * (self.components[k].width_stride / 8));
262
263                        let data: &mut [i16; 64] = buffer
264                            .get_mut(k)
265                            .unwrap()
266                            .get_mut(start..start + 64)
267                            .ok_or(DecodeErrors::FormatStatic("Slice to Small"))?
268                            .try_into()
269                            .unwrap();
270
271                        if self.spec_start == 0 {
272                            let pos = self.components[k].dc_huff_table & (MAX_COMPONENTS - 1);
273                            let dc_table = self
274                                .dc_huffman_tables
275                                .get(pos)
276                                .ok_or(DecodeErrors::FormatStatic(
277                                    "No huffman table for DC component"
278                                ))?
279                                .as_ref()
280                                .ok_or(DecodeErrors::FormatStatic(
281                                    "Huffman table at index  {} not initialized"
282                                ))?;
283
284                            let dc_pred = &mut self.components[k].dc_pred;
285
286                            if self.succ_high == 0 {
287                                // first scan for this mcu
288                                stream.decode_prog_dc_first(
289                                    &mut self.stream,
290                                    dc_table,
291                                    &mut data[0],
292                                    dc_pred
293                                )?;
294                            } else {
295                                // refining scans for this MCU
296                                stream.decode_prog_dc_refine(&mut self.stream, &mut data[0])?;
297                            }
298                        } else {
299                            let pos = self.components[k].ac_huff_table;
300                            let ac_table = self
301                                .ac_huffman_tables
302                                .get(pos)
303                                .ok_or_else(|| {
304                                    DecodeErrors::Format(format!(
305                                        "No huffman table for component:{pos}"
306                                    ))
307                                })?
308                                .as_ref()
309                                .ok_or_else(|| {
310                                    DecodeErrors::Format(format!(
311                                        "Huffman table at index  {pos} not initialized"
312                                    ))
313                                })?;
314
315                            if self.succ_high == 0 {
316                                debug_assert!(stream.eob_run == 0, "EOB run is not zero");
317
318                                stream.decode_mcu_ac_first(&mut self.stream, ac_table, data)?;
319                            } else {
320                                // refinement scan
321                                stream.decode_mcu_ac_refine(&mut self.stream, ac_table, data)?;
322                            }
323                            // Check for a marker.
324                            // It can appear in stream CC https://github.com/etemesi254/zune-image/issues/300
325                            // if let Some(marker) = stream.marker.take() {
326                            //     self.parse_marker_inner(marker)?;
327                            // }
328                        }
329                    }
330
331                    // + EOB and investigate effect.
332                    self.todo -= 1;
333
334                    self.handle_rst_main(stream)?;
335                }
336            }
337        } else {
338            if self.spec_end != 0 {
339                return Err(DecodeErrors::HuffmanDecode(
340                    "Can't merge dc and AC corrupt jpeg".to_string()
341                ));
342            }
343            // process scan n elements in order
344
345            // Do the error checking with allocs here.
346            // Make the one in the inner loop free of allocations.
347            for k in 0..self.num_scans {
348                let n = self.z_order[k as usize];
349
350                if n >= self.components.len() {
351                    return Err(DecodeErrors::Format(format!(
352                        "Cannot find component {n}, corrupt image"
353                    )));
354                }
355
356                let component = &mut self.components[n];
357                let _ = self
358                    .dc_huffman_tables
359                    .get(component.dc_huff_table)
360                    .ok_or_else(|| {
361                        DecodeErrors::Format(format!(
362                            "No huffman table for component:{}",
363                            component.dc_huff_table
364                        ))
365                    })?
366                    .as_ref()
367                    .ok_or_else(|| {
368                        DecodeErrors::Format(format!(
369                            "Huffman table at index  {} not initialized",
370                            component.dc_huff_table
371                        ))
372                    })?;
373            }
374            // Interleaved scan
375
376            // Components shall not be interleaved in progressive mode, except for
377            // the DC coefficients in the first scan for each component of a progressive frame.
378            for i in 0..self.mcu_y {
379                for j in 0..self.mcu_x {
380                    // process scan n elements in order
381                    for k in 0..self.num_scans {
382                        let n = self.z_order[k as usize];
383                        let component = &mut self.components[n];
384                        let huff_table = self
385                            .dc_huffman_tables
386                            .get(component.dc_huff_table)
387                            .ok_or(DecodeErrors::FormatStatic("No huffman table for component"))?
388                            .as_ref()
389                            .ok_or(DecodeErrors::FormatStatic(
390                                "Huffman table at index not initialized"
391                            ))?;
392
393                        for v_samp in 0..component.vertical_sample {
394                            for h_samp in 0..component.horizontal_sample {
395                                let x2 = j * component.horizontal_sample + h_samp;
396                                let y2 = i * component.vertical_sample + v_samp;
397                                let position = 64 * (x2 + y2 * component.width_stride / 8);
398                                let buf_n = &mut buffer[n];
399
400                                let Some(data) = &mut buf_n.get_mut(position) else {
401                                    // TODO: (CAE), this is another weird sub-sampling bug, so on fix
402                                    // remove this
403                                    return Err(DecodeErrors::FormatStatic("Invalid image"));
404                                };
405
406                                if self.succ_high == 0 {
407                                    stream.decode_prog_dc_first(
408                                        &mut self.stream,
409                                        huff_table,
410                                        data,
411                                        &mut component.dc_pred
412                                    )?;
413                                } else {
414                                    stream.decode_prog_dc_refine(&mut self.stream, data)?;
415                                }
416                            }
417                        }
418                    }
419                    // We want wrapping subtraction here because it means
420                    // we get a higher number in the case this underflows
421                    self.todo -= 1;
422                    // after every scan that's a mcu, count down restart markers.
423                    self.handle_rst_main(stream)?;
424                }
425            }
426        }
427        return Ok(());
428    }
429
430    pub(crate) fn handle_rst_main(&mut self, stream: &mut BitStream) -> Result<(), DecodeErrors> {
431        if self.todo == 0 {
432            stream.refill(&mut self.stream)?;
433        }
434
435        if self.todo == 0
436            && self.restart_interval != 0
437            && stream.marker.is_none()
438            && !stream.seen_eoi
439        {
440            // if no marker and we are to reset RST, look for the marker, this matches
441            // libjpeg-turbo behaviour and allows us to decode images in
442            // https://github.com/etemesi254/zune-image/issues/261
443            let _start = self.stream.position()?;
444            // skip bytes until we find marker
445            let marker = get_marker(&mut self.stream, stream);
446
447            // In some images, the RST marker on the last section may not be available
448            // as it is maybe stopped by an EOI marker, see in the case of https://github.com/etemesi254/zune-image/issues/292
449            // what happened was that we would go looking for the RST marker exhausting all the data
450            // in the image and this would return an error, so for now
451            // translate it to a warning, but return the image decoded up
452            // until that point
453            if let Ok(marker) = marker {
454                let _end = self.stream.position()?;
455                stream.marker = Some(marker);
456                // NB some warnings may be false positives.
457                warn!(
458                    "{} Extraneous bytes before marker {:?}",
459                    _end - _start,
460                    marker
461                );
462            } else {
463                warn!("RST marker was not found, where expected, image may be garbled")
464            }
465        }
466        if self.todo == 0 {
467            self.handle_rst(stream)?
468        }
469        Ok(())
470    }
471    #[allow(clippy::too_many_lines)]
472    #[allow(clippy::needless_range_loop, clippy::cast_sign_loss)]
473    fn finish_progressive_decoding(
474        &mut self, block: &[Vec<i16>; MAX_COMPONENTS], pixels: &mut [u8]
475    ) -> Result<(), DecodeErrors> {
476        // This function is complicated because we need to replicate
477        // the function in mcu.rs
478        //
479        // The advantage is that we do very little allocation and very lot
480        // channel reusing.
481        // The trick is to notice that we repeat the same procedure per MCU
482        // width.
483        //
484        // So we can set it up that we only allocate temporary storage large enough
485        // to store a single mcu width, then reuse it per invocation.
486        //
487        // This is advantageous to us.
488        //
489        // Remember we need to have the whole MCU buffer so we store 3 unprocessed
490        // channels in memory, and then we allocate the whole output buffer in memory, both of
491        // which are huge.
492        //
493        //
494
495        let mcu_height = if self.is_interleaved {
496            self.mcu_y
497        } else {
498            // For non-interleaved images( (1*1) subsampling)
499            // number of MCU's are the widths (+7 to account for paddings) divided by 8.
500            self.info.height.div_ceil(8) as usize
501        };
502
503        // Size of our output image(width*height)
504        let is_hv = usize::from(self.is_interleaved);
505        let upsampler_scratch_size = is_hv * self.components[0].width_stride;
506        let width = usize::from(self.info.width);
507        let padded_width = calculate_padded_width(width, self.info.sample_ratio);
508
509        let mut upsampler_scratch_space = vec![0; upsampler_scratch_size];
510        let mut tmp = [0_i32; DCT_BLOCK];
511
512        for (pos, comp) in self.components.iter_mut().enumerate() {
513            // Allocate only needed components.
514            //
515            // For special colorspaces i.e YCCK and CMYK, just allocate all of the needed
516            // components.
517            if min(
518                self.options.jpeg_get_out_colorspace().num_components() - 1,
519                pos
520            ) == pos
521                || self.input_colorspace == ColorSpace::YCCK
522                || self.input_colorspace == ColorSpace::CMYK
523            {
524                // allocate enough space to hold a whole MCU width
525                // this means we should take into account sampling ratios
526                // `*8` is because each MCU spans 8 widths.
527                let len = comp.width_stride * comp.vertical_sample * 8;
528
529                comp.needed = true;
530                comp.raw_coeff = vec![0; len];
531            } else {
532                comp.needed = false;
533            }
534        }
535
536        let mut pixels_written = 0;
537
538        // dequantize, idct and color convert.
539        for i in 0..mcu_height {
540            'component: for (position, component) in &mut self.components.iter_mut().enumerate() {
541                if !component.needed {
542                    continue 'component;
543                }
544                let qt_table = &component.quantization_table;
545
546                // step is the number of pixels this iteration wil be handling
547                // Given by the number of mcu's height and the length of the component block
548                // Since the component block contains the whole channel as raw pixels
549                // we this evenly divides the pixels into MCU blocks
550                //
551                // For interleaved images, this gives us the exact pixels comprising a whole MCU
552                // block
553                let step = block[position].len() / mcu_height;
554                // where we will be reading our pixels from.
555                let start = i * step;
556
557                let slice = &block[position][start..start + step];
558
559                let temp_channel = &mut component.raw_coeff;
560
561                // The next logical step is to iterate width wise.
562                // To figure out how many pixels we iterate by we use effective pixels
563                // Given to us by component.x
564                // iterate per effective pixels.
565                let mcu_x = component.width_stride / 8;
566
567                // iterate per every vertical sample.
568                for k in 0..component.vertical_sample {
569                    for j in 0..mcu_x {
570                        // after writing a single stride, we need to skip 8 rows.
571                        // This does the row calculation
572                        let width_stride = k * 8 * component.width_stride;
573                        let start = j * 64 + width_stride;
574
575                        // See https://github.com/etemesi254/zune-image/issues/262 sample 3.
576                        let Some(qt_slice) = slice.get(start..start + 64) else {
577                            return Err(DecodeErrors::FormatStatic(
578                                "Invalid slice , would panic, invalid image"
579                            ));
580                        };
581                        // dequantize
582                        for ((x, out), qt_val) in
583                            qt_slice.iter().zip(tmp.iter_mut()).zip(qt_table.iter())
584                        {
585                            *out = i32::from(*x) * qt_val;
586                        }
587                        // determine where to write.
588                        let sl = &mut temp_channel[component.idct_pos..];
589
590                        component.idct_pos += 8;
591                        // tmp now contains a dequantized block so idct it
592                        (self.idct_func)(&mut tmp, sl, component.width_stride);
593                    }
594                    // after every write of 8, skip 7 since idct write stride wise 8 times.
595                    //
596                    // Remember each MCU is 8x8 block, so each idct will write 8 strides into
597                    // sl
598                    //
599                    // and component.idct_pos is one stride long
600                    component.idct_pos += 7 * component.width_stride;
601                }
602                component.idct_pos = 0;
603            }
604
605            // process that width up until it's impossible
606            self.post_process(
607                pixels,
608                i,
609                mcu_height,
610                width,
611                padded_width,
612                &mut pixels_written,
613                &mut upsampler_scratch_space
614            )?;
615        }
616
617        debug!("Finished decoding image");
618
619        return Ok(());
620    }
621    pub(crate) fn reset_params(&mut self) {
622        /*
623        Apparently, grayscale images which can be down sampled exists, which is weird in the sense
624        that it has one component Y, which is not usually down sampled.
625
626        This means some calculations will be wrong, so for that we explicitly reset params
627        for such occurrences, warn and reset the image info to appear as if it were
628        a non-sampled image to ensure decoding works
629        */
630        self.h_max = 1;
631        self.v_max = 1;
632        self.info.sample_ratio = SampleRatios::None;
633        self.is_interleaved = false;
634        self.components[0].vertical_sample = 1;
635        self.components[0].width_stride = (((self.info.width as usize) + 7) / 8) * 8;
636        self.components[0].horizontal_sample = 1;
637    }
638}
639
640///Get a marker from the bit-stream.
641///
642/// This reads until it gets a marker or end of file is encountered
643pub fn get_marker<T>(
644    reader: &mut ZReader<T>, stream: &mut BitStream
645) -> Result<Marker, DecodeErrors>
646where
647    T: ZByteReaderTrait
648{
649    if let Some(marker) = stream.marker {
650        stream.marker = None;
651        return Ok(marker);
652    }
653
654    // read until we get a marker
655
656    while !reader.eof()? {
657        let marker = reader.read_u8_err()?;
658
659        if marker == 255 {
660            let mut r = reader.read_u8_err()?;
661            // 0xFF 0XFF(some images may be like that)
662            while r == 0xFF {
663                r = reader.read_u8_err()?;
664            }
665
666            if r != 0 {
667                return Marker::from_u8(r)
668                    .ok_or_else(|| DecodeErrors::Format(format!("Unknown marker 0xFF{r:X}")));
669            }
670        }
671    }
672    return Err(DecodeErrors::ExhaustedData);
673}
674
675#[cfg(test)]
676mod tests{
677    use zune_core::bytestream::ZCursor;
678    use crate::JpegDecoder;
679
680    #[test]
681    fn test_progressive_dri_420_color() {
682        // 16x16 progressive 4:2:0 JPEG with DRI (restart markers)
683        // Original: R=x*16, G=y*16, B=128
684        // Bug: all three conditions (progressive + DRI + 4:2:0) produced grayscale
685        const JPEG: &[u8] = &[
686            0xff, 0xd8, 0xff, 0xdb, 0x00, 0xc5, 0x00, 0x03, 0x04, 0x04, 0x06, 0x04, 0x06, 0x06,
687            0x06, 0x06, 0x06, 0x07, 0x06, 0x06, 0x06, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07,
688            0x08, 0x07, 0x08, 0x07, 0x08, 0x07, 0x08, 0x08, 0x09, 0x08, 0x09, 0x09, 0x08, 0x09,
689            0x08, 0x09, 0x08, 0x0a, 0x0a, 0x0a, 0x08, 0x09, 0x09, 0x0a, 0x0a, 0x0a, 0x0a, 0x09,
690            0x0a, 0x0c, 0x0c, 0x0c, 0x0a, 0x0c, 0x0b, 0x0b, 0x0c, 0x0d, 0x0c, 0x0d, 0x0b, 0x0b,
691            0x09, 0x01, 0x02, 0x04, 0x04, 0x07, 0x06, 0x07, 0x08, 0x07, 0x07, 0x08, 0x07, 0x08,
692            0x08, 0x08, 0x07, 0x0a, 0x0b, 0x0d, 0x0d, 0x0b, 0x0a, 0x0d, 0x0b, 0x0b, 0x0c, 0x0b,
693            0x0b, 0x0d, 0x15, 0x18, 0x13, 0x0c, 0x0c, 0x13, 0x18, 0x15, 0x10, 0x49, 0x12, 0x0d,
694            0x12, 0x49, 0x10, 0x12, 0x15, 0x0b, 0x0b, 0x15, 0x12, 0x1d, 0x0b, 0x15, 0x0b, 0x1d,
695            0x2a, 0x20, 0x20, 0x2a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x50, 0x02, 0x03, 0x03, 0x03,
696            0x05, 0x04, 0x05, 0x05, 0x05, 0x05, 0x05, 0x06, 0x04, 0x05, 0x04, 0x06, 0x05, 0x05,
697            0x05, 0x05, 0x05, 0x05, 0x06, 0x05, 0x05, 0x04, 0x05, 0x05, 0x06, 0x07, 0x06, 0x05,
698            0x06, 0x06, 0x05, 0x06, 0x07, 0x06, 0x06, 0x06, 0x04, 0x06, 0x06, 0x06, 0x06, 0x06,
699            0x07, 0x07, 0x06, 0x06, 0x07, 0x05, 0x06, 0x05, 0x07, 0x07, 0x07, 0x07, 0x07, 0x0a,
700            0x0b, 0x0a, 0x0a, 0x0a, 0x52, 0xff, 0xc2, 0x00, 0x11, 0x08, 0x00, 0x10, 0x00, 0x10,
701            0x03, 0x01, 0x22, 0x00, 0x02, 0x11, 0x01, 0x03, 0x11, 0x02, 0xff, 0xc4, 0x00, 0x17,
702            0x00, 0x01, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
703            0x00, 0x00, 0x00, 0x07, 0x02, 0x05, 0x08, 0xff, 0xdd, 0x00, 0x04, 0x00, 0x04, 0xff,
704            0xda, 0x00, 0x08, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0xca, 0xac, 0xcc, 0x4c, 0xdf,
705            0xff, 0xda, 0x00, 0x08, 0x01, 0x02, 0x00, 0x00, 0x00, 0x00, 0xb0, 0xff, 0xda, 0x00,
706            0x08, 0x01, 0x03, 0x00, 0x00, 0x00, 0x00, 0x8f, 0xff, 0xc4, 0x00, 0x1f, 0x10, 0x00,
707            0x01, 0x04, 0x02, 0x02, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
708            0x00, 0x00, 0x05, 0x06, 0x21, 0x31, 0x07, 0xf1, 0x20, 0x61, 0xa1, 0xc1, 0xe1, 0xff,
709            0xda, 0x00, 0x08, 0x01, 0x01, 0x00, 0x01, 0x02, 0x00, 0x49, 0x6a, 0x24, 0xb5, 0x12,
710            0x5a, 0x89, 0x2d, 0x4f, 0xff, 0xda, 0x00, 0x08, 0x01, 0x02, 0x00, 0x01, 0x02, 0x00,
711            0x59, 0x78, 0x7f, 0xff, 0xda, 0x00, 0x08, 0x01, 0x03, 0x00, 0x01, 0x02, 0x00, 0xc3,
712            0xd9, 0x47, 0xff, 0xda, 0x00, 0x08, 0x01, 0x01, 0x00, 0x03, 0x3f, 0x02, 0xe1, 0xff,
713            0xda, 0x00, 0x08, 0x01, 0x02, 0x00, 0x03, 0x3f, 0x02, 0x3f, 0xff, 0xda, 0x00, 0x08,
714            0x01, 0x03, 0x00, 0x03, 0x3f, 0x02, 0xa9, 0x3f, 0xff, 0xda, 0x00, 0x08, 0x01, 0x01,
715            0x00, 0x03, 0x3f, 0x21, 0xa8, 0x3a, 0x2a, 0x0a, 0x83, 0xff, 0xda, 0x00, 0x08, 0x01,
716            0x02, 0x00, 0x03, 0x3f, 0x21, 0xec, 0xff, 0xda, 0x00, 0x08, 0x01, 0x03, 0x00, 0x03,
717            0x3f, 0x21, 0xf1, 0x1f, 0xff, 0xda, 0x00, 0x08, 0x01, 0x01, 0x00, 0x03, 0x3f, 0x10,
718            0xd2, 0x57, 0xa9, 0xa4, 0xd2, 0x7f, 0xff, 0xda, 0x00, 0x08, 0x01, 0x02, 0x00, 0x03,
719            0x3f, 0x10, 0xb3, 0xff, 0xda, 0x00, 0x08, 0x01, 0x03, 0x00, 0x03, 0x3f, 0x10, 0xfa,
720            0x8f, 0xff, 0xd9,
721        ];
722
723        let mut decoder = JpegDecoder::new(ZCursor::new(JPEG));
724        let pixels = decoder.decode().unwrap();
725        let (w, h) = decoder.dimensions().unwrap();
726        assert_eq!((w, h), (16, 16));
727
728        // The image has color — R varies 0-240, G varies 0-240, B≈128
729        // If the decoder produces R==G==B for every pixel, it's outputting grayscale
730        let all_gray = pixels.chunks(3).all(|p| p[0] == p[1] && p[1] == p[2]);
731        assert!(
732            !all_gray,
733            "Output is grayscale (R==G==B for all pixels). \
734             Expected color output for progressive 4:2:0 JPEG with DRI."
735        );
736
737        // Verify first pixel is close to expected (R≈0, G≈0, B≈128)
738        assert!(pixels[2] > 100, "Blue channel should be around 128, got {}", pixels[2]);
739    }
740
741    #[test]
742    fn make_test(){
743        let data = ZCursor::new([255, 216, 255, 224, 0, 16, 74, 70, 73, 70, 0, 1, 0, 2, 0, 28, 0, 28, 0, 0, 255, 219, 0, 67, 0, 40, 28, 30, 20, 30, 25, 40, 35, 33, 35, 45, 43, 40, 48, 60, 100, 65, 60, 55, 55, 60, 123, 88, 93, 65, 100, 145, 128, 153, 150, 143, 128, 140, 138, 160, 180, 230, 195, 160, 170, 218, 173, 138, 140, 200, 255, 203, 218, 255, 238, 245, 255, 101, 0, 62, 8, 255, 255, 250, 255, 230, 253, 255, 17, 255, 219, 0, 67, 1, 43, 45, 45, 42, 60, 48, 60, 118, 65, 65, 118, 248, 165, 140, 165, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 241, 255, 255, 255, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 255, 192, 0, 17, 8, 0, 32, 0, 32, 3, 2, 17, 0, 1, 34, 1, 3, 17, 1, 255, 196, 0, 24, 0, 1, 1, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 3, 0, 1, 4, 255, 196, 0, 37, 16, 0, 2, 2, 1, 4, 1, 3, 5, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 17, 0, 4, 18, 33, 48, 34, 65, 81, 113, 19, 20, 51, 97, 161, 255, 196, 0, 22, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 255, 196, 0, 26, 17, 1, 0, 2, 3, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 17, 18, 38, 65, 255, 218, 0, 12, 3, 1, 0, 2, 17, 3, 17, 0, 63, 0, 175, 119, 49, 197, 184, 2, 0, 0, 0, 16, 13, 129, 103, 161, 102, 178, 115, 125, 202, 68, 236, 173, 25, 42, 164, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 38, 0, 0, 0, 0, 250, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 67, 1, 43, 45, 45, 60, 48, 60, 118, 65, 65, 118, 248, 165, 140, 165, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 241, 255, 255, 255, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 255, 192, 0, 17, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 255, 192, 0, 17, 8, 0, 32, 0, 32, 3, 1, 34, 0, 2, 17, 1, 3, 17, 1, 255, 196, 0, 24, 0, 1, 1, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 126, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 198]);
744        let mut decoder = JpegDecoder::new(data);
745        decoder.decode().unwrap();
746
747    }
748}