Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -589,6 +589,8 @@ impl MessageHandler<DocumentMessage, (u64, &InputPreprocessorMessageHandler, &Pe
responses.add(BroadcastEvent::DocumentIsDirty);
}
PasteImage { image, mouse } => {
// All the image's pixels have been converted to 0..=1, linear, and premultiplied by `Color::from_rgba8_srgb`

let image_size = DVec2::new(image.width as f64, image.height as f64);

let Some(image_node_type) = crate::messages::portfolio::document::node_graph::resolve_document_node_type("Image") else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2211,7 +2211,34 @@ fn static_nodes() -> Vec<DocumentNodeType> {
DocumentInputType::value("Index", TaggedValue::U32(0), false),
],
outputs: vec![DocumentOutputType::new("Image", FrontendGraphDataType::Raster)],
properties: node_properties::index_node_properties,
properties: node_properties::index_properties,
..Default::default()
},
// Applies the given color to each pixel of an image but maintains the alpha value
DocumentNodeType {
name: "Color Fill",
category: "Image Adjustments",
identifier: NodeImplementation::proto("graphene_core::raster::adjustments::ColorFillNode<_>"),
inputs: vec![
DocumentInputType::value("Image", TaggedValue::ImageFrame(ImageFrame::empty()), true),
DocumentInputType::value("Color", TaggedValue::Color(Color::BLACK), false),
],
outputs: vec![DocumentOutputType::new("Image", FrontendGraphDataType::Raster)],
properties: node_properties::color_fill_properties,
..Default::default()
},
DocumentNodeType {
name: "Color Overlay",
category: "Image Adjustments",
identifier: NodeImplementation::proto("graphene_core::raster::adjustments::ColorOverlayNode<_, _, _>"),
inputs: vec![
DocumentInputType::value("Image", TaggedValue::ImageFrame(ImageFrame::empty()), true),
DocumentInputType::value("Color", TaggedValue::Color(Color::BLACK), false),
DocumentInputType::value("Blend Mode", TaggedValue::BlendMode(BlendMode::Normal), false),
DocumentInputType::value("Opacity", TaggedValue::F32(100.), false),
],
outputs: vec![DocumentOutputType::new("Image", FrontendGraphDataType::Raster)],
properties: node_properties::color_overlay_properties,
..Default::default()
},
]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1810,7 +1810,7 @@ pub fn no_properties(_document_node: &DocumentNode, _node_id: NodeId, _context:
string_properties("Node has no properties")
}

pub fn index_node_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
pub fn index_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
let index = number_widget(document_node, node_id, 1, "Index", NumberInput::default().min(0.), true);

vec![LayoutGroup::Row { widgets: index }]
Expand Down Expand Up @@ -1938,3 +1938,16 @@ pub fn artboard_properties(document_node: &DocumentNode, node_id: NodeId, _conte
};
vec![location, dimensions, background, clip]
}

pub fn color_fill_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
let color = color_widget(document_node, node_id, 1, "Color", ColorInput::default(), true);
vec![color]
}

pub fn color_overlay_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
let color = color_widget(document_node, node_id, 1, "Color", ColorInput::default(), true);
let blend_mode = blend_mode(document_node, node_id, 2, "Blend Mode", true);
let opacity = number_widget(document_node, node_id, 3, "Opacity", NumberInput::default().percentage(), true);

vec![color, blend_mode, LayoutGroup::Row { widgets: opacity }]
}
87 changes: 81 additions & 6 deletions node-graph/gcore/src/raster/adjustments.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#![allow(clippy::too_many_arguments)]

use super::curve::{Curve, CurveManipulatorGroup, ValueMapperNode};
use super::{Channel, Color, Node};
use super::{Channel, Color, ImageFrame, Node, RGBMut};

use bezier_rs::{Bezier, TValue};
use dyn_any::{DynAny, StaticType};
Expand Down Expand Up @@ -415,9 +415,8 @@ fn blend_node(input: (Color, Color), blend_mode: BlendMode, opacity: f32) -> Col
blend_colors(input.0, input.1, blend_mode, opacity / 100.)
}

