mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2025-08-03 13:02:20 +00:00
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:
parent
41f7ce0bb3
commit
8d778e4848
7 changed files with 331 additions and 5 deletions
|
@ -371,6 +371,35 @@ fn static_nodes() -> Vec<DocumentNodeType> {
|
||||||
outputs: vec![DocumentOutputType::new("Image", FrontendGraphDataType::Raster)],
|
outputs: vec![DocumentOutputType::new("Image", FrontendGraphDataType::Raster)],
|
||||||
properties: node_properties::mask_properties,
|
properties: node_properties::mask_properties,
|
||||||
},
|
},
|
||||||
|
DocumentNodeType {
|
||||||
|
name: "Insert Channel",
|
||||||
|
category: "Image Adjustments",
|
||||||
|
identifier: NodeImplementation::proto("graphene_std::raster::InsertChannelNode<_, _, _, _>"),
|
||||||
|
inputs: vec![
|
||||||
|
DocumentInputType::value("Image", TaggedValue::ImageFrame(ImageFrame::empty()), true),
|
||||||
|
DocumentInputType::value("Insertion", TaggedValue::ImageFrame(ImageFrame::empty()), true),
|
||||||
|
DocumentInputType::value("Replace", TaggedValue::RedGreenBlue(RedGreenBlue::Red), false),
|
||||||
|
],
|
||||||
|
outputs: vec![DocumentOutputType::new("Image", FrontendGraphDataType::Raster)],
|
||||||
|
properties: node_properties::insert_channel_properties,
|
||||||
|
},
|
||||||
|
DocumentNodeType {
|
||||||
|
name: "Combine Channels",
|
||||||
|
category: "Image Adjustments",
|
||||||
|
identifier: NodeImplementation::proto("graphene_std::raster::CombineChannelsNode"),
|
||||||
|
inputs: vec![
|
||||||
|
DocumentInputType::value("None", TaggedValue::None, false),
|
||||||
|
DocumentInputType::value("Red", TaggedValue::ImageFrame(ImageFrame::empty()), true),
|
||||||
|
DocumentInputType::value("Green", TaggedValue::ImageFrame(ImageFrame::empty()), true),
|
||||||
|
DocumentInputType::value("Blue", TaggedValue::ImageFrame(ImageFrame::empty()), true),
|
||||||
|
DocumentInputType::value("Alpha", TaggedValue::ImageFrame(ImageFrame::empty()), true),
|
||||||
|
],
|
||||||
|
outputs: vec![DocumentOutputType {
|
||||||
|
name: "Image",
|
||||||
|
data_type: FrontendGraphDataType::Raster,
|
||||||
|
}],
|
||||||
|
properties: node_properties::no_properties,
|
||||||
|
},
|
||||||
DocumentNodeType {
|
DocumentNodeType {
|
||||||
name: "Blend",
|
name: "Blend",
|
||||||
category: "Image Adjustments",
|
category: "Image Adjustments",
|
||||||
|
@ -483,6 +512,86 @@ fn static_nodes() -> Vec<DocumentNodeType> {
|
||||||
outputs: vec![DocumentOutputType::new("Image", FrontendGraphDataType::Raster)],
|
outputs: vec![DocumentOutputType::new("Image", FrontendGraphDataType::Raster)],
|
||||||
properties: node_properties::luminance_properties,
|
properties: node_properties::luminance_properties,
|
||||||
},
|
},
|
||||||
|
DocumentNodeType {
|
||||||
|
name: "Extract Channel",
|
||||||
|
category: "Image Adjustments",
|
||||||
|
identifier: NodeImplementation::proto("graphene_core::raster::ExtractChannelNode<_>"),
|
||||||
|
inputs: vec![
|
||||||
|
DocumentInputType::value("Image", TaggedValue::ImageFrame(ImageFrame::empty()), true),
|
||||||
|
DocumentInputType::value("From", TaggedValue::RedGreenBlue(RedGreenBlue::Red), false),
|
||||||
|
],
|
||||||
|
outputs: vec![DocumentOutputType::new("Image", FrontendGraphDataType::Raster)],
|
||||||
|
properties: node_properties::extract_channel_properties,
|
||||||
|
},
|
||||||
|
DocumentNodeType {
|
||||||
|
name: "Extract Alpha",
|
||||||
|
category: "Image Adjustments",
|
||||||
|
identifier: NodeImplementation::proto("graphene_core::raster::ExtractAlphaNode<>"),
|
||||||
|
inputs: vec![DocumentInputType::value("Image", TaggedValue::ImageFrame(ImageFrame::empty()), true)],
|
||||||
|
outputs: vec![DocumentOutputType::new("Image", FrontendGraphDataType::Raster)],
|
||||||
|
properties: node_properties::no_properties,
|
||||||
|
},
|
||||||
|
DocumentNodeType {
|
||||||
|
name: "Split Channels",
|
||||||
|
category: "Image Adjustments",
|
||||||
|
identifier: NodeImplementation::DocumentNode(NodeNetwork {
|
||||||
|
inputs: vec![0],
|
||||||
|
outputs: vec![NodeOutput::new(4, 0), NodeOutput::new(1, 0), NodeOutput::new(2, 0), NodeOutput::new(3, 0), NodeOutput::new(4, 0)],
|
||||||
|
nodes: [
|
||||||
|
DocumentNode {
|
||||||
|
name: "Identity".to_string(),
|
||||||
|
inputs: vec![NodeInput::Network(concrete!(ImageFrame<Color>))],
|
||||||
|
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::IdNode")),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
DocumentNode {
|
||||||
|
name: "RedNode".to_string(),
|
||||||
|
inputs: vec![NodeInput::node(0, 0), NodeInput::value(TaggedValue::RedGreenBlue(RedGreenBlue::Red), false)],
|
||||||
|
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::raster::ExtractChannelNode<_>")),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
DocumentNode {
|
||||||
|
name: "GreenNode".to_string(),
|
||||||
|
inputs: vec![NodeInput::node(0, 0), NodeInput::value(TaggedValue::RedGreenBlue(RedGreenBlue::Green), false)],
|
||||||
|
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::raster::ExtractChannelNode<_>")),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
DocumentNode {
|
||||||
|
name: "BlueNode".to_string(),
|
||||||
|
inputs: vec![NodeInput::node(0, 0), NodeInput::value(TaggedValue::RedGreenBlue(RedGreenBlue::Blue), false)],
|
||||||
|
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::raster::ExtractChannelNode<_>")),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
DocumentNode {
|
||||||
|
name: "AlphaNode".to_string(),
|
||||||
|
inputs: vec![NodeInput::node(0, 0)],
|
||||||
|
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::raster::ExtractAlphaNode<>")),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
DocumentNode {
|
||||||
|
name: "EmptyOutput".to_string(),
|
||||||
|
inputs: vec![NodeInput::value(TaggedValue::ImageFrame(ImageFrame::empty()), false)],
|
||||||
|
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::IdNode")),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
]
|
||||||
|
.into_iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(id, node)| (id as NodeId, node))
|
||||||
|
.collect(),
|
||||||
|
|
||||||
|
..Default::default()
|
||||||
|
}),
|
||||||
|
inputs: vec![DocumentInputType::value("Image", TaggedValue::ImageFrame(ImageFrame::empty()), true)],
|
||||||
|
outputs: vec![
|
||||||
|
DocumentOutputType::new("Empty", FrontendGraphDataType::Raster),
|
||||||
|
DocumentOutputType::new("Red", FrontendGraphDataType::Raster),
|
||||||
|
DocumentOutputType::new("Green", FrontendGraphDataType::Raster),
|
||||||
|
DocumentOutputType::new("Blue", FrontendGraphDataType::Raster),
|
||||||
|
DocumentOutputType::new("Alpha", FrontendGraphDataType::Raster),
|
||||||
|
],
|
||||||
|
properties: node_properties::no_properties,
|
||||||
|
},
|
||||||
DocumentNodeType {
|
DocumentNodeType {
|
||||||
name: "Gaussian Blur",
|
name: "Gaussian Blur",
|
||||||
category: "Image Filters",
|
category: "Image Filters",
|
||||||
|
|
|
@ -231,6 +231,26 @@ fn number_widget(document_node: &DocumentNode, node_id: NodeId, index: usize, na
|
||||||
widgets
|
widgets
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//TODO Use generalized Version of this as soon as it's available
|
||||||
|
fn color_channel(document_node: &DocumentNode, node_id: u64, index: usize, name: &str, blank_assist: bool) -> LayoutGroup {
|
||||||
|
let mut widgets = start_widgets(document_node, node_id, index, name, FrontendGraphDataType::General, blank_assist);
|
||||||
|
if let &NodeInput::Value {
|
||||||
|
tagged_value: TaggedValue::RedGreenBlue(mode),
|
||||||
|
exposed: false,
|
||||||
|
} = &document_node.inputs[index]
|
||||||
|
{
|
||||||
|
let calculation_modes = [RedGreenBlue::Red, RedGreenBlue::Green, RedGreenBlue::Blue];
|
||||||
|
let mut entries = Vec::with_capacity(calculation_modes.len());
|
||||||
|
for method in calculation_modes {
|
||||||
|
entries.push(DropdownEntryData::new(method.to_string()).on_update(update_value(move |_| TaggedValue::RedGreenBlue(method), node_id, index)));
|
||||||
|
}
|
||||||
|
let entries = vec![entries];
|
||||||
|
|
||||||
|
widgets.extend_from_slice(&[WidgetHolder::unrelated_separator(), DropdownInput::new(entries).selected_index(Some(mode as u32)).widget_holder()]);
|
||||||
|
}
|
||||||
|
LayoutGroup::Row { widgets }.with_tooltip("Color Channel")
|
||||||
|
}
|
||||||
|
|
||||||
//TODO Use generalized Version of this as soon as it's available
|
//TODO Use generalized Version of this as soon as it's available
|
||||||
fn blend_mode(document_node: &DocumentNode, node_id: u64, index: usize, name: &str, blank_assist: bool) -> LayoutGroup {
|
fn blend_mode(document_node: &DocumentNode, node_id: u64, index: usize, name: &str, blank_assist: bool) -> LayoutGroup {
|
||||||
let mut widgets = start_widgets(document_node, node_id, index, name, FrontendGraphDataType::General, blank_assist);
|
let mut widgets = start_widgets(document_node, node_id, index, name, FrontendGraphDataType::General, blank_assist);
|
||||||
|
@ -251,7 +271,7 @@ fn blend_mode(document_node: &DocumentNode, node_id: u64, index: usize, name: &s
|
||||||
LayoutGroup::Row { widgets }.with_tooltip("Formula used for blending")
|
LayoutGroup::Row { widgets }.with_tooltip("Formula used for blending")
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Generalize this for all dropdowns ( also see blend_mode )
|
// TODO: Generalize this for all dropdowns ( also see blend_mode and channel_extration )
|
||||||
fn luminance_calculation(document_node: &DocumentNode, node_id: u64, index: usize, name: &str, blank_assist: bool) -> LayoutGroup {
|
fn luminance_calculation(document_node: &DocumentNode, node_id: u64, index: usize, name: &str, blank_assist: bool) -> LayoutGroup {
|
||||||
let mut widgets = start_widgets(document_node, node_id, index, name, FrontendGraphDataType::General, blank_assist);
|
let mut widgets = start_widgets(document_node, node_id, index, name, FrontendGraphDataType::General, blank_assist);
|
||||||
if let &NodeInput::Value {
|
if let &NodeInput::Value {
|
||||||
|
@ -590,6 +610,18 @@ pub fn luminance_properties(document_node: &DocumentNode, node_id: NodeId, _cont
|
||||||
vec![luminance_calc]
|
vec![luminance_calc]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn insert_channel_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
|
||||||
|
let color_channel = color_channel(document_node, node_id, 2, "Into", true);
|
||||||
|
|
||||||
|
vec![color_channel]
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn extract_channel_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
|
||||||
|
let color_channel = color_channel(document_node, node_id, 1, "From", true);
|
||||||
|
|
||||||
|
vec![color_channel]
|
||||||
|
}
|
||||||
|
|
||||||
pub fn adjust_hsl_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
|
pub fn adjust_hsl_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
|
||||||
let hue_shift = number_widget(document_node, node_id, 1, "Hue Shift", NumberInput::default().min(-180.).max(180.).unit("°"), true);
|
let hue_shift = number_widget(document_node, node_id, 1, "Hue Shift", NumberInput::default().min(-180.).max(180.).unit("°"), true);
|
||||||
let saturation_shift = number_widget(document_node, node_id, 2, "Saturation Shift", NumberInput::default().min(-100.).max(100.).unit("%"), true);
|
let saturation_shift = number_widget(document_node, node_id, 2, "Saturation Shift", NumberInput::default().min(-100.).max(100.).unit("%"), true);
|
||||||
|
|
|
@ -140,6 +140,11 @@ pub trait RGB: Pixel {
|
||||||
self.blue()
|
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 {
|
pub trait AssociatedAlpha: RGB + Alpha {
|
||||||
fn to_unassociated<Out: UnassociatedAlpha>(&self) -> Out;
|
fn to_unassociated<Out: UnassociatedAlpha>(&self) -> Out;
|
||||||
|
|
|
@ -46,7 +46,7 @@ impl core::fmt::Display for LuminanceCalculation {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BlendMode {
|
impl BlendMode {
|
||||||
pub fn list() -> [BlendMode; 26] {
|
pub fn list() -> [BlendMode; 29] {
|
||||||
[
|
[
|
||||||
BlendMode::Normal,
|
BlendMode::Normal,
|
||||||
BlendMode::Multiply,
|
BlendMode::Multiply,
|
||||||
|
@ -74,6 +74,9 @@ impl BlendMode {
|
||||||
BlendMode::Saturation,
|
BlendMode::Saturation,
|
||||||
BlendMode::Color,
|
BlendMode::Color,
|
||||||
BlendMode::Luminosity,
|
BlendMode::Luminosity,
|
||||||
|
BlendMode::InsertRed,
|
||||||
|
BlendMode::InsertGreen,
|
||||||
|
BlendMode::InsertBlue,
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -121,6 +124,11 @@ pub enum BlendMode {
|
||||||
Saturation,
|
Saturation,
|
||||||
Color,
|
Color,
|
||||||
Luminosity,
|
Luminosity,
|
||||||
|
|
||||||
|
// Other Stuff
|
||||||
|
InsertRed,
|
||||||
|
InsertGreen,
|
||||||
|
InsertBlue,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl core::fmt::Display for BlendMode {
|
impl core::fmt::Display for BlendMode {
|
||||||
|
@ -157,6 +165,10 @@ impl core::fmt::Display for BlendMode {
|
||||||
BlendMode::Saturation => write!(f, "Saturation"),
|
BlendMode::Saturation => write!(f, "Saturation"),
|
||||||
BlendMode::Color => write!(f, "Color"),
|
BlendMode::Color => write!(f, "Color"),
|
||||||
BlendMode::Luminosity => write!(f, "Luminosity"),
|
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)
|
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)]
|
#[derive(Debug, Clone, Copy, Default)]
|
||||||
pub struct LevelsNode<InputStart, InputMid, InputEnd, OutputStart, OutputEnd> {
|
pub struct LevelsNode<InputStart, InputMid, InputEnd, OutputStart, OutputEnd> {
|
||||||
input_start: InputStart,
|
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::Saturation => background.blend_saturation(foreground),
|
||||||
BlendMode::Color => background.blend_color(foreground),
|
BlendMode::Color => background.blend_color(foreground),
|
||||||
BlendMode::Luminosity => background.blend_luminosity(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))
|
background.alpha_blend(target_color.to_associated_alpha(opacity as f32))
|
||||||
|
|
|
@ -12,7 +12,7 @@ use spirv_std::num_traits::Euclid;
|
||||||
|
|
||||||
use bytemuck::{Pod, Zeroable};
|
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)]
|
#[repr(C)]
|
||||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
|
@ -86,6 +86,17 @@ impl RGB for Color {
|
||||||
self.blue
|
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 {
|
impl Pixel for Color {
|
||||||
#[cfg(not(target_arch = "spirv"))]
|
#[cfg(not(target_arch = "spirv"))]
|
||||||
|
@ -391,6 +402,42 @@ impl Color {
|
||||||
Color::from_hsla(hue, saturation, lightness, alpha)
|
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)]
|
#[inline(always)]
|
||||||
pub fn blend_normal(_c_b: f32, c_s: f32) -> f32 {
|
pub fn blend_normal(_c_b: f32, c_s: f32) -> f32 {
|
||||||
c_s
|
c_s
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use dyn_any::{DynAny, StaticType};
|
use dyn_any::{DynAny, StaticType};
|
||||||
use glam::{DAffine2, DVec2};
|
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::transform::Transform;
|
||||||
|
|
||||||
use graphene_core::value::CopiedNode;
|
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)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub struct MaskImageNode<P, S, Stencil> {
|
pub struct MaskImageNode<P, S, Stencil> {
|
||||||
stencil: Stencil,
|
stencil: Stencil,
|
||||||
|
@ -182,7 +230,7 @@ pub struct MaskImageNode<P, S, Stencil> {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[node_macro::node_fn(MaskImageNode<_P, _S>)]
|
#[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
|
// _P is the color of the input image. It must have an alpha channel because that is going to
|
||||||
// be modified by the mask
|
// be modified by the mask
|
||||||
_P: Copy + Alpha,
|
_P: Copy + Alpha,
|
||||||
|
|
|
@ -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::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<Color>]),
|
||||||
register_node!(graphene_std::raster::MaskImageNode<_, _, _>, input: ImageFrame<Color>, params: [ImageFrame<Luma>]),
|
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::raster::EmptyImageNode<_, _>, input: DAffine2, params: [Color]),
|
||||||
register_node!(graphene_std::memo::MonitorNode<_>, input: ImageFrame<Color>, params: []),
|
register_node!(graphene_std::memo::MonitorNode<_>, input: ImageFrame<Color>, params: []),
|
||||||
register_node!(graphene_std::memo::MonitorNode<_>, input: graphene_core::GraphicGroup, 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
|
// Filters
|
||||||
raster_node!(graphene_core::raster::LuminanceNode<_>, params: [LuminanceCalculation]),
|
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]),
|
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_std::image_segmentation::ImageSegmentationNode<_>, input: ImageFrame<Color>, params: [ImageFrame<Color>]),
|
||||||
register_node!(graphene_core::raster::IndexNode<_>, input: Vec<ImageFrame<Color>>, params: [u32]),
|
register_node!(graphene_core::raster::IndexNode<_>, input: Vec<ImageFrame<Color>>, params: [u32]),
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue