This commit is contained in:
0m.ax 2026-07-18 03:05:48 +02:00
parent f6e64b6e0b
commit 73c3462543
6 changed files with 371 additions and 365 deletions

78
src/bouncing_image.rs Normal file
View file

@ -0,0 +1,78 @@
use crate::display::Display;
use crate::drawable::Drawable;
use crate::png_data::PngData;
use crate::{DISPLAY_HEIGHT, DISPLAY_WIDTH};
/// Represents a bouncing image on the screen.
pub struct BouncingImage {
img: PngData,
x: i32,
y: i32,
x1: i32,
y1: i32,
x2: i32,
y2: i32,
move_x: i32,
move_y: i32,
rate: u32,
}
impl BouncingImage {
/// Initializes a new BouncingImage.
pub fn new(img_file: &str, move_x: i32, move_y: i32, rate: u32, start_x: i32, start_y: i32) -> Self {
let img = PngData::open(img_file).expect("Could not load image");
let mut bb = BouncingImage {
x2: DISPLAY_WIDTH - img.width as i32,
y2: DISPLAY_HEIGHT - img.height as i32,
img,
x: start_x,
y: start_y,
x1: 0,
y1: 0,
move_x,
move_y,
rate,
};
if bb.x == -1 {
bb.x = (bb.x1 + bb.x2) / 2;
}
if bb.y == -1 {
bb.y = (bb.y1 + bb.y2) / 2;
}
bb
}
/// Draws a PNG image at the given coordinates.
fn draw_png(&mut self, display: &mut Display, x: i32, y: i32) {
for sy in 0..self.img.height {
for sx in 0..self.img.width {
let index = (sy * self.img.width + sx) as usize * 4;
let rgba = &self.img.pixels[index..index + 4];
if rgba[3] > 0 {
display.set_pixel(x + sx as i32, y + sy as i32, rgba[0], rgba[1], rgba[2]);
}
}
}
}
}
impl Drawable for BouncingImage {
fn rate(&self) -> u32 {
self.rate
}
/// Draws the image and updates its position.
fn draw_and_move(&mut self, display: &mut Display, _tick: u32) {
self.draw_png(display, self.x, self.y);
self.x += self.move_x;
self.y += self.move_y;
if self.x < self.x1 || self.x > self.x2 {
self.move_x *= -1;
}
if self.y < self.y1 || self.y > self.y2 {
self.move_y *= -1;
}
}
}

110
src/circle.rs Normal file
View file

@ -0,0 +1,110 @@
use std::sync::{Arc, Mutex};
use crate::color;
use crate::display::Display;
use crate::drawable::Drawable;
/// Represents a circle drawn at coordinates received via shared state.
pub struct Circle {
x: Arc<Mutex<u32>>,
y: Arc<Mutex<u32>>,
set: Arc<Mutex<bool>>,
radius: u32,
}
impl Circle {
pub fn new(x: Arc<Mutex<u32>>, y: Arc<Mutex<u32>>, set: Arc<Mutex<bool>>) -> Self {
Circle {
x,
y,
set,
radius: 0,
}
}
/// Draws the 8 symmetric points for a circle using octant symmetry.
fn draw_circle_octants(
&self,
display: &mut Display,
cx: i32,
cy: i32,
x: i32,
y: i32,
r: u8,
g: u8,
b: u8,
) {
display.set_pixel(cx + x, cy + y, r, g, b);
display.set_pixel(cx - x, cy + y, r, g, b);
display.set_pixel(cx + x, cy - y, r, g, b);
display.set_pixel(cx - x, cy - y, r, g, b);
display.set_pixel(cx + y, cy + x, r, g, b);
display.set_pixel(cx - y, cy + x, r, g, b);
display.set_pixel(cx + y, cy - x, r, g, b);
display.set_pixel(cx - y, cy - x, r, g, b);
}
/// Draws a circle using the Midpoint Circle Algorithm.
fn draw_circle(
&self,
display: &mut Display,
center_x: u32,
center_y: u32,
radius: u32,
r: u8,
g: u8,
b: u8,
) {
let radius_i32: i32 = radius as i32;
let mut x: i32 = 0;
let mut y: i32 = radius_i32;
let mut d: i32 = 3 - 2 * radius_i32;
while y >= x {
self.draw_circle_octants(
display,
center_x as i32,
center_y as i32,
x,
y,
r,
g,
b,
);
x += 1;
if d > 0 {
y -= 1;
d = d + 4 * (x - y) + 10;
} else {
d = d + 4 * x + 6;
}
}
}
}
impl Drawable for Circle {
fn rate(&self) -> u32 {
1
}
fn draw_and_move(&mut self, display: &mut Display, tick: u32) {
self.radius = 150;
let hsv_color = color::Hsv {
h: (tick % 360).try_into().unwrap(),
s: 1.0,
v: 1.0,
};
let rgb: color::Rgb = hsv_color.into();
let draw_y = *self.x.lock().unwrap();
let draw_x = *self.y.lock().unwrap();
let radius = self.radius;
self.radius -= 1;
for i in 0..10 {
self.draw_circle(display, draw_y, draw_x, radius - i, rgb.r, rgb.g, rgb.b);
}
}
}

