use std::{fmt, result::Result};
#[cfg(feature = "png-decode")]
use {png::DecodingError, std::fs::File};
#[cfg(feature = "png-decode")]
pub enum PixelFormat {
Zrgb,
Rgba,
}
pub enum Mask<'a> {
Color(u32),
Bits(&'a Vec<bool>),
None
}
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum BlitError {
BlittingBeyondBoundaries,
}
impl fmt::Display for BlitError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("tzfile error: ")?;
f.write_str(match self {
BlitError::BlittingBeyondBoundaries => "You are blitting outside the framebuffer !",
})
}
}
pub struct Bitmap<'a> {
pub w: usize,
pub h: usize,
pub x: isize,
pub y: isize,
pub pixels: &'a Vec<u32>,
}
pub struct Framebuffer<'a> {
pub width: usize,
pub height: usize,
pub pixels: &'a mut Vec<u32>,
}
struct ClippedCoords {
ux: usize,
uy: usize,
x_end: usize,
y_end: usize,
c: usize,
src_pixel_skip: usize,
}
impl Bitmap<'_> {
pub fn blit_mask(&self, fb: &mut Framebuffer, mask: Mask) {
let mut cr = match self.compute_clipping(fb) {
Some(c) => c,
None => return
};
for inc_y in 0..cr.y_end {
let x_offset: usize = inc_y * fb.width;
let y_offset: usize = cr.uy * fb.width;
for inc_x in 0..cr.x_end {
match mask {
Mask::Color(c) => if self.pixels[cr.c] != c { fb.pixels[inc_x + x_offset + cr.ux + y_offset] = self.pixels[cr.c] },
Mask::Bits(b) => if b[cr.c] { fb.pixels[inc_x + x_offset + cr.ux + y_offset] = self.pixels[cr.c] },
Mask::None => fb.pixels[inc_x + x_offset + cr.ux + y_offset] = self.pixels[cr.c]
}
cr.c += 1;
}
cr.c += cr.src_pixel_skip;
}
}
pub fn blit(&self, fb: &mut Framebuffer) {
self.blit_mask(fb, Mask::None);
}
pub fn blit_part(&self, fb: &mut Framebuffer, start_offset: usize, w: usize, h: usize) {
let mut c = 0 + start_offset;
let mut t_pixels = vec![0; w * h];
for inc_y in 0..h {
for inc_x in 0..w {
t_pixels[inc_x + inc_y * w ] = self.pixels[c];
c += 1;
}
c += self.w - w;
}
let tx = self.x;
let ty = self.y;
let tw = w;
let th = h;
let t = Bitmap { x: tx, y: ty, w: tw, h: th, pixels: &t_pixels};
t.blit(fb);
}
fn compute_clipping(&self, fb: &Framebuffer) -> Option<ClippedCoords> {
let ux = if self.x > 0 { self.x as usize } else { 0 };
let uy = if self.y > 0 { self.y as usize } else { 0 };
let cropped_x = self.x.abs() as usize;
let cropped_y = self.y.abs() as usize;
let r = if ux + self.w <= fb.width && uy + self.h < fb.height && self.x >= 0 && self.y < 0 && (self.y + self.h as isize) > 0 {
Some(ClippedCoords {
x_end: self.w,
y_end: self.h - cropped_y,
src_pixel_skip: 0,
c: cropped_y * self.w,
ux: ux,
uy: uy,
})
}
else if self.x < 0 && self.y < 0 && self.x + self.w as isize > 0 && self.y + self.h as isize > 0 {
Some(ClippedCoords {
x_end: self.w - cropped_x,
y_end: self.h - cropped_y,
src_pixel_skip: cropped_x,
c: cropped_y * self.w + cropped_x,
ux: ux,
uy: uy,
})
}
else if ux + self.w > fb.width && ux < fb.width && self.y < 0 && self.y + self.h as isize > 0 {
Some(ClippedCoords {
x_end: fb.width - ux,
y_end: self.h - cropped_y,
src_pixel_skip: self.w - (fb.width - ux),
c: cropped_y * self.w,
ux: ux,
uy: uy,
})
}
else if uy + self.h > fb.height && self.x < 0 && self.x + self.w as isize > 0 && uy < fb.height {
Some(ClippedCoords {
x_end: self.w - cropped_x,
y_end: fb.height - uy,
src_pixel_skip: cropped_x,
c: cropped_x,
ux: ux,
uy: uy,
})
}
else if ux + self.w > fb.width && uy + self.h > fb.height && ux < fb.width && uy < fb.height {
Some(ClippedCoords {
x_end: fb.width - ux,
y_end: fb.height - uy,
src_pixel_skip: self.w - (fb.width - ux),
c: 0,
ux: ux,
uy: uy,
})
}
else if ux + self.w < fb.width && self.x + self.w as isize > 0 && uy + self.h > fb.height && uy < fb.height {
Some(ClippedCoords {
x_end: self.w,
y_end: fb.height - uy,
src_pixel_skip: 0,
c: 0,
ux: ux,
uy: uy,
})
}
else if self.x < 0 && self.x + self.w as isize > 0 && self.y > 0 && uy < fb.height {
Some(ClippedCoords {
x_end: self.w - cropped_x,
y_end: self.h,
src_pixel_skip: cropped_x,
c: cropped_x,
ux: ux,
uy: uy,
})
}
else if ux + self.w > fb.width && self.y >= 0 && ux <= fb.width && uy + self.h < fb.height {
Some(ClippedCoords {
x_end: fb.width - ux,
y_end: self.h,
src_pixel_skip: self.w - (fb.width - ux),
c: 0,
ux: ux,
uy: uy,
})
}
else if ux > fb.width || uy > fb.height || (self.x + self.w as isize) < 0 || (self.y + self.h as isize) < 0 {
None
}
else {
Some(ClippedCoords {
x_end: self.w,
y_end: self.h,
src_pixel_skip: 0,
c: 0,
ux: ux,
uy: uy,
})
};
r
}
}
impl Framebuffer<'_> {
pub fn clear_area(
&mut self,
w: usize,
h: usize,
x: usize,
y: usize,
clear_color: u32,
) -> Result<(), BlitError> {
if ((x + w) * (y + h)) > self.pixels.len() {
return Err(BlitError::BlittingBeyondBoundaries);
};
for inc_y in 0..h {
let x_offset: usize = inc_y * self.width;
let y_offset: usize = y * self.width;
for inc_x in 0..w {
self.pixels[inc_x + x_offset + x + y_offset] = clear_color;
}
}
Ok(())
}
pub fn clear(&mut self, clear_color: u32) {
for inc_x in 0..self.width * self.height {
self.pixels[inc_x] = clear_color;
}
}
pub fn draw_pixel(&mut self, x: usize, y: usize, color: u32) -> Result<(), BlitError> {
if x > self.width || y > self.height {
return Err(BlitError::BlittingBeyondBoundaries);
};
self.pixels[x + y * self.width] = color;
Ok(())
}
pub fn draw_fatpixel(
&mut self,
x: usize,
y: usize,
size: usize,
color: u32,
) -> Result<(), BlitError> {
if x > self.width - size || y > self.height - size {
return Err(BlitError::BlittingBeyondBoundaries);
};
self.clear_area(size, size, x, y, color)?;
Ok(())
}
}
#[cfg(feature = "png-decode")]
pub fn from_png_file(
pngfile: &str,
pxfmt: PixelFormat,
) -> Result<(usize, usize, Vec<u32>), DecodingError> {
let shift: u32 = match pxfmt {
PixelFormat::Zrgb => 0,
PixelFormat::Rgba => 8,
};
let decoder = png::Decoder::new(File::open(&pngfile)?);
let (info, mut reader) = decoder.read_info()?;
let mut buf = vec![0; info.buffer_size()];
reader.next_frame(&mut buf)?;
let u32_buffer: Vec<u32> = buf
.chunks(3)
.map(|v| ((v[0] as u32) << 16) | ((v[1] as u32) << 8) | v[2] as u32)
.map(|x| x << shift)
.collect();
Ok((info.width as usize, info.height as usize, u32_buffer))
}
#[cfg(test)]
mod tests {
#[test]
fn it_works() {
assert_eq!(2 + 2, 4);
}
}