#[inline(always)]
pub fn blend_colors(foreground: Color, background: Color, blend_mode: BlendMode, opacity: f32) -> Color {
let target_color = match blend_mode {
pub fn apply_blend_mode(foreground: Color, background: Color, blend_mode: BlendMode) -> Color {
match blend_mode {
// Normal group
BlendMode::Normal => background.blend_rgb(foreground, Color::blend_normal),
// Darken group
Expand Down Expand Up @@ -450,10 +449,19 @@ pub fn blend_colors(foreground: Color, background: Color, blend_mode: BlendMode,
BlendMode::Saturation => background.blend_saturation(foreground),
BlendMode::Color => background.blend_color(foreground),
BlendMode::Luminosity => background.blend_luminosity(foreground),
// Other utility blend modes (hidden from the normal list)
// Other utility blend modes (hidden from the normal list) - do not have alpha blend
_ => panic!("Used blend mode without alpha blend"),
}
}

#[inline(always)]
pub fn blend_colors(foreground: Color, background: Color, blend_mode: BlendMode, opacity: f32) -> Color {
let target_color = match blend_mode {
// Other utility blend modes (hidden from the normal list) - do not have alpha blend
BlendMode::Erase => return background.alpha_subtract(foreground),
BlendMode::Restore => return background.alpha_add(foreground),
BlendMode::MultiplyAlpha => return background.alpha_multiply(foreground),
blend_mode => apply_blend_mode(foreground, background, blend_mode),
};

background.alpha_blend(target_color.to_associated_alpha(opacity))
Expand Down Expand Up @@ -914,7 +922,7 @@ fn generate_curves<_Channel: Channel + super::Linear>(_primary: (), curve: Curve
bezier.find_tvalues_for_x(x)
.next()
.map(|t| bezier.evaluate(TValue::Parametric(t.clamp(0., 1.))).y)
// a very bad approximation if bezier_rs failes
// Fall back to a very bad approximation if Bezier-rs fails
.unwrap_or_else(|| (x - x0) / (x3 - x0) * (y3 - y0) + y0)
};
lut[index] = _Channel::from_f64(y);
Expand All @@ -926,6 +934,73 @@ fn generate_curves<_Channel: Channel + super::Linear>(_primary: (), curve: Curve
ValueMapperNode::new(lut)
}

#[derive(Debug, Clone)]
pub struct ColorFillNode<C> {
color: C,
}

#[node_macro::node_fn(ColorFillNode)]
pub fn color_fill_node(mut image_frame: ImageFrame<Color>, color: Color) -> ImageFrame<Color> {
for pixel in &mut image_frame.image.data {
pixel.set_red(color.r());
pixel.set_blue(color.b());
pixel.set_green(color.g());
pixel.alpha_multiply(color);
}

image_frame
}

pub struct ColorOverlayNode<Color, BlendMode, Opacity> {
color: Color,
blend_mode: BlendMode,
opacity: Opacity,
}

#[node_macro::node_fn(ColorOverlayNode)]
pub fn color_overlay_node(mut image: ImageFrame<Color>, color: Color, blend_mode: BlendMode, opacity: f32) -> ImageFrame<Color> {
let opacity = (opacity / 100.).clamp(0., 1.);
for pixel in &mut image.image.data {
let image = pixel.map_rgb(|channel| channel * (1. - opacity));

// The apply blend mode function divides rgb by the alpha channel for the background. This undoes that.
let associated_pixel = Color::from_rgbaf32_unchecked(pixel.r() * pixel.a(), pixel.g() * pixel.a(), pixel.b() * pixel.a(), pixel.a());
let overlay = apply_blend_mode(color, associated_pixel, blend_mode).map_rgb(|channel| channel * opacity);

*pixel = Color::from_rgbaf32(image.r() + overlay.r(), image.g() + overlay.g(), image.b() + overlay.b(), pixel.a()).unwrap();
}

image
}

#[test]
fn color_overlay_multiply() {
use crate::raster::Image;
use crate::value::ClonedNode;

let image_color = Color::from_rgbaf32_unchecked(0.7, 0.6, 0.5, 0.4);
let image = ImageFrame {
image: Image::new(1, 1, image_color),
..Default::default()
};

// Color { red: 0., green: 1., blue: 0., alpha: 1. }
let overlay_color = Color::GREEN;

// 100% of the output should come from the multiplied value
let opacity = 100_f32;

let result = ColorOverlayNode {
color: ClonedNode(overlay_color),
blend_mode: ClonedNode(BlendMode::Multiply),
opacity: ClonedNode(opacity),
}
.eval(image);

// The output should just be the original green and alpha channels (as we multiply them by 1 and other channels by 0)
assert_eq!(result.image.data[0], Color::from_rgbaf32_unchecked(0., image_color.g(), 0., image_color.a()));
}

#[cfg(feature = "alloc")]
pub use index_node::IndexNode;

Expand Down
4 changes: 3 additions & 1 deletion node-graph/gcore/src/raster/color.rs
Original file line number Diff line number Diff line change
Expand Up @@ -336,14 +336,16 @@ impl Color {
/// ```
#[inline(always)]
pub fn from_rgba8_srgb(red: u8, green: u8, blue: u8, alpha: u8) -> Color {
let alpha = alpha as f32 / 255.;
let map_range = |int_color| int_color as f32 / 255.0;
Color {
red: map_range(red),
green: map_range(green),
blue: map_range(blue),
alpha: map_range(alpha),
alpha,
}
.to_linear_srgb()
.map_rgb(|channel| channel * alpha)
}

/// Create a [Color] from a hue, saturation, lightness and alpha (all between 0 and 1)
Expand Down
2 changes: 2 additions & 0 deletions node-graph/interpreted-executor/src/node_registry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -428,6 +428,8 @@ fn node_registry() -> HashMap<NodeIdentifier, HashMap<NodeIOTypes, NodeConstruct
raster_node!(graphene_core::raster::LevelsNode<_, _, _, _, _>, params: [f32, f32, f32, f32, f32]),
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::adjustments::ColorFillNode<_>, input: ImageFrame<Color>, params: [Color]),
register_node!(graphene_core::raster::adjustments::ColorOverlayNode<_, _, _>, input: ImageFrame<Color>, params: [Color, BlendMode, f32]),
vec![(
NodeIdentifier::new("graphene_core::raster::BlendNode<_, _, _, _>"),
|args| {
Expand Down