mirror of
https://gitlab.com/asus-linux/asusctl.git
synced 2026-02-06 00:15:04 +01:00
anime: CLI and user-daemon work
This commit is contained in:
@@ -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 {
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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() {
|
||||
@@ -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() {
|
||||
@@ -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
108
rog-anime/src/sequencer.rs
Normal 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])
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user