use embedded_graphics::prelude::*;
use crate::{
color_table::ColorTable,
parser::{le_u16, le_u32, take, take_slice},
ParseError,
};
mod dib_header;
use dib_header::DibHeader;
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
#[non_exhaustive]
pub enum Bpp {
Bits1,
Bits4,
Bits8,
Bits16,
Bits24,
Bits32,
}
impl Bpp {
const fn new(value: u16) -> Result<Self, ParseError> {
Ok(match value {
1 => Self::Bits1,
4 => Self::Bits4,
8 => Self::Bits8,
16 => Self::Bits16,
24 => Self::Bits24,
32 => Self::Bits32,
_ => return Err(ParseError::UnsupportedBpp(value)),
})
}
fn parse(input: &[u8]) -> Result<(&[u8], Self), ParseError> {
le_u16(input).and_then(|(input, value)| Ok((input, Self::new(value)?)))
}
pub const fn bits(self) -> u16 {
match self {
Self::Bits1 => 1,
Self::Bits4 => 4,
Self::Bits8 => 8,
Self::Bits16 => 16,
Self::Bits24 => 24,
Self::Bits32 => 32,
}
}
}
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
#[non_exhaustive]
pub enum RowOrder {
BottomUp,
TopDown,
}
impl Default for RowOrder {
fn default() -> Self {
Self::BottomUp
}
}
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
pub struct Header {
pub file_size: u32,
pub image_data_start: usize,
pub image_size: Size,
pub bpp: Bpp,
pub image_data_len: u32,
pub channel_masks: Option<ChannelMasks>,
pub row_order: RowOrder,
}
impl Header {
pub(crate) fn parse(
input: &[u8],
) -> Result<(&[u8], (Header, Option<ColorTable<'_>>)), ParseError> {
let (input, magic) = take::<2>(input)?;
if &magic != b"BM" {
return Err(ParseError::InvalidFileSignature(magic));
}
let (input, file_size) = le_u32(input)?;
let (input, _reserved_1) = le_u16(input)?;
let (input, _reserved_2) = le_u16(input)?;
let (input, image_data_start) = le_u32(input)?;
let (input, dib_header) = DibHeader::parse(input)?;
let (input, color_table) = if dib_header.color_table_num_entries > 0 {
let (input, table) =
take_slice(input, dib_header.color_table_num_entries as usize * 4)?;
(input, Some(ColorTable::new(table)))
} else {
(input, None)
};
Ok((
input,
(
Header {
file_size,
image_data_start: image_data_start as usize,
image_size: dib_header.image_size,
image_data_len: dib_header.image_data_len,
bpp: dib_header.bpp,
channel_masks: dib_header.channel_masks,
row_order: dib_header.row_order,
},
color_table,
),
))
}
pub(crate) fn bytes_per_row(&self) -> usize {
let bits_per_row = self.image_size.width as usize * usize::from(self.bpp.bits());
(bits_per_row + 31) / 32 * (32 / 8)
}
}
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)]
pub struct ChannelMasks {
pub red: u32,
pub green: u32,
pub blue: u32,
pub alpha: u32,
}
impl ChannelMasks {
pub const RGB555: Self = Self {
red: 0b11111_00000_00000,
green: 0b00000_11111_00000,
blue: 0b00000_00000_11111,
alpha: 0,
};
pub const RGB565: Self = Self {
red: 0b11111_000000_00000,
green: 0b00000_111111_00000,
blue: 0b00000_000000_11111,
alpha: 0,
};
pub const RGB888: Self = Self {
red: 0xFF0000,
green: 0x00FF00,
blue: 0x0000FF,
alpha: 0,
};
}
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
pub enum CompressionMethod {
Rgb,
Bitfields,
}
impl CompressionMethod {
const fn new(value: u32) -> Result<Self, ParseError> {
Ok(match value {
0 => Self::Rgb,
3 => Self::Bitfields,
_ => return Err(ParseError::UnsupportedCompressionMethod(value)),
})
}
fn parse(input: &[u8]) -> Result<(&[u8], Self), ParseError> {
le_u32(input).and_then(|(input, value)| Ok((input, Self::new(value)?)))
}
}