Coverage Report

Created: 2026-09-28 07:57

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/image/src/io/free_functions.rs
Line
Count
Source
1
use std::fs::File;
2
use std::io::{BufRead, BufWriter, Seek, Write};
3
use std::path::Path;
4
use std::{iter, mem::size_of};
5
6
use crate::io::encoder::ImageEncoderBoxed;
7
use crate::io::DecodedImageAttributes;
8
use crate::{codecs::*, ExtendedColorType, ImageReaderOptions};
9
10
use crate::error::{
11
    ImageError, ImageFormatHint, ImageResult, LimitError, LimitErrorKind, UnsupportedError,
12
    UnsupportedErrorKind,
13
};
14
use crate::{DynamicImage, ImageDecoder, ImageFormat};
15
16
/// Create a new image from a Reader.
17
///
18
/// Assumes the reader is already buffered. For optimal performance,
19
/// consider wrapping the reader with a `BufReader::new()`.
20
///
21
/// Try [`ImageReaderOptions`] for more advanced uses.
22
54.0k
pub fn load<R: BufRead + Seek>(r: R, format: ImageFormat) -> ImageResult<DynamicImage> {
23
54.0k
    let mut reader = ImageReaderOptions::new(r);
24
54.0k
    reader.set_format(format);
25
54.0k
    reader.decode()
26
54.0k
}
image::io::free_functions::load::<std::io::cursor::Cursor<&[u8]>>
Line
Count
Source
22
6.95k
pub fn load<R: BufRead + Seek>(r: R, format: ImageFormat) -> ImageResult<DynamicImage> {
23
6.95k
    let mut reader = ImageReaderOptions::new(r);
24
6.95k
    reader.set_format(format);
25
6.95k
    reader.decode()
26
6.95k
}
Unexecuted instantiation: image::io::free_functions::load::<_>
Unexecuted instantiation: image::io::free_functions::load::<std::io::cursor::Cursor<&[u8]>>
image::io::free_functions::load::<std::io::cursor::Cursor<&[u8]>>
Line
Count
Source
22
2.83k
pub fn load<R: BufRead + Seek>(r: R, format: ImageFormat) -> ImageResult<DynamicImage> {
23
2.83k
    let mut reader = ImageReaderOptions::new(r);
24
2.83k
    reader.set_format(format);
25
2.83k
    reader.decode()
26
2.83k
}
image::io::free_functions::load::<std::io::cursor::Cursor<&[u8]>>
Line
Count
Source
22
1.71k
pub fn load<R: BufRead + Seek>(r: R, format: ImageFormat) -> ImageResult<DynamicImage> {
23
1.71k
    let mut reader = ImageReaderOptions::new(r);
24
1.71k
    reader.set_format(format);
25
1.71k
    reader.decode()
26
1.71k
}
image::io::free_functions::load::<std::io::cursor::Cursor<&[u8]>>
Line
Count
Source
22
6.79k
pub fn load<R: BufRead + Seek>(r: R, format: ImageFormat) -> ImageResult<DynamicImage> {
23
6.79k
    let mut reader = ImageReaderOptions::new(r);
24
6.79k
    reader.set_format(format);
25
6.79k
    reader.decode()
26
6.79k
}
image::io::free_functions::load::<std::io::cursor::Cursor<&[u8]>>
Line
Count
Source
22
3.44k
pub fn load<R: BufRead + Seek>(r: R, format: ImageFormat) -> ImageResult<DynamicImage> {
23
3.44k
    let mut reader = ImageReaderOptions::new(r);
24
3.44k
    reader.set_format(format);
25
3.44k
    reader.decode()
26
3.44k
}
image::io::free_functions::load::<std::io::cursor::Cursor<&[u8]>>
Line
Count
Source
22
9.29k
pub fn load<R: BufRead + Seek>(r: R, format: ImageFormat) -> ImageResult<DynamicImage> {
23
9.29k
    let mut reader = ImageReaderOptions::new(r);
24
9.29k
    reader.set_format(format);
25
9.29k
    reader.decode()
26
9.29k
}
image::io::free_functions::load::<std::io::cursor::Cursor<&[u8]>>
Line
Count
Source
22
9.70k
pub fn load<R: BufRead + Seek>(r: R, format: ImageFormat) -> ImageResult<DynamicImage> {
23
9.70k
    let mut reader = ImageReaderOptions::new(r);
24
9.70k
    reader.set_format(format);
25
9.70k
    reader.decode()
26
9.70k
}
image::io::free_functions::load::<std::io::cursor::Cursor<&[u8]>>
Line
Count
Source
22
3.15k
pub fn load<R: BufRead + Seek>(r: R, format: ImageFormat) -> ImageResult<DynamicImage> {
23
3.15k
    let mut reader = ImageReaderOptions::new(r);
24
3.15k
    reader.set_format(format);
25
3.15k
    reader.decode()
26
3.15k
}
image::io::free_functions::load::<std::io::cursor::Cursor<&[u8]>>
Line
Count
Source
22
10.1k
pub fn load<R: BufRead + Seek>(r: R, format: ImageFormat) -> ImageResult<DynamicImage> {
23
10.1k
    let mut reader = ImageReaderOptions::new(r);
24
10.1k
    reader.set_format(format);
25
10.1k
    reader.decode()
26
10.1k
}
27
28
/// Saves the supplied buffer to a file at the path specified.
29
///
30
/// The image format is derived from the file extension. The buffer is assumed to have the correct
31
/// format according to the specified color type. This will lead to corrupted files if the buffer
32
/// contains malformed data.
33
0
pub fn save_buffer(
34
0
    path: impl AsRef<Path>,
35
0
    buf: &[u8],
36
0
    width: u32,
37
0
    height: u32,
38
0
    color: impl Into<ExtendedColorType>,
39
0
) -> ImageResult<()> {
40
0
    let format = ImageFormat::from_path(path.as_ref())?;
41
0
    save_buffer_with_format(path, buf, width, height, color, format)
42
0
}
43
44
/// Saves the supplied buffer to a file given the path and desired format.
45
///
46
/// The buffer is assumed to have the correct format according to the specified color type. This
47
/// will lead to corrupted files if the buffer contains malformed data.
48
0
pub fn save_buffer_with_format(
49
0
    path: impl AsRef<Path>,
50
0
    buf: &[u8],
51
0
    width: u32,
52
0
    height: u32,
53
0
    color: impl Into<ExtendedColorType>,
54
0
    format: ImageFormat,
55
0
) -> ImageResult<()> {
56
0
    let buffered_file_write = &mut BufWriter::new(File::create(path)?); // always seekable
57
0
    let encoder = encoder_for_format(format, buffered_file_write)?;
58
0
    encoder.write_image(buf, width, height, color.into())
59
0
}
60
61
1.71k
pub(crate) fn encoder_for_format<'a, W: Write + Seek>(
62
1.71k
    format: ImageFormat,