82
src/display.rs Normal file
View file

@ -0,0 +1,82 @@
use std::net::{ToSocketAddrs, UdpSocket};
const QUEUE_LEN: usize = 1000;
const MSG_PAYLOAD_SIZE: usize = 7 * 211;
const MSGSIZE: usize = 2 + MSG_PAYLOAD_SIZE;
/// Manages the connection and data sent to the display.
pub struct Display {
socket: UdpSocket,
bufs: Vec<[u8; MSGSIZE]>,
next_buf: usize,
pos_in_buf: usize,
}
impl Display {
/// Creates a new Display and connects to the specified host and port.
pub fn new(host: &str, port: u16) -> Self {
let remote_addr = (host, port)
.to_socket_addrs()
.expect("Invalid remote address")
.next()
.expect("Could not resolve host");
let socket = UdpSocket::bind("0.0.0.0:0").expect("Could not bind to local port");
socket.connect(remote_addr).expect("Could not connect to remote");
let mut bufs = vec![[0; MSGSIZE]; QUEUE_LEN];
for buf in bufs.iter_mut() {
buf[0] = 0x00;
buf[1] = 0x01;
}
Display {
socket,
bufs,
next_buf: 0,
pos_in_buf: 0,
}
}
/// Flushes the current buffer if it contains pixel data.
pub fn flush_frame(&mut self) {
if self.pos_in_buf > 0 {
let len = 2 + self.pos_in_buf * 7;
let buf_to_send = &self.bufs[self.next_buf][..len];
self.socket.send(buf_to_send).expect("Failed to send data");
self.next_buf = (self.next_buf + 1) % QUEUE_LEN;
self.pos_in_buf = 0;
}
}
/// Sets a pixel color at a specific coordinate.
pub fn set_pixel(&mut self, x: i32, y: i32, r: u8, g: u8, b: u8) {
if let (Ok(output_x), Ok(output_y)) = (u16::try_from(x), u16::try_from(y)) {
let offset = 2 + self.pos_in_buf * 7;
let buf = &mut self.bufs[self.next_buf][offset..offset + 7];
buf[0] = output_x as u8;
buf[1] = (output_x >> 8) as u8;
buf[2] = output_y as u8;
buf[3] = (output_y >> 8) as u8;
buf[4] = r;
buf[5] = g;
buf[6] = b;
self.pos_in_buf += 1;
if self.pos_in_buf == 211 {
self.flush_frame();
}
}
}
/// Clears the entire screen to black.
#[allow(dead_code)]
pub fn blank_screen(&mut self, width: i32, height: i32) {
for _x in 0..width {
for _y in 0..height {
// self.set_pixel(x, y, 0, 0, 0);
}
}
self.flush_frame();
}
}

9
src/drawable.rs Normal file
View file

@ -0,0 +1,9 @@
use crate::display::Display;
pub trait Drawable {
/// The frame rate divisor for this drawable. A rate of 1 means every frame.
fn rate(&self) -> u32;
/// Draw the object to the display and update its internal state.
fn draw_and_move(&mut self, display: &mut Display, tick: u32);
}

