use crate::component::Component;
use crate::context::RenderContext;
use crate::event::EventHandler;
use crate::layout::Rect;
use crate::render::Renderer;
use anyhow::Result;
use image::GenericImageView;
#[derive(Debug, Clone)]
pub enum ImageData {
Rgb(Vec<u8>),
Rgba(Vec<u8>),
Png(Vec<u8>),
}
impl ImageData {
pub fn to_rgb(&self, width: u32, height: u32) -> Result<Vec<u8>> {
match self {
ImageData::Rgb(data) => Ok(data.clone()),
ImageData::Rgba(data) => {
let mut rgb = Vec::with_capacity((width * height * 3) as usize);
for chunk in data.chunks(4) {
if chunk.len() >= 3 {
rgb.push(chunk[0]);
rgb.push(chunk[1]);
rgb.push(chunk[2]);
}
}
Ok(rgb)
}
ImageData::Png(data) => {
let img = image::load_from_memory(data)?;
Ok(img.to_rgb8().into_raw())
}
}
}
}
pub struct Image {
data: ImageData,
width: u32,
height: u32,
dirty: bool,
}
impl Image {
pub fn from_rgb(data: Vec<u8>, width: u32, height: u32) -> Self {
Image {
data: ImageData::Rgb(data),
width,
height,
dirty: true,
}
}
pub fn from_rgba(data: Vec<u8>, width: u32, height: u32) -> Self {
Image {
data: ImageData::Rgba(data),
width,
height,
dirty: true,
}
}
pub fn from_png(data: Vec<u8>) -> Result<Self> {
let img = image::load_from_memory(&data)?;
let (width, height) = img.dimensions();
Ok(Image {
data: ImageData::Png(data),
width,
height,
dirty: true,
})
}
pub fn set_rgb(&mut self, data: Vec<u8>, width: u32, height: u32) {
self.data = ImageData::Rgb(data);
self.width = width;
self.height = height;
self.dirty = true;
}
pub fn dimensions(&self) -> (u32, u32) {
(self.width, self.height)
}
}
impl EventHandler for Image {}
impl Component for Image {
fn render(
&mut self,
renderer: &mut Renderer,
bounds: Rect,
_ctx: &RenderContext,
) -> Result<()> {
let rgb_data = self.data.to_rgb(self.width, self.height)?;
renderer.render_image(
&rgb_data,
self.width,
self.height,
bounds.x,
bounds.y,
Some(bounds.width),
Some(bounds.height),
)?;
self.dirty = false;
Ok(())
}
fn min_size(&self) -> (u16, u16) {
let min_cols = (self.width / 8).max(1) as u16;
let min_rows = (self.height / 16).max(1) as u16;
(min_cols, min_rows)
}
fn mark_dirty(&mut self) {
self.dirty = true;
}
fn is_dirty(&self) -> bool {
self.dirty
}
fn name(&self) -> &str {
"Image"
}
}
pub struct Animation {
current_frame: Vec<u8>,
width: u32,
height: u32,
playing: bool,
dirty: bool,
}
impl Animation {
pub fn new(width: u32, height: u32) -> Self {
Animation {
current_frame: vec![0u8; (width * height * 3) as usize],
width,
height,
playing: true,
dirty: true,
}
}
pub fn set_frame(&mut self, data: Vec<u8>) {
self.current_frame = data;
self.dirty = true;
}
pub fn set_frame_ref(&mut self, data: &[u8]) {
self.current_frame.clear();
self.current_frame.extend_from_slice(data);
self.dirty = true;
}
pub fn frame_buffer_mut(&mut self) -> &mut Vec<u8> {
self.dirty = true;
&mut self.current_frame
}
pub fn resize(&mut self, width: u32, height: u32) {
self.width = width;
self.height = height;
self.current_frame = vec![0u8; (width * height * 3) as usize];
self.dirty = true;
}
pub fn dimensions(&self) -> (u32, u32) {
(self.width, self.height)
}
pub fn is_playing(&self) -> bool {
self.playing
}
pub fn play(&mut self) {
self.playing = true;
}
pub fn pause(&mut self) {
self.playing = false;
}
pub fn toggle(&mut self) {
self.playing = !self.playing;
}
}
impl EventHandler for Animation {}
impl Component for Animation {
fn render(
&mut self,
renderer: &mut Renderer,
bounds: Rect,
_ctx: &RenderContext,
) -> Result<()> {
if self.current_frame.is_empty() {
return Ok(());
}
renderer.render_image(
&self.current_frame,
self.width,
self.height,
bounds.x,
bounds.y,
Some(bounds.width),
Some(bounds.height),
)?;
self.dirty = self.playing;
Ok(())
}
fn min_size(&self) -> (u16, u16) {
let min_cols = (self.width / 8).max(1) as u16;
let min_rows = (self.height / 16).max(1) as u16;
(min_cols, min_rows)
}
fn mark_dirty(&mut self) {
self.dirty = true;
}
fn is_dirty(&self) -> bool {
self.playing || self.dirty
}
fn name(&self) -> &str {
"Animation"
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_image_creation() {
let data = vec![255u8; 30]; let img = Image::from_rgb(data, 10, 1);
assert_eq!(img.dimensions(), (10, 1));
assert!(img.is_dirty());
}
#[test]
fn test_animation_creation() {
let anim = Animation::new(100, 50);
assert_eq!(anim.dimensions(), (100, 50));
assert!(anim.is_playing());
assert!(anim.is_dirty());
}
#[test]
fn test_animation_play_pause() {
let mut anim = Animation::new(100, 50);
assert!(anim.is_playing());
anim.pause();
assert!(!anim.is_playing());
anim.play();
assert!(anim.is_playing());
anim.toggle();
assert!(!anim.is_playing());
}
#[test]
fn test_image_data_rgb_passthrough() {
let data = vec![1, 2, 3, 4, 5, 6];
let img_data = ImageData::Rgb(data.clone());
let result = img_data.to_rgb(2, 1).unwrap();
assert_eq!(result, data);
}
#[test]
fn test_image_data_rgba_to_rgb() {
let rgba = vec![1, 2, 3, 255, 4, 5, 6, 255];
let img_data = ImageData::Rgba(rgba);
let result = img_data.to_rgb(2, 1).unwrap();
assert_eq!(result, vec![1, 2, 3, 4, 5, 6]);
}
}