Skip to main content

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}