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 | |