1use embedded_graphics::{
2 geometry::Point,
3 iterator::raw::RawDataSlice,
4 pixelcolor::raw::{LittleEndian, RawU1, RawU16, RawU24, RawU32, RawU4, RawU8},
5 prelude::RawData,
6};
7
8use 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)]
23pub 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
37impl<'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)]
147pub enum ColorType {
148 Index1,
149 Index4,
150 Index8,
151 Rgb555,
152 Rgb565,
153 Rgb888,
154 Xrgb8888,
155}
156
157impl 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