1 | use std::fs::File; |
2 | use std::io::{BufRead, BufReader, BufWriter, Seek}; |
3 | use std::path::Path; |
4 | use std::u32; |
5 | |
6 | use crate::codecs::*; |
7 | |
8 | use crate::dynimage::DynamicImage; |
9 | use crate::error::{ImageError, ImageFormatHint, ImageResult}; |
10 | use crate::image; |
11 | use crate::image::ImageFormat; |
12 | #[allow (unused_imports)] // When no features are supported |
13 | use crate::image::{ImageDecoder, ImageEncoder}; |
14 | use crate::{ |
15 | color, |
16 | error::{UnsupportedError, UnsupportedErrorKind}, |
17 | ImageOutputFormat, |
18 | }; |
19 | |
20 | pub(crate) fn open_impl(path: &Path) -> ImageResult<DynamicImage> { |
21 | let buffered_read: BufReader = BufReader::new(inner:File::open(path).map_err(op:ImageError::IoError)?); |
22 | |
23 | load(r:buffered_read, format:ImageFormat::from_path(path)?) |
24 | } |
25 | |
26 | /// Create a new image from a Reader. |
27 | /// |
28 | /// Assumes the reader is already buffered. For optimal performance, |
29 | /// consider wrapping the reader with a `BufReader::new()`. |
30 | /// |
31 | /// Try [`io::Reader`] for more advanced uses. |
32 | /// |
33 | /// [`io::Reader`]: io/struct.Reader.html |
34 | #[allow (unused_variables)] |
35 | // r is unused if no features are supported. |
36 | pub fn load<R: BufRead + Seek>(r: R, format: ImageFormat) -> ImageResult<DynamicImage> { |
37 | load_inner(r, super::Limits::default(), format) |
38 | } |
39 | |
40 | pub(crate) trait DecoderVisitor { |
41 | type Result; |
42 | fn visit_decoder<'a, D: ImageDecoder<'a>>(self, decoder: D) -> ImageResult<Self::Result>; |
43 | } |
44 | |
45 | pub(crate) fn load_decoder<R: BufRead + Seek, V: DecoderVisitor>( |
46 | r: R, |
47 | format: ImageFormat, |
48 | limits: super::Limits, |
49 | visitor: V, |
50 | ) -> ImageResult<V::Result> { |
51 | #[allow (unreachable_patterns)] |
52 | // Default is unreachable if all features are supported. |
53 | match format { |
54 | #[cfg (feature = "avif-decoder" )] |
55 | image::ImageFormat::Avif => visitor.visit_decoder(avif::AvifDecoder::new(r)?), |
56 | #[cfg (feature = "png" )] |
57 | image::ImageFormat::Png => visitor.visit_decoder(png::PngDecoder::with_limits(r, limits)?), |
58 | #[cfg (feature = "gif" )] |
59 | image::ImageFormat::Gif => visitor.visit_decoder(gif::GifDecoder::new(r)?), |
60 | #[cfg (feature = "jpeg" )] |
61 | image::ImageFormat::Jpeg => visitor.visit_decoder(jpeg::JpegDecoder::new(r)?), |
62 | #[cfg (feature = "webp" )] |
63 | image::ImageFormat::WebP => visitor.visit_decoder(webp::WebPDecoder::new(r)?), |
64 | #[cfg (feature = "tiff" )] |
65 | image::ImageFormat::Tiff => visitor.visit_decoder(tiff::TiffDecoder::new(r)?), |
66 | #[cfg (feature = "tga" )] |
67 | image::ImageFormat::Tga => visitor.visit_decoder(tga::TgaDecoder::new(r)?), |
68 | #[cfg (feature = "dds" )] |
69 | image::ImageFormat::Dds => visitor.visit_decoder(dds::DdsDecoder::new(r)?), |
70 | #[cfg (feature = "bmp" )] |
71 | image::ImageFormat::Bmp => visitor.visit_decoder(bmp::BmpDecoder::new(r)?), |
72 | #[cfg (feature = "ico" )] |
73 | image::ImageFormat::Ico => visitor.visit_decoder(ico::IcoDecoder::new(r)?), |
74 | #[cfg (feature = "hdr" )] |
75 | image::ImageFormat::Hdr => visitor.visit_decoder(hdr::HdrAdapter::new(BufReader::new(r))?), |
76 | #[cfg (feature = "exr" )] |
77 | image::ImageFormat::OpenExr => visitor.visit_decoder(openexr::OpenExrDecoder::new(r)?), |
78 | #[cfg (feature = "pnm" )] |
79 | image::ImageFormat::Pnm => visitor.visit_decoder(pnm::PnmDecoder::new(r)?), |
80 | #[cfg (feature = "farbfeld" )] |
81 | image::ImageFormat::Farbfeld => visitor.visit_decoder(farbfeld::FarbfeldDecoder::new(r)?), |
82 | #[cfg (feature = "qoi" )] |
83 | image::ImageFormat::Qoi => visitor.visit_decoder(qoi::QoiDecoder::new(r)?), |
84 | _ => Err(ImageError::Unsupported( |
85 | ImageFormatHint::Exact(format).into(), |
86 | )), |
87 | } |
88 | } |
89 | |
90 | pub(crate) fn load_inner<R: BufRead + Seek>( |
91 | r: R, |
92 | limits: super::Limits, |
93 | format: ImageFormat, |
94 | ) -> ImageResult<DynamicImage> { |
95 | struct LoadVisitor(super::Limits); |
96 | |
97 | impl DecoderVisitor for LoadVisitor { |
98 | type Result = DynamicImage; |
99 | |
100 | fn visit_decoder<'a, D: ImageDecoder<'a>>( |
101 | self, |
102 | mut decoder: D, |
103 | ) -> ImageResult<Self::Result> { |
104 | let mut limits: Limits = self.0; |
105 | // Check that we do not allocate a bigger buffer than we are allowed to |
106 | // FIXME: should this rather go in `DynamicImage::from_decoder` somehow? |
107 | limits.reserve(amount:decoder.total_bytes())?; |
108 | decoder.set_limits(limits)?; |
109 | DynamicImage::from_decoder(decoder) |
110 | } |
111 | } |
112 | |
113 | load_decoder(r, format, limits.clone(), visitor:LoadVisitor(limits)) |
114 | } |
115 | |
116 | pub(crate) fn image_dimensions_impl(path: &Path) -> ImageResult<(u32, u32)> { |
117 | let format: ImageFormat = image::ImageFormat::from_path(path)?; |
118 | let reader: BufReader = BufReader::new(inner:File::open(path)?); |
119 | image_dimensions_with_format_impl(buffered_read:reader, format) |
120 | } |
121 | |
122 | #[allow (unused_variables)] |
123 | // fin is unused if no features are supported. |
124 | pub(crate) fn image_dimensions_with_format_impl<R: BufRead + Seek>( |
125 | buffered_read: R, |
126 | format: ImageFormat, |
127 | ) -> ImageResult<(u32, u32)> { |
128 | struct DimVisitor; |
129 | |
130 | impl DecoderVisitor for DimVisitor { |
131 | type Result = (u32, u32); |
132 | fn visit_decoder<'a, D: ImageDecoder<'a>>(self, decoder: D) -> ImageResult<Self::Result> { |
133 | Ok(decoder.dimensions()) |
134 | } |
135 | } |
136 | |
137 | load_decoder(r:buffered_read, format, super::Limits::default(), visitor:DimVisitor) |
138 | } |
139 | |
140 | #[allow (unused_variables)] |
141 | // Most variables when no features are supported |
142 | pub(crate) fn save_buffer_impl( |
143 | path: &Path, |
144 | buf: &[u8], |
145 | width: u32, |
146 | height: u32, |
147 | color: color::ColorType, |
148 | ) -> ImageResult<()> { |
149 | let format: ImageFormat = ImageFormat::from_path(path)?; |
150 | save_buffer_with_format_impl(path, buf, width, height, color, format) |
151 | } |
152 | |
153 | #[allow (unused_variables)] |
154 | // Most variables when no features are supported |
155 | pub(crate) fn save_buffer_with_format_impl( |
156 | path: &Path, |
157 | buf: &[u8], |
158 | width: u32, |
159 | height: u32, |
160 | color: color::ColorType, |
161 | format: ImageFormat, |
162 | ) -> ImageResult<()> { |
163 | let buffered_file_write = &mut BufWriter::new(File::create(path)?); // always seekable |
164 | |
165 | let format = match format { |
166 | #[cfg (feature = "pnm" )] |
167 | image::ImageFormat::Pnm => { |
168 | let ext = path |
169 | .extension() |
170 | .and_then(|s| s.to_str()) |
171 | .map_or("" .to_string(), |s| s.to_ascii_lowercase()); |
172 | ImageOutputFormat::Pnm(match &*ext { |
173 | "pbm" => pnm::PnmSubtype::Bitmap(pnm::SampleEncoding::Binary), |
174 | "pgm" => pnm::PnmSubtype::Graymap(pnm::SampleEncoding::Binary), |
175 | "ppm" => pnm::PnmSubtype::Pixmap(pnm::SampleEncoding::Binary), |
176 | "pam" => pnm::PnmSubtype::ArbitraryMap, |
177 | _ => { |
178 | return Err(ImageError::Unsupported( |
179 | ImageFormatHint::Exact(format).into(), |
180 | )) |
181 | } // Unsupported Pnm subtype. |
182 | }) |
183 | } |
184 | // #[cfg(feature = "hdr")] |
185 | // image::ImageFormat::Hdr => hdr::HdrEncoder::new(fout).encode(&[Rgb<f32>], width, height), // usize |
186 | format => format.into(), |
187 | }; |
188 | |
189 | write_buffer_impl(buffered_file_write, buf, width, height, color, format) |
190 | } |
191 | |
192 | #[allow (unused_variables)] |
193 | // Most variables when no features are supported |
194 | pub(crate) fn write_buffer_impl<W: std::io::Write + Seek>( |
195 | buffered_write: &mut W, |
196 | buf: &[u8], |
197 | width: u32, |
198 | height: u32, |
199 | color: color::ColorType, |
200 | format: ImageOutputFormat, |
201 | ) -> ImageResult<()> { |
202 | match format { |
203 | #[cfg (feature = "png" )] |
204 | ImageOutputFormat::Png => { |
205 | png::PngEncoder::new(buffered_write).write_image(buf, width, height, color) |
206 | } |
207 | #[cfg (feature = "jpeg" )] |
208 | ImageOutputFormat::Jpeg(quality) => { |
209 | jpeg::JpegEncoder::new_with_quality(buffered_write, quality) |
210 | .write_image(buf, width, height, color) |
211 | } |
212 | #[cfg (feature = "pnm" )] |
213 | ImageOutputFormat::Pnm(subtype) => pnm::PnmEncoder::new(buffered_write) |
214 | .with_subtype(subtype) |
215 | .write_image(buf, width, height, color), |
216 | #[cfg (feature = "gif" )] |
217 | ImageOutputFormat::Gif => { |
218 | gif::GifEncoder::new(buffered_write).encode(buf, width, height, color) |
219 | } |
220 | #[cfg (feature = "ico" )] |
221 | ImageOutputFormat::Ico => { |
222 | ico::IcoEncoder::new(buffered_write).write_image(buf, width, height, color) |
223 | } |
224 | #[cfg (feature = "bmp" )] |
225 | ImageOutputFormat::Bmp => { |
226 | bmp::BmpEncoder::new(buffered_write).write_image(buf, width, height, color) |
227 | } |
228 | #[cfg (feature = "farbfeld" )] |
229 | ImageOutputFormat::Farbfeld => { |
230 | farbfeld::FarbfeldEncoder::new(buffered_write).write_image(buf, width, height, color) |
231 | } |
232 | #[cfg (feature = "tga" )] |
233 | ImageOutputFormat::Tga => { |
234 | tga::TgaEncoder::new(buffered_write).write_image(buf, width, height, color) |
235 | } |
236 | #[cfg (feature = "exr" )] |
237 | ImageOutputFormat::OpenExr => { |
238 | openexr::OpenExrEncoder::new(buffered_write).write_image(buf, width, height, color) |
239 | } |
240 | #[cfg (feature = "tiff" )] |
241 | ImageOutputFormat::Tiff => { |
242 | tiff::TiffEncoder::new(buffered_write).write_image(buf, width, height, color) |
243 | } |
244 | #[cfg (feature = "avif-encoder" )] |
245 | ImageOutputFormat::Avif => { |
246 | avif::AvifEncoder::new(buffered_write).write_image(buf, width, height, color) |
247 | } |
248 | #[cfg (feature = "qoi" )] |
249 | ImageOutputFormat::Qoi => { |
250 | qoi::QoiEncoder::new(buffered_write).write_image(buf, width, height, color) |
251 | } |
252 | #[cfg (feature = "webp" )] |
253 | ImageOutputFormat::WebP => { |
254 | webp::WebPEncoder::new_lossless(buffered_write).write_image(buf, width, height, color) |
255 | } |
256 | |
257 | image::ImageOutputFormat::Unsupported(msg) => Err(ImageError::Unsupported( |
258 | UnsupportedError::from_format_and_kind( |
259 | ImageFormatHint::Unknown, |
260 | UnsupportedErrorKind::Format(ImageFormatHint::Name(msg)), |
261 | ), |
262 | )), |
263 | } |
264 | } |
265 | |
266 | static MAGIC_BYTES: [(&[u8], ImageFormat); 23] = [ |
267 | (b" \x89PNG \r\n\x1a\n" , ImageFormat::Png), |
268 | (&[0xff, 0xd8, 0xff], ImageFormat::Jpeg), |
269 | (b"GIF89a" , ImageFormat::Gif), |
270 | (b"GIF87a" , ImageFormat::Gif), |
271 | (b"RIFF" , ImageFormat::WebP), // TODO: better magic byte detection, see https://github.com/image-rs/image/issues/660 |
272 | (b"MM \x00*" , ImageFormat::Tiff), |
273 | (b"II* \x00" , ImageFormat::Tiff), |
274 | (b"DDS " , ImageFormat::Dds), |
275 | (b"BM" , ImageFormat::Bmp), |
276 | (&[0, 0, 1, 0], ImageFormat::Ico), |
277 | (b"#?RADIANCE" , ImageFormat::Hdr), |
278 | (b"P1" , ImageFormat::Pnm), |
279 | (b"P2" , ImageFormat::Pnm), |
280 | (b"P3" , ImageFormat::Pnm), |
281 | (b"P4" , ImageFormat::Pnm), |
282 | (b"P5" , ImageFormat::Pnm), |
283 | (b"P6" , ImageFormat::Pnm), |
284 | (b"P7" , ImageFormat::Pnm), |
285 | (b"farbfeld" , ImageFormat::Farbfeld), |
286 | (b" \0\0\0 ftypavif" , ImageFormat::Avif), |
287 | (b" \0\0\0\x1cftypavif" , ImageFormat::Avif), |
288 | (&[0x76, 0x2f, 0x31, 0x01], ImageFormat::OpenExr), // = &exr::meta::magic_number::BYTES |
289 | (b"qoif" , ImageFormat::Qoi), |
290 | ]; |
291 | |
292 | /// Guess image format from memory block |
293 | /// |
294 | /// Makes an educated guess about the image format based on the Magic Bytes at the beginning. |
295 | /// TGA is not supported by this function. |
296 | /// This is not to be trusted on the validity of the whole memory block |
297 | pub fn guess_format(buffer: &[u8]) -> ImageResult<ImageFormat> { |
298 | match guess_format_impl(buffer) { |
299 | Some(format: ImageFormat) => Ok(format), |
300 | None => Err(ImageError::Unsupported(ImageFormatHint::Unknown.into())), |
301 | } |
302 | } |
303 | |
304 | pub(crate) fn guess_format_impl(buffer: &[u8]) -> Option<ImageFormat> { |
305 | for &(signature: &[u8], format: ImageFormat) in &MAGIC_BYTES { |
306 | if buffer.starts_with(needle:signature) { |
307 | return Some(format); |
308 | } |
309 | } |
310 | |
311 | None |
312 | } |
313 | |