Skip to content

Commit

Permalink
add Gamma ramp; properly cumulate temp, brightness, gamma, inversion
Browse files Browse the repository at this point in the history
Inversion now flips the cumulated gamma ramp left-to-right (rather
than upside-donw), keeping any temperature, brightness and gamma
adjustments.

The script toggle-invert-display.sh inverts the display colors and
adjusts gamma in the inverted mode to compensate for perceptual
nonlinearity.
  • Loading branch information
piater authored and MaxVerevkin committed Sep 12, 2022
1 parent 3a03aa5 commit 983b152
Show file tree
Hide file tree
Showing 5 changed files with 104 additions and 11 deletions.
19 changes: 18 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,12 @@ $ busctl --user introspect rs.wl-gammarelay / rs.wl.gammarelay
NAME TYPE SIGNATURE RESULT/VALUE FLAGS
.ToggleInverted method - - -
.UpdateBrightness method d - -
.UpdateGamma method d - -
.UpdateTemperature method n - -
.Brightness property d 1 emits-change writable
.Gamma property d 1 emits-change writable
.Inverted property b false emits-change writable
.Temperature property q 4500 emits-change writable
.Temperature property q 6500 emits-change writable
```

## Installation
Expand Down Expand Up @@ -42,6 +44,12 @@ i3status-rust hueshift block has the builtin support for this backend since 0.21
"on-scroll-up": "busctl --user -- call rs.wl-gammarelay / rs.wl.gammarelay UpdateBrightness d +0.02",
"on-scroll-down": "busctl --user -- call rs.wl-gammarelay / rs.wl.gammarelay UpdateBrightness d -0.02"
}
"custom/wl-gammarelay-gamma": {
"format": "{}% γ",
"exec": "wl-gammarelay-rs watch {g}",
"on-scroll-up": "busctl --user -- call rs.wl-gammarelay / rs.wl.gammarelay UpdateGamma d +0.02",
"on-scroll-down": "busctl --user -- call rs.wl-gammarelay / rs.wl.gammarelay UpdateGamma d -0.02"
}
```

## Watch for changes
Expand Down Expand Up @@ -86,4 +94,13 @@ busctl --user call rs.wl-gammarelay / rs.wl.gammarelay UpdateBrightness d 0.1

# Decrease the brightness by `10%`:
busctl --user -- call rs.wl-gammarelay / rs.wl.gammarelay UpdateBrightness d -0.1

# Set display gamma to `1.0`:
busctl --user set-property rs.wl-gammarelay / rs.wl.gammarelay Gamma d 1

# Increase gamma by `0.1`:
busctl --user call rs.wl-gammarelay / rs.wl.gammarelay UpdateGamma d 0.1

