Implement clipping masks, stroke align, and stroke paint order (#2644)

* refactor: opacity + blend_mode -> blend_style

* Add code for clipping

* Add alt-click masking

* Clip to all colors. Fill option

* Fix undo not working. Fix strokes not being white

* Allow clipped to be grouped or raster

* Switch to alpha mode in mask-type

* add plumbing to know if clipped in frontend and add fill slider

* Attempt at document upgrade code

* Fix fill slider

* Add clipped styling and Alt-click layer border

* Use mask attr judiciously by using clip when possible

* Fix breaking documents and upgrade code

* Fix fixes

* No-op toggle if last child of parent and don't show clip UI if last element

* Fix mouse styles by plumbing clippable to frontend

* Fix Clip detection by disallowed groups as clipPath according to SVG spec doesn't allow <g>

* Add opacity to clippers can_use_clip check

* Fix issue with clipping not working nicely with strokes by using masks

* Add vello code

* cleanup

* Add stroke alignment hacks to SVG renderer

* svg: Fix mask bounds in vector data

* vello: Implement mask hacks to support stroke alignment

* Move around alignment and doc upgrade code

* rename Line X -> X

* An attempt at fixing names not updating

* svg: add stroke order with svg

* vello: add stroke order with by calling one before the other explicitly

* fix merge

* fix svg renderer messing up transform det

* Code review; reorder and rename parameters (TODO: fix tools)

* Fixes to previous

* Formatting

* fix bug 3

* some moving around (not fixed)

* fix issue 1

* fix vello

* Final code review

---------

Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
mTvare
2025-06-19 19:11:01 -07:00
committed by Keavon Chambers
co-authored by Keavon Chambers
parent 8a3f133140
commit e238753a35
28 changed files with 1025 additions and 311 deletions
@@ -13,6 +13,7 @@ use graphene_core::raster::BlendMode;
use graphene_core::raster_types::{CPU, GPU, RasterDataTable};
use graphene_core::text::{Font, TypesettingConfig};
use graphene_core::vector::style::Gradient;
use graphene_std::NodeInputDecleration;
use graphene_std::vector::{ManipulatorPointId, PointId, SegmentId, VectorModificationType};
use std::collections::VecDeque;
@@ -258,7 +259,7 @@ pub fn get_viewport_pivot(layer: LayerNodeIdentifier, network_interface: &NodeNe
network_interface.document_metadata().transform_to_viewport(layer).transform_point2(min + (max - min) * pivot)
}
/// Get the current gradient of a layer from the closest Fill node
/// Get the current gradient of a layer from the closest "Fill" node.
pub fn get_gradient(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option<Gradient> {
let fill_index = 1;
@@ -269,7 +270,7 @@ pub fn get_gradient(layer: LayerNodeIdentifier, network_interface: &NodeNetworkI
Some(gradient.clone())
}
/// Get the current fill of a layer from the closest Fill node
/// Get the current fill of a layer from the closest "Fill" node.
pub fn get_fill_color(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option<Color> {
let fill_index = 1;
@@ -280,16 +281,16 @@ pub fn get_fill_color(layer: LayerNodeIdentifier, network_interface: &NodeNetwor
Some(color.to_linear_srgb())
}
/// Get the current blend mode of a layer from the closest Blend Mode node
/// Get the current blend mode of a layer from the closest "Blending" node.
pub fn get_blend_mode(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option<BlendMode> {
let inputs = NodeGraphLayer::new(layer, network_interface).find_node_inputs("Blend Mode")?;
let inputs = NodeGraphLayer::new(layer, network_interface).find_node_inputs("Blending")?;
let TaggedValue::BlendMode(blend_mode) = inputs.get(1)?.as_value()? else {
return None;
};
Some(*blend_mode)
}
/// Get the current opacity of a layer from the closest Opacity node.
/// Get the current opacity of a layer from the closest "Blending" node.
/// This may differ from the actual opacity contained within the data type reaching this layer, because that actual opacity may be:
/// - Multiplied with additional opacity nodes earlier in the chain
/// - Set by an Opacity node with an exposed input value driven by another node
@@ -298,13 +299,29 @@ pub fn get_blend_mode(layer: LayerNodeIdentifier, network_interface: &NodeNetwor
///
/// With those limitations in mind, the intention of this function is to show just the value already present in an upstream Opacity node so that value can be directly edited.
pub fn get_opacity(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option<f64> {
let inputs = NodeGraphLayer::new(layer, network_interface).find_node_inputs("Opacity")?;
let TaggedValue::F64(opacity) = inputs.get(1)?.as_value()? else {
let inputs = NodeGraphLayer::new(layer, network_interface).find_node_inputs("Blending")?;
let TaggedValue::F64(opacity) = inputs.get(2)?.as_value()? else {
return None;
};
Some(*opacity)
}
pub fn get_clip_mode(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option<bool> {
let inputs = NodeGraphLayer::new(layer, network_interface).find_node_inputs("Blending")?;
let TaggedValue::Bool(clip) = inputs.get(4)?.as_value()? else {
return None;
};
Some(*clip)
}
pub fn get_fill(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option<f64> {
let inputs = NodeGraphLayer::new(layer, network_interface).find_node_inputs("Blending")?;
let TaggedValue::F64(fill) = inputs.get(3)?.as_value()? else {
return None;
};
Some(*fill)
}
pub fn get_fill_id(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option<NodeId> {
NodeGraphLayer::new(layer, network_interface).upstream_node_id_from_name("Fill")
}
@@ -356,7 +373,7 @@ pub fn get_text(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInter
}
pub fn get_stroke_width(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option<f64> {
let weight_node_input_index = 2;
let weight_node_input_index = graphene_std::vector::stroke::WeightInput::INDEX;
if let TaggedValue::F64(width) = NodeGraphLayer::new(layer, network_interface).find_input("Stroke", weight_node_input_index)? {
Some(*width)
} else {