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:
0HyperCube
2022-11-12 21:23:28 +00:00
committed by Keavon Chambers
parent e8256dd350
commit 504136b61b
37 changed files with 1213 additions and 294 deletions

View File

@@ -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>

View File

@@ -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.);
}
}