63
1.71k
    buffered_write: &'a mut W,
64
1.71k
) -> ImageResult<Box<dyn ImageEncoderBoxed + 'a>> {
65
1.71k
    Ok(match format {
66
        #[cfg(feature = "png")]
67
0
        ImageFormat::Png => Box::new(png::PngEncoder::new(buffered_write)),
68
        #[cfg(feature = "jpeg")]
69
0
        ImageFormat::Jpeg => Box::new(jpeg::JpegEncoder::new(buffered_write)),
70
        #[cfg(feature = "pnm")]
71
0
        ImageFormat::Pnm => Box::new(pnm::PnmEncoder::new(buffered_write)),
72
        #[cfg(feature = "gif")]
73
0
        ImageFormat::Gif => Box::new(gif::GifEncoder::new(buffered_write)),
74
        #[cfg(feature = "ico")]
75
0
        ImageFormat::Ico => Box::new(ico::IcoEncoder::new(buffered_write)),
76
        #[cfg(feature = "bmp")]
77
0
        ImageFormat::Bmp => Box::new(bmp::BmpEncoder::new(buffered_write)),
78
        #[cfg(feature = "ff")]
79
0
        ImageFormat::Farbfeld => Box::new(farbfeld::FarbfeldEncoder::new(buffered_write)),
80
        #[cfg(feature = "tga")]
81
0
        ImageFormat::Tga => Box::new(tga::TgaEncoder::new(buffered_write)),
82
        #[cfg(feature = "exr")]
83
0
        ImageFormat::OpenExr => Box::new(openexr::OpenExrEncoder::new(buffered_write)),
84
        #[cfg(feature = "tiff")]
85
0
        ImageFormat::Tiff => Box::new(tiff::TiffEncoder::new(buffered_write)),
86
        #[cfg(feature = "avif")]
87
0
        ImageFormat::Avif => Box::new(avif::AvifEncoder::new(buffered_write)),
88
        #[cfg(feature = "qoi")]
89
0
        ImageFormat::Qoi => Box::new(qoi::QoiEncoder::new(buffered_write)),
90
        #[cfg(feature = "webp")]
91
1.71k
        ImageFormat::WebP => Box::new(webp::WebPEncoder::new_lossless(buffered_write)),
92
        #[cfg(feature = "hdr")]
93
0
        ImageFormat::Hdr => Box::new(hdr::HdrEncoder::new(buffered_write)),
94
        #[allow(unreachable_patterns)] // unreachable if all formats are enabled
95
        _ => {
96
0
            return Err(ImageError::Unsupported(
97
0
                UnsupportedError::from_format_and_kind(
98
0
                    ImageFormatHint::Unknown,
99
0
                    UnsupportedErrorKind::Format(ImageFormatHint::Name(format!("{format:?}"))),
100
0
                ),
101
0
            ));
102
        }
103
    })
