anime: CLI and user-daemon work

This commit is contained in:
Luke D Jones
2021-04-07 22:20:29 +12:00
parent 8010da0891
commit 7d0f15d738
65 changed files with 721 additions and 188 deletions

View File

@@ -1,25 +1,26 @@
use std::path::Path;
use std::{path::Path, time::Duration};
use crate::{
anime_data::{AniMeDataBuffer, ANIME_DATA_LEN},
data::{AniMeDataBuffer, ANIME_DATA_LEN},
error::AnimeError,
};
const WIDTH: usize = 74;
const HEIGHT: usize = 36;
/// Mostly intended to be used with ASUS gifs, but can be used for other purposes (like images)
#[derive(Debug, Clone)]
pub struct AniMeDiagonal([[u8; WIDTH]; HEIGHT]);
pub struct AniMeDiagonal([[u8; WIDTH]; HEIGHT], Option<Duration>);
impl Default for AniMeDiagonal {
fn default() -> Self {
Self::new()
Self::new(None)
}
}
impl AniMeDiagonal {
pub fn new() -> Self {
Self([[0u8; WIDTH]; HEIGHT])
pub fn new(duration: Option<Duration>) -> Self {
Self([[0u8; WIDTH]; HEIGHT], duration)
}
pub fn get_mut(&mut self) -> &mut [[u8; WIDTH]; HEIGHT] {
@@ -38,14 +39,18 @@ impl AniMeDiagonal {
/// Generate the base image from inputs. The result can be displayed as is or
/// updated via scale, position, or angle then displayed again after `update()`.
pub fn from_png(path: &Path, bright: f32) -> Result<Self, AnimeError> {
pub fn from_png(
path: &Path,
duration: Option<Duration>,
bright: f32,
) -> Result<Self, AnimeError> {
use pix::el::Pixel;
let data = std::fs::read(path)?;
let data = std::io::Cursor::new(data);
let decoder = png_pong::Decoder::new(data)?.into_steps();
let png_pong::Step { raster, delay: _ } = decoder.last().ok_or(AnimeError::NoFrames)??;
let mut matrix = AniMeDiagonal::new();
let mut matrix = AniMeDiagonal::new(duration);
let width;
match raster {

View File

@@ -22,13 +22,19 @@ impl AniMeFrame {
}
}
/// A gif animation. This is a collection of frames from the gif, and a duration
/// that the animation should be shown for.
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct AniMeGif(Vec<AniMeFrame>);
pub struct AniMeGif(Vec<AniMeFrame>, Option<Duration>);
impl AniMeGif {
/// Create an animation using the 74x36 ASUS gif format
pub fn create_diagonal_gif(file_name: &Path, brightness: f32) -> Result<Self, AnimeError> {
let mut matrix = AniMeDiagonal::new();
pub fn create_diagonal_gif(
file_name: &Path,
duration: Option<Duration>,
brightness: f32,
) -> Result<Self, AnimeError> {
let mut matrix = AniMeDiagonal::new(None);
let mut decoder = gif::DecodeOptions::new();
// Configure the decoder such that it will expand the image to RGBA.
@@ -59,7 +65,7 @@ impl AniMeGif {
delay: Duration::from_millis(wait as u64),
});
}
Ok(Self(frames))
Ok(Self(frames, duration))
}
/// Create an animation using a gif of any size. This method must precompute the
@@ -69,6 +75,7 @@ impl AniMeGif {
scale: f32,
angle: f32,
translation: Vec2,
duration: Option<Duration>,
brightness: f32,
) -> Result<Self, AnimeError> {
let mut frames = Vec::new();
@@ -98,8 +105,14 @@ impl AniMeGif {
if matches!(frame.dispose, gif::DisposalMethod::Background) {
let pixels: Vec<Pixel> =
vec![Pixel::default(); (width as u32 * height as u32) as usize];
image =
AniMeImage::new(Vec2::new(scale,scale), angle, translation, brightness, pixels, width as u32);
image = AniMeImage::new(
Vec2::new(scale, scale),
angle,
translation,
brightness,
pixels,
width as u32,
);
}
for (y, row) in frame.buffer.chunks(frame.width as usize * 4).enumerate() {
for (x, px) in row.chunks(4).enumerate() {
@@ -122,10 +135,14 @@ impl AniMeGif {
delay: Duration::from_millis(wait as u64),
});
}
Ok(Self(frames))
Ok(Self(frames, duration))
}
pub fn frames(&self) -> &[AniMeFrame] {
&self.0
}
pub fn duration(&self) -> Option<Duration> {
self.1
}
}

View File

@@ -1,5 +1,7 @@
use crate::anime_data::{AniMeDataBuffer, ANIME_DATA_LEN};
use crate::anime_image::LED_IMAGE_POSITIONS;
use std::time::Duration;
use crate::data::{AniMeDataBuffer, ANIME_DATA_LEN};
use crate::image::LED_IMAGE_POSITIONS;
const WIDTH: usize = 33;
const HEIGHT: usize = 55;
@@ -10,17 +12,17 @@ const HEIGHT: usize = 55;
/// Width = 33
/// height = 55
#[derive(Debug, Clone)]
pub struct AniMeGrid([[u8; WIDTH]; HEIGHT]);
pub struct AniMeGrid([[u8; WIDTH]; HEIGHT], Option<Duration>);
impl Default for AniMeGrid {
fn default() -> Self {
Self::new()
Self::new(None)
}
}
impl AniMeGrid {
pub fn new() -> Self {
AniMeGrid([[0u8; WIDTH]; HEIGHT])
pub fn new(duration: Option<Duration>) -> Self {
AniMeGrid([[0u8; WIDTH]; HEIGHT], duration)
}
pub fn set(&mut self, x: usize, y: usize, b: u8) {
@@ -97,11 +99,11 @@ impl From<AniMeGrid> for AniMeDataBuffer {
#[cfg(test)]
mod tests {
use crate::anime_grid::*;
use crate::grid::*;
#[test]
fn check_data_alignment() {
let mut matrix = AniMeGrid::new();
let mut matrix = AniMeGrid::new(None);
{
let tmp = matrix.get_mut();
for row in tmp.iter_mut() {

View File

@@ -4,7 +4,7 @@ pub use glam::Vec2;
use glam::{Mat3, Vec3};
use crate::{
anime_data::{AniMeDataBuffer, ANIME_DATA_LEN},
data::{AniMeDataBuffer, ANIME_DATA_LEN},
error::AnimeError,
};
@@ -252,7 +252,7 @@ impl AniMeImage {
width = ras.width();
ras.pixels()
.iter()
.map(|px| crate::anime_image::Pixel {
.map(|px| crate::image::Pixel {
color: <u8>::from(px.one()) as u32,
alpha: <f32>::from(px.alpha()),
})
@@ -261,7 +261,14 @@ impl AniMeImage {
_ => return Err(AnimeError::Format),
};
let mut matrix = AniMeImage::new(Vec2::new(scale, scale), angle, translation, bright, pixels, width);
let mut matrix = AniMeImage::new(
Vec2::new(scale, scale),
angle,
translation,
bright,
pixels,
width,
);
matrix.update();
Ok(matrix)
@@ -1539,7 +1546,7 @@ pub const LED_IMAGE_POSITIONS: [Option<Led>; LED_PIXEL_LEN] = [
#[cfg(test)]
mod tests {
use crate::anime_image::*;
use crate::image::*;
#[test]
fn led_positions() {

View File

@@ -1,89 +1,25 @@
use serde_derive::{Deserialize, Serialize};
/// The main data conversion for transfering in shortform over dbus or other,
/// or writing directly to the USB device
mod anime_data;
use std::{path::Path, time::Duration};
mod data;
pub use anime_data::*;
pub use data::*;
/// Useful for specialised effects that required a grid of data
mod anime_grid;
pub use anime_grid::*;
mod grid;
pub use grid::*;
/// Transform a PNG image for displaying on AniMe matrix display
mod anime_image;
pub use anime_image::*;
mod image;
pub use image::*;
mod anime_diagonal;
pub use anime_diagonal::*;
mod diagonal;
pub use diagonal::*;
mod anime_gif;
pub use anime_gif::*;
use error::AnimeError;
mod gif;
pub use crate::gif::*;
mod sequencer;
pub use sequencer::*;
/// Base errors that are possible
pub mod error;
// TODO: make schema to rebuild the full sequence without requiring saving the actual
// packet data
#[derive(Debug, Clone, Deserialize, Serialize)]
pub enum AniMeBlock {
/// Full gif sequence. Immutable.
Animation(AniMeGif),
/// Basic image, can have properties changed
Image(Box<AniMeDataBuffer>),
/// A pause to be used between sequences
Pause(Duration),
}
impl AniMeBlock {
pub fn asus_gif(file: &Path, brightness: f32) -> Result<Self, AnimeError> {
let frames = AniMeGif::create_diagonal_gif(file, brightness)?;
Ok(Self::Animation(frames))
}
pub fn png(
file: &Path,
scale: f32,
angle: f32,
translation: Vec2,
brightness: f32,
) -> Result<Self, AnimeError> {
let image = AniMeImage::from_png(file, scale, angle, translation, brightness)?;
let data = <AniMeDataBuffer>::from(&image);
Ok(Self::Image(Box::new(data)))
}
pub fn image_gif(
file: &Path,
scale: f32,
angle: f32,
translation: Vec2,
brightness: f32,
) -> Result<Self, AnimeError> {
let frames = AniMeGif::create_png_gif(file, scale, angle, translation, brightness)?;
Ok(Self::Animation(frames))
}
pub fn get_animation(&self) -> Option<&AniMeGif> {
match self {
AniMeBlock::Animation(anim) => Some(anim),
_ => None,
}
}
pub fn get_image(&self) -> Option<&AniMeDataBuffer> {
match self {
AniMeBlock::Image(image) => Some(image),
_ => None,
}
}
pub fn get_pause(&self) -> Option<Duration> {
match self {
AniMeBlock::Pause(pause) => Some(*pause),
_ => None,
}
}
}

108
rog-anime/src/sequencer.rs Normal file
View File

@@ -0,0 +1,108 @@
use std::{path::Path, time::Duration};
use glam::Vec2;
use serde_derive::{Deserialize, Serialize};
use crate::{error::AnimeError, AniMeDataBuffer, AniMeGif, AniMeImage};
///
#[derive(Debug, Deserialize, Serialize)]
pub enum Action {
/// Full gif sequence. Immutable.
Animation(AniMeGif),
/// Basic image, can have properties changed and image updated via those properties
Image(Box<AniMeDataBuffer>),
/// A pause to be used between sequences
Pause(Duration),
/// Placeholder
AudioEq,
/// Placeholder
SystemInfo,
/// Placeholder
TimeDate,
/// Placeholder
Matrix,
}
/// An optimised precomputed set of actions
#[derive(Debug, Deserialize, Serialize, Default)]
pub struct Sequences(Vec<Action>);
impl Sequences {
pub fn new() -> Self {
Self(Vec::new())
}
pub fn add_asus_gif(
&mut self,
file: &Path,
duration: Option<Duration>,
brightness: f32,
) -> Result<(), AnimeError> {
let frames = AniMeGif::create_diagonal_gif(file, duration, brightness)?;
self.0.push(Action::Animation(frames));
Ok(())
}
pub fn add_png(
&mut self,
file: &Path,
scale: f32,
angle: f32,
translation: Vec2,
brightness: f32,
) -> Result<(), AnimeError> {
let image = AniMeImage::from_png(file, scale, angle, translation, brightness)?;
let data = <AniMeDataBuffer>::from(&image);
self.0.push(Action::Image(Box::new(data)));
Ok(())
}
pub fn add_image_gif(
&mut self,
file: &Path,
scale: f32,
angle: f32,
translation: Vec2,
duration: Option<Duration>,
brightness: f32,
) -> Result<(), AnimeError> {
let frames =
AniMeGif::create_png_gif(file, scale, angle, translation, duration, brightness)?;
self.0.push(Action::Animation(frames));
Ok(())
}
pub fn add_pause(&mut self, duration: Duration) -> Result<(), AnimeError> {
self.0.push(Action::Pause(duration));
Ok(())
}
pub fn iter(&self) -> ActionIterator {
ActionIterator {
actions: &self,
next_idx: 0,
}
}
}
pub struct ActionIterator<'a> {
actions: &'a Sequences,
next_idx: usize,
}
impl<'a> Iterator for ActionIterator<'a> {
type Item = &'a Action;
fn next(&mut self) -> Option<&'a Action> {
if self.next_idx == self.actions.0.len() {
self.next_idx = 0;
return None;
}
let current = self.next_idx;
self.next_idx += 1;
Some(&self.actions.0[current])
}
}