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