104
1.71k
}
Unexecuted instantiation: image::io::free_functions::encoder_for_format::<_>
image::io::free_functions::encoder_for_format::<std::io::cursor::Cursor<alloc::vec::Vec<u8>>>
Line
Count
Source
61
1.71k
pub(crate) fn encoder_for_format<'a, W: Write + Seek>(
62
1.71k
    format: ImageFormat,
63
1.71k
    buffered_write: &'a mut W,
64
1.71k
) -> ImageResult<Box<dyn ImageEncoderBoxed + 'a>> {
65
1.71k
    Ok(match format {
66
        #[cfg(feature = "png")]
67
0
        ImageFormat::Png => Box::new(png::PngEncoder::new(buffered_write)),
68
        #[cfg(feature = "jpeg")]
69
0
        ImageFormat::Jpeg => Box::new(jpeg::JpegEncoder::new(buffered_write)),
70
        #[cfg(feature = "pnm")]
71
0
        ImageFormat::Pnm => Box::new(pnm::PnmEncoder::new(buffered_write)),
72
        #[cfg(feature = "gif")]
73
0
        ImageFormat::Gif => Box::new(gif::GifEncoder::new(buffered_write)),
74
        #[cfg(feature = "ico")]
75
0
        ImageFormat::Ico => Box::new(ico::IcoEncoder::new(buffered_write)),
76
        #[cfg(feature = "bmp")]
77
0
        ImageFormat::Bmp => Box::new(bmp::BmpEncoder::new(buffered_write)),
78
        #[cfg(feature = "ff")]
79
0
        ImageFormat::Farbfeld => Box::new(farbfeld::FarbfeldEncoder::new(buffered_write)),
80
        #[cfg(feature = "tga")]
81
0
        ImageFormat::Tga => Box::new(tga::TgaEncoder::new(buffered_write)),
82
        #[cfg(feature = "exr")]
83
0
        ImageFormat::OpenExr => Box::new(openexr::OpenExrEncoder::new(buffered_write)),
84
        #[cfg(feature = "tiff")]
85
0
        ImageFormat::Tiff => Box::new(tiff::TiffEncoder::new(buffered_write)),
86
        #[cfg(feature = "avif")]
87
0
        ImageFormat::Avif => Box::new(avif::AvifEncoder::new(buffered_write)),
88
        #[cfg(feature = "qoi")]
89
0
        ImageFormat::Qoi => Box::new(qoi::QoiEncoder::new(buffered_write)),
90
        #[cfg(feature = "webp")]
91
1.71k
        ImageFormat::WebP => Box::new(webp::WebPEncoder::new_lossless(buffered_write)),
92
        #[cfg(feature = "hdr")]
93
0
        ImageFormat::Hdr => Box::new(hdr::HdrEncoder::new(buffered_write)),
94
        #[allow(unreachable_patterns)] // unreachable if all formats are enabled
95
        _ => {
96
0
            return Err(ImageError::Unsupported(
97
0
                UnsupportedError::from_format_and_kind(
98
0
                    ImageFormatHint::Unknown,
99
0
                    UnsupportedErrorKind::Format(ImageFormatHint::Name(format!("{format:?}"))),
100
0
                ),
101
0
            ));
102
        }
103
    })
