Files
asusctl/rog-anime/src/gif.rs

316 lines
9.7 KiB
Rust

use std::convert::TryFrom;
use std::fs::File;
use std::path::Path;
use std::time::Duration;
use glam::Vec2;
use log::error;
use serde_derive::{Deserialize, Serialize};
use crate::error::{AnimeError, Result};
use crate::{AnimeDataBuffer, AnimeDiagonal, AnimeImage, AnimeType, Pixel};
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct AnimeFrame {
/// Precomputed data for the frame. This can be transferred directly to the
/// the `asusd` daemon over dbus or converted to USB packet with
/// `AnimePacketType::from(buffer)`
data: AnimeDataBuffer,
delay: Duration,
}
impl AnimeFrame {
/// Get the inner data buffer of the gif frame
#[inline]
pub fn frame(&self) -> &AnimeDataBuffer {
&self.data
}
/// Get the `Duration` of the delay for this frame
#[inline]
pub fn delay(&self) -> Duration {
self.delay
}
}
/// Defines the time or animation cycle count to use for a gif
#[derive(Debug, Copy, Clone, Deserialize, Serialize)]
pub enum AnimTime {
/// Time in milliseconds for animation to run
Time(Duration),
/// How many full animation loops to run or how many seconds if image is
/// static
Count(u32),
/// Run for infinite time
Infinite,
/// Fade in, play for, fade out
Fade(Fade),
}
impl Default for AnimTime {
#[inline]
fn default() -> Self {
Self::Infinite
}
}
/// Fancy brightness control: fade in/out, show at brightness for n time
#[derive(Debug, Copy, Clone, Deserialize, Serialize)]
pub struct Fade {
fade_in: Duration,
show_for: Option<Duration>,
fade_out: Duration,
}
impl Fade {
pub fn new(fade_in: Duration, show_for: Option<Duration>, fade_out: Duration) -> Self {
Self {
fade_in,
show_for,
fade_out,
}
}
pub fn fade_in(&self) -> Duration {
self.fade_in
}
pub fn show_for(&self) -> Option<Duration> {
self.show_for
}
pub fn fade_out(&self) -> Duration {
self.fade_out
}
pub fn total_fade_time(&self) -> Duration {
self.fade_in + self.fade_out
}
}
/// 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>, AnimTime);
impl AnimeGif {
/// Create an animation using the 74x36 ASUS gif format
#[inline]
pub fn from_diagonal_gif(
file_name: &Path,
duration: AnimTime,
brightness: f32,
anime_type: AnimeType,
) -> Result<Self> {
let mut matrix = AnimeDiagonal::new(anime_type, None);
let mut decoder = gif::DecodeOptions::new();
// Configure the decoder such that it will expand the image to RGBA.
decoder.set_color_output(gif::ColorOutput::RGBA);
// Read the file header
let file = File::open(file_name).map_err(|e| {
error!("Could not open {file_name:?}: {e:?}");
e
})?;
let mut decoder = decoder.read_info(file)?;
let mut frames = Vec::default();
while let Some(frame) = decoder.read_next_frame()? {
let wait = frame.delay * 10;
// if matches!(frame.dispose, gif::DisposalMethod::Background) {
// frames = Vec::new();
// }
for (y, row) in frame.buffer.chunks(frame.width as usize * 4).enumerate() {
for (x, px) in row.chunks(4).enumerate() {
if px[3] != 255 {
// should be t but not in some gifs? What, ASUS, what?
continue;
}
let tmp = matrix.get_mut();
let y = y + frame.top as usize;
if y >= tmp.len() {
return Err(AnimeError::PixelGifHeight(tmp.len()));
}
let x = x + frame.left as usize;
if x >= tmp[y].len() {
return Err(AnimeError::PixelGifWidth(tmp[y].len()));
}
matrix.get_mut()[y][x] = (px[0] as f32 * brightness) as u8;
}
}
frames.push(AnimeFrame {
data: matrix.into_data_buffer(anime_type)?,
delay: Duration::from_millis(wait as u64),
});
}
Ok(Self(frames, duration))
}
/// Create an animation using the 74x36 ASUS gif format from a png
#[inline]
pub fn from_diagonal_png(
file_name: &Path,
anime_type: AnimeType,
duration: AnimTime,
brightness: f32,
) -> Result<Self> {
let image = AnimeDiagonal::from_png(file_name, None, brightness, anime_type)?;
let mut total = Duration::from_millis(1000);
if let AnimTime::Fade(fade) = duration {
total = fade.total_fade_time();
if let Some(middle) = fade.show_for {
total += middle;
}
}
// Make frame delay 30ms, and find frame count
let frame_count = total.as_millis() / 30;
let single = AnimeFrame {
data: image.into_data_buffer(anime_type)?,
delay: Duration::from_millis(30),
};
let frames = vec![single; frame_count as usize];
Ok(Self(frames, duration))
}
/// Create an animation using a gif of any size. This method must precompute
/// the result.
#[inline]
pub fn from_gif(
file_name: &Path,
scale: f32,
angle: f32,
translation: Vec2,
duration: AnimTime,
brightness: f32,
anime_type: AnimeType,
) -> Result<Self> {
let mut frames = Vec::new();
let mut decoder = gif::DecodeOptions::new();
// Configure the decoder such that it will expand the image to RGBA.
decoder.set_color_output(gif::ColorOutput::RGBA);
// Read the file header
let file = File::open(file_name).map_err(|e| {
error!("Could not open {file_name:?}: {e:?}");
e
})?;
let mut decoder = decoder.read_info(file)?;
let height = decoder.height();
let width = decoder.width();
let pixels: Vec<Pixel> =
vec![Pixel::default(); (decoder.width() as u32 * decoder.height() as u32) as usize];
let mut image = AnimeImage::new(
Vec2::new(scale, scale),
angle,
translation,
brightness,
pixels,
decoder.width() as u32,
anime_type,
)?;
while let Some(frame) = decoder.read_next_frame()? {
let wait = frame.delay * 10;
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,
anime_type,
)?;
}
for (y, row) in frame.buffer.chunks(frame.width as usize * 4).enumerate() {
for (x, px) in row.chunks(4).enumerate() {
if px[3] != 255 {
// should be t but not in some gifs? What, ASUS, what?
continue;
}
let pos =
(x + frame.left as usize) + ((y + frame.top as usize) * width as usize);
image.get_mut()[pos] = Pixel {
color: ((px[0] as u32 + px[1] as u32 + px[2] as u32) / 3),
alpha: 1.0,
};
}
}
image.update();
frames.push(AnimeFrame {
data: <AnimeDataBuffer>::try_from(&image)?,
delay: Duration::from_millis(wait as u64),
});
}
Ok(Self(frames, duration))
}
/// Make a static gif out of a greyscale png. If no duration is specified
/// then the default will be 1 second long. If `AnimTime::Cycles` is
/// specified for `duration` then this can be considered how many
/// seconds the image will show for.
#[inline]
pub fn from_png(
file_name: &Path,
scale: f32,
angle: f32,
translation: Vec2,
duration: AnimTime,
brightness: f32,
anime_type: AnimeType,
) -> Result<Self> {
let image =
AnimeImage::from_png(file_name, scale, angle, translation, brightness, anime_type)?;
let mut total = Duration::from_millis(1000);
if let AnimTime::Fade(fade) = duration {
total = fade.total_fade_time();
if let Some(middle) = fade.show_for {
total += middle;
}
}
// Make frame delay 30ms, and find frame count
let frame_count = total.as_millis() / 30;
let single = AnimeFrame {
data: <AnimeDataBuffer>::try_from(&image)?,
delay: Duration::from_millis(30),
};
let frames = vec![single; frame_count as usize];
Ok(Self(frames, duration))
}
/// Get a slice of the frames this gif has
#[inline]
pub fn frames(&self) -> &[AnimeFrame] {
&self.0
}
/// Get the time/count for this gif
#[inline]
pub fn duration(&self) -> AnimTime {
self.1
}
/// Get the frame count
pub fn frame_count(&self) -> usize {
self.0.len()
}
/// Get total gif time for one run
pub fn total_frame_time(&self) -> Duration {
let mut time = 0;
self.0.iter().for_each(|f| time += f.delay.as_millis());
Duration::from_millis(time as u64)
}
}