Skip to main content

mozjpeg/
decompress.rs

1//! See the `Decompress` struct instead. You don't need to use this module directly.
2use bytemuck::Pod;
3use crate::{colorspace::ColorSpace, PixelDensity};
4use crate::colorspace::ColorSpaceExt;
5use crate::component::CompInfo;
6use crate::component::CompInfoExt;
7use crate::errormgr::unwinding_error_mgr;
8use crate::errormgr::ErrorMgr;
9use crate::ffi;
10use crate::ffi::jpeg_decompress_struct;
11use crate::ffi::DCTSIZE;
12use crate::ffi::JPEG_LIB_VERSION;
13use crate::ffi::J_COLOR_SPACE as COLOR_SPACE;
14use crate::marker::Marker;
15use crate::readsrc::SourceMgr;
16use libc::fdopen;
17use std::cmp::min;
18use std::fs::File;
19use std::io;
20use std::io::BufRead;
21use std::io::BufReader;
22use std::marker::PhantomData;
23use std::mem;
24use std::mem::MaybeUninit;
25use std::os::raw::{c_int, c_uchar, c_ulong, c_void};
26use std::path::Path;
27use std::ptr;
28use std::ptr::addr_of_mut;
29use std::slice;
30
31const MAX_MCU_HEIGHT: usize = 16;
32const MAX_COMPONENTS: usize = 4;
33
34/// Empty list of markers
35///
36/// By default markers are not read from JPEG files.
37pub const NO_MARKERS: &[Marker] = &[];
38
39/// App 0-14 and comment markers
40///
41/// ```rust
42/// # use mozjpeg::*;
43/// Decompress::with_markers(ALL_MARKERS);
44/// ```
45pub const ALL_MARKERS: &[Marker] = &[
46    Marker::APP(0), Marker::APP(1), Marker::APP(2), Marker::APP(3), Marker::APP(4),
47    Marker::APP(5), Marker::APP(6), Marker::APP(7), Marker::APP(8), Marker::APP(9),
48    Marker::APP(10), Marker::APP(11), Marker::APP(12), Marker::APP(13), Marker::APP(14),
49    Marker::COM,
50];
51
52/// Algorithm for the DCT step.
53#[derive(Clone, Copy, Debug)]
54pub enum DctMethod {
55    /// slow but accurate integer algorithm
56    IntegerSlow,
57    /// faster, less accurate integer method
58    IntegerFast,
59    /// floating-point method
60    Float,
61}
62
63/// Use `Decompress` static methods instead of creating this directly
64pub struct DecompressBuilder<'markers> {
65    save_markers: &'markers [Marker],
66    err_mgr: Option<Box<ErrorMgr>>,
67}
68
69#[deprecated(note = "Renamed to DecompressBuilder")]
70#[doc(hidden)]
71pub use DecompressBuilder as DecompressConfig;
72
73impl<'markers> DecompressBuilder<'markers> {
74    #[inline]
75    #[must_use]
76    pub const fn new() -> Self {
77        DecompressBuilder {
78            err_mgr: None,
79            save_markers: NO_MARKERS,
80        }
81    }
82
83    #[inline]
84    #[must_use]
85    pub fn with_err(mut self, err: ErrorMgr) -> Self {
86        self.err_mgr = Some(Box::new(err));
87        self
88    }
89
90    #[inline]
91    #[must_use]
92    pub const fn with_markers(mut self, save_markers: &'markers [Marker]) -> Self {
93        self.save_markers = save_markers;
94        self
95    }
96
97    #[inline]
98    pub fn from_path<P: AsRef<Path>>(self, path: P) -> io::Result<Decompress<BufReader<File>>> {
99        self.from_file(File::open(path.as_ref())?)
100    }
101
102    /// Reads from an already-open `File`.
103    /// Use `from_reader` if you want to customize buffer size.
104    #[inline]
105    pub fn from_file(self, file: File) -> io::Result<Decompress<BufReader<File>>> {
106        self.from_reader(BufReader::new(file))
107    }
108
109    /// Reads from a `Vec` or a slice.
110    #[inline]
111    pub fn from_mem(self, mem: &[u8]) -> io::Result<Decompress<&[u8]>> {
112        self.from_reader(mem)
113    }
114
115    /// Takes `BufReader`. If you have `io::Read`, wrap it in `io::BufReader::new(read)`.
116    #[inline]
117    pub fn from_reader<R: BufRead>(self, reader: R) -> io::Result<Decompress<R>> {
118        Decompress::from_builder_and_reader(self, reader)
119    }
120}
121
122impl<'markers> Default for DecompressBuilder<'markers> {
123    #[inline]
124    fn default() -> Self {
125        Self::new()
126    }
127}
128
129/// Get pixels out of a JPEG file
130///
131/// High-level wrapper for `jpeg_decompress_struct`
132///
133/// ```rust
134/// # use mozjpeg::*;
135/// # fn t() -> std::io::Result<()> {
136/// let d = Decompress::new_path("image.jpg")?;
137/// # Ok(()) }
138/// ```
139pub struct Decompress<R> {
140    cinfo: jpeg_decompress_struct,
141    err_mgr: Box<ErrorMgr>,
142    src_mgr: Option<Box<SourceMgr<R>>>,
143}
144
145/// Marker type and data slice returned by `MarkerIter`
146pub struct MarkerData<'a> {
147    pub marker: Marker,
148    pub data: &'a [u8],
149}
150
151/// See `Decompress.markers()`
152pub struct MarkerIter<'a> {
153    marker_list: *mut ffi::jpeg_marker_struct,
154    _references: ::std::marker::PhantomData<MarkerData<'a>>,
155}
156
157impl<'a> Iterator for MarkerIter<'a> {
158    type Item = MarkerData<'a>;
159    #[inline]
160    fn next(&mut self) -> Option<MarkerData<'a>> {
161        if self.marker_list.is_null() {
162            return None;
163        }
164        unsafe {
165            let last = &*self.marker_list;
166            self.marker_list = last.next;
167            Some(MarkerData {
168                marker: last.marker.into(),
169                data: ::std::slice::from_raw_parts(last.data, last.data_length as usize),
170            })
171        }
172    }
173}
174
175impl Decompress<()> {
176    /// Short for builder().with_err()
177    #[inline]
178    #[doc(hidden)]
179    #[must_use]
180    pub fn with_err(err_mgr: ErrorMgr) -> DecompressBuilder<'static> {
181        DecompressBuilder::new().with_err(err_mgr)
182    }
183
184    /// Short for builder().with_markers()
185    #[inline]
186    #[doc(hidden)]
187    #[must_use]
188    pub fn with_markers(markers: &[Marker]) -> DecompressBuilder<'_> {
189        DecompressBuilder::new().with_markers(markers)
190    }
191
192    /// Use builder()
193    #[deprecated(note = "renamed to builder()")]
194    #[doc(hidden)]
195    #[must_use]
196    pub fn config() -> DecompressBuilder<'static> {
197        DecompressBuilder::new()
198    }
199
200    /// This is `DecompressBuilder::new()`
201    #[inline]
202    #[must_use]
203    pub const fn builder() -> DecompressBuilder<'static> {
204        DecompressBuilder::new()
205    }
206}
207
208impl Decompress<BufReader<File>> {
209    /// Decode file at path
210    #[inline]
211    pub fn new_path<P: AsRef<Path>>(path: P) -> io::Result<Self> {
212        DecompressBuilder::new().from_path(path)
213    }
214
215    /// Decode an already-opened file
216    #[inline]
217    pub fn new_file(file: File) -> io::Result<Self> {
218        DecompressBuilder::new().from_file(file)
219    }
220}
221
222impl<'mem> Decompress<&'mem [u8]> {
223    /// Decode from a JPEG file already in memory
224    #[inline]
225    pub fn new_mem(mem: &'mem [u8]) -> io::Result<Self> {
226        DecompressBuilder::new().from_mem(mem)
227    }
228}
229
230impl<R> Decompress<R> {
231    /// Decode from an `io::BufRead`, which is `BufReader` wrapping any `io::Read`.
232    #[inline]
233    pub fn new_reader(reader: R) -> io::Result<Self> where R: BufRead {
234        DecompressBuilder::new().from_reader(reader)
235    }
236
237    fn from_builder_and_reader(builder: DecompressBuilder<'_>, reader: R) -> io::Result<Self> where R: BufRead {
238        let src_mgr = Box::new(SourceMgr::new(reader)?);
239        let err_mgr = builder.err_mgr.unwrap_or_else(unwinding_error_mgr);
240        unsafe {
241            let mut newself = Decompress {
242                cinfo: mem::zeroed(),
243                src_mgr: Some(src_mgr),
244                err_mgr,
245            };
246            let src_ptr = newself.src_mgr.as_mut().unwrap().iface_c_ptr();
247            newself.cinfo.common.err = addr_of_mut!(*newself.err_mgr);
248            ffi::jpeg_create_decompress(&mut newself.cinfo);
249            newself.cinfo.src = src_ptr;
250            for &marker in builder.save_markers {
251                newself.save_marker(marker);
252            }
253            newself.read_header()?;
254            Ok(newself)
255        }
256    }
257
258    #[inline]
259    #[must_use]
260    pub fn components(&self) -> &[CompInfo] {
261        if self.cinfo.comp_info.is_null() {
262            return &[];
263        }
264        unsafe {
265            slice::from_raw_parts(self.cinfo.comp_info, self.cinfo.num_components as usize)
266        }
267    }
268
269    #[inline]
270    pub(crate) fn components_mut(&mut self) -> &mut [CompInfo] {
271        if self.cinfo.comp_info.is_null() {
272            return &mut [];
273        }
274        unsafe {
275            slice::from_raw_parts_mut(self.cinfo.comp_info, self.cinfo.num_components as usize)
276        }
277    }
278
279    /// Result here is mostly useless, because it will panic if the file is invalid
280    #[inline]
281    fn read_header(&mut self) -> io::Result<()> {
282        // require_image = 0 allows handling this error without unwinding
283        let res = unsafe { ffi::jpeg_read_header(&mut self.cinfo, 0) };
284        if res == 1 {
285            Ok(())
286        } else {
287            Err(io::Error::new(io::ErrorKind::Other, "no image in the JPEG file"))
288        }
289    }
290
291    #[inline]
292    #[must_use]
293    pub fn color_space(&self) -> COLOR_SPACE {
294        self.cinfo.jpeg_color_space
295    }
296
297    /// It's generally bogus in libjpeg
298    #[inline]
299    #[must_use]
300    pub fn gamma(&self) -> f64 {
301        self.cinfo.output_gamma
302    }
303
304    /// Get pixel density of an image from the JFIF APP0 segment.
305    /// Returns None in case of an invalid density unit.
306    #[inline]
307    #[must_use]
308    pub fn pixel_density(&mut self) -> Option<PixelDensity> {
309        Some(PixelDensity {
310            unit: match self.cinfo.density_unit {
311                0 => crate::PixelDensityUnit::PixelAspectRatio,
312                1 => crate::PixelDensityUnit::Inches,
313                2 => crate::PixelDensityUnit::Centimeters,
314                _ => return None,
315            },
316            x: self.cinfo.X_density,
317            y: self.cinfo.Y_density,
318        })
319    }
320
321    /// Markers are available only if you enable them via `with_markers()`
322    #[inline]
323    #[must_use]
324    pub fn markers(&self) -> MarkerIter<'_> {
325        MarkerIter {
326            marker_list: self.cinfo.marker_list,
327            _references: PhantomData,
328        }
329    }
330
331    #[inline]
332    fn save_marker(&mut self, marker: Marker) {
333        unsafe {
334            ffi::jpeg_save_markers(&mut self.cinfo, marker.into(), 0xFFFF);
335        }
336    }
337
338    /// width,height
339    #[inline]
340    #[must_use]
341    pub fn size(&self) -> (usize, usize) {
342        (self.width(), self.height())
343    }
344
345    #[inline]
346    #[must_use]
347    pub fn width(&self) -> usize {
348        self.cinfo.image_width as usize
349    }
350
351    #[inline]
352    #[must_use]
353    pub fn height(&self) -> usize {
354        self.cinfo.image_height as usize
355    }
356
357    /// Start decompression with conversion to RGB
358    #[inline(always)]
359    pub fn rgb(self) -> io::Result<DecompressStarted<R>> {
360        self.to_colorspace(ffi::J_COLOR_SPACE::JCS_RGB)
361    }
362
363    /// Start decompression with conversion to `colorspace`
364    pub fn to_colorspace(mut self, colorspace: ColorSpace) -> io::Result<DecompressStarted<R>> {
365        self.cinfo.out_color_space = colorspace;
366        DecompressStarted::start_decompress(self)
367    }
368
369    /// Start decompression with conversion to RGBA
370    #[inline(always)]
371    pub fn rgba(self) -> io::Result<DecompressStarted<R>> {
372        self.to_colorspace(ffi::J_COLOR_SPACE::JCS_EXT_RGBA)
373    }
374
375    /// Start decompression with conversion to grayscale.
376    #[inline(always)]
377    pub fn grayscale(self) -> io::Result<DecompressStarted<R>> {
378        self.to_colorspace(ffi::J_COLOR_SPACE::JCS_GRAYSCALE)
379    }
380
381    /// Selects the algorithm used for the DCT step.
382    pub fn dct_method(&mut self, method: DctMethod) {
383        self.cinfo.dct_method = match method {
384            DctMethod::IntegerSlow => ffi::J_DCT_METHOD::JDCT_ISLOW,
385            DctMethod::IntegerFast => ffi::J_DCT_METHOD::JDCT_IFAST,
386            DctMethod::Float => ffi::J_DCT_METHOD::JDCT_FLOAT,
387        }
388    }
389
390    // If `true`, do careful upsampling of chroma components.  If `false`,
391    // a faster but sloppier method is used.  Default is `true`.  The visual
392    // impact of the sloppier method is often very small.
393    pub fn do_fancy_upsampling(&mut self, value: bool) {
394        self.cinfo.do_fancy_upsampling = ffi::boolean::from(value);
395    }
396
397    /// If `true`, interblock smoothing is applied in early stages of decoding
398    /// progressive JPEG files; if `false`, not.  Default is `true`.  Early
399    /// progression stages look "fuzzy" with smoothing, "blocky" without.
400    /// In any case, block smoothing ceases to be applied after the first few
401    /// AC coefficients are known to full accuracy, so it is relevant only
402    /// when using buffered-image mode for progressive images.
403    pub fn do_block_smoothing(&mut self, value: bool) {
404        self.cinfo.do_block_smoothing = ffi::boolean::from(value);
405    }
406
407    #[inline(always)]
408    pub fn raw(mut self) -> io::Result<DecompressStarted<R>> {
409        self.cinfo.raw_data_out = ffi::boolean::from(true);
410        DecompressStarted::start_decompress(self)
411    }
412
413    fn out_color_space(&self) -> ColorSpace {
414        self.cinfo.out_color_space
415    }
416
417    /// Start decompression without colorspace conversion
418    pub fn image(self) -> io::Result<Format<R>> {
419        use crate::ffi::J_COLOR_SPACE::{JCS_CMYK, JCS_GRAYSCALE, JCS_RGB};
420        match self.out_color_space() {
421            JCS_RGB => Ok(Format::RGB(DecompressStarted::start_decompress(self)?)),
422            JCS_CMYK => Ok(Format::CMYK(DecompressStarted::start_decompress(self)?)),
423            JCS_GRAYSCALE => Ok(Format::Gray(DecompressStarted::start_decompress(self)?)),
424            _ => Ok(Format::RGB(self.rgb()?)),
425        }
426    }
427
428    /// Rescales the output image by `numerator / 8` during decompression.
429    /// `numerator` must be between 1 and 16.
430    /// Thus setting a value of `8` will result in an unscaled image.
431    #[track_caller]
432    #[inline]
433    pub fn scale(&mut self, numerator: u8) {
434        assert!(1 <= numerator && numerator <= 16, "numerator must be between 1 and 16");
435        self.cinfo.scale_num = numerator.into();
436        self.cinfo.scale_denom = 8;
437    }
438}
439
440/// See `Decompress.image()`
441pub enum Format<R> {
442    RGB(DecompressStarted<R>),
443    Gray(DecompressStarted<R>),
444    CMYK(DecompressStarted<R>),
445}
446
447/// See methods on `Decompress`
448pub struct DecompressStarted<R> {
449    dec: Decompress<R>,
450}
451
452impl<R> DecompressStarted<R> {
453    fn start_decompress(dec: Decompress<R>) -> io::Result<Self> {
454        let mut dec = Self { dec };
455        if 0 != unsafe { ffi::jpeg_start_decompress(&mut dec.dec.cinfo) } {
456            Ok(dec)
457        } else {
458            io_suspend_err()
459        }
460    }
461
462    #[must_use]
463    pub fn color_space(&self) -> ColorSpace {
464        self.dec.out_color_space()
465    }
466
467    /// Gets the minimal buffer size for using `DecompressStarted::read_scanlines_flat_into`
468    #[inline(always)]
469    #[must_use]
470    pub fn min_flat_buffer_size(&self) -> usize {
471        self.color_space().num_components() * self.width() * self.height()
472    }
473
474    fn can_read_more_scanlines(&self) -> bool {
475        self.dec.cinfo.output_scanline < self.dec.cinfo.output_height
476    }
477
478    /// Append data
479    #[track_caller]
480    pub fn read_raw_data(&mut self, image_dest: &mut [&mut Vec<ffi::JSAMPLE>]) {
481        while self.can_read_more_scanlines() {
482            self.read_raw_data_chunk(image_dest);
483        }
484    }
485
486    #[track_caller]
487    fn read_raw_data_chunk(&mut self, image_dest: &mut [&mut Vec<ffi::JSAMPLE>]) {
488        assert!(0 != self.dec.cinfo.raw_data_out, "Raw data not set");
489
490        let mcu_height = self.dec.cinfo.max_v_samp_factor as usize * DCTSIZE;
491        if mcu_height > MAX_MCU_HEIGHT {
492            panic!("Subsampling factor too large");
493        }
494
495        let num_components = self.dec.components().len();
496        if num_components > MAX_COMPONENTS || num_components > image_dest.len() {
497            panic!("Too many components. Image has {}, destination vector has {} (max supported is {})", num_components, image_dest.len(), MAX_COMPONENTS);
498        }
499
500        unsafe {
501            let mut row_ptrs = [[ptr::null_mut::<ffi::JSAMPLE>(); MAX_MCU_HEIGHT]; MAX_COMPONENTS];
502            let mut comp_ptrs = [ptr::null_mut::<*mut ffi::JSAMPLE>(); MAX_COMPONENTS];
503            for ((comp_info, comp_dest), (comp_ptrs, row_ptrs)) in self.dec.components().iter().zip(&mut *image_dest).zip(comp_ptrs.iter_mut().zip(row_ptrs.iter_mut())) {
504                let row_stride = comp_info.row_stride();
505
506                let comp_height = comp_info.v_samp_factor as usize * DCTSIZE;
507                let required_len = comp_height * row_stride;
508                comp_dest.try_reserve(required_len).expect("oom");
509                let comp_dest = &mut comp_dest.spare_capacity_mut()[..required_len];
510
511                // row_ptrs were initialized to null
512                for (row_ptr, comp_dest) in row_ptrs.iter_mut().zip(comp_dest.chunks_exact_mut(row_stride)).take(comp_height) {
513                    *row_ptr = comp_dest.as_mut_ptr().cast();
514                }
515                *comp_ptrs = row_ptrs.as_mut_ptr();
516            }
517
518            let lines_read = ffi::jpeg_read_raw_data(&mut self.dec.cinfo, comp_ptrs.as_mut_ptr(), mcu_height as u32) as usize;
519
520            assert_eq!(lines_read, mcu_height); // Partial reads would make subsampled height tricky to define
521
522            for (comp_info, comp_dest) in self.dec.components().iter().zip(image_dest) {
523                let row_stride = comp_info.row_stride();
524
525                let comp_height = comp_info.v_samp_factor as usize * DCTSIZE;
526                let original_len = comp_dest.len();
527                let required_len = comp_height * row_stride;
528                debug_assert!(original_len + required_len <= comp_dest.capacity());
529                comp_dest.set_len(original_len + required_len);
530            }
531        }
532    }
533
534    #[must_use]
535    pub fn width(&self) -> usize {
536        self.dec.cinfo.output_width as usize
537    }
538
539    #[must_use]
540    pub fn height(&self) -> usize {
541        self.dec.cinfo.output_height as usize
542    }
543
544    /// Supports any pixel type that is marked as "plain old data", see bytemuck crate.
545    ///
546    /// Pixels can either have number of bytes matching number of channels, e.g. RGB as
547    /// `[u8; 3]` or `rgb::RGB8`, or be an amorphous blob of `u8`s.
548    pub fn read_scanlines<T: Pod>(&mut self) -> io::Result<Vec<T>> {
549        let num_components = self.color_space().num_components();
550        if num_components != mem::size_of::<T>() && mem::size_of::<T>() != 1 {
551            return Err(io::Error::new(
552                io::ErrorKind::Unsupported,
553                format!("pixel size must have {num_components} bytes, but has {}", mem::size_of::<T>()),
554            ));
555        }
556        let width = self.width();
557        let height = self.height();
558        let mut image_dst: Vec<T> = Vec::new();
559        let required_len = height * width * (num_components / mem::size_of::<T>());
560        image_dst.try_reserve_exact(required_len).map_err(|_| io::ErrorKind::OutOfMemory)?;
561        let read_len = self.read_scanlines_into_uninit(&mut image_dst.spare_capacity_mut()[..required_len])?.len();
562        if read_len <= required_len {
563            unsafe { image_dst.set_len(read_len); }
564        }
565        Ok(image_dst)
566    }
567
568    /// Supports any pixel type that is marked as "plain old data", see bytemuck crate.
569    /// `[u8; 3]` and `rgb::RGB8` are fine, for example. `[u8]` is allowed for any pixel type.
570    ///
571    /// Allocation-less version of `read_scanlines`
572    pub fn read_scanlines_into<'dest, T: Pod>(&mut self, dest: &'dest mut [T]) -> io::Result<&'dest mut [T]> {
573        let dest_uninit = unsafe {
574            std::mem::transmute::<&'dest mut [T], &'dest mut [MaybeUninit<T>]>(dest)
575        };
576        self.read_scanlines_into_uninit(dest_uninit)
577    }
578
579    /// Returns written-to slice
580    pub fn read_scanlines_into_uninit<'dest, T: Pod>(&mut self, dest: &'dest mut [MaybeUninit<T>]) -> io::Result<&'dest mut [T]> {
581        let num_components = self.color_space().num_components();
582        let item_size = if mem::size_of::<T>() == 1 {
583            num_components
584        } else if num_components == mem::size_of::<T>() {
585            1
586        } else {
587            return Err(io::Error::new(
588                io::ErrorKind::Unsupported,
589                format!("pixel size must have {num_components} bytes, but has {}", mem::size_of::<T>()),
590            ));
591        };
592        let width = self.width();
593        let height = self.height();
594        let line_width = width * item_size;
595        if dest.len() % line_width != 0 {
596            return Err(io::Error::new(
597                io::ErrorKind::Unsupported,
598                format!("destination slice length must be multiple of {width}x{num_components} bytes long, got {}B", std::mem::size_of_val(dest)),
599            ));
600        }
601        for row in dest.chunks_exact_mut(line_width) {
602            if !self.can_read_more_scanlines() {
603                return Err(io::ErrorKind::UnexpectedEof.into());
604            }
605            let start_line = self.dec.cinfo.output_scanline as usize;
606            let mut row_ptr = row.as_mut_ptr().cast::<ffi::JSAMPLE>();
607            let rows = std::ptr::addr_of_mut!(row_ptr);
608            unsafe {
609                let rows_read = ffi::jpeg_read_scanlines(&mut self.dec.cinfo, rows, 1) as usize;
610                debug_assert_eq!(start_line + rows_read, self.dec.cinfo.output_scanline as usize, "{start_line}+{rows_read} != {} of {height}", self.dec.cinfo.output_scanline);
611                if 0 == rows_read {
612                    return Err(io::ErrorKind::UnexpectedEof.into());
613                }
614            }
615        }
616        let dest_init = unsafe {
617            std::mem::transmute::<&'dest mut [MaybeUninit<T>], &'dest mut [T]>(dest)
618        };
619        Ok(dest_init)
620    }
621
622    #[deprecated(note = "use read_scanlines::<u8>")]
623    #[doc(hidden)]
624    pub fn read_scanlines_flat(&mut self) -> io::Result<Vec<u8>> {
625        self.read_scanlines()
626    }
627
628    #[deprecated(note = "use read_scanlines_into::<u8>")]
629    #[doc(hidden)]
630    pub fn read_scanlines_flat_into<'dest>(&mut self, dest: &'dest mut [u8]) -> io::Result<&'dest mut [u8]> {
631        self.read_scanlines_into(dest)
632    }
633
634    #[must_use]
635    pub fn components(&self) -> &[CompInfo] {
636        self.dec.components()
637    }
638
639    #[deprecated(note = "too late to mutate, use components()")]
640    #[doc(hidden)]
641    pub fn components_mut(&mut self) -> &[CompInfo] {
642        self.dec.components_mut()
643    }
644
645    #[deprecated(note = "use finish()")]
646    #[doc(hidden)]
647    #[must_use]
648    pub fn finish_decompress(mut self) -> bool {
649        self.finish_internal().is_ok()
650    }
651
652    /// Finish decompress and return the reader
653    pub fn finish_into_inner(mut self) -> io::Result<R> where R: BufRead {
654        self.finish_internal()?;
655        self.dec.cinfo.src = ptr::null_mut();
656        let mgr = self.dec.src_mgr.take().ok_or(io::ErrorKind::Other)?;
657        Ok(mgr.into_inner())
658    }
659
660    #[inline]
661    pub fn finish(mut self) -> io::Result<()> {
662        self.finish_internal()
663    }
664
665    #[inline]
666    fn finish_internal(&mut self) -> io::Result<()> {
667        if 0 != unsafe { ffi::jpeg_finish_decompress(&mut self.dec.cinfo) } {
668            Ok(())
669        } else {
670            io_suspend_err()
671        }
672    }
673}
674
675#[cold]
676fn io_suspend_err<T>() -> io::Result<T> {
677    Err(io::ErrorKind::WouldBlock.into())
678}
679
680impl<R> Drop for Decompress<R> {
681    fn drop(&mut self) {
682        unsafe {
683            ffi::jpeg_destroy_decompress(&mut self.cinfo);
684        }
685    }
686}
687
688#[test]
689fn read_incomplete_file() {
690    use crate::colorspace::{ColorSpace, ColorSpaceExt};
691    use std::fs::File;
692    use std::io::Read;
693
694    let data = std::fs::read("tests/test.jpg").unwrap();
695    assert_eq!(2169, data.len());
696
697    // the reader fakes EOI marker, so it always succeeds!
698    for l in [data.len()/2, data.len()/3, data.len()/4, data.len()-4, data.len()-3, data.len()-2, data.len()-1] {
699        let dinfo = Decompress::new_mem(&data[..l]).unwrap();
700        let mut dinfo = dinfo.rgb().unwrap();
701        let _bitmap: Vec<[u8; 3]> = dinfo.read_scanlines().unwrap();
702        let remaining = dinfo.finish_into_inner().unwrap();
703        assert_eq!(0, remaining.len());
704    }
705}
706
707#[test]
708fn mem_no_trailer() {
709    let data = std::fs::read("tests/test.jpg").unwrap();
710    let dinfo = Decompress::new_mem(&data).unwrap();
711    let mut dinfo = dinfo.rgb().unwrap();
712
713    let _: Vec<[u8; 3]> = dinfo.read_scanlines().unwrap();
714
715    assert_eq!(dinfo.finish_into_inner().unwrap().len(), 0);
716}
717
718#[test]
719fn file_no_trailer() {
720    let dinfo = Decompress::new_file(File::open("tests/test.jpg").unwrap()).unwrap();
721    let mut dinfo = dinfo.rgb().unwrap();
722
723    let _: Vec<[u8; 3]> = dinfo.read_scanlines().unwrap();
724
725    assert_eq!(dinfo.finish_into_inner().unwrap().buffer().len(), 0);
726}
727
728#[test]
729fn mem_trailer() {
730    let data = std::fs::read("tests/trailer.jpg").unwrap();
731    let dinfo = Decompress::new_mem(&data).unwrap();
732    let mut dinfo = dinfo.rgb().unwrap();
733
734    let _: Vec<[u8; 3]> = dinfo.read_scanlines().unwrap();
735
736    assert_ne!(dinfo.finish_into_inner().unwrap().len(), 0);
737}
738
739#[test]
740fn file_trailer() {
741    let dinfo = Decompress::new_file(File::open("tests/trailer.jpg").unwrap()).unwrap();
742    let mut dinfo = dinfo.rgb().unwrap();
743
744    let _: Vec<[u8; 3]> = dinfo.read_scanlines().unwrap();
745
746    assert_ne!(dinfo.finish_into_inner().unwrap().buffer().len(), 0);
747}
748
749#[test]
750fn file_trailer_bytes_left() {
751    let dinfo = Decompress::new_file(File::open("tests/test.jpg").unwrap()).unwrap();
752    let mut dinfo = dinfo.rgb().unwrap();
753
754    let _: Vec<[u8; 3]> = dinfo.read_scanlines().unwrap();
755
756    assert_eq!(dinfo.finish_into_inner().unwrap().buffer().len(), 0);
757}
758
759#[test]
760fn read_file() {
761    use crate::colorspace::{ColorSpace, ColorSpaceExt};
762    use std::fs::File;
763    use std::io::Read;
764
765    let data = std::fs::read("tests/test.jpg").unwrap();
766    assert_eq!(2169, data.len());
767
768    let dinfo = Decompress::new_mem(&data[..]).unwrap();
769
770    assert_eq!(1.0, dinfo.gamma());
771    assert_eq!(ColorSpace::JCS_YCbCr, dinfo.color_space());
772    assert_eq!(dinfo.components().len(), dinfo.color_space().num_components() as usize);
773
774    assert_eq!((45, 30), dinfo.size());
775    {
776        let comps = dinfo.components();
777        assert_eq!(2, comps[0].h_samp_factor);
778        assert_eq!(2, comps[0].v_samp_factor);
779
780        assert_eq!(48, comps[0].row_stride());
781        assert_eq!(32, comps[0].col_stride());
782
783        assert_eq!(1, comps[1].h_samp_factor);
784        assert_eq!(1, comps[1].v_samp_factor);
785        assert_eq!(1, comps[2].h_samp_factor);
786        assert_eq!(1, comps[2].v_samp_factor);
787
788        assert_eq!(24, comps[1].row_stride());
789        assert_eq!(16, comps[1].col_stride());
790        assert_eq!(24, comps[2].row_stride());
791        assert_eq!(16, comps[2].col_stride());
792    }
793
794    let mut dinfo = dinfo.raw().unwrap();
795
796    let mut has_chunks = false;
797    let mut bitmaps = [&mut Vec::new(), &mut Vec::new(), &mut Vec::new()];
798    while dinfo.can_read_more_scanlines() {
799        has_chunks = true;
800        dinfo.read_raw_data_chunk(&mut bitmaps);
801        assert_eq!(bitmaps[0].len(), 4 * bitmaps[1].len());
802    }
803    assert!(has_chunks);
804
805    for (bitmap, comp) in bitmaps.iter().zip(dinfo.components()) {
806        assert_eq!(comp.row_stride() * comp.col_stride(), bitmap.len());
807    }
808
809    assert!(dinfo.finish().is_ok());
810}
811
812#[test]
813fn no_markers() {
814    use crate::colorspace::{ColorSpace, ColorSpaceExt};
815    use std::fs::File;
816    use std::io::Read;
817
818    // btw tests src manager with 1-byte len, which requires libjpeg to refill the buffer a lot
819    let tricky_buf = io::BufReader::with_capacity(1, File::open("tests/test.jpg").unwrap());
820    let dinfo = Decompress::builder().from_reader(tricky_buf).unwrap();
821    assert_eq!(0, dinfo.markers().count());
822
823    let res = dinfo.rgb().unwrap().read_scanlines::<[u8; 3]>().unwrap();
824    assert_eq!(res.len(), 45 * 30);
825
826    let dinfo = Decompress::builder().with_markers(&[]).from_path("tests/test.jpg").unwrap();
827    assert_eq!(0, dinfo.markers().count());
828}
829
830#[test]
831fn buffer_into_inner() {
832    use std::io::Read;
833
834    let data = std::fs::read("tests/test.jpg").unwrap();
835    let orig_data_len = data.len();
836
837    let dec = Decompress::builder()
838        .from_reader(BufReader::with_capacity(data.len()/17, std::io::Cursor::new(data)))
839        .unwrap();
840    let mut dec = dec.rgb().unwrap();
841    let _: Vec<[u8; 3]> = dec.read_scanlines().unwrap();
842    let mut buf = dec.finish_into_inner().unwrap();
843    assert_eq!(0, buf.fill_buf().unwrap().len());
844
845    let mut data = buf.into_inner().into_inner();
846    assert_eq!(orig_data_len, data.len());
847
848    // put two images in one vec
849    data.extend_from_slice(b"unexpected data after first image eoi");
850    data.append(&mut data.clone());
851    let appended_len = data.len() - orig_data_len;
852
853    // read one image
854    let dec = Decompress::builder()
855        .from_reader(BufReader::with_capacity(data.len()/21, std::io::Cursor::new(data)))
856        .unwrap();
857    let mut dec = dec.rgb().unwrap();
858    let _: Vec<[u8; 3]> = dec.read_scanlines().unwrap();
859
860    // expect buf to have the other one
861    let mut buf = dec.finish_into_inner().unwrap();
862    let mut tmp = Vec::new();
863    buf.read_to_end(&mut tmp).unwrap();
864    let data = buf.into_inner().into_inner();
865    assert_eq!(appended_len, tmp.len());
866    assert_eq!(data[orig_data_len..], tmp);
867}
868
869#[test]
870fn read_file_rgb() {
871    use crate::colorspace::{ColorSpace, ColorSpaceExt};
872    use std::fs::File;
873    use std::io::Read;
874
875    let data = std::fs::read("tests/test.jpg").unwrap();
876    let dinfo = Decompress::builder().with_markers(ALL_MARKERS).from_mem(&data[..]).unwrap();
877
878    assert_eq!(ColorSpace::JCS_YCbCr, dinfo.color_space());
879
880    assert_eq!(1, dinfo.markers().count());
881
882    let mut dinfo = dinfo.rgb().unwrap();
883    assert_eq!(ColorSpace::JCS_RGB, dinfo.color_space());
884    assert_eq!(dinfo.components().len(), dinfo.color_space().num_components() as usize);
885
886    let bitmap: Vec<[u8; 3]> = dinfo.read_scanlines().unwrap();
887    assert_eq!(bitmap.len(), 45 * 30);
888
889    assert!(!bitmap.contains(&[0; 3]));
890
891    dinfo.finish().unwrap();
892}
893
894#[test]
895fn drops_reader() {
896    #[repr(align(1024))]
897    struct CountsDrops<'a, R> {drop_count: &'a mut u8, reader: R}
898
899    impl<R> Drop for CountsDrops<'_, R> {
900        fn drop(&mut self) {
901            assert!(self as *mut _ as usize % 1024 == 0); // alignment
902            *self.drop_count += 1;
903        }
904    }
905    impl<R: io::Read> io::Read for CountsDrops<'_, R> {
906        fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
907            self.reader.read(buf)
908        }
909    }
910    let mut drop_count = 0;
911    let r = Decompress::builder().from_reader(BufReader::new(CountsDrops {
912        drop_count: &mut drop_count,
913        reader: File::open("tests/test.jpg").unwrap(),
914    })).unwrap();
915    drop(r);
916    assert_eq!(1, drop_count);
917}