use embedded_graphics::{
geometry::Point,
iterator::raw::RawDataSlice,
pixelcolor::raw::{LittleEndian, RawU1, RawU16, RawU24, RawU32, RawU4, RawU8},
prelude::RawData,
};
use crate::{
color_table::ColorTable,
header::{Bpp, Header},
raw_iter::RawPixels,
ChannelMasks, ParseError, RowOrder,
};
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
pub struct RawBmp<'a> {
header: Header,
pub(crate) color_type: ColorType,
color_table: Option<ColorTable<'a>>,
image_data: &'a [u8],
}
impl<'a> RawBmp<'a> {
pub fn from_slice(bytes: &'a [u8]) -> Result<Self, ParseError> {
let (_remaining, (header, color_table)) = Header::parse(bytes)?;
let color_type = ColorType::from_header(&header)?;
let compressed_data_length = header.image_data_len as usize;
let image_data = &bytes
.get(header.image_data_start..)
.and_then(|bytes| bytes.get(..compressed_data_length))
.ok_or(ParseError::UnexpectedEndOfFile)?;
Ok(Self {
header,
color_type,
color_table,
image_data,
})
}
pub const fn color_table(&self) -> Option<&ColorTable<'a>> {
self.color_table.as_ref()
}
pub const fn image_data(&self) -> &'a [u8] {
self.image_data
}
pub const fn header(&self) -> &Header {
&self.header
}
pub fn pixels(&self) -> RawPixels<'_> {
RawPixels::new(self)
}
pub fn pixel(&self, p: Point) -> Option<u32> {
if matches!(
self.header.compression_method,
crate::header::CompressionMethod::Rle8 | crate::header::CompressionMethod::Rle4
) {
return None;
}
let width = self.header.image_size.width as i32;
let height = self.header.image_size.height as i32;
if p.x < 0 || p.x >= width || p.y < 0 || p.y >= height {
return None;
}
let mut row_chunks = self.image_data.chunks_exact(self.header.bytes_per_row());
let row = match self.header.row_order {
RowOrder::BottomUp => row_chunks.nth_back(p.y as usize),
RowOrder::TopDown => row_chunks.nth(p.y as usize),
}?;
match self.header.bpp {
Bpp::Bits1 => RawDataSlice::<RawU1, LittleEndian>::new(row)
.into_iter()
.nth(p.x as usize)
.map(|raw| u32::from(raw.into_inner())),
Bpp::Bits4 => RawDataSlice::<RawU4, LittleEndian>::new(row)
.into_iter()
.nth(p.x as usize)
.map(|raw| u32::from(raw.into_inner())),
Bpp::Bits8 => RawDataSlice::<RawU8, LittleEndian>::new(row)
.into_iter()
.nth(p.x as usize)
.map(|raw| u32::from(raw.into_inner())),
Bpp::Bits16 => RawDataSlice::<RawU16, LittleEndian>::new(row)
.into_iter()
.nth(p.x as usize)
.map(|raw| u32::from(raw.into_inner())),
Bpp::Bits24 => RawDataSlice::<RawU24, LittleEndian>::new(row)
.into_iter()
.nth(p.x as usize)
.map(|raw| raw.into_inner()),
Bpp::Bits32 => RawDataSlice::<RawU32, LittleEndian>::new(row)
.into_iter()
.nth(p.x as usize)
.map(|raw| raw.into_inner()),
}
}
}
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
pub enum ColorType {
Index1,
Index4,
Index8,
Rgb555,
Rgb565,
Rgb888,
Xrgb8888,
}
impl ColorType {
pub(crate) fn from_header(header: &Header) -> Result<ColorType, ParseError> {
Ok(match header.bpp {
Bpp::Bits1 => ColorType::Index1,
Bpp::Bits4 => ColorType::Index4,
Bpp::Bits8 => ColorType::Index8,
Bpp::Bits16 => {
if let Some(masks) = header.channel_masks {
match masks {
ChannelMasks::RGB555 => ColorType::Rgb555,
ChannelMasks::RGB565 => ColorType::Rgb565,
_ => return Err(ParseError::UnsupportedChannelMasks),
}
} else {
ColorType::Rgb555
}
}
Bpp::Bits24 => ColorType::Rgb888,
Bpp::Bits32 => {
if let Some(masks) = header.channel_masks {
if masks == ChannelMasks::RGB888 {
ColorType::Xrgb8888
} else {
return Err(ParseError::UnsupportedChannelMasks);
}
} else {
ColorType::Xrgb8888
}
}
})
}
}