mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-17 07:18:04 +08:00
Brush blend modes and erase/restore (#1261)
* Made blit node numerically stable. * Added blend mode parameter to brush strokes. * Fixed difference blend mode. * Added erase/restore blend modes. * Added blend mode and draw mode widgets. * Added comment explaining the ImageFrame.transform. * Initial blit/blend version. * Working version of erase/restore. * Improved inlining for blend functions. * Dsiable the blend mode selector in erase/draw mode. * Fixed incorrect bounds calculation. * Use factor instead of percentage for opacity * Rearrange options bar widgets * Tidy up blend modes * Code review --------- Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
@@ -309,12 +309,15 @@ fn blend_mode(document_node: &DocumentNode, node_id: u64, index: usize, name: &s
|
||||
exposed: false,
|
||||
} = &document_node.inputs[index]
|
||||
{
|
||||
let calculation_modes = BlendMode::list();
|
||||
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::BlendMode(method), node_id, index)));
|
||||
}
|
||||
let entries = vec![entries];
|
||||
let entries = BlendMode::list()
|
||||
.iter()
|
||||
.map(|category| {
|
||||
category
|
||||
.iter()
|
||||
.map(|mode| DropdownEntryData::new(mode.to_string()).on_update(update_value(move |_| TaggedValue::BlendMode(*mode), node_id, index)))
|
||||
.collect()
|
||||
})
|
||||
.collect();
|
||||
|
||||
widgets.extend_from_slice(&[WidgetHolder::unrelated_separator(), DropdownInput::new(entries).selected_index(Some(mode as u32)).widget_holder()]);
|
||||
}
|
||||
|
||||
@@ -15,13 +15,51 @@ use document_legacy::layers::layer_layer::CachedOutputData;
|
||||
use document_legacy::LayerId;
|
||||
use graph_craft::document::value::TaggedValue;
|
||||
use graph_craft::document::{NodeId, NodeInput, NodeNetwork};
|
||||
use graphene_core::raster::ImageFrame;
|
||||
use graphene_core::raster::{BlendMode, ImageFrame};
|
||||
use graphene_core::vector::brush_stroke::{BrushInputSample, BrushStroke, BrushStyle};
|
||||
use graphene_core::Color;
|
||||
|
||||
use glam::DAffine2;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
const EXPOSED_BLEND_MODES: &'static [&'static [BlendMode]] = {
|
||||
use BlendMode::*;
|
||||
&[
|
||||
// Basic group
|
||||
&[Normal],
|
||||
// Darken group
|
||||
&[Darken, Multiply, ColorBurn, LinearBurn, DarkerColor],
|
||||
// Lighten group
|
||||
&[Lighten, Screen, ColorDodge, LinearDodge, LighterColor],
|
||||
// Contrast group
|
||||
&[Overlay, SoftLight, HardLight, VividLight, LinearLight, PinLight, HardMix],
|
||||
// Inversion group
|
||||
&[Difference, Exclusion, Subtract, Divide],
|
||||
// Component group
|
||||
&[Hue, Saturation, Color, Luminosity],
|
||||
]
|
||||
};
|
||||
|
||||
fn blend_mode_dropdown_idx(target_blend_mode: BlendMode) -> Option<u32> {
|
||||
let mut i = 0;
|
||||
for group in EXPOSED_BLEND_MODES {
|
||||
for &blend_mode in group.iter() {
|
||||
if blend_mode == target_blend_mode {
|
||||
return Some(i);
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Copy, Clone, Debug, Serialize, Deserialize, specta::Type)]
|
||||
pub enum DrawMode {
|
||||
Draw = 0,
|
||||
Erase,
|
||||
Restore,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct BrushTool {
|
||||
fsm_state: BrushToolFsmState,
|
||||
@@ -35,6 +73,8 @@ pub struct BrushOptions {
|
||||
flow: f64,
|
||||
spacing: f64,
|
||||
color: ToolColorOptions,
|
||||
blend_mode: BlendMode,
|
||||
draw_mode: DrawMode,
|
||||
}
|
||||
|
||||
impl Default for BrushOptions {
|
||||
@@ -45,6 +85,8 @@ impl Default for BrushOptions {
|
||||
flow: 100.,
|
||||
spacing: 20.,
|
||||
color: ToolColorOptions::default(),
|
||||
blend_mode: BlendMode::Normal,
|
||||
draw_mode: DrawMode::Draw,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -69,10 +111,12 @@ pub enum BrushToolMessage {
|
||||
#[remain::sorted]
|
||||
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize, specta::Type)]
|
||||
pub enum BrushToolMessageOptionsUpdate {
|
||||
BlendMode(BlendMode),
|
||||
ChangeDiameter(f64),
|
||||
Color(Option<Color>),
|
||||
ColorType(ToolColorType),
|
||||
Diameter(f64),
|
||||
DrawMode(DrawMode),
|
||||
Flow(f64),
|
||||
Hardness(f64),
|
||||
Spacing(f64),
|
||||
@@ -135,6 +179,14 @@ impl PropertyHolder for BrushTool {
|
||||
|
||||
widgets.push(WidgetHolder::section_separator());
|
||||
|
||||
let draw_mode_entries: Vec<_> = [DrawMode::Draw, DrawMode::Erase, DrawMode::Restore]
|
||||
.into_iter()
|
||||
.map(|draw_mode| RadioEntryData::new(format!("{draw_mode:?}")).on_update(move |_| BrushToolMessage::UpdateOptions(BrushToolMessageOptionsUpdate::DrawMode(draw_mode)).into()))
|
||||
.collect();
|
||||
widgets.push(RadioInput::new(draw_mode_entries).selected_index(self.options.draw_mode as u32).widget_holder());
|
||||
|
||||
widgets.push(WidgetHolder::section_separator());
|
||||
|
||||
widgets.append(&mut self.options.color.create_widgets(
|
||||
"Color",
|
||||
false,
|
||||
@@ -143,6 +195,29 @@ impl PropertyHolder for BrushTool {
|
||||
WidgetCallback::new(|color: &ColorInput| BrushToolMessage::UpdateOptions(BrushToolMessageOptionsUpdate::Color(color.value)).into()),
|
||||
));
|
||||
|
||||
widgets.push(WidgetHolder::related_separator());
|
||||
|
||||
let blend_mode_entries: Vec<Vec<_>> = EXPOSED_BLEND_MODES
|
||||
.iter()
|
||||
.map(|group| {
|
||||
group
|
||||
.iter()
|
||||
.map(|blend_mode| {
|
||||
DropdownEntryData::new(format!("{blend_mode}"))
|
||||
.value(format!("{blend_mode:?}"))
|
||||
.on_update(|_| BrushToolMessage::UpdateOptions(BrushToolMessageOptionsUpdate::BlendMode(*blend_mode)).into())
|
||||
})
|
||||
.collect()
|
||||
})
|
||||
.collect();
|
||||
widgets.push(
|
||||
DropdownInput::new(blend_mode_entries)
|
||||
.selected_index(blend_mode_dropdown_idx(self.options.blend_mode))
|
||||
.tooltip("The blend mode used with the background when performing a brush stroke. Only used in draw mode.")
|
||||
.disabled(self.options.draw_mode != DrawMode::Draw)
|
||||
.widget_holder(),
|
||||
);
|
||||
|
||||
Layout::WidgetLayout(WidgetLayout::new(vec![LayoutGroup::Row { widgets }]))
|
||||
}
|
||||
}
|
||||
@@ -151,6 +226,7 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for BrushTo
|
||||
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) {
|
||||
if let ToolMessage::Brush(BrushToolMessage::UpdateOptions(action)) = message {
|
||||
match action {
|
||||
BrushToolMessageOptionsUpdate::BlendMode(blend_mode) => self.options.blend_mode = blend_mode,
|
||||
BrushToolMessageOptionsUpdate::ChangeDiameter(change) => {
|
||||
let needs_rounding = ((self.options.diameter + change.abs() / 2.) % change.abs() - change.abs() / 2.).abs() > 0.5;
|
||||
if needs_rounding && change > 0. {
|
||||
@@ -164,6 +240,7 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for BrushTo
|
||||
self.register_properties(responses, LayoutTarget::ToolOptions);
|
||||
}
|
||||
BrushToolMessageOptionsUpdate::Diameter(diameter) => self.options.diameter = diameter,
|
||||
BrushToolMessageOptionsUpdate::DrawMode(draw_mode) => self.options.draw_mode = draw_mode,
|
||||
BrushToolMessageOptionsUpdate::Hardness(hardness) => self.options.hardness = hardness,
|
||||
BrushToolMessageOptionsUpdate::Flow(flow) => self.options.flow = flow,
|
||||
BrushToolMessageOptionsUpdate::Spacing(spacing) => self.options.spacing = spacing,
|
||||
@@ -297,6 +374,11 @@ impl Fsm for BrushToolFsmState {
|
||||
.max((tool_data.transform.matrix2 * glam::DVec2::Y).length());
|
||||
|
||||
// Start a new stroke with a single sample
|
||||
let blend_mode = match tool_options.draw_mode {
|
||||
DrawMode::Draw => tool_options.blend_mode,
|
||||
DrawMode::Erase => BlendMode::Erase,
|
||||
DrawMode::Restore => BlendMode::Restore,
|
||||
};
|
||||
tool_data.strokes.push(BrushStroke {
|
||||
trace: vec![BrushInputSample { position: layer_position }],
|
||||
style: BrushStyle {
|
||||
@@ -305,6 +387,7 @@ impl Fsm for BrushToolFsmState {
|
||||
hardness: tool_options.hardness,
|
||||
flow: tool_options.flow,
|
||||
spacing: tool_options.spacing,
|
||||
blend_mode,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user