mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-15 14:18:04 +08:00
Add renderer support for painting vector with "fill" and "stroke" attributes with graphic List<T> types (#4111)
* Add conversion from Fill to Table<Graphic>
* Refactor Vector vello renderer for Gradient / Color
* Refactor Vector SVG renderer for Gradient / Color
* Fix conflicts
* Add basic clipping-based fill for SVG rendering
* Use Cow to avoid cloning graphic list for fill
* Cleanup for Cow usage
* format code
* Use `<pattern>` instead of `<clipPath>` for clip
This simplifies the future implementation of clipping-based rendering
for strokes, as the stroke does not support the use of a clip path but
rather paint sources from a paint server.
* Move svg pattern rendering function to RenderExt
* Fix comment
* Fix empty fill list rendering as default black
Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
* Move opaque check function to Graphic impl
* Add color converter and debug node to use graphic
* WIP: Use List<Graphic> to render Color & Gradient
* Use `Arc<List<Vector>>` for vector_data metadata
This exposes List's attributes to message handlers, enabling them to
access the necessary attribute data such as ATTR_STROKE_PAINT_GRAPHIC
as `Fill` and `Stroke` will not have paint information in the future.
* Recurse opacity checks on nested `Graphic`
Also extracts `fill_graphic_list_at` /
`stroke_paint_graphic_list_at` to share the row-attribute
lookup across the existing call sites.
* Fix fill and stroke visibility check degradation
* Fix clipping based stroke paint positioning
* Refactor vello renderer for stroke to use graphic
* Reduce `Fill` / `Stroke.color` to `List<Graphic>` allocations
* Revert "Use `Arc<List<Vector>>` for vector_data metadata"
This reverts commit 4285243a5d
* Expose paint row attributes as dedicated metadata for vectors
Add `fill_attributes` / `stroke_paint_attributes` to `DocumentMetadata`
so the `ExpandFillStrokeOnSelectedLayers` handler can read row paint
visibility without exposing entire `List<Vector>`.
* Fix transparency check to consider fill opacity
* Fix consistency of gradient placement for SVG stroke
* Rename `stroke_paint_..` to `stroke_..`
* Remove debug nodes
* Allow to use any graphic type without casting
* Rename `fill_graphic` / `stroke_graphic` to `fill` / `stroke`
* Fix SVG pattern placement when stroke transform differs from item transform
* Fix click target fill check for empty list in graphic
* Fix blank fill/stroke attribute masking legacy style
* Fix SVG's paint order trick for vector/raster fills
* Add zero-division guard for pattern wraparound prevention
* Code review
---------
Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
@@ -12,6 +12,8 @@ use crate::messages::prelude::*;
|
||||
use glam::{DAffine2, IVec2};
|
||||
use graph_craft::document::NodeId;
|
||||
use graphene_std::Color;
|
||||
use graphene_std::Graphic;
|
||||
use graphene_std::list::List;
|
||||
use graphene_std::raster::BlendMode;
|
||||
use graphene_std::raster::Image;
|
||||
use graphene_std::transform::Footprint;
|
||||
@@ -235,6 +237,16 @@ pub enum DocumentMessage {
|
||||
UpdateVectorData {
|
||||
vector_data: HashMap<NodeId, Arc<Vector>>,
|
||||
},
|
||||
// `Message` is only serialized at `editor_wrapper.rs`, and only inputs from JS pass through it.
|
||||
// `UpdateFillAttributes` and `UpdateStrokeAttributes` are produced inside `editor.handle_message` by `node_graph_executor.rs` and consumed in the same dispatch loop, so it never reaches that serialization point.
|
||||
#[serde(skip)]
|
||||
UpdateFillAttributes {
|
||||
fill_attributes: HashMap<NodeId, Arc<List<Graphic>>>,
|
||||
},
|
||||
#[serde(skip)]
|
||||
UpdateStrokeAttributes {
|
||||
stroke_attributes: HashMap<NodeId, Arc<List<Graphic>>>,
|
||||
},
|
||||
Undo,
|
||||
UngroupSelectedLayers,
|
||||
UngroupLayer {
|
||||
|
||||
@@ -1404,6 +1404,34 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
|
||||
.collect();
|
||||
self.network_interface.update_vector_data(layer_vector_data);
|
||||
}
|
||||
DocumentMessage::UpdateFillAttributes { fill_attributes } => {
|
||||
// Convert NodeId keys to LayerNodeIdentifier keys, filtering to only layers
|
||||
let layer_fill_attributes = fill_attributes
|
||||
.into_iter()
|
||||
.filter(|(node_id, _)| self.network_interface.document_network().nodes.contains_key(node_id))
|
||||
.filter_map(|(node_id, attrs)| {
|
||||
self.network_interface.is_layer(&node_id, &[]).then(|| {
|
||||
let layer = LayerNodeIdentifier::new(node_id, &self.network_interface);
|
||||
(layer, attrs)
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
self.network_interface.update_fill_attributes(layer_fill_attributes);
|
||||
}
|
||||
DocumentMessage::UpdateStrokeAttributes { stroke_attributes } => {
|
||||
// Convert NodeId keys to LayerNodeIdentifier keys, filtering to only layers
|
||||
let layer_stroke_attributes = stroke_attributes
|
||||
.into_iter()
|
||||
.filter(|(node_id, _)| self.network_interface.document_network().nodes.contains_key(node_id))
|
||||
.filter_map(|(node_id, attrs)| {
|
||||
self.network_interface.is_layer(&node_id, &[]).then(|| {
|
||||
let layer = LayerNodeIdentifier::new(node_id, &self.network_interface);
|
||||
(layer, attrs)
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
self.network_interface.update_stroke_attributes(layer_stroke_attributes);
|
||||
}
|
||||
DocumentMessage::Undo => {
|
||||
if self.network_interface.transaction_status() != TransactionStatus::Finished {
|
||||
return;
|
||||
@@ -2486,10 +2514,23 @@ impl DocumentMessageHandler {
|
||||
continue;
|
||||
};
|
||||
|
||||
let has_fill = !matches!(style.fill, Fill::None);
|
||||
let fill_graphic_list = self.network_interface.document_metadata().layer_fill_attributes.get(&layer);
|
||||
let stroke_graphic_list = self.network_interface.document_metadata().layer_stroke_attributes.get(&layer);
|
||||
|
||||
let has_fill = if let Some(list) = fill_graphic_list {
|
||||
list.element(0).is_some()
|
||||
} else {
|
||||
!matches!(style.fill, Fill::None)
|
||||
};
|
||||
// `style.stroke` is `Some` whenever a `Stroke` node is in the chain, even with weight 0 or a transparent color.
|
||||
// So `is_some()` would treat invisibly-stroked fill-only layers as having a stroke.
|
||||
let has_stroke = style.stroke.as_ref().is_some_and(|s| s.has_renderable_stroke());
|
||||
// `ATTR_STROKE` is the source of truth when set; fall back to `style.stroke.color` only when no attribute is present.
|
||||
let stroke_visible = if let Some(list) = stroke_graphic_list {
|
||||
list.element(0).is_some_and(|g| !g.is_fully_transparent())
|
||||
} else {
|
||||
style.stroke.as_ref().and_then(|s| s.color()).is_some_and(|c| c.a() != 0.)
|
||||
};
|
||||
let has_stroke = style.stroke.as_ref().is_some_and(|s| s.has_renderable_stroke()) && stroke_visible;
|
||||
|
||||
// No stroke means there's nothing to solidify. Fill-only layers are already in the desired form, so skip.
|
||||
if !has_stroke {
|
||||
|
||||
@@ -6,6 +6,8 @@ use crate::messages::portfolio::document::utility_types::network_interface::Flow
|
||||
use crate::messages::tool::common_functionality::graph_modification_utils;
|
||||
use glam::{DAffine2, DVec2};
|
||||
use graph_craft::document::NodeId;
|
||||
use graphene_std::Graphic;
|
||||
use graphene_std::list::List;
|
||||
use graphene_std::math::quad::Quad;
|
||||
use graphene_std::subpath;
|
||||
use graphene_std::transform::Footprint;
|
||||
@@ -39,6 +41,12 @@ pub struct DocumentMetadata {
|
||||
/// Vector data keyed by layer ID, used as fallback when no Path node exists.
|
||||
/// This provides accurate SegmentIds for layers without explicit Path nodes.
|
||||
pub layer_vector_data: HashMap<LayerNodeIdentifier, Arc<Vector>>,
|
||||
/// Per-layer `ATTR_FILL` attribute, exposed so message handlers can read paint
|
||||
/// information that lives on the list.
|
||||
pub layer_fill_attributes: HashMap<LayerNodeIdentifier, Arc<List<Graphic>>>,
|
||||
/// Per-layer `ATTR_STROKE` attribute, exposed so message handlers can read
|
||||
/// stroke paint information that lives on the list.
|
||||
pub layer_stroke_attributes: HashMap<LayerNodeIdentifier, Arc<List<Graphic>>>,
|
||||
/// Transform from document space to viewport space.
|
||||
pub document_to_viewport: DAffine2,
|
||||
}
|
||||
|
||||
@@ -24,6 +24,8 @@ use graph_craft::application_io::resource::ResourceId;
|
||||
use graph_craft::document::value::TaggedValue;
|
||||
use graph_craft::document::{DocumentNode, DocumentNodeImplementation, NodeId, NodeInput, NodeNetwork, OldDocumentNodeImplementation, OldNodeNetwork};
|
||||
use graphene_std::ContextDependencies;
|
||||
use graphene_std::Graphic;
|
||||
use graphene_std::list::List;
|
||||
use graphene_std::math::quad::Quad;
|
||||
use graphene_std::subpath::Subpath;
|
||||
use graphene_std::transform::Footprint;
|
||||
@@ -3401,6 +3403,16 @@ impl NodeNetworkInterface {
|
||||
pub fn update_vector_data(&mut self, new_layer_vector_data: HashMap<LayerNodeIdentifier, Arc<Vector>>) {
|
||||
self.document_metadata.layer_vector_data = new_layer_vector_data;
|
||||
}
|
||||
|
||||
/// Update the per-layer `ATTR_FILL` snapshot.
|
||||
pub fn update_fill_attributes(&mut self, new_layer_fill_attributes: HashMap<LayerNodeIdentifier, Arc<List<Graphic>>>) {
|
||||
self.document_metadata.layer_fill_attributes = new_layer_fill_attributes;
|
||||
}
|
||||
|
||||
/// Update the per-layer `ATTR_STROKE` snapshot.
|
||||
pub fn update_stroke_attributes(&mut self, new_layer_stroke_attributes: HashMap<LayerNodeIdentifier, Arc<List<Graphic>>>) {
|
||||
self.document_metadata.layer_stroke_attributes = new_layer_stroke_attributes;
|
||||
}
|
||||
}
|
||||
|
||||
// Public mutable methods
|
||||
|
||||
@@ -446,6 +446,8 @@ impl NodeGraphExecutor {
|
||||
text_frames,
|
||||
clip_targets,
|
||||
vector_data,
|
||||
fill_attributes,
|
||||
stroke_attributes,
|
||||
backgrounds: _,
|
||||
} = render_output.metadata;
|
||||
|
||||
@@ -460,6 +462,8 @@ impl NodeGraphExecutor {
|
||||
responses.add(DocumentMessage::UpdateTextFrames { text_frames });
|
||||
responses.add(DocumentMessage::UpdateClipTargets { clip_targets });
|
||||
responses.add(DocumentMessage::UpdateVectorData { vector_data });
|
||||
responses.add(DocumentMessage::UpdateFillAttributes { fill_attributes });
|
||||
responses.add(DocumentMessage::UpdateStrokeAttributes { stroke_attributes });
|
||||
responses.add(DocumentMessage::RenderScrollbars);
|
||||
responses.add(DocumentMessage::RenderRulers);
|
||||
responses.add(OverlaysMessage::Draw);
|
||||
|
||||
Reference in New Issue
Block a user