| 1 | use std::error::Error; |
| 2 | use std::fmt; |
| 3 | use std::fs::File; |
| 4 | use std::io::{BufRead, BufReader, Cursor, Seek}; |
| 5 | use std::path::Path; |
| 6 | |
| 7 | mod container; |
| 8 | mod formats; |
| 9 | mod util; |
| 10 | |
| 11 | pub use container::heif::Compression; |
| 12 | use { |
| 13 | container::heif::{self}, |
| 14 | formats::*, |
| 15 | }; |
| 16 | |
| 17 | /// An Error type used in failure cases. |
| 18 | #[derive (Debug)] |
| 19 | pub enum ImageError { |
| 20 | /// Used when the given data is not a supported format. |
| 21 | NotSupported, |
| 22 | /// Used when the image has an invalid format. |
| 23 | CorruptedImage, |
| 24 | /// Used when an IoError occurs when trying to read the given data. |
| 25 | IoError(std::io::Error), |
| 26 | } |
| 27 | |
| 28 | impl Error for ImageError {} |
| 29 | |
| 30 | impl fmt::Display for ImageError { |
| 31 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
| 32 | use self::ImageError::*; |
| 33 | match self { |
| 34 | NotSupported => f.write_str(data:"Could not decode image" ), |
| 35 | CorruptedImage => f.write_str(data:"Hit end of file before finding size" ), |
| 36 | IoError(error: &Error) => error.fmt(f), |
| 37 | } |
| 38 | } |
| 39 | } |
| 40 | |
| 41 | impl From<std::io::Error> for ImageError { |
| 42 | fn from(err: std::io::Error) -> ImageError { |
| 43 | ImageError::IoError(err) |
| 44 | } |
| 45 | } |
| 46 | |
| 47 | pub type ImageResult<T> = Result<T, ImageError>; |
| 48 | |
| 49 | /// Types of image formats that this crate can identify. |
| 50 | #[non_exhaustive ] |
| 51 | #[derive (Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] |
| 52 | pub enum ImageType { |
| 53 | /// Animated sprite image format |
| 54 | /// <https://github.com/aseprite/aseprite> |
| 55 | Aseprite, |
| 56 | /// Standard Bitmap |
| 57 | Bmp, |
| 58 | /// DirectDraw Surface |
| 59 | Dds, |
| 60 | /// OpenEXR |
| 61 | Exr, |
| 62 | /// Farbfeld |
| 63 | /// <https://tools.suckless.org/farbfeld/> |
| 64 | Farbfeld, |
| 65 | /// Standard GIF |
| 66 | Gif, |
| 67 | /// Radiance HDR |
| 68 | Hdr, |
| 69 | /// Image Container Format |
| 70 | Heif(Compression), |
| 71 | /// Icon file |
| 72 | Ico, |
| 73 | /// Interleaved Bitmap |
| 74 | Ilbm, |
| 75 | /// Standard JPEG |
| 76 | Jpeg, |
| 77 | /// JPEG XL |
| 78 | Jxl, |
| 79 | /// Khronos Texture Container |
| 80 | Ktx2, |
| 81 | /// Standard PNG |
| 82 | Png, |
| 83 | /// Portable Any Map |
| 84 | Pnm, |
| 85 | /// Photoshop Document |
| 86 | Psd, |
| 87 | /// Quite OK Image Format |
| 88 | /// <https://qoiformat.org/> |
| 89 | Qoi, |
| 90 | /// Truevision Graphics Adapter |
| 91 | Tga, |
| 92 | /// Standard TIFF |
| 93 | Tiff, |
| 94 | /// Valve Texture Format |
| 95 | Vtf, |
| 96 | /// Standard Webp |
| 97 | Webp, |
| 98 | } |
| 99 | |
| 100 | /// Holds the size information of an image. |
| 101 | #[derive (Clone, Copy, Debug, PartialEq, Eq, Hash)] |
| 102 | pub struct ImageSize { |
| 103 | /// Width of an image in pixels. |
| 104 | pub width: usize, |
| 105 | /// Height of an image in pixels. |
| 106 | pub height: usize, |
| 107 | } |
| 108 | |
| 109 | impl Ord for ImageSize { |
| 110 | fn cmp(&self, other: &Self) -> std::cmp::Ordering { |
| 111 | (self.width * self.height).cmp(&(other.width * other.height)) |
| 112 | } |
| 113 | } |
| 114 | |
| 115 | impl PartialOrd for ImageSize { |
| 116 | fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> { |
| 117 | Some(self.cmp(other)) |
| 118 | } |
| 119 | } |
| 120 | |
| 121 | /// Get the image type from a header |
| 122 | /// |
| 123 | /// # Arguments |
| 124 | /// * `header` - The header of the file. |
| 125 | /// |
| 126 | /// # Remarks |
| 127 | /// |
| 128 | /// This will check the header to determine what image type the data is. |
| 129 | pub fn image_type(header: &[u8]) -> ImageResult<ImageType> { |
| 130 | formats::image_type(&mut Cursor::new(inner:header)) |
| 131 | } |
| 132 | |
| 133 | /// Get the image size from a local file |
| 134 | /// |
| 135 | /// # Arguments |
| 136 | /// * `path` - A local path to the file to parse. |
| 137 | /// |
| 138 | /// # Remarks |
| 139 | /// |
| 140 | /// Will try to read as little of the file as possible in order to get the |
| 141 | /// proper size information. |
| 142 | /// |
| 143 | /// # Error |
| 144 | /// |
| 145 | /// This method will return an [`ImageError`] under the following conditions: |
| 146 | /// |
| 147 | /// * The header isn't recognized as a supported image format |
| 148 | /// * The data isn't long enough to find the size for the given format |
| 149 | /// |
| 150 | /// The minimum data required is 12 bytes. Anything shorter will return [`ImageError::IoError`]. |
| 151 | /// |
| 152 | /// # Examples |
| 153 | /// |
| 154 | /// ``` |
| 155 | /// use imagesize::size; |
| 156 | /// |
| 157 | /// match size("test/test.webp" ) { |
| 158 | /// Ok(dim) => { |
| 159 | /// assert_eq!(dim.width, 716); |
| 160 | /// assert_eq!(dim.height, 716); |
| 161 | /// } |
| 162 | /// Err(why) => println!("Error getting size: {:?}" , why) |
| 163 | /// } |
| 164 | /// ``` |
| 165 | /// |
| 166 | /// [`ImageError`]: enum.ImageError.html |
| 167 | pub fn size<P: AsRef<Path>>(path: P) -> ImageResult<ImageSize> { |
| 168 | let file: File = File::open(path)?; |
| 169 | let reader: BufReader = BufReader::new(inner:file); |
| 170 | reader_size(reader) |
| 171 | } |
| 172 | |
| 173 | /// Get the image size from a block of raw data. |
| 174 | /// |
| 175 | /// # Arguments |
| 176 | /// * `data` - A Vec containing the data to parse for image size. |
| 177 | /// |
| 178 | /// # Error |
| 179 | /// |
| 180 | /// This method will return an [`ImageError`] under the following conditions: |
| 181 | /// |
| 182 | /// * The header isn't recognized as a supported image format |
| 183 | /// * The data isn't long enough to find the size for the given format |
| 184 | /// |
| 185 | /// The minimum data required is 12 bytes. Anything shorter will return [`ImageError::IoError`]. |
| 186 | /// |
| 187 | /// # Examples |
| 188 | /// |
| 189 | /// ``` |
| 190 | /// use imagesize::blob_size; |
| 191 | /// |
| 192 | /// // First few bytes of arbitrary data. |
| 193 | /// let data = vec![0x89, 0x89, 0x89, 0x89, 0x0D, 0x0A, 0x1A, 0x0A, |
| 194 | /// 0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52, |
| 195 | /// 0x00, 0x00, 0x00, 0x7B, 0x01, 0x00, 0x01, 0x41, |
| 196 | /// 0x08, 0x06, 0x00, 0x00, 0x00, 0x9A, 0x38, 0xC4]; |
| 197 | /// |
| 198 | /// assert_eq!(blob_size(&data).is_err(), true); |
| 199 | /// ``` |
| 200 | /// |
| 201 | /// [`ImageError`]: enum.ImageError.html |
| 202 | pub fn blob_size(data: &[u8]) -> ImageResult<ImageSize> { |
| 203 | let reader: Cursor<&[u8]> = Cursor::new(inner:data); |
| 204 | reader_size(reader) |
| 205 | } |
| 206 | |
| 207 | /// Get the image size from a reader |
| 208 | /// |
| 209 | /// # Arguments |
| 210 | /// * `reader` - A reader for the data |
| 211 | /// |
| 212 | /// # Error |
| 213 | /// |
| 214 | /// This method will return an [`ImageError`] under the following conditions: |
| 215 | /// |
| 216 | /// * The header isn't recognized as a supported image format |
| 217 | /// * The data isn't long enough to find the size for the given format |
| 218 | /// |
| 219 | /// The minimum data required is 12 bytes. Anything shorter will return [`ImageError::IoError`]. |
| 220 | /// |
| 221 | /// # Examples |
| 222 | /// |
| 223 | /// ``` |
| 224 | /// use std::io::Cursor; |
| 225 | /// use imagesize::reader_size; |
| 226 | /// |
| 227 | /// // PNG Header with size 123x321 |
| 228 | /// let reader = Cursor::new([ |
| 229 | /// 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, |
| 230 | /// 0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52, |
| 231 | /// 0x00, 0x00, 0x00, 0x7B, 0x00, 0x00, 0x01, 0x41, |
| 232 | /// 0x08, 0x06, 0x00, 0x00, 0x00, 0x9A, 0x38, 0xC4 |
| 233 | /// ]); |
| 234 | /// |
| 235 | /// match reader_size(reader) { |
| 236 | /// Ok(dim) => { |
| 237 | /// assert_eq!(dim.width, 123); |
| 238 | /// assert_eq!(dim.height, 321); |
| 239 | /// } |
| 240 | /// Err(why) => println!("Error getting reader size: {:?}" , why) |
| 241 | /// } |
| 242 | /// ``` |
| 243 | /// |
| 244 | /// [`ImageError`]: enum.ImageError.html |
| 245 | pub fn reader_size<R: BufRead + Seek>(mut reader: R) -> ImageResult<ImageSize> { |
| 246 | dispatch_header(&mut reader) |
| 247 | } |
| 248 | |
| 249 | /// Calls the correct image size method based on the image type |
| 250 | /// |
| 251 | /// # Arguments |
| 252 | /// * `reader` - A reader for the data |
| 253 | /// * `header` - The header of the file |
| 254 | fn dispatch_header<R: BufRead + Seek>(reader: &mut R) -> ImageResult<ImageSize> { |
| 255 | match formats::image_type(reader)? { |
| 256 | ImageType::Aseprite => aesprite::size(reader), |
| 257 | ImageType::Bmp => bmp::size(reader), |
| 258 | ImageType::Dds => dds::size(reader), |
| 259 | ImageType::Exr => exr::size(reader), |
| 260 | ImageType::Farbfeld => farbfeld::size(reader), |
| 261 | ImageType::Gif => gif::size(reader), |
| 262 | ImageType::Hdr => hdr::size(reader), |
| 263 | ImageType::Ico => ico::size(reader), |
| 264 | ImageType::Ilbm => ilbm::size(reader), |
| 265 | ImageType::Jpeg => jpeg::size(reader), |
| 266 | ImageType::Jxl => jxl::size(reader), |
| 267 | ImageType::Ktx2 => ktx2::size(reader), |
| 268 | ImageType::Png => png::size(reader), |
| 269 | ImageType::Pnm => pnm::size(reader), |
| 270 | ImageType::Psd => psd::size(reader), |
| 271 | ImageType::Qoi => qoi::size(reader), |
| 272 | ImageType::Tga => tga::size(reader), |
| 273 | ImageType::Tiff => tiff::size(reader), |
| 274 | ImageType::Vtf => vtf::size(reader), |
| 275 | ImageType::Webp => webp::size(reader), |
| 276 | |
| 277 | ImageType::Heif(..) => heif::size(reader), |
| 278 | } |
| 279 | } |
| 280 | |