use crate::dms::multi::{Color, ColorClassic, ColorCtx, ColorScheme};
use crate::dms::oer::Oer;
use crc::Crc;
use fstr::FStr;
use log::debug;
use pix::{
Raster,
el::Pixel,
rgb::{SRgb8, SRgba8},
};
const CRC: Crc<u16> = Crc::<u16>::new(&crc::CRC_16_IBM_SDLC);
#[derive(Debug, thiserror::Error)]
pub enum GraphicError {
#[error("Invalid number")]
InvalidNumber,
#[error("Duplicate number")]
DuplicateNumber,
#[error("Invalid height")]
InvalidHeight,
#[error("Invalid width")]
InvalidWidth,
#[error("Invalid bitmap")]
InvalidBitmap,
#[error("Invalid transparent color")]
InvalidTransparentColor,
#[error("Too big")]
TooBig,
}
#[derive(Clone, Default)]
pub struct Graphic {
pub number: u8,
pub name: FStr<64>,
pub height: u8,
pub width: u16,
pub gtype: ColorScheme,
pub transparent_color: Option<Color>,
pub bitmap: Vec<u8>,
}
#[derive(Clone)]
pub struct GraphicTable<const G: usize = 32> {
graphics: [Graphic; G],
}
impl Graphic {
fn is_bitmap_valid(&self) -> bool {
let pix = usize::from(self.height) * usize::from(self.width);
let len = match self.gtype {
ColorScheme::Monochrome1Bit => pix.div_ceil(8),
ColorScheme::Color24Bit => pix * 3,
_ => pix,
};
len == self.bitmap.len()
}
fn is_transparent_color_valid(&self) -> bool {
matches!(
(self.gtype, self.transparent_color),
(_, None)
| (ColorScheme::Monochrome1Bit, Some(Color::Legacy(_)))
| (ColorScheme::Monochrome8Bit, Some(Color::Legacy(_)))
| (ColorScheme::ColorClassic, Some(Color::Legacy(_)))
| (ColorScheme::Color24Bit, Some(Color::Rgb(_, _, _)))
)
}
pub fn validate(
&self,
width: u16,
height: u16,
) -> Result<(), GraphicError> {
if self.number < 1 {
Err(GraphicError::InvalidNumber)
} else if self.height < 1 || u16::from(self.height) > height {
Err(GraphicError::InvalidHeight)
} else if self.width < 1 || self.width > width {
Err(GraphicError::InvalidWidth)
} else if !self.is_bitmap_valid() {
Err(GraphicError::InvalidBitmap)
} else if !self.is_transparent_color_valid() {
Err(GraphicError::InvalidTransparentColor)
} else {
Ok(())
}
}
pub fn version_id(&self) -> u16 {
let mut oer = Oer::from(Vec::with_capacity(256));
oer.u8(self.number);
oer.u16(self.height.into());
oer.u16(self.width);
oer.u8(self.gtype as u8);
oer.u8(match self.transparent_color {
Some(_) => 1,
None => 0,
});
match self.transparent_color {
Some(Color::Rgb(r, g, b)) => {
oer.u8(r);
oer.u8(g);
oer.u8(b);
}
Some(Color::Legacy(c)) => {
oer.u8(c);
oer.u8(0);
oer.u8(0);
}
_ => {
oer.u8(0);
oer.u8(0);
oer.u8(0);
}
}
oer.octet_str(&self.bitmap);
let buf = Vec::from(oer);
u16::from_be(CRC.checksum(&buf))
}
pub fn to_raster(&self) -> Raster<SRgba8> {
let fg = match self.gtype {
ColorScheme::Monochrome1Bit | ColorScheme::Monochrome8Bit => {
ColorClassic::White.rgb()
}
_ => ColorClassic::Amber.rgb(),
};
let ctx = ColorCtx::new(
ColorScheme::Color24Bit,
fg,
ColorClassic::Black.rgb(),
);
let width = self.width.into();
let height = self.height.into();
let mut raster =
Raster::with_clear(self.width.into(), self.height.into());
for y in 0..height {
for x in 0..width {
if let Some(clr) = self.pixel_fn(x, y, &ctx) {
*raster.pixel_mut(x, y) = clr.convert();
}
}
}
raster
}
pub(crate) fn render_graphic(
&self,
page: &mut Raster<SRgb8>,
x: i32,
y: i32,
ctx: &ColorCtx,
) -> Result<(), GraphicError> {
debug_assert!(x > 0);
debug_assert!(y > 0);
let x = x - 1;
let y = y - 1;
let w = i32::from(self.width);
let h = i32::from(self.height);
let width = i32::try_from(page.width()).unwrap();
let height = i32::try_from(page.height()).unwrap();
if x + w > width || y + h > height {
return Err(GraphicError::TooBig);
}
for yy in 0..h {
for xx in 0..w {
if let Some(clr) = self.pixel_fn(xx, yy, ctx) {
*page.pixel_mut(x + xx, y + yy) = clr;
}
}
}
Ok(())
}
fn pixel_fn(&self, x: i32, y: i32, ctx: &ColorCtx) -> Option<SRgb8> {
match self.gtype {
ColorScheme::Monochrome1Bit => self.pixel_1(x, y, ctx),
ColorScheme::Monochrome8Bit | ColorScheme::ColorClassic => {
self.pixel_8(x, y, ctx)
}
ColorScheme::Color24Bit => self.pixel_24(x, y),
}
}
fn pixel_1(&self, x: i32, y: i32, ctx: &ColorCtx) -> Option<SRgb8> {
let offset = y * i32::from(self.width) + x;
let by = offset as usize / 8;
let bi = 7 - (offset & 7);
let lit = ((self.bitmap[by] >> bi) & 1) != 0;
match (lit, self.transparent_color) {
(false, Some(Color::Legacy(0))) => None,
(true, Some(Color::Legacy(1))) => None,
(false, _) => {
let (red, green, blue) = ctx.rgb(ctx.background())?;
Some(SRgb8::new(red, green, blue))
}
(true, _) => {
let (red, green, blue) = ctx.rgb(ctx.foreground())?;
Some(SRgb8::new(red, green, blue))
}
}
}
fn pixel_8(&self, x: i32, y: i32, ctx: &ColorCtx) -> Option<SRgb8> {
let offset = y * i32::from(self.width) + x;
let v = self.bitmap[offset as usize];
if let Some(Color::Legacy(c)) = self.transparent_color
&& v == c
{
return None;
}
match ctx.rgb(Color::Legacy(v)) {
Some((red, green, blue)) => Some(SRgb8::new(red, green, blue)),
None => {
debug!("pixel_8 -- Bad color {}", v);
None
}
}
}
fn pixel_24(&self, x: i32, y: i32) -> Option<SRgb8> {
let offset = 3 * (y * i32::from(self.width) + x) as usize;
let blue = self.bitmap[offset];
let green = self.bitmap[offset + 1];
let red = self.bitmap[offset + 2];
if let Some(Color::Rgb(r, g, b)) = self.transparent_color
&& red == r
&& green == g
&& blue == b
{
return None;
}
Some(SRgb8::new(red, green, blue))
}
}
impl<const G: usize> Default for GraphicTable<G> {
fn default() -> Self {
let graphics: [Graphic; G] = [(); G].map(|_| Graphic::default());
GraphicTable { graphics }
}
}
impl<const G: usize> GraphicTable<G> {
pub(crate) fn retain(&mut self, width: u16, height: u16) {
for graphic in &mut self.graphics {
if graphic.width > width || u16::from(graphic.height) > height {
*graphic = Graphic::default();
}
}
}
pub fn validate(
&self,
width: u16,
height: u16,
) -> Result<(), GraphicError> {
for graphic in &self.graphics {
if graphic.number > 0 {
graphic.validate(width, height)?;
}
}
self.validate_graphic_numbers()
}
fn validate_graphic_numbers(&self) -> Result<(), GraphicError> {
for i in 1..self.graphics.len() {
let num = self.graphics[i - 1].number;
if num > 0 && self.graphics[i..].iter().any(|g| g.number == num) {
return Err(GraphicError::DuplicateNumber);
}
}
Ok(())
}
pub fn graphic(&self, gnum: u8) -> Option<&Graphic> {
self.graphics.iter().find(|g| g.number == gnum)
}
pub fn graphic_mut(&mut self, gnum: u8) -> Option<&mut Graphic> {
self.graphics.iter_mut().find(|g| g.number == gnum)
}
}
#[cfg(test)]
mod test {
use super::*;
fn graphic_table() -> GraphicTable<3> {
let mut graphics = GraphicTable::default();
let g = graphics.graphic_mut(0).unwrap();
*g = Graphic {
name: FStr::from_str_lossy("Example 2", b'\0'),
number: 4,
height: 6,
width: 10,
gtype: ColorScheme::Monochrome1Bit,
transparent_color: None,
bitmap: vec![0x84, 0x92, 0x63, 0x08, 0xC2, 0x48, 0xA1, 0x70],
};
let g = graphics.graphic_mut(0).unwrap();
*g = Graphic {
name: FStr::from_str_lossy("Example 3", b'\0'),
number: 5,
height: 4,
width: 4,
gtype: ColorScheme::ColorClassic,
transparent_color: Some(Color::Legacy(ColorClassic::White.into())),
bitmap: vec![1, 1, 1, 1, 7, 7, 1, 7, 7, 1, 7, 7, 1, 1, 1, 1],
};
let g = graphics.graphic_mut(0).unwrap();
*g = Graphic {
name: FStr::from_str_lossy("Example 4", b'\0'),
number: 7,
height: 2,
width: 2,
gtype: ColorScheme::Color24Bit,
transparent_color: Some(Color::Rgb(0, 0xFF, 0)),
bitmap: vec![
0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
0x00, 0xFF,
],
};
graphics.validate(50, 50).unwrap();
graphics
}
#[test]
fn graphic_version_id() {
let graphics = graphic_table();
let graphic = graphics.graphic(4).unwrap();
assert_eq!(graphic.version_id(), 0xBFF5);
let graphic = graphics.graphic(5).unwrap();
assert_eq!(graphic.version_id(), 0x8FE0);
let graphic = graphics.graphic(7).unwrap();
assert_eq!(graphic.version_id(), 0x078D);
}
}