| use std::{ |
| path::{self, PathBuf}, |
| sync::Arc, |
| time::Duration, |
| }; |
| |
| use forma::{ |
| AffineTransform, Composition, Fill, FillRule, Func, Image, Order, PathBuilder, Point, Props, |
| Style, |
| }; |
| use image::GenericImageView; |
| |
| use crate::{App, Keyboard}; |
| |
| #[derive(Debug)] |
| pub struct Texture { |
| width: usize, |
| height: usize, |
| time: Duration, |
| image: Arc<Image>, |
| } |
| |
| impl Texture { |
| pub fn new() -> Self { |
| Self { |
| width: 1000, |
| height: 1000, |
| time: Duration::ZERO, |
| image: Arc::new(load_image(&PathBuf::from("assets/image/butterfly.jpg"))), |
| } |
| } |
| |
| fn transform(&self, t: f32) -> AffineTransform { |
| let scale = 1.5 - t.cos(); |
| let mut t = AffineTransform { |
| ux: t.cos() * scale, |
| uy: t.sin() * scale, |
| vx: -t.sin() * scale, |
| vy: t.cos() * scale, |
| tx: 0.0, |
| ty: 0.0, |
| }; |
| |
| let (x, y) = (self.width as f32 * 0.5, self.height as f32 * 0.5); |
| (t.tx, t.ty) = ( |
| self.image.width() as f32 * 0.5 - t.ux * x - t.vx * y, |
| self.image.height() as f32 * 0.5 - t.uy * x - t.vy * y, |
| ); |
| t |
| } |
| } |
| |
| impl Default for Texture { |
| fn default() -> Self { |
| Self::new() |
| } |
| } |
| |
| fn load_image(file_path: &path::Path) -> Image { |
| let img = image::io::Reader::open(file_path) |
| .expect("Unable to open file") |
| .decode() |
| .expect("Unable to decode file"); |
| |
| let data: Vec<_> = img.to_rgb8().pixels().map(|p| [p.0[0], p.0[1], p.0[2], 255]).collect(); |
| Image::from_srgba(&data[..], img.width() as usize, img.height() as usize).unwrap() |
| } |
| |
| impl App for Texture { |
| fn width(&self) -> usize { |
| self.width |
| } |
| |
| fn height(&self) -> usize { |
| self.height |
| } |
| |
| fn set_width(&mut self, width: usize) { |
| self.width = width; |
| } |
| |
| fn set_height(&mut self, height: usize) { |
| self.height = height; |
| } |
| |
| fn compose(&mut self, composition: &mut Composition, elapsed: Duration, _: &Keyboard) { |
| const PAD: f32 = 32.0; |
| |
| let (w, h) = (self.width as f32, self.height as f32); |
| let layer = composition.get_mut_or_insert_default(Order::new(0).unwrap()).clear().insert( |
| &PathBuilder::new() |
| .move_to(Point { x: PAD, y: PAD }) |
| .line_to(Point { x: w - PAD, y: PAD }) |
| .line_to(Point { x: w - PAD, y: h - PAD }) |
| .line_to(Point { x: PAD, y: h - PAD }) |
| .build(), |
| ); |
| |
| self.time += elapsed; |
| |
| layer.set_props(Props { |
| fill_rule: FillRule::NonZero, |
| func: Func::Draw(Style { |
| fill: Fill::Texture(forma::Texture { |
| transform: self.transform(self.time.as_secs_f32()), |
| image: self.image.clone(), |
| }), |
| ..Default::default() |
| }), |
| }); |
| } |
| } |