use std::{convert::TryFrom, ops::Not};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Brightness {
pub level: u16,
}
impl Brightness {
pub const MIN: Self = Self { level: u16::min_value() };
pub const MAX: Self = Self { level: u16::max_value() };
pub(crate) fn spread(self, soft_max: u16) -> Self {
assert!(self.level <= soft_max);
let level = u32::from(self.level);
let soft_max = u32::from(soft_max);
let max = u32::from(u16::max_value());
let converted = (level * max + soft_max / 2 + 1) / soft_max;
Self { level: u16::try_from(converted).expect("Color brigthness bug") }
}
pub(crate) fn compress(self, soft_max: u16) -> Self {
let level = u32::from(self.level);
let soft_max = u32::from(soft_max);
let max = u32::from(u16::max_value());
let converted = (level * soft_max + max / 2 + 1) / max;
Self { level: u16::try_from(converted).expect("Color brigthness bug") }
}
}
impl Default for Brightness {
fn default() -> Self {
Self::MAX
}
}
impl Not for Brightness {
type Output = Self;
fn not(self) -> Self::Output {
Self {
level: if Self::MAX.level / 3 > self.level {
self.level * 3
} else {
self.level / 3
},
}
}
}
pub trait ApproxBrightness {
fn approx_brightness(&self) -> Brightness;
fn set_approx_brightness(&mut self, brightness: Brightness);
fn with_approx_brightness(mut self, brightness: Brightness) -> Self
where
Self: Sized,
{
self.set_approx_brightness(brightness);
self
}
}
#[derive(Debug, Clone, Copy)]
pub struct Channel {
value: u8,
weight: u8,
weighted: u16,
}
impl Channel {
pub fn new(value: u8, weight: u8) -> Self {
let mut this = Self { value, weight, weighted: 0 };
this.update_cache();
this
}
pub fn value(self) -> u8 {
self.value
}
#[allow(dead_code)]
pub fn weight(self) -> u8 {
self.weight
}
fn weighted(self) -> u16 {
self.weighted
}
fn set_value(&mut self, value: u8) {
self.value = value;
self.update_cache();
}
fn update_cache(&mut self) {
self.weighted = u16::from(self.value) * u16::from(self.weight);
}
}
#[derive(Debug)]
pub struct ChannelVector<'channels> {
soft_max: u8,
channels: &'channels mut [Channel],
total_value: u64,
total_weights: u64,
}
impl<'channels> ChannelVector<'channels> {
pub fn new(channels: &'channels mut [Channel], soft_max: u8) -> Self {
let mut this =
Self { total_value: 0, channels, soft_max, total_weights: 0 };
this.update_cache();
this.total_weights =
this.channels.iter().map(|chan| chan.weight).map(u64::from).sum();
assert!(this.total_weights > 0);
this
}
fn raw_brightness(&self) -> u64 {
(self.total_value + self.total_weights / 2 + 1) / self.total_weights
}
fn update<F, T>(&mut self, updater: F) -> T
where
F: FnOnce(&mut [Channel]) -> T,
{
let ret = updater(self.channels);
self.update_cache();
ret
}
fn update_cache(&mut self) {
self.total_value = self
.channels
.iter()
.copied()
.map(Channel::weighted)
.map(u64::from)
.sum();
}
fn set_brightness_total_zero(&mut self, level: u64) {
let res = u8::try_from(level / self.total_weights);
let value = res.unwrap_or(self.soft_max);
self.update(|channels| {
for entry in channels {
entry.set_value(value);
}
});
}
fn set_brightness_total_nonzero(&mut self, level: u64) {
let new_total = level;
let total_value = self.total_value;
let soft_max = self.soft_max;
self.update(|channels| {
for entry in channels {
let lifted = u64::from(entry.value) * new_total;
let divided = (lifted + total_value / 2 + 1) / total_value;
let res = u8::try_from(divided);
entry.set_value(res.unwrap_or(soft_max).min(soft_max));
}
});
}
}
impl<'channels> ApproxBrightness for ChannelVector<'channels> {
fn approx_brightness(&self) -> Brightness {
let max = u16::from(self.soft_max);
let level =
u16::try_from(self.raw_brightness()).expect("Color brightness bug");
Brightness { level }.spread(max)
}
fn set_approx_brightness(&mut self, brightness: Brightness) {
let max = u64::from(self.soft_max) * self.total_weights;
let max = u16::try_from(max).expect("Color brightness bug");
let level = u64::from(brightness.compress(max).level);
if self.total_value == 0 {
self.set_brightness_total_zero(level);
} else {
self.set_brightness_total_nonzero(level);
}
}
}