summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPhilipp Gesang <phg@phi-gamma.net>2021-11-22 00:24:59 +0100
committerPhilipp Gesang <phg@phi-gamma.net>2021-11-23 12:56:31 +0100
commit84a552f3265d82e9fa553e9ddd3597a56739cbee (patch)
tree7ca4891177a75bd497ee38f434fcd4c541c4230e
parent6126258a2947cf3421c1c84634b307517a93505b (diff)
downloadvtcol-84a552f3265d82e9fa553e9ddd3597a56739cbee.tar.gz
implement fading
vtcol fade --ms 1000 --frequency 10 --to solarized & dmesg
-rw-r--r--src/lib.rs99
-rw-r--r--src/vtcol.rs99
2 files changed, 195 insertions, 3 deletions
diff --git a/src/lib.rs b/src/lib.rs
index 213d58c..5952517 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -2,7 +2,8 @@ use std::{convert::TryFrom,
fmt,
io::{self, BufWriter, Error, Write},
os::unix::io::{AsRawFd, RawFd},
- path::{Path, PathBuf}};
+ path::{Path, PathBuf},
+ time::{Duration, Instant}};
trait IsMinusOne
{
@@ -776,3 +777,99 @@ impl fmt::Display for Console
write!(f, "Console(fd={})", self.0)
}
}
+
+/* Use doubles for fractional scaling. */
+struct FadePalette([f64; PALETTE_SIZE * 3]);
+
+impl FadePalette
+{
+ fn new() -> Self { Self([0f64; PALETTE_SIZE * 3]) }
+
+ /** Linear interpolation between self and ``goal``.
+
+ We scale each byte individually so there can be a maximum
+ of 256 interpolation steps.
+ */
+ fn towards(&self, goal: &FadePalette, progress: f64) -> FadePalette
+ {
+ let mut res = FadePalette::new();
+
+ for (i, (a, b)) in self.0.iter().zip(goal.0.iter()).enumerate() {
+ res.0[i] = a + (b - a) * progress;
+ }
+
+ res
+ }
+}
+
+impl From<&Palette> for FadePalette
+{
+ fn from(pal: &Palette) -> Self
+ {
+ let mut fpal = Self::new();
+ pal.0.iter().enumerate().for_each(|(i, &b)| {
+ fpal.0[i] = b as f64;
+ });
+ fpal
+ }
+}
+
+impl From<&FadePalette> for Palette
+{
+ fn from(fpal: &FadePalette) -> Self
+ {
+ let mut pal = Self::new();
+ fpal.0.iter().enumerate().for_each(|(i, &b)| {
+ let b = if b < 0f64 {
+ 0
+ } else if 256f64 <= b {
+ 255
+ } else {
+ b.round() as u8
+ };
+ pal.0[i] = b;
+ });
+ pal
+ }
+}
+
+pub struct Fade
+{
+ from: Palette,
+ to: Palette,
+ hz: u8,
+ duration: Duration,
+}
+
+impl Fade
+{
+ pub fn new(from: Palette, to: Palette, duration: Duration, hz: u8) -> Self
+ {
+ let hz = if hz == 0 { 1 } else { hz };
+ Self { from, to, hz, duration }
+ }
+
+ pub fn commence(self, con: &Console) -> io::Result<()>
+ {
+ let Self { from, to, hz, duration } = self;
+ con.apply_palette(&from)?;
+
+ let fade = FadePalette::from(&con.current_palette()?);
+ let fade_to = FadePalette::from(&to);
+ let t_0 = Instant::now();
+ let tick = Duration::from_millis(1_000u64 / hz as u64);
+ let iters = (duration.as_millis() / tick.as_millis()) as u32;
+
+ let mut i = 0;
+ while i < iters {
+ i += 1;
+ let progress = f64::from(i) / f64::from(iters);
+ let pal = Palette::from(&fade.towards(&fade_to, progress));
+ con.apply_palette(&pal)?;
+ let next = i * tick;
+ std::thread::sleep(next.saturating_sub(t_0.elapsed()));
+ }
+
+ Ok(())
+ }
+}
diff --git a/src/vtcol.rs b/src/vtcol.rs
index b0c4b85..3694753 100644
--- a/src/vtcol.rs
+++ b/src/vtcol.rs
@@ -1,13 +1,17 @@
pub mod lib;
-use vtcol::{Console, Palette, Scheme};
+use vtcol::{Console, Fade, Palette, Scheme};
use anyhow::{anyhow, Result};
use std::{io::{self, BufWriter},
- sync::atomic::{AtomicBool, Ordering}};
+ sync::atomic::{AtomicBool, Ordering},
+ time::Duration};
static VERBOSITY: AtomicBool = AtomicBool::new(false);
+const DEFAULT_FADE_DURATION_MS: u64 = 500;
+const DEFAULT_FADE_UPDATE_HZ: u8 = 25;
+
macro_rules! vrb {
( $( $e:expr ),* ) => {(
if VERBOSITY.load(Ordering::SeqCst) { println!( $( $e ),* ) }
@@ -29,6 +33,8 @@ enum Job
Get,
/** Toggle between two schemes. */
Toggle(Scheme, Scheme),
+ /** Fade from current scheme to another. */
+ Fade(Option<Scheme>, Scheme, Duration, u8),
}
impl<'a> Job
@@ -91,6 +97,43 @@ impl<'a> Job
.takes_value(true),
),
)
+ .subcommand(
+ SubCommand::with_name("fade")
+ .about("fade from one scheme to another")
+ .arg(
+ Arg::with_name("from")
+ .short("f")
+ .long("from")
+ .value_name("NAME1")
+ .help("initial color scheme (default: current)")
+ .takes_value(true),
+ )
+ .arg(
+ Arg::with_name("to")
+ .short("t")
+ .long("to")
+ .value_name("NAME2")
+ .help("final color scheme")
+ .takes_value(true)
+ .required(true),
+ )
+ .arg(
+ Arg::with_name("ms")
+ .value_name("MS")
+ .short("m")
+ .long("ms")
+ .help("how long (in ms) the fade should take")
+ .takes_value(true),
+ )
+ .arg(
+ Arg::with_name("frequency")
+ .value_name("HZ")
+ .short("h")
+ .long("frequency")
+ .help("rate (HZ/s) of intermediate scheme changes")
+ .takes_value(true),
+ ),
+ )
.arg(
Arg::with_name("verbose")
.short("v")
@@ -148,6 +191,31 @@ impl<'a> Job
)),
}
},
+ ("fade", Some(subm)) => {
+ let dur: u64 = if let Some(ms) = subm.value_of("ms") {
+ ms.parse()?
+ } else {
+ DEFAULT_FADE_DURATION_MS
+ };
+ let hz: u8 = if let Some(ms) = subm.value_of("frequency") {
+ ms.parse()?
+ } else {
+ DEFAULT_FADE_UPDATE_HZ
+ };
+ let dur = Duration::from_millis(dur);
+
+ match (subm.value_of("from"), subm.value_of("to")) {
+ (_, None) =>
+ Err(anyhow!("please supply color scheme to fade to")),
+ (from, Some(to)) =>
+ Ok(Self::Fade(
+ from.map(Scheme::from),
+ Scheme::from(to),
+ dur,
+ hz,
+ )),
+ }
+ },
(junk, _) =>
Err(anyhow!(
"invalid subcommand [{}]; try ``{} --help``",
@@ -212,6 +280,7 @@ impl<'a> Job
Self::Set(scm) => Self::set_scheme(scm)?,
Self::Get => Self::get_scheme()?,
Self::Toggle(one, two) => Self::toggle_scheme(one, two)?,
+ Self::Fade(from, to, ms, hz) => Self::fade(from, to, ms, hz)?,
}
Ok(())
@@ -257,6 +326,32 @@ impl<'a> Job
Self::set_scheme(one)
}
}
+
+ /** Fade from one scheme to another.
+
+ If ``from`` is ``None``, the current palette is used as starting point. */
+ fn fade(
+ from: Option<Scheme>,
+ to: Scheme,
+ dur: Duration,
+ hz: u8,
+ ) -> Result<()>
+ {
+ let fd = Console::current()?;
+ vrb!("console fd: {}", fd);
+
+ let from = if let Some(from) = from {
+ Palette::try_from(&from)?
+ } else {
+ fd.current_palette()?
+ };
+ let to = Palette::try_from(&to)?;
+
+ let fade = Fade::new(from, to, dur, hz);
+
+ fade.commence(&fd)?;
+ Ok(())
+ }
} /* [impl Job] */
fn main() -> Result<()>