image/io/
free_functions.rs

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::new(File::open(path).map_err(ImageError::IoError)?);
22
23    load(buffered_read, 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 = 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(decoder.total_bytes())?;
108            decoder.set_limits(limits)?;
109            DynamicImage::from_decoder(decoder)
110        }
111    }
112
113    load_decoder(r, format, limits.clone(), LoadVisitor(limits))
114}
115
116pub(crate) fn image_dimensions_impl(path: &Path) -> ImageResult<(u32, u32)> {
117    let format = image::ImageFormat::from_path(path)?;
118    let reader = BufReader::new(File::open(path)?);
119    image_dimensions_with_format_impl(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(buffered_read, format, super::Limits::default(), 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::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) => 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, format) in &MAGIC_BYTES {
306        if buffer.starts_with(signature) {
307            return Some(format);
308        }
309    }
310
311    None
312}