mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-19 10:58:04 +08:00
Implement the Brush tool (#1099)
* Implement Brush Node * Add color Input * Add VectorPointsNode * Add Erase Node * Adapt compilation infrastructure to allow non Image Frame inputs * Remove debug output from TransformNode * Fix transform calculation * Fix Blending by making the brush texture use associated alpha * Code improvements and UX polish * Rename Opacity to Flow * Add erase option to brush node + fix freehand tool * Fix crash * Revert erase implementation * Fix flattening id calculation * Fix some transformation issues * Fix changing the pivot location * Fix vector data modify bounds * Minor fn name cleanup * Fix some tests * Fix tests --------- Co-authored-by: Keavon Chambers <keavon@keavon.com> Co-authored-by: hypercube <0hypercube@gmail.com>
This commit is contained in:
committed by
Keavon Chambers
parent
758f757775
commit
589ff9a2d3
@@ -300,7 +300,7 @@ pub struct InvertRGBNode;
|
||||
|
||||
#[node_macro::node_fn(InvertRGBNode)]
|
||||
fn invert_image(color: Color) -> Color {
|
||||
color.map_rgb(|c| 1. - c)
|
||||
color.map_rgb(|c| color.a() - c)
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
@@ -339,43 +339,55 @@ pub struct BlendNode<BlendMode, Opacity> {
|
||||
opacity: Opacity,
|
||||
}
|
||||
|
||||
impl<Opacity: dyn_any::StaticTypeSized, Blend: dyn_any::StaticTypeSized> StaticType for BlendNode<Blend, Opacity> {
|
||||
type Static = BlendNode<Blend::Static, Opacity::Static>;
|
||||
}
|
||||
|
||||
#[node_macro::node_fn(BlendNode)]
|
||||
fn blend_node(input: (Color, Color), blend_mode: BlendMode, opacity: f64) -> Color {
|
||||
let (source_color, backdrop) = input;
|
||||
let actual_opacity = 1. - (opacity / 100.) as f32;
|
||||
return match blend_mode {
|
||||
BlendMode::Normal => backdrop.blend_rgb(source_color, Color::blend_normal),
|
||||
BlendMode::Multiply => backdrop.blend_rgb(source_color, Color::blend_multiply),
|
||||
BlendMode::Darken => backdrop.blend_rgb(source_color, Color::blend_darken),
|
||||
BlendMode::ColorBurn => backdrop.blend_rgb(source_color, Color::blend_color_burn),
|
||||
BlendMode::LinearBurn => backdrop.blend_rgb(source_color, Color::blend_linear_burn),
|
||||
BlendMode::DarkerColor => backdrop.blend_darker_color(source_color),
|
||||
let opacity = opacity / 100.;
|
||||
|
||||
BlendMode::Screen => backdrop.blend_rgb(source_color, Color::blend_screen),
|
||||
BlendMode::Lighten => backdrop.blend_rgb(source_color, Color::blend_lighten),
|
||||
BlendMode::ColorDodge => backdrop.blend_rgb(source_color, Color::blend_color_dodge),
|
||||
BlendMode::LinearDodge => backdrop.blend_rgb(source_color, Color::blend_linear_dodge),
|
||||
BlendMode::LighterColor => backdrop.blend_lighter_color(source_color),
|
||||
let (foreground, background) = input;
|
||||
let foreground = foreground.to_linear_srgb();
|
||||
let background = background.to_linear_srgb();
|
||||
|
||||
BlendMode::Overlay => source_color.blend_rgb(backdrop, Color::blend_hardlight),
|
||||
BlendMode::SoftLight => backdrop.blend_rgb(source_color, Color::blend_softlight),
|
||||
BlendMode::HardLight => backdrop.blend_rgb(source_color, Color::blend_hardlight),
|
||||
BlendMode::VividLight => backdrop.blend_rgb(source_color, Color::blend_vivid_light),
|
||||
BlendMode::LinearLight => backdrop.blend_rgb(source_color, Color::blend_linear_light),
|
||||
BlendMode::PinLight => backdrop.blend_rgb(source_color, Color::blend_pin_light),
|
||||
BlendMode::HardMix => backdrop.blend_rgb(source_color, Color::blend_hard_mix),
|
||||
let target_color = match blend_mode {
|
||||
BlendMode::Normal => background.blend_rgb(foreground, Color::blend_normal),
|
||||
BlendMode::Multiply => background.blend_rgb(foreground, Color::blend_multiply),
|
||||
BlendMode::Darken => background.blend_rgb(foreground, Color::blend_darken),
|
||||
BlendMode::ColorBurn => background.blend_rgb(foreground, Color::blend_color_burn),
|
||||
BlendMode::LinearBurn => background.blend_rgb(foreground, Color::blend_linear_burn),
|
||||
BlendMode::DarkerColor => background.blend_darker_color(foreground),
|
||||
|
||||
BlendMode::Difference => backdrop.blend_rgb(source_color, Color::blend_exclusion),
|
||||
BlendMode::Exclusion => backdrop.blend_rgb(source_color, Color::blend_exclusion),
|
||||
BlendMode::Subtract => backdrop.blend_rgb(source_color, Color::blend_subtract),
|
||||
BlendMode::Divide => backdrop.blend_rgb(source_color, Color::blend_divide),
|
||||
BlendMode::Screen => background.blend_rgb(foreground, Color::blend_screen),
|
||||
BlendMode::Lighten => background.blend_rgb(foreground, Color::blend_lighten),
|
||||
BlendMode::ColorDodge => background.blend_rgb(foreground, Color::blend_color_dodge),
|
||||
BlendMode::LinearDodge => background.blend_rgb(foreground, Color::blend_linear_dodge),
|
||||
BlendMode::LighterColor => background.blend_lighter_color(foreground),
|
||||
|
||||
BlendMode::Hue => backdrop.blend_hue(source_color),
|
||||
BlendMode::Saturation => backdrop.blend_saturation(source_color),
|
||||
BlendMode::Color => backdrop.blend_color(source_color),
|
||||
BlendMode::Luminosity => backdrop.blend_luminosity(source_color),
|
||||
}
|
||||
.lerp(backdrop, actual_opacity);
|
||||
BlendMode::Overlay => foreground.blend_rgb(background, Color::blend_hardlight),
|
||||
BlendMode::SoftLight => background.blend_rgb(foreground, Color::blend_softlight),
|
||||
BlendMode::HardLight => background.blend_rgb(foreground, Color::blend_hardlight),
|
||||
BlendMode::VividLight => background.blend_rgb(foreground, Color::blend_vivid_light),
|
||||
BlendMode::LinearLight => background.blend_rgb(foreground, Color::blend_linear_light),
|
||||
BlendMode::PinLight => background.blend_rgb(foreground, Color::blend_pin_light),
|
||||
BlendMode::HardMix => background.blend_rgb(foreground, Color::blend_hard_mix),
|
||||
|
||||
BlendMode::Difference => background.blend_rgb(foreground, Color::blend_exclusion),
|
||||
BlendMode::Exclusion => background.blend_rgb(foreground, Color::blend_exclusion),
|
||||
BlendMode::Subtract => background.blend_rgb(foreground, Color::blend_subtract),
|
||||
BlendMode::Divide => background.blend_rgb(foreground, Color::blend_divide),
|
||||
|
||||
BlendMode::Hue => background.blend_hue(foreground),
|
||||
BlendMode::Saturation => background.blend_saturation(foreground),
|
||||
BlendMode::Color => background.blend_color(foreground),
|
||||
BlendMode::Luminosity => background.blend_luminosity(foreground),
|
||||
};
|
||||
|
||||
let multiplied_target_color = target_color.to_associated_alpha(opacity as f32);
|
||||
let blended = background.alpha_blend(multiplied_target_color);
|
||||
|
||||
blended.to_gamma_srgb()
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
|
||||
@@ -83,6 +83,11 @@ impl Color {
|
||||
Color { red, green, blue, alpha }
|
||||
}
|
||||
|
||||
/// Return an opaque `Color` from given `f32` RGB channels.
|
||||
pub fn from_unassociated_alpha(red: f32, green: f32, blue: f32, alpha: f32) -> Color {
|
||||
Color::from_rgbaf32_unchecked(red * alpha, green * alpha, blue * alpha, alpha)
|
||||
}
|
||||
|
||||
/// Return an opaque SDR `Color` given RGB channels from `0` to `255`.
|
||||
///
|
||||
/// # Examples
|
||||
@@ -602,14 +607,49 @@ impl Color {
|
||||
pub fn map_rgb<F: Fn(f32) -> f32>(&self, f: F) -> Self {
|
||||
Self::from_rgbaf32_unchecked(f(self.r()), f(self.g()), f(self.b()), self.a())
|
||||
}
|
||||
pub fn blend_rgb<F: Fn(f32, f32) -> f32>(&self, other: Color, f: F) -> Self {
|
||||
Color {
|
||||
red: f(self.red, other.red).clamp(0., 1.),
|
||||
green: f(self.green, other.green).clamp(0., 1.),
|
||||
blue: f(self.blue, other.blue).clamp(0., 1.),
|
||||
|
||||
pub fn apply_opacity(&self, opacity: f32) -> Self {
|
||||
Self::from_rgbaf32_unchecked(self.r(), self.g(), self.b(), self.a() * opacity)
|
||||
}
|
||||
|
||||
pub fn to_associated_alpha(&self, alpha: f32) -> Self {
|
||||
Self {
|
||||
red: self.red * alpha,
|
||||
green: self.green * alpha,
|
||||
blue: self.blue * alpha,
|
||||
alpha: self.alpha * alpha,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_unassociated_alpha(&self) -> Self {
|
||||
let unmultiply = 1. / self.alpha;
|
||||
Self {
|
||||
red: self.red * unmultiply,
|
||||
green: self.green * unmultiply,
|
||||
blue: self.blue * unmultiply,
|
||||
alpha: self.alpha,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn blend_rgb<F: Fn(f32, f32) -> f32>(&self, other: Color, f: F) -> Self {
|
||||
let background = self.to_unassociated_alpha();
|
||||
Color {
|
||||
red: f(background.red, other.red).clamp(0., 1.),
|
||||
green: f(background.green, other.green).clamp(0., 1.),
|
||||
blue: f(background.blue, other.blue).clamp(0., 1.),
|
||||
alpha: other.alpha,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn alpha_blend(&self, other: Color) -> Self {
|
||||
let inv_alpha = 1. - other.alpha;
|
||||
Self {
|
||||
red: self.red * inv_alpha + other.red,
|
||||
green: self.green * inv_alpha + other.green,
|
||||
blue: self.blue * inv_alpha + other.blue,
|
||||
alpha: self.alpha * inv_alpha + other.alpha,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user