Skip to main content

imagesize/
lib.rs

1#![allow(dead_code)]
2
3use std::error::Error;
4use std::fmt;
5use std::fs::File;
6use std::io::{BufRead, BufReader, Cursor, Seek};
7use std::path::Path;
8
9mod container;
10mod formats;
11mod util;
12
13pub use container::{
14    atc::AtcCompression, dds::DdsCompression, heif::Compression, pkm::PkmCompression,
15    pvrtc::PvrtcCompression,
16};
17
18/// Groups related compression algorithms regardless of their container format
19#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
20pub enum CompressionFamily {
21    /// Block Compression family (BC1-7, also known as DXT1-5, ATI1-2)
22    BlockCompression,
23    /// Ericsson Texture Compression family (ETC1, ETC2 variants)
24    Etc,
25    /// Ericsson Alpha Compression (EAC R11, RG11)
26    Eac,
27    /// PowerVR Texture Compression
28    Pvrtc,
29    /// Adaptive Scalable Texture Compression
30    Astc,
31    /// Adaptive Texture Compression (Qualcomm Adreno)
32    Atc,
33    /// Uncompressed formats
34    Uncompressed,
35}
36
37use {
38    container::heif::{self},
39    formats::*,
40};
41
42/// An Error type used in failure cases.
43#[derive(Debug)]
44pub enum ImageError {
45    /// Used when the given data is not a supported format.
46    NotSupported,
47    /// Used when the image has an invalid format.
48    CorruptedImage,
49    /// Used when an IoError occurs when trying to read the given data.
50    IoError(std::io::Error),
51}
52
53impl Error for ImageError {}
54
55impl fmt::Display for ImageError {
56    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
57        use self::ImageError::*;
58        match self {
59            NotSupported => f.write_str("Could not decode image"),
60            CorruptedImage => f.write_str("Hit end of file before finding size"),
61            IoError(error) => error.fmt(f),
62        }
63    }
64}
65
66impl From<std::io::Error> for ImageError {
67    fn from(err: std::io::Error) -> ImageError {
68        ImageError::IoError(err)
69    }
70}
71
72pub type ImageResult<T> = Result<T, ImageError>;
73
74/// Types of image formats that this crate can identify.
75///
76/// Many container formats support multiple inner compression formats. For these formats,
77/// the enum contains the inner compression type to provide more detailed information:
78///
79/// - `Dds(DdsCompression)` - DirectDraw Surface with various BC compression formats
80/// - `Etc2(PkmCompression)` - ETC/PKM container with ETC1, ETC2, EAC variants
81/// - `Eac(PkmCompression)` - EAC formats (unified with ETC2 detection)
82/// - `Atc(AtcCompression)` - Adaptive Texture Compression variants
83/// - `Pvrtc(PvrtcCompression)` - PowerVR texture compression with 2bpp/4bpp variants
84///
85/// # Helper Methods
86///
87/// The `ImageType` provides several helper methods to query compression information
88/// across different container formats:
89///
90/// - [`compression_family()`](ImageType::compression_family) - Groups related compression algorithms
91/// - [`is_block_compressed()`](ImageType::is_block_compressed) - Checks for BC/DXT compression
92/// - [`container_format()`](ImageType::container_format) - Returns container format name
93/// - [`is_multi_compression_container()`](ImageType::is_multi_compression_container) - Checks if container supports multiple compression types
94///
95/// # Examples
96///
97/// ## Basic Format Detection
98///
99/// ```rust
100/// use imagesize::{image_type, ImageType, PkmCompression};
101///
102/// // Create a PKM header for ETC2 format
103/// let mut header = vec![b'P', b'K', b'M', b' ', b'2', b'0'];
104/// header.extend_from_slice(&0x0001u16.to_be_bytes()); // ETC2 RGB
105/// header.extend_from_slice(&[0x00, 0x40, 0x00, 0x40]); // Extended dimensions
106/// header.extend_from_slice(&[0x00, 0x40, 0x00, 0x40]); // Original dimensions
107///
108/// match image_type(&header).unwrap() {
109///     ImageType::Etc2(PkmCompression::Etc2) => println!("This is ETC2 RGB format"),
110///     ImageType::Etc2(compression) => println!("This is ETC2 format: {:?}", compression),
111///     other => println!("Other format: {:?}", other),
112/// }
113/// ```
114///
115/// ## Using Helper Methods for Cross-Container Queries
116///
117/// ```rust
118/// use imagesize::{ImageType, CompressionFamily, DdsCompression, PvrtcCompression};
119///
120/// // Query compression families across different containers
121/// let dds_bc1 = ImageType::Dds(DdsCompression::Bc1);
122/// let pvr_etc2 = ImageType::Pvrtc(PvrtcCompression::Etc2Rgb);
123/// let png = ImageType::Png;
124///
125/// // Group related compression algorithms
126/// assert_eq!(dds_bc1.compression_family(), Some(CompressionFamily::BlockCompression));
127/// assert_eq!(pvr_etc2.compression_family(), Some(CompressionFamily::Etc));
128/// assert_eq!(png.compression_family(), None); // Simple formats don't have compression
129///
130/// // Check for specific compression types
131/// assert!(dds_bc1.is_block_compressed());
132/// assert!(!pvr_etc2.is_block_compressed());
133///
134/// // Identify container formats
135/// assert_eq!(dds_bc1.container_format(), Some("DDS"));
136/// assert_eq!(pvr_etc2.container_format(), Some("PowerVR"));
137/// assert_eq!(png.container_format(), None);
138///
139/// // Check multi-compression support
140/// assert!(dds_bc1.is_multi_compression_container()); // DDS supports BC1-7, RGBA, etc.
141/// assert!(pvr_etc2.is_multi_compression_container()); // PowerVR supports PVRTC, ETC2, EAC
142/// ```
143#[non_exhaustive]
144#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
145pub enum ImageType {
146    /// Animated sprite image format
147    /// <https://github.com/aseprite/aseprite>
148    #[cfg(feature = "aesprite")]
149    Aseprite,
150    /// Adaptive Scalable Texture Compression
151    #[cfg(feature = "astc")]
152    Astc,
153    /// Adaptive Texture Compression
154    #[cfg(feature = "atc")]
155    Atc(AtcCompression),
156    /// Standard Bitmap
157    #[cfg(feature = "bmp")]
158    Bmp,
159    /// DirectDraw Surface
160    #[cfg(feature = "dds")]
161    Dds(DdsCompression),
162    /// Ericsson Texture Compression - Alpha Channel (now unified with ETC2)
163    #[cfg(feature = "eac")]
164    Eac(PkmCompression),
165    /// Ericsson Texture Compression 2 (includes ETC1, ETC2 variants)
166    #[cfg(feature = "etc2")]
167    Etc2(PkmCompression),
168    /// OpenEXR
169    #[cfg(feature = "exr")]
170    Exr,
171    /// Farbfeld
172    /// <https://tools.suckless.org/farbfeld/>
173    #[cfg(feature = "farbfeld")]
174    Farbfeld,
175    /// Standard GIF
176    #[cfg(feature = "gif")]
177    Gif,
178    /// Radiance HDR
179    #[cfg(feature = "hdr")]
180    Hdr,
181    /// Image Container Format
182    #[cfg(feature = "heif")]
183    Heif(Compression),
184    /// Icon file
185    #[cfg(feature = "ico")]
186    Ico,
187    /// Interleaved Bitmap
188    #[cfg(feature = "ilbm")]
189    Ilbm,
190    /// Standard JPEG
191    #[cfg(feature = "jpeg")]
192    Jpeg,
193    /// JPEG XL
194    #[cfg(feature = "jxl")]
195    Jxl,
196    /// Khronos Texture Container
197    #[cfg(feature = "ktx2")]
198    Ktx2,
199    /// Standard PNG
200    #[cfg(feature = "png")]
201    Png,
202    /// Portable Any Map
203    #[cfg(feature = "pnm")]
204    Pnm,
205    /// PowerVR Texture Compression
206    #[cfg(feature = "pvrtc")]
207    Pvrtc(PvrtcCompression),
208    /// Photoshop Document
209    #[cfg(feature = "psd")]
210    Psd,
211    /// Quite OK Image Format
212    /// <https://qoiformat.org/>
213    #[cfg(feature = "qoi")]
214    Qoi,
215    /// Truevision Graphics Adapter
216    #[cfg(feature = "tga")]
217    Tga,
218    /// Standard TIFF
219    #[cfg(feature = "tiff")]
220    Tiff,
221    /// Valve Texture Format
222    #[cfg(feature = "vtf")]
223    Vtf,
224    /// Standard Webp
225    #[cfg(feature = "webp")]
226    Webp,
227}
228
229impl ImageType {
230    /// Returns the compression family for texture formats
231    ///
232    /// Groups related compression algorithms regardless of their container format.
233    /// Returns None for simple image formats like PNG, JPEG, etc.
234    ///
235    /// # Examples
236    ///
237    /// ```rust
238    /// use imagesize::{ImageType, CompressionFamily, DdsCompression, PvrtcCompression};
239    ///
240    /// let dds_type = ImageType::Dds(DdsCompression::Bc1);
241    /// assert_eq!(dds_type.compression_family(), Some(CompressionFamily::BlockCompression));
242    ///
243    /// let pvrtc_etc2_type = ImageType::Pvrtc(PvrtcCompression::Etc2Rgb);
244    /// assert_eq!(pvrtc_etc2_type.compression_family(), Some(CompressionFamily::Etc));
245    ///
246    /// let png_type = ImageType::Png;
247    /// assert_eq!(png_type.compression_family(), None);
248    /// ```
249    pub fn compression_family(&self) -> Option<CompressionFamily> {
250        match self {
251            #[cfg(feature = "dds")]
252            ImageType::Dds(compression) => match compression {
253                DdsCompression::Bc1
254                | DdsCompression::Bc2
255                | DdsCompression::Bc3
256                | DdsCompression::Bc4
257                | DdsCompression::Bc5
258                | DdsCompression::Bc6h
259                | DdsCompression::Bc7 => Some(CompressionFamily::BlockCompression),
260                DdsCompression::Rgba32 | DdsCompression::Rgb24 => {
261                    Some(CompressionFamily::Uncompressed)
262                }
263                DdsCompression::Unknown => None,
264            },
265
266            #[cfg(feature = "etc2")]
267            ImageType::Etc2(compression) => match compression {
268                PkmCompression::Etc1
269                | PkmCompression::Etc2
270                | PkmCompression::Etc2A1
271                | PkmCompression::Etc2A8 => Some(CompressionFamily::Etc),
272                PkmCompression::EacR
273                | PkmCompression::EacRg
274                | PkmCompression::EacRSigned
275                | PkmCompression::EacRgSigned => Some(CompressionFamily::Eac),
276                PkmCompression::Unknown => None,
277            },
278
279            #[cfg(feature = "eac")]
280            ImageType::Eac(compression) => match compression {
281                PkmCompression::EacR
282                | PkmCompression::EacRg
283                | PkmCompression::EacRSigned
284                | PkmCompression::EacRgSigned => Some(CompressionFamily::Eac),
285                PkmCompression::Etc1
286                | PkmCompression::Etc2
287                | PkmCompression::Etc2A1
288                | PkmCompression::Etc2A8 => Some(CompressionFamily::Etc),
289                PkmCompression::Unknown => None,
290            },
291
292            #[cfg(feature = "pvrtc")]
293            ImageType::Pvrtc(compression) => match compression {
294                PvrtcCompression::Pvrtc2BppRgb
295                | PvrtcCompression::Pvrtc2BppRgba
296                | PvrtcCompression::Pvrtc4BppRgb
297                | PvrtcCompression::Pvrtc4BppRgba => Some(CompressionFamily::Pvrtc),
298                PvrtcCompression::Etc2Rgb
299                | PvrtcCompression::Etc2Rgba
300                | PvrtcCompression::Etc2RgbA1 => Some(CompressionFamily::Etc),
301                PvrtcCompression::EacR11 | PvrtcCompression::EacRg11 => {
302                    Some(CompressionFamily::Eac)
303                }
304                PvrtcCompression::Unknown => None,
305            },
306
307            #[cfg(feature = "atc")]
308            ImageType::Atc(_) => Some(CompressionFamily::Atc),
309
310            #[cfg(feature = "astc")]
311            ImageType::Astc => Some(CompressionFamily::Astc),
312
313            // Simple formats don't have compression families
314            _ => None,
315        }
316    }
317
318    /// Returns true if the image uses block compression (BC/DXT family)
319    ///
320    /// Block compression includes BC1-7 formats (also known as DXT1-5, ATI1-2).
321    ///
322    /// # Examples
323    ///
324    /// ```rust
325    /// use imagesize::{ImageType, DdsCompression};
326    ///
327    /// let bc1_type = ImageType::Dds(DdsCompression::Bc1);
328    /// assert!(bc1_type.is_block_compressed());
329    ///
330    /// let png_type = ImageType::Png;
331    /// assert!(!png_type.is_block_compressed());
332    /// ```
333    pub fn is_block_compressed(&self) -> bool {
334        matches!(
335            self.compression_family(),
336            Some(CompressionFamily::BlockCompression)
337        )
338    }
339
340    /// Returns the container format name for texture formats
341    ///
342    /// Returns a human-readable string identifying the container format.
343    /// Returns None for simple image formats.
344    ///
345    /// # Examples
346    ///
347    /// ```rust
348    /// use imagesize::{ImageType, DdsCompression, PvrtcCompression};
349    ///
350    /// let dds_type = ImageType::Dds(DdsCompression::Bc1);
351    /// assert_eq!(dds_type.container_format(), Some("DDS"));
352    ///
353    /// let pvr_type = ImageType::Pvrtc(PvrtcCompression::Pvrtc2BppRgb);
354    /// assert_eq!(pvr_type.container_format(), Some("PowerVR"));
355    ///
356    /// let png_type = ImageType::Png;
357    /// assert_eq!(png_type.container_format(), None);
358    /// ```
359    pub fn container_format(&self) -> Option<&'static str> {
360        match self {
361            #[cfg(feature = "dds")]
362            ImageType::Dds(_) => Some("DDS"),
363
364            #[cfg(feature = "etc2")]
365            ImageType::Etc2(_) => Some("PKM"),
366
367            #[cfg(feature = "eac")]
368            ImageType::Eac(_) => Some("PKM"),
369
370            #[cfg(feature = "pvrtc")]
371            ImageType::Pvrtc(_) => Some("PowerVR"),
372
373            #[cfg(feature = "atc")]
374            ImageType::Atc(_) => Some("PKM"), // ATC typically uses PKM containers
375
376            #[cfg(feature = "astc")]
377            ImageType::Astc => Some("ASTC"), // Direct ASTC format
378
379            #[cfg(feature = "heif")]
380            ImageType::Heif(_) => Some("HEIF"),
381
382            #[cfg(feature = "ktx2")]
383            ImageType::Ktx2 => Some("KTX2"),
384
385            // Simple formats don't have containers
386            _ => None,
387        }
388    }
389
390    /// Returns true if the image format supports multiple compression types within the same container
391    ///
392    /// Some container formats like PowerVR can store different compression algorithms.
393    ///
394    /// # Examples
395    ///
396    /// ```rust
397    /// use imagesize::{ImageType, PvrtcCompression, DdsCompression};
398    ///
399    /// let pvr_type = ImageType::Pvrtc(PvrtcCompression::Etc2Rgb);
400    /// assert!(pvr_type.is_multi_compression_container());
401    ///
402    /// let dds_type = ImageType::Dds(DdsCompression::Bc1);
403    /// assert!(dds_type.is_multi_compression_container());
404    ///
405    /// let png_type = ImageType::Png;
406    /// assert!(!png_type.is_multi_compression_container());
407    /// ```
408    pub fn is_multi_compression_container(&self) -> bool {
409        match self {
410            #[cfg(feature = "dds")]
411            ImageType::Dds(_) => true, // DDS supports BC1-7, RGBA, etc.
412
413            #[cfg(feature = "pvrtc")]
414            ImageType::Pvrtc(_) => true, // PowerVR supports PVRTC, ETC2, EAC
415
416            #[cfg(feature = "ktx2")]
417            ImageType::Ktx2 => true, // KTX2 supports many formats
418
419            _ => false,
420        }
421    }
422
423    /// Calls the correct image size method based on the image type
424    ///
425    /// # Arguments
426    /// * `reader` - A reader for the data
427    pub fn reader_size<R: BufRead + Seek>(&self, reader: &mut R) -> ImageResult<ImageSize> {
428        match self {
429            #[cfg(feature = "aesprite")]
430            ImageType::Aseprite => aesprite::size(reader),
431            #[cfg(feature = "astc")]
432            ImageType::Astc => astc::size(reader),
433            #[cfg(feature = "atc")]
434            ImageType::Atc(..) => container::atc::size(reader),
435            #[cfg(feature = "bmp")]
436            ImageType::Bmp => bmp::size(reader),
437            #[cfg(feature = "dds")]
438            ImageType::Dds(..) => container::dds::size(reader),
439            #[cfg(feature = "eac")]
440            ImageType::Eac(..) => container::pkm::size(reader),
441            #[cfg(feature = "etc2")]
442            ImageType::Etc2(..) => container::pkm::size(reader),
443            #[cfg(feature = "exr")]
444            ImageType::Exr => exr::size(reader),
445            #[cfg(feature = "farbfeld")]
446            ImageType::Farbfeld => farbfeld::size(reader),
447            #[cfg(feature = "gif")]
448            ImageType::Gif => gif::size(reader),
449            #[cfg(feature = "hdr")]
450            ImageType::Hdr => hdr::size(reader),
451            #[cfg(feature = "ico")]
452            ImageType::Ico => ico::size(reader),
453            #[cfg(feature = "ilbm")]
454            ImageType::Ilbm => ilbm::size(reader),
455            #[cfg(feature = "jpeg")]
456            ImageType::Jpeg => jpeg::size(reader),
457            #[cfg(feature = "jxl")]
458            ImageType::Jxl => jxl::size(reader),
459            #[cfg(feature = "ktx2")]
460            ImageType::Ktx2 => ktx2::size(reader),
461            #[cfg(feature = "png")]
462            ImageType::Png => png::size(reader),
463            #[cfg(feature = "pnm")]
464            ImageType::Pnm => pnm::size(reader),
465            #[cfg(feature = "pvrtc")]
466            ImageType::Pvrtc(..) => container::pvrtc::size(reader),
467            #[cfg(feature = "psd")]
468            ImageType::Psd => psd::size(reader),
469            #[cfg(feature = "qoi")]
470            ImageType::Qoi => qoi::size(reader),
471            #[cfg(feature = "tga")]
472            ImageType::Tga => tga::size(reader),
473            #[cfg(feature = "tiff")]
474            ImageType::Tiff => tiff::size(reader),
475            #[cfg(feature = "vtf")]
476            ImageType::Vtf => vtf::size(reader),
477            #[cfg(feature = "webp")]
478            ImageType::Webp => webp::size(reader),
479
480            #[cfg(feature = "heif")]
481            ImageType::Heif(..) => heif::size(reader),
482        }
483    }
484}
485
486/// Holds the size information of an image.
487#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
488pub struct ImageSize {
489    /// Width of an image in pixels.
490    pub width: usize,
491    /// Height of an image in pixels.
492    pub height: usize,
493}
494
495impl Ord for ImageSize {
496    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
497        (self.width * self.height).cmp(&(other.width * other.height))
498    }
499}
500
501impl PartialOrd for ImageSize {
502    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
503        Some(self.cmp(other))
504    }
505}
506
507/// Get the image type from a header
508///
509/// # Arguments
510/// * `header` - The header of the file.
511///
512/// # Remarks
513///
514/// This will check the header to determine what image type the data is.
515pub fn image_type(header: &[u8]) -> ImageResult<ImageType> {
516    formats::image_type(&mut Cursor::new(header))
517}
518
519/// Get the image size from a local file
520///
521/// # Arguments
522/// * `path` - A local path to the file to parse.
523///
524/// # Remarks
525///
526/// Will try to read as little of the file as possible in order to get the
527/// proper size information.
528///
529/// # Error
530///
531/// This method will return an [`ImageError`] under the following conditions:
532///
533/// * The header isn't recognized as a supported image format
534/// * The data isn't long enough to find the size for the given format
535///
536/// The minimum data required is 12 bytes. Anything shorter will return [`ImageError::IoError`].
537///
538/// # Examples
539///
540/// ```
541/// use imagesize::size;
542///
543/// match size("test/test.webp") {
544///     Ok(dim) => {
545///         assert_eq!(dim.width, 716);
546///         assert_eq!(dim.height, 716);
547///     }
548///     Err(why) => println!("Error getting size: {:?}", why)
549/// }
550/// ```
551///
552/// [`ImageError`]: enum.ImageError.html
553pub fn size<P: AsRef<Path>>(path: P) -> ImageResult<ImageSize> {
554    let file = File::open(path)?;
555    let reader = BufReader::new(file);
556    reader_size(reader)
557}
558
559/// Get the image size from a block of raw data.
560///
561/// # Arguments
562/// * `data` - A Vec containing the data to parse for image size.
563///
564/// # Error
565///
566/// This method will return an [`ImageError`] under the following conditions:
567///
568/// * The header isn't recognized as a supported image format
569/// * The data isn't long enough to find the size for the given format
570///
571/// The minimum data required is 12 bytes. Anything shorter will return [`ImageError::IoError`].
572///
573/// # Examples
574///
575/// ```
576/// use imagesize::blob_size;
577///
578/// // First few bytes of arbitrary data.
579/// let data = vec![0x89, 0x89, 0x89, 0x89, 0x0D, 0x0A, 0x1A, 0x0A,
580///                 0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52,
581///                 0x00, 0x00, 0x00, 0x7B, 0x01, 0x00, 0x01, 0x41,
582///                 0x08, 0x06, 0x00, 0x00, 0x00, 0x9A, 0x38, 0xC4];
583///
584/// assert_eq!(blob_size(&data).is_err(), true);
585/// ```
586///
587/// [`ImageError`]: enum.ImageError.html
588pub fn blob_size(data: &[u8]) -> ImageResult<ImageSize> {
589    let reader = Cursor::new(data);
590    reader_size(reader)
591}
592
593/// Get the image size from a reader
594///
595/// # Arguments
596/// * `reader` - A reader for the data
597///
598/// # Error
599///
600/// This method will return an [`ImageError`] under the following conditions:
601///
602/// * The header isn't recognized as a supported image format
603/// * The data isn't long enough to find the size for the given format
604///
605/// The minimum data required is 12 bytes. Anything shorter will return [`ImageError::IoError`].
606///
607/// # Examples
608///
609/// ```
610/// use std::io::Cursor;
611/// use imagesize::reader_size;
612///
613/// // PNG Header with size 123x321
614/// let reader = Cursor::new([
615///     0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A,
616///     0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52,
617///     0x00, 0x00, 0x00, 0x7B, 0x00, 0x00, 0x01, 0x41,
618///     0x08, 0x06, 0x00, 0x00, 0x00, 0x9A, 0x38, 0xC4
619/// ]);
620///
621/// match reader_size(reader) {
622///     Ok(dim) => {
623///         assert_eq!(dim.width, 123);
624///         assert_eq!(dim.height, 321);
625///     }
626///     Err(why) => println!("Error getting reader size: {:?}", why)
627/// }
628/// ```
629///
630/// [`ImageError`]: enum.ImageError.html
631pub fn reader_size<R: BufRead + Seek>(mut reader: R) -> ImageResult<ImageSize> {
632    reader_type(&mut reader)?.reader_size(&mut reader)
633}
634
635/// Get the image type from a reader
636///
637/// # Arguments
638/// * `reader` - A reader for the data
639///
640/// # Remarks
641///
642/// This will check the header to determine what image type the data is.
643pub fn reader_type<R: BufRead + Seek>(mut reader: R) -> ImageResult<ImageType> {
644    formats::image_type(&mut reader)
645}