View file

@ -1,381 +1,88 @@
use std::fs::File;
use std::io::BufReader;
use std::net::{ToSocketAddrs, UdpSocket};
use std::time::Duration;
use std::net::{SocketAddr, UdpSocket};
use std::sync::{Arc, Mutex};
use std::thread;
use std::sync::{Mutex,Arc};
use std::time::Duration;
use socket2::{Domain, Socket, Type};
use std::net::{Ipv4Addr, SocketAddr};
mod bouncing_image;
mod circle;
mod color;
// Constants from the C code
const QUEUE_LEN: usize = 1000;
const MSG_PAYLOAD_SIZE: usize = 7 * 211;
const MSGSIZE: usize = 2 + MSG_PAYLOAD_SIZE;
mod display;
mod drawable;
mod png_data;
const DISPLAY_HOST: &str = "100.65.13.2";
const DISPLAY_PORT: u16 = 5005;
const DISPLAY_WIDTH: i32 = 1920;
const DISPLAY_HEIGHT: i32 = 1080;
/// Represents the data decoded from a PNG file.
struct PngData {
width: u32,
height: u32,
pixels: Vec<u8>,
}
impl PngData {
/// Loads and decodes a PNG image from the given path.
fn open(path: &str) -> Result<Self, png::DecodingError> {
let file = File::open(path).expect("Failed to open PNG file");
let decoder = png::Decoder::new(BufReader::new(file));
let mut reader = decoder.read_info()?;
let mut buf = vec![0; reader.output_buffer_size()];
let info = reader.next_frame(&mut buf)?;
Ok(PngData {
width: info.width,
height: info.height,
pixels: buf,
})
}
}
/// Represents a bouncing image on the screen.
struct BouncingImage {
img: PngData,
x: i32,
y: i32,
x1: i32,
y1: i32,
x2: i32,
y2: i32,
move_x: i32,
move_y: i32,
rate: u32,
}
trait Drawable {
// Associated function signature; `Self` refers to the implementor type.
fn rate(&self) -> u32;
fn draw_and_move(&mut self, display: &mut Display,tick:u32);
}
impl BouncingImage {
/// Initializes a new BouncingImage.
fn new(img_file: &str, move_x: i32, move_y: i32, rate: u32, start_x: i32, start_y: i32) -> Self {
let img = PngData::open(img_file).expect("Could not load image");
let mut bb = BouncingImage {
x2: DISPLAY_WIDTH - img.width as i32,
y2: DISPLAY_HEIGHT - img.height as i32,
img,
x: start_x,
y: start_y,
x1: 0,
y1: 0,
move_x,
move_y,
rate,
};
if bb.x == -1 {
bb.x = (bb.x1 + bb.x2) / 2;
}
if bb.y == -1 {
bb.y = (bb.y1 + bb.y2) / 2;
}
bb
}
/// Draws a PNG image at the given coordinates.
fn draw_png(&mut self, display: &mut Display, x: i32, y: i32) {
for sy in 0..self.img.height {
for sx in 0..self.img.width {
let index = (sy * self.img.width + sx) as usize * 4;
let rgba = &self.img.pixels[index..index + 4];
if rgba[3] > 0 { // Check alpha channel
display.set_pixel((x + sx as i32), (y + sy as i32), rgba[0], rgba[1], rgba[2]);
}
}
}
}
}
impl Drawable for BouncingImage {
fn rate(&self) -> u32 {
return self.rate;
}
/// Draws the image and updates its position.
fn draw_and_move(&mut self, display: &mut Display,_: u32) {
self.draw_png(display, self.x, self.y);
self.x += self.move_x;
self.y += self.move_y;
if self.x < self.x1 || self.x > self.x2 {
self.move_x *= -1;
}
if self.y < self.y1 || self.y > self.y2 {
self.move_y *= -1;
}
}
}
struct Circle {
x: Arc<Mutex<u32>>,
y: Arc<Mutex<u32>>,
set: Arc<Mutex<bool>>,
radius: u32
}
impl Circle {
fn new(x:Arc<Mutex<u32>>, y: Arc<Mutex<u32>>, set: Arc<Mutex<bool>>) -> Self {
Circle {
x,
y,
set,
radius:0
}
}
/// This exploits the eight-way symmetry of a circle.
fn draw_circle_octants(&mut self, display: &mut Display, cx: i32, cy: i32, x: i32, y: i32, r: u8, g: u8, b: u8) {
display.set_pixel(cx + x, cy + y, r, g, b);
display.set_pixel(cx - x, cy + y, r, g, b);
display.set_pixel(cx + x, cy - y, r, g, b);
display.set_pixel(cx - x, cy - y, r, g, b);
display.set_pixel(cx + y, cy + x, r, g, b);
display.set_pixel(cx - y, cy + x, r, g, b);
display.set_pixel(cx + y, cy - x, r, g, b);
display.set_pixel(cx - y, cy - x, r, g, b);
}
/// Draws a circle using the Midpoint Circle Algorithm.
///
/// # Arguments
/// * `center_x`: The x-coordinate of the circle's center.
/// * `center_y`: The y-coordinate of the circle's center.
/// * `radius`: The radius of the circle. Must be non-negative.
/// * `r`, `g`, `b`: The RGB color components for the circle.
pub fn draw_circle(&mut self, display: &mut Display, center_x: u32, center_y: u32, radius: u32, r: u8, g: u8, b: u8) {
if radius < 0 {
// Or return an error: Err("Radius cannot be negative".into())
return;
}
let radius_i32:i32 = radius.try_into().unwrap();
let mut x:i32 = 0;
let mut y:i32 = radius_i32;
// Initial decision parameter
let mut d:i32 = 3 - 2 * radius_i32;
// Iterate through the first octant and draw points in all 8 octants
while y >= x {
self.draw_circle_octants(display,center_x.try_into().unwrap(), center_y.try_into().unwrap(), x, y, r, g, b);
x += 1;
// Update the decision parameter
if d > 0 {
y -= 1;
d = d + 4 * (x - y) + 10;
} else {
d = d + 4 * x + 6;
}
}
}
}
impl Drawable for Circle {
fn rate(&self) -> u32 {
1
}
/// Helper method to draw the 8 symmetric points for a given (x, y) offset.
fn draw_and_move(&mut self, display: &mut Display,tick:u32){
// if(*self.set.lock().unwrap()){
self.radius = 150;
// *self.set.lock().unwrap() = false;
//}
//if(self.radius != 0){
let hsv_color = color::Hsv {
h: ((tick)%360).try_into().unwrap(),
s: 1.0,
v: 1.0,
};
let rgb: color::Rgb = hsv_color.into();
let draw_y =*self.x.lock().unwrap();
let draw_x = *self.y.lock().unwrap();
//let radius = (tick/30) % (300/2);
let radius = self.radius;
self.radius = self.radius -1;
for i in 0..10 {
self.draw_circle(display,draw_y,draw_x,radius-i,rgb.r,rgb.g,rgb.b);
}
}
}
/// Manages the connection and data sent to the display.
struct Display {
socket: UdpSocket,
bufs: Vec<[u8; MSGSIZE]>,
next_buf: usize,
pos_in_buf: usize,
}
impl Display {
/// Creates a new Display and connects to the specified host and port.
fn new(host: &str, port: u16) -> Self {
let remote_addr = (host, port)
.to_socket_addrs()
.expect("Invalid remote address")
.next()
.expect("Could not resolve host");
let socket = UdpSocket::bind("0.0.0.0:0").expect("Could not bind to local port");
socket.connect(remote_addr).expect("Could not connect to remote");
let mut bufs = vec![[0; MSGSIZE]; QUEUE_LEN];
for buf in bufs.iter_mut() {
buf[0] = 0x00;
buf[1] = 0x01;
}
Display {
socket,
bufs,
next_buf: 0,
pos_in_buf: 0,
}
}
/// Flushes the current buffer if it contains pixel data.
fn flush_frame(&mut self) {
if self.pos_in_buf > 0 {
let len = 2 + self.pos_in_buf * 7;
let buf_to_send = &self.bufs[self.next_buf][..len];
self.socket.send(buf_to_send).expect("Failed to send data");
self.next_buf = (self.next_buf + 1) % QUEUE_LEN;
self.pos_in_buf = 0;
}
}
/// Sets a pixel color at a specific coordinate.
fn set_pixel(&mut self, x: i32, y: i32, r: u8, g: u8, b: u8) {
if let (Ok(output_x),Ok(output_y)) = (u16::try_from(x), u16::try_from(y)) {
let offset = 2 + self.pos_in_buf * 7;
let buf = &mut self.bufs[self.next_buf][offset..offset + 7];
buf[0] = output_x as u8;
buf[1] = (output_x >> 8) as u8;
buf[2] = output_y as u8;
buf[3] = (output_y >> 8) as u8;
buf[4] = r;
buf[5] = g;
buf[6] = b;
self.pos_in_buf += 1;
if self.pos_in_buf == 211 {
self.flush_frame();
}
}
}
/// Clears the entire screen to black.
#[allow(dead_code)]
fn blank_screen(&mut self) {
for x in 0..DISPLAY_WIDTH {
for y in 0..DISPLAY_HEIGHT {
//self.set_pixel(x as u16, y as u16, 0, 0, 0);
}
}
self.flush_frame();
}
}
use circle::Circle;
use display::Display;
use drawable::Drawable;
// Display configuration constants
pub const DISPLAY_HOST: &str = "127.0.0.1";
pub const DISPLAY_PORT: u16 = 5005;
pub const DISPLAY_WIDTH: i32 = 1920;
pub const DISPLAY_HEIGHT: i32 = 1080;
/// Unpacks a 4-byte slice into two u16 values (little-endian).
fn unpack_coordinates(buffer: &[u8]) -> Option<(u16, u16)> {
if buffer.len() != 4 {
return None;
}
// Try to convert the first 2 bytes to a u16 for x.
let x_bytes: [u8; 2] = buffer[0..2].try_into().ok()?;
// Try to convert the next 2 bytes to a u16 for y.
let y_bytes: [u8; 2] = buffer[2..4].try_into().ok()?;
// Reconstruct the u16 values from their little-endian byte representation.
let x = u16::from_le_bytes(x_bytes);
let y = u16::from_le_bytes(y_bytes);
Some((x, y))
}
fn main() {
let x:Arc<Mutex<u32>> = Arc::new(Mutex::new(0));
let x: Arc<Mutex<u32>> = Arc::new(Mutex::new(0));
let x_thread = x.clone();
let y:Arc<Mutex<u32>>= Arc::new(Mutex::new(0));
let y: Arc<Mutex<u32>> = Arc::new(Mutex::new(0));
let y_thread = y.clone();
let set:Arc<Mutex<bool>> = Arc::new(Mutex::new(false));
let set: Arc<Mutex<bool>> = Arc::new(Mutex::new(false));
let set_thread = set.clone();
let circle = Box::new(Circle::new(x,y,set));
let mut images:Vec<Box<dyn Drawable>> = vec![
// Box::new(BouncingImage::new("images/unicorn_cc.png", 13, -10, 2, -1, -1)),
// Box::new(BouncingImage::new("images/windows_logo.png", -8, 3, 2, -1, -1)),
// Box::new(BouncingImage::new("images/spade.png", 32, -12, 1, 0, 0)),
// Box::new(BouncingImage::new("images/dvdvideo.png", 20, 6, 5, 1000, 800)),
// Box::new(BouncingImage::new("images/hackaday.png", 40, 18, 3, 500, 800)),
circle
let circle = Box::new(Circle::new(x, y, set));
let mut images: Vec<Box<dyn Drawable>> = vec![
Box::new(bouncing_image::BouncingImage::new("images/unicorn_cc.png", 13, -10, 2, -1, -1)),
// Box::new(bouncing_image::BouncingImage::new("images/windows_logo.png", -8, 3, 2, -1, -1)),
// Box::new(bouncing_image::BouncingImage::new("images/spade.png", 32, -12, 1, 0, 0)),
// Box::new(bouncing_image::BouncingImage::new("images/dvdvideo.png", 20, 6, 5, 1000, 800)),
// Box::new(bouncing_image::BouncingImage::new("images/hackaday.png", 40, 18, 3, 500, 800)),
circle,
];
let mut display = Display::new(DISPLAY_HOST, DISPLAY_PORT);
let mut frame_counter: u32 = 0;
// Spawn a UDP listener thread for receiving coordinates
thread::spawn(move || {
let bind_address = format!("0.0.0.0:12345");
let bind_address = "0.0.0.0:12345";
let socket = Socket::new(Domain::IPV4, Type::DGRAM, None).unwrap();
socket.set_reuse_address(true).unwrap();
//socket.set_nonblocking(true).unwrap();
//socket.join_multicast_v4(&Ipv4Addr::new(239, 1, 1, 1), &Ipv4Addr::new(0, 0, 0, 0)).unwrap();
socket.bind(&"0.0.0.0:1234".parse::<SocketAddr>().unwrap().into()).unwrap();
// Bind the UDP socket to the specified address and port.
socket
.bind(&"0.0.0.0:1234".parse::<SocketAddr>().unwrap().into())
.unwrap();
let socket: UdpSocket = socket.into();
println!("Listening for UDP packets on {}", bind_address);
// Create a buffer to hold incoming data. 4 bytes for two u16 values.
let mut buf = [0u8; 4];
loop {
// Wait for a packet to arrive.
match socket.recv_from(&mut buf) {
Ok((number_of_bytes, src_addr)) => {
println!("\nReceived {} bytes from {}", number_of_bytes, src_addr);
// Ensure we received the correct number of bytes.
if number_of_bytes == 4 {
// Unpack the buffer into coordinates.
if let Some((x_rev, y_rev)) = unpack_coordinates(&buf) {
println!("Received Coordinates: X = {}, Y = {}", x_rev, y_rev);
let x_32:u32 = x_rev.into();
let y_32:u32 = y_rev.into();
*x_thread.lock().unwrap() = x_32;
*y_thread.lock().unwrap() = y_32;
*x_thread.lock().unwrap() = x_rev.into();
*y_thread.lock().unwrap() = y_rev.into();
*set_thread.lock().unwrap() = true;
} else {
// This case should ideally not be reached if number_of_bytes is 4.
eprintln!("Error: Failed to unpack coordinate data.");
}
} else {
@ -387,29 +94,23 @@ fn main() {
}
Err(e) => {
eprintln!("Error receiving data: {}", e);
// Decide if you want to break the loop on an error.
// For a continuous server, you might just log and continue.
}
}
}
});
// display.blank_screen();
let mut tick:u32 = 0;
let mut tick: u32 = 0;
loop {
for (i, bb) in images.iter_mut().enumerate() {
for bb in images.iter_mut() {
if bb.rate() > 0 && frame_counter % bb.rate() != 0 {
continue;
}
bb.draw_and_move(&mut display,tick);
bb.draw_and_move(&mut display, tick);
}
display.flush_frame();
tick+=1;
tick += 1;
frame_counter += 1;
// A small delay to control the frame rate
std::thread::sleep(Duration::from_millis(16));
}
}

26
src/png_data.rs Normal file
View file

@ -0,0 +1,26 @@
use std::fs::File;
use std::io::BufReader;
/// Represents the data decoded from a PNG file.
pub struct PngData {
pub width: u32,
pub height: u32,
pub pixels: Vec<u8>,
}
impl PngData {
/// Loads and decodes a PNG image from the given path.
pub fn open(path: &str) -> Result<Self, png::DecodingError> {
let file = File::open(path).expect("Failed to open PNG file");
let decoder = png::Decoder::new(BufReader::new(file));
let mut reader = decoder.read_info()?;
let mut buf = vec![0; reader.output_buffer_size()];
let info = reader.next_frame(&mut buf)?;
Ok(PngData {
width: info.width,
height: info.height,
pixels: buf,
})
}
}