Introduce Split/Combine Channels nodes (#1153)

* Add Channel Extrataction Node

* Add hacky BlendModes for Inserting Color Channels

* Fix Channel Exporter

* Add Monochrome Option and Multi Output Node

* Fix Input Mapping

* Fix Formatting

* Split Alpha Extraction to seperate node

* Remove unnecessary functionality

* Add Alpha Channel as an output to the extract channel node

* Fix compilation

* Add unpolished 'Combine Channels' Node

* Fix Rebasing Issues

* Add a bit of polish

* Fix Rebase Issues

* Switch from 'ColorChannel' to 'RedGreenBlue'
I initially added an enum to hold color channels called 'ColorChannel', but while implementing the nodes, there somebody allready added a similar enum so I switched to that type

* Add correct names

* Add Improvement

- Some Performance Improvements
- Some Formatting Improvements

* Add some improvements
Most of this stuff was done by TrueDoctor in my Luchbreak :D

* Implement IO Improvements
- Converted primary output from split node to a dummy output
- Removed primary Input from split node

* Fix Formatting

* Fix Combine RGB Node (hopefully final :D )

* Swap around Inputs and Outputs
Move from ARGB -> RGBA

* Improve naming

* More naming fixes

* Fix Replace -> Into

* Rename Replacment -> Insertion

* Add blank assist area

---------

Co-authored-by: Keavon Chambers <keavon@keavon.com>
Co-authored-by: Dennis Kobert <dennis@kobert.dev>
This commit is contained in:
isiko 2023-05-25 12:15:00 +02:00 committed by Keavon Chambers
parent 41f7ce0bb3
commit 8d778e4848
7 changed files with 331 additions and 5 deletions

View file

@ -140,6 +140,11 @@ pub trait RGB: Pixel {
self.blue()
}
}
pub trait RGBMut: RGB {
fn set_red(&mut self, red: Self::ColorChannel);
fn set_green(&mut self, green: Self::ColorChannel);
fn set_blue(&mut self, blue: Self::ColorChannel);
}
pub trait AssociatedAlpha: RGB + Alpha {
fn to_unassociated<Out: UnassociatedAlpha>(&self) -> Out;

View file

@ -46,7 +46,7 @@ impl core::fmt::Display for LuminanceCalculation {
}
impl BlendMode {
pub fn list() -> [BlendMode; 26] {
pub fn list() -> [BlendMode; 29] {
[
BlendMode::Normal,
BlendMode::Multiply,
@ -74,6 +74,9 @@ impl BlendMode {
BlendMode::Saturation,
BlendMode::Color,
BlendMode::Luminosity,
BlendMode::InsertRed,
BlendMode::InsertGreen,
BlendMode::InsertBlue,
]
}
}
@ -121,6 +124,11 @@ pub enum BlendMode {
Saturation,
Color,
Luminosity,
// Other Stuff
InsertRed,
InsertGreen,
InsertBlue,
}
impl core::fmt::Display for BlendMode {
@ -157,6 +165,10 @@ impl core::fmt::Display for BlendMode {
BlendMode::Saturation => write!(f, "Saturation"),
BlendMode::Color => write!(f, "Color"),
BlendMode::Luminosity => write!(f, "Luminosity"),
BlendMode::InsertRed => write!(f, "Insert Red"),
BlendMode::InsertGreen => write!(f, "Insert Green"),
BlendMode::InsertBlue => write!(f, "Insert Blue"),
}
}
}
@ -178,6 +190,30 @@ fn luminance_color_node(color: Color, luminance_calc: LuminanceCalculation) -> C
color.map_rgb(|_| luminance)
}
#[derive(Debug, Clone, Copy, Default)]
pub struct ExtractChannelNode<TargetChannel> {
channel: TargetChannel,
}
#[node_macro::node_fn(ExtractChannelNode)]
fn extract_channel_node(color: Color, channel: RedGreenBlue) -> Color {
let extracted_value = match channel {
RedGreenBlue::Red => color.r(),
RedGreenBlue::Green => color.g(),
RedGreenBlue::Blue => color.b(),
};
return color.map_rgb(|_| extracted_value);
}
#[derive(Debug, Clone, Copy, Default)]
pub struct ExtractAlphaNode;
#[node_macro::node_fn(ExtractAlphaNode)]
fn extract_alpha_node(color: Color) -> Color {
let alpha = color.a();
Color::from_rgbaf32(alpha, alpha, alpha, 1.0).unwrap()
}
#[derive(Debug, Clone, Copy, Default)]
pub struct LevelsNode<InputStart, InputMid, InputEnd, OutputStart, OutputEnd> {
input_start: InputStart,
@ -397,6 +433,10 @@ fn blend_node(input: (Color, Color), blend_mode: BlendMode, opacity: f64) -> Col
BlendMode::Saturation => background.blend_saturation(foreground),
BlendMode::Color => background.blend_color(foreground),
BlendMode::Luminosity => background.blend_luminosity(foreground),
BlendMode::InsertRed => foreground.with_red(background.r()),
BlendMode::InsertGreen => foreground.with_green(background.g()),
BlendMode::InsertBlue => foreground.with_blue(background.b()),
};
background.alpha_blend(target_color.to_associated_alpha(opacity as f32))

View file

@ -12,7 +12,7 @@ use spirv_std::num_traits::Euclid;
use bytemuck::{Pod, Zeroable};
use super::{Alpha, AssociatedAlpha, Luminance, Pixel, Rec709Primaries, RGB, SRGB};
use super::{Alpha, AssociatedAlpha, Luminance, Pixel, RGBMut, Rec709Primaries, RGB, SRGB};
#[repr(C)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
@ -86,6 +86,17 @@ impl RGB for Color {
self.blue
}
}
impl RGBMut for Color {
fn set_red(&mut self, red: Self::ColorChannel) {
self.red = red;
}
fn set_green(&mut self, green: Self::ColorChannel) {
self.green = green;
}
fn set_blue(&mut self, blue: Self::ColorChannel) {
self.blue = blue;
}
}
impl Pixel for Color {
#[cfg(not(target_arch = "spirv"))]
@ -391,6 +402,42 @@ impl Color {
Color::from_hsla(hue, saturation, lightness, alpha)
}
pub fn with_alpha(&self, alpha: f32) -> Color {
Color {
red: self.red,
green: self.green,
blue: self.blue,
alpha,
}
}
pub fn with_red(&self, red: f32) -> Color {
Color {
red,
green: self.green,
blue: self.blue,
alpha: self.alpha,
}
}
pub fn with_green(&self, green: f32) -> Color {
Color {
red: self.red,
green,
blue: self.blue,
alpha: self.alpha,
}
}
pub fn with_blue(&self, blue: f32) -> Color {
Color {
red: self.red,
green: self.green,
blue,
alpha: self.alpha,
}
}
#[inline(always)]
pub fn blend_normal(_c_b: f32, c_s: f32) -> f32 {
c_s

View file

@ -1,6 +1,6 @@
use dyn_any::{DynAny, StaticType};
use glam::{DAffine2, DVec2};
use graphene_core::raster::{Alpha, BlendMode, BlendNode, Image, ImageFrame, LinearChannel, Luminance, Pixel, RasterMut, Sample};
use graphene_core::raster::{Alpha, BlendMode, BlendNode, Image, ImageFrame, Linear, LinearChannel, Luminance, Pixel, RGBMut, Raster, RasterMut, RedGreenBlue, Sample};
use graphene_core::transform::Transform;
use graphene_core::value::CopiedNode;
@ -174,6 +174,54 @@ fn compute_transformed_bounding_box(transform: DAffine2) -> Bbox {
}
}
#[derive(Debug, Clone, Copy)]
pub struct InsertChannelNode<P, S, Insertion, TargetChannel> {
insertion: Insertion,
target_channel: TargetChannel,
_p: PhantomData<P>,
_s: PhantomData<S>,
}
#[node_macro::node_fn(InsertChannelNode<_P, _S>)]
fn insert_channel_node<
// _P is the color of the input image.
_P: RGBMut,
_S: Pixel + Luminance,
// Input image
Input: RasterMut<Pixel = _P>,
Insertion: Raster<Pixel = _S>,
>(
mut image: Input,
insertion: Insertion,
target_channel: RedGreenBlue,
) -> Input
where
_P::ColorChannel: Linear,
{
if insertion.width() == 0 {
return image;
}
if insertion.width() != image.width() || insertion.height() != image.height() {
log::warn!("Stencil and image have different sizes. This is not supported.");
return image;
}
for y in 0..image.height() {
for x in 0..image.width() {
let image_pixel = image.get_pixel_mut(x, y).unwrap();
let insertion_pixel = insertion.get_pixel(x, y).unwrap();
match target_channel {
RedGreenBlue::Red => image_pixel.set_red(insertion_pixel.l().cast_linear_channel()),
RedGreenBlue::Green => image_pixel.set_green(insertion_pixel.l().cast_linear_channel()),
RedGreenBlue::Blue => image_pixel.set_blue(insertion_pixel.l().cast_linear_channel()),
}
}
}
image
}
#[derive(Debug, Clone, Copy)]
pub struct MaskImageNode<P, S, Stencil> {
stencil: Stencil,
@ -182,7 +230,7 @@ pub struct MaskImageNode<P, S, Stencil> {
}
#[node_macro::node_fn(MaskImageNode<_P, _S>)]
fn mask_image<
fn mask_imge<
// _P is the color of the input image. It must have an alpha channel because that is going to
// be modified by the mask
_P: Copy + Alpha,

View file

@ -153,6 +153,49 @@ fn node_registry() -> HashMap<NodeIdentifier, HashMap<NodeIOTypes, NodeConstruct
register_node!(graphene_std::raster::DownresNode<_>, input: ImageFrame<Color>, params: []),
register_node!(graphene_std::raster::MaskImageNode<_, _, _>, input: ImageFrame<Color>, params: [ImageFrame<Color>]),
register_node!(graphene_std::raster::MaskImageNode<_, _, _>, input: ImageFrame<Color>, params: [ImageFrame<Luma>]),
register_node!(graphene_std::raster::InsertChannelNode<_, _, _, _>, input: ImageFrame<Color>, params: [ImageFrame<Color>, RedGreenBlue]),
register_node!(graphene_std::raster::InsertChannelNode<_, _, _, _>, input: ImageFrame<Color>, params: [ImageFrame<Luma>, RedGreenBlue]),
vec![(
NodeIdentifier::new("graphene_std::raster::CombineChannelsNode"),
|args| {
use graphene_core::raster::*;
use graphene_core::value::*;
let channel_r: DowncastBothNode<(), ImageFrame<Color>> = DowncastBothNode::new(args[0]);
let channel_g: DowncastBothNode<(), ImageFrame<Color>> = DowncastBothNode::new(args[1]);
let channel_b: DowncastBothNode<(), ImageFrame<Color>> = DowncastBothNode::new(args[2]);
let channel_a: DowncastBothNode<(), ImageFrame<Color>> = DowncastBothNode::new(args[3]);
let insert_r = InsertChannelNode::new(channel_r.clone(), CopiedNode::new(RedGreenBlue::Red));
let insert_g = InsertChannelNode::new(channel_g.clone(), CopiedNode::new(RedGreenBlue::Green));
let insert_b = InsertChannelNode::new(channel_b.clone(), CopiedNode::new(RedGreenBlue::Blue));
let complete_node = insert_r.then(insert_g).then(insert_b);
let complete_node = complete_node.then(MaskImageNode::new(channel_a.clone()));
// TODO: Move to FN Node for better performance
let (mut transform, mut bounds) = (DAffine2::ZERO, glam::UVec2::ZERO);
for imf in [channel_a, channel_r, channel_g, channel_b] {
let image = imf.eval(());
if image.image.width() > bounds.x {
bounds = glam::UVec2::new(image.image.width(), image.image.height());
transform = image.transform;
}
}
let empty_image = ImageFrame {
image: Image::new(bounds.x, bounds.y, Color::BLACK),
transform,
};
let final_image = ClonedNode::new(empty_image).then(complete_node);
let any: DynAnyNode<(), _, _> = graphene_std::any::DynAnyNode::new(ValueNode::new(final_image));
Box::pin(any)
},
NodeIOTypes::new(
concrete!(()),
concrete!(ImageFrame<Color>),
vec![value_fn!(ImageFrame<Color>), value_fn!(ImageFrame<Color>), value_fn!(ImageFrame<Color>), value_fn!(ImageFrame<Color>)],
),
)],
register_node!(graphene_std::raster::EmptyImageNode<_, _>, input: DAffine2, params: [Color]),
register_node!(graphene_std::memo::MonitorNode<_>, input: ImageFrame<Color>, params: []),
register_node!(graphene_std::memo::MonitorNode<_>, input: graphene_core::GraphicGroup, params: []),
@ -251,6 +294,8 @@ fn node_registry() -> HashMap<NodeIdentifier, HashMap<NodeIOTypes, NodeConstruct
)],
// Filters
raster_node!(graphene_core::raster::LuminanceNode<_>, params: [LuminanceCalculation]),
raster_node!(graphene_core::raster::ExtractChannelNode<_>, params: [RedGreenBlue]),
raster_node!(graphene_core::raster::ExtractAlphaNode<>, params: []),
raster_node!(graphene_core::raster::LevelsNode<_, _, _, _, _>, params: [f64, f64, f64, f64, f64]),
register_node!(graphene_std::image_segmentation::ImageSegmentationNode<_>, input: ImageFrame<Color>, params: [ImageFrame<Color>]),
register_node!(graphene_core::raster::IndexNode<_>, input: Vec<ImageFrame<Color>>, params: [u32]),