# Decrease gamma by `0.1`:
busctl --user -- call rs.wl-gammarelay / rs.wl.gammarelay UpdateGamma d -0.1
```
29 changes: 22 additions & 7 deletions src/color.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Color {
pub temp: u16,
pub gamma: f64,
pub brightness: f64,
pub inverted: bool,
}
Expand All @@ -10,12 +11,24 @@ impl Default for Color {
fn default() -> Self {
Self {
temp: 6500,
gamma: 1.0,
brightness: 1.0,
inverted: false,
}
}
}

fn map_intensity(v: f64, white: f64, color: Color, v_max_gamma: f64) -> u16 {
// A gamma ramp is computed as f(x) = x^γ, for x ∈ [0,1].
// Multiple gamma adjustments can reasonably be combined as
// f(x) = x^(γ₁γ₂) = (x^γ₁)^γ₂.
// Here, x^γ₁ ≡ (v * white) is the color-temperature-adjusted intensity,
// and γ₂ is the overall gamma correction.
// The factor v_max_gamma adjusts for the fact that generally v ∉ [0,1]:
// (v/v_max)^γ * v_max = v^γ * v_max^(1-γ)
((v * white).powf(color.gamma) * v_max_gamma) as u16
}

pub fn colorramp_fill(r: &mut [u16], g: &mut [u16], b: &mut [u16], ramp_size: usize, color: Color) {
let color_i = ((color.temp as usize - 1000) / 100) * 3;
let [white_r, white_g, white_b] = interpolate_color(
Expand All @@ -24,17 +37,19 @@ pub fn colorramp_fill(r: &mut [u16], g: &mut [u16], b: &mut [u16], ramp_size: us
&BLACKBODY_COLOR[(color_i + 3)..],
);

let step = u16::MAX as f64 * color.brightness / (ramp_size - 1) as f64;
let v_max = u16::MAX as f64 * color.brightness;
let v_max_gamma = v_max.powf(1.0 - color.gamma);
let step = v_max / (ramp_size - 1) as f64;
for i in 0..ramp_size {
let v = step * i as f64;
if !color.inverted {
r[i] = (v * white_r) as u16;
g[i] = (v * white_g) as u16;
b[i] = (v * white_b) as u16;
r[i] = map_intensity(v, white_r, color, v_max_gamma);
g[i] = map_intensity(v, white_g, color, v_max_gamma);
b[i] = map_intensity(v, white_b, color, v_max_gamma);
} else {
r[i] = u16::MAX - (v * white_r) as u16;
g[i] = u16::MAX - (v * white_g) as u16;
b[i] = u16::MAX - (v * white_b) as u16;
r[ramp_size - 1 - i] = map_intensity(v, white_r, color, v_max_gamma);
g[ramp_size - 1 - i] = map_intensity(v, white_g, color, v_max_gamma);
b[ramp_size - 1 - i] = map_intensity(v, white_b, color, v_max_gamma);
}
}
}
Expand Down
19 changes: 17 additions & 2 deletions src/dbus_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,27 +6,33 @@ pub async fn watch_dbus(format: &str) -> Result<()> {
let conn = zbus::ConnectionBuilder::session()?.build().await?;
let proxy = BusInterfaceProxy::new(&conn).await?;
let mut temperature = proxy.temperature().await?;
let mut gamma = proxy.gamma().await?;
let mut brightness = proxy.brightness().await?;
let mut t_stream = proxy.receive_temperature_changed().await;
let mut g_stream = proxy.receive_gamma_changed().await;
let mut b_stream = proxy.receive_brightness_changed().await;
loop {
print_formatted(format, temperature, brightness);
print_formatted(format, temperature, gamma, brightness);
tokio::select! {
Some(t) = t_stream.next() => {
temperature = t.get().await?;
}
Some(g) = g_stream.next() => {
gamma = g.get().await?;
}
Some(b) = b_stream.next() => {
brightness = b.get().await?;
}
}
}
}

fn print_formatted(format: &str, temperature: u16, brightness: f64) {
fn print_formatted(format: &str, temperature: u16, gamma: f64, brightness: f64) {
println!(
"{}",
format
.replace("{t}", &temperature.to_string())
.replace("{g}", &format!("{:.2}", gamma))
.replace("{b}", &format!("{:.2}", brightness))
.replace("{bp}", &format!("{:.0}", brightness * 100.))
);
Expand All @@ -41,6 +47,9 @@ trait BusInterface {
/// UpdateBrightness method
fn update_brightness(&self, delta_brightness: f64) -> zbus::Result<()>;

/// UpdateGamma method
fn update_gamma(&self, delta_gamma: f64) -> zbus::Result<()>;

/// UpdateTemperature method
fn update_temperature(&self, delta_temp: i16) -> zbus::Result<()>;

Expand All @@ -50,6 +59,12 @@ trait BusInterface {
#[dbus_proxy(property)]
fn set_brightness(&self, value: f64) -> zbus::Result<()>;

/// Gamma property
#[dbus_proxy(property)]
fn gamma(&self) -> zbus::Result<f64>;
#[dbus_proxy(property)]
fn set_gamma(&self, value: f64) -> zbus::Result<()>;

/// Temperature property
#[dbus_proxy(property)]
fn temperature(&self) -> zbus::Result<u16>;
Expand Down
31 changes: 30 additions & 1 deletion src/dbus_server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,35 @@ impl Server {
Ok(())
}

#[dbus_interface(property)]
async fn gamma(&self) -> f64 {
self.color.gamma
}

#[dbus_interface(property)]
async fn set_gamma(&mut self, gamma: f64) -> Result<(), zbus::fdo::Error> {
if gamma > 0.0 {
self.color.gamma = gamma;
self.send_color().await;
Ok(())
} else {
Err(zbus::fdo::Error::InvalidArgs(
"gamma must be greater than zero".into(),
))
}
}

async fn update_gamma(
&mut self,
#[zbus(signal_context)] cx: zbus::SignalContext<'_>,
delta_gamma: f64,
) -> Result<(), zbus::fdo::Error> {
self.color.gamma = (self.color.gamma + delta_gamma).max(0.0);
self.send_color().await;
self.gamma_changed(&cx).await?;
Ok(())
}

#[dbus_interface(property)]
async fn brightness(&self) -> f64 {
self.color.brightness
Expand All @@ -89,7 +118,7 @@ impl Server {
#[zbus(signal_context)] cx: zbus::SignalContext<'_>,
delta_brightness: f64,
) -> Result<(), zbus::fdo::Error> {
self.color.brightness = (self.color.brightness + delta_brightness).clamp(0.0, 1.0) as _;
self.color.brightness = (self.color.brightness + delta_brightness).clamp(0.0, 1.0);
self.send_color().await;
self.brightness_changed(&cx).await?;
Ok(())
Expand Down
17 changes: 17 additions & 0 deletions toggle-invert-display.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#!/bin/sh

# The human eye is more sensitive to contrast at high than at low
# levels of intensity. Thus, contast at high levels of intensity is
# lost if the display is inverted. This script compensates for this by
# gamma correction. The value of γ=0.6 is not based on any theory;
# adjust it to your liking.

dbus="rs.wl-gammarelay / rs.wl.gammarelay"

if [ "$(busctl --user get-property $dbus Inverted)" = "b false" ]; then
busctl --user set-property $dbus Inverted b true
busctl --user set-property $dbus Gamma d 0.6
else
busctl --user set-property $dbus Inverted b false
busctl --user set-property $dbus Gamma d 1.0
fi

0 comments on commit 983b152

Please sign in to comment.