1use std::fs::File;
2use std::io::{BufRead, BufReader, BufWriter, Seek};
3use std::path::Path;
4use std::u32;
5
6use crate::codecs::*;
7
8use crate::dynimage::DynamicImage;
9use crate::error::{ImageError, ImageFormatHint, ImageResult};
10use crate::image;
11use crate::image::ImageFormat;
12#[allow(unused_imports)] // When no features are supported
13use crate::image::{ImageDecoder, ImageEncoder};
14use crate::{
15 color,
16 error::{UnsupportedError, UnsupportedErrorKind},
17 ImageOutputFormat,
18};
19
20pub(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.
36pub fn load<R: BufRead + Seek>(r: R, format: ImageFormat) -> ImageResult<DynamicImage> {
37 load_inner(r, super::Limits::default(), format)
38}
39
40pub(crate) trait DecoderVisitor {
41 type Result;
42 fn visit_decoder<'a, D: ImageDecoder<'a>>(self, decoder: D) -> ImageResult<Self::Result>;
43}
44
45pub(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
90pub(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
116pub(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.
124pub(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
142pub(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
155pub(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
194pub(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
266static 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
297pub 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
304pub(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