Files
rayhunter/bin/src/framebuffer.rs
T
Markus Unterwaditzer 5c5333f0c7 Remove RecordingCBM
Colorblind mode is a property of the respective display, and decision
whether to display something in colorblind mode should lie with the
display thread. The display thread already needs to know about
colorblind mode for the initial state.

In #226, there are multiple implementations of display thread, and at
least one of them is dealing with a one-bit display anyway.

Aside, I think rayhunter should send an initial DisplayState on startup,
UI threads should not assume that the device is already recording. But
this can be discussed separately.
2025-04-08 08:58:08 -07:00

114 lines
3.4 KiB
Rust

use image::{codecs::gif::GifDecoder, imageops::FilterType, AnimationDecoder, DynamicImage};
use std::{io::Cursor, time::Duration};
const FB_PATH:&str = "/dev/fb0";
#[derive(Copy, Clone)]
// TODO actually poll for this, maybe w/ fbset?
struct Dimensions {
height: u32,
width: u32,
}
#[allow(dead_code)]
#[derive(Copy, Clone)]
pub enum Color565 {
Red = 0b1111100000000000,
Green = 0b0000011111100000,
Blue = 0b0000000000011111,
White = 0b1111111111111111,
Black = 0b0000000000000000,
Cyan = 0b0000011111111111,
Yellow = 0b1111111111100000,
Pink = 0b1111010010011111,
}
pub enum DisplayState {
Recording,
Paused,
WarningDetected,
}
impl Color565 {
pub fn from_display_state(state: DisplayState, colorblind: bool) -> Self {
match state {
DisplayState::Paused => Color565::White,
DisplayState::Recording => if colorblind {
Color565::Green
} else {
Color565::Blue
},
DisplayState::WarningDetected => Color565::Red,
}
}
}
#[derive(Copy, Clone)]
pub struct Framebuffer<'a> {
dimensions: Dimensions,
path: &'a str,
}
impl Framebuffer<'_>{
pub const fn new() -> Self {
Framebuffer{
dimensions: Dimensions{height: 128, width: 128},
path: FB_PATH,
}
}
fn write(&mut self, img: DynamicImage) {
let mut width = img.width();
let mut height = img.height();
let resized_img: DynamicImage;
if height > self.dimensions.height ||
width > self.dimensions.width {
resized_img = img.resize( self.dimensions.width, self.dimensions.height, FilterType::CatmullRom);
width = self.dimensions.width.min(resized_img.width());
height = self.dimensions.height.min(resized_img.height());
} else {
resized_img = img;
}
let img_rgba8 = resized_img.as_rgba8().unwrap();
let mut buf = Vec::new();
for y in 0..height {
for x in 0..width {
let px = img_rgba8.get_pixel(x, y);
let mut rgb565: u16 = (px[0] as u16 & 0b11111000) << 8;
rgb565 |= (px[1] as u16 & 0b11111100) << 3;
rgb565 |= (px[2] as u16) >> 3;
buf.extend(rgb565.to_le_bytes());
}
}
std::fs::write(self.path, &buf).unwrap();
}
pub fn draw_gif(&mut self, img_buffer: &[u8]) {
// this is dumb and i'm sure there's a better way to loop this
let cursor = Cursor::new(img_buffer);
let decoder = GifDecoder::new(cursor).unwrap();
for maybe_frame in decoder.into_frames() {
let frame = maybe_frame.unwrap();
let (numerator, _) = frame.delay().numer_denom_ms();
let img = DynamicImage::from(frame.into_buffer());
self.write(img);
std::thread::sleep(Duration::from_millis(numerator as u64));
}
}
pub fn draw_img(&mut self, img_buffer: &[u8]) {
let img = image::load_from_memory(img_buffer).unwrap();
self.write(img);
}
pub fn draw_line(&mut self, color: Color565, height: u32){
let px_num= height * self.dimensions.width;
let color: u16 = color as u16;
let mut buffer: Vec<u8> = Vec::new();
for _ in 0..px_num {
buffer.extend(color.to_le_bytes());
}
std::fs::write(self.path, &buffer).unwrap();
}
}