104
1.71k
}
105
106
static MAGIC_BYTES: [(&[u8], &[u8], ImageFormat); 21] = [
107
    (b"\x89PNG\r\n\x1a\n", b"", ImageFormat::Png),
108
    (&[0xff, 0xd8, 0xff], b"", ImageFormat::Jpeg),
109
    (b"GIF89a", b"", ImageFormat::Gif),
110
    (b"GIF87a", b"", ImageFormat::Gif),
111
    (
112
        b"RIFF\0\0\0\0WEBP",
113
        b"\xFF\xFF\xFF\xFF\0\0\0\0",
114
        ImageFormat::WebP,
115
    ),
116
    (b"MM\x00*", b"", ImageFormat::Tiff),
117
    (b"II*\x00", b"", ImageFormat::Tiff),
118
    (b"BM", b"", ImageFormat::Bmp),
119
    (&[0, 0, 1, 0], b"", ImageFormat::Ico),
120
    (b"#?RADIANCE", b"", ImageFormat::Hdr),
121
    (b"\0\0\0\0ftypavif", b"\0\0\0\0", ImageFormat::Avif),
122
    (&[0x76, 0x2f, 0x31, 0x01], b"", ImageFormat::OpenExr), // = &exr::meta::magic_number::BYTES
123
    (b"qoif", b"", ImageFormat::Qoi),
124
    (b"P1", b"", ImageFormat::Pnm),
125
    (b"P2", b"", ImageFormat::Pnm),
126
    (b"P3", b"", ImageFormat::Pnm),
127
    (b"P4", b"", ImageFormat::Pnm),
128
    (b"P5", b"", ImageFormat::Pnm),
129
    (b"P6", b"", ImageFormat::Pnm),
130
    (b"P7", b"", ImageFormat::Pnm),
131
    (b"farbfeld", b"", ImageFormat::Farbfeld),
132
];
133
134
/// Guess image format from memory block
135
///
136
/// Makes an educated guess about the image format based on the Magic Bytes at the beginning.
137
/// TGA is not supported by this function.
138
/// This is not to be trusted on the validity of the whole memory block
139
0
pub fn guess_format(buffer: &[u8]) -> ImageResult<ImageFormat> {
140
0
    match guess_format_impl(buffer) {
141
0
        Some(format) => Ok(format),
142
0
        None => Err(ImageError::Unsupported(ImageFormatHint::Unknown.into())),
143
    }
144
0
}
145
146
14.1k
pub(crate) fn guess_format_impl(buffer: &[u8]) -> Option<ImageFormat> {
147
104k
    for &(signature, mask, format) in &MAGIC_BYTES {
148
104k
        if mask.is_empty() {
149
89.3k
            if buffer.starts_with(signature) {
150
13.6k
                return Some(format);
151
75.6k
            }
152
15.2k
        } else if buffer.len() >= signature.len()
153
15.2k
            && buffer
154
15.2k
                .iter()
155
15.2k
                .zip(signature.iter())
156
15.2k
                .zip(mask.iter().chain(iter::repeat(&0xFF)))
157
37.6k
                .all(|((&byte, &sig), &mask)| byte & mask == sig)
158
        {
159
497
            return Some(format);
160
14.7k
        }
161
    }
162
163
0
    None
164
14.1k
}
165
166
/// Reads all of the bytes of a decoder into a Vec<T>. No particular alignment
167
/// of the output buffer is guaranteed.
168
///
169
/// Panics if there isn't enough memory to decode the image.
170
48.6k
pub(crate) fn decoder_to_vec<T>(
171
48.6k
    decoder: &mut (impl ImageDecoder + ?Sized),
172
48.6k
) -> ImageResult<(Vec<T>, DecodedImageAttributes)>
173
48.6k
where
174
48.6k
    T: crate::traits::Primitive + bytemuck::Pod,
175
{
176
48.6k
    let total_bytes = usize::try_from(decoder.prepare_image()?.total_bytes());
177
48.6k
    if total_bytes.is_err() || total_bytes.unwrap() > isize::MAX as usize {
178
0
        return Err(ImageError::Limits(LimitError::from_kind(
179
0
            LimitErrorKind::InsufficientMemory,
180
0
        )));
181
48.6k
    }
182
183
48.6k
    let mut buf = vec![num_traits::Zero::zero(); total_bytes.unwrap() / size_of::<T>()];
184
48.6k
    let attr = decoder.read_image(bytemuck::cast_slice_mut(buf.as_mut_slice()))?;
185
20.4k
    Ok((buf, attr))
186
48.6k
}
image::io::free_functions::decoder_to_vec::<f32, dyn image::io::decoder::ImageDecoder>
Line
Count
Source
170
3.70k
pub(crate) fn decoder_to_vec<T>(
171
3.70k
    decoder: &mut (impl ImageDecoder + ?Sized),
172
3.70k
) -> ImageResult<(Vec<T>, DecodedImageAttributes)>
173
3.70k
where
174
3.70k
    T: crate::traits::Primitive + bytemuck::Pod,
