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::{DynamicRawColors, RawPixels},
try_const, 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 const fn from_slice(bytes: &'a [u8]) -> Result<Self, ParseError> {
let (_remaining, (header, color_table)) = try_const!(Header::parse(bytes));
let color_type = try_const!(ColorType::from_header(&header));
if bytes.len() < header.image_data_start {
return Err(ParseError::UnexpectedEndOfFile);
}
let (_, image_data) = bytes.split_at(header.image_data_start);
let data_length = if let crate::header::CompressionMethod::Rgb = header.compression_method {
let height = header.image_size.height as usize;
let Some(data_length) = header.bytes_per_row().checked_mul(height) else {
return Err(ParseError::UnexpectedEndOfFile);
};
data_length
} else {
header.image_data_len as usize
};
if image_data.len() < data_length {
return Err(ParseError::UnexpectedEndOfFile);
}
let (image_data, _) = image_data.split_at(data_length);
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 colors(&self) -> DynamicRawColors<'_> {
self.pixels().colors
}
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) const 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 let ChannelMasks::RGB888 = masks {
ColorType::Xrgb8888
} else {
return Err(ParseError::UnsupportedChannelMasks);
}
} else {
ColorType::Xrgb8888
}
}
})
}
}