Implement gradient color interpolation option (#548)

* Added the better color averaging code (tested & functional)

* rustfmt

* Make Color f32 0..1, clarify premul/unpremul

* Fix imports and test name

* Premultiply gradient colors matching CSS

* Fix indentation

* fixup

* Add gradient image

---------

Co-authored-by: K's Thinkpad <K.T.Kraft@protonmail.com>
This commit is contained in:
Ivan Molodetskikh
2024-07-16 10:22:03 +03:00
committed by GitHub
parent 0824737757
commit 3ace97660f
27 changed files with 1092 additions and 79 deletions
+215 -43
View File
@@ -410,8 +410,8 @@ impl Default for FocusRing {
Self { Self {
off: false, off: false,
width: FloatOrInt(4.), width: FloatOrInt(4.),
active_color: Color::new(127, 200, 255, 255), active_color: Color::from_rgba8_unpremul(127, 200, 255, 255),
inactive_color: Color::new(80, 80, 80, 255), inactive_color: Color::from_rgba8_unpremul(80, 80, 80, 255),
active_gradient: None, active_gradient: None,
inactive_gradient: None, inactive_gradient: None,
} }
@@ -428,6 +428,8 @@ pub struct Gradient {
pub angle: i16, pub angle: i16,
#[knuffel(property, default)] #[knuffel(property, default)]
pub relative_to: GradientRelativeTo, pub relative_to: GradientRelativeTo,
#[knuffel(property(name = "in"), str, default)]
pub in_: GradientInterpolation,
} }
#[derive(knuffel::DecodeScalar, Debug, Default, Clone, Copy, PartialEq, Eq)] #[derive(knuffel::DecodeScalar, Debug, Default, Clone, Copy, PartialEq, Eq)]
@@ -437,6 +439,30 @@ pub enum GradientRelativeTo {
WorkspaceView, WorkspaceView,
} }
#[derive(Default, Debug, Clone, Copy, PartialEq)]
pub struct GradientInterpolation {
pub color_space: GradientColorSpace,
pub hue_interpolation: HueInterpolation,
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
pub enum GradientColorSpace {
#[default]
Srgb,
SrgbLinear,
Oklab,
Oklch,
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
pub enum HueInterpolation {
#[default]
Shorter,
Longer,
Increasing,
Decreasing,
}
#[derive(knuffel::Decode, Debug, Clone, Copy, PartialEq)] #[derive(knuffel::Decode, Debug, Clone, Copy, PartialEq)]
pub struct Border { pub struct Border {
#[knuffel(child)] #[knuffel(child)]
@@ -458,8 +484,8 @@ impl Default for Border {
Self { Self {
off: true, off: true,
width: FloatOrInt(4.), width: FloatOrInt(4.),
active_color: Color::new(255, 200, 127, 255), active_color: Color::from_rgba8_unpremul(255, 200, 127, 255),
inactive_color: Color::new(80, 80, 80, 255), inactive_color: Color::from_rgba8_unpremul(80, 80, 80, 255),
active_gradient: None, active_gradient: None,
inactive_gradient: None, inactive_gradient: None,
} }
@@ -492,23 +518,49 @@ impl From<FocusRing> for Border {
} }
} }
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] /// RGB color in [0, 1] with unpremultiplied alpha.
#[derive(Debug, Default, Clone, Copy, PartialEq)]
pub struct Color { pub struct Color {
pub r: u8, pub r: f32,
pub g: u8, pub g: f32,
pub b: u8, pub b: f32,
pub a: u8, pub a: f32,
} }
impl Color { impl Color {
pub const fn new(r: u8, g: u8, b: u8, a: u8) -> Self { pub const fn new_unpremul(r: f32, g: f32, b: f32, a: f32) -> Self {
Self { r, g, b, a } Self { r, g, b, a }
} }
}
impl From<Color> for [f32; 4] { pub fn from_rgba8_unpremul(r: u8, g: u8, b: u8, a: u8) -> Self {
fn from(c: Color) -> Self { Self::from_array_unpremul([r, g, b, a].map(|x| x as f32 / 255.))
let [r, g, b, a] = [c.r, c.g, c.b, c.a].map(|x| x as f32 / 255.); }
pub fn from_array_premul([r, g, b, a]: [f32; 4]) -> Self {
let a = a.clamp(0., 1.);
if a == 0. {
Self::new_unpremul(0., 0., 0., 0.)
} else {
Self {
r: (r / a).clamp(0., 1.),
g: (g / a).clamp(0., 1.),
b: (b / a).clamp(0., 1.),
a,
}
}
}
pub fn from_array_unpremul([r, g, b, a]: [f32; 4]) -> Self {
Self { r, g, b, a }
}
pub fn to_array_unpremul(self) -> [f32; 4] {
[self.r, self.g, self.b, self.a]
}
pub fn to_array_premul(self) -> [f32; 4] {
let [r, g, b, a] = [self.r, self.g, self.b, self.a];
[r * a, g * a, b * a, a] [r * a, g * a, b * a, a]
} }
} }
@@ -1429,12 +1481,73 @@ impl CornerRadius {
} }
} }
impl FromStr for GradientInterpolation {
type Err = miette::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut iter = s.split_whitespace();
let in_part1 = iter.next();
let in_part2 = iter.next();
let in_part3 = iter.next();
let Some(in_part1) = in_part1 else {
return Err(miette!("missing color space"));
};
let color = match in_part1 {
"srgb" => GradientColorSpace::Srgb,
"srgb-linear" => GradientColorSpace::SrgbLinear,
"oklab" => GradientColorSpace::Oklab,
"oklch" => GradientColorSpace::Oklch,
x => {
return Err(miette!(
"invalid color space {x}; can be srgb, srgb-linear, oklab or oklch"
))
}
};
let interpolation = if let Some(in_part2) = in_part2 {
if color != GradientColorSpace::Oklch {
return Err(miette!("only oklch color space can have hue interpolation"));
}
if in_part3 != Some("hue") {
return Err(miette!(
"interpolation must end with \"hue\", like \"oklch shorter hue\""
));
} else if iter.next().is_some() {
return Err(miette!("unexpected text after hue interpolation"));
} else {
match in_part2 {
"shorter" => HueInterpolation::Shorter,
"longer" => HueInterpolation::Longer,
"increasing" => HueInterpolation::Increasing,
"decreasing" => HueInterpolation::Decreasing,
x => {
return Err(miette!(
"invalid hue interpolation {x}; \
can be shorter, longer, increasing, decreasing"
))
}
}
}
} else {
HueInterpolation::default()
};
Ok(Self {
color_space: color,
hue_interpolation: interpolation,
})
}
}
impl FromStr for Color { impl FromStr for Color {
type Err = miette::Error; type Err = miette::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> { fn from_str(s: &str) -> Result<Self, Self::Err> {
let [r, g, b, a] = csscolorparser::parse(s).into_diagnostic()?.to_rgba8(); let color = csscolorparser::parse(s).into_diagnostic()?.to_array();
Ok(Self { r, g, b, a }) Ok(Self::from_array_unpremul(color.map(|x| x as f32)))
} }
} }
@@ -1453,7 +1566,7 @@ struct ColorRgba {
impl From<ColorRgba> for Color { impl From<ColorRgba> for Color {
fn from(value: ColorRgba) -> Self { fn from(value: ColorRgba) -> Self {
let ColorRgba { r, g, b, a } = value; let ColorRgba { r, g, b, a } = value;
Self { r, g, b, a } Self::from_array_unpremul([r, g, b, a].map(|x| x as f32 / 255.))
} }
} }
@@ -2771,41 +2884,25 @@ mod tests {
focus_ring: FocusRing { focus_ring: FocusRing {
off: false, off: false,
width: FloatOrInt(5.), width: FloatOrInt(5.),
active_color: Color { active_color: Color::from_rgba8_unpremul(0, 100, 200, 255),
r: 0, inactive_color: Color::from_rgba8_unpremul(255, 200, 100, 0),
g: 100,
b: 200,
a: 255,
},
inactive_color: Color {
r: 255,
g: 200,
b: 100,
a: 0,
},
active_gradient: Some(Gradient { active_gradient: Some(Gradient {
from: Color::new(10, 20, 30, 255), from: Color::from_rgba8_unpremul(10, 20, 30, 255),
to: Color::new(0, 128, 255, 255), to: Color::from_rgba8_unpremul(0, 128, 255, 255),
angle: 180, angle: 180,
relative_to: GradientRelativeTo::WorkspaceView, relative_to: GradientRelativeTo::WorkspaceView,
in_: GradientInterpolation {
color_space: GradientColorSpace::Srgb,
hue_interpolation: HueInterpolation::Shorter,
},
}), }),
inactive_gradient: None, inactive_gradient: None,
}, },
border: Border { border: Border {
off: false, off: false,
width: FloatOrInt(3.), width: FloatOrInt(3.),
active_color: Color { active_color: Color::from_rgba8_unpremul(255, 200, 127, 255),
r: 255, inactive_color: Color::from_rgba8_unpremul(255, 200, 100, 0),
g: 200,
b: 127,
a: 255,
},
inactive_color: Color {
r: 255,
g: 200,
b: 100,
a: 0,
},
active_gradient: None, active_gradient: None,
inactive_gradient: None, inactive_gradient: None,
}, },
@@ -3095,6 +3192,81 @@ mod tests {
assert!("10% ".parse::<SizeChange>().is_err()); assert!("10% ".parse::<SizeChange>().is_err());
} }
#[test]
fn parse_gradient_interpolation() {
assert_eq!(
"srgb".parse::<GradientInterpolation>().unwrap(),
GradientInterpolation {
color_space: GradientColorSpace::Srgb,
..Default::default()
}
);
assert_eq!(
"srgb-linear".parse::<GradientInterpolation>().unwrap(),
GradientInterpolation {
color_space: GradientColorSpace::SrgbLinear,
..Default::default()
}
);
assert_eq!(
"oklab".parse::<GradientInterpolation>().unwrap(),
GradientInterpolation {
color_space: GradientColorSpace::Oklab,
..Default::default()
}
);
assert_eq!(
"oklch".parse::<GradientInterpolation>().unwrap(),
GradientInterpolation {
color_space: GradientColorSpace::Oklch,
..Default::default()
}
);
assert_eq!(
"oklch shorter hue"
.parse::<GradientInterpolation>()
.unwrap(),
GradientInterpolation {
color_space: GradientColorSpace::Oklch,
hue_interpolation: HueInterpolation::Shorter,
}
);
assert_eq!(
"oklch longer hue".parse::<GradientInterpolation>().unwrap(),
GradientInterpolation {
color_space: GradientColorSpace::Oklch,
hue_interpolation: HueInterpolation::Longer,
}
);
assert_eq!(
"oklch decreasing hue"
.parse::<GradientInterpolation>()
.unwrap(),
GradientInterpolation {
color_space: GradientColorSpace::Oklch,
hue_interpolation: HueInterpolation::Decreasing,
}
);
assert_eq!(
"oklch increasing hue"
.parse::<GradientInterpolation>()
.unwrap(),
GradientInterpolation {
color_space: GradientColorSpace::Oklch,
hue_interpolation: HueInterpolation::Increasing,
}
);
assert!("".parse::<GradientInterpolation>().is_err());
assert!("srgb shorter hue".parse::<GradientInterpolation>().is_err());
assert!("oklch shorter".parse::<GradientInterpolation>().is_err());
assert!("oklch shorter h".parse::<GradientInterpolation>().is_err());
assert!("oklch a hue".parse::<GradientInterpolation>().is_err());
assert!("oklch shorter hue a"
.parse::<GradientInterpolation>()
.is_err());
}
#[test] #[test]
fn parse_iso_level3_shift() { fn parse_iso_level3_shift() {
assert_eq!( assert_eq!(
@@ -4,7 +4,7 @@ use std::time::Duration;
use niri::animation::ANIMATION_SLOWDOWN; use niri::animation::ANIMATION_SLOWDOWN;
use niri::render_helpers::border::BorderRenderElement; use niri::render_helpers::border::BorderRenderElement;
use niri_config::CornerRadius; use niri_config::{Color, CornerRadius, GradientInterpolation};
use smithay::backend::renderer::element::RenderElement; use smithay::backend::renderer::element::RenderElement;
use smithay::backend::renderer::gles::GlesRenderer; use smithay::backend::renderer::gles::GlesRenderer;
use smithay::utils::{Logical, Physical, Rectangle, Size}; use smithay::utils::{Logical, Physical, Rectangle, Size};
@@ -64,8 +64,9 @@ impl TestCase for GradientAngle {
[BorderRenderElement::new( [BorderRenderElement::new(
area.size, area.size,
Rectangle::from_loc_and_size((0., 0.), area.size), Rectangle::from_loc_and_size((0., 0.), area.size),
[1., 0., 0., 1.], GradientInterpolation::default(),
[0., 1., 0., 1.], Color::new_unpremul(1., 0., 0., 1.),
Color::new_unpremul(0., 1., 0., 1.),
self.angle - FRAC_PI_2, self.angle - FRAC_PI_2,
Rectangle::from_loc_and_size((0., 0.), area.size), Rectangle::from_loc_and_size((0., 0.), area.size),
0., 0.,
+5 -4
View File
@@ -5,7 +5,7 @@ use std::time::Duration;
use niri::animation::ANIMATION_SLOWDOWN; use niri::animation::ANIMATION_SLOWDOWN;
use niri::layout::focus_ring::FocusRing; use niri::layout::focus_ring::FocusRing;
use niri::render_helpers::border::BorderRenderElement; use niri::render_helpers::border::BorderRenderElement;
use niri_config::{Color, CornerRadius, FloatOrInt}; use niri_config::{Color, CornerRadius, FloatOrInt, GradientInterpolation};
use smithay::backend::renderer::element::RenderElement; use smithay::backend::renderer::element::RenderElement;
use smithay::backend::renderer::gles::GlesRenderer; use smithay::backend::renderer::gles::GlesRenderer;
use smithay::utils::{Logical, Physical, Point, Rectangle, Size}; use smithay::utils::{Logical, Physical, Point, Rectangle, Size};
@@ -23,7 +23,7 @@ impl GradientArea {
let border = FocusRing::new(niri_config::FocusRing { let border = FocusRing::new(niri_config::FocusRing {
off: false, off: false,
width: FloatOrInt(1.), width: FloatOrInt(1.),
active_color: Color::new(255, 255, 255, 128), active_color: Color::from_rgba8_unpremul(255, 255, 255, 128),
inactive_color: Color::default(), inactive_color: Color::default(),
active_gradient: None, active_gradient: None,
inactive_gradient: None, inactive_gradient: None,
@@ -104,8 +104,9 @@ impl TestCase for GradientArea {
[BorderRenderElement::new( [BorderRenderElement::new(
area.size, area.size,
g_area, g_area,
[1., 0., 0., 1.], GradientInterpolation::default(),
[0., 1., 0., 1.], Color::new_unpremul(1., 0., 0., 1.),
Color::new_unpremul(0., 1., 0., 1.),
FRAC_PI_4, FRAC_PI_4,
Rectangle::from_loc_and_size((0, 0), rect_size).to_f64(), Rectangle::from_loc_and_size((0, 0), rect_size).to_f64(),
0., 0.,
@@ -0,0 +1,53 @@
use niri::render_helpers::border::BorderRenderElement;
use niri_config::{
Color, CornerRadius, GradientColorSpace, GradientInterpolation, HueInterpolation,
};
use smithay::backend::renderer::element::RenderElement;
use smithay::backend::renderer::gles::GlesRenderer;
use smithay::utils::{Logical, Physical, Rectangle, Size};
use super::TestCase;
pub struct GradientOklab {
gradient_format: GradientInterpolation,
}
impl GradientOklab {
pub fn new(_size: Size<i32, Logical>) -> Self {
Self {
gradient_format: GradientInterpolation {
color_space: GradientColorSpace::Oklab,
hue_interpolation: HueInterpolation::Shorter,
},
}
}
}
impl TestCase for GradientOklab {
fn render(
&mut self,
_renderer: &mut GlesRenderer,
size: Size<i32, Physical>,
) -> Vec<Box<dyn RenderElement<GlesRenderer>>> {
let (a, b) = (size.w / 6, size.h / 3);
let size = (size.w - a * 2, size.h - b * 2);
let area = Rectangle::from_loc_and_size((a, b), size).to_f64();
[BorderRenderElement::new(
area.size,
Rectangle::from_loc_and_size((0., 0.), area.size),
self.gradient_format,
Color::new_unpremul(1., 0., 0., 1.),
Color::new_unpremul(0., 1., 0., 1.),
0.,
Rectangle::from_loc_and_size((0., 0.), area.size),
0.,
CornerRadius::default(),
1.,
)
.with_location(area.loc)]
.into_iter()
.map(|elem| Box::new(elem) as _)
.collect()
}
}
@@ -0,0 +1,51 @@
use niri::render_helpers::border::BorderRenderElement;
use niri_config::{Color, CornerRadius, GradientColorSpace, GradientInterpolation};
use smithay::backend::renderer::element::RenderElement;
use smithay::backend::renderer::gles::GlesRenderer;
use smithay::utils::{Logical, Physical, Rectangle, Size};
use super::TestCase;
pub struct GradientOklabAlpha {
gradient_format: GradientInterpolation,
}
impl GradientOklabAlpha {
pub fn new(_size: Size<i32, Logical>) -> Self {
Self {
gradient_format: GradientInterpolation {
color_space: GradientColorSpace::Oklab,
hue_interpolation: Default::default(),
},
}
}
}
impl TestCase for GradientOklabAlpha {
fn render(
&mut self,
_renderer: &mut GlesRenderer,
size: Size<i32, Physical>,
) -> Vec<Box<dyn RenderElement<GlesRenderer>>> {
let (a, b) = (size.w / 6, size.h / 3);
let size = (size.w - a * 2, size.h - b * 2);
let area = Rectangle::from_loc_and_size((a, b), size).to_f64();
[BorderRenderElement::new(
area.size,
Rectangle::from_loc_and_size((0., 0.), area.size),
self.gradient_format,
Color::new_unpremul(1., 0., 0., 1.),
Color::new_unpremul(0., 1., 0., 0.),
0.,
Rectangle::from_loc_and_size((0., 0.), area.size),
0.,
CornerRadius::default(),
1.,
)
.with_location(area.loc)]
.into_iter()
.map(|elem| Box::new(elem) as _)
.collect()
}
}
@@ -0,0 +1,53 @@
use niri::render_helpers::border::BorderRenderElement;
use niri_config::{
Color, CornerRadius, GradientColorSpace, GradientInterpolation, HueInterpolation,
};
use smithay::backend::renderer::element::RenderElement;
use smithay::backend::renderer::gles::GlesRenderer;
use smithay::utils::{Logical, Physical, Rectangle, Size};
use super::TestCase;
pub struct GradientOklchAlpha {
gradient_format: GradientInterpolation,
}
impl GradientOklchAlpha {
pub fn new(_size: Size<i32, Logical>) -> Self {
Self {
gradient_format: GradientInterpolation {
color_space: GradientColorSpace::Oklch,
hue_interpolation: HueInterpolation::Longer,
},
}
}
}
impl TestCase for GradientOklchAlpha {
fn render(
&mut self,
_renderer: &mut GlesRenderer,
size: Size<i32, Physical>,
) -> Vec<Box<dyn RenderElement<GlesRenderer>>> {
let (a, b) = (size.w / 6, size.h / 3);
let size = (size.w - a * 2, size.h - b * 2);
let area = Rectangle::from_loc_and_size((a, b), size).to_f64();
[BorderRenderElement::new(
area.size,
Rectangle::from_loc_and_size((0., 0.), area.size),
self.gradient_format,
Color::new_unpremul(1., 0., 0., 1.),
Color::new_unpremul(0., 1., 0., 0.),
0.,
Rectangle::from_loc_and_size((0., 0.), area.size),
0.,
CornerRadius::default(),
1.,
)
.with_location(area.loc)]
.into_iter()
.map(|elem| Box::new(elem) as _)
.collect()
}
}
@@ -0,0 +1,53 @@
use niri::render_helpers::border::BorderRenderElement;
use niri_config::{
Color, CornerRadius, GradientColorSpace, GradientInterpolation, HueInterpolation,
};
use smithay::backend::renderer::element::RenderElement;
use smithay::backend::renderer::gles::GlesRenderer;
use smithay::utils::{Logical, Physical, Rectangle, Size};
use super::TestCase;
pub struct GradientOklchDecreasing {
gradient_format: GradientInterpolation,
}
impl GradientOklchDecreasing {
pub fn new(_size: Size<i32, Logical>) -> Self {
Self {
gradient_format: GradientInterpolation {
color_space: GradientColorSpace::Oklch,
hue_interpolation: HueInterpolation::Decreasing,
},
}
}
}
impl TestCase for GradientOklchDecreasing {
fn render(
&mut self,
_renderer: &mut GlesRenderer,
size: Size<i32, Physical>,
) -> Vec<Box<dyn RenderElement<GlesRenderer>>> {
let (a, b) = (size.w / 6, size.h / 3);
let size = (size.w - a * 2, size.h - b * 2);
let area = Rectangle::from_loc_and_size((a, b), size).to_f64();
[BorderRenderElement::new(
area.size,
Rectangle::from_loc_and_size((0., 0.), area.size),
self.gradient_format,
Color::new_unpremul(1., 0., 0., 1.),
Color::new_unpremul(0., 1., 0., 1.),
0.,
Rectangle::from_loc_and_size((0., 0.), area.size),
0.,
CornerRadius::default(),
1.,
)
.with_location(area.loc)]
.into_iter()
.map(|elem| Box::new(elem) as _)
.collect()
}
}
@@ -0,0 +1,53 @@
use niri::render_helpers::border::BorderRenderElement;
use niri_config::{
Color, CornerRadius, GradientColorSpace, GradientInterpolation, HueInterpolation,
};
use smithay::backend::renderer::element::RenderElement;
use smithay::backend::renderer::gles::GlesRenderer;
use smithay::utils::{Logical, Physical, Rectangle, Size};
use super::TestCase;
pub struct GradientOklchIncreasing {
gradient_format: GradientInterpolation,
}
impl GradientOklchIncreasing {
pub fn new(_size: Size<i32, Logical>) -> Self {
Self {
gradient_format: GradientInterpolation {
color_space: GradientColorSpace::Oklch,
hue_interpolation: HueInterpolation::Increasing,
},
}
}
}
impl TestCase for GradientOklchIncreasing {
fn render(
&mut self,
_renderer: &mut GlesRenderer,
size: Size<i32, Physical>,
) -> Vec<Box<dyn RenderElement<GlesRenderer>>> {
let (a, b) = (size.w / 6, size.h / 3);
let size = (size.w - a * 2, size.h - b * 2);
let area = Rectangle::from_loc_and_size((a, b), size).to_f64();
[BorderRenderElement::new(
area.size,
Rectangle::from_loc_and_size((0., 0.), area.size),
self.gradient_format,
Color::new_unpremul(1., 0., 0., 1.),
Color::new_unpremul(0., 1., 0., 1.),
0.,
Rectangle::from_loc_and_size((0., 0.), area.size),
0.,
CornerRadius::default(),
1.,
)
.with_location(area.loc)]
.into_iter()
.map(|elem| Box::new(elem) as _)
.collect()
}
}
@@ -0,0 +1,53 @@
use niri::render_helpers::border::BorderRenderElement;
use niri_config::{
Color, CornerRadius, GradientColorSpace, GradientInterpolation, HueInterpolation,
};
use smithay::backend::renderer::element::RenderElement;
use smithay::backend::renderer::gles::GlesRenderer;
use smithay::utils::{Logical, Physical, Rectangle, Size};
use super::TestCase;
pub struct GradientOklchLonger {
gradient_format: GradientInterpolation,
}
impl GradientOklchLonger {
pub fn new(_size: Size<i32, Logical>) -> Self {
Self {
gradient_format: GradientInterpolation {
color_space: GradientColorSpace::Oklch,
hue_interpolation: HueInterpolation::Longer,
},
}
}
}
impl TestCase for GradientOklchLonger {
fn render(
&mut self,
_renderer: &mut GlesRenderer,
size: Size<i32, Physical>,
) -> Vec<Box<dyn RenderElement<GlesRenderer>>> {
let (a, b) = (size.w / 6, size.h / 3);
let size = (size.w - a * 2, size.h - b * 2);
let area = Rectangle::from_loc_and_size((a, b), size).to_f64();
[BorderRenderElement::new(
area.size,
Rectangle::from_loc_and_size((0., 0.), area.size),
self.gradient_format,
Color::new_unpremul(1., 0., 0., 1.),
Color::new_unpremul(0., 1., 0., 1.),
0.,
Rectangle::from_loc_and_size((0., 0.), area.size),
0.,
CornerRadius::default(),
1.,
)
.with_location(area.loc)]
.into_iter()
.map(|elem| Box::new(elem) as _)
.collect()
}
}
@@ -0,0 +1,53 @@
use niri::render_helpers::border::BorderRenderElement;
use niri_config::{
Color, CornerRadius, GradientColorSpace, GradientInterpolation, HueInterpolation,
};
use smithay::backend::renderer::element::RenderElement;
use smithay::backend::renderer::gles::GlesRenderer;
use smithay::utils::{Logical, Physical, Rectangle, Size};
use super::TestCase;
pub struct GradientOklchShorter {
gradient_format: GradientInterpolation,
}
impl GradientOklchShorter {
pub fn new(_size: Size<i32, Logical>) -> Self {
Self {
gradient_format: GradientInterpolation {
color_space: GradientColorSpace::Oklch,
hue_interpolation: HueInterpolation::Shorter,
},
}
}
}
impl TestCase for GradientOklchShorter {
fn render(
&mut self,
_renderer: &mut GlesRenderer,
size: Size<i32, Physical>,
) -> Vec<Box<dyn RenderElement<GlesRenderer>>> {
let (a, b) = (size.w / 6, size.h / 3);
let size = (size.w - a * 2, size.h - b * 2);
let area = Rectangle::from_loc_and_size((a, b), size).to_f64();
[BorderRenderElement::new(
area.size,
Rectangle::from_loc_and_size((0., 0.), area.size),
self.gradient_format,
Color::new_unpremul(1., 0., 0., 1.),
Color::new_unpremul(0., 1., 0., 1.),
0.,
Rectangle::from_loc_and_size((0., 0.), area.size),
0.,
CornerRadius::default(),
1.,
)
.with_location(area.loc)]
.into_iter()
.map(|elem| Box::new(elem) as _)
.collect()
}
}
@@ -0,0 +1,53 @@
use niri::render_helpers::border::BorderRenderElement;
use niri_config::{
Color, CornerRadius, GradientColorSpace, GradientInterpolation, HueInterpolation,
};
use smithay::backend::renderer::element::RenderElement;
use smithay::backend::renderer::gles::GlesRenderer;
use smithay::utils::{Logical, Physical, Rectangle, Size};
use super::TestCase;
pub struct GradientSrgb {
gradient_format: GradientInterpolation,
}
impl GradientSrgb {
pub fn new(_size: Size<i32, Logical>) -> Self {
Self {
gradient_format: GradientInterpolation {
color_space: GradientColorSpace::Srgb,
hue_interpolation: HueInterpolation::Shorter,
},
}
}
}
impl TestCase for GradientSrgb {
fn render(
&mut self,
_renderer: &mut GlesRenderer,
size: Size<i32, Physical>,
) -> Vec<Box<dyn RenderElement<GlesRenderer>>> {
let (a, b) = (size.w / 6, size.h / 3);
let size = (size.w - a * 2, size.h - b * 2);
let area = Rectangle::from_loc_and_size((a, b), size).to_f64();
[BorderRenderElement::new(
area.size,
Rectangle::from_loc_and_size((0., 0.), area.size),
self.gradient_format,
Color::new_unpremul(1., 0., 0., 1.),
Color::new_unpremul(0., 1., 0., 1.),
0.,
Rectangle::from_loc_and_size((0., 0.), area.size),
0.,
CornerRadius::default(),
1.,
)
.with_location(area.loc)]
.into_iter()
.map(|elem| Box::new(elem) as _)
.collect()
}
}
@@ -0,0 +1,51 @@
use niri::render_helpers::border::BorderRenderElement;
use niri_config::{Color, CornerRadius, GradientColorSpace, GradientInterpolation};
use smithay::backend::renderer::element::RenderElement;
use smithay::backend::renderer::gles::GlesRenderer;
use smithay::utils::{Logical, Physical, Rectangle, Size};
use super::TestCase;
pub struct GradientSrgbAlpha {
gradient_format: GradientInterpolation,
}
impl GradientSrgbAlpha {
pub fn new(_size: Size<i32, Logical>) -> Self {
Self {
gradient_format: GradientInterpolation {
color_space: GradientColorSpace::Srgb,
hue_interpolation: Default::default(),
},
}
}
}
impl TestCase for GradientSrgbAlpha {
fn render(
&mut self,
_renderer: &mut GlesRenderer,
size: Size<i32, Physical>,
) -> Vec<Box<dyn RenderElement<GlesRenderer>>> {
let (a, b) = (size.w / 6, size.h / 3);
let size = (size.w - a * 2, size.h - b * 2);
let area = Rectangle::from_loc_and_size((a, b), size).to_f64();
[BorderRenderElement::new(
area.size,
Rectangle::from_loc_and_size((0., 0.), area.size),
self.gradient_format,
Color::new_unpremul(1., 0., 0., 1.),
Color::new_unpremul(0., 1., 0., 0.),
0.,
Rectangle::from_loc_and_size((0., 0.), area.size),
0.,
CornerRadius::default(),
1.,
)
.with_location(area.loc)]
.into_iter()
.map(|elem| Box::new(elem) as _)
.collect()
}
}
@@ -0,0 +1,53 @@
use niri::render_helpers::border::BorderRenderElement;
use niri_config::{
Color, CornerRadius, GradientColorSpace, GradientInterpolation, HueInterpolation,
};
use smithay::backend::renderer::element::RenderElement;
use smithay::backend::renderer::gles::GlesRenderer;
use smithay::utils::{Logical, Physical, Rectangle, Size};
use super::TestCase;
pub struct GradientSrgbLinear {
gradient_format: GradientInterpolation,
}
impl GradientSrgbLinear {
pub fn new(_size: Size<i32, Logical>) -> Self {
Self {
gradient_format: GradientInterpolation {
color_space: GradientColorSpace::SrgbLinear,
hue_interpolation: HueInterpolation::Shorter,
},
}
}
}
impl TestCase for GradientSrgbLinear {
fn render(
&mut self,
_renderer: &mut GlesRenderer,
size: Size<i32, Physical>,
) -> Vec<Box<dyn RenderElement<GlesRenderer>>> {
let (a, b) = (size.w / 6, size.h / 3);
let size = (size.w - a * 2, size.h - b * 2);
let area = Rectangle::from_loc_and_size((a, b), size).to_f64();
[BorderRenderElement::new(
area.size,
Rectangle::from_loc_and_size((0., 0.), area.size),
self.gradient_format,
Color::new_unpremul(1., 0., 0., 1.),
Color::new_unpremul(0., 1., 0., 1.),
0.,
Rectangle::from_loc_and_size((0., 0.), area.size),
0.,
CornerRadius::default(),
1.,
)
.with_location(area.loc)]
.into_iter()
.map(|elem| Box::new(elem) as _)
.collect()
}
}
@@ -0,0 +1,51 @@
use niri::render_helpers::border::BorderRenderElement;
use niri_config::{Color, CornerRadius, GradientColorSpace, GradientInterpolation};
use smithay::backend::renderer::element::RenderElement;
use smithay::backend::renderer::gles::GlesRenderer;
use smithay::utils::{Logical, Physical, Rectangle, Size};
use super::TestCase;
pub struct GradientSrgbLinearAlpha {
gradient_format: GradientInterpolation,
}
impl GradientSrgbLinearAlpha {
pub fn new(_size: Size<i32, Logical>) -> Self {
Self {
gradient_format: GradientInterpolation {
color_space: GradientColorSpace::SrgbLinear,
hue_interpolation: Default::default(),
},
}
}
}
impl TestCase for GradientSrgbLinearAlpha {
fn render(
&mut self,
_renderer: &mut GlesRenderer,
size: Size<i32, Physical>,
) -> Vec<Box<dyn RenderElement<GlesRenderer>>> {
let (a, b) = (size.w / 6, size.h / 3);
let size = (size.w - a * 2, size.h - b * 2);
let area = Rectangle::from_loc_and_size((a, b), size).to_f64();
[BorderRenderElement::new(
area.size,
Rectangle::from_loc_and_size((0., 0.), area.size),
self.gradient_format,
Color::new_unpremul(1., 0., 0., 1.),
Color::new_unpremul(0., 1., 0., 0.),
0.,
Rectangle::from_loc_and_size((0., 0.), area.size),
0.,
CornerRadius::default(),
1.,
)
.with_location(area.loc)]
.into_iter()
.map(|elem| Box::new(elem) as _)
.collect()
}
}
+2 -2
View File
@@ -50,8 +50,8 @@ impl Layout {
border: niri_config::Border { border: niri_config::Border {
off: false, off: false,
width: FloatOrInt(4.), width: FloatOrInt(4.),
active_color: Color::new(255, 163, 72, 255), active_color: Color::from_rgba8_unpremul(255, 163, 72, 255),
inactive_color: Color::new(50, 50, 50, 255), inactive_color: Color::from_rgba8_unpremul(50, 50, 50, 255),
active_gradient: None, active_gradient: None,
inactive_gradient: None, inactive_gradient: None,
}, },
+11
View File
@@ -6,6 +6,17 @@ use smithay::utils::{Physical, Size};
pub mod gradient_angle; pub mod gradient_angle;
pub mod gradient_area; pub mod gradient_area;
pub mod gradient_oklab;
pub mod gradient_oklab_alpha;
pub mod gradient_oklch_alpha;
pub mod gradient_oklch_decreasing;
pub mod gradient_oklch_increasing;
pub mod gradient_oklch_longer;
pub mod gradient_oklch_shorter;
pub mod gradient_srgb;
pub mod gradient_srgb_alpha;
pub mod gradient_srgblinear;
pub mod gradient_srgblinear_alpha;
pub mod layout; pub mod layout;
pub mod tile; pub mod tile;
pub mod window; pub mod window;
+1 -1
View File
@@ -72,7 +72,7 @@ impl Tile {
border: niri_config::Border { border: niri_config::Border {
off: false, off: false,
width: FloatOrInt(32.), width: FloatOrInt(32.),
active_color: Color::new(255, 163, 72, 255), active_color: Color::from_rgba8_unpremul(255, 163, 72, 255),
..Default::default() ..Default::default()
}, },
..Default::default() ..Default::default()
+24 -2
View File
@@ -5,8 +5,6 @@ use std::env;
use std::sync::atomic::Ordering; use std::sync::atomic::Ordering;
use adw::prelude::{AdwApplicationWindowExt, NavigationPageExt}; use adw::prelude::{AdwApplicationWindowExt, NavigationPageExt};
use cases::tile::Tile;
use cases::window::Window;
use gtk::prelude::{ use gtk::prelude::{
AdjustmentExt, ApplicationExt, ApplicationExtManual, BoxExt, GtkWindowExt, WidgetExt, AdjustmentExt, ApplicationExt, ApplicationExtManual, BoxExt, GtkWindowExt, WidgetExt,
}; };
@@ -18,7 +16,20 @@ use tracing_subscriber::EnvFilter;
use crate::cases::gradient_angle::GradientAngle; use crate::cases::gradient_angle::GradientAngle;
use crate::cases::gradient_area::GradientArea; use crate::cases::gradient_area::GradientArea;
use crate::cases::gradient_oklab::GradientOklab;
use crate::cases::gradient_oklab_alpha::GradientOklabAlpha;
use crate::cases::gradient_oklch_alpha::GradientOklchAlpha;
use crate::cases::gradient_oklch_decreasing::GradientOklchDecreasing;
use crate::cases::gradient_oklch_increasing::GradientOklchIncreasing;
use crate::cases::gradient_oklch_longer::GradientOklchLonger;
use crate::cases::gradient_oklch_shorter::GradientOklchShorter;
use crate::cases::gradient_srgb::GradientSrgb;
use crate::cases::gradient_srgb_alpha::GradientSrgbAlpha;
use crate::cases::gradient_srgblinear::GradientSrgbLinear;
use crate::cases::gradient_srgblinear_alpha::GradientSrgbLinearAlpha;
use crate::cases::layout::Layout; use crate::cases::layout::Layout;
use crate::cases::tile::Tile;
use crate::cases::window::Window;
use crate::cases::TestCase; use crate::cases::TestCase;
mod cases; mod cases;
@@ -112,6 +123,17 @@ fn build_ui(app: &adw::Application) {
s.add(GradientAngle::new, "Gradient - Angle"); s.add(GradientAngle::new, "Gradient - Angle");
s.add(GradientArea::new, "Gradient - Area"); s.add(GradientArea::new, "Gradient - Area");
s.add(GradientSrgb::new, "Gradient - Srgb");
s.add(GradientSrgbLinear::new, "Gradient - SrgbLinear");
s.add(GradientOklab::new, "Gradient - Oklab");
s.add(GradientOklchShorter::new, "Gradient - Oklch Shorter");
s.add(GradientOklchLonger::new, "Gradient - Oklch Longer");
s.add(GradientOklchIncreasing::new, "Gradient - Oklch Increasing");
s.add(GradientOklchDecreasing::new, "Gradient - Oklch Decreasing");
s.add(GradientSrgbAlpha::new, "Gradient - Srgb Alpha");
s.add(GradientSrgbLinearAlpha::new, "Gradient - SrgbLinear Alpha");
s.add(GradientOklabAlpha::new, "Gradient - Oklab Alpha");
s.add(GradientOklchAlpha::new, "Gradient - Oklch Alpha");
let content_headerbar = adw::HeaderBar::new(); let content_headerbar = adw::HeaderBar::new();
+1
View File
@@ -153,6 +153,7 @@ layout {
// The angle is the same as in linear-gradient, and is optional, // The angle is the same as in linear-gradient, and is optional,
// defaulting to 180 (top-to-bottom gradient). // defaulting to 180 (top-to-bottom gradient).
// You can use any CSS linear-gradient tool on the web to set these up. // You can use any CSS linear-gradient tool on the web to set these up.
// Changing the color space is also supported, check the wiki for more info.
// //
// active-gradient from="#80c8ff" to="#bbddff" angle=45 // active-gradient from="#80c8ff" to="#bbddff" angle=45
+9 -6
View File
@@ -1,7 +1,7 @@
use std::iter::zip; use std::iter::zip;
use arrayvec::ArrayVec; use arrayvec::ArrayVec;
use niri_config::{CornerRadius, Gradient, GradientRelativeTo}; use niri_config::{CornerRadius, Gradient, GradientInterpolation, GradientRelativeTo};
use smithay::backend::renderer::element::Kind; use smithay::backend::renderer::element::Kind;
use smithay::utils::{Logical, Point, Rectangle, Size}; use smithay::utils::{Logical, Point, Rectangle, Size};
@@ -72,7 +72,7 @@ impl FocusRing {
}; };
for buf in &mut self.buffers { for buf in &mut self.buffers {
buf.set_color(color.into()); buf.set_color(color.to_array_premul());
} }
let radius = radius.fit_to(self.full_size.w as f32, self.full_size.h as f32); let radius = radius.fit_to(self.full_size.w as f32, self.full_size.h as f32);
@@ -91,6 +91,7 @@ impl FocusRing {
to: color, to: color,
angle: 0, angle: 0,
relative_to: GradientRelativeTo::Window, relative_to: GradientRelativeTo::Window,
in_: GradientInterpolation::default(),
}); });
let full_rect = Rectangle::from_loc_and_size((-width, -width), self.full_size); let full_rect = Rectangle::from_loc_and_size((-width, -width), self.full_size);
@@ -178,8 +179,9 @@ impl FocusRing {
border.update( border.update(
size, size,
Rectangle::from_loc_and_size(gradient_area.loc - loc, gradient_area.size), Rectangle::from_loc_and_size(gradient_area.loc - loc, gradient_area.size),
gradient.from.into(), gradient.in_,
gradient.to.into(), gradient.from,
gradient.to,
((gradient.angle as f32) - 90.).to_radians(), ((gradient.angle as f32) - 90.).to_radians(),
Rectangle::from_loc_and_size(full_rect.loc - loc, full_rect.size), Rectangle::from_loc_and_size(full_rect.loc - loc, full_rect.size),
rounded_corner_border_width, rounded_corner_border_width,
@@ -198,8 +200,9 @@ impl FocusRing {
gradient_area.loc - self.locations[0], gradient_area.loc - self.locations[0],
gradient_area.size, gradient_area.size,
), ),
gradient.from.into(), gradient.in_,
gradient.to.into(), gradient.from,
gradient.to,
((gradient.angle as f32) - 90.).to_radians(), ((gradient.angle as f32) - 90.).to_radians(),
Rectangle::from_loc_and_size(full_rect.loc - self.locations[0], full_rect.size), Rectangle::from_loc_and_size(full_rect.loc - self.locations[0], full_rect.size),
rounded_corner_border_width, rounded_corner_border_width,
+4 -3
View File
@@ -1,7 +1,7 @@
use std::rc::Rc; use std::rc::Rc;
use std::time::Duration; use std::time::Duration;
use niri_config::CornerRadius; use niri_config::{Color, CornerRadius, GradientInterpolation};
use smithay::backend::allocator::Fourcc; use smithay::backend::allocator::Fourcc;
use smithay::backend::renderer::element::{Element, Kind}; use smithay::backend::renderer::element::{Element, Kind};
use smithay::backend::renderer::gles::GlesRenderer; use smithay::backend::renderer::gles::GlesRenderer;
@@ -757,8 +757,9 @@ impl<W: LayoutElement> Tile<W> {
return BorderRenderElement::new( return BorderRenderElement::new(
geo.size, geo.size,
Rectangle::from_loc_and_size((0., 0.), geo.size), Rectangle::from_loc_and_size((0., 0.), geo.size),
elem.color(), GradientInterpolation::default(),
elem.color(), Color::from_array_premul(elem.color()),
Color::from_array_premul(elem.color()),
0., 0.,
Rectangle::from_loc_and_size((0., 0.), geo.size), Rectangle::from_loc_and_size((0., 0.), geo.size),
0., 0.,
+34 -9
View File
@@ -1,7 +1,9 @@
use std::collections::HashMap; use std::collections::HashMap;
use glam::{Mat3, Vec2}; use glam::{Mat3, Vec2};
use niri_config::CornerRadius; use niri_config::{
Color, CornerRadius, GradientColorSpace, GradientInterpolation, HueInterpolation,
};
use smithay::backend::renderer::element::{Element, Id, Kind, RenderElement, UnderlyingStorage}; use smithay::backend::renderer::element::{Element, Id, Kind, RenderElement, UnderlyingStorage};
use smithay::backend::renderer::gles::{GlesError, GlesFrame, GlesRenderer, Uniform}; use smithay::backend::renderer::gles::{GlesError, GlesFrame, GlesRenderer, Uniform};
use smithay::backend::renderer::utils::{CommitCounter, DamageSet, OpaqueRegions}; use smithay::backend::renderer::utils::{CommitCounter, DamageSet, OpaqueRegions};
@@ -28,8 +30,9 @@ pub struct BorderRenderElement {
struct Parameters { struct Parameters {
size: Size<f64, Logical>, size: Size<f64, Logical>,
gradient_area: Rectangle<f64, Logical>, gradient_area: Rectangle<f64, Logical>,
color_from: [f32; 4], gradient_format: GradientInterpolation,
color_to: [f32; 4], color_from: Color,
color_to: Color,
angle: f32, angle: f32,
geometry: Rectangle<f64, Logical>, geometry: Rectangle<f64, Logical>,
border_width: f32, border_width: f32,
@@ -43,8 +46,9 @@ impl BorderRenderElement {
pub fn new( pub fn new(
size: Size<f64, Logical>, size: Size<f64, Logical>,
gradient_area: Rectangle<f64, Logical>, gradient_area: Rectangle<f64, Logical>,
color_from: [f32; 4], gradient_format: GradientInterpolation,
color_to: [f32; 4], color_from: Color,
color_to: Color,
angle: f32, angle: f32,
geometry: Rectangle<f64, Logical>, geometry: Rectangle<f64, Logical>,
border_width: f32, border_width: f32,
@@ -57,6 +61,7 @@ impl BorderRenderElement {
params: Parameters { params: Parameters {
size, size,
gradient_area, gradient_area,
gradient_format,
color_from, color_from,
color_to, color_to,
angle, angle,
@@ -77,6 +82,7 @@ impl BorderRenderElement {
params: Parameters { params: Parameters {
size: Default::default(), size: Default::default(),
gradient_area: Default::default(), gradient_area: Default::default(),
gradient_format: GradientInterpolation::default(),
color_from: Default::default(), color_from: Default::default(),
color_to: Default::default(), color_to: Default::default(),
angle: 0., angle: 0.,
@@ -97,8 +103,9 @@ impl BorderRenderElement {
&mut self, &mut self,
size: Size<f64, Logical>, size: Size<f64, Logical>,
gradient_area: Rectangle<f64, Logical>, gradient_area: Rectangle<f64, Logical>,
color_from: [f32; 4], gradient_format: GradientInterpolation,
color_to: [f32; 4], color_from: Color,
color_to: Color,
angle: f32, angle: f32,
geometry: Rectangle<f64, Logical>, geometry: Rectangle<f64, Logical>,
border_width: f32, border_width: f32,
@@ -108,6 +115,7 @@ impl BorderRenderElement {
let params = Parameters { let params = Parameters {
size, size,
gradient_area, gradient_area,
gradient_format,
color_from, color_from,
color_to, color_to,
angle, angle,
@@ -128,6 +136,7 @@ impl BorderRenderElement {
let Parameters { let Parameters {
size, size,
gradient_area, gradient_area,
gradient_format,
color_from, color_from,
color_to, color_to,
angle, angle,
@@ -162,13 +171,29 @@ impl BorderRenderElement {
let input_to_geo = let input_to_geo =
Mat3::from_scale(area_size) * Mat3::from_translation(-geo_loc / area_size); Mat3::from_scale(area_size) * Mat3::from_translation(-geo_loc / area_size);
let colorspace = match gradient_format.color_space {
GradientColorSpace::Srgb => 0.,
GradientColorSpace::SrgbLinear => 1.,
GradientColorSpace::Oklab => 2.,
GradientColorSpace::Oklch => 3.,
};
let hue_interpolation = match gradient_format.hue_interpolation {
HueInterpolation::Shorter => 0.,
HueInterpolation::Longer => 1.,
HueInterpolation::Increasing => 2.,
HueInterpolation::Decreasing => 3.,
};
self.inner.update( self.inner.update(
size, size,
None, None,
scale, scale,
vec![ vec![
Uniform::new("color_from", color_from), Uniform::new("colorspace", colorspace),
Uniform::new("color_to", color_to), Uniform::new("hue_interpolation", hue_interpolation),
Uniform::new("color_from", color_from.to_array_unpremul()),
Uniform::new("color_to", color_to.to_array_unpremul()),
Uniform::new("grad_offset", grad_offset.to_array()), Uniform::new("grad_offset", grad_offset.to_array()),
Uniform::new("grad_width", w), Uniform::new("grad_width", w),
Uniform::new("grad_vec", grad_vec.to_array()), Uniform::new("grad_vec", grad_vec.to_array()),
+173 -1
View File
@@ -10,6 +10,8 @@ uniform float niri_scale;
uniform vec2 niri_size; uniform vec2 niri_size;
varying vec2 niri_v_coords; varying vec2 niri_v_coords;
uniform float colorspace;
uniform float hue_interpolation;
uniform vec4 color_from; uniform vec4 color_from;
uniform vec4 color_to; uniform vec4 color_to;
uniform vec2 grad_offset; uniform vec2 grad_offset;
@@ -21,6 +23,176 @@ uniform vec2 geo_size;
uniform vec4 outer_radius; uniform vec4 outer_radius;
uniform float border_width; uniform float border_width;
vec4 premul_rect(vec4 color) {
color.rgb *= color.a;
return color;
}
vec4 premul_lch(vec4 color) {
color.xy *= color.a;
return color;
}
vec4 unpremul_rect(vec4 color) {
if (color.a == 0.0)
return color;
color.rgb /= color.a;
return color;
}
vec4 unpremul_lch(vec4 color) {
if (color.a == 0.0)
return color;
color.xy /= color.a;
return color;
}
vec4 premul_mix_unpremul_rect(vec4 color1, vec4 color2, float ratio) {
vec4 mixed = mix(premul_rect(color1), premul_rect(color2), ratio);
return unpremul_rect(mixed);
}
vec4 premul_mix_unpremul_lch(vec4 color1, vec4 color2, float ratio) {
vec4 mixed = mix(premul_lch(color1), premul_lch(color2), ratio);
return unpremul_lch(mixed);
}
vec3 srgb_to_linear(vec3 color) {
return pow(color, vec3(2.2));
}
vec3 linear_to_srgb(vec3 color) {
return pow(color, vec3(1.0 / 2.2));
}
vec3 lab_to_lch(vec3 color) {
float c = sqrt(pow(color.y, 2.0) + pow(color.z, 2.0));
float h = degrees(atan(color.z, color.y)) ;
h += h <= 0.0 ?
360.0 :
0.0 ;
return vec3(
color.x,
c,
h
);
}
vec3 lch_to_lab(vec3 color) {
float a = color.y * clamp(cos(radians(color.z)), -1.0, 1.0);
float b = color.y * clamp(sin(radians(color.z)), -1.0, 1.0);
return vec3(
color.x,
a,
b
);
}
vec3 linear_to_oklab(vec3 color){
mat3 rgb_to_lms = mat3(
vec3(0.4122214708, 0.5363325363, 0.0514459929),
vec3(0.2119034982, 0.6806995451, 0.1073969566),
vec3(0.0883024619, 0.2817188376, 0.6299787005)
);
mat3 lms_to_oklab = mat3(
vec3(0.2104542553, 0.7936177850, -0.0040720468),
vec3(1.9779984951, -2.4285922050, 0.4505937099),
vec3(0.0259040371, 0.7827717662, -0.8086757660)
);
vec3 lms = color * rgb_to_lms;
lms = pow(lms, vec3(1.0 / 3.0));
return lms * lms_to_oklab;
}
vec3 oklab_to_linear(vec3 color){
mat3 oklab_to_lms = mat3(
vec3(1.0, 0.3963377774, 0.2158037573),
vec3(1.0, -0.1055613458, -0.0638541728),
vec3(1.0, -0.0894841775, -1.2914855480)
);
mat3 lms_to_rgb = mat3(
vec3(4.0767416621, -3.3077115913, 0.2309699292),
vec3(-1.2684380046, 2.6097574011, -0.3413193965),
vec3(-0.0041960863, -0.7034186147, 1.7076147010)
);
vec3 lms = color * oklab_to_lms;
lms = pow(lms, vec3(3.0));
return lms * lms_to_rgb;
}
vec4 color_mix(vec4 color1, vec4 color2, float color_ratio) {
vec4 color_out;
// srgb
if (colorspace == 0.0) {
return mix(premul_rect(color1), premul_rect(color2), color_ratio);
}
color1.rgb = srgb_to_linear(color1.rgb);
color2.rgb = srgb_to_linear(color2.rgb);
// srgb-linear
if (colorspace == 1.0) {
color_out = premul_mix_unpremul_rect(color1, color2, color_ratio);
// oklab
} else if (colorspace == 2.0) {
color1.xyz = linear_to_oklab(color1.rgb);
color2.xyz = linear_to_oklab(color2.rgb);
color_out = premul_mix_unpremul_rect(color1, color2, color_ratio);
color_out.rgb = oklab_to_linear(color_out.xyz);
// oklch
} else if (colorspace == 3.0) {
color1.xyz = lab_to_lch(linear_to_oklab(color1.rgb));
color2.xyz = lab_to_lch(linear_to_oklab(color2.rgb));
color_out = premul_mix_unpremul_lch(color1, color2, color_ratio);
float min_hue = min(color1.z, color2.z);
float max_hue = max(color1.z, color2.z);
float path_direct_distance = (max_hue - min_hue) * color_ratio;
float path_mod_distance = (360.0 - max_hue + min_hue) * color_ratio;
float path_mod =
color1.z == min_hue ?
mod(color1.z - path_mod_distance, 360.0) :
mod(color1.z + path_mod_distance, 360.0) ;
float path_direct =
color1.z == min_hue ?
color1.z + path_direct_distance :
color1.z - path_direct_distance ;
// shorter
if (hue_interpolation == 0.0) {
color_out.z =
max_hue - min_hue > 360.0 - max_hue + min_hue ?
path_mod :
path_direct ;
// longer
} else if (hue_interpolation == 1.0) {
color_out.z =
max_hue - min_hue <= 360.0 - max_hue + min_hue ?
path_mod :
path_direct ;
// increasing
} else if (hue_interpolation == 2.0) {
color_out.z =
color1.z > color2.z ?
path_mod :
path_direct ;
// decreasing
} else if (hue_interpolation == 3.0) {
color_out.z =
color1.z <= color2.z ?
path_mod :
path_direct ;
}
color_out.rgb = clamp(oklab_to_linear(lch_to_lab(color_out.xyz)), 0.0, 1.0);
}
return premul_rect(vec4(linear_to_srgb(color_out.rgb), color_out.a));
}
vec4 gradient_color(vec2 coords) { vec4 gradient_color(vec2 coords) {
coords = coords + grad_offset; coords = coords + grad_offset;
@@ -33,7 +205,7 @@ vec4 gradient_color(vec2 coords) {
frac += 1.0; frac += 1.0;
frac = clamp(frac, 0.0, 1.0); frac = clamp(frac, 0.0, 1.0);
return mix(color_from, color_to, frac); return color_mix(color_from, color_to, frac);
} }
float rounding_alpha(vec2 coords, vec2 size, vec4 corner_radius) { float rounding_alpha(vec2 coords, vec2 size, vec4 corner_radius) {
+2
View File
@@ -34,6 +34,8 @@ impl Shaders {
renderer, renderer,
include_str!("border.frag"), include_str!("border.frag"),
&[ &[
UniformName::new("colorspace", UniformType::_1f),
UniformName::new("hue_interpolation", UniformType::_1f),
UniformName::new("color_from", UniformType::_4f), UniformName::new("color_from", UniformType::_4f),
UniformName::new("color_to", UniformType::_4f), UniformName::new("color_to", UniformType::_4f),
UniformName::new("grad_offset", UniformType::_2f), UniformName::new("grad_offset", UniformType::_2f),
+4 -3
View File
@@ -2,7 +2,7 @@ use std::cell::{Cell, RefCell};
use std::cmp::{max, min}; use std::cmp::{max, min};
use std::time::Duration; use std::time::Duration;
use niri_config::{CornerRadius, WindowRule}; use niri_config::{Color, CornerRadius, GradientInterpolation, WindowRule};
use smithay::backend::renderer::element::surface::render_elements_from_surface_tree; use smithay::backend::renderer::element::surface::render_elements_from_surface_tree;
use smithay::backend::renderer::element::{Id, Kind}; use smithay::backend::renderer::element::{Id, Kind};
use smithay::backend::renderer::gles::GlesRenderer; use smithay::backend::renderer::gles::GlesRenderer;
@@ -289,8 +289,9 @@ impl Mapped {
return BorderRenderElement::new( return BorderRenderElement::new(
geo.size, geo.size,
Rectangle::from_loc_and_size((0., 0.), geo.size), Rectangle::from_loc_and_size((0., 0.), geo.size),
elem.color(), GradientInterpolation::default(),
elem.color(), Color::from_array_premul(elem.color()),
Color::from_array_premul(elem.color()),
0., 0.,
Rectangle::from_loc_and_size((0., 0.), geo.size), Rectangle::from_loc_and_size((0., 0.), geo.size),
0., 0.,
+23 -2
View File
@@ -32,7 +32,7 @@ layout {
active-color "#ffc87f" active-color "#ffc87f"
inactive-color "#505050" inactive-color "#505050"
// active-gradient from="#ffbb66" to="#ffc880" angle=45 relative-to="workspace-view" // active-gradient from="#ffbb66" to="#ffc880" angle=45 relative-to="workspace-view"
// inactive-gradient from="#505050" to="#808080" angle=45 relative-to="workspace-view" // inactive-gradient from="#505050" to="#808080" angle=45 relative-to="workspace-view" in="srgb-linear"
} }
struts { struts {
@@ -169,7 +169,7 @@ layout {
inactive-color "#505050" inactive-color "#505050"
// active-gradient from="#ffbb66" to="#ffc880" angle=45 relative-to="workspace-view" // active-gradient from="#ffbb66" to="#ffc880" angle=45 relative-to="workspace-view"
// inactive-gradient from="#505050" to="#808080" angle=45 relative-to="workspace-view" // inactive-gradient from="#505050" to="#808080" angle=45 relative-to="workspace-view" in="srgb-linear"
} }
} }
``` ```
@@ -238,6 +238,27 @@ layout {
} }
``` ```
<sup>Since: 0.1.8</sup> You can set the gradient interpolation color space using syntax like `in="srgb-linear"` or `in="oklch longer hue"`.
Supported color spaces are:
- `srgb` (the default),
- `srgb-linear`,
- `oklab`,
- `oklch` with `shorter hue` or `longer hue` or `increasing hue` or `decreasing hue`.
They are rendered the same as CSS.
For example, `active-gradient from="#f00f" to="#0f05" angle=45 in="oklch longer hue"` will look the same as CSS `linear-gradient(45deg in oklch longer hue, #f00f, #0f05)`.
![](./img/gradients-oklch.png)
```kdl
layout {
border {
active-gradient from="#f00f" to="#0f05" angle=45 in="oklch longer hue"
}
}
```
### `struts` ### `struts`
Struts shrink the area occupied by windows, similarly to layer-shell panels. Struts shrink the area occupied by windows, similarly to layer-shell panels.
+3
View File
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:a3e4db583ac849ec3bd6c2312a84a16eaad810ebb7599cad3762a7dfc66e865b
size 23180