175
{
176
3.70k
    let total_bytes = usize::try_from(decoder.prepare_image()?.total_bytes());
177
3.70k
    if total_bytes.is_err() || total_bytes.unwrap() > isize::MAX as usize {
178
0
        return Err(ImageError::Limits(LimitError::from_kind(
179
0
            LimitErrorKind::InsufficientMemory,
180
0
        )));
181
3.70k
    }
182
183
3.70k
    let mut buf = vec![num_traits::Zero::zero(); total_bytes.unwrap() / size_of::<T>()];
184
3.70k
    let attr = decoder.read_image(bytemuck::cast_slice_mut(buf.as_mut_slice()))?;
185
148
    Ok((buf, attr))
186
3.70k
}
image::io::free_functions::decoder_to_vec::<u8, dyn image::io::decoder::ImageDecoder>
Line
Count
Source
170
41.7k
pub(crate) fn decoder_to_vec<T>(
171
41.7k
    decoder: &mut (impl ImageDecoder + ?Sized),
172
41.7k
) -> ImageResult<(Vec<T>, DecodedImageAttributes)>
173
41.7k
where
174
41.7k
    T: crate::traits::Primitive + bytemuck::Pod,
175
{
176
41.7k
    let total_bytes = usize::try_from(decoder.prepare_image()?.total_bytes());
177
41.7k
    if total_bytes.is_err() || total_bytes.unwrap() > isize::MAX as usize {
178
0
        return Err(ImageError::Limits(LimitError::from_kind(
179
0
            LimitErrorKind::InsufficientMemory,
180
0
        )));
181
41.7k
    }
182
183
41.7k
    let mut buf = vec![num_traits::Zero::zero(); total_bytes.unwrap() / size_of::<T>()];
184
41.7k
    let attr = decoder.read_image(bytemuck::cast_slice_mut(buf.as_mut_slice()))?;
185
19.6k
    Ok((buf, attr))
186
41.7k
}
image::io::free_functions::decoder_to_vec::<u16, dyn image::io::decoder::ImageDecoder>
Line
Count
Source
170
3.17k
pub(crate) fn decoder_to_vec<T>(
171
3.17k
    decoder: &mut (impl ImageDecoder + ?Sized),
172
3.17k
) -> ImageResult<(Vec<T>, DecodedImageAttributes)>
173
3.17k
where
174
3.17k
    T: crate::traits::Primitive + bytemuck::Pod,
175
{
176
3.17k
    let total_bytes = usize::try_from(decoder.prepare_image()?.total_bytes());
177
3.17k
    if total_bytes.is_err() || total_bytes.unwrap() > isize::MAX as usize {
178
0
        return Err(ImageError::Limits(LimitError::from_kind(
179
0
            LimitErrorKind::InsufficientMemory,
180
0
        )));
181
3.17k
    }
182
183
3.17k
    let mut buf = vec![num_traits::Zero::zero(); total_bytes.unwrap() / size_of::<T>()];
184
3.17k
    let attr = decoder.read_image(bytemuck::cast_slice_mut(buf.as_mut_slice()))?;
185
660
    Ok((buf, attr))
186
3.17k
}
187
188
#[test]
189
fn test_guess_format_agrees_with_extension() {
190
    let found = glob::glob("tests/images/**/*.*")
191
        .unwrap()
192
        .filter_map(|path| test_file(path.unwrap()))
193
        .collect::<std::collections::HashSet<_>>();
194
195
    // Check we’ve actually tested anything.
196
    for fmt in ImageFormat::all() {
197
        use ImageFormat::*;
198
        let found = found.contains(&fmt);
199
        if matches!(
200
            fmt,
201
            Bmp | Farbfeld | Gif | Ico | Hdr | Jpeg | OpenExr | Png | Pnm | Qoi | Tiff | WebP
202
        ) {
203
            assert!(found, "No {fmt:?} test files found");
204
        } else {
205
            assert!(!found, "Add `{fmt:?}` to test_guess_format");
206
        }
207
    }
208
209
    fn test_file(path: std::path::PathBuf) -> Option<ImageFormat> {
210
        let is_supported = |fmt| MAGIC_BYTES.iter().any(|item| fmt == item.2);
211
212
        let disppath = path.display();
213
        match ImageFormat::from_path(path.as_path()) {
214
            Ok(fmt) if is_supported(fmt) => {
215
                let buf = std::fs::read(path.as_path()).unwrap();
216
                let got = guess_format_impl(&buf);
217
                assert_eq!(Some(fmt), got, "{disppath}");
218
                Some(fmt)
219
            }
220
            Ok(fmt) => {
221
                eprintln!("{disppath}: unguessable format {fmt:?}, ignoring");
222
                None
223
            }
224
            Err(_) => {
225
                eprintln!("{disppath}: unrecognised extension, ignoring");
226
                None
227
            }
228
        }
229
    }
230
}