Coverage Report

Created: 2026-05-16 07:04

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/image/src/io.rs
Line
Count
Source
1
//! Input and output of images.
2
3
use std::io;
4
use std::io::Read as _;
5
6
/// The decoder traits.
7
pub(crate) mod decoder;
8
/// The encoder traits.
9
pub(crate) mod encoder;
10
11
pub(crate) mod format;
12
pub(crate) mod free_functions;
13
pub(crate) mod image_reader_type;
14
pub(crate) mod limits;
15
16
pub use decoder::{
17
    DecodedAnimationAttributes, DecodedImageAttributes, DecodedMetadataHint, FormatAttributes,
18
    SequenceControl,
19
};
20
21
pub use image_reader_type::DecodedImageMetadata;
22
23
/// Adds `read_exact_vec`
24
pub(crate) trait ReadExt {
25
    fn read_exact_vec(&mut self, vec: &mut Vec<u8>, len: usize) -> io::Result<()>;
26
}
27
28
impl<R: io::Read> ReadExt for R {
29
656
    fn read_exact_vec(&mut self, vec: &mut Vec<u8>, len: usize) -> io::Result<()> {
30
656
        let initial_len = vec.len();
31
656
        vec.try_reserve(len)?;
32
656
        match self.take(len as u64).read_to_end(vec) {
33
656
            Ok(read) if read == len => Ok(()),
34
114
            fail => {
35
114
                vec.truncate(initial_len);
36
114
                Err(fail.err().unwrap_or(io::ErrorKind::UnexpectedEof.into()))
37
            }
38
        }
39
656
    }
40
}
41
42
/// Communicate the layout of an image.
43
///
44
/// Describes a packed rectangular layout with given bit-depth in
45
/// [`ImageDecoder::peek_layout`](crate::ImageDecoder::peek_layout). Standard layouts from `image`
46
/// are row-major with no padding between rows and pixels packed by consecutive channels.
47
#[non_exhaustive]
48
pub struct ImageLayout {
49
    /// The color model of each pixel.
50
    pub color: crate::ColorType,
51
    /// The number of pixels in the horizontal direction.
52
    pub width: u32,
53
    /// The number of pixels in the vertical direction.
54
    pub height: u32,
55
}
56
57
impl ImageLayout {
58
    /// A layout matching a [`DynamicImage`][`crate::DynamicImage`] of the given dimensions and
59
    /// color type.
60
152k
    pub fn new(w: u32, h: u32, color: crate::ColorType) -> ImageLayout {
61
152k
        ImageLayout {
62
152k
            color,
63
152k
            width: w,
64
152k
            height: h,
65
152k
        }
66
152k
    }
67
68
    /// A layout with no pixels, of the given [`ColorType`][`crate::ColorType`].
69
    ///
70
    /// This is equivalent to `ImageLayout::new(0, 0, color)`.
71
0
    pub fn empty(color: crate::ColorType) -> Self {
72
0
        ImageLayout {
73
0
            color,
74
0
            width: 0,
75
0
            height: 0,
76
0
        }
77
0
    }
78
79
    /// Return width and height as a tuple, consistent with
80
    /// [`GenericImageView::dimensions`][`crate::GenericImageView::dimensions`].
81
    ///
82
    /// Note that this refers to underlying pixel matrix, not the orientation of the image as
83
    /// indicated to be viewed in user facing applications by metadata.
84
6.85k
    pub fn dimensions(&self) -> (u32, u32) {
85
6.85k
        (self.width, self.height)
86
6.85k
    }
87
88
    /// The total number of bytes in the described image.
89
    ///
90
    /// See also [`DecodedLayout::total_bytes`].
91
120k
    pub fn total_bytes(&self) -> u64 {
92
120k
        let ImageLayout { width, height, .. } = *self;
93
120k
        let total_pixels = u64::from(width) * u64::from(height);
94
120k
        let bytes_per_pixel = u64::from(self.color.bytes_per_pixel());
95
120k
        total_pixels.saturating_mul(bytes_per_pixel)
96
120k
    }
97
98
    /// Checks if the provided dimensions would overflow a `u64`.
99
    ///
100
    /// FIXME: instead have `try_total_bytes() -> Result<u64, _>`? But should it return an
101
    /// unsupported error (as formats that use this do) or should it instead return another error
102
    /// since the method is then unrelated to a format.
103
3.09k
    pub(crate) fn total_bytes_overflows_u64(&self) -> bool {
104
3.09k
        let &ImageLayout { width, height, .. } = self;
105
3.09k
        let bytes_per_pixel: u8 = self.color.bytes_per_pixel();
106
3.09k
        u64::from(width) * u64::from(height) > u64::MAX / u64::from(bytes_per_pixel)
107
3.09k
    }
108
}
109
110
/// Describes the next image for
111
/// [`ImageDecoder::prepare_image`](`crate::ImageDecoder::prepare_image`).
112
///
113
/// For external crates constructing an instance, use [`Self::new`] with the intended color type
114
/// and then fill in all applicable fields. This initializes the layout, a minimal descriptor of an
115
/// expected [`DynamicImage`][`crate::DynamicImage`] (or equivalently sized other buffer).
116
#[non_exhaustive]
117
pub struct DecoderPreparedImage {
118
    /// The layout of the primary image data.
119
    pub layout: ImageLayout,
120
}
121
122
/// Defaults all fields except the layout.
123
impl From<ImageLayout> for DecoderPreparedImage {
124
5.46k
    fn from(layout: ImageLayout) -> Self {
125
5.46k
        DecoderPreparedImage { layout }
126
5.46k
    }
127
}
128
129
impl DecoderPreparedImage {
130
    /// A layout matching a [`DynamicImage`][`crate::DynamicImage`] of the given dimensions and
131
    /// color type.
132
143k
    pub fn new(w: u32, h: u32, color: crate::ColorType) -> DecoderPreparedImage {
133
143k
        DecoderPreparedImage {
134
143k
            layout: ImageLayout::new(w, h, color),
135
143k
        }
136
143k
    }
137
138
    /// The total number of bytes in the decoded image.
139
    ///
140
    /// This is the size of the buffer that must be passed to
141
    /// [`ImageDecoder::read_image`](`crate::ImageDecoder::read_image`) or
142
    /// `read_image_with_progress`. The returned value may exceed `usize::MAX`, in which case it
143
    /// isn't actually possible to construct a buffer to decode all the image data into. If the
144
    /// size does not fit in a u64 then `u64::MAX` is returned. For all practical purposes all
145
    /// platforms will fail to allocate that much memory.
146
118k
    pub fn total_bytes(&self) -> u64 {
147
118k
        self.layout.total_bytes()
148
118k
    }
149
}