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}