zune_image/codecs.rs
1/*
2 * Copyright (c) 2023.
3 *
4 * This software is free software;
5 *
6 * You can redistribute it or modify it under terms of the MIT, Apache License or Zlib license
7 */
8
9//! Entry point for all supported codecs the library understands
10//!
11//! The codecs here can be enabled and disabled at will depending on the configured interface,
12//! it is recommended that you enable encoders and decoders that you only use
13//!
14//!
15//! # Note on Compatibility with images
16//!
17//! - The library automatically tries to convert the image with highest compatibility
18//! this means that it will automatically convert the image to a supported bit depth
19//! and supported colorspace in case the image is not in the supported colorspace
20//! E.g if you open a HDR or EXR image whose format is `f32` `[0.0-1.0]` and convert it to JPEG,
21//! which understands 8 bit images`[0-255]`, the library will internally convert it to 8 bit images
22//!
23//! - For image depth, we convert it to the most appropriate depth, e.g trying to store F32 images in png
24//! will convert it to 16 bit images, this allows us to preserve as much information as possible
25//! during the conversation.
26//!
27//! - **Warning**: For this to work, the image will be cloned and the depth or colorspace modified on the
28//! clone. The current image is left as is, unmodified.
29//! **This may cause huge memory usage as cloning is expensive**
30//!
31//!
32#![allow(unused_imports, unused_variables, non_camel_case_types, dead_code)]
33
34use std::io::{BufRead, Cursor, Seek};
35use std::path::Path;
36
37use zune_core::bytestream::{ZByteReaderTrait, ZByteWriterTrait, ZCursor, ZReader};
38use zune_core::log::trace;
39use zune_core::options::{DecoderOptions, EncoderOptions};
40
41use crate::codecs;
42use crate::errors::ImgEncodeErrors::ImageEncodeErrors;
43use crate::errors::{ImageErrors, ImgEncodeErrors};
44use crate::image::Image;
45use crate::traits::{DecoderTrait, EncoderTrait};
46
47pub mod bmp;
48mod exr;
49pub mod farbfeld;
50pub mod hdr;
51pub mod jpeg;
52pub mod jpeg_xl;
53pub mod png;
54pub mod ppm;
55pub mod psd;
56pub mod qoi;
57
58pub mod webp;
59pub(crate) fn create_options_for_encoder(
60 options: Option<EncoderOptions>, image: &Image
61) -> EncoderOptions {
62 // choose if we take options from pre-configured , or we create default options
63 let start_options = if let Some(configured_opts) = options {
64 configured_opts
65 } else {
66 EncoderOptions::default()
67 };
68 let (width, height) = image.dimensions();
69 // then set image configuration
70 start_options
71 .set_width(width)
72 .set_height(height)
73 .set_depth(image.depth())
74 .set_colorspace(image.colorspace())
75}
76/// All supported image formats
77///
78/// This enum contains supported image formats, either
79/// encoders or decoders for a particular image
80#[derive(Copy, Clone, Debug, Eq, PartialEq)]
81#[non_exhaustive]
82pub enum ImageFormat {
83 /// Joint Photographic Experts Group
84 JPEG,
85 /// Portable Network Graphics
86 PNG,
87 /// Portable Pixel Map image
88 PPM,
89 /// Photoshop PSD component
90 PSD,
91 /// Farbfeld format
92 Farbfeld,
93 /// Quite Okay Image
94 QOI,
95 /// JPEG XL, new format
96 JPEG_XL,
97 /// Radiance HDR decoder
98 HDR,
99 /// Windows Bitmap Files
100 BMP,
101 ///
102 WEBP,
103 /// Any unknown format
104 Unknown
105}
106
107impl ImageFormat {
108 pub fn has_decoder(self) -> bool {
109 #[cfg(feature = "jpeg-xl")]
110 {
111 // for jpeg-xl we know we have a decoder when the header can be parsed
112 // but here, we aren't passing any data below, and since we are using is_ok()
113 // to determine if we have okay, the jxl one will always fail.
114 //
115 // So we lift the check out of the decoder and do it here
116 if self == ImageFormat::JPEG_XL {
117 return true;
118 }
119 }
120 #[cfg(feature = "webp")]
121 {
122 if self == ImageFormat::WEBP {
123 return true;
124 }
125 }
126 return self.decoder(Cursor::new(&[])).is_ok();
127 }
128 pub fn decoder<'a, T>(&self, data: T) -> Result<Box<dyn DecoderTrait + 'a>, ImageErrors>
129 where
130 T: ZByteReaderTrait + 'a + BufRead + Seek
131 {
132 self.decoder_with_options(data, DecoderOptions::default())
133 }
134
135 pub fn decoder_with_options<'a, T>(
136 &self, data: T, options: DecoderOptions
137 ) -> Result<Box<dyn DecoderTrait + 'a>, ImageErrors>
138 where
139 T: ZByteReaderTrait + 'a + BufRead + Seek
140 {
141 match self {
142 ImageFormat::JPEG => {
143 #[cfg(feature = "jpeg")]
144 {
145 Ok(Box::new(zune_jpeg::JpegDecoder::new_with_options(
146 data, options
147 )))
148 }
149 #[cfg(not(feature = "jpeg"))]
150 {
151 Err(ImageErrors::ImageDecoderNotIncluded(*self))
152 }
153 }
154
155 ImageFormat::PNG => {
156 #[cfg(feature = "png")]
157 {
158 Ok(Box::new(zune_png::PngDecoder::new_with_options(
159 data, options
160 )))
161 }
162 #[cfg(not(feature = "png"))]
163 {
164 Err(ImageErrors::ImageDecoderNotIncluded(*self))
165 }
166 }
167 ImageFormat::PPM => {
168 #[cfg(feature = "ppm")]
169 {
170 Ok(Box::new(zune_ppm::PPMDecoder::new_with_options(
171 data, options
172 )))
173 }
174 #[cfg(not(feature = "ppm"))]
175 {
176 Err(ImageErrors::ImageDecoderNotIncluded(*self))
177 }
178 }
179 ImageFormat::PSD => {
180 #[cfg(feature = "psd")]
181 {
182 Ok(Box::new(zune_psd::PSDDecoder::new_with_options(
183 data, options
184 )))
185 }
186 #[cfg(not(feature = "psd"))]
187 {
188 Err(ImageErrors::ImageDecoderNotIncluded(*self))
189 }
190 }
191
192 ImageFormat::Farbfeld => {
193 #[cfg(feature = "farbfeld")]
194 {
195 Ok(Box::new(zune_farbfeld::FarbFeldDecoder::new_with_options(
196 data, options
197 )))
198 }
199 #[cfg(not(feature = "farbfeld"))]
200 {
201 Err(ImageErrors::ImageDecoderNotIncluded(*self))
202 }
203 }
204
205 ImageFormat::QOI => {
206 #[cfg(feature = "qoi")]
207 {
208 Ok(Box::new(zune_qoi::QoiDecoder::new_with_options(
209 data, options
210 )))
211 }
212 #[cfg(not(feature = "qoi"))]
213 {
214 Err(ImageErrors::ImageDecoderNotIncluded(*self))
215 }
216 }
217 ImageFormat::HDR => {
218 #[cfg(feature = "hdr")]
219 {
220 Ok(Box::new(zune_hdr::HdrDecoder::new_with_options(
221 data, options
222 )))
223 }
224 #[cfg(not(feature = "hdr"))]
225 {
226 Err(ImageErrors::ImageDecoderNotIncluded(*self))
227 }
228 }
229 ImageFormat::BMP => {
230 #[cfg(feature = "bmp")]
231 {
232 Ok(Box::new(zune_bmp::BmpDecoder::new_with_options(
233 data, options
234 )))
235 }
236 #[cfg(not(feature = "bmp"))]
237 {
238 Err(ImageErrors::ImageDecoderNotIncluded(*self))
239 }
240 }
241 ImageFormat::JPEG_XL => {
242 #[cfg(feature = "jpeg-xl")]
243 {
244 // use a ZByteReader which implements read, this prevents unnecessary
245 // copy
246
247 let reader = ZReader::new(data);
248 Ok(Box::new(codecs::jpeg_xl::JxlDecoder::try_new(
249 reader, options
250 )?))
251 }
252 #[cfg(not(feature = "jpeg-xl"))]
253 {
254 Err(ImageErrors::ImageDecoderNotIncluded(*self))
255 }
256 }
257 ImageFormat::WEBP => {
258 #[cfg(feature = "webp")]
259 {
260 Ok(Box::new(codecs::webp::ZuneWebpDecoder::new(data)?))
261 }
262 #[cfg(not(feature = "webp"))]
263 {
264 Err(ImageErrors::ImageDecoderNotIncluded(*self))
265 }
266 }
267 ImageFormat::Unknown => Err(ImageErrors::ImageDecoderNotImplemented(*self))
268 }
269 }
270 /// Return true if an image format has an encoder that can convert the image
271 /// into that format
272 pub fn has_encoder(&self) -> bool {
273 // if the feature is included, means we have an encoder
274 #[allow(clippy::match_like_matches_macro)]
275 match self {
276 ImageFormat::JPEG => cfg!(feature = "jpeg"),
277 ImageFormat::PNG => cfg!(feature = "png"),
278 ImageFormat::PPM => cfg!(feature = "ppm"),
279 ImageFormat::Farbfeld => cfg!(feature = "farbfeld"),
280 ImageFormat::QOI => cfg!(feature = "qoi"),
281 ImageFormat::JPEG_XL => cfg!(feature = "jpeg-xl"),
282 ImageFormat::HDR => cfg!(feature = "hdr"),
283 ImageFormat::WEBP => cfg!(feature = "webp"),
284 _ => false
285 }
286 }
287 pub fn encode<T: ZByteWriterTrait>(
288 &self, image: &Image, encoder_options: EncoderOptions, sink: T
289 ) -> Result<usize, ImageErrors> {
290 match self {
291 ImageFormat::JPEG => {
292 #[cfg(feature = "jpeg")]
293 {
294 let mut encoder = codecs::jpeg::JpegEncoder::new_with_options(encoder_options);
295 return encoder.encode(image, sink);
296 }
297 }
298 ImageFormat::PNG => {
299 #[cfg(feature = "png")]
300 {
301 let mut encoder = codecs::png::PngEncoder::new_with_options(encoder_options);
302 return encoder.encode(image, sink);
303 }
304 }
305 ImageFormat::PPM => {
306 #[cfg(feature = "ppm")]
307 {
308 let mut encoder = codecs::ppm::PPMEncoder::new_with_options(encoder_options);
309 return encoder.encode(image, sink);
310 }
311 }
312 ImageFormat::Farbfeld => {
313 #[cfg(feature = "farbfeld")]
314 {
315 let mut encoder =
316 codecs::farbfeld::FarbFeldEncoder::new_with_options(encoder_options);
317 return encoder.encode(image, sink);
318 }
319 }
320 ImageFormat::QOI => {
321 #[cfg(feature = "qoi")]
322 {
323 let mut encoder = codecs::qoi::QoiEncoder::new_with_options(encoder_options);
324 return encoder.encode(image, sink);
325 }
326 }
327 ImageFormat::JPEG_XL => {
328 #[cfg(feature = "jpeg-xl")]
329 {
330 let mut encoder =
331 codecs::jpeg_xl::JxlEncoder::new_with_options(encoder_options);
332 return encoder.encode(image, sink);
333 }
334 }
335 ImageFormat::HDR => {
336 #[cfg(feature = "hdr")]
337 {
338 let mut encoder = codecs::hdr::HdrEncoder::new_with_options(encoder_options);
339 return encoder.encode(image, sink);
340 }
341 }
342 ImageFormat::WEBP => {
343 #[cfg(feature = "webp")]
344 {
345 let mut encoder = codecs::webp::ZuneWebpImageEncoder::new();
346 return encoder.encode(image, sink);
347 }
348 }
349 _ => {}
350 }
351 Err(ImageErrors::EncodeErrors(
352 ImgEncodeErrors::NoEncoderForFormat(*self)
353 ))
354 }
355
356 pub fn guess_format<T>(bytes: T) -> Option<(ImageFormat, T)>
357 where
358 T: ZByteReaderTrait
359 {
360 guess_format(bytes)
361 }
362
363 #[allow(unreachable_code)]
364 pub fn encoder_for_extension<P: AsRef<str>>(extension: P) -> Option<ImageFormat> {
365 match extension.as_ref() {
366 "qoi" => {
367 #[cfg(feature = "qoi")]
368 {
369 return Some(ImageFormat::QOI)
370 }
371 return None
372 }
373 "ppm" | "pam" | "pgm" | "pbm" | "pfm" => {
374 #[cfg(feature = "ppm")]
375 {
376 return Some(ImageFormat::PPM)
377 }
378 return None
379 }
380 "jpeg" | "jpg" => {
381 #[cfg(feature = "jpeg")]
382 {
383 return Some(ImageFormat::JPEG)
384 }
385 return None
386
387 }
388 "jxl" => {
389 #[cfg(feature = "jpeg-xl")]
390 {
391 return Some(ImageFormat::JPEG_XL)
392 }
393 return None
394
395 }
396 "ff" => {
397 #[cfg(feature = "farbfeld")]
398 {
399 return Some(ImageFormat::Farbfeld)
400 }
401 return None
402
403 }
404 "hdr" => {
405 #[cfg(feature = "hdr")]
406 {
407 return Some(ImageFormat::HDR)
408 }
409 return None
410
411 }
412 "png" => {
413 #[cfg(feature = "png")]
414 {
415 return Some(ImageFormat::PNG)
416 }
417 return None
418
419 }
420 "webp" => {
421 #[cfg(feature = "webp")]
422 {
423 return Some(ImageFormat::WEBP)
424 }
425 return None
426
427 }
428 _ => return None
429 }
430 }
431}
432
433// save options
434impl Image {
435 /// Save the image to a file and use the extension to
436 /// determine the format
437 ///
438 /// If the extension cannot be determined from the path, it's an error.
439 ///
440 /// # Arguments
441 ///
442 /// * `file`: The file to save the image to
443 ///
444 /// returns: Result<(), ImageErrors>
445 ///
446 /// # Examples
447 ///
448 /// - Encode image to jpeg format, requires `jpeg` feature
449 ///
450 /// ```no_run
451 /// use zune_core::colorspace::ColorSpace;
452 /// use zune_image::image::Image;
453 /// // create a luma image
454 /// let image = Image::fill::<u8>(128,ColorSpace::Luma,100,100);
455 /// // save to jpeg
456 /// image.save("hello.jpg").unwrap();
457 /// ```
458 pub fn save<P: AsRef<Path>>(&self, file: P) -> Result<(), ImageErrors> {
459 return if let Some(ext) = file.as_ref().extension() {
460 if let Some(format) = ImageFormat::encoder_for_extension(ext.to_string_lossy()) {
461 self.save_to(file, format)
462 } else {
463 let msg = format!("No encoder for extension {ext:?}");
464
465 Err(ImageErrors::EncodeErrors(ImgEncodeErrors::Generic(msg)))
466 }
467 } else {
468 let msg = format!("No extension for file {:?}", file.as_ref());
469
470 Err(ImageErrors::EncodeErrors(ImgEncodeErrors::Generic(msg)))
471 };
472 }
473 /// Save an image using a specified format to a file
474 ///
475 /// The image may be cloned and the clone modified to fit preferences
476 /// for that specific image format, e.g if the image is in f32 and being saved
477 /// to jpeg, the clone will be modified to be in u8, and that will be the format
478 /// saved to jpeg.
479 ///
480 /// # Arguments
481 ///
482 /// * `file`: The file path to which the image will be saved
483 /// * `format`: The format to save the image into. It's an error if the
484 /// format doesn't have an encoder(not all formats do)
485 ///
486 /// returns: Result<(), ImageErrors>
487 ///
488 ///
489 /// # Examples
490 ///
491 /// - Save a black grayscale image to JPEG, requires the JPEG feature
492 /// ```no_run
493 /// use zune_core::colorspace::ColorSpace;
494 /// use zune_image::codecs::ImageFormat;
495 /// use zune_image::errors::ImageErrors;
496 /// use zune_image::image::Image;
497 /// fn main()->Result<(),ImageErrors>{
498 /// // create a simple 200x200 grayscale image consisting of pure black
499 /// let image = Image::fill::<u8>(0,ColorSpace::Luma,200,200);
500 /// // save that to jpeg
501 /// #[cfg(feature = "jpeg")]
502 /// {
503 /// image.save_to("black.jpg",ImageFormat::JPEG)?;
504 /// }
505 /// Ok(())
506 /// }
507 /// ```
508 pub fn save_to<P: AsRef<Path>>(&self, file: P, format: ImageFormat) -> Result<(), ImageErrors> {
509 // open a file for which we will write directly to
510 let mut file = std::io::BufWriter::new(
511 std::fs::OpenOptions::new()
512 .create(true)
513 .write(true)
514 .truncate(true)
515 .open(file)?
516 );
517 self.encode(format, &mut file)?;
518 Ok(())
519 }
520
521 /// Encode an image returning a vector containing the result
522 /// of the encoding
523 ///
524 /// # Arguments
525 ///
526 /// * `format`: The format to use for encoding, it's an error if the
527 /// relevant encoder is not present either because it's not supported, or it's not
528 /// included as a feature.
529 ///
530 /// returns: `Result<Vec<u8, Global>, ImageErrors>`
531 ///
532 /// # Examples
533 ///
534 /// - Encode a simple image to QOI format, needs qoi format to be enabled
535 /// ```
536 /// use zune_core::colorspace::ColorSpace;
537 /// use zune_image::codecs::ImageFormat;
538 /// use zune_image::image::Image;
539 /// // create an image using from fn, to generate a gradient image
540 /// let image = Image::from_fn::<u8,_>(300,300,ColorSpace::RGB,|x,y,px|{
541 /// let r = (0.3 * x as f32) as u8;
542 /// let b = (0.3 * y as f32) as u8;
543 /// px[0] = r;
544 /// px[2] = b;
545 /// });
546 /// // write to qoi now
547 /// let contents = image.write_to_vec(ImageFormat::QOI).unwrap();
548 /// ```
549 pub fn write_to_vec(&self, format: ImageFormat) -> Result<Vec<u8>, ImageErrors> {
550 if format.has_encoder() {
551 let mut sink = vec![];
552 self.encode(format, &mut sink)?;
553 Ok(sink)
554 // encode
555 } else {
556 Err(ImageErrors::EncodeErrors(
557 crate::errors::ImgEncodeErrors::NoEncoderForFormat(format)
558 ))
559 }
560 }
561
562 /// Write data to a sink using a custom encoder returning how many bytes were written if successful
563 ///
564 /// # Arguments
565 ///
566 /// * `encoder`: The encoder to use for encoding
567 /// * `sink`: Where does output data goes
568 ///
569 /// returns: `Result<usize, ImageErrors>`
570 ///
571 /// # Examples
572 ///
573 /// - Encode a simple image to JPEG format
574 /// ```
575 /// use zune_core::colorspace::ColorSpace;
576 /// // requires jpeg feature
577 /// use zune_image::codecs::jpeg::JpegEncoder;
578 /// use zune_image::image::Image;
579 ///
580 /// let encoder = JpegEncoder::new();
581 ///
582 /// // create an image using from fn, to generate a gradient image
583 /// let image = Image::from_fn::<u8,_>(300,300,ColorSpace::RGB,|x,y,px|{
584 /// let r = (0.3 * x as f32) as u8;
585 /// let b = (0.3 * y as f32) as u8;
586 /// px[0] = r;
587 /// px[2] = b;
588 /// });
589 ///
590 /// let mut output = vec![];
591 ///
592 /// // write to jpeg now
593 /// let contents = image.write_with_encoder(encoder, &mut output).unwrap();
594 /// ```
595 pub fn write_with_encoder<T: ZByteWriterTrait>(
596 &self, mut encoder: impl EncoderTrait, sink: T
597 ) -> Result<usize, ImageErrors> {
598 encoder.encode(self, sink)
599 }
600
601 /// Open an encoded file for which the library has a configured decoder for it
602 ///
603 ///
604 /// - The decoders supported can be switched on and off depending on how
605 /// you configure your Cargo.toml. It is generally recommended to not enable decoders
606 /// you will not be using as it reduces both the security attack surface and dependency
607 ///
608 /// # Arguments
609 /// - file: The file path from which to read the file from, the file must be a supported format
610 /// otherwise it's an error to try and decode
611 ///
612 /// See also [read](Self::read) for reading from memory
613 pub fn open<P: AsRef<Path>>(file: P) -> Result<Image, ImageErrors> {
614 Self::open_with_options(file, DecoderOptions::default())
615 }
616
617 /// Open an encoded file for which the library has a configured decoder for it
618 /// with the specified custom decoder options
619 ///
620 /// This allows you to modify decoding steps where possible by specifying how the
621 /// decoder should behave
622 ///
623 /// # Example
624 /// - Decode a file with strict mode enabled and only expect images with less
625 /// than 100 pixels in width
626 ///
627 /// ```no_run
628 /// use zune_core::options::DecoderOptions;
629 /// use zune_image::image::Image;
630 /// let options = DecoderOptions::default().set_strict_mode(true).set_max_width(100);
631 /// let image = Image::open_with_options("/a/file.jpeg",options).unwrap();
632 /// ```
633 pub fn open_with_options<P: AsRef<Path>>(
634 file: P, options: DecoderOptions
635 ) -> Result<Image, ImageErrors> {
636 let reader = std::io::BufReader::new(std::fs::File::open(file)?);
637 Self::read(reader, options)
638 }
639 /// Open a new file from memory with the configured options
640 ///
641 /// # Arguments
642 /// - `src`: The encoded buffer loaded into memory
643 /// - `options`: The configured decoded options
644 /// # Example
645 /// - Open a memory source with the default options
646 ///
647 ///```no_run
648 /// use zune_core::bytestream::ZCursor;
649 /// use zune_core::options::DecoderOptions;
650 /// use zune_image::image::Image;
651 /// // create a simple ppm p5 grayscale format
652 /// let image = Image::read(ZCursor::new(b"P5 1 1 255 1"),DecoderOptions::default());
653 ///```
654 pub fn read<T>(src: T, options: DecoderOptions) -> Result<Image, ImageErrors>
655 where
656 T: ZByteReaderTrait + Seek + BufRead
657 {
658 let decoder = ImageFormat::guess_format(src);
659
660 if let Some((format, data)) = decoder {
661 let mut image_decoder = format.decoder_with_options(data, options)?;
662 // save format
663 let mut image = image_decoder.decode()?;
664 image.metadata.format = Some(format);
665 Ok(image)
666 } else {
667 Err(ImageErrors::ImageDecoderNotImplemented(
668 ImageFormat::Unknown
669 ))
670 }
671 }
672
673 /// Encode to a generic sink an image of a specific format
674 ///
675 /// # Arguments
676 ///
677 /// - format: The image format to write to sink
678 /// - sink: An encapsulation of where we will be sending data to
679 ///
680 /// # Returns
681 /// - The size of bytes written to sink or an error if it occurs
682 ///
683 pub fn encode<T: ZByteWriterTrait>(
684 &self, format: ImageFormat, sink: T
685 ) -> Result<usize, ImageErrors> {
686 self.encode_with_options(format, EncoderOptions::default(), sink)
687 }
688 /// Encode to a generic sink an image of a specific format
689 ///
690 /// # Arguments
691 ///
692 /// - format: The image format to write to sink
693 /// - sink: An encapsulation of where we will be sending data to
694 /// - encoder_options: Custom options used for encoding
695 /// # Returns
696 /// - The size of bytes written to sink or an error if it occurs
697 ///
698 fn encode_with_options<T: ZByteWriterTrait>(
699 &self, format: ImageFormat, encoder_options: EncoderOptions, sink: T
700 ) -> Result<usize, ImageErrors> {
701 format.encode(self, encoder_options, sink)
702 }
703
704 /// Open a new file from memory with the configured decoder
705 ///
706 /// # Arguments
707 /// - `decoder`: The configured decoder
708 /// # Example
709 /// - Open a memory source with the ppm decoder
710 ///
711 ///```no_run
712 /// // requires ppm feature
713 /// use std::io::Cursor;
714 /// use zune_image::codecs::ppm::PPMDecoder;
715 /// use zune_image::image::Image;
716 ///
717 /// // create a simple ppm p5 grayscale format
718 /// let decoder = PPMDecoder::new(Cursor::new(b"P5 1 1 255 1"));
719 ///
720 /// let image = Image::from_decoder(decoder);
721 ///```
722 pub fn from_decoder(mut decoder: impl DecoderTrait) -> Result<Image, ImageErrors> {
723 decoder.decode()
724 }
725}
726/// Guess the format of an image based on it's magic bytes
727///
728/// # Arguments
729/// - bytes: The data source containing the image pixels
730///
731/// # Returns
732/// - Some(format,T): The image format and the data source.
733/// - None: Indicates the format isn't known/understood by the library
734pub fn guess_format<T>(bytes: T) -> Option<(ImageFormat, T)>
735where
736 T: ZByteReaderTrait
737{
738 let mut reader = ZReader::new(bytes);
739 // stolen from imagers
740 let magic_bytes: Vec<(&[u8], ImageFormat)> = vec![
741 (&[137, 80, 78, 71, 13, 10, 26, 10], ImageFormat::PNG),
742 // Of course with jpg we need to relax our definition of what is a jpeg
743 // the best identifier would be 0xFF,0xd8 0xff but nop, some images exist
744 // which do not have that
745 (&[0xff, 0xd8], ImageFormat::JPEG),
746 (b"P5", ImageFormat::PPM),
747 (b"P6", ImageFormat::PPM),
748 (b"P7", ImageFormat::PPM),
749 (b"Pf", ImageFormat::PPM),
750 (b"PF", ImageFormat::PPM),
751 (b"8BPS", ImageFormat::PSD),
752 (b"farbfeld", ImageFormat::Farbfeld),
753 (b"qoif", ImageFormat::QOI),
754 (b"#?RADIANCE\n", ImageFormat::HDR),
755 (b"#?RGBE\n", ImageFormat::HDR),
756 (
757 &[
758 0x00, 0x00, 0x00, 0x0C, 0x4A, 0x58, 0x4C, 0x20, 0x0D, 0x0A, 0x87, 0x0A
759 ],
760 ImageFormat::JPEG_XL
761 ),
762 (&[0xFF, 0x0A], ImageFormat::JPEG_XL),
763 ];
764
765 for (magic, decoder) in magic_bytes {
766 if reader.peek_at(0, magic.len()).ok()?.starts_with(magic) {
767 return Some((decoder, reader.consume()));
768 }
769 }
770 #[cfg(feature = "bmp")]
771 {
772 // get a slice reference
773 // bmp requires 15 bytes to determine if it is a valid one.
774 // so take 16 just to be safe
775
776 let reference = reader.peek_at(0, 16).ok()?;
777
778 if zune_bmp::probe_bmp(reference) {
779 return Some((ImageFormat::BMP, reader.consume()));
780 }
781 }
782 #[cfg(feature = "webp")]
783 {
784 let reference = reader.peek_at(0, 12).ok()?;
785 if is_webp(reference) {
786 return Some((ImageFormat::WEBP, reader.consume()));
787 }
788 }
789
790 None
791}
792
793pub fn is_webp(data: &[u8]) -> bool {
794 // WebP files are at least 12 bytes long
795 if data.len() < 12 {
796 return false;
797 }
798
799 &data[0..4] == b"RIFF" && &data[8..12] == b"WEBP"
800}