312 lines
9.3 KiB
Rust
312 lines
9.3 KiB
Rust
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 (Floyd–Steinberg)
|
||
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
|
||
}
|