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