mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-15 22:28:10 +08:00
Node graph improvements (#855)
* Selecting multiple nodes * Improve logs * Log bad types in dyn any * Add (broken) node links * New topological sort * Fix reorder ids function * Input and output node * Add nodes that operate on images * Fixups * Show node parameters together with layer properties * New nodes don't crash editor * Fix tests * Node positions backend * Generate node graph on value change * Add expose input message * Fix tests Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
committed by
Keavon Chambers
parent
bbe98d3fe3
commit
2994afa6b8
@@ -26,3 +26,8 @@ proc-macro2 = {version = "1.0", default-features = false, features = ["proc-macr
|
||||
quote = {version = "1.0", default-features = false }
|
||||
image = "*"
|
||||
dyn-clone = "1.0"
|
||||
|
||||
[dependencies.serde]
|
||||
version = "1.0"
|
||||
optional = true
|
||||
features = ["derive"]
|
||||
|
||||
@@ -98,7 +98,8 @@ impl<Reader: std::io::Read> Node<Reader> for BufferNode {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, DynAny)]
|
||||
#[derive(Clone, Debug, PartialEq, DynAny)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct Image {
|
||||
pub width: u32,
|
||||
pub height: u32,
|
||||
@@ -165,16 +166,104 @@ pub fn export_image_node<'n>() -> impl Node<(Image, &'n str), Output = Result<()
|
||||
})
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct GrayscaleImageNode;
|
||||
|
||||
impl Node<Image> for GrayscaleImageNode {
|
||||
type Output = Image;
|
||||
fn eval(self, mut image: Image) -> Image {
|
||||
for pixel in &mut image.data {
|
||||
let avg = (pixel.r() + pixel.g() + pixel.b()) / 3.;
|
||||
*pixel = Color::from_rgbaf32_unchecked(avg, avg, avg, pixel.a());
|
||||
}
|
||||
image
|
||||
}
|
||||
}
|
||||
impl Node<Image> for &GrayscaleImageNode {
|
||||
type Output = Image;
|
||||
fn eval(self, mut image: Image) -> Image {
|
||||
for pixel in &mut image.data {
|
||||
let avg = (pixel.r() + pixel.g() + pixel.b()) / 3.;
|
||||
*pixel = Color::from_rgbaf32_unchecked(avg, avg, avg, pixel.a());
|
||||
}
|
||||
image
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct BrightenImageNode<N: Node<(), Output = f32>>(N);
|
||||
|
||||
impl<N: Node<(), Output = f32>> Node<Image> for BrightenImageNode<N> {
|
||||
type Output = Image;
|
||||
fn eval(self, mut image: Image) -> Image {
|
||||
let brightness = self.0.eval(());
|
||||
let per_channel = |col: f32| (col + brightness / 255.).clamp(0., 1.);
|
||||
for pixel in &mut image.data {
|
||||
*pixel = Color::from_rgbaf32_unchecked(per_channel(pixel.r()), per_channel(pixel.g()), per_channel(pixel.b()), pixel.a());
|
||||
}
|
||||
image
|
||||
}
|
||||
}
|
||||
impl<N: Node<(), Output = f32> + Copy> Node<Image> for &BrightenImageNode<N> {
|
||||
type Output = Image;
|
||||
fn eval(self, mut image: Image) -> Image {
|
||||
let brightness = self.0.eval(());
|
||||
let per_channel = |col: f32| (col + brightness / 255.).clamp(0., 1.);
|
||||
for pixel in &mut image.data {
|
||||
*pixel = Color::from_rgbaf32_unchecked(per_channel(pixel.r()), per_channel(pixel.g()), per_channel(pixel.b()), pixel.a());
|
||||
}
|
||||
image
|
||||
}
|
||||
}
|
||||
|
||||
impl<N: Node<(), Output = f32> + Copy> BrightenImageNode<N> {
|
||||
pub fn new(node: N) -> Self {
|
||||
Self(node)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct HueShiftImage<N: Node<(), Output = f32>>(N);
|
||||
|
||||
impl<N: Node<(), Output = f32>> Node<Image> for HueShiftImage<N> {
|
||||
type Output = Image;
|
||||
fn eval(self, mut image: Image) -> Image {
|
||||
let hue_shift = self.0.eval(());
|
||||
for pixel in &mut image.data {
|
||||
let [hue, saturation, luminance, alpha] = pixel.to_hsla();
|
||||
*pixel = Color::from_hsla(hue + hue_shift / 360., saturation, luminance, alpha);
|
||||
}
|
||||
image
|
||||
}
|
||||
}
|
||||
impl<N: Node<(), Output = f32> + Copy> Node<Image> for &HueShiftImage<N> {
|
||||
type Output = Image;
|
||||
fn eval(self, mut image: Image) -> Image {
|
||||
let hue_shift = self.0.eval(());
|
||||
for pixel in &mut image.data {
|
||||
let [hue, saturation, luminance, alpha] = pixel.to_hsla();
|
||||
*pixel = Color::from_hsla(hue + hue_shift / 360., saturation, luminance, alpha);
|
||||
}
|
||||
image
|
||||
}
|
||||
}
|
||||
|
||||
impl<N: Node<(), Output = f32> + Copy> HueShiftImage<N> {
|
||||
pub fn new(node: N) -> Self {
|
||||
Self(node)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use graphene_core::raster::color::Color;
|
||||
use graphene_core::raster::GrayscaleNode;
|
||||
use graphene_core::raster::GrayscaleColorNode;
|
||||
|
||||
#[test]
|
||||
fn map_node() {
|
||||
let array = [Color::from_rgbaf32(1.0, 0.0, 0.0, 1.0).unwrap()];
|
||||
let map = MapNode(GrayscaleNode, PhantomData);
|
||||
let map = MapNode(GrayscaleColorNode, PhantomData);
|
||||
let values = map.eval(array.into_iter());
|
||||
assert_eq!(values[0], Color::from_rgbaf32(0.33333334, 0.33333334, 0.33333334, 1.0).unwrap());
|
||||
}
|
||||
@@ -182,7 +271,7 @@ mod test {
|
||||
#[test]
|
||||
fn load_image() {
|
||||
let image = image_node::<&str>();
|
||||
let gray = MapImageNode::new(GrayscaleNode);
|
||||
let gray = MapImageNode::new(GrayscaleColorNode);
|
||||
|
||||
let grayscale_picture = image.then(MapResultNode::new(&gray));
|
||||
let export = export_image_node();
|
||||
|
||||
Reference in New Issue
Block a user