diff options
author | Valentin Popov <valentin@popov.link> | 2024-01-08 00:21:28 +0300 |
---|---|---|
committer | Valentin Popov <valentin@popov.link> | 2024-01-08 00:21:28 +0300 |
commit | 1b6a04ca5504955c571d1c97504fb45ea0befee4 (patch) | |
tree | 7579f518b23313e8a9748a88ab6173d5e030b227 /vendor/image/src/codecs/bmp/decoder.rs | |
parent | 5ecd8cf2cba827454317368b68571df0d13d7842 (diff) | |
download | fparkan-1b6a04ca5504955c571d1c97504fb45ea0befee4.tar.xz fparkan-1b6a04ca5504955c571d1c97504fb45ea0befee4.zip |
Initial vendor packages
Signed-off-by: Valentin Popov <valentin@popov.link>
Diffstat (limited to 'vendor/image/src/codecs/bmp/decoder.rs')
-rw-r--r-- | vendor/image/src/codecs/bmp/decoder.rs | 1483 |
1 files changed, 1483 insertions, 0 deletions
diff --git a/vendor/image/src/codecs/bmp/decoder.rs b/vendor/image/src/codecs/bmp/decoder.rs new file mode 100644 index 0000000..58c0650 --- /dev/null +++ b/vendor/image/src/codecs/bmp/decoder.rs @@ -0,0 +1,1483 @@ +use std::cmp::{self, Ordering}; +use std::convert::TryFrom; +use std::io::{self, Cursor, Read, Seek, SeekFrom}; +use std::iter::{repeat, Iterator, Rev}; +use std::marker::PhantomData; +use std::slice::ChunksMut; +use std::{error, fmt, mem}; + +use byteorder::{LittleEndian, ReadBytesExt}; + +use crate::color::ColorType; +use crate::error::{ + DecodingError, ImageError, ImageResult, UnsupportedError, UnsupportedErrorKind, +}; +use crate::image::{self, ImageDecoder, ImageDecoderRect, ImageFormat, Progress}; + +const BITMAPCOREHEADER_SIZE: u32 = 12; +const BITMAPINFOHEADER_SIZE: u32 = 40; +const BITMAPV2HEADER_SIZE: u32 = 52; +const BITMAPV3HEADER_SIZE: u32 = 56; +const BITMAPV4HEADER_SIZE: u32 = 108; +const BITMAPV5HEADER_SIZE: u32 = 124; + +static LOOKUP_TABLE_3_BIT_TO_8_BIT: [u8; 8] = [0, 36, 73, 109, 146, 182, 219, 255]; +static LOOKUP_TABLE_4_BIT_TO_8_BIT: [u8; 16] = [ + 0, 17, 34, 51, 68, 85, 102, 119, 136, 153, 170, 187, 204, 221, 238, 255, +]; +static LOOKUP_TABLE_5_BIT_TO_8_BIT: [u8; 32] = [ + 0, 8, 16, 25, 33, 41, 49, 58, 66, 74, 82, 90, 99, 107, 115, 123, 132, 140, 148, 156, 165, 173, + 181, 189, 197, 206, 214, 222, 230, 239, 247, 255, +]; +static LOOKUP_TABLE_6_BIT_TO_8_BIT: [u8; 64] = [ + 0, 4, 8, 12, 16, 20, 24, 28, 32, 36, 40, 45, 49, 53, 57, 61, 65, 69, 73, 77, 81, 85, 89, 93, + 97, 101, 105, 109, 113, 117, 121, 125, 130, 134, 138, 142, 146, 150, 154, 158, 162, 166, 170, + 174, 178, 182, 186, 190, 194, 198, 202, 206, 210, 215, 219, 223, 227, 231, 235, 239, 243, 247, + 251, 255, +]; + +static R5_G5_B5_COLOR_MASK: Bitfields = Bitfields { + r: Bitfield { len: 5, shift: 10 }, + g: Bitfield { len: 5, shift: 5 }, + b: Bitfield { len: 5, shift: 0 }, + a: Bitfield { len: 0, shift: 0 }, +}; +const R8_G8_B8_COLOR_MASK: Bitfields = Bitfields { + r: Bitfield { len: 8, shift: 24 }, + g: Bitfield { len: 8, shift: 16 }, + b: Bitfield { len: 8, shift: 8 }, + a: Bitfield { len: 0, shift: 0 }, +}; +const R8_G8_B8_A8_COLOR_MASK: Bitfields = Bitfields { + r: Bitfield { len: 8, shift: 16 }, + g: Bitfield { len: 8, shift: 8 }, + b: Bitfield { len: 8, shift: 0 }, + a: Bitfield { len: 8, shift: 24 }, +}; + +const RLE_ESCAPE: u8 = 0; +const RLE_ESCAPE_EOL: u8 = 0; +const RLE_ESCAPE_EOF: u8 = 1; +const RLE_ESCAPE_DELTA: u8 = 2; + +/// The maximum width/height the decoder will process. +const MAX_WIDTH_HEIGHT: i32 = 0xFFFF; + +#[derive(PartialEq, Copy, Clone)] +enum ImageType { + Palette, + RGB16, + RGB24, + RGB32, + RGBA32, + RLE8, + RLE4, + Bitfields16, + Bitfields32, +} + +#[derive(PartialEq)] +enum BMPHeaderType { + Core, + Info, + V2, + V3, + V4, + V5, +} + +#[derive(PartialEq)] +enum FormatFullBytes { + RGB24, + RGB32, + RGBA32, + Format888, +} + +enum Chunker<'a> { + FromTop(ChunksMut<'a, u8>), + FromBottom(Rev<ChunksMut<'a, u8>>), +} + +pub(crate) struct RowIterator<'a> { + chunks: Chunker<'a>, +} + +impl<'a> Iterator for RowIterator<'a> { + type Item = &'a mut [u8]; + + #[inline(always)] + fn next(&mut self) -> Option<&'a mut [u8]> { + match self.chunks { + Chunker::FromTop(ref mut chunks) => chunks.next(), + Chunker::FromBottom(ref mut chunks) => chunks.next(), + } + } +} + +/// All errors that can occur when attempting to parse a BMP +#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)] +enum DecoderError { + // Failed to decompress RLE data. + CorruptRleData, + + /// The bitfield mask interleaves set and unset bits + BitfieldMaskNonContiguous, + /// Bitfield mask invalid (e.g. too long for specified type) + BitfieldMaskInvalid, + /// Bitfield (of the specified width – 16- or 32-bit) mask not present + BitfieldMaskMissing(u32), + /// Bitfield (of the specified width – 16- or 32-bit) masks not present + BitfieldMasksMissing(u32), + + /// BMP's "BM" signature wrong or missing + BmpSignatureInvalid, + /// More than the exactly one allowed plane specified by the format + MoreThanOnePlane, + /// Invalid amount of bits per channel for the specified image type + InvalidChannelWidth(ChannelWidthError, u16), + + /// The width is negative + NegativeWidth(i32), + /// One of the dimensions is larger than a soft limit + ImageTooLarge(i32, i32), + /// The height is `i32::min_value()` + /// + /// General negative heights specify top-down DIBs + InvalidHeight, + + /// Specified image type is invalid for top-down BMPs (i.e. is compressed) + ImageTypeInvalidForTopDown(u32), + /// Image type not currently recognized by the decoder + ImageTypeUnknown(u32), + + /// Bitmap header smaller than the core header + HeaderTooSmall(u32), + + /// The palette is bigger than allowed by the bit count of the BMP + PaletteSizeExceeded { + colors_used: u32, + bit_count: u16, + }, +} + +impl fmt::Display for DecoderError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + DecoderError::CorruptRleData => f.write_str("Corrupt RLE data"), + DecoderError::BitfieldMaskNonContiguous => f.write_str("Non-contiguous bitfield mask"), + DecoderError::BitfieldMaskInvalid => f.write_str("Invalid bitfield mask"), + DecoderError::BitfieldMaskMissing(bb) => { + f.write_fmt(format_args!("Missing {}-bit bitfield mask", bb)) + } + DecoderError::BitfieldMasksMissing(bb) => { + f.write_fmt(format_args!("Missing {}-bit bitfield masks", bb)) + } + DecoderError::BmpSignatureInvalid => f.write_str("BMP signature not found"), + DecoderError::MoreThanOnePlane => f.write_str("More than one plane"), + DecoderError::InvalidChannelWidth(tp, n) => { + f.write_fmt(format_args!("Invalid channel bit count for {}: {}", tp, n)) + } + DecoderError::NegativeWidth(w) => f.write_fmt(format_args!("Negative width ({})", w)), + DecoderError::ImageTooLarge(w, h) => f.write_fmt(format_args!( + "Image too large (one of ({}, {}) > soft limit of {})", + w, h, MAX_WIDTH_HEIGHT + )), + DecoderError::InvalidHeight => f.write_str("Invalid height"), + DecoderError::ImageTypeInvalidForTopDown(tp) => f.write_fmt(format_args!( + "Invalid image type {} for top-down image.", + tp + )), + DecoderError::ImageTypeUnknown(tp) => { + f.write_fmt(format_args!("Unknown image compression type {}", tp)) + } + DecoderError::HeaderTooSmall(s) => { + f.write_fmt(format_args!("Bitmap header too small ({} bytes)", s)) + } + DecoderError::PaletteSizeExceeded { + colors_used, + bit_count, + } => f.write_fmt(format_args!( + "Palette size {} exceeds maximum size for BMP with bit count of {}", + colors_used, bit_count + )), + } + } +} + +impl From<DecoderError> for ImageError { + fn from(e: DecoderError) -> ImageError { + ImageError::Decoding(DecodingError::new(ImageFormat::Bmp.into(), e)) + } +} + +impl error::Error for DecoderError {} + +/// Distinct image types whose saved channel width can be invalid +#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)] +enum ChannelWidthError { + /// RGB + Rgb, + /// 8-bit run length encoding + Rle8, + /// 4-bit run length encoding + Rle4, + /// Bitfields (16- or 32-bit) + Bitfields, +} + +impl fmt::Display for ChannelWidthError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(match self { + ChannelWidthError::Rgb => "RGB", + ChannelWidthError::Rle8 => "RLE8", + ChannelWidthError::Rle4 => "RLE4", + ChannelWidthError::Bitfields => "bitfields", + }) + } +} + +/// Convenience function to check if the combination of width, length and number of +/// channels would result in a buffer that would overflow. +fn check_for_overflow(width: i32, length: i32, channels: usize) -> ImageResult<()> { + num_bytes(width, length, channels) + .map(|_| ()) + .ok_or_else(|| { + ImageError::Unsupported(UnsupportedError::from_format_and_kind( + ImageFormat::Bmp.into(), + UnsupportedErrorKind::GenericFeature(format!( + "Image dimensions ({}x{} w/{} channels) are too large", + width, length, channels + )), + )) + }) +} + +/// Calculate how many many bytes a buffer holding a decoded image with these properties would +/// require. Returns `None` if the buffer size would overflow or if one of the sizes are negative. +fn num_bytes(width: i32, length: i32, channels: usize) -> Option<usize> { + if width <= 0 || length <= 0 { + None + } else { + match channels.checked_mul(width as usize) { + Some(n) => n.checked_mul(length as usize), + None => None, + } + } +} + +/// Call the provided function on each row of the provided buffer, returning Err if the provided +/// function returns an error, extends the buffer if it's not large enough. +fn with_rows<F>( + buffer: &mut [u8], + width: i32, + height: i32, + channels: usize, + top_down: bool, + mut func: F, +) -> io::Result<()> +where + F: FnMut(&mut [u8]) -> io::Result<()>, +{ + // An overflow should already have been checked for when this is called, + // though we check anyhow, as it somehow seems to increase performance slightly. + let row_width = channels.checked_mul(width as usize).unwrap(); + let full_image_size = row_width.checked_mul(height as usize).unwrap(); + assert_eq!(buffer.len(), full_image_size); + + if !top_down { + for row in buffer.chunks_mut(row_width).rev() { + func(row)?; + } + } else { + for row in buffer.chunks_mut(row_width) { + func(row)?; + } + } + Ok(()) +} + +fn set_8bit_pixel_run<'a, T: Iterator<Item = &'a u8>>( + pixel_iter: &mut ChunksMut<u8>, + palette: &[[u8; 3]], + indices: T, + n_pixels: usize, +) -> bool { + for idx in indices.take(n_pixels) { + if let Some(pixel) = pixel_iter.next() { + let rgb = palette[*idx as usize]; + pixel[0] = rgb[0]; + pixel[1] = rgb[1]; + pixel[2] = rgb[2]; + } else { + return false; + } + } + true +} + +fn set_4bit_pixel_run<'a, T: Iterator<Item = &'a u8>>( + pixel_iter: &mut ChunksMut<u8>, + palette: &[[u8; 3]], + indices: T, + mut n_pixels: usize, +) -> bool { + for idx in indices { + macro_rules! set_pixel { + ($i:expr) => { + if n_pixels == 0 { + break; + } + if let Some(pixel) = pixel_iter.next() { + let rgb = palette[$i as usize]; + pixel[0] = rgb[0]; + pixel[1] = rgb[1]; + pixel[2] = rgb[2]; + } else { + return false; + } + n_pixels -= 1; + }; + } + set_pixel!(idx >> 4); + set_pixel!(idx & 0xf); + } + true +} + +#[rustfmt::skip] +fn set_2bit_pixel_run<'a, T: Iterator<Item = &'a u8>>( + pixel_iter: &mut ChunksMut<u8>, + palette: &[[u8; 3]], + indices: T, + mut n_pixels: usize, +) -> bool { + for idx in indices { + macro_rules! set_pixel { + ($i:expr) => { + if n_pixels == 0 { + break; + } + if let Some(pixel) = pixel_iter.next() { + let rgb = palette[$i as usize]; + pixel[0] = rgb[0]; + pixel[1] = rgb[1]; + pixel[2] = rgb[2]; + } else { + return false; + } + n_pixels -= 1; + }; + } + set_pixel!((idx >> 6) & 0x3u8); + set_pixel!((idx >> 4) & 0x3u8); + set_pixel!((idx >> 2) & 0x3u8); + set_pixel!( idx & 0x3u8); + } + true +} + +fn set_1bit_pixel_run<'a, T: Iterator<Item = &'a u8>>( + pixel_iter: &mut ChunksMut<u8>, + palette: &[[u8; 3]], + indices: T, +) { + for idx in indices { + let mut bit = 0x80; + loop { + if let Some(pixel) = pixel_iter.next() { + let rgb = palette[((idx & bit) != 0) as usize]; + pixel[0] = rgb[0]; + pixel[1] = rgb[1]; + pixel[2] = rgb[2]; + } else { + return; + } + + bit >>= 1; + if bit == 0 { + break; + } + } + } +} + +#[derive(PartialEq, Eq)] +struct Bitfield { + shift: u32, + len: u32, +} + +impl Bitfield { + fn from_mask(mask: u32, max_len: u32) -> ImageResult<Bitfield> { + if mask == 0 { + return Ok(Bitfield { shift: 0, len: 0 }); + } + let mut shift = mask.trailing_zeros(); + let mut len = (!(mask >> shift)).trailing_zeros(); + if len != mask.count_ones() { + return Err(DecoderError::BitfieldMaskNonContiguous.into()); + } + if len + shift > max_len { + return Err(DecoderError::BitfieldMaskInvalid.into()); + } + if len > 8 { + shift += len - 8; + len = 8; + } + Ok(Bitfield { shift, len }) + } + + fn read(&self, data: u32) -> u8 { + let data = data >> self.shift; + match self.len { + 1 => ((data & 0b1) * 0xff) as u8, + 2 => ((data & 0b11) * 0x55) as u8, + 3 => LOOKUP_TABLE_3_BIT_TO_8_BIT[(data & 0b00_0111) as usize], + 4 => LOOKUP_TABLE_4_BIT_TO_8_BIT[(data & 0b00_1111) as usize], + 5 => LOOKUP_TABLE_5_BIT_TO_8_BIT[(data & 0b01_1111) as usize], + 6 => LOOKUP_TABLE_6_BIT_TO_8_BIT[(data & 0b11_1111) as usize], + 7 => ((data & 0x7f) << 1 | (data & 0x7f) >> 6) as u8, + 8 => (data & 0xff) as u8, + _ => panic!(), + } + } +} + +#[derive(PartialEq, Eq)] +struct Bitfields { + r: Bitfield, + g: Bitfield, + b: Bitfield, + a: Bitfield, +} + +impl Bitfields { + fn from_mask( + r_mask: u32, + g_mask: u32, + b_mask: u32, + a_mask: u32, + max_len: u32, + ) -> ImageResult<Bitfields> { + let bitfields = Bitfields { + r: Bitfield::from_mask(r_mask, max_len)?, + g: Bitfield::from_mask(g_mask, max_len)?, + b: Bitfield::from_mask(b_mask, max_len)?, + a: Bitfield::from_mask(a_mask, max_len)?, + }; + if bitfields.r.len == 0 || bitfields.g.len == 0 || bitfields.b.len == 0 { + return Err(DecoderError::BitfieldMaskMissing(max_len).into()); + } + Ok(bitfields) + } +} + +/// A bmp decoder +pub struct BmpDecoder<R> { + reader: R, + + bmp_header_type: BMPHeaderType, + indexed_color: bool, + + width: i32, + height: i32, + data_offset: u64, + top_down: bool, + no_file_header: bool, + add_alpha_channel: bool, + has_loaded_metadata: bool, + image_type: ImageType, + + bit_count: u16, + colors_used: u32, + palette: Option<Vec<[u8; 3]>>, + bitfields: Option<Bitfields>, +} + +enum RLEInsn { + EndOfFile, + EndOfRow, + Delta(u8, u8), + Absolute(u8, Vec<u8>), + PixelRun(u8, u8), +} + +impl<R: Read + Seek> BmpDecoder<R> { + fn new_decoder(reader: R) -> BmpDecoder<R> { + BmpDecoder { + reader, + + bmp_header_type: BMPHeaderType::Info, + indexed_color: false, + + width: 0, + height: 0, + data_offset: 0, + top_down: false, + no_file_header: false, + add_alpha_channel: false, + has_loaded_metadata: false, + image_type: ImageType::Palette, + + bit_count: 0, + colors_used: 0, + palette: None, + bitfields: None, + } + } + + /// Create a new decoder that decodes from the stream ```r``` + pub fn new(reader: R) -> ImageResult<BmpDecoder<R>> { + let mut decoder = Self::new_decoder(reader); + decoder.read_metadata()?; + Ok(decoder) + } + + /// Create a new decoder that decodes from the stream ```r``` without first + /// reading a BITMAPFILEHEADER. This is useful for decoding the CF_DIB format + /// directly from the Windows clipboard. + pub fn new_without_file_header(reader: R) -> ImageResult<BmpDecoder<R>> { + let mut decoder = Self::new_decoder(reader); + decoder.no_file_header = true; + decoder.read_metadata()?; + Ok(decoder) + } + + #[cfg(feature = "ico")] + pub(crate) fn new_with_ico_format(reader: R) -> ImageResult<BmpDecoder<R>> { + let mut decoder = Self::new_decoder(reader); + decoder.read_metadata_in_ico_format()?; + Ok(decoder) + } + + /// If true, the palette in BMP does not apply to the image even if it is found. + /// In other words, the output image is the indexed color. + pub fn set_indexed_color(&mut self, indexed_color: bool) { + self.indexed_color = indexed_color; + } + + #[cfg(feature = "ico")] + pub(crate) fn reader(&mut self) -> &mut R { + &mut self.reader + } + + fn read_file_header(&mut self) -> ImageResult<()> { + if self.no_file_header { + return Ok(()); + } + let mut signature = [0; 2]; + self.reader.read_exact(&mut signature)?; + + if signature != b"BM"[..] { + return Err(DecoderError::BmpSignatureInvalid.into()); + } + + // The next 8 bytes represent file size, followed the 4 reserved bytes + // We're not interesting these values + self.reader.read_u32::<LittleEndian>()?; + self.reader.read_u32::<LittleEndian>()?; + + self.data_offset = u64::from(self.reader.read_u32::<LittleEndian>()?); + + Ok(()) + } + + /// Read BITMAPCOREHEADER https://msdn.microsoft.com/en-us/library/vs/alm/dd183372(v=vs.85).aspx + /// + /// returns Err if any of the values are invalid. + fn read_bitmap_core_header(&mut self) -> ImageResult<()> { + // As height/width values in BMP files with core headers are only 16 bits long, + // they won't be larger than `MAX_WIDTH_HEIGHT`. + self.width = i32::from(self.reader.read_u16::<LittleEndian>()?); + self.height = i32::from(self.reader.read_u16::<LittleEndian>()?); + + check_for_overflow(self.width, self.height, self.num_channels())?; + + // Number of planes (format specifies that this should be 1). + if self.reader.read_u16::<LittleEndian>()? != 1 { + return Err(DecoderError::MoreThanOnePlane.into()); + } + + self.bit_count = self.reader.read_u16::<LittleEndian>()?; + self.image_type = match self.bit_count { + 1 | 4 | 8 => ImageType::Palette, + 24 => ImageType::RGB24, + _ => { + return Err(DecoderError::InvalidChannelWidth( + ChannelWidthError::Rgb, + self.bit_count, + ) + .into()) + } + }; + + Ok(()) + } + + /// Read BITMAPINFOHEADER https://msdn.microsoft.com/en-us/library/vs/alm/dd183376(v=vs.85).aspx + /// or BITMAPV{2|3|4|5}HEADER. + /// + /// returns Err if any of the values are invalid. + fn read_bitmap_info_header(&mut self) -> ImageResult<()> { + self.width = self.reader.read_i32::<LittleEndian>()?; + self.height = self.reader.read_i32::<LittleEndian>()?; + + // Width can not be negative + if self.width < 0 { + return Err(DecoderError::NegativeWidth(self.width).into()); + } else if self.width > MAX_WIDTH_HEIGHT || self.height > MAX_WIDTH_HEIGHT { + // Limit very large image sizes to avoid OOM issues. Images with these sizes are + // unlikely to be valid anyhow. + return Err(DecoderError::ImageTooLarge(self.width, self.height).into()); + } + + if self.height == i32::min_value() { + return Err(DecoderError::InvalidHeight.into()); + } + + // A negative height indicates a top-down DIB. + if self.height < 0 { + self.height *= -1; + self.top_down = true; + } + + check_for_overflow(self.width, self.height, self.num_channels())?; + + // Number of planes (format specifies that this should be 1). + if self.reader.read_u16::<LittleEndian>()? != 1 { + return Err(DecoderError::MoreThanOnePlane.into()); + } + + self.bit_count = self.reader.read_u16::<LittleEndian>()?; + let image_type_u32 = self.reader.read_u32::<LittleEndian>()?; + + // Top-down dibs can not be compressed. + if self.top_down && image_type_u32 != 0 && image_type_u32 != 3 { + return Err(DecoderError::ImageTypeInvalidForTopDown(image_type_u32).into()); + } + self.image_type = match image_type_u32 { + 0 => match self.bit_count { + 1 | 2 | 4 | 8 => ImageType::Palette, + 16 => ImageType::RGB16, + 24 => ImageType::RGB24, + 32 if self.add_alpha_channel => ImageType::RGBA32, + 32 => ImageType::RGB32, + _ => { + return Err(DecoderError::InvalidChannelWidth( + ChannelWidthError::Rgb, + self.bit_count, + ) + .into()) + } + }, + 1 => match self.bit_count { + 8 => ImageType::RLE8, + _ => { + return Err(DecoderError::InvalidChannelWidth( + ChannelWidthError::Rle8, + self.bit_count, + ) + .into()) + } + }, + 2 => match self.bit_count { + 4 => ImageType::RLE4, + _ => { + return Err(DecoderError::InvalidChannelWidth( + ChannelWidthError::Rle4, + self.bit_count, + ) + .into()) + } + }, + 3 => match self.bit_count { + 16 => ImageType::Bitfields16, + 32 => ImageType::Bitfields32, + _ => { + return Err(DecoderError::InvalidChannelWidth( + ChannelWidthError::Bitfields, + self.bit_count, + ) + .into()) + } + }, + 4 => { + // JPEG compression is not implemented yet. + return Err(ImageError::Unsupported( + UnsupportedError::from_format_and_kind( + ImageFormat::Bmp.into(), + UnsupportedErrorKind::GenericFeature("JPEG compression".to_owned()), + ), + )); + } + 5 => { + // PNG compression is not implemented yet. + return Err(ImageError::Unsupported( + UnsupportedError::from_format_and_kind( + ImageFormat::Bmp.into(), + UnsupportedErrorKind::GenericFeature("PNG compression".to_owned()), + ), + )); + } + 11 | 12 | 13 => { + // CMYK types are not implemented yet. + return Err(ImageError::Unsupported( + UnsupportedError::from_format_and_kind( + ImageFormat::Bmp.into(), + UnsupportedErrorKind::GenericFeature("CMYK format".to_owned()), + ), + )); + } + _ => { + // Unknown compression type. + return Err(DecoderError::ImageTypeUnknown(image_type_u32).into()); + } + }; + + // The next 12 bytes represent data array size in bytes, + // followed the horizontal and vertical printing resolutions + // We will calculate the pixel array size using width & height of image + // We're not interesting the horz or vert printing resolutions + self.reader.read_u32::<LittleEndian>()?; + self.reader.read_u32::<LittleEndian>()?; + self.reader.read_u32::<LittleEndian>()?; + + self.colors_used = self.reader.read_u32::<LittleEndian>()?; + + // The next 4 bytes represent number of "important" colors + // We're not interested in this value, so we'll skip it + self.reader.read_u32::<LittleEndian>()?; + + Ok(()) + } + + fn read_bitmasks(&mut self) -> ImageResult<()> { + let r_mask = self.reader.read_u32::<LittleEndian>()?; + let g_mask = self.reader.read_u32::<LittleEndian>()?; + let b_mask = self.reader.read_u32::<LittleEndian>()?; + + let a_mask = match self.bmp_header_type { + BMPHeaderType::V3 | BMPHeaderType::V4 | BMPHeaderType::V5 => { + self.reader.read_u32::<LittleEndian>()? + } + _ => 0, + }; + + self.bitfields = match self.image_type { + ImageType::Bitfields16 => { + Some(Bitfields::from_mask(r_mask, g_mask, b_mask, a_mask, 16)?) + } + ImageType::Bitfields32 => { + Some(Bitfields::from_mask(r_mask, g_mask, b_mask, a_mask, 32)?) + } + _ => None, + }; + + if self.bitfields.is_some() && a_mask != 0 { + self.add_alpha_channel = true; + } + + Ok(()) + } + + fn read_metadata(&mut self) -> ImageResult<()> { + if !self.has_loaded_metadata { + self.read_file_header()?; + let bmp_header_offset = self.reader.stream_position()?; + let bmp_header_size = self.reader.read_u32::<LittleEndian>()?; + let bmp_header_end = bmp_header_offset + u64::from(bmp_header_size); + + self.bmp_header_type = match bmp_header_size { + BITMAPCOREHEADER_SIZE => BMPHeaderType::Core, + BITMAPINFOHEADER_SIZE => BMPHeaderType::Info, + BITMAPV2HEADER_SIZE => BMPHeaderType::V2, + BITMAPV3HEADER_SIZE => BMPHeaderType::V3, + BITMAPV4HEADER_SIZE => BMPHeaderType::V4, + BITMAPV5HEADER_SIZE => BMPHeaderType::V5, + _ if bmp_header_size < BITMAPCOREHEADER_SIZE => { + // Size of any valid header types won't be smaller than core header type. + return Err(DecoderError::HeaderTooSmall(bmp_header_size).into()); + } + _ => { + return Err(ImageError::Unsupported( + UnsupportedError::from_format_and_kind( + ImageFormat::Bmp.into(), + UnsupportedErrorKind::GenericFeature(format!( + "Unknown bitmap header type (size={})", + bmp_header_size + )), + ), + )) + } + }; + + match self.bmp_header_type { + BMPHeaderType::Core => { + self.read_bitmap_core_header()?; + } + BMPHeaderType::Info + | BMPHeaderType::V2 + | BMPHeaderType::V3 + | BMPHeaderType::V4 + | BMPHeaderType::V5 => { + self.read_bitmap_info_header()?; + } + }; + + match self.image_type { + ImageType::Bitfields16 | ImageType::Bitfields32 => self.read_bitmasks()?, + _ => {} + }; + + self.reader.seek(SeekFrom::Start(bmp_header_end))?; + + match self.image_type { + ImageType::Palette | ImageType::RLE4 | ImageType::RLE8 => self.read_palette()?, + _ => {} + }; + + if self.no_file_header { + // Use the offset of the end of metadata instead of reading a BMP file header. + self.data_offset = self.reader.stream_position()?; + } + + self.has_loaded_metadata = true; + } + Ok(()) + } + + #[cfg(feature = "ico")] + #[doc(hidden)] + pub fn read_metadata_in_ico_format(&mut self) -> ImageResult<()> { + self.no_file_header = true; + self.add_alpha_channel = true; + self.read_metadata()?; + + // The height field in an ICO file is doubled to account for the AND mask + // (whether or not an AND mask is actually present). + self.height /= 2; + Ok(()) + } + + fn get_palette_size(&mut self) -> ImageResult<usize> { + match self.colors_used { + 0 => Ok(1 << self.bit_count), + _ => { + if self.colors_used > 1 << self.bit_count { + return Err(DecoderError::PaletteSizeExceeded { + colors_used: self.colors_used, + bit_count: self.bit_count, + } + .into()); + } + Ok(self.colors_used as usize) + } + } + } + + fn bytes_per_color(&self) -> usize { + match self.bmp_header_type { + BMPHeaderType::Core => 3, + _ => 4, + } + } + + fn read_palette(&mut self) -> ImageResult<()> { + const MAX_PALETTE_SIZE: usize = 256; // Palette indices are u8. + + let bytes_per_color = self.bytes_per_color(); + let palette_size = self.get_palette_size()?; + let max_length = MAX_PALETTE_SIZE * bytes_per_color; + + let length = palette_size * bytes_per_color; + let mut buf = Vec::with_capacity(max_length); + + // Resize and read the palette entries to the buffer. + // We limit the buffer to at most 256 colours to avoid any oom issues as + // 8-bit images can't reference more than 256 indexes anyhow. + buf.resize(cmp::min(length, max_length), 0); + self.reader.by_ref().read_exact(&mut buf)?; + + // Allocate 256 entries even if palette_size is smaller, to prevent corrupt files from + // causing an out-of-bounds array access. + match length.cmp(&max_length) { + Ordering::Greater => { + self.reader + .seek(SeekFrom::Current((length - max_length) as i64))?; + } + Ordering::Less => buf.resize(max_length, 0), + Ordering::Equal => (), + } + + let p: Vec<[u8; 3]> = (0..MAX_PALETTE_SIZE) + .map(|i| { + let b = buf[bytes_per_color * i]; + let g = buf[bytes_per_color * i + 1]; + let r = buf[bytes_per_color * i + 2]; + [r, g, b] + }) + .collect(); + + self.palette = Some(p); + + Ok(()) + } + + /// Get the palette that is embedded in the BMP image, if any. + pub fn get_palette(&self) -> Option<&[[u8; 3]]> { + self.palette.as_ref().map(|vec| &vec[..]) + } + + fn num_channels(&self) -> usize { + if self.indexed_color { + 1 + } else if self.add_alpha_channel { + 4 + } else { + 3 + } + } + + fn rows<'a>(&self, pixel_data: &'a mut [u8]) -> RowIterator<'a> { + let stride = self.width as usize * self.num_channels(); + if self.top_down { + RowIterator { + chunks: Chunker::FromTop(pixel_data.chunks_mut(stride)), + } + } else { + RowIterator { + chunks: Chunker::FromBottom(pixel_data.chunks_mut(stride).rev()), + } + } + } + + fn read_palettized_pixel_data(&mut self, buf: &mut [u8]) -> ImageResult<()> { + let num_channels = self.num_channels(); + let row_byte_length = ((i32::from(self.bit_count) * self.width + 31) / 32 * 4) as usize; + let mut indices = vec![0; row_byte_length]; + let palette = self.palette.as_ref().unwrap(); + let bit_count = self.bit_count; + let reader = &mut self.reader; + let width = self.width as usize; + let skip_palette = self.indexed_color; + + reader.seek(SeekFrom::Start(self.data_offset))?; + + if num_channels == 4 { + buf.chunks_exact_mut(4).for_each(|c| c[3] = 0xFF); + } + + with_rows( + buf, + self.width, + self.height, + num_channels, + self.top_down, + |row| { + reader.read_exact(&mut indices)?; + if skip_palette { + row.clone_from_slice(&indices[0..width]); + } else { + let mut pixel_iter = row.chunks_mut(num_channels); + match bit_count { + 1 => { + set_1bit_pixel_run(&mut pixel_iter, palette, indices.iter()); + } + 2 => { + set_2bit_pixel_run(&mut pixel_iter, palette, indices.iter(), width); + } + 4 => { + set_4bit_pixel_run(&mut pixel_iter, palette, indices.iter(), width); + } + 8 => { + set_8bit_pixel_run(&mut pixel_iter, palette, indices.iter(), width); + } + _ => panic!(), + }; + } + Ok(()) + }, + )?; + + Ok(()) + } + + fn read_16_bit_pixel_data( + &mut self, + buf: &mut [u8], + bitfields: Option<&Bitfields>, + ) -> ImageResult<()> { + let num_channels = self.num_channels(); + let row_padding_len = self.width as usize % 2 * 2; + let row_padding = &mut [0; 2][..row_padding_len]; + let bitfields = match bitfields { + Some(b) => b, + None => self.bitfields.as_ref().unwrap(), + }; + let reader = &mut self.reader; + + reader.seek(SeekFrom::Start(self.data_offset))?; + + with_rows( + buf, + self.width, + self.height, + num_channels, + self.top_down, + |row| { + for pixel in row.chunks_mut(num_channels) { + let data = u32::from(reader.read_u16::<LittleEndian>()?); + + pixel[0] = bitfields.r.read(data); + pixel[1] = bitfields.g.read(data); + pixel[2] = bitfields.b.read(data); + if num_channels == 4 { + if bitfields.a.len != 0 { + pixel[3] = bitfields.a.read(data); + } else { + pixel[3] = 0xFF; + } + } + } + reader.read_exact(row_padding) + }, + )?; + + Ok(()) + } + + /// Read image data from a reader in 32-bit formats that use bitfields. + fn read_32_bit_pixel_data(&mut self, buf: &mut [u8]) -> ImageResult<()> { + let num_channels = self.num_channels(); + + let bitfields = self.bitfields.as_ref().unwrap(); + + let reader = &mut self.reader; + reader.seek(SeekFrom::Start(self.data_offset))?; + + with_rows( + buf, + self.width, + self.height, + num_channels, + self.top_down, + |row| { + for pixel in row.chunks_mut(num_channels) { + let data = reader.read_u32::<LittleEndian>()?; + + pixel[0] = bitfields.r.read(data); + pixel[1] = bitfields.g.read(data); + pixel[2] = bitfields.b.read(data); + if num_channels == 4 { + if bitfields.a.len != 0 { + pixel[3] = bitfields.a.read(data); + } else { + pixel[3] = 0xff; + } + } + } + Ok(()) + }, + )?; + + Ok(()) + } + + /// Read image data from a reader where the colours are stored as 8-bit values (24 or 32-bit). + fn read_full_byte_pixel_data( + &mut self, + buf: &mut [u8], + format: &FormatFullBytes, + ) -> ImageResult<()> { + let num_channels = self.num_channels(); + let row_padding_len = match *format { + FormatFullBytes::RGB24 => (4 - (self.width as usize * 3) % 4) % 4, + _ => 0, + }; + let row_padding = &mut [0; 4][..row_padding_len]; + + self.reader.seek(SeekFrom::Start(self.data_offset))?; + + let reader = &mut self.reader; + + with_rows( + buf, + self.width, + self.height, + num_channels, + self.top_down, + |row| { + for pixel in row.chunks_mut(num_channels) { + if *format == FormatFullBytes::Format888 { + reader.read_u8()?; + } + + // Read the colour values (b, g, r). + // Reading 3 bytes and reversing them is significantly faster than reading one + // at a time. + reader.read_exact(&mut pixel[0..3])?; + pixel[0..3].reverse(); + + if *format == FormatFullBytes::RGB32 { + reader.read_u8()?; + } + + // Read the alpha channel if present + if *format == FormatFullBytes::RGBA32 { + reader.read_exact(&mut pixel[3..4])?; + } else if num_channels == 4 { + pixel[3] = 0xFF; + } + } + reader.read_exact(row_padding) + }, + )?; + + Ok(()) + } + + fn read_rle_data(&mut self, buf: &mut [u8], image_type: ImageType) -> ImageResult<()> { + // Seek to the start of the actual image data. + self.reader.seek(SeekFrom::Start(self.data_offset))?; + + let num_channels = self.num_channels(); + let p = self.palette.as_ref().unwrap(); + + // Handling deltas in the RLE scheme means that we need to manually + // iterate through rows and pixels. Even if we didn't have to handle + // deltas, we have to ensure that a single runlength doesn't straddle + // two rows. + let mut row_iter = self.rows(buf); + + while let Some(row) = row_iter.next() { + let mut pixel_iter = row.chunks_mut(num_channels); + + let mut x = 0; + loop { + let instruction = { + let control_byte = self.reader.read_u8()?; + match control_byte { + RLE_ESCAPE => { + let op = self.reader.read_u8()?; + + match op { + RLE_ESCAPE_EOL => RLEInsn::EndOfRow, + RLE_ESCAPE_EOF => RLEInsn::EndOfFile, + RLE_ESCAPE_DELTA => { + let xdelta = self.reader.read_u8()?; + let ydelta = self.reader.read_u8()?; + RLEInsn::Delta(xdelta, ydelta) + } + _ => { + let mut length = op as usize; + if self.image_type == ImageType::RLE4 { + length = (length + 1) / 2; + } + length += length & 1; + let mut buffer = vec![0; length]; + self.reader.read_exact(&mut buffer)?; + RLEInsn::Absolute(op, buffer) + } + } + } + _ => { + let palette_index = self.reader.read_u8()?; + RLEInsn::PixelRun(control_byte, palette_index) + } + } + }; + + match instruction { + RLEInsn::EndOfFile => { + pixel_iter.for_each(|p| p.fill(0)); + row_iter.for_each(|r| r.fill(0)); + return Ok(()); + } + RLEInsn::EndOfRow => { + pixel_iter.for_each(|p| p.fill(0)); + break; + } + RLEInsn::Delta(x_delta, y_delta) => { + // The msdn site on bitmap compression doesn't specify + // what happens to the values skipped when encountering + // a delta code, however IE and the windows image + // preview seems to replace them with black pixels, + // so we stick to that. + + if y_delta > 0 { + // Zero out the remainder of the current row. + pixel_iter.for_each(|p| p.fill(0)); + + // If any full rows are skipped, zero them out. + for _ in 1..y_delta { + let row = row_iter.next().ok_or(DecoderError::CorruptRleData)?; + row.fill(0); + } + + // Set the pixel iterator to the start of the next row. + pixel_iter = row_iter + .next() + .ok_or(DecoderError::CorruptRleData)? + .chunks_mut(num_channels); + + // Zero out the pixels up to the current point in the row. + for _ in 0..x { + pixel_iter + .next() + .ok_or(DecoderError::CorruptRleData)? + .fill(0); + } + } + + for _ in 0..x_delta { + let pixel = pixel_iter.next().ok_or(DecoderError::CorruptRleData)?; + pixel.fill(0); + } + x += x_delta as usize; + } + RLEInsn::Absolute(length, indices) => { + // Absolute mode cannot span rows, so if we run + // out of pixels to process, we should stop + // processing the image. + match image_type { + ImageType::RLE8 => { + if !set_8bit_pixel_run( + &mut pixel_iter, + p, + indices.iter(), + length as usize, + ) { + return Err(DecoderError::CorruptRleData.into()); + } + } + ImageType::RLE4 => { + if !set_4bit_pixel_run( + &mut pixel_iter, + p, + indices.iter(), + length as usize, + ) { + return Err(DecoderError::CorruptRleData.into()); + } + } + _ => unreachable!(), + } + x += length as usize; + } + RLEInsn::PixelRun(n_pixels, palette_index) => { + // A pixel run isn't allowed to span rows, but we + // simply continue on to the next row if we run + // out of pixels to set. + match image_type { + ImageType::RLE8 => { + if !set_8bit_pixel_run( + &mut pixel_iter, + p, + repeat(&palette_index), + n_pixels as usize, + ) { + return Err(DecoderError::CorruptRleData.into()); + } + } + ImageType::RLE4 => { + if !set_4bit_pixel_run( + &mut pixel_iter, + p, + repeat(&palette_index), + n_pixels as usize, + ) { + return Err(DecoderError::CorruptRleData.into()); + } + } + _ => unreachable!(), + } + x += n_pixels as usize; + } + } + } + } + + Ok(()) + } + + /// Read the actual data of the image. This function is deliberately not public because it + /// cannot be called multiple times without seeking back the underlying reader in between. + pub(crate) fn read_image_data(&mut self, buf: &mut [u8]) -> ImageResult<()> { + match self.image_type { + ImageType::Palette => self.read_palettized_pixel_data(buf), + ImageType::RGB16 => self.read_16_bit_pixel_data(buf, Some(&R5_G5_B5_COLOR_MASK)), + ImageType::RGB24 => self.read_full_byte_pixel_data(buf, &FormatFullBytes::RGB24), + ImageType::RGB32 => self.read_full_byte_pixel_data(buf, &FormatFullBytes::RGB32), + ImageType::RGBA32 => self.read_full_byte_pixel_data(buf, &FormatFullBytes::RGBA32), + ImageType::RLE8 => self.read_rle_data(buf, ImageType::RLE8), + ImageType::RLE4 => self.read_rle_data(buf, ImageType::RLE4), + ImageType::Bitfields16 => match self.bitfields { + Some(_) => self.read_16_bit_pixel_data(buf, None), + None => Err(DecoderError::BitfieldMasksMissing(16).into()), + }, + ImageType::Bitfields32 => match self.bitfields { + Some(R8_G8_B8_COLOR_MASK) => { + self.read_full_byte_pixel_data(buf, &FormatFullBytes::Format888) + } + Some(R8_G8_B8_A8_COLOR_MASK) => { + self.read_full_byte_pixel_data(buf, &FormatFullBytes::RGBA32) + } + Some(_) => self.read_32_bit_pixel_data(buf), + None => Err(DecoderError::BitfieldMasksMissing(32).into()), + }, + } + } +} + +/// Wrapper struct around a `Cursor<Vec<u8>>` +pub struct BmpReader<R>(Cursor<Vec<u8>>, PhantomData<R>); +impl<R> Read for BmpReader<R> { + fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> { + self.0.read(buf) + } + fn read_to_end(&mut self, buf: &mut Vec<u8>) -> io::Result<usize> { + if self.0.position() == 0 && buf.is_empty() { + mem::swap(buf, self.0.get_mut()); + Ok(buf.len()) + } else { + self.0.read_to_end(buf) + } + } +} + +impl<'a, R: 'a + Read + Seek> ImageDecoder<'a> for BmpDecoder<R> { + type Reader = BmpReader<R>; + + fn dimensions(&self) -> (u32, u32) { + (self.width as u32, self.height as u32) + } + + fn color_type(&self) -> ColorType { + if self.indexed_color { + ColorType::L8 + } else if self.add_alpha_channel { + ColorType::Rgba8 + } else { + ColorType::Rgb8 + } + } + + fn into_reader(self) -> ImageResult<Self::Reader> { + Ok(BmpReader( + Cursor::new(image::decoder_to_vec(self)?), + PhantomData, + )) + } + + fn read_image(mut self, buf: &mut [u8]) -> ImageResult<()> { + assert_eq!(u64::try_from(buf.len()), Ok(self.total_bytes())); + self.read_image_data(buf) + } +} + +impl<'a, R: 'a + Read + Seek> ImageDecoderRect<'a> for BmpDecoder<R> { + fn read_rect_with_progress<F: Fn(Progress)>( + &mut self, + x: u32, + y: u32, + width: u32, + height: u32, + buf: &mut [u8], + progress_callback: F, + ) -> ImageResult<()> { + let start = self.reader.stream_position()?; + image::load_rect( + x, + y, + width, + height, + buf, + progress_callback, + self, + |_, _| Ok(()), + |s, buf| s.read_image_data(buf), + )?; + self.reader.seek(SeekFrom::Start(start))?; + Ok(()) + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_bitfield_len() { + for len in 1..9 { + let bitfield = Bitfield { shift: 0, len }; + for i in 0..(1 << len) { + let read = bitfield.read(i); + let calc = (i as f64 / ((1 << len) - 1) as f64 * 255f64).round() as u8; + if read != calc { + println!("len:{} i:{} read:{} calc:{}", len, i, read, calc); + } + assert_eq!(read, calc); + } + } + } + + #[test] + fn read_rect() { + let f = std::fs::File::open("tests/images/bmp/images/Core_8_Bit.bmp").unwrap(); + let mut decoder = super::BmpDecoder::new(f).unwrap(); + + let mut buf: Vec<u8> = vec![0; 8 * 8 * 3]; + decoder.read_rect(0, 0, 8, 8, &mut *buf).unwrap(); + } + + #[test] + fn read_rle_too_short() { + let data = vec![ + 0x42, 0x4d, 0x04, 0xee, 0xfe, 0xff, 0xff, 0x10, 0xff, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x7c, 0x00, 0x00, 0x00, 0x0c, 0x41, 0x00, 0x00, 0x07, 0x10, 0x00, 0x00, 0x01, 0x00, + 0x04, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, + 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x21, + 0xff, 0x00, 0x66, 0x61, 0x72, 0x62, 0x66, 0x65, 0x6c, 0x64, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xff, 0xd8, 0xff, 0x00, 0x00, 0x19, 0x51, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfa, 0xff, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x00, + 0x00, 0x00, 0x00, 0x2d, 0x31, 0x31, 0x35, 0x36, 0x00, 0xff, 0x00, 0x00, 0x52, 0x3a, + 0x37, 0x30, 0x7e, 0x71, 0x63, 0x91, 0x5a, 0x04, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2d, 0x35, 0x37, 0x00, 0xff, 0x00, 0x00, 0x52, + 0x3a, 0x37, 0x30, 0x7e, 0x71, 0x63, 0x91, 0x5a, 0x04, 0x05, 0x3c, 0x00, 0x00, 0x11, + 0x00, 0x5d, 0x7a, 0x82, 0xb7, 0xca, 0x2d, 0x31, 0xff, 0xff, 0xc7, 0x95, 0x33, 0x2e, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7c, 0x00, + 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x66, 0x00, 0x4d, + 0x4d, 0x00, 0x2a, 0x00, + ]; + + let decoder = BmpDecoder::new(Cursor::new(&data)).unwrap(); + let mut buf = vec![0; usize::try_from(decoder.total_bytes()).unwrap()]; + assert!(decoder.read_image(&mut buf).is_ok()); + } + + #[test] + fn test_no_header() { + let tests = [ + "Info_R8_G8_B8.bmp", + "Info_A8_R8_G8_B8.bmp", + "Info_8_Bit.bmp", + "Info_4_Bit.bmp", + "Info_1_Bit.bmp", + ]; + + for name in &tests { + let path = format!("tests/images/bmp/images/{name}"); + let ref_img = crate::open(&path).unwrap(); + let mut data = std::fs::read(&path).unwrap(); + // skip the BITMAPFILEHEADER + let slice = &mut data[14..]; + let decoder = BmpDecoder::new_without_file_header(Cursor::new(slice)).unwrap(); + let no_hdr_img = crate::DynamicImage::from_decoder(decoder).unwrap(); + assert_eq!(ref_img, no_hdr_img); + } + } +} |