diff --git a/src/bouncing_image.rs b/src/bouncing_image.rs new file mode 100644 index 0000000..e6365b8 --- /dev/null +++ b/src/bouncing_image.rs @@ -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; + } + } +} diff --git a/src/circle.rs b/src/circle.rs new file mode 100644 index 0000000..eb5fe41 --- /dev/null +++ b/src/circle.rs @@ -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>, + y: Arc>, + set: Arc>, + radius: u32, +} + +impl Circle { + pub fn new(x: Arc>, y: Arc>, set: Arc>) -> 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); + } + } +} diff --git a/src/display.rs b/src/display.rs new file mode 100644 index 0000000..3515d4a --- /dev/null +++ b/src/display.rs @@ -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(); + } +} diff --git a/src/drawable.rs b/src/drawable.rs new file mode 100644 index 0000000..898ddbb --- /dev/null +++ b/src/drawable.rs @@ -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); +} diff --git a/src/main.rs b/src/main.rs index ad21f83..b8258f5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,415 +1,116 @@ -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, -} - -impl PngData { - /// Loads and decodes a PNG image from the given path. - fn open(path: &str) -> Result { - 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>, - y: Arc>, - set: Arc>, - radius: u32 -} -impl Circle { - - fn new(x:Arc>, y: Arc>, set: Arc>) -> 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> = Arc::new(Mutex::new(0)); + let x: Arc> = Arc::new(Mutex::new(0)); let x_thread = x.clone(); - - let y:Arc>= Arc::new(Mutex::new(0)); + + let y: Arc> = Arc::new(Mutex::new(0)); let y_thread = y.clone(); - let set:Arc> = Arc::new(Mutex::new(false)); + let set: Arc> = Arc::new(Mutex::new(false)); let set_thread = set.clone(); - let circle = Box::new(Circle::new(x,y,set)); - let mut images:Vec> = 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> = 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 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::().unwrap().into()).unwrap(); - // Bind the UDP socket to the specified address and port. - let socket: UdpSocket = socket.into(); + 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 + .bind(&"0.0.0.0:1234".parse::().unwrap().into()) + .unwrap(); + let socket: UdpSocket = socket.into(); - println!("Listening for UDP packets on {}", bind_address); + 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); + let mut buf = [0u8; 4]; - // 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) { + loop { + match socket.recv_from(&mut buf) { + Ok((number_of_bytes, src_addr)) => { + println!("\nReceived {} bytes from {}", number_of_bytes, src_addr); - 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; - *set_thread.lock().unwrap() = true; - + if number_of_bytes == 4 { + if let Some((x_rev, y_rev)) = unpack_coordinates(&buf) { + println!("Received Coordinates: X = {}, Y = {}", x_rev, y_rev); + *x_thread.lock().unwrap() = x_rev.into(); + *y_thread.lock().unwrap() = y_rev.into(); + *set_thread.lock().unwrap() = true; + } else { + eprintln!("Error: Failed to unpack coordinate data."); + } } else { - // This case should ideally not be reached if number_of_bytes is 4. - eprintln!("Error: Failed to unpack coordinate data."); + eprintln!( + "Warning: Received packet with incorrect size ({} bytes). Expected 4.", + number_of_bytes + ); } - } else { - eprintln!( - "Warning: Received packet with incorrect size ({} bytes). Expected 4.", - number_of_bytes - ); + } + Err(e) => { + eprintln!("Error receiving data: {}", e); } } - 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)); } } - diff --git a/src/png_data.rs b/src/png_data.rs new file mode 100644 index 0000000..cce3a29 --- /dev/null +++ b/src/png_data.rs @@ -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, +} + +impl PngData { + /// Loads and decodes a PNG image from the given path. + pub fn open(path: &str) -> Result { + 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, + }) + } +}