mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-15 22:28:10 +08:00
Add a basic API and rudimentary frontend for node graph layers (#846)
* Node graph API stub * Rename and fix SetInputValue * Get list of links from network * Test populating node graph UI * Node properties * Fix viewport bounds * Slightly change promise usage * A tiny bit of cleanup I did while reading code * Cleanup and work towards hooking up node links in Vue template * Add the brighten colour node * Run cargo fmt * Add to and from hsla * GrayscaleImage node with small perf improvement * Fix gutter panel resizing * Display node links from backend * Add support for connecting node links * Use existing message * Fix formatting error * Add a (currently crashing) brighten node * Replace brighten node with proto node implementation * Add support for connecting node links * Update watch dirs * Add hue shift node * Add create_node function to editor api * Basic insert node UI * Fix broken names * Add log * Fix positioning * Set connector index to 0 * Add properties for Heu shift / brighten * Allow deselecting nodes * Redesign Properties panel collapsible sections Co-authored-by: Keavon Chambers <keavon@keavon.com> Co-authored-by: Dennis Kobert <dennis@kobert.dev>
This commit is contained in:
committed by
Keavon Chambers
parent
e8256dd350
commit
504136b61b
@@ -22,6 +22,58 @@ impl<'n> Node<Color> for &'n GrayscaleNode {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct BrightenColorNode<N: Node<(), Output = f32>>(N);
|
||||
|
||||
impl<N: Node<(), Output = f32>> Node<Color> for BrightenColorNode<N> {
|
||||
type Output = Color;
|
||||
fn eval(self, color: Color) -> Color {
|
||||
let brightness = self.0.eval(());
|
||||
let per_channel = |col: f32| (col + brightness / 255.).clamp(0., 1.);
|
||||
Color::from_rgbaf32_unchecked(per_channel(color.r()), per_channel(color.g()), per_channel(color.b()), color.a())
|
||||
}
|
||||
}
|
||||
impl<N: Node<(), Output = f32> + Copy> Node<Color> for &BrightenColorNode<N> {
|
||||
type Output = Color;
|
||||
fn eval(self, color: Color) -> Color {
|
||||
let brightness = self.0.eval(());
|
||||
let per_channel = |col: f32| (col + brightness / 255.).clamp(0., 1.);
|
||||
Color::from_rgbaf32_unchecked(per_channel(color.r()), per_channel(color.g()), per_channel(color.b()), color.a())
|
||||
}
|
||||
}
|
||||
|
||||
impl<N: Node<(), Output = f32> + Copy> BrightenColorNode<N> {
|
||||
pub fn new(node: N) -> Self {
|
||||
Self(node)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct HueShiftNode<N: Node<(), Output = f32>>(N);
|
||||
|
||||
impl<N: Node<(), Output = f32>> Node<Color> for HueShiftNode<N> {
|
||||
type Output = Color;
|
||||
fn eval(self, color: Color) -> Color {
|
||||
let hue_shift = self.0.eval(());
|
||||
let [hue, saturation, luminance, alpha] = color.to_hsla();
|
||||
Color::from_hsla(hue + hue_shift / 360., saturation, luminance, alpha)
|
||||
}
|
||||
}
|
||||
impl<N: Node<(), Output = f32> + Copy> Node<Color> for &HueShiftNode<N> {
|
||||
type Output = Color;
|
||||
fn eval(self, color: Color) -> Color {
|
||||
let hue_shift = self.0.eval(());
|
||||
let [hue, saturation, luminance, alpha] = color.to_hsla();
|
||||
Color::from_hsla(hue + hue_shift / 360., saturation, luminance, alpha)
|
||||
}
|
||||
}
|
||||
|
||||
impl<N: Node<(), Output = f32> + Copy> HueShiftNode<N> {
|
||||
pub fn new(node: N) -> Self {
|
||||
Self(node)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ForEachNode<MN>(pub MN);
|
||||
|
||||
impl<'n, I: Iterator<Item = S>, MN: 'n, S> Node<I> for &'n ForEachNode<MN>
|
||||
|
||||
@@ -82,6 +82,41 @@ impl Color {
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a [Color] from a hue, saturation, luminance and alpha (all between 0 and 1)
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use graphene_core::raster::color::Color;
|
||||
/// let color = Color::from_hsla(0.5, 0.2, 0.3, 1.);
|
||||
/// ```
|
||||
pub fn from_hsla(hue: f32, saturation: f32, luminance: f32, alpha: f32) -> Color {
|
||||
let temp1 = if luminance < 0.5 {
|
||||
luminance * (saturation + 1.)
|
||||
} else {
|
||||
luminance + saturation - luminance * saturation
|
||||
};
|
||||
let temp2 = 2. * luminance - temp1;
|
||||
|
||||
let mut red = (hue + 1. / 3.).rem_euclid(1.);
|
||||
let mut green = hue.rem_euclid(1.);
|
||||
let mut blue = (hue - 1. / 3.).rem_euclid(1.);
|
||||
|
||||
for channel in [&mut red, &mut green, &mut blue] {
|
||||
*channel = if *channel * 6. < 1. {
|
||||
temp2 + (temp1 - temp2) * 6. * *channel
|
||||
} else if *channel * 2. < 1. {
|
||||
temp1
|
||||
} else if *channel * 3. < 2. {
|
||||
temp2 + (temp1 - temp2) * (2. / 3. - *channel) * 6.
|
||||
} else {
|
||||
temp2
|
||||
}
|
||||
.clamp(0., 1.);
|
||||
}
|
||||
|
||||
Color { red, green, blue, alpha }
|
||||
}
|
||||
|
||||
/// Return the `red` component.
|
||||
///
|
||||
/// # Examples
|
||||
@@ -154,6 +189,38 @@ impl Color {
|
||||
[(self.red * 255.) as u8, (self.green * 255.) as u8, (self.blue * 255.) as u8, (self.alpha * 255.) as u8]
|
||||
}
|
||||
|
||||
// https://www.niwa.nu/2013/05/math-behind-colorspace-conversions-rgb-hsl/
|
||||
/// Convert a [Color] to a hue, saturation, luminance and alpha (all between 0 and 1)
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use graphene_core::raster::color::Color;
|
||||
/// let color = Color::from_hsla(0.5, 0.2, 0.3, 1.).to_hsla();
|
||||
/// ```
|
||||
pub fn to_hsla(&self) -> [f32; 4] {
|
||||
let min_channel = self.red.min(self.green).min(self.blue);
|
||||
let max_channel = self.red.max(self.green).max(self.blue);
|
||||
|
||||
let luminance = (min_channel + max_channel) / 2.;
|
||||
let saturation = if min_channel == max_channel {
|
||||
0.
|
||||
} else if luminance <= 0.5 {
|
||||
(max_channel - min_channel) / (max_channel + min_channel)
|
||||
} else {
|
||||
(max_channel - min_channel) / (2. - max_channel - min_channel)
|
||||
};
|
||||
let hue = if self.red > self.green && self.red > self.blue {
|
||||
(self.green - self.blue) / (max_channel - min_channel)
|
||||
} else if self.green > self.red && self.green > self.blue {
|
||||
2. + (self.blue - self.red) / (max_channel - min_channel)
|
||||
} else {
|
||||
4. + (self.red - self.green) / (max_channel - min_channel)
|
||||
} / 6.;
|
||||
let hue = hue.rem_euclid(1.);
|
||||
|
||||
[hue, saturation, luminance, self.alpha]
|
||||
}
|
||||
|
||||
// TODO: Readd formatting
|
||||
|
||||
/// Creates a color from a 8-character RGBA hex string (without a # prefix).
|
||||
@@ -191,3 +258,37 @@ impl Color {
|
||||
Some(Color::from_rgb8(r, g, b))
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hsl_roundtrip() {
|
||||
for (red, green, blue) in [
|
||||
(24, 98, 118),
|
||||
(69, 11, 89),
|
||||
(54, 82, 38),
|
||||
(47, 76, 50),
|
||||
(25, 15, 73),
|
||||
(62, 57, 33),
|
||||
(55, 2, 18),
|
||||
(12, 3, 82),
|
||||
(91, 16, 98),
|
||||
(91, 39, 82),
|
||||
(97, 53, 32),
|
||||
(76, 8, 91),
|
||||
(54, 87, 19),
|
||||
(56, 24, 88),
|
||||
(14, 82, 34),
|
||||
(61, 86, 31),
|
||||
(73, 60, 75),
|
||||
(95, 79, 88),
|
||||
(13, 34, 4),
|
||||
(82, 84, 84),
|
||||
] {
|
||||
let col = Color::from_rgb8(red, green, blue);
|
||||
let [hue, saturation, luminance, alpha] = col.to_hsla();
|
||||
let result = Color::from_hsla(hue, saturation, luminance, alpha);
|
||||
assert!((col.r() - result.r()) < f32::EPSILON * 100.);
|
||||
assert!((col.g() - result.g()) < f32::EPSILON * 100.);
|
||||
assert!((col.b() - result.b()) < f32::EPSILON * 100.);
|
||||
assert!((col.a() - result.a()) < f32::EPSILON * 100.);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user