| 1 | use embedded_graphics::{ |
| 2 | geometry::Point, |
| 3 | iterator::raw::RawDataSlice, |
| 4 | pixelcolor::raw::{LittleEndian, RawU1, RawU16, RawU24, RawU32, RawU4, RawU8}, |
| 5 | prelude::RawData, |
| 6 | }; |
| 7 | |
| 8 | use crate::{ |
| 9 | color_table::ColorTable, |
| 10 | header::{Bpp, Header}, |
| 11 | raw_iter::RawPixels, |
| 12 | ChannelMasks, ParseError, RowOrder, |
| 13 | }; |
| 14 | |
| 15 | /// Low-level access to BMP image data. |
| 16 | /// |
| 17 | /// This struct can be used to access the image data in a BMP file at a lower level than with the |
| 18 | /// [`Bmp`](crate::Bmp) struct. It doesn't do automatic color conversion and doesn't apply the color |
| 19 | /// table, if it is present in the BMP file. For images with a color table the iterator returned by |
| 20 | /// [`pixels`](Self::pixels) will instead return the color indices, that can be looked up manually |
| 21 | /// using the [`ColorTable`] struct. |
| 22 | #[derive (Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] |
| 23 | pub struct RawBmp<'a> { |
| 24 | /// Image header. |
| 25 | header: Header, |
| 26 | |
| 27 | /// Color type. |
| 28 | pub(crate) color_type: ColorType, |
| 29 | |
| 30 | /// Color table for color mapped images. |
| 31 | color_table: Option<ColorTable<'a>>, |
| 32 | |
| 33 | /// Image data. |
| 34 | image_data: &'a [u8], |
| 35 | } |
| 36 | |
| 37 | impl<'a> RawBmp<'a> { |
| 38 | /// Create a bitmap object from a byte slice. |
| 39 | /// |
| 40 | /// The created object keeps a shared reference to the input and does not dynamically allocate |
| 41 | /// memory. |
| 42 | pub fn from_slice(bytes: &'a [u8]) -> Result<Self, ParseError> { |
| 43 | let (_remaining, (header, color_table)) = Header::parse(bytes)?; |
| 44 | |
| 45 | let color_type = ColorType::from_header(&header)?; |
| 46 | |
| 47 | let height = header |
| 48 | .image_size |
| 49 | .height |
| 50 | .try_into() |
| 51 | .map_err(|_| ParseError::InvalidImageDimensions)?; |
| 52 | |
| 53 | let data_length = header |
| 54 | .bytes_per_row() |
| 55 | .checked_mul(height) |
| 56 | .ok_or(ParseError::InvalidImageDimensions)?; |
| 57 | |
| 58 | // The `get` is split into two calls to prevent an possible integer overflow. |
| 59 | let image_data = &bytes |
| 60 | .get(header.image_data_start..) |
| 61 | .and_then(|bytes| bytes.get(..data_length)) |
| 62 | .ok_or(ParseError::UnexpectedEndOfFile)?; |
| 63 | |
| 64 | Ok(Self { |
| 65 | header, |
| 66 | color_type, |
| 67 | color_table, |
| 68 | image_data, |
| 69 | }) |
| 70 | } |
| 71 | |
| 72 | /// Returns the color table associated with the image. |
| 73 | pub const fn color_table(&self) -> Option<&ColorTable<'a>> { |
| 74 | self.color_table.as_ref() |
| 75 | } |
| 76 | |
| 77 | /// Returns a slice containing the raw image data. |
| 78 | pub const fn image_data(&self) -> &'a [u8] { |
| 79 | self.image_data |
| 80 | } |
| 81 | |
| 82 | /// Returns a reference to the BMP header. |
| 83 | pub const fn header(&self) -> &Header { |
| 84 | &self.header |
| 85 | } |
| 86 | |
| 87 | /// Returns an iterator over the raw pixels in the image. |
| 88 | /// |
| 89 | /// The iterator returns the raw pixel colors as [`u32`] values. To automatically convert the |
| 90 | /// raw values into [`embedded_graphics`] color types use [`Bmp::pixels`](crate::Bmp::pixels) |
| 91 | /// instead. |
| 92 | pub fn pixels(&self) -> RawPixels<'_> { |
| 93 | RawPixels::new(self) |
| 94 | } |
| 95 | |
| 96 | /// Returns the raw color of a pixel. |
| 97 | /// |
| 98 | /// Returns `None` if `p` is outside the image bounding box. Note that this function doesn't |
| 99 | /// apply a color map, if the image contains one. |
| 100 | pub fn pixel(&self, p: Point) -> Option<u32> { |
| 101 | let width = self.header.image_size.width as i32; |
| 102 | let height = self.header.image_size.height as i32; |
| 103 | |
| 104 | if p.x < 0 || p.x >= width || p.y < 0 || p.y >= height { |
| 105 | return None; |
| 106 | } |
| 107 | |
| 108 | // The specialized implementations of `Iterator::nth` for `Chunks` and |
| 109 | // `RawDataSlice::IntoIter` are `O(1)`, which also makes this method `O(1)`. |
| 110 | |
| 111 | let mut row_chunks = self.image_data.chunks_exact(self.header.bytes_per_row()); |
| 112 | let row = match self.header.row_order { |
| 113 | RowOrder::BottomUp => row_chunks.nth_back(p.y as usize), |
| 114 | RowOrder::TopDown => row_chunks.nth(p.y as usize), |
| 115 | }?; |
| 116 | |
| 117 | match self.header.bpp { |
| 118 | Bpp::Bits1 => RawDataSlice::<RawU1, LittleEndian>::new(row) |
| 119 | .into_iter() |
| 120 | .nth(p.x as usize) |
| 121 | .map(|raw| u32::from(raw.into_inner())), |
| 122 | Bpp::Bits4 => RawDataSlice::<RawU4, LittleEndian>::new(row) |
| 123 | .into_iter() |
| 124 | .nth(p.x as usize) |
| 125 | .map(|raw| u32::from(raw.into_inner())), |
| 126 | Bpp::Bits8 => RawDataSlice::<RawU8, LittleEndian>::new(row) |
| 127 | .into_iter() |
| 128 | .nth(p.x as usize) |
| 129 | .map(|raw| u32::from(raw.into_inner())), |
| 130 | Bpp::Bits16 => RawDataSlice::<RawU16, LittleEndian>::new(row) |
| 131 | .into_iter() |
| 132 | .nth(p.x as usize) |
| 133 | .map(|raw| u32::from(raw.into_inner())), |
| 134 | Bpp::Bits24 => RawDataSlice::<RawU24, LittleEndian>::new(row) |
| 135 | .into_iter() |
| 136 | .nth(p.x as usize) |
| 137 | .map(|raw| raw.into_inner()), |
| 138 | Bpp::Bits32 => RawDataSlice::<RawU32, LittleEndian>::new(row) |
| 139 | .into_iter() |
| 140 | .nth(p.x as usize) |
| 141 | .map(|raw| raw.into_inner()), |
| 142 | } |
| 143 | } |
| 144 | } |
| 145 | |
| 146 | #[derive (Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] |
| 147 | pub enum ColorType { |
| 148 | Index1, |
| 149 | Index4, |
| 150 | Index8, |
| 151 | Rgb555, |
| 152 | Rgb565, |
| 153 | Rgb888, |
| 154 | Xrgb8888, |
| 155 | } |
| 156 | |
| 157 | impl ColorType { |
| 158 | pub(crate) fn from_header(header: &Header) -> Result<ColorType, ParseError> { |
| 159 | Ok(match header.bpp { |
| 160 | Bpp::Bits1 => ColorType::Index1, |
| 161 | Bpp::Bits4 => ColorType::Index4, |
| 162 | Bpp::Bits8 => ColorType::Index8, |
| 163 | Bpp::Bits16 => { |
| 164 | if let Some(masks) = header.channel_masks { |
| 165 | match masks { |
| 166 | ChannelMasks::RGB555 => ColorType::Rgb555, |
| 167 | ChannelMasks::RGB565 => ColorType::Rgb565, |
| 168 | _ => return Err(ParseError::UnsupportedChannelMasks), |
| 169 | } |
| 170 | } else { |
| 171 | // According to the GDI docs the default 16 bpp color format is Rgb555 if no |
| 172 | // color masks are defined: |
| 173 | // https://docs.microsoft.com/en-us/windows/win32/api/wingdi/ns-wingdi-bitmapinfoheader |
| 174 | ColorType::Rgb555 |
| 175 | } |
| 176 | } |
| 177 | Bpp::Bits24 => ColorType::Rgb888, |
| 178 | Bpp::Bits32 => { |
| 179 | if let Some(masks) = header.channel_masks { |
| 180 | if masks == ChannelMasks::RGB888 { |
| 181 | ColorType::Xrgb8888 |
| 182 | } else { |
| 183 | return Err(ParseError::UnsupportedChannelMasks); |
| 184 | } |
| 185 | } else { |
| 186 | ColorType::Xrgb8888 |
| 187 | } |
| 188 | } |
| 189 | }) |
| 190 | } |
| 191 | } |
| 192 | |