Files
killpaper/src/display/epaper.rs
2025-09-05 00:47:46 +02:00

312 lines
9.3 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
use embedded_graphics::{
image::{Image, ImageRaw},
mono_font::MonoTextStyleBuilder,
prelude::*,
text::{Baseline, Text, TextStyleBuilder},
};
use crate::epd::epd7in5_v2::Display7in5;
use crate::epd::epd7in5_v2::Epd7in5;
use crate::epd::graphics::Display;
use crate::epd::traits::*;
use crate::{display::KillDisplay, epd::color::Color};
use linux_embedded_hal::{
Delay, SPIError, SpidevDevice,
spidev::{self, SpidevOptions},
};
use embedded_hal::digital::OutputPin;
use embedded_hal::digital::PinState;
use crate::killinfo::KillInfo;
use image::RgbImage;
pub struct EPaper {
spi: SpidevDevice,
epd: Epd7in5<
SpidevDevice,
gpiocdev_embedded_hal::InputPin,
gpiocdev_embedded_hal::OutputPin,
gpiocdev_embedded_hal::OutputPin,
Delay,
>,
display: Display<800, 480, false, 48000, Color>,
delay: Delay,
}
impl EPaper {
pub fn new() -> Result<EPaper, SPIError> {
let mut spi = SpidevDevice::open("/dev/spidev0.0").expect("spidev directory");
let options = SpidevOptions::new()
.bits_per_word(8)
.max_speed_hz(10_000_000)
.mode(spidev::SpiModeFlags::SPI_MODE_0)
.build();
spi.configure(&options).expect("spi configuration");
// setup display pins
let mut cs =
gpiocdev_embedded_hal::OutputPin::new("/dev/gpiochip0", 26, PinState::High).unwrap();
cs.set_high().unwrap();
let busy = gpiocdev_embedded_hal::InputPin::new("/dev/gpiochip0", 24).unwrap();
let dc =
gpiocdev_embedded_hal::OutputPin::new("/dev/gpiochip0", 25, PinState::High).unwrap();
let rst =
gpiocdev_embedded_hal::OutputPin::new("/dev/gpiochip0", 17, PinState::High).unwrap();
let mut delay = Delay {};
println!("START");
let epd7in5 = Epd7in5::new(&mut spi, busy, dc, rst, &mut delay, None).expect("epd new");
let display = Display7in5::default();
println!("Device successfully initialized!");
return Ok(EPaper {
spi,
epd: epd7in5,
display,
delay: Delay {},
});
}
pub fn sleep(&mut self) -> Result<(), SPIError> {
self.epd.sleep(&mut self.spi, &mut self.delay)?;
Ok(())
}
}
impl KillDisplay for EPaper {
fn flush(&mut self) {
let _ = self.epd.update_and_display_frame(
&mut self.spi,
self.display.buffer(),
&mut self.delay,
);
}
fn clear(&mut self, on: bool) {
let color = if on { Color::Black } else { Color::White };
self.display.clear(color).ok();
}
fn draw_text(&mut self, x: i32, y: i32, text: &str) {
let style = MonoTextStyleBuilder::new()
.font(&embedded_graphics::mono_font::ascii::FONT_8X13)
.text_color(Color::White)
.background_color(Color::Black)
.build();
let text_style = TextStyleBuilder::new().baseline(Baseline::Top).build();
let _ = Text::with_text_style(text, Point::new(x, y), style, text_style)
.draw(&mut self.display);
}
fn draw_kill_info(&mut self, kill: &KillInfo, x: i32, y: i32) {
let mut current_y = y;
// Draw Alliance logo (64x64) if present
if let Some(alliance) = &kill.victim.alliance {
let bw_vec = rgb_to_bw_dithered(&alliance.logo);
let logo_raw = ImageRaw::<Color>::new(bw_vec.as_slice(), alliance.logo.width() as u32);
Image::new(&logo_raw, Point::new(x, y))
.draw(&mut self.display)
.ok();
}
// Draw Ship icon (64x64) if present
if let Some(ship) = &kill.victim.ship {
let bw_vec = rgb_to_bw_dithered(&ship.icon);
let width = ship.icon.width() as u32;
let raw_image: ImageRaw<'_, Color> = ImageRaw::<Color>::new(&bw_vec, width);
Image::new(&raw_image, Point::new(x + 64 + 4, current_y))
.draw(&mut self.display)
.ok();
}
let text_x = x + 64 + 64 + 8;
let style = MonoTextStyleBuilder::new()
.font(&embedded_graphics::mono_font::ascii::FONT_8X13)
.text_color(Color::White)
.background_color(Color::Black)
.build();
current_y += 8;
// Character name + Alliance short
let alliance_short = kill
.victim
.alliance
.as_ref()
.map(|a| a.short.as_str())
.unwrap_or("");
let char_name = kill
.victim
.character
.as_ref()
.map(|a| a.name.as_str())
.unwrap_or("");
let char_line = format!("{} [{}]", char_name, alliance_short);
Text::new(&char_line, Point::new(text_x, current_y), style)
.draw(&mut self.display)
.ok();
current_y += 16;
// Ship name + total value
let ship_name = kill
.victim
.ship
.as_ref()
.map(|s| s.name.as_str())
.unwrap_or("Unknown");
let value_line = format!(
"{} - {:.2}M ISK",
ship_name,
kill.total_value / 1_000_000f64
);
Text::new(&value_line, Point::new(text_x, current_y), style)
.draw(&mut self.display)
.ok();
current_y += 16;
// System name
Text::new(&kill.system_name, Point::new(text_x, current_y), style)
.draw(&mut self.display)
.ok();
}
fn draw_loss_info(&mut self, kill: &KillInfo, x: i32, y: i32) {
let mut current_y = y;
// Draw Alliance logo (64x64) if present
if let Some(character) = &kill.victim.character {
let bw_vec = rgb_to_bw_dithered(&character.portrait);
let logo_raw = ImageRaw::<Color>::new(&bw_vec, character.portrait.width() as u32);
Image::new(&logo_raw, Point::new(x, y))
.draw(&mut self.display)
.ok();
}
// Draw Ship icon (64x64) if present
if let Some(ship) = &kill.victim.ship {
let bw_vec = rgb_to_bw_dithered(&ship.icon);
let logo_raw = ImageRaw::<Color>::new(&bw_vec, ship.icon.width() as u32);
Image::new(&logo_raw, Point::new(x + 64 + 4, current_y))
.draw(&mut self.display)
.ok();
}
let text_x = x + 64 + 64 + 8;
let style = MonoTextStyleBuilder::new()
.font(&embedded_graphics::mono_font::ascii::FONT_8X13)
.text_color(Color::Black)
.background_color(Color::White)
.build();
current_y += 8;
// Character name + Alliance short
let alliance_short = kill
.victim
.alliance
.as_ref()
.map(|a| a.short.as_str())
.unwrap_or("");
let char_name = kill
.victim
.character
.as_ref()
.map(|a| a.name.as_str())
.unwrap_or("");
let char_line = format!("{} [{}]", char_name, alliance_short);
Text::new(&char_line, Point::new(text_x, current_y), style)
.draw(&mut self.display)
.ok();
current_y += 16;
// Ship name + total value
let ship_name = kill
.victim
.ship
.as_ref()
.map(|s| s.name.as_str())
.unwrap_or("Unknown");
let value_line = format!(
"{} - {:.2}M ISK",
ship_name,
kill.total_value / 1_000_000f64
);
Text::new(&value_line, Point::new(text_x, current_y), style)
.draw(&mut self.display)
.ok();
current_y += 16;
// System name
Text::new(&kill.system_name, Point::new(text_x, current_y), style)
.draw(&mut self.display)
.ok();
}
}
fn rgb_to_bw_dithered(rgb: &RgbImage) -> Vec<u8> {
let w = rgb.width() as usize;
let h = rgb.height() as usize;
// Convert to grayscale f32 buffer
let mut gray: Vec<f32> = rgb
.pixels()
.map(|p| {
let [r, g, b] = p.0;
0.299 * r as f32 + 0.587 * g as f32 + 0.114 * b as f32
})
.collect();
let mut bits = Vec::new();
let mut byte = 0u8;
let mut bit_idx = 7;
for y in 0..h {
for x in 0..w {
let idx = y * w + x;
let old = gray[idx];
let new = if old >= 128.0 { 255.0 } else { 0.0 };
let error = old - new;
// Quantize to B/W
let bw = new > 127.0;
if bw {
byte |= 1 << bit_idx;
}
bit_idx -= 1;
if bit_idx == -1 {
bits.push(byte);
byte = 0;
bit_idx = 7;
}
// Distribute error (FloydSteinberg)
if x + 1 < w {
gray[idx + 1] += error * 7.0 / 16.0;
}
if y + 1 < h {
if x > 0 {
gray[idx + w - 1] += error * 3.0 / 16.0;
}
gray[idx + w] += error * 5.0 / 16.0;
if x + 1 < w {
gray[idx + w + 1] += error * 1.0 / 16.0;
}
}
}
}
if bit_idx > 0 {
bits.push(byte);
}
bits
}