1use alloc::boxed::Box;
2use alloc::vec::Vec;
3use core::iter;
4use core::mem::size_of;
5#[cfg(feature = "std")]
6use no_std_io::io::BufRead;
7use no_std_io::io::{self, Seek, Write};
8#[cfg(feature = "std")]
9use std::fs::File;
10#[cfg(feature = "std")]
11use std::path::Path;
12
13#[cfg(all(
14 not(feature = "std"),
15 any(
16 feature = "png",
17 feature = "jpeg",
18 feature = "gif",
19 feature = "bmp",
20 feature = "ico",
21 feature = "pnm",
22 feature = "tga",
23 feature = "tiff",
24 feature = "exr",
25 feature = "avif",
26 feature = "qoi",
27 feature = "webp",
28 feature = "hdr",
29 feature = "ff"
30 )
31))]
32use crate::codecs::*;
33use crate::io::encoder::ImageEncoderBoxed;
34#[cfg(feature = "std")]
35use crate::{codecs::*, ExtendedColorType, ImageReader};
36
37use crate::error::{
38 ImageError, ImageFormatHint, ImageResult, LimitError, LimitErrorKind, ParameterError,
39 ParameterErrorKind, UnsupportedError, UnsupportedErrorKind,
40};
41#[cfg(feature = "std")]
42use crate::DynamicImage;
43use crate::{ImageDecoder, ImageFormat};
44
45#[cfg(feature = "std")]
52pub fn load<R: BufRead + Seek>(r: R, format: ImageFormat) -> ImageResult<DynamicImage> {
53 let mut reader = ImageReader::new(r);
54 reader.set_format(format);
55 reader.decode()
56}
57
58#[cfg(feature = "std")]
64pub fn save_buffer(
65 path: impl AsRef<Path>,
66 buf: &[u8],
67 width: u32,
68 height: u32,
69 color: impl Into<ExtendedColorType>,
70) -> ImageResult<()> {
71 let format = ImageFormat::from_path(path.as_ref())?;
72 save_buffer_with_format(path, buf, width, height, color, format)
73}
74
75#[cfg(feature = "std")]
80pub fn save_buffer_with_format(
81 path: impl AsRef<Path>,
82 buf: &[u8],
83 width: u32,
84 height: u32,
85 color: impl Into<ExtendedColorType>,
86 format: ImageFormat,
87) -> ImageResult<()> {
88 let buffered_file_write = &mut io::BufWriter::new(File::create(path)?); let encoder = encoder_for_format(format, buffered_file_write)?;
90 encoder.write_image(buf, width, height, color.into())
91}
92
93pub(crate) fn encoder_for_format<'a, W: Write + Seek>(
94 format: ImageFormat,
95 buffered_write: &'a mut W,
96) -> ImageResult<Box<dyn ImageEncoderBoxed + 'a>> {
97 let _ = &buffered_write;
98 match format {
99 #[cfg(feature = "png")]
100 ImageFormat::Png => Ok(Box::new(png::PngEncoder::new(buffered_write))),
101 #[cfg(feature = "jpeg")]
102 ImageFormat::Jpeg => Ok(Box::new(jpeg::JpegEncoder::new(buffered_write))),
103 #[cfg(feature = "pnm")]
104 ImageFormat::Pnm => Ok(Box::new(pnm::PnmEncoder::new(buffered_write))),
105 #[cfg(feature = "gif")]
106 ImageFormat::Gif => Ok(Box::new(gif::GifEncoder::new(buffered_write))),
107 #[cfg(feature = "ico")]
108 ImageFormat::Ico => Ok(Box::new(ico::IcoEncoder::new(buffered_write))),
109 #[cfg(feature = "bmp")]
110 ImageFormat::Bmp => Ok(Box::new(bmp::BmpEncoder::new(buffered_write))),
111 #[cfg(feature = "ff")]
112 ImageFormat::Farbfeld => Ok(Box::new(farbfeld::FarbfeldEncoder::new(buffered_write))),
113 #[cfg(feature = "tga")]
114 ImageFormat::Tga => Ok(Box::new(tga::TgaEncoder::new(buffered_write))),
115 #[cfg(feature = "exr")]
116 ImageFormat::OpenExr => Ok(Box::new(openexr::OpenExrEncoder::new(buffered_write))),
117 #[cfg(feature = "tiff")]
118 ImageFormat::Tiff => Ok(Box::new(tiff::TiffEncoder::new(buffered_write))),
119 #[cfg(feature = "avif")]
120 ImageFormat::Avif => Ok(Box::new(avif::AvifEncoder::new(buffered_write))),
121 #[cfg(feature = "qoi")]
122 ImageFormat::Qoi => Ok(Box::new(qoi::QoiEncoder::new(buffered_write))),
123 #[cfg(feature = "webp")]
124 ImageFormat::WebP => Ok(Box::new(webp::WebPEncoder::new_lossless(buffered_write))),
125 #[cfg(feature = "hdr")]
126 ImageFormat::Hdr => Ok(Box::new(hdr::HdrEncoder::new(buffered_write))),
127 _ => Err(ImageError::Unsupported(
128 UnsupportedError::from_format_and_kind(
129 ImageFormatHint::Unknown,
130 UnsupportedErrorKind::Format(ImageFormatHint::Name(alloc::format!("{format:?}"))),
131 ),
132 )),
133 }
134}
135
136static MAGIC_BYTES: [(&[u8], &[u8], ImageFormat); 22] = [
137 (b"\x89PNG\r\n\x1a\n", b"", ImageFormat::Png),
138 (&[0xff, 0xd8, 0xff], b"", ImageFormat::Jpeg),
139 (b"GIF89a", b"", ImageFormat::Gif),
140 (b"GIF87a", b"", ImageFormat::Gif),
141 (
142 b"RIFF\0\0\0\0WEBP",
143 b"\xFF\xFF\xFF\xFF\0\0\0\0",
144 ImageFormat::WebP,
145 ),
146 (b"MM\x00*", b"", ImageFormat::Tiff),
147 (b"II*\x00", b"", ImageFormat::Tiff),
148 (b"DDS ", b"", ImageFormat::Dds),
149 (b"BM", b"", ImageFormat::Bmp),
150 (&[0, 0, 1, 0], b"", ImageFormat::Ico),
151 (b"#?RADIANCE", b"", ImageFormat::Hdr),
152 (b"\0\0\0\0ftypavif", b"\xFF\xFF\0\0", ImageFormat::Avif),
153 (&[0x76, 0x2f, 0x31, 0x01], b"", ImageFormat::OpenExr), (b"qoif", b"", ImageFormat::Qoi),
155 (b"P1", b"", ImageFormat::Pnm),
156 (b"P2", b"", ImageFormat::Pnm),
157 (b"P3", b"", ImageFormat::Pnm),
158 (b"P4", b"", ImageFormat::Pnm),
159 (b"P5", b"", ImageFormat::Pnm),
160 (b"P6", b"", ImageFormat::Pnm),
161 (b"P7", b"", ImageFormat::Pnm),
162 (b"farbfeld", b"", ImageFormat::Farbfeld),
163];
164
165pub fn guess_format(buffer: &[u8]) -> ImageResult<ImageFormat> {
171 match guess_format_impl(buffer) {
172 Some(format) => Ok(format),
173 None => Err(ImageError::Unsupported(ImageFormatHint::Unknown.into())),
174 }
175}
176
177pub(crate) fn guess_format_impl(buffer: &[u8]) -> Option<ImageFormat> {
178 for &(signature, mask, format) in &MAGIC_BYTES {
179 if mask.is_empty() {
180 if buffer.starts_with(signature) {
181 return Some(format);
182 }
183 } else if buffer.len() >= signature.len()
184 && buffer
185 .iter()
186 .zip(signature.iter())
187 .zip(mask.iter().chain(iter::repeat(&0xFF)))
188 .all(|((&byte, &sig), &mask)| byte & mask == sig)
189 {
190 return Some(format);
191 }
192 }
193
194 None
195}
196
197#[allow(dead_code)]
200#[allow(clippy::too_many_arguments)]
201pub(crate) fn load_rect<D, F1, F2, E>(
202 x: u32,
203 y: u32,
204 width: u32,
205 height: u32,
206 buf: &mut [u8],
207 row_pitch: usize,
208 decoder: &mut D,
209 scanline_bytes: usize,
210 mut seek_scanline: F1,
211 mut read_scanline: F2,
212) -> ImageResult<()>
213where
214 D: ImageDecoder,
215 F1: FnMut(&mut D, u64) -> io::Result<()>,
216 F2: FnMut(&mut D, &mut [u8]) -> Result<(), E>,
217 ImageError: From<E>,
218{
219 let scanline_bytes = u64::try_from(scanline_bytes).unwrap();
220 let row_pitch = u64::try_from(row_pitch).unwrap();
221
222 let (x, y, width, height) = (
223 u64::from(x),
224 u64::from(y),
225 u64::from(width),
226 u64::from(height),
227 );
228 let dimensions = decoder.dimensions();
229 let bytes_per_pixel = u64::from(decoder.color_type().bytes_per_pixel());
230 let row_bytes = bytes_per_pixel * u64::from(dimensions.0);
231 let total_bytes = width * height * bytes_per_pixel;
232
233 assert!(
234 buf.len() >= usize::try_from(total_bytes).unwrap_or(usize::MAX),
235 "output buffer too short\n expected `{}`, provided `{}`",
236 total_bytes,
237 buf.len()
238 );
239
240 let mut current_scanline = 0;
241 let mut tmp = Vec::new();
242 let mut tmp_scanline = None;
243
244 {
245 let mut read_image_range =
248 |mut start: u64, end: u64, mut output: &mut [u8]| -> ImageResult<()> {
249 let target_scanline = start / scanline_bytes;
252 if tmp_scanline == Some(target_scanline) {
253 let position = target_scanline * scanline_bytes;
254 let offset = start.saturating_sub(position);
255 let len = (end - start)
256 .min(scanline_bytes - offset)
257 .min(end - position);
258
259 output
260 .write_all(&tmp[offset as usize..][..len as usize])
261 .unwrap();
262 start += len;
263
264 if start == end {
265 return Ok(());
266 }
267 }
268
269 let target_scanline = start / scanline_bytes;
270 if target_scanline != current_scanline {
271 seek_scanline(decoder, target_scanline)?;
272 current_scanline = target_scanline;
273 }
274
275 let mut position = current_scanline * scanline_bytes;
276 while position < end {
277 if position >= start && end - position >= scanline_bytes {
278 read_scanline(decoder, &mut output[..(scanline_bytes as usize)])?;
279 output = &mut output[scanline_bytes as usize..];
280 } else {
281 tmp.resize(scanline_bytes as usize, 0u8);
282 read_scanline(decoder, &mut tmp)?;
283 tmp_scanline = Some(current_scanline);
284
285 let offset = start.saturating_sub(position);
286 let len = (end - start)
287 .min(scanline_bytes - offset)
288 .min(end - position);
289
290 output
291 .write_all(&tmp[offset as usize..][..len as usize])
292 .unwrap();
293 }
294
295 current_scanline += 1;
296 position += scanline_bytes;
297 }
298 Ok(())
299 };
300
301 if x + width > u64::from(dimensions.0)
302 || y + height > u64::from(dimensions.1)
303 || width == 0
304 || height == 0
305 {
306 return Err(ImageError::Parameter(ParameterError::from_kind(
307 ParameterErrorKind::DimensionMismatch,
308 )));
309 }
310 if scanline_bytes > usize::MAX as u64 {
311 return Err(ImageError::Limits(LimitError::from_kind(
312 LimitErrorKind::InsufficientMemory,
313 )));
314 }
315
316 if x == 0 && width == u64::from(dimensions.0) && row_pitch == row_bytes {
317 let start = x * bytes_per_pixel + y * row_bytes;
318 let end = (x + width) * bytes_per_pixel + (y + height - 1) * row_bytes;
319 read_image_range(start, end, buf)?;
320 } else {
321 for (output_slice, row) in buf.chunks_mut(row_pitch as usize).zip(y..(y + height)) {
322 let start = x * bytes_per_pixel + row * row_bytes;
323 let end = (x + width) * bytes_per_pixel + row * row_bytes;
324 read_image_range(start, end, output_slice)?;
325 }
326 }
327 }
328
329 Ok(seek_scanline(decoder, 0)?)
331}
332
333pub(crate) fn decoder_to_vec<T>(decoder: impl ImageDecoder) -> ImageResult<Vec<T>>
338where
339 T: crate::traits::Primitive + bytemuck::Pod,
340{
341 let total_bytes = usize::try_from(decoder.total_bytes());
342 if total_bytes.is_err() || total_bytes.unwrap() > isize::MAX as usize {
343 return Err(ImageError::Limits(LimitError::from_kind(
344 LimitErrorKind::InsufficientMemory,
345 )));
346 }
347
348 let mut buf = alloc::vec![num_traits::Zero::zero(); total_bytes.unwrap() / size_of::<T>()];
349 decoder.read_image(bytemuck::cast_slice_mut(buf.as_mut_slice()))?;
350 Ok(buf)
351}
352
353#[cfg(test)]
354mod tests {
355 use crate::ColorType;
356 use std::io;
357
358 use super::{load_rect, ImageDecoder, ImageResult};
359
360 #[test]
361 fn test_load_rect() {
362 struct MockDecoder {
363 scanline_number: u64,
364 scanline_bytes: u64,
365 }
366 impl ImageDecoder for MockDecoder {
367 fn dimensions(&self) -> (u32, u32) {
368 (5, 5)
369 }
370 fn color_type(&self) -> ColorType {
371 ColorType::L8
372 }
373 fn read_image(self, _buf: &mut [u8]) -> ImageResult<()> {
374 unimplemented!()
375 }
376 fn read_image_boxed(self: Box<Self>, buf: &mut [u8]) -> ImageResult<()> {
377 (*self).read_image(buf)
378 }
379 }
380
381 const DATA: [u8; 25] = [
382 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
383 24,
384 ];
385
386 fn seek_scanline(m: &mut MockDecoder, n: u64) -> io::Result<()> {
387 m.scanline_number = n;
388 Ok(())
389 }
390 fn read_scanline(m: &mut MockDecoder, buf: &mut [u8]) -> io::Result<()> {
391 let bytes_read = m.scanline_number * m.scanline_bytes;
392 if bytes_read >= 25 {
393 return Ok(());
394 }
395
396 let len = m.scanline_bytes.min(25 - bytes_read);
397 buf[..(len as usize)].copy_from_slice(&DATA[(bytes_read as usize)..][..(len as usize)]);
398 m.scanline_number += 1;
399 Ok(())
400 }
401
402 for scanline_bytes in 1..30 {
403 let mut output = [0u8; 26];
404
405 load_rect(
406 0,
407 0,
408 5,
409 5,
410 &mut output,
411 5,
412 &mut MockDecoder {
413 scanline_number: 0,
414 scanline_bytes,
415 },
416 scanline_bytes as usize,
417 seek_scanline,
418 read_scanline,
419 )
420 .unwrap();
421 assert_eq!(output[0..25], DATA);
422 assert_eq!(output[25], 0);
423
424 output = [0u8; 26];
425 load_rect(
426 3,
427 2,
428 1,
429 1,
430 &mut output,
431 1,
432 &mut MockDecoder {
433 scanline_number: 0,
434 scanline_bytes,
435 },
436 scanline_bytes as usize,
437 seek_scanline,
438 read_scanline,
439 )
440 .unwrap();
441 assert_eq!(output[0..2], [13, 0]);
442
443 output = [0u8; 26];
444 load_rect(
445 3,
446 2,
447 2,
448 2,
449 &mut output,
450 2,
451 &mut MockDecoder {
452 scanline_number: 0,
453 scanline_bytes,
454 },
455 scanline_bytes as usize,
456 seek_scanline,
457 read_scanline,
458 )
459 .unwrap();
460 assert_eq!(output[0..5], [13, 14, 18, 19, 0]);
461
462 output = [0u8; 26];
463 load_rect(
464 1,
465 1,
466 2,
467 4,
468 &mut output,
469 2,
470 &mut MockDecoder {
471 scanline_number: 0,
472 scanline_bytes,
473 },
474 scanline_bytes as usize,
475 seek_scanline,
476 read_scanline,
477 )
478 .unwrap();
479 assert_eq!(output[0..9], [6, 7, 11, 12, 16, 17, 21, 22, 0]);
480 }
481 }
482
483 #[test]
484 fn test_load_rect_single_scanline() {
485 const DATA: [u8; 25] = [
486 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
487 24,
488 ];
489
490 struct MockDecoder;
491 impl ImageDecoder for MockDecoder {
492 fn dimensions(&self) -> (u32, u32) {
493 (5, 5)
494 }
495 fn color_type(&self) -> ColorType {
496 ColorType::L8
497 }
498 fn read_image(self, _buf: &mut [u8]) -> ImageResult<()> {
499 unimplemented!()
500 }
501 fn read_image_boxed(self: Box<Self>, buf: &mut [u8]) -> ImageResult<()> {
502 (*self).read_image(buf)
503 }
504 }
505
506 let mut seeks = 0;
508 let seek_scanline = |_d: &mut MockDecoder, n: u64| -> io::Result<()> {
509 seeks += 1;
510 assert_eq!(n, 0);
511 assert_eq!(seeks, 1);
512 Ok(())
513 };
514
515 fn read_scanline(_m: &mut MockDecoder, buf: &mut [u8]) -> io::Result<()> {
516 buf.copy_from_slice(&DATA);
517 Ok(())
518 }
519
520 let mut output = [0; 26];
521 load_rect(
522 1,
523 1,
524 2,
525 4,
526 &mut output,
527 2,
528 &mut MockDecoder,
529 DATA.len(),
530 seek_scanline,
531 read_scanline,
532 )
533 .unwrap();
534 assert_eq!(output[0..9], [6, 7, 11, 12, 16, 17, 21, 22, 0]);
535 }
536}