Skip to content

Commit a1a6736

Browse files
committed
raster-nodes: create mod blending_nodes and move assoc nodes
1 parent edd0d9f commit a1a6736

6 files changed

Lines changed: 266 additions & 248 deletions

File tree

editor/src/messages/portfolio/document_migration.rs

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,27 @@ const NODE_REPLACEMENTS: &[NodeReplacement<'static>] = &[
251251
node: graphene_std::vector::auto_tangents::IDENTIFIER,
252252
aliases: &["graphene_core::vector::GenerateHandlesNode", "graphene_core::vector::RemoveHandlesNode"],
253253
},
254+
// raster::blending_nodes
255+
NodeReplacement {
256+
node: graphene_std::raster_nodes::blending_nodes::blend::IDENTIFIER,
257+
aliases: &[
258+
"graphene_raster_nodes::adjustments::BlendNode",
259+
"graphene_core::raster::adjustments::BlendNode",
260+
"graphene_core::raster::BlendNode",
261+
],
262+
},
263+
NodeReplacement {
264+
node: graphene_std::raster_nodes::blending_nodes::blend_color_pair::IDENTIFIER,
265+
aliases: &["graphene_raster_nodes::adjustments::BlendColorPairNode", "graphene_core::raster::BlendColorPairNode"],
266+
},
267+
NodeReplacement {
268+
node: graphene_std::raster_nodes::blending_nodes::color_overlay::IDENTIFIER,
269+
aliases: &[
270+
"graphene_raster_nodes::adjustments::ColorOverlayNode",
271+
"graphene_core::raster::adjustments::ColorOverlayNode",
272+
"graphene_raster_nodes::generate_curves::ColorOverlayNode",
273+
],
274+
},
254275
// raster::adjustments
255276
NodeReplacement {
256277
node: graphene_std::raster_nodes::adjustments::luminance::IDENTIFIER,
@@ -292,14 +313,6 @@ const NODE_REPLACEMENTS: &[NodeReplacement<'static>] = &[
292313
node: graphene_std::raster_nodes::adjustments::threshold::IDENTIFIER,
293314
aliases: &["graphene_core::raster::adjustments::ThresholdNode", "graphene_core::raster::ThresholdNode"],
294315
},
295-
NodeReplacement {
296-
node: graphene_std::raster_nodes::adjustments::blend::IDENTIFIER,
297-
aliases: &["graphene_core::raster::adjustments::BlendNode", "graphene_core::raster::BlendNode"],
298-
},
299-
NodeReplacement {
300-
node: graphene_std::raster_nodes::adjustments::blend_color_pair::IDENTIFIER,
301-
aliases: &["graphene_core::raster::BlendColorPairNode"],
302-
},
303316
// this node doesn't seem to exist?
304317
// (graphene_std::raster_nodes::adjustments::blend_color::IDENTIFIER, &["graphene_core::raster::adjustments::BlendColorsNode","graphene_core::raster::BlendColorsNode"]),
305318
NodeReplacement {
@@ -326,10 +339,6 @@ const NODE_REPLACEMENTS: &[NodeReplacement<'static>] = &[
326339
node: graphene_std::raster_nodes::adjustments::exposure::IDENTIFIER,
327340
aliases: &["graphene_core::raster::adjustments::ExposureNode", "graphene_core::raster::ExposureNode"],
328341
},
329-
NodeReplacement {
330-
node: graphene_std::raster_nodes::adjustments::color_overlay::IDENTIFIER,
331-
aliases: &["graphene_core::raster::adjustments::ColorOverlayNode", "graphene_raster_nodes::generate_curves::ColorOverlayNode"],
332-
},
333342
// raster
334343
NodeReplacement {
335344
node: graphene_std::raster_nodes::generate_curves::generate_curves::IDENTIFIER,

node-graph/gbrush/src/brush.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ use graphene_core::registry::FutureWrapperNode;
1414
use graphene_core::transform::Transform;
1515
use graphene_core::value::ClonedNode;
1616
use graphene_core::{Ctx, Node};
17-
use graphene_raster_nodes::adjustments::blend_colors;
17+
use graphene_raster_nodes::blending_nodes::blend_colors;
1818
use graphene_raster_nodes::std_nodes::{empty_image, extend_image_to_bounds};
1919

2020
#[derive(Clone, Copy, Debug, PartialEq)]
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
use graphene_core::Color;
2+
use graphene_core::gradient::GradientStops;
3+
use graphene_core::raster_types::{CPU, RasterDataTable};
4+
5+
pub trait Adjust<P> {
6+
fn adjust(&mut self, map_fn: impl Fn(&P) -> P);
7+
}
8+
impl Adjust<Color> for Color {
9+
fn adjust(&mut self, map_fn: impl Fn(&Color) -> Color) {
10+
*self = map_fn(self);
11+
}
12+
}
13+
impl Adjust<Color> for Option<Color> {
14+
fn adjust(&mut self, map_fn: impl Fn(&Color) -> Color) {
15+
if let Some(v) = self {
16+
*v = map_fn(v)
17+
}
18+
}
19+
}
20+
impl Adjust<Color> for GradientStops {
21+
fn adjust(&mut self, map_fn: impl Fn(&Color) -> Color) {
22+
for (_pos, c) in self.iter_mut() {
23+
*c = map_fn(c);
24+
}
25+
}
26+
}
27+
impl Adjust<Color> for RasterDataTable<CPU> {
28+
fn adjust(&mut self, map_fn: impl Fn(&Color) -> Color) {
29+
for instance in self.instance_mut_iter() {
30+
for c in instance.instance.data_mut().data.iter_mut() {
31+
*c = map_fn(c);
32+
}
33+
}
34+
}
35+
}

node-graph/graster-nodes/src/adjustments.rs

Lines changed: 6 additions & 235 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,13 @@
11
#![allow(clippy::too_many_arguments)]
22

3+
use crate::adjust::Adjust;
34
use crate::cubic_spline::CubicSplines;
45
use dyn_any::DynAny;
5-
use graphene_core::Node;
6-
use graphene_core::blending::BlendMode;
76
use graphene_core::color::Color;
8-
use graphene_core::color::Pixel;
97
use graphene_core::context::Ctx;
108
use graphene_core::gradient::GradientStops;
11-
use graphene_core::raster::image::Image;
12-
use graphene_core::raster_types::{CPU, Raster, RasterDataTable};
9+
use graphene_core::raster_types::{CPU, RasterDataTable};
1310
use graphene_core::registry::types::{Angle, Percentage, SignedPercentage};
14-
use std::cmp::Ordering;
1511
use std::fmt::Debug;
1612

1713
// TODO: Implement the following:
@@ -137,10 +133,10 @@ fn make_opaque<T: Adjust<Color>>(
137133
fn brightness_contrast<T: Adjust<Color>>(
138134
_: impl Ctx,
139135
#[implementations(
140-
Color,
141-
RasterDataTable<CPU>,
142-
GradientStops,
143-
)]
136+
Color,
137+
RasterDataTable<CPU>,
138+
GradientStops,
139+
)]
144140
mut input: T,
145141
brightness: SignedPercentage,
146142
contrast: SignedPercentage,
@@ -447,178 +443,6 @@ async fn threshold<T: Adjust<Color>>(
447443
image
448444
}
449445

450-
trait Blend<P: Pixel> {
451-
fn blend(&self, under: &Self, blend_fn: impl Fn(P, P) -> P) -> Self;
452-
}
453-
impl Blend<Color> for Color {
454-
fn blend(&self, under: &Self, blend_fn: impl Fn(Color, Color) -> Color) -> Self {
455-
blend_fn(*self, *under)
456-
}
457-
}
458-
impl Blend<Color> for Option<Color> {
459-
fn blend(&self, under: &Self, blend_fn: impl Fn(Color, Color) -> Color) -> Self {
460-
match (self, under) {
461-
(Some(a), Some(b)) => Some(blend_fn(*a, *b)),
462-
(a, None) => *a,
463-
(None, b) => *b,
464-
}
465-
}
466-
}
467-
impl Blend<Color> for RasterDataTable<CPU> {
468-
fn blend(&self, under: &Self, blend_fn: impl Fn(Color, Color) -> Color) -> Self {
469-
let mut result_table = self.clone();
470-
471-
for (over, under) in result_table.instance_mut_iter().zip(under.instance_ref_iter()) {
472-
let data = over.instance.data.iter().zip(under.instance.data.iter()).map(|(a, b)| blend_fn(*a, *b)).collect();
473-
474-
*over.instance = Raster::new_cpu(Image {
475-
data,
476-
width: over.instance.width,
477-
height: over.instance.height,
478-
base64_string: None,
479-
});
480-
}
481-
482-
result_table
483-
}
484-
}
485-
impl Blend<Color> for GradientStops {
486-
fn blend(&self, under: &Self, blend_fn: impl Fn(Color, Color) -> Color) -> Self {
487-
let mut combined_stops = self.iter().map(|(position, _)| position).chain(under.iter().map(|(position, _)| position)).collect::<Vec<_>>();
488-
combined_stops.dedup_by(|&mut a, &mut b| (a - b).abs() < 1e-6);
489-
combined_stops.sort_by(|a, b| a.partial_cmp(b).unwrap_or(Ordering::Equal));
490-
491-
let stops = combined_stops
492-
.into_iter()
493-
.map(|&position| {
494-
let over_color = self.evaluate(position);
495-
let under_color = under.evaluate(position);
496-
let color = blend_fn(over_color, under_color);
497-
(position, color)
498-
})
499-
.collect::<Vec<_>>();
500-
501-
GradientStops::new(stops)
502-
}
503-
}
504-
505-
#[node_macro::node(category("Raster"))]
506-
async fn blend<T: Blend<Color> + Send>(
507-
_: impl Ctx,
508-
#[implementations(
509-
Color,
510-
RasterDataTable<CPU>,
511-
GradientStops,
512-
)]
513-
over: T,
514-
#[expose]
515-
#[implementations(
516-
Color,
517-
RasterDataTable<CPU>,
518-
GradientStops,
519-
)]
520-
under: T,
521-
blend_mode: BlendMode,
522-
#[default(100.)] opacity: Percentage,
523-
) -> T {
524-
over.blend(&under, |a, b| blend_colors(a, b, blend_mode, opacity / 100.))
525-
}
526-
527-
#[node_macro::node(category(""), skip_impl)]
528-
fn blend_color_pair<BlendModeNode, OpacityNode>(input: (Color, Color), blend_mode: &'n BlendModeNode, opacity: &'n OpacityNode) -> Color
529-
where
530-
BlendModeNode: Node<'n, (), Output = BlendMode> + 'n,
531-
OpacityNode: Node<'n, (), Output = Percentage> + 'n,
532-
{
533-
let blend_mode = blend_mode.eval(());
534-
let opacity = opacity.eval(());
535-
blend_colors(input.0, input.1, blend_mode, opacity / 100.)
536-
}
537-
538-
pub fn apply_blend_mode(foreground: Color, background: Color, blend_mode: BlendMode) -> Color {
539-
match blend_mode {
540-
// Normal group
541-
BlendMode::Normal => background.blend_rgb(foreground, Color::blend_normal),
542-
// Darken group
543-
BlendMode::Darken => background.blend_rgb(foreground, Color::blend_darken),
544-
BlendMode::Multiply => background.blend_rgb(foreground, Color::blend_multiply),
545-
BlendMode::ColorBurn => background.blend_rgb(foreground, Color::blend_color_burn),
546-
BlendMode::LinearBurn => background.blend_rgb(foreground, Color::blend_linear_burn),
547-
BlendMode::DarkerColor => background.blend_darker_color(foreground),
548-
// Lighten group
549-
BlendMode::Lighten => background.blend_rgb(foreground, Color::blend_lighten),
550-
BlendMode::Screen => background.blend_rgb(foreground, Color::blend_screen),
551-
BlendMode::ColorDodge => background.blend_rgb(foreground, Color::blend_color_dodge),
552-
BlendMode::LinearDodge => background.blend_rgb(foreground, Color::blend_linear_dodge),
553-
BlendMode::LighterColor => background.blend_lighter_color(foreground),
554-
// Contrast group
555-
BlendMode::Overlay => foreground.blend_rgb(background, Color::blend_hardlight),
556-
BlendMode::SoftLight => background.blend_rgb(foreground, Color::blend_softlight),
557-
BlendMode::HardLight => background.blend_rgb(foreground, Color::blend_hardlight),
558-
BlendMode::VividLight => background.blend_rgb(foreground, Color::blend_vivid_light),
559-
BlendMode::LinearLight => background.blend_rgb(foreground, Color::blend_linear_light),
560-
BlendMode::PinLight => background.blend_rgb(foreground, Color::blend_pin_light),
561-
BlendMode::HardMix => background.blend_rgb(foreground, Color::blend_hard_mix),
562-
// Inversion group
563-
BlendMode::Difference => background.blend_rgb(foreground, Color::blend_difference),
564-
BlendMode::Exclusion => background.blend_rgb(foreground, Color::blend_exclusion),
565-
BlendMode::Subtract => background.blend_rgb(foreground, Color::blend_subtract),
566-
BlendMode::Divide => background.blend_rgb(foreground, Color::blend_divide),
567-
// Component group
568-
BlendMode::Hue => background.blend_hue(foreground),
569-
BlendMode::Saturation => background.blend_saturation(foreground),
570-
BlendMode::Color => background.blend_color(foreground),
571-
BlendMode::Luminosity => background.blend_luminosity(foreground),
572-
// Other utility blend modes (hidden from the normal list) - do not have alpha blend
573-
_ => panic!("Used blend mode without alpha blend"),
574-
}
575-
}
576-
577-
trait Adjust<P> {
578-
fn adjust(&mut self, map_fn: impl Fn(&P) -> P);
579-
}
580-
impl Adjust<Color> for Color {
581-
fn adjust(&mut self, map_fn: impl Fn(&Color) -> Color) {
582-
*self = map_fn(self);
583-
}
584-
}
585-
impl Adjust<Color> for Option<Color> {
586-
fn adjust(&mut self, map_fn: impl Fn(&Color) -> Color) {
587-
if let Some(v) = self {
588-
*v = map_fn(v)
589-
}
590-
}
591-
}
592-
impl Adjust<Color> for GradientStops {
593-
fn adjust(&mut self, map_fn: impl Fn(&Color) -> Color) {
594-
for (_pos, c) in self.iter_mut() {
595-
*c = map_fn(c);
596-
}
597-
}
598-
}
599-
impl Adjust<Color> for RasterDataTable<CPU> {
600-
fn adjust(&mut self, map_fn: impl Fn(&Color) -> Color) {
601-
for instance in self.instance_mut_iter() {
602-
for c in instance.instance.data_mut().data.iter_mut() {
603-
*c = map_fn(c);
604-
}
605-
}
606-
}
607-
}
608-
609-
#[inline(always)]
610-
pub fn blend_colors(foreground: Color, background: Color, blend_mode: BlendMode, opacity: f64) -> Color {
611-
let target_color = match blend_mode {
612-
// Other utility blend modes (hidden from the normal list) - do not have alpha blend
613-
BlendMode::Erase => return background.alpha_subtract(foreground),
614-
BlendMode::Restore => return background.alpha_add(foreground),
615-
BlendMode::MultiplyAlpha => return background.alpha_multiply(foreground),
616-
blend_mode => apply_blend_mode(foreground, background, blend_mode),
617-
};
618-
619-
background.alpha_blend(target_color.to_associated_alpha(opacity as f32))
620-
}
621-
622446
// Aims for interoperable compatibility with:
623447
// https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=%27grdm%27%20%3D%20Gradient%20Map
624448
// https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=Gradient%20settings%20(Photoshop%206.0)
@@ -1146,56 +970,3 @@ async fn exposure<T: Adjust<Color>>(
1146970
});
1147971
input
1148972
}
1149-
1150-
#[node_macro::node(category("Raster: Adjustment"))]
1151-
fn color_overlay<T: Adjust<Color>>(
1152-
_: impl Ctx,
1153-
#[implementations(
1154-
Color,
1155-
RasterDataTable<CPU>,
1156-
GradientStops,
1157-
)]
1158-
mut image: T,
1159-
#[default(Color::BLACK)] color: Color,
1160-
blend_mode: BlendMode,
1161-
#[default(100.)] opacity: Percentage,
1162-
) -> T {
1163-
let opacity = (opacity as f32 / 100.).clamp(0., 1.);
1164-
1165-
image.adjust(|pixel| {
1166-
let image = pixel.map_rgb(|channel| channel * (1. - opacity));
1167-
1168-
// The apply blend mode function divides rgb by the alpha channel for the background. This undoes that.
1169-
let associated_pixel = Color::from_rgbaf32_unchecked(pixel.r() * pixel.a(), pixel.g() * pixel.a(), pixel.b() * pixel.a(), pixel.a());
1170-
let overlay = apply_blend_mode(color, associated_pixel, blend_mode).map_rgb(|channel| channel * opacity);
1171-
1172-
Color::from_rgbaf32_unchecked(image.r() + overlay.r(), image.g() + overlay.g(), image.b() + overlay.b(), pixel.a())
1173-
});
1174-
image
1175-
}
1176-
1177-
#[cfg(test)]
1178-
mod test {
1179-
use graphene_core::blending::BlendMode;
1180-
use graphene_core::color::Color;
1181-
use graphene_core::raster::image::Image;
1182-
use graphene_core::raster_types::{Raster, RasterDataTable};
1183-
1184-
#[tokio::test]
1185-
async fn color_overlay_multiply() {
1186-
let image_color = Color::from_rgbaf32_unchecked(0.7, 0.6, 0.5, 0.4);
1187-
let image = Image::new(1, 1, image_color);
1188-
1189-
// Color { red: 0., green: 1., blue: 0., alpha: 1. }
1190-
let overlay_color = Color::GREEN;
1191-
1192-
// 100% of the output should come from the multiplied value
1193-
let opacity = 100_f64;
1194-
1195-
let result = super::color_overlay((), RasterDataTable::new(Raster::new_cpu(image.clone())), overlay_color, BlendMode::Multiply, opacity);
1196-
let result = result.instance_ref_iter().next().unwrap().instance;
1197-
1198-
// The output should just be the original green and alpha channels (as we multiply them by 1 and other channels by 0)
1199-
assert_eq!(result.data[0], Color::from_rgbaf32_unchecked(0., image_color.g(), 0., image_color.a()));
1200-
}
1201-
}

0 commit comments

Comments
 (0)