1 use std::ffi::OsString;
2 use std::fs::File;
3 use std::io::{BufRead, BufReader, BufWriter, Seek};
4 use std::path::Path;
5 use std::u32;
6 
7 #[cfg(feature = "bmp")]
8 use crate::bmp;
9 #[cfg(feature = "gif")]
10 use crate::gif;
11 #[cfg(feature = "hdr")]
12 use crate::hdr;
13 #[cfg(feature = "ico")]
14 use crate::ico;
15 #[cfg(feature = "jpeg")]
16 use crate::jpeg;
17 #[cfg(feature = "png")]
18 use crate::png;
19 #[cfg(feature = "pnm")]
20 use crate::pnm;
21 #[cfg(feature = "tga")]
22 use crate::tga;
23 #[cfg(feature = "dds")]
24 use crate::dds;
25 #[cfg(feature = "tiff")]
26 use crate::tiff;
27 #[cfg(feature = "webp")]
28 use crate::webp;
29 #[cfg(feature = "farbfeld")]
30 use crate::farbfeld;
31 
32 use crate::color;
33 use crate::image;
34 use crate::dynimage::DynamicImage;
35 use crate::error::{ImageError, ImageFormatHint, ImageResult};
36 use crate::image::{ImageDecoder, ImageEncoder, ImageFormat};
37 
38 /// Internal error type for guessing format from path.
39 pub(crate) enum PathError {
40     /// The extension did not fit a supported format.
41     UnknownExtension(OsString),
42     /// Extension could not be converted to `str`.
43     NoExtension,
44 }
45 
open_impl(path: &Path) -> ImageResult<DynamicImage>46 pub(crate) fn open_impl(path: &Path) -> ImageResult<DynamicImage> {
47     let fin = match File::open(path) {
48         Ok(f) => f,
49         Err(err) => return Err(ImageError::IoError(err)),
50     };
51     let fin = BufReader::new(fin);
52 
53     load(fin, ImageFormat::from_path(path)?)
54 }
55 
56 /// Create a new image from a Reader
57 ///
58 /// Try [`io::Reader`] for more advanced uses.
59 ///
60 /// [`io::Reader`]: io/struct.Reader.html
load<R: BufRead + Seek>(r: R, format: ImageFormat) -> ImageResult<DynamicImage>61 pub fn load<R: BufRead + Seek>(r: R, format: ImageFormat) -> ImageResult<DynamicImage> {
62     #[allow(deprecated, unreachable_patterns)]
63     // Default is unreachable if all features are supported.
64     match format {
65         #[cfg(feature = "png")]
66         image::ImageFormat::Png => DynamicImage::from_decoder(png::PngDecoder::new(r)?),
67         #[cfg(feature = "gif")]
68         image::ImageFormat::Gif => DynamicImage::from_decoder(gif::GifDecoder::new(r)?),
69         #[cfg(feature = "jpeg")]
70         image::ImageFormat::Jpeg => DynamicImage::from_decoder(jpeg::JpegDecoder::new(r)?),
71         #[cfg(feature = "webp")]
72         image::ImageFormat::WebP => DynamicImage::from_decoder(webp::WebPDecoder::new(r)?),
73         #[cfg(feature = "tiff")]
74         image::ImageFormat::Tiff => DynamicImage::from_decoder(tiff::TiffDecoder::new(r)?),
75         #[cfg(feature = "tga")]
76         image::ImageFormat::Tga => DynamicImage::from_decoder(tga::TgaDecoder::new(r)?),
77         #[cfg(feature = "dds")]
78         image::ImageFormat::Dds => DynamicImage::from_decoder(dds::DdsDecoder::new(r)?),
79         #[cfg(feature = "bmp")]
80         image::ImageFormat::Bmp => DynamicImage::from_decoder(bmp::BmpDecoder::new(r)?),
81         #[cfg(feature = "ico")]
82         image::ImageFormat::Ico => DynamicImage::from_decoder(ico::IcoDecoder::new(r)?),
83         #[cfg(feature = "hdr")]
84         image::ImageFormat::Hdr => DynamicImage::from_decoder(hdr::HDRAdapter::new(BufReader::new(r))?),
85         #[cfg(feature = "pnm")]
86         image::ImageFormat::Pnm => DynamicImage::from_decoder(pnm::PnmDecoder::new(BufReader::new(r))?),
87         #[cfg(feature = "farbfeld")]
88         image::ImageFormat::Farbfeld => DynamicImage::from_decoder(farbfeld::FarbfeldDecoder::new(r)?),
89         _ => Err(ImageError::Unsupported(ImageFormatHint::Exact(format).into())),
90     }
91 }
92 
image_dimensions_impl(path: &Path) -> ImageResult<(u32, u32)>93 pub(crate) fn image_dimensions_impl(path: &Path) -> ImageResult<(u32, u32)> {
94     let format = image::ImageFormat::from_path(path)?;
95 
96     let fin = File::open(path)?;
97     let fin = BufReader::new(fin);
98 
99     image_dimensions_with_format_impl(fin, format)
100 }
101 
image_dimensions_with_format_impl<R: BufRead + Seek>(fin: R, format: ImageFormat) -> ImageResult<(u32, u32)>102 pub(crate) fn image_dimensions_with_format_impl<R: BufRead + Seek>(fin: R, format: ImageFormat)
103     -> ImageResult<(u32, u32)>
104 {
105     #[allow(unreachable_patterns)]
106     // Default is unreachable if all features are supported.
107     Ok(match format {
108         #[cfg(feature = "jpeg")]
109         image::ImageFormat::Jpeg => jpeg::JpegDecoder::new(fin)?.dimensions(),
110         #[cfg(feature = "png")]
111         image::ImageFormat::Png => png::PngDecoder::new(fin)?.dimensions(),
112         #[cfg(feature = "gif")]
113         image::ImageFormat::Gif => gif::GifDecoder::new(fin)?.dimensions(),
114         #[cfg(feature = "webp")]
115         image::ImageFormat::WebP => webp::WebPDecoder::new(fin)?.dimensions(),
116         #[cfg(feature = "tiff")]
117         image::ImageFormat::Tiff => tiff::TiffDecoder::new(fin)?.dimensions(),
118         #[cfg(feature = "tga")]
119         image::ImageFormat::Tga => tga::TgaDecoder::new(fin)?.dimensions(),
120         #[cfg(feature = "dds")]
121         image::ImageFormat::Dds => dds::DdsDecoder::new(fin)?.dimensions(),
122         #[cfg(feature = "bmp")]
123         image::ImageFormat::Bmp => bmp::BmpDecoder::new(fin)?.dimensions(),
124         #[cfg(feature = "ico")]
125         image::ImageFormat::Ico => ico::IcoDecoder::new(fin)?.dimensions(),
126         #[cfg(feature = "hdr")]
127         image::ImageFormat::Hdr => hdr::HDRAdapter::new(fin)?.dimensions(),
128         #[cfg(feature = "pnm")]
129         image::ImageFormat::Pnm => {
130             pnm::PnmDecoder::new(fin)?.dimensions()
131         }
132         format => return Err(ImageError::Unsupported(ImageFormatHint::Exact(format).into())),
133     })
134 }
135 
save_buffer_impl( path: &Path, buf: &[u8], width: u32, height: u32, color: color::ColorType, ) -> ImageResult<()>136 pub(crate) fn save_buffer_impl(
137     path: &Path,
138     buf: &[u8],
139     width: u32,
140     height: u32,
141     color: color::ColorType,
142 ) -> ImageResult<()> {
143     let fout = &mut BufWriter::new(File::create(path)?);
144     let ext = path.extension()
145         .and_then(|s| s.to_str())
146         .map_or("".to_string(), |s| s.to_ascii_lowercase());
147 
148     match &*ext {
149         #[cfg(feature = "gif")]
150         "gif" => gif::Encoder::new(fout).encode(buf, width, height, color),
151         #[cfg(feature = "ico")]
152         "ico" => ico::ICOEncoder::new(fout).write_image(buf, width, height, color),
153         #[cfg(feature = "jpeg")]
154         "jpg" | "jpeg" => jpeg::JPEGEncoder::new(fout).write_image(buf, width, height, color),
155         #[cfg(feature = "png")]
156         "png" => png::PNGEncoder::new(fout).write_image(buf, width, height, color),
157         #[cfg(feature = "pnm")]
158         "pbm" => pnm::PNMEncoder::new(fout)
159             .with_subtype(pnm::PNMSubtype::Bitmap(pnm::SampleEncoding::Binary))
160             .write_image(buf, width, height, color),
161         #[cfg(feature = "pnm")]
162         "pgm" => pnm::PNMEncoder::new(fout)
163             .with_subtype(pnm::PNMSubtype::Graymap(pnm::SampleEncoding::Binary))
164             .write_image(buf, width, height, color),
165         #[cfg(feature = "pnm")]
166         "ppm" => pnm::PNMEncoder::new(fout)
167             .with_subtype(pnm::PNMSubtype::Pixmap(pnm::SampleEncoding::Binary))
168             .write_image(buf, width, height, color),
169         #[cfg(feature = "pnm")]
170         "pam" => pnm::PNMEncoder::new(fout).write_image(buf, width, height, color),
171         #[cfg(feature = "bmp")]
172         "bmp" => bmp::BMPEncoder::new(fout).write_image(buf, width, height, color),
173         #[cfg(feature = "tiff")]
174         "tif" | "tiff" => tiff::TiffEncoder::new(fout)
175             .write_image(buf, width, height, color),
176         _ => Err(ImageError::Unsupported(ImageFormatHint::from(path).into())),
177     }
178 }
179 
save_buffer_with_format_impl( path: &Path, buf: &[u8], width: u32, height: u32, color: color::ColorType, format: ImageFormat, ) -> ImageResult<()>180 pub(crate) fn save_buffer_with_format_impl(
181     path: &Path,
182     buf: &[u8],
183     width: u32,
184     height: u32,
185     color: color::ColorType,
186     format: ImageFormat,
187 ) -> ImageResult<()> {
188     let fout = &mut BufWriter::new(File::create(path)?);
189 
190     match format {
191         #[cfg(feature = "gif")]
192         image::ImageFormat::Gif => gif::Encoder::new(fout).encode(buf, width, height, color),
193         #[cfg(feature = "ico")]
194         image::ImageFormat::Ico => ico::ICOEncoder::new(fout).write_image(buf, width, height, color),
195         #[cfg(feature = "jpeg")]
196         image::ImageFormat::Jpeg => jpeg::JPEGEncoder::new(fout).write_image(buf, width, height, color),
197         #[cfg(feature = "png")]
198         image::ImageFormat::Png => png::PNGEncoder::new(fout).write_image(buf, width, height, color),
199         #[cfg(feature = "bmp")]
200         image::ImageFormat::Bmp => bmp::BMPEncoder::new(fout).write_image(buf, width, height, color),
201         #[cfg(feature = "tiff")]
202         image::ImageFormat::Tiff => tiff::TiffEncoder::new(fout)
203             .write_image(buf, width, height, color),
204         format => return Err(ImageError::Unsupported(ImageFormatHint::Exact(format).into())),
205     }
206 }
207 
208 /// Guess format from a path.
209 ///
210 /// Returns `PathError::NoExtension` if the path has no extension or returns a
211 /// `PathError::UnknownExtension` containing the extension if  it can not be convert to a `str`.
guess_format_from_path_impl(path: &Path) -> Result<ImageFormat, PathError>212 pub(crate) fn guess_format_from_path_impl(path: &Path) -> Result<ImageFormat, PathError> {
213     let exact_ext = path.extension();
214 
215     let ext = exact_ext
216         .and_then(|s| s.to_str())
217         .map(str::to_ascii_lowercase);
218 
219     let ext = ext.as_ref()
220         .map(String::as_str);
221 
222     Ok(match ext {
223         Some("jpg") | Some("jpeg") => image::ImageFormat::Jpeg,
224         Some("png") => image::ImageFormat::Png,
225         Some("gif") => image::ImageFormat::Gif,
226         Some("webp") => image::ImageFormat::WebP,
227         Some("tif") | Some("tiff") => image::ImageFormat::Tiff,
228         Some("tga") => image::ImageFormat::Tga,
229         Some("dds") => image::ImageFormat::Dds,
230         Some("bmp") => image::ImageFormat::Bmp,
231         Some("ico") => image::ImageFormat::Ico,
232         Some("hdr") => image::ImageFormat::Hdr,
233         Some("pbm") | Some("pam") | Some("ppm") | Some("pgm") => image::ImageFormat::Pnm,
234         Some("ff") | Some("farbfeld") => image::ImageFormat::Farbfeld,
235         // The original extension is used, instead of _format
236         _ => return match exact_ext {
237             None => Err(PathError::NoExtension),
238             Some(os) => Err(PathError::UnknownExtension(os.to_owned())),
239         },
240     })
241 }
242 
243 static MAGIC_BYTES: [(&'static [u8], ImageFormat); 19] = [
244     (b"\x89PNG\r\n\x1a\n", ImageFormat::Png),
245     (&[0xff, 0xd8, 0xff], ImageFormat::Jpeg),
246     (b"GIF89a", ImageFormat::Gif),
247     (b"GIF87a", ImageFormat::Gif),
248     (b"RIFF", ImageFormat::WebP), // TODO: better magic byte detection, see https://github.com/image-rs/image/issues/660
249     (b"MM\x00*", ImageFormat::Tiff),
250     (b"II*\x00", ImageFormat::Tiff),
251     (b"DDS ", ImageFormat::Dds),
252     (b"BM", ImageFormat::Bmp),
253     (&[0, 0, 1, 0], ImageFormat::Ico),
254     (b"#?RADIANCE", ImageFormat::Hdr),
255     (b"P1", ImageFormat::Pnm),
256     (b"P2", ImageFormat::Pnm),
257     (b"P3", ImageFormat::Pnm),
258     (b"P4", ImageFormat::Pnm),
259     (b"P5", ImageFormat::Pnm),
260     (b"P6", ImageFormat::Pnm),
261     (b"P7", ImageFormat::Pnm),
262     (b"farbfeld", ImageFormat::Farbfeld),
263 ];
264 
265 /// Guess image format from memory block
266 ///
267 /// Makes an educated guess about the image format based on the Magic Bytes at the beginning.
268 /// TGA is not supported by this function.
269 /// This is not to be trusted on the validity of the whole memory block
guess_format(buffer: &[u8]) -> ImageResult<ImageFormat>270 pub fn guess_format(buffer: &[u8]) -> ImageResult<ImageFormat> {
271     match guess_format_impl(buffer) {
272         Some(format) => Ok(format),
273         None => Err(ImageError::Unsupported(ImageFormatHint::Unknown.into())),
274     }
275 }
276 
guess_format_impl(buffer: &[u8]) -> Option<ImageFormat>277 pub(crate) fn guess_format_impl(buffer: &[u8]) -> Option<ImageFormat> {
278     for &(signature, format) in &MAGIC_BYTES {
279         if buffer.starts_with(signature) {
280             return Some(format);
281         }
282     }
283 
284     None
285 }
286 
287 impl From<PathError> for ImageError {
from(path: PathError) -> Self288     fn from(path: PathError) -> Self {
289         let format_hint = match path {
290             PathError::NoExtension => ImageFormatHint::Unknown,
291             PathError::UnknownExtension(ext) => ImageFormatHint::PathExtension(ext.into()),
292         };
293         ImageError::Unsupported(format_hint.into())
294     }
295 }
296