Instance tables refactor part 6: unwrap VectorData and ImageFrame from single-row to multi-row tables (#2684)
* Start refactoring the boolean operations code * Switch to iterators in the boolean operations code * Make boolean operations work on rows of a table, not Vecs of single-row tables * Remove more .transform() * Simplify brush code * Attempt to remove .transform() by using Instance<Image<Color>> in brush code, but a regression is introduced * Improve blend_image_closure * Simplify * Remove leading underscore from type arguments * Remove .transform() from ImageFrameTable<P> and fix Mask node behavior on stencils not fully overlapping its target image * Remove more .one_instance_ref() * Fully remove .one_instance_ref() and improve the 'Combine Channels' node robustness * Fully remove .once_instance_mut() * Fix tests * Remove .one_empty_image() * Make Instances<T>::default() return an empty table for images, but still not yet vector --------- Co-authored-by: hypercube <0hypercube@gmail.com>
This commit is contained in:
parent
76ecdc8f1b
commit
cb4289169d
|
|
@ -3292,7 +3292,7 @@ mod document_message_handler_tests {
|
|||
let document = editor.active_document();
|
||||
let rect_bbox_before = document.metadata().bounding_box_viewport(rect_layer).unwrap();
|
||||
|
||||
// Moving rectangle from folder1 --> folder2
|
||||
// Moving rectangle from folder1 to folder2
|
||||
editor.handle_message(DocumentMessage::MoveSelectedLayersTo { parent: folder2, insert_index: 0 }).await;
|
||||
|
||||
// Rectangle's viewport position after moving
|
||||
|
|
@ -3304,6 +3304,10 @@ mod document_message_handler_tests {
|
|||
let after_center = (rect_bbox_after[0] + rect_bbox_after[1]) / 2.;
|
||||
let distance = before_center.distance(after_center);
|
||||
|
||||
assert!(distance < 1., "Rectangle should maintain its viewport position after moving between transformed groups");
|
||||
assert!(
|
||||
distance < 1.,
|
||||
"Rectangle should maintain its viewport position after moving between transformed groups\n\
|
||||
Before: {before_center:?}, After: {after_center:?}, Distance: {distance} (should be < 1)"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -413,13 +413,10 @@ impl<'a> ModifyInputsContext<'a> {
|
|||
pub fn transform_set_direct(&mut self, transform: DAffine2, skip_rerender: bool, transform_node_id: Option<NodeId>) {
|
||||
// If the Transform node didn't exist yet, create it now
|
||||
let Some(transform_node_id) = transform_node_id.or_else(|| {
|
||||
// Check if the transform is the identity transform and if so, don't create a new Transform node
|
||||
if let Some((scale, angle, translation)) = (transform.matrix2.determinant() != 0.).then(|| transform.to_scale_angle_translation()) {
|
||||
// Check if the transform is the identity transform within an epsilon
|
||||
if scale.x.abs() < 1e-6 && scale.y.abs() < 1e-6 && angle.abs() < 1e-6 && translation.x.abs() < 1e-6 && translation.y.abs() < 1e-6 {
|
||||
// We don't want to pollute the graph with an unnecessary Transform node, so we avoid creating and setting it by returning None
|
||||
return None;
|
||||
}
|
||||
// Check if the transform is the identity transform (within an epsilon) and if so, don't create a new Transform node
|
||||
if transform.abs_diff_eq(DAffine2::IDENTITY, 1e-6) {
|
||||
// We don't want to pollute the graph with an unnecessary Transform node, so we avoid creating and setting it by returning None
|
||||
return None;
|
||||
}
|
||||
|
||||
// Create the Transform node
|
||||
|
|
@ -453,7 +450,7 @@ impl<'a> ModifyInputsContext<'a> {
|
|||
|
||||
pub fn brush_modify(&mut self, strokes: Vec<BrushStroke>) {
|
||||
let Some(brush_node_id) = self.existing_node_id("Brush", true) else { return };
|
||||
self.set_input_with_refresh(InputConnector::node(brush_node_id, 2), NodeInput::value(TaggedValue::BrushStrokes(strokes), false), false);
|
||||
self.set_input_with_refresh(InputConnector::node(brush_node_id, 1), NodeInput::value(TaggedValue::BrushStrokes(strokes), false), false);
|
||||
}
|
||||
|
||||
pub fn resize_artboard(&mut self, location: IVec2, dimensions: IVec2) {
|
||||
|
|
|
|||
|
|
@ -636,7 +636,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||
.collect(),
|
||||
..Default::default()
|
||||
}),
|
||||
inputs: vec![NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::one_empty_image()), true)],
|
||||
inputs: vec![NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::default()), true)],
|
||||
..Default::default()
|
||||
},
|
||||
persistent_node_metadata: DocumentNodePersistentMetadata {
|
||||
|
|
@ -893,7 +893,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||
.collect(),
|
||||
..Default::default()
|
||||
}),
|
||||
inputs: vec![NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::one_empty_image()), true)],
|
||||
inputs: vec![NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::default()), true)],
|
||||
..Default::default()
|
||||
},
|
||||
persistent_node_metadata: DocumentNodePersistentMetadata {
|
||||
|
|
@ -978,7 +978,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||
|
||||
..Default::default()
|
||||
}),
|
||||
inputs: vec![NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::one_empty_image()), true)],
|
||||
inputs: vec![NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::default()), true)],
|
||||
..Default::default()
|
||||
},
|
||||
persistent_node_metadata: DocumentNodePersistentMetadata {
|
||||
|
|
@ -1019,6 +1019,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||
description: Cow::Borrowed("TODO"),
|
||||
properties: None,
|
||||
},
|
||||
// TODO: Remove this and just use the proto node definition directly
|
||||
DocumentNodeDefinition {
|
||||
identifier: "Brush",
|
||||
category: "Raster",
|
||||
|
|
@ -1029,9 +1030,8 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||
nodes: vec![DocumentNode {
|
||||
inputs: vec![
|
||||
NodeInput::network(concrete!(ImageFrameTable<Color>), 0),
|
||||
NodeInput::network(concrete!(ImageFrameTable<Color>), 1),
|
||||
NodeInput::network(concrete!(Vec<graphene_core::vector::brush_stroke::BrushStroke>), 2),
|
||||
NodeInput::network(concrete!(BrushCache), 3),
|
||||
NodeInput::network(concrete!(Vec<graphene_core::vector::brush_stroke::BrushStroke>), 1),
|
||||
NodeInput::network(concrete!(BrushCache), 2),
|
||||
],
|
||||
manual_composition: Some(concrete!(Context)),
|
||||
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_std::brush::BrushNode")),
|
||||
|
|
@ -1044,15 +1044,14 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||
..Default::default()
|
||||
}),
|
||||
inputs: vec![
|
||||
NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::one_empty_image()), true),
|
||||
NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::one_empty_image()), false),
|
||||
NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::default()), true),
|
||||
NodeInput::value(TaggedValue::BrushStrokes(Vec::new()), false),
|
||||
NodeInput::value(TaggedValue::BrushCache(BrushCache::new_proto()), false),
|
||||
],
|
||||
..Default::default()
|
||||
},
|
||||
persistent_node_metadata: DocumentNodePersistentMetadata {
|
||||
input_properties: vec![("Background", "TODO").into(), ("Bounds", "TODO").into(), ("Trace", "TODO").into(), ("Cache", "TODO").into()],
|
||||
input_properties: vec![("Background", "TODO").into(), ("Trace", "TODO").into(), ("Cache", "TODO").into()],
|
||||
output_names: vec!["Image".to_string()],
|
||||
network_metadata: Some(NodeNetworkMetadata {
|
||||
persistent_metadata: NodeNetworkPersistentMetadata {
|
||||
|
|
@ -1084,7 +1083,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||
node_template: NodeTemplate {
|
||||
document_node: DocumentNode {
|
||||
implementation: DocumentNodeImplementation::proto("graphene_core::memo::MemoNode"),
|
||||
inputs: vec![NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::one_empty_image()), true)],
|
||||
inputs: vec![NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::default()), true)],
|
||||
manual_composition: Some(concrete!(Context)),
|
||||
..Default::default()
|
||||
},
|
||||
|
|
@ -1103,7 +1102,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||
node_template: NodeTemplate {
|
||||
document_node: DocumentNode {
|
||||
implementation: DocumentNodeImplementation::proto("graphene_core::memo::ImpureMemoNode"),
|
||||
inputs: vec![NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::one_empty_image()), true)],
|
||||
inputs: vec![NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::default()), true)],
|
||||
manual_composition: Some(concrete!(Context)),
|
||||
..Default::default()
|
||||
},
|
||||
|
|
@ -1803,7 +1802,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||
.collect(),
|
||||
..Default::default()
|
||||
}),
|
||||
inputs: vec![NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::one_empty_image()), true)],
|
||||
inputs: vec![NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::default()), true)],
|
||||
..Default::default()
|
||||
},
|
||||
persistent_node_metadata: DocumentNodePersistentMetadata {
|
||||
|
|
@ -2685,7 +2684,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||
// ..Default::default()
|
||||
// }),
|
||||
// inputs: vec![
|
||||
// NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::one_empty_image()), true),
|
||||
// NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::default()), true),
|
||||
// NodeInput::scope("editor-api"),
|
||||
// NodeInput::value(TaggedValue::ImaginateController(Default::default()), false),
|
||||
// NodeInput::value(TaggedValue::F64(0.), false), // Remember to keep index used in `ImaginateRandom` updated with this entry's index
|
||||
|
|
|
|||
|
|
@ -635,17 +635,17 @@ impl<'a> Selected<'a> {
|
|||
}
|
||||
|
||||
pub fn apply_transformation(&mut self, transformation: DAffine2, transform_operation: Option<TransformOperation>) {
|
||||
if !self.selected.is_empty() {
|
||||
// TODO: Cache the result of `shallowest_unique_layers` to avoid this heavy computation every frame of movement, see https://github.com/GraphiteEditor/Graphite/pull/481
|
||||
for layer in self.network_interface.shallowest_unique_layers(&[]) {
|
||||
match &mut self.original_transforms {
|
||||
OriginalTransforms::Layer(layer_transforms) => {
|
||||
Self::transform_layer(self.network_interface.document_metadata(), layer, layer_transforms.get(&layer), transformation, self.responses)
|
||||
}
|
||||
OriginalTransforms::Path(path_transforms) => {
|
||||
if let Some(initial_points) = path_transforms.get_mut(&layer) {
|
||||
Self::transform_path(self.network_interface.document_metadata(), layer, initial_points, transformation, self.responses, transform_operation)
|
||||
}
|
||||
if self.selected.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: Cache the result of `shallowest_unique_layers` to avoid this heavy computation every frame of movement, see https://github.com/GraphiteEditor/Graphite/pull/481
|
||||
for layer in self.network_interface.shallowest_unique_layers(&[]) {
|
||||
match &mut self.original_transforms {
|
||||
OriginalTransforms::Layer(layer_transforms) => Self::transform_layer(self.network_interface.document_metadata(), layer, layer_transforms.get(&layer), transformation, self.responses),
|
||||
OriginalTransforms::Path(path_transforms) => {
|
||||
if let Some(initial_points) = path_transforms.get_mut(&layer) {
|
||||
Self::transform_path(self.network_interface.document_metadata(), layer, initial_points, transformation, self.responses, transform_operation)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -930,6 +930,23 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMes
|
|||
document.network_interface.set_input(&InputConnector::node(*node_id, 2), old_inputs[2].clone(), network_path);
|
||||
// We have removed the last input, so we don't add index 3
|
||||
}
|
||||
|
||||
if reference == "Brush" && inputs_count == 4 {
|
||||
let node_definition = resolve_document_node_type(reference).unwrap();
|
||||
let new_node_template = node_definition.default_node_template();
|
||||
let document_node = new_node_template.document_node;
|
||||
document.network_interface.replace_implementation(node_id, network_path, document_node.implementation.clone());
|
||||
document
|
||||
.network_interface
|
||||
.replace_implementation_metadata(node_id, network_path, new_node_template.persistent_node_metadata);
|
||||
|
||||
let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path);
|
||||
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);
|
||||
// We have removed the second input ("bounds"), so we don't add index 1 and we shift the rest of the inputs down by one
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[2].clone(), network_path);
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 2), old_inputs[3].clone(), network_path);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Eventually remove this document upgrade code
|
||||
|
|
|
|||
|
|
@ -276,15 +276,16 @@ impl BrushToolData {
|
|||
let Some(reference) = document.network_interface.reference(&node_id, &[]) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
if *reference == Some("Brush".to_string()) && node_id != layer.to_node() {
|
||||
let points_input = node.inputs.get(2)?;
|
||||
let Some(TaggedValue::BrushStrokes(strokes)) = points_input.as_value() else {
|
||||
continue;
|
||||
};
|
||||
let points_input = node.inputs.get(1)?;
|
||||
let Some(TaggedValue::BrushStrokes(strokes)) = points_input.as_value() else { continue };
|
||||
self.strokes.clone_from(strokes);
|
||||
|
||||
return Some(layer);
|
||||
} else if *reference == Some("Transform".to_string()) {
|
||||
}
|
||||
|
||||
if *reference == Some("Transform".to_string()) {
|
||||
let upstream = document.metadata().upstream_transform(node_id);
|
||||
let pivot = DAffine2::from_translation(upstream.transform_point2(get_current_normalized_pivot(&node.inputs)));
|
||||
self.transform = pivot * get_current_transform(&node.inputs) * pivot.inverse() * self.transform;
|
||||
|
|
|
|||
|
|
@ -397,31 +397,28 @@ impl MessageHandler<TransformLayerMessage, TransformData<'_>> for TransformLayer
|
|||
return;
|
||||
}
|
||||
|
||||
let Some(vector_data) = selected_layers.first().and_then(|&layer| document.network_interface.compute_modified_vector(layer)) else {
|
||||
selected.original_transforms.clear();
|
||||
return;
|
||||
};
|
||||
if let Some(vector_data) = selected_layers.first().and_then(|&layer| document.network_interface.compute_modified_vector(layer)) {
|
||||
if let [point] = selected_points.as_slice() {
|
||||
if matches!(point, ManipulatorPointId::Anchor(_)) {
|
||||
if let Some([handle1, handle2]) = point.get_handle_pair(&vector_data) {
|
||||
let handle1_length = handle1.length(&vector_data);
|
||||
let handle2_length = handle2.length(&vector_data);
|
||||
|
||||
if let [point] = selected_points.as_slice() {
|
||||
if matches!(point, ManipulatorPointId::Anchor(_)) {
|
||||
if let Some([handle1, handle2]) = point.get_handle_pair(&vector_data) {
|
||||
let handle1_length = handle1.length(&vector_data);
|
||||
let handle2_length = handle2.length(&vector_data);
|
||||
|
||||
if (handle1_length == 0. && handle2_length == 0. && !using_select_tool) || (handle1_length == f64::MAX && handle2_length == f64::MAX && !using_select_tool) {
|
||||
// G should work for this point but not R and S
|
||||
if matches!(transform_type, TransformType::Rotate | TransformType::Scale) {
|
||||
selected.original_transforms.clear();
|
||||
return;
|
||||
if (handle1_length == 0. && handle2_length == 0. && !using_select_tool) || (handle1_length == f64::MAX && handle2_length == f64::MAX && !using_select_tool) {
|
||||
// G should work for this point but not R and S
|
||||
if matches!(transform_type, TransformType::Rotate | TransformType::Scale) {
|
||||
selected.original_transforms.clear();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let handle_length = point.as_handle().map(|handle| handle.length(&vector_data));
|
||||
} else {
|
||||
let handle_length = point.as_handle().map(|handle| handle.length(&vector_data));
|
||||
|
||||
if handle_length == Some(0.) {
|
||||
selected.original_transforms.clear();
|
||||
return;
|
||||
if handle_length == Some(0.) {
|
||||
selected.original_transforms.clear();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ use graphene_core::renderer::{RenderSvgSegmentList, SvgSegment};
|
|||
use graphene_core::text::FontCache;
|
||||
use graphene_core::vector::style::ViewMode;
|
||||
use graphene_std::Context;
|
||||
use graphene_std::instances::Instance;
|
||||
use graphene_std::vector::{VectorData, VectorDataTable};
|
||||
use graphene_std::wasm_application_io::{WasmApplicationIo, WasmEditorApi};
|
||||
use interpreted_executor::dynamic_executor::{DynamicExecutor, IntrospectError, ResolvedDocumentNodeTypesDelta};
|
||||
|
|
@ -293,9 +294,16 @@ impl NodeRuntime {
|
|||
Self::process_graphic_element(&mut self.thumbnail_renders, parent_network_node_id, &io.output, responses, update_thumbnails)
|
||||
// Insert the vector modify if we are dealing with vector data
|
||||
} else if let Some(record) = introspected_data.downcast_ref::<IORecord<Context, VectorDataTable>>() {
|
||||
self.vector_modify.insert(parent_network_node_id, record.output.one_instance_ref().instance.clone());
|
||||
let default = Instance {
|
||||
instance: VectorData::empty(),
|
||||
..Default::default()
|
||||
};
|
||||
self.vector_modify.insert(
|
||||
parent_network_node_id,
|
||||
record.output.instance_ref_iter().next().unwrap_or_else(|| default.to_instance_ref()).instance.clone(),
|
||||
);
|
||||
} else {
|
||||
log::warn!("failed to downcast monitor node output {parent_network_node_id:?}");
|
||||
log::warn!("Failed to downcast monitor node output {parent_network_node_id:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use crate::instances::Instances;
|
||||
use crate::text::FontCache;
|
||||
use crate::transform::{Footprint, Transform, TransformMut};
|
||||
use crate::transform::Footprint;
|
||||
use crate::vector::style::ViewMode;
|
||||
use alloc::sync::Arc;
|
||||
use core::fmt::Debug;
|
||||
|
|
@ -37,17 +37,6 @@ impl Hash for SurfaceFrame {
|
|||
}
|
||||
}
|
||||
|
||||
impl Transform for SurfaceFrame {
|
||||
fn transform(&self) -> DAffine2 {
|
||||
self.transform
|
||||
}
|
||||
}
|
||||
impl TransformMut for SurfaceFrame {
|
||||
fn transform_mut(&mut self) -> &mut DAffine2 {
|
||||
&mut self.transform
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "dyn-any")]
|
||||
unsafe impl StaticType for SurfaceFrame {
|
||||
type Static = SurfaceFrame;
|
||||
|
|
@ -152,18 +141,6 @@ unsafe impl<T: 'static> StaticType for SurfaceHandleFrame<T> {
|
|||
type Static = SurfaceHandleFrame<T>;
|
||||
}
|
||||
|
||||
impl<T> Transform for SurfaceHandleFrame<T> {
|
||||
fn transform(&self) -> DAffine2 {
|
||||
self.transform
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> TransformMut for SurfaceHandleFrame<T> {
|
||||
fn transform_mut(&mut self) -> &mut DAffine2 {
|
||||
&mut self.transform
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: think about how to automatically clean up memory
|
||||
/*
|
||||
impl<'a, Surface> Drop for SurfaceHandle<'a, Surface> {
|
||||
|
|
|
|||
|
|
@ -277,13 +277,13 @@ pub trait GraphicElementRendered {
|
|||
fn render_to_vello(&self, scene: &mut Scene, transform: DAffine2, context: &mut RenderContext, _render_params: &RenderParams);
|
||||
fn bounding_box(&self, transform: DAffine2, include_stroke: bool) -> Option<[DVec2; 2]>;
|
||||
|
||||
// The upstream click targets for each layer are collected during the render so that they do not have to be calculated for each click detection
|
||||
/// The upstream click targets for each layer are collected during the render so that they do not have to be calculated for each click detection.
|
||||
fn add_upstream_click_targets(&self, _click_targets: &mut Vec<ClickTarget>) {}
|
||||
|
||||
// TODO: Store all click targets in a vec which contains the AABB, click target, and path
|
||||
// fn add_click_targets(&self, click_targets: &mut Vec<([DVec2; 2], ClickTarget, Vec<NodeId>)>, current_path: Option<NodeId>) {}
|
||||
|
||||
// Recursively iterate over data in the render (including groups upstream from vector data in the case of a boolean operation) to collect the footprints, click targets, and vector modify
|
||||
/// Recursively iterate over data in the render (including groups upstream from vector data in the case of a boolean operation) to collect the footprints, click targets, and vector modify.
|
||||
fn collect_metadata(&self, _metadata: &mut RenderMetadata, _footprint: Footprint, _element_id: Option<NodeId>) {}
|
||||
|
||||
fn contains_artboard(&self) -> bool {
|
||||
|
|
@ -639,9 +639,10 @@ impl GraphicElementRendered for VectorDataTable {
|
|||
}
|
||||
|
||||
fn collect_metadata(&self, metadata: &mut RenderMetadata, mut footprint: Footprint, element_id: Option<NodeId>) {
|
||||
let instance_transform = self.transform();
|
||||
for instance in self.instance_ref_iter() {
|
||||
let instance_transform = *instance.transform;
|
||||
let instance = instance.instance;
|
||||
|
||||
for instance in self.instance_ref_iter().map(|instance| instance.instance) {
|
||||
if let Some(element_id) = element_id {
|
||||
let stroke_width = instance.style.stroke().as_ref().map_or(0., Stroke::weight);
|
||||
let filled = instance.style.fill() != &Fill::None;
|
||||
|
|
@ -905,14 +906,15 @@ impl GraphicElementRendered for ImageFrameTable<Color> {
|
|||
}
|
||||
|
||||
fn collect_metadata(&self, metadata: &mut RenderMetadata, footprint: Footprint, element_id: Option<NodeId>) {
|
||||
let instance_transform = self.transform();
|
||||
|
||||
let Some(element_id) = element_id else { return };
|
||||
let subpath = Subpath::new_rect(DVec2::ZERO, DVec2::ONE);
|
||||
|
||||
metadata.click_targets.insert(element_id, vec![ClickTarget::new(subpath, 0.)]);
|
||||
metadata.upstream_footprints.insert(element_id, footprint);
|
||||
metadata.local_transforms.insert(element_id, instance_transform);
|
||||
// TODO: Find a way to handle more than one row of the graphical data table
|
||||
if let Some(image) = self.instance_ref_iter().next() {
|
||||
metadata.local_transforms.insert(element_id, *image.transform);
|
||||
}
|
||||
}
|
||||
|
||||
fn add_upstream_click_targets(&self, click_targets: &mut Vec<ClickTarget>) {
|
||||
|
|
@ -933,8 +935,8 @@ impl GraphicElementRendered for RasterFrame {
|
|||
fn render_to_vello(&self, scene: &mut Scene, transform: DAffine2, context: &mut RenderContext, _render_params: &RenderParams) {
|
||||
use vello::peniko;
|
||||
|
||||
let mut render_stuff = |image: vello::peniko::Image, blend_mode: crate::AlphaBlending| {
|
||||
let image_transform = transform * self.transform() * DAffine2::from_scale(1. / DVec2::new(image.width as f64, image.height as f64));
|
||||
let mut render_stuff = |image: vello::peniko::Image, instance_transform: DAffine2, blend_mode: crate::AlphaBlending| {
|
||||
let image_transform = transform * instance_transform * DAffine2::from_scale(1. / DVec2::new(image.width as f64, image.height as f64));
|
||||
let layer = blend_mode != Default::default();
|
||||
|
||||
let Some(bounds) = self.bounding_box(transform, true) else { return };
|
||||
|
|
@ -960,7 +962,7 @@ impl GraphicElementRendered for RasterFrame {
|
|||
|
||||
let image = vello::peniko::Image::new(image.to_flat_u8().0.into(), peniko::Format::Rgba8, image.width, image.height).with_extend(peniko::Extend::Repeat);
|
||||
|
||||
render_stuff(image, *instance.alpha_blending);
|
||||
render_stuff(image, *instance.transform, *instance.alpha_blending);
|
||||
}
|
||||
}
|
||||
RasterFrame::TextureFrame(image_texture) => {
|
||||
|
|
@ -971,15 +973,22 @@ impl GraphicElementRendered for RasterFrame {
|
|||
let id = image.data.id();
|
||||
context.resource_overrides.insert(id, instance.instance.texture.clone());
|
||||
|
||||
render_stuff(image, *instance.alpha_blending);
|
||||
render_stuff(image, *instance.transform, *instance.alpha_blending);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn bounding_box(&self, transform: DAffine2, _include_stroke: bool) -> Option<[DVec2; 2]> {
|
||||
let transform = transform * self.transform();
|
||||
(transform.matrix2.determinant() != 0.).then(|| (transform * Quad::from_box([DVec2::ZERO, DVec2::ONE])).bounding_box())
|
||||
let calculate_transform = |instance_transform| {
|
||||
let transform: DAffine2 = transform * instance_transform;
|
||||
(transform.matrix2.determinant() != 0.).then(|| (transform * Quad::from_box([DVec2::ZERO, DVec2::ONE])).bounding_box())
|
||||
};
|
||||
|
||||
match self {
|
||||
RasterFrame::ImageFrame(instances) => instances.instance_ref_iter().flat_map(|instance| calculate_transform(*instance.transform)).reduce(Quad::combine_bounds),
|
||||
RasterFrame::TextureFrame(instances) => instances.instance_ref_iter().flat_map(|instance| calculate_transform(*instance.transform)).reduce(Quad::combine_bounds),
|
||||
}
|
||||
}
|
||||
|
||||
fn collect_metadata(&self, metadata: &mut RenderMetadata, footprint: Footprint, element_id: Option<NodeId>) {
|
||||
|
|
@ -988,7 +997,21 @@ impl GraphicElementRendered for RasterFrame {
|
|||
let subpath = Subpath::new_rect(DVec2::ZERO, DVec2::ONE);
|
||||
metadata.click_targets.insert(element_id, vec![ClickTarget::new(subpath, 0.)]);
|
||||
metadata.upstream_footprints.insert(element_id, footprint);
|
||||
metadata.local_transforms.insert(element_id, self.transform());
|
||||
|
||||
match self {
|
||||
RasterFrame::ImageFrame(instances) => {
|
||||
// TODO: Find a way to handle more than one row of the graphical data table
|
||||
if let Some(image) = instances.instance_ref_iter().next() {
|
||||
metadata.local_transforms.insert(element_id, *image.transform);
|
||||
}
|
||||
}
|
||||
RasterFrame::TextureFrame(instances) => {
|
||||
// TODO: Find a way to handle more than one row of the graphical data table
|
||||
if let Some(image_texture) = instances.instance_ref_iter().next() {
|
||||
metadata.local_transforms.insert(element_id, *image_texture.transform);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn add_upstream_click_targets(&self, click_targets: &mut Vec<ClickTarget>) {
|
||||
|
|
@ -1031,11 +1054,27 @@ impl GraphicElementRendered for GraphicElement {
|
|||
}
|
||||
GraphicElement::VectorData(vector_data) => {
|
||||
metadata.upstream_footprints.insert(element_id, footprint);
|
||||
metadata.local_transforms.insert(element_id, vector_data.transform());
|
||||
// TODO: Find a way to handle more than one row of the graphical data table
|
||||
if let Some(vector_data) = vector_data.instance_ref_iter().next() {
|
||||
metadata.local_transforms.insert(element_id, *vector_data.transform);
|
||||
}
|
||||
}
|
||||
GraphicElement::RasterFrame(raster_frame) => {
|
||||
metadata.upstream_footprints.insert(element_id, footprint);
|
||||
metadata.local_transforms.insert(element_id, raster_frame.transform());
|
||||
match raster_frame {
|
||||
RasterFrame::ImageFrame(instances) => {
|
||||
// TODO: Find a way to handle more than one row of images
|
||||
if let Some(image) = instances.instance_ref_iter().next() {
|
||||
metadata.local_transforms.insert(element_id, *image.transform);
|
||||
}
|
||||
}
|
||||
RasterFrame::TextureFrame(instances) => {
|
||||
// TODO: Find a way to handle more than one row of image textures
|
||||
if let Some(image_texture) = instances.instance_ref_iter().next() {
|
||||
metadata.local_transforms.insert(element_id, *image_texture.transform);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,5 @@
|
|||
use crate::application_io::TextureFrameTable;
|
||||
use crate::raster::Pixel;
|
||||
use crate::raster::image::{Image, ImageFrameTable};
|
||||
use crate::transform::{Transform, TransformMut};
|
||||
use crate::AlphaBlending;
|
||||
use crate::uuid::NodeId;
|
||||
use crate::vector::VectorDataTable;
|
||||
use crate::{AlphaBlending, GraphicElement, RasterFrame};
|
||||
use dyn_any::StaticType;
|
||||
use glam::DAffine2;
|
||||
use std::hash::Hash;
|
||||
|
|
@ -47,26 +42,6 @@ impl<T> Instances<T> {
|
|||
self.source_node_id.push(instance.source_node_id);
|
||||
}
|
||||
|
||||
pub fn one_instance_ref(&self) -> InstanceRef<T> {
|
||||
InstanceRef {
|
||||
instance: self.instance.first().unwrap_or_else(|| panic!("ONE INSTANCE EXPECTED, FOUND {}", self.instance.len())),
|
||||
transform: self.transform.first().unwrap_or_else(|| panic!("ONE INSTANCE EXPECTED, FOUND {}", self.instance.len())),
|
||||
alpha_blending: self.alpha_blending.first().unwrap_or_else(|| panic!("ONE INSTANCE EXPECTED, FOUND {}", self.instance.len())),
|
||||
source_node_id: self.source_node_id.first().unwrap_or_else(|| panic!("ONE INSTANCE EXPECTED, FOUND {}", self.instance.len())),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn one_instance_mut(&mut self) -> InstanceMut<T> {
|
||||
let length = self.instance.len();
|
||||
|
||||
InstanceMut {
|
||||
instance: self.instance.first_mut().unwrap_or_else(|| panic!("ONE INSTANCE EXPECTED, FOUND {}", length)),
|
||||
transform: self.transform.first_mut().unwrap_or_else(|| panic!("ONE INSTANCE EXPECTED, FOUND {}", length)),
|
||||
alpha_blending: self.alpha_blending.first_mut().unwrap_or_else(|| panic!("ONE INSTANCE EXPECTED, FOUND {}", length)),
|
||||
source_node_id: self.source_node_id.first_mut().unwrap_or_else(|| panic!("ONE INSTANCE EXPECTED, FOUND {}", length)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn instance_iter(self) -> impl DoubleEndedIterator<Item = Instance<T>> {
|
||||
self.instance
|
||||
.into_iter()
|
||||
|
|
@ -81,7 +56,7 @@ impl<T> Instances<T> {
|
|||
})
|
||||
}
|
||||
|
||||
pub fn instance_ref_iter(&self) -> impl DoubleEndedIterator<Item = InstanceRef<T>> {
|
||||
pub fn instance_ref_iter(&self) -> impl DoubleEndedIterator<Item = InstanceRef<T>> + Clone {
|
||||
self.instance
|
||||
.iter()
|
||||
.zip(self.transform.iter())
|
||||
|
|
@ -146,11 +121,8 @@ impl<T> Instances<T> {
|
|||
|
||||
impl<T: Default + Hash + 'static> Default for Instances<T> {
|
||||
fn default() -> Self {
|
||||
// TODO: Remove once all types have been converted to tables
|
||||
let converted_to_tables = [TypeId::of::<crate::Artboard>(), TypeId::of::<crate::GraphicElement>()];
|
||||
|
||||
use core::any::TypeId;
|
||||
if converted_to_tables.contains(&TypeId::of::<T>()) {
|
||||
if TypeId::of::<T>() != TypeId::of::<crate::vector::VectorData>() {
|
||||
// TODO: Remove the 'static trait bound when this special casing is removed by making all types return empty
|
||||
Self::empty()
|
||||
} else {
|
||||
|
|
@ -188,7 +160,7 @@ fn one_source_node_id_default() -> Vec<Option<NodeId>> {
|
|||
vec![None]
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||
pub struct InstanceRef<'a, T> {
|
||||
pub instance: &'a T,
|
||||
pub transform: &'a DAffine2,
|
||||
|
|
@ -196,6 +168,20 @@ pub struct InstanceRef<'a, T> {
|
|||
pub source_node_id: &'a Option<NodeId>,
|
||||
}
|
||||
|
||||
impl<T> InstanceRef<'_, T> {
|
||||
pub fn to_instance_cloned(self) -> Instance<T>
|
||||
where
|
||||
T: Clone,
|
||||
{
|
||||
Instance {
|
||||
instance: self.instance.clone(),
|
||||
transform: *self.transform,
|
||||
alpha_blending: *self.alpha_blending,
|
||||
source_node_id: *self.source_node_id,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct InstanceMut<'a, T> {
|
||||
pub instance: &'a mut T,
|
||||
|
|
@ -204,7 +190,7 @@ pub struct InstanceMut<'a, T> {
|
|||
pub source_node_id: &'a mut Option<NodeId>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Default, Debug)]
|
||||
#[derive(Copy, Clone, Default, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||
pub struct Instance<T> {
|
||||
pub instance: T,
|
||||
pub transform: DAffine2,
|
||||
|
|
@ -224,64 +210,31 @@ impl<T> Instance<T> {
|
|||
source_node_id: self.source_node_id,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// VECTOR DATA TABLE
|
||||
impl Transform for VectorDataTable {
|
||||
fn transform(&self) -> DAffine2 {
|
||||
*self.one_instance_ref().transform
|
||||
pub fn to_instance_ref(&self) -> InstanceRef<T> {
|
||||
InstanceRef {
|
||||
instance: &self.instance,
|
||||
transform: &self.transform,
|
||||
alpha_blending: &self.alpha_blending,
|
||||
source_node_id: &self.source_node_id,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl TransformMut for VectorDataTable {
|
||||
fn transform_mut(&mut self) -> &mut DAffine2 {
|
||||
self.one_instance_mut().transform
|
||||
}
|
||||
}
|
||||
|
||||
// TEXTURE FRAME TABLE
|
||||
impl Transform for TextureFrameTable {
|
||||
fn transform(&self) -> DAffine2 {
|
||||
*self.one_instance_ref().transform
|
||||
pub fn to_instance_mut(&mut self) -> InstanceMut<T> {
|
||||
InstanceMut {
|
||||
instance: &mut self.instance,
|
||||
transform: &mut self.transform,
|
||||
alpha_blending: &mut self.alpha_blending,
|
||||
source_node_id: &mut self.source_node_id,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl TransformMut for TextureFrameTable {
|
||||
fn transform_mut(&mut self) -> &mut DAffine2 {
|
||||
self.one_instance_mut().transform
|
||||
}
|
||||
}
|
||||
|
||||
// IMAGE FRAME TABLE
|
||||
impl<P: Pixel> Transform for ImageFrameTable<P>
|
||||
where
|
||||
GraphicElement: From<Image<P>>,
|
||||
{
|
||||
fn transform(&self) -> DAffine2 {
|
||||
*self.one_instance_ref().transform
|
||||
}
|
||||
}
|
||||
impl<P: Pixel> TransformMut for ImageFrameTable<P>
|
||||
where
|
||||
GraphicElement: From<Image<P>>,
|
||||
{
|
||||
fn transform_mut(&mut self) -> &mut DAffine2 {
|
||||
self.one_instance_mut().transform
|
||||
}
|
||||
}
|
||||
|
||||
// RASTER FRAME
|
||||
impl Transform for RasterFrame {
|
||||
fn transform(&self) -> DAffine2 {
|
||||
match self {
|
||||
RasterFrame::ImageFrame(image_frame) => image_frame.transform(),
|
||||
RasterFrame::TextureFrame(texture_frame) => texture_frame.transform(),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl TransformMut for RasterFrame {
|
||||
fn transform_mut(&mut self) -> &mut DAffine2 {
|
||||
match self {
|
||||
RasterFrame::ImageFrame(image_frame) => image_frame.transform_mut(),
|
||||
RasterFrame::TextureFrame(texture_frame) => texture_frame.transform_mut(),
|
||||
pub fn to_table(self) -> Instances<T> {
|
||||
Instances {
|
||||
instance: vec![self.instance],
|
||||
transform: vec![self.transform],
|
||||
alpha_blending: vec![self.alpha_blending],
|
||||
source_node_id: vec![self.source_node_id],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ use glam::{DAffine2, DVec2};
|
|||
fn log_to_console<T: core::fmt::Debug>(_: impl Ctx, #[implementations(String, bool, f64, u32, u64, DVec2, VectorDataTable, DAffine2, Color, Option<Color>)] value: T) -> T {
|
||||
#[cfg(not(target_arch = "spirv"))]
|
||||
// KEEP THIS `debug!()` - It acts as the output for the debug node itself
|
||||
debug!("{:#?}", value);
|
||||
log::debug!("{:#?}", value);
|
||||
value
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -586,22 +586,22 @@ impl<'i, N: for<'a> Node<'a, I> + Copy, I: 'i> Copy for TypeNode<N, I, <N as Nod
|
|||
|
||||
// Into
|
||||
pub struct IntoNode<O>(PhantomData<O>);
|
||||
impl<_O> IntoNode<_O> {
|
||||
impl<O> IntoNode<O> {
|
||||
#[cfg(feature = "alloc")]
|
||||
pub const fn new() -> Self {
|
||||
Self(core::marker::PhantomData)
|
||||
}
|
||||
}
|
||||
impl<_O> Default for IntoNode<_O> {
|
||||
impl<O> Default for IntoNode<O> {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
impl<'input, I: 'input, _O: 'input> Node<'input, I> for IntoNode<_O>
|
||||
impl<'input, I: 'input, O: 'input> Node<'input, I> for IntoNode<O>
|
||||
where
|
||||
I: Into<_O> + Sync + Send,
|
||||
I: Into<O> + Sync + Send,
|
||||
{
|
||||
type Output = ::dyn_any::DynFuture<'input, _O>;
|
||||
type Output = ::dyn_any::DynFuture<'input, O>;
|
||||
|
||||
#[inline]
|
||||
fn eval(&'input self, input: I) -> Self::Output {
|
||||
|
|
|
|||
|
|
@ -665,9 +665,9 @@ impl Blend<Color> for Option<Color> {
|
|||
}
|
||||
impl Blend<Color> for ImageFrameTable<Color> {
|
||||
fn blend(&self, under: &Self, blend_fn: impl Fn(Color, Color) -> Color) -> Self {
|
||||
let mut result = self.clone();
|
||||
let mut result_table = self.clone();
|
||||
|
||||
for (over, under) in result.instance_mut_iter().zip(under.instance_ref_iter()) {
|
||||
for (over, under) in result_table.instance_mut_iter().zip(under.instance_ref_iter()) {
|
||||
let data = over.instance.data.iter().zip(under.instance.data.iter()).map(|(a, b)| blend_fn(*a, *b)).collect();
|
||||
|
||||
*over.instance = Image {
|
||||
|
|
@ -678,7 +678,7 @@ impl Blend<Color> for ImageFrameTable<Color> {
|
|||
};
|
||||
}
|
||||
|
||||
result
|
||||
result_table
|
||||
}
|
||||
}
|
||||
impl Blend<Color> for GradientStops {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
use crate::Color;
|
||||
use crate::graphene_core::raster::image::ImageFrameTable;
|
||||
use crate::instances::Instance;
|
||||
use crate::raster::Image;
|
||||
use crate::vector::brush_stroke::BrushStroke;
|
||||
use crate::vector::brush_stroke::BrushStyle;
|
||||
|
|
@ -16,12 +16,12 @@ struct BrushCacheImpl {
|
|||
prev_input: Vec<BrushStroke>,
|
||||
|
||||
// The strokes that have been fully processed and blended into the background.
|
||||
#[cfg_attr(feature = "serde", serde(deserialize_with = "crate::graphene_core::raster::image::migrate_image_frame"))]
|
||||
background: ImageFrameTable<Color>,
|
||||
#[cfg_attr(feature = "serde", serde(deserialize_with = "crate::graphene_core::raster::image::migrate_image_frame"))]
|
||||
blended_image: ImageFrameTable<Color>,
|
||||
#[cfg_attr(feature = "serde", serde(deserialize_with = "crate::graphene_core::raster::image::migrate_image_frame"))]
|
||||
last_stroke_texture: ImageFrameTable<Color>,
|
||||
#[cfg_attr(feature = "serde", serde(deserialize_with = "crate::graphene_core::raster::image::migrate_image_frame_instance"))]
|
||||
background: Instance<Image<Color>>,
|
||||
#[cfg_attr(feature = "serde", serde(deserialize_with = "crate::graphene_core::raster::image::migrate_image_frame_instance"))]
|
||||
blended_image: Instance<Image<Color>>,
|
||||
#[cfg_attr(feature = "serde", serde(deserialize_with = "crate::graphene_core::raster::image::migrate_image_frame_instance"))]
|
||||
last_stroke_texture: Instance<Image<Color>>,
|
||||
|
||||
// A cache for brush textures.
|
||||
#[cfg_attr(feature = "serde", serde(skip))]
|
||||
|
|
@ -29,9 +29,9 @@ struct BrushCacheImpl {
|
|||
}
|
||||
|
||||
impl BrushCacheImpl {
|
||||
fn compute_brush_plan(&mut self, mut background: ImageFrameTable<Color>, input: &[BrushStroke]) -> BrushPlan {
|
||||
fn compute_brush_plan(&mut self, mut background: Instance<Image<Color>>, input: &[BrushStroke]) -> BrushPlan {
|
||||
// Do background invalidation.
|
||||
if background.one_instance_ref().instance != self.background.one_instance_ref().instance {
|
||||
if background != self.background {
|
||||
self.background = background.clone();
|
||||
return BrushPlan {
|
||||
strokes: input.to_vec(),
|
||||
|
|
@ -56,7 +56,11 @@ impl BrushCacheImpl {
|
|||
background = core::mem::take(&mut self.blended_image);
|
||||
|
||||
// Check if the first non-blended stroke is an extension of the last one.
|
||||
let mut first_stroke_texture = ImageFrameTable::one_empty_image();
|
||||
let mut first_stroke_texture = Instance {
|
||||
instance: Image::default(),
|
||||
transform: glam::DAffine2::ZERO,
|
||||
..Default::default()
|
||||
};
|
||||
let mut first_stroke_point_skip = 0;
|
||||
let strokes = input[num_blended_strokes..].to_vec();
|
||||
if !strokes.is_empty() && self.prev_input.len() > num_blended_strokes {
|
||||
|
|
@ -80,7 +84,7 @@ impl BrushCacheImpl {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn cache_results(&mut self, input: Vec<BrushStroke>, blended_image: ImageFrameTable<Color>, last_stroke_texture: ImageFrameTable<Color>) {
|
||||
pub fn cache_results(&mut self, input: Vec<BrushStroke>, blended_image: Instance<Image<Color>>, last_stroke_texture: Instance<Image<Color>>) {
|
||||
self.prev_input = input;
|
||||
self.blended_image = blended_image;
|
||||
self.last_stroke_texture = last_stroke_texture;
|
||||
|
|
@ -95,8 +99,8 @@ impl Hash for BrushCacheImpl {
|
|||
#[derive(Clone, Debug, Default)]
|
||||
pub struct BrushPlan {
|
||||
pub strokes: Vec<BrushStroke>,
|
||||
pub background: ImageFrameTable<Color>,
|
||||
pub first_stroke_texture: ImageFrameTable<Color>,
|
||||
pub background: Instance<Image<Color>>,
|
||||
pub first_stroke_texture: Instance<Image<Color>>,
|
||||
pub first_stroke_point_skip: usize,
|
||||
}
|
||||
|
||||
|
|
@ -160,12 +164,12 @@ impl BrushCache {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn compute_brush_plan(&self, background: ImageFrameTable<Color>, input: &[BrushStroke]) -> BrushPlan {
|
||||
pub fn compute_brush_plan(&self, background: Instance<Image<Color>>, input: &[BrushStroke]) -> BrushPlan {
|
||||
let mut inner = self.inner.lock().unwrap();
|
||||
inner.compute_brush_plan(background, input)
|
||||
}
|
||||
|
||||
pub fn cache_results(&self, input: Vec<BrushStroke>, blended_image: ImageFrameTable<Color>, last_stroke_texture: ImageFrameTable<Color>) {
|
||||
pub fn cache_results(&self, input: Vec<BrushStroke>, blended_image: Instance<Image<Color>>, last_stroke_texture: Instance<Image<Color>>) {
|
||||
let mut inner = self.inner.lock().unwrap();
|
||||
inner.cache_results(input, blended_image, last_stroke_texture)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ use super::discrete_srgb::float_to_srgb_u8;
|
|||
use crate::AlphaBlending;
|
||||
use crate::GraphicElement;
|
||||
use crate::instances::{Instance, Instances};
|
||||
use crate::transform::TransformMut;
|
||||
use alloc::vec::Vec;
|
||||
use core::hash::{Hash, Hasher};
|
||||
use dyn_any::StaticType;
|
||||
|
|
@ -232,7 +231,7 @@ pub fn migrate_image_frame<'de, D: serde::Deserializer<'de>>(deserializer: D) ->
|
|||
fn from(element: GraphicElement) -> Self {
|
||||
match element {
|
||||
GraphicElement::RasterFrame(crate::RasterFrame::ImageFrame(image)) => Self {
|
||||
image: image.one_instance_ref().instance.clone(),
|
||||
image: image.instance_ref_iter().next().unwrap().instance.clone(),
|
||||
},
|
||||
_ => panic!("Expected Image, found {:?}", element),
|
||||
}
|
||||
|
|
@ -270,27 +269,90 @@ pub fn migrate_image_frame<'de, D: serde::Deserializer<'de>>(deserializer: D) ->
|
|||
FormatVersions::OldImageFrame(image_frame_with_transform_and_blending) => {
|
||||
let OldImageFrame { image, transform, alpha_blending } = image_frame_with_transform_and_blending;
|
||||
let mut image_frame_table = ImageFrameTable::new(image);
|
||||
*image_frame_table.one_instance_mut().transform = transform;
|
||||
*image_frame_table.one_instance_mut().alpha_blending = alpha_blending;
|
||||
*image_frame_table.instance_mut_iter().next().unwrap().transform = transform;
|
||||
*image_frame_table.instance_mut_iter().next().unwrap().alpha_blending = alpha_blending;
|
||||
image_frame_table
|
||||
}
|
||||
FormatVersions::ImageFrame(image_frame) => ImageFrameTable::new(image_frame.one_instance_ref().instance.image.clone()),
|
||||
FormatVersions::ImageFrame(image_frame) => ImageFrameTable::new(image_frame.instance_ref_iter().next().unwrap().instance.image.clone()),
|
||||
FormatVersions::ImageFrameTable(image_frame_table) => image_frame_table,
|
||||
})
|
||||
}
|
||||
|
||||
// TODO: Eventually remove this migration document upgrade code
|
||||
pub fn migrate_image_frame_instance<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<Instance<Image<Color>>, D::Error> {
|
||||
use serde::Deserialize;
|
||||
|
||||
#[derive(Clone, Default, Debug, PartialEq, specta::Type)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct ImageFrame<P: Pixel> {
|
||||
pub image: Image<P>,
|
||||
}
|
||||
impl From<ImageFrame<Color>> for GraphicElement {
|
||||
fn from(image_frame: ImageFrame<Color>) -> Self {
|
||||
GraphicElement::RasterFrame(crate::RasterFrame::ImageFrame(ImageFrameTable::new(image_frame.image)))
|
||||
}
|
||||
}
|
||||
impl From<GraphicElement> for ImageFrame<Color> {
|
||||
fn from(element: GraphicElement) -> Self {
|
||||
match element {
|
||||
GraphicElement::RasterFrame(crate::RasterFrame::ImageFrame(image)) => Self {
|
||||
image: image.instance_ref_iter().next().unwrap().instance.clone(),
|
||||
},
|
||||
_ => panic!("Expected Image, found {:?}", element),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "dyn-any")]
|
||||
unsafe impl<P> StaticType for ImageFrame<P>
|
||||
where
|
||||
P: dyn_any::StaticTypeSized + Pixel,
|
||||
P::Static: Pixel,
|
||||
{
|
||||
type Static = ImageFrame<P::Static>;
|
||||
}
|
||||
|
||||
#[derive(Clone, Default, Debug, PartialEq, specta::Type)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct OldImageFrame<P: Pixel> {
|
||||
image: Image<P>,
|
||||
transform: DAffine2,
|
||||
alpha_blending: AlphaBlending,
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
#[serde(untagged)]
|
||||
enum FormatVersions {
|
||||
Image(Image<Color>),
|
||||
OldImageFrame(OldImageFrame<Color>),
|
||||
ImageFrame(Instances<ImageFrame<Color>>),
|
||||
ImageFrameTable(ImageFrameTable<Color>),
|
||||
ImageInstance(Instance<Image<Color>>),
|
||||
}
|
||||
|
||||
Ok(match FormatVersions::deserialize(deserializer)? {
|
||||
FormatVersions::Image(image) => Instance {
|
||||
instance: image,
|
||||
..Default::default()
|
||||
},
|
||||
FormatVersions::OldImageFrame(image_frame_with_transform_and_blending) => Instance {
|
||||
instance: image_frame_with_transform_and_blending.image,
|
||||
transform: image_frame_with_transform_and_blending.transform,
|
||||
alpha_blending: image_frame_with_transform_and_blending.alpha_blending,
|
||||
source_node_id: None,
|
||||
},
|
||||
FormatVersions::ImageFrame(image_frame) => Instance {
|
||||
instance: image_frame.instance_ref_iter().next().unwrap().instance.image.clone(),
|
||||
..Default::default()
|
||||
},
|
||||
FormatVersions::ImageFrameTable(image_frame_table) => image_frame_table.instance_iter().next().unwrap_or_default(),
|
||||
FormatVersions::ImageInstance(image_instance) => image_instance,
|
||||
})
|
||||
}
|
||||
|
||||
// TODO: Rename to ImageTable
|
||||
pub type ImageFrameTable<P> = Instances<Image<P>>;
|
||||
|
||||
/// Construct a 0x0 image frame table. This is useful because ImageFrameTable::default() will return a 1x1 image frame table.
|
||||
impl ImageFrameTable<Color> {
|
||||
pub fn one_empty_image() -> Self {
|
||||
let mut result = Self::new(Image::default());
|
||||
*result.transform_mut() = DAffine2::ZERO;
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: Debug + Copy + Pixel> Sample for Image<P> {
|
||||
type Pixel = P;
|
||||
|
||||
|
|
@ -305,62 +367,6 @@ impl<P: Debug + Copy + Pixel> Sample for Image<P> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<P> Sample for ImageFrameTable<P>
|
||||
where
|
||||
P: Debug + Copy + Pixel,
|
||||
GraphicElement: From<Image<P>>,
|
||||
{
|
||||
type Pixel = P;
|
||||
|
||||
// TODO: Improve sampling logic
|
||||
#[inline(always)]
|
||||
fn sample(&self, pos: DVec2, area: DVec2) -> Option<Self::Pixel> {
|
||||
let image_transform = self.one_instance_ref().transform;
|
||||
let image = self.one_instance_ref().instance;
|
||||
|
||||
let image_size = DVec2::new(image.width() as f64, image.height() as f64);
|
||||
let pos = (DAffine2::from_scale(image_size) * image_transform.inverse()).transform_point2(pos);
|
||||
|
||||
Sample::sample(image, pos, area)
|
||||
}
|
||||
}
|
||||
|
||||
impl<P> Bitmap for ImageFrameTable<P>
|
||||
where
|
||||
P: Copy + Pixel,
|
||||
GraphicElement: From<Image<P>>,
|
||||
{
|
||||
type Pixel = P;
|
||||
|
||||
fn width(&self) -> u32 {
|
||||
let image = self.one_instance_ref().instance;
|
||||
|
||||
image.width()
|
||||
}
|
||||
|
||||
fn height(&self) -> u32 {
|
||||
let image = self.one_instance_ref().instance;
|
||||
|
||||
image.height()
|
||||
}
|
||||
|
||||
fn get_pixel(&self, x: u32, y: u32) -> Option<Self::Pixel> {
|
||||
let image = self.one_instance_ref().instance;
|
||||
|
||||
image.get_pixel(x, y)
|
||||
}
|
||||
}
|
||||
|
||||
impl<P> BitmapMut for ImageFrameTable<P>
|
||||
where
|
||||
P: Copy + Pixel,
|
||||
GraphicElement: From<Image<P>>,
|
||||
{
|
||||
fn get_pixel_mut(&mut self, x: u32, y: u32) -> Option<&mut Self::Pixel> {
|
||||
self.one_instance_mut().instance.get_pixel_mut(x, y)
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: Copy + Pixel> Image<P> {
|
||||
pub fn get_mut(&mut self, x: usize, y: usize) -> &mut P {
|
||||
&mut self.data[y * (self.width as usize) + x]
|
||||
|
|
|
|||
|
|
@ -266,11 +266,11 @@ pub struct DynAnyNode<I, O, Node> {
|
|||
_o: PhantomData<O>,
|
||||
}
|
||||
|
||||
impl<'input, _I, _O, N> Node<'input, Any<'input>> for DynAnyNode<_I, _O, N>
|
||||
impl<'input, I, O, N> Node<'input, Any<'input>> for DynAnyNode<I, O, N>
|
||||
where
|
||||
_I: 'input + dyn_any::StaticType + WasmNotSend,
|
||||
_O: 'input + dyn_any::StaticType + WasmNotSend,
|
||||
N: 'input + Node<'input, _I, Output = DynFuture<'input, _O>>,
|
||||
I: 'input + dyn_any::StaticType + WasmNotSend,
|
||||
O: 'input + dyn_any::StaticType + WasmNotSend,
|
||||
N: 'input + Node<'input, I, Output = DynFuture<'input, O>>,
|
||||
{
|
||||
type Output = FutureAny<'input>;
|
||||
#[inline]
|
||||
|
|
@ -294,11 +294,11 @@ where
|
|||
self.node.serialize()
|
||||
}
|
||||
}
|
||||
impl<'input, _I, _O, N> DynAnyNode<_I, _O, N>
|
||||
impl<'input, I, O, N> DynAnyNode<I, O, N>
|
||||
where
|
||||
_I: 'input + dyn_any::StaticType,
|
||||
_O: 'input + dyn_any::StaticType,
|
||||
N: 'input + Node<'input, _I, Output = DynFuture<'input, _O>>,
|
||||
I: 'input + dyn_any::StaticType,
|
||||
O: 'input + dyn_any::StaticType,
|
||||
N: 'input + Node<'input, I, Output = DynFuture<'input, O>>,
|
||||
{
|
||||
pub const fn new(node: N) -> Self {
|
||||
Self {
|
||||
|
|
|
|||
|
|
@ -129,7 +129,9 @@ mod test {
|
|||
.instance
|
||||
.as_vector_data()
|
||||
.unwrap()
|
||||
.one_instance_ref()
|
||||
.instance_ref_iter()
|
||||
.next()
|
||||
.unwrap()
|
||||
.instance
|
||||
.bounding_box_with_transform(*instanced.transform)
|
||||
.unwrap();
|
||||
|
|
|
|||
|
|
@ -253,9 +253,9 @@ fn isometric_grid_test() {
|
|||
|
||||
// Works properly
|
||||
let grid = grid((), (), GridType::Isometric, 10., (30., 30.).into(), 5, 5);
|
||||
assert_eq!(grid.one_instance_ref().instance.point_domain.ids().len(), 5 * 5);
|
||||
assert_eq!(grid.one_instance_ref().instance.segment_bezier_iter().count(), 4 * 5 + 4 * 9);
|
||||
for (_, bezier, _, _) in grid.one_instance_ref().instance.segment_bezier_iter() {
|
||||
assert_eq!(grid.instance_ref_iter().next().unwrap().instance.point_domain.ids().len(), 5 * 5);
|
||||
assert_eq!(grid.instance_ref_iter().next().unwrap().instance.segment_bezier_iter().count(), 4 * 5 + 4 * 9);
|
||||
for (_, bezier, _, _) in grid.instance_ref_iter().next().unwrap().instance.segment_bezier_iter() {
|
||||
assert_eq!(bezier.handles, bezier_rs::BezierHandles::Linear);
|
||||
assert!(
|
||||
((bezier.start - bezier.end).length() - 10.).abs() < 1e-5,
|
||||
|
|
@ -268,9 +268,9 @@ fn isometric_grid_test() {
|
|||
#[test]
|
||||
fn skew_isometric_grid_test() {
|
||||
let grid = grid((), (), GridType::Isometric, 10., (40., 30.).into(), 5, 5);
|
||||
assert_eq!(grid.one_instance_ref().instance.point_domain.ids().len(), 5 * 5);
|
||||
assert_eq!(grid.one_instance_ref().instance.segment_bezier_iter().count(), 4 * 5 + 4 * 9);
|
||||
for (_, bezier, _, _) in grid.one_instance_ref().instance.segment_bezier_iter() {
|
||||
assert_eq!(grid.instance_ref_iter().next().unwrap().instance.point_domain.ids().len(), 5 * 5);
|
||||
assert_eq!(grid.instance_ref_iter().next().unwrap().instance.segment_bezier_iter().count(), 4 * 5 + 4 * 9);
|
||||
for (_, bezier, _, _) in grid.instance_ref_iter().next().unwrap().instance.segment_bezier_iter() {
|
||||
assert_eq!(bezier.handles, bezier_rs::BezierHandles::Linear);
|
||||
let vector = bezier.start - bezier.end;
|
||||
let angle = (vector.angle_to(DVec2::X).to_degrees() + 180.) % 180.;
|
||||
|
|
|
|||
|
|
@ -60,8 +60,8 @@ pub fn migrate_vector_data<'de, D: serde::Deserializer<'de>>(deserializer: D) ->
|
|||
region_domain: old.region_domain,
|
||||
upstream_graphic_group: old.upstream_graphic_group,
|
||||
});
|
||||
*vector_data_table.one_instance_mut().transform = old.transform;
|
||||
*vector_data_table.one_instance_mut().alpha_blending = old.alpha_blending;
|
||||
*vector_data_table.instance_mut_iter().next().unwrap().transform = old.transform;
|
||||
*vector_data_table.instance_mut_iter().next().unwrap().alpha_blending = old.alpha_blending;
|
||||
vector_data_table
|
||||
}
|
||||
EitherFormat::VectorDataTable(vector_data_table) => vector_data_table,
|
||||
|
|
|
|||
|
|
@ -1193,11 +1193,16 @@ async fn flatten_vector_elements(_: impl Ctx, graphic_group_input: GraphicGroupT
|
|||
}
|
||||
}
|
||||
|
||||
let mut output_table = VectorDataTable::default();
|
||||
let Some(mut output) = output_table.instance_mut_iter().next() else { return output_table };
|
||||
// Create a table with one instance of an empty VectorData, then get a mutable reference to it which we append flattened subpaths to
|
||||
let mut output_table = VectorDataTable::new(VectorData::default());
|
||||
let Some(mut output) = output_table.instance_mut_iter().next() else {
|
||||
return output_table;
|
||||
};
|
||||
|
||||
// Flatten the graphic group input into the output VectorData instance
|
||||
flatten_group(&graphic_group_input, &mut output);
|
||||
|
||||
// Return the single-row VectorDataTable containing the flattened VectorData subpaths
|
||||
output_table
|
||||
}
|
||||
|
||||
|
|
@ -1217,6 +1222,8 @@ async fn sample_points(_: impl Ctx, vector_data: VectorDataTable, spacing: f64,
|
|||
style: std::mem::take(&mut vector_data_instance.instance.style),
|
||||
upstream_graphic_group: std::mem::take(&mut vector_data_instance.instance.upstream_graphic_group),
|
||||
};
|
||||
// Transfer the stroke transform from the input vector data to the result.
|
||||
result.style.set_stroke_transform(vector_data_instance.transform);
|
||||
|
||||
// Using `stroke_bezpath_iter` so that the `subpath_segment_lengths` is aligned to the segments of each bezpath.
|
||||
// So we can index into `subpath_segment_lengths` to get the length of the segments.
|
||||
|
|
@ -1249,10 +1256,6 @@ async fn sample_points(_: impl Ctx, vector_data: VectorDataTable, spacing: f64,
|
|||
result.append_bezpath(sample_bezpath);
|
||||
}
|
||||
|
||||
// Transfer the style from the input vector data to the result.
|
||||
result.style = vector_data_instance.instance.style;
|
||||
result.style.set_stroke_transform(vector_data_instance.transform);
|
||||
|
||||
vector_data_instance.instance = result;
|
||||
result_table.push(vector_data_instance);
|
||||
}
|
||||
|
|
@ -1459,11 +1462,6 @@ async fn spline(_: impl Ctx, vector_data: VectorDataTable) -> VectorDataTable {
|
|||
result_table.push(vector_data_instance);
|
||||
}
|
||||
|
||||
// TODO: remove after pt6 of instance table refactor
|
||||
if result_table.is_empty() {
|
||||
return VectorDataTable::new(VectorData::empty());
|
||||
}
|
||||
|
||||
result_table
|
||||
}
|
||||
|
||||
|
|
@ -1732,14 +1730,14 @@ fn bevel(_: impl Ctx, source: VectorDataTable, #[default(10.)] distance: Length)
|
|||
|
||||
#[node_macro::node(category("Vector"), path(graphene_core::vector))]
|
||||
fn close_path(_: impl Ctx, source: VectorDataTable) -> VectorDataTable {
|
||||
let mut new_table = VectorDataTable::empty();
|
||||
let mut result_table = VectorDataTable::empty();
|
||||
|
||||
for mut source_instance in source.instance_iter() {
|
||||
source_instance.instance.close_subpaths();
|
||||
new_table.push(source_instance);
|
||||
result_table.push(source_instance);
|
||||
}
|
||||
|
||||
new_table
|
||||
result_table
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Vector"), path(graphene_core::vector))]
|
||||
|
|
|
|||
|
|
@ -109,9 +109,7 @@ macro_rules! tagged_value {
|
|||
}
|
||||
pub fn from_type(input: &Type) -> Option<Self> {
|
||||
match input {
|
||||
Type::Generic(_) => {
|
||||
None
|
||||
}
|
||||
Type::Generic(_) => None,
|
||||
Type::Concrete(concrete_type) => {
|
||||
let internal_id = concrete_type.id?;
|
||||
use std::any::TypeId;
|
||||
|
|
|
|||
|
|
@ -1,14 +1,16 @@
|
|||
use crate::raster::{BlendImageTupleNode, blend_image_closure, extend_image_to_bounds};
|
||||
use crate::raster::{empty_image, extend_image_to_bounds};
|
||||
use glam::{DAffine2, DVec2};
|
||||
use graph_craft::generic::FnNode;
|
||||
use graph_craft::proto::FutureWrapperNode;
|
||||
use graphene_core::instances::Instance;
|
||||
use graphene_core::raster::adjustments::blend_colors;
|
||||
use graphene_core::raster::bbox::{AxisAlignedBbox, Bbox};
|
||||
use graphene_core::raster::brush_cache::BrushCache;
|
||||
use graphene_core::raster::image::{Image, ImageFrameTable};
|
||||
use graphene_core::raster::{Alpha, Bitmap, BlendMode, Color, Pixel, Sample};
|
||||
use graphene_core::transform::{Transform, TransformMut};
|
||||
use graphene_core::value::{ClonedNode, ValueNode};
|
||||
use graphene_core::raster::{Alpha, BitmapMut, BlendMode, Color, Pixel, Sample};
|
||||
use graphene_core::renderer::GraphicElementRendered;
|
||||
use graphene_core::transform::Transform;
|
||||
use graphene_core::value::ClonedNode;
|
||||
use graphene_core::vector::VectorDataTable;
|
||||
use graphene_core::vector::brush_stroke::{BrushStroke, BrushStyle};
|
||||
use graphene_core::{Ctx, GraphicElement, Node};
|
||||
|
|
@ -31,12 +33,6 @@ impl<P: Pixel + Alpha> Transform for BrushStampGenerator<P> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<P: Pixel + Alpha> TransformMut for BrushStampGenerator<P> {
|
||||
fn transform_mut(&mut self) -> &mut DAffine2 {
|
||||
&mut self.transform
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: Pixel + Alpha> Sample for BrushStampGenerator<P> {
|
||||
type Pixel = P;
|
||||
|
||||
|
|
@ -139,93 +135,78 @@ where
|
|||
pub async fn create_brush_texture(brush_style: &BrushStyle) -> Image<Color> {
|
||||
let stamp = brush_stamp_generator(brush_style.diameter, brush_style.color, brush_style.hardness, brush_style.flow);
|
||||
let transform = DAffine2::from_scale_angle_translation(DVec2::splat(brush_style.diameter), 0., -DVec2::splat(brush_style.diameter / 2.));
|
||||
use crate::raster::empty_image;
|
||||
let blank_texture = empty_image((), transform, Color::TRANSPARENT);
|
||||
let image = crate::raster::blend_image_closure(stamp, blank_texture, |a, b| blend_colors(a, b, BlendMode::Normal, 1.));
|
||||
let blank_texture = empty_image((), transform, Color::TRANSPARENT).instance_iter().next().unwrap_or_default();
|
||||
let image = blend_stamp_closure(stamp, blank_texture, |a, b| blend_colors(a, b, BlendMode::Normal, 1.));
|
||||
|
||||
image.one_instance_ref().instance.clone()
|
||||
image.instance
|
||||
}
|
||||
|
||||
macro_rules! inline_blend_funcs {
|
||||
($bg:ident, $fg:ident, $blend_mode:ident, $opacity:ident, [$($mode:path,)*]) => {
|
||||
match std::hint::black_box($blend_mode) {
|
||||
$(
|
||||
$mode => {
|
||||
blend_image_closure($fg, $bg, |a, b| blend_colors(a, b, $mode, $opacity))
|
||||
}
|
||||
)*
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub fn blend_with_mode(background: ImageFrameTable<Color>, foreground: ImageFrameTable<Color>, blend_mode: BlendMode, opacity: f64) -> ImageFrameTable<Color> {
|
||||
pub fn blend_with_mode(background: Instance<Image<Color>>, foreground: Instance<Image<Color>>, blend_mode: BlendMode, opacity: f64) -> Instance<Image<Color>> {
|
||||
let opacity = opacity / 100.;
|
||||
inline_blend_funcs!(
|
||||
background,
|
||||
foreground,
|
||||
blend_mode,
|
||||
opacity,
|
||||
[
|
||||
// Normal group
|
||||
BlendMode::Normal,
|
||||
// Darken group
|
||||
BlendMode::Darken,
|
||||
BlendMode::Multiply,
|
||||
BlendMode::ColorBurn,
|
||||
BlendMode::LinearBurn,
|
||||
BlendMode::DarkerColor,
|
||||
// Lighten group
|
||||
BlendMode::Lighten,
|
||||
BlendMode::Screen,
|
||||
BlendMode::ColorDodge,
|
||||
BlendMode::LinearDodge,
|
||||
BlendMode::LighterColor,
|
||||
// Contrast group
|
||||
BlendMode::Overlay,
|
||||
BlendMode::SoftLight,
|
||||
BlendMode::HardLight,
|
||||
BlendMode::VividLight,
|
||||
BlendMode::LinearLight,
|
||||
BlendMode::PinLight,
|
||||
BlendMode::HardMix,
|
||||
// Inversion group
|
||||
BlendMode::Difference,
|
||||
BlendMode::Exclusion,
|
||||
BlendMode::Subtract,
|
||||
BlendMode::Divide,
|
||||
// Component group
|
||||
BlendMode::Hue,
|
||||
BlendMode::Saturation,
|
||||
BlendMode::Color,
|
||||
BlendMode::Luminosity,
|
||||
// Other utility blend modes (hidden from the normal list)
|
||||
BlendMode::Erase,
|
||||
BlendMode::Restore,
|
||||
BlendMode::MultiplyAlpha,
|
||||
]
|
||||
)
|
||||
match std::hint::black_box(blend_mode) {
|
||||
// Normal group
|
||||
BlendMode::Normal => blend_image_closure(foreground, background, |a, b| blend_colors(a, b, BlendMode::Normal, opacity)),
|
||||
// Darken group
|
||||
BlendMode::Darken => blend_image_closure(foreground, background, |a, b| blend_colors(a, b, BlendMode::Darken, opacity)),
|
||||
BlendMode::Multiply => blend_image_closure(foreground, background, |a, b| blend_colors(a, b, BlendMode::Multiply, opacity)),
|
||||
BlendMode::ColorBurn => blend_image_closure(foreground, background, |a, b| blend_colors(a, b, BlendMode::ColorBurn, opacity)),
|
||||
BlendMode::LinearBurn => blend_image_closure(foreground, background, |a, b| blend_colors(a, b, BlendMode::LinearBurn, opacity)),
|
||||
BlendMode::DarkerColor => blend_image_closure(foreground, background, |a, b| blend_colors(a, b, BlendMode::DarkerColor, opacity)),
|
||||
// Lighten group
|
||||
BlendMode::Lighten => blend_image_closure(foreground, background, |a, b| blend_colors(a, b, BlendMode::Lighten, opacity)),
|
||||
BlendMode::Screen => blend_image_closure(foreground, background, |a, b| blend_colors(a, b, BlendMode::Screen, opacity)),
|
||||
BlendMode::ColorDodge => blend_image_closure(foreground, background, |a, b| blend_colors(a, b, BlendMode::ColorDodge, opacity)),
|
||||
BlendMode::LinearDodge => blend_image_closure(foreground, background, |a, b| blend_colors(a, b, BlendMode::LinearDodge, opacity)),
|
||||
BlendMode::LighterColor => blend_image_closure(foreground, background, |a, b| blend_colors(a, b, BlendMode::LighterColor, opacity)),
|
||||
// Contrast group
|
||||
BlendMode::Overlay => blend_image_closure(foreground, background, |a, b| blend_colors(a, b, BlendMode::Overlay, opacity)),
|
||||
BlendMode::SoftLight => blend_image_closure(foreground, background, |a, b| blend_colors(a, b, BlendMode::SoftLight, opacity)),
|
||||
BlendMode::HardLight => blend_image_closure(foreground, background, |a, b| blend_colors(a, b, BlendMode::HardLight, opacity)),
|
||||
BlendMode::VividLight => blend_image_closure(foreground, background, |a, b| blend_colors(a, b, BlendMode::VividLight, opacity)),
|
||||
BlendMode::LinearLight => blend_image_closure(foreground, background, |a, b| blend_colors(a, b, BlendMode::LinearLight, opacity)),
|
||||
BlendMode::PinLight => blend_image_closure(foreground, background, |a, b| blend_colors(a, b, BlendMode::PinLight, opacity)),
|
||||
BlendMode::HardMix => blend_image_closure(foreground, background, |a, b| blend_colors(a, b, BlendMode::HardMix, opacity)),
|
||||
// Inversion group
|
||||
BlendMode::Difference => blend_image_closure(foreground, background, |a, b| blend_colors(a, b, BlendMode::Difference, opacity)),
|
||||
BlendMode::Exclusion => blend_image_closure(foreground, background, |a, b| blend_colors(a, b, BlendMode::Exclusion, opacity)),
|
||||
BlendMode::Subtract => blend_image_closure(foreground, background, |a, b| blend_colors(a, b, BlendMode::Subtract, opacity)),
|
||||
BlendMode::Divide => blend_image_closure(foreground, background, |a, b| blend_colors(a, b, BlendMode::Divide, opacity)),
|
||||
// Component group
|
||||
BlendMode::Hue => blend_image_closure(foreground, background, |a, b| blend_colors(a, b, BlendMode::Hue, opacity)),
|
||||
BlendMode::Saturation => blend_image_closure(foreground, background, |a, b| blend_colors(a, b, BlendMode::Saturation, opacity)),
|
||||
BlendMode::Color => blend_image_closure(foreground, background, |a, b| blend_colors(a, b, BlendMode::Color, opacity)),
|
||||
BlendMode::Luminosity => blend_image_closure(foreground, background, |a, b| blend_colors(a, b, BlendMode::Luminosity, opacity)),
|
||||
// Other utility blend modes (hidden from the normal list)
|
||||
BlendMode::Erase => blend_image_closure(foreground, background, |a, b| blend_colors(a, b, BlendMode::Erase, opacity)),
|
||||
BlendMode::Restore => blend_image_closure(foreground, background, |a, b| blend_colors(a, b, BlendMode::Restore, opacity)),
|
||||
BlendMode::MultiplyAlpha => blend_image_closure(foreground, background, |a, b| blend_colors(a, b, BlendMode::MultiplyAlpha, opacity)),
|
||||
}
|
||||
}
|
||||
|
||||
#[node_macro::node(category(""))]
|
||||
async fn brush(_: impl Ctx, image_frame_table: ImageFrameTable<Color>, bounds: ImageFrameTable<Color>, strokes: Vec<BrushStroke>, cache: BrushCache) -> ImageFrameTable<Color> {
|
||||
#[node_macro::node(category("Raster"))]
|
||||
async fn brush(_: impl Ctx, mut image_frame_table: ImageFrameTable<Color>, strokes: Vec<BrushStroke>, cache: BrushCache) -> ImageFrameTable<Color> {
|
||||
// TODO: Find a way to handle more than one instance
|
||||
let Some(image_frame_instance) = image_frame_table.instance_ref_iter().next() else {
|
||||
return ImageFrameTable::default();
|
||||
};
|
||||
let image_frame_instance = image_frame_instance.to_instance_cloned();
|
||||
|
||||
let [start, end] = image_frame_instance.clone().to_table().bounding_box(DAffine2::IDENTITY, false).unwrap_or([DVec2::ZERO, DVec2::ZERO]);
|
||||
let image_bbox = AxisAlignedBbox { start, end };
|
||||
let stroke_bbox = strokes.iter().map(|s| s.bounding_box()).reduce(|a, b| a.union(&b)).unwrap_or(AxisAlignedBbox::ZERO);
|
||||
let image_bbox = Bbox::from_transform(image_frame_table.transform()).to_axis_aligned_bbox();
|
||||
let bbox = if image_bbox.size().length() < 0.1 { stroke_bbox } else { stroke_bbox.union(&image_bbox) };
|
||||
let background_bounds = bbox.to_transform();
|
||||
|
||||
let mut draw_strokes: Vec<_> = strokes.iter().filter(|&s| !matches!(s.style.blend_mode, BlendMode::Erase | BlendMode::Restore)).cloned().collect();
|
||||
let erase_restore_strokes: Vec<_> = strokes.iter().filter(|&s| matches!(s.style.blend_mode, BlendMode::Erase | BlendMode::Restore)).cloned().collect();
|
||||
|
||||
let mut brush_plan = cache.compute_brush_plan(image_frame_table, &draw_strokes);
|
||||
let mut brush_plan = cache.compute_brush_plan(image_frame_instance, &draw_strokes);
|
||||
|
||||
let mut background_bounds = bbox.to_transform();
|
||||
// TODO: Find a way to handle more than one instance
|
||||
let Some(mut actual_image) = extend_image_to_bounds((), brush_plan.background.to_table(), background_bounds).instance_iter().next() else {
|
||||
return ImageFrameTable::default();
|
||||
};
|
||||
|
||||
// If the bounds are empty (no size on images or det(transform) = 0), keep the target bounds
|
||||
let bounds_empty = bounds.instance_ref_iter().all(|bounds| bounds.instance.width() == 0 || bounds.instance.height() == 0);
|
||||
if bounds.transform().matrix2.determinant() != 0. && !bounds_empty {
|
||||
background_bounds = bounds.transform();
|
||||
}
|
||||
|
||||
let mut actual_image = extend_image_to_bounds((), brush_plan.background, background_bounds);
|
||||
let final_stroke_idx = brush_plan.strokes.len().saturating_sub(1);
|
||||
for (idx, stroke) in brush_plan.strokes.into_iter().enumerate() {
|
||||
// Create brush texture.
|
||||
|
|
@ -262,14 +243,16 @@ async fn brush(_: impl Ctx, image_frame_table: ImageFrameTable<Color>, bounds: I
|
|||
);
|
||||
let blit_target = if idx == 0 {
|
||||
let target = core::mem::take(&mut brush_plan.first_stroke_texture);
|
||||
extend_image_to_bounds((), target, stroke_to_layer)
|
||||
extend_image_to_bounds((), target.to_table(), stroke_to_layer)
|
||||
} else {
|
||||
use crate::raster::empty_image;
|
||||
empty_image((), stroke_to_layer, Color::TRANSPARENT)
|
||||
// EmptyImageNode::new(CopiedNode::new(stroke_to_layer), CopiedNode::new(Color::TRANSPARENT)).eval(())
|
||||
};
|
||||
|
||||
blit_node.eval(blit_target).await
|
||||
let instances = blit_node.eval(blit_target).await;
|
||||
assert_eq!(instances.len(), 1);
|
||||
instances.instance_iter().next().unwrap_or_default()
|
||||
};
|
||||
|
||||
// Cache image before doing final blend, and store final stroke texture.
|
||||
|
|
@ -284,9 +267,11 @@ async fn brush(_: impl Ctx, image_frame_table: ImageFrameTable<Color>, bounds: I
|
|||
let has_erase_strokes = strokes.iter().any(|s| s.style.blend_mode == BlendMode::Erase);
|
||||
if has_erase_strokes {
|
||||
let opaque_image = Image::new(bbox.size().x as u32, bbox.size().y as u32, Color::WHITE);
|
||||
let mut erase_restore_mask = ImageFrameTable::new(opaque_image);
|
||||
*erase_restore_mask.transform_mut() = background_bounds;
|
||||
*erase_restore_mask.one_instance_mut().alpha_blending = Default::default();
|
||||
let mut erase_restore_mask = Instance {
|
||||
instance: opaque_image,
|
||||
transform: background_bounds,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
for stroke in erase_restore_strokes {
|
||||
let mut brush_texture = cache.get_cached_brush(&stroke.style);
|
||||
|
|
@ -306,7 +291,7 @@ async fn brush(_: impl Ctx, image_frame_table: ImageFrameTable<Color>, bounds: I
|
|||
FutureWrapperNode::new(ClonedNode::new(positions)),
|
||||
FutureWrapperNode::new(ClonedNode::new(blend_params)),
|
||||
);
|
||||
erase_restore_mask = blit_node.eval(erase_restore_mask).await;
|
||||
erase_restore_mask = blit_node.eval(erase_restore_mask.to_table()).await.instance_iter().next().unwrap_or_default();
|
||||
}
|
||||
// Yes, this is essentially the same as the above, but we duplicate to inline the blend mode.
|
||||
BlendMode::Restore => {
|
||||
|
|
@ -316,25 +301,87 @@ async fn brush(_: impl Ctx, image_frame_table: ImageFrameTable<Color>, bounds: I
|
|||
FutureWrapperNode::new(ClonedNode::new(positions)),
|
||||
FutureWrapperNode::new(ClonedNode::new(blend_params)),
|
||||
);
|
||||
erase_restore_mask = blit_node.eval(erase_restore_mask).await;
|
||||
erase_restore_mask = blit_node.eval(erase_restore_mask.to_table()).await.instance_iter().next().unwrap_or_default();
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
let blend_params = FnNode::new(|(a, b)| blend_colors(a, b, BlendMode::MultiplyAlpha, 1.));
|
||||
let blend_executor = BlendImageTupleNode::new(FutureWrapperNode::new(ValueNode::new(blend_params)));
|
||||
actual_image = blend_executor.eval((actual_image, erase_restore_mask)).await;
|
||||
actual_image = blend_image_closure(erase_restore_mask, actual_image, |a, b| blend_params.eval((a, b)));
|
||||
}
|
||||
|
||||
actual_image
|
||||
let first_row = image_frame_table.instance_mut_iter().next().unwrap();
|
||||
*first_row.instance = actual_image.instance;
|
||||
*first_row.transform = actual_image.transform;
|
||||
*first_row.alpha_blending = actual_image.alpha_blending;
|
||||
*first_row.source_node_id = actual_image.source_node_id;
|
||||
|
||||
image_frame_table
|
||||
}
|
||||
|
||||
pub fn blend_image_closure(foreground: Instance<Image<Color>>, mut background: Instance<Image<Color>>, map_fn: impl Fn(Color, Color) -> Color) -> Instance<Image<Color>> {
|
||||
let foreground_size = DVec2::new(foreground.instance.width as f64, foreground.instance.height as f64);
|
||||
let background_size = DVec2::new(background.instance.width as f64, background.instance.height as f64);
|
||||
|
||||
// Transforms a point from the background image to the foreground image
|
||||
let background_to_foreground = DAffine2::from_scale(foreground_size) * foreground.transform.inverse() * background.transform * DAffine2::from_scale(1. / background_size);
|
||||
|
||||
// Footprint of the foreground image (0, 0)..(1, 1) in the background image space
|
||||
let background_aabb = Bbox::unit().affine_transform(background.transform.inverse() * foreground.transform).to_axis_aligned_bbox();
|
||||
|
||||
// Clamp the foreground image to the background image
|
||||
let start = (background_aabb.start * background_size).max(DVec2::ZERO).as_uvec2();
|
||||
let end = (background_aabb.end * background_size).min(background_size).as_uvec2();
|
||||
|
||||
for y in start.y..end.y {
|
||||
for x in start.x..end.x {
|
||||
let background_point = DVec2::new(x as f64, y as f64);
|
||||
let foreground_point = background_to_foreground.transform_point2(background_point);
|
||||
|
||||
let source_pixel = foreground.instance.sample(foreground_point);
|
||||
let Some(destination_pixel) = background.instance.get_pixel_mut(x, y) else { continue };
|
||||
|
||||
*destination_pixel = map_fn(source_pixel, *destination_pixel);
|
||||
}
|
||||
}
|
||||
|
||||
background
|
||||
}
|
||||
|
||||
pub fn blend_stamp_closure(foreground: BrushStampGenerator<Color>, mut background: Instance<Image<Color>>, map_fn: impl Fn(Color, Color) -> Color) -> Instance<Image<Color>> {
|
||||
let background_size = DVec2::new(background.instance.width as f64, background.instance.height as f64);
|
||||
|
||||
// Transforms a point from the background image to the foreground image
|
||||
let background_to_foreground = background.transform * DAffine2::from_scale(1. / background_size);
|
||||
|
||||
// Footprint of the foreground image (0, 0)..(1, 1) in the background image space
|
||||
let background_aabb = Bbox::unit().affine_transform(background.transform.inverse() * foreground.transform).to_axis_aligned_bbox();
|
||||
|
||||
// Clamp the foreground image to the background image
|
||||
let start = (background_aabb.start * background_size).max(DVec2::ZERO).as_uvec2();
|
||||
let end = (background_aabb.end * background_size).min(background_size).as_uvec2();
|
||||
|
||||
let area = background_to_foreground.transform_point2(DVec2::new(1., 1.)) - background_to_foreground.transform_point2(DVec2::ZERO);
|
||||
for y in start.y..end.y {
|
||||
for x in start.x..end.x {
|
||||
let background_point = DVec2::new(x as f64, y as f64);
|
||||
let foreground_point = background_to_foreground.transform_point2(background_point);
|
||||
|
||||
let Some(source_pixel) = foreground.sample(foreground_point, area) else { continue };
|
||||
let Some(destination_pixel) = background.instance.get_pixel_mut(x, y) else { continue };
|
||||
|
||||
*destination_pixel = map_fn(source_pixel, *destination_pixel);
|
||||
}
|
||||
}
|
||||
|
||||
background
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use glam::DAffine2;
|
||||
use graphene_core::raster::Bitmap;
|
||||
use graphene_core::transform::Transform;
|
||||
|
||||
#[test]
|
||||
|
|
@ -350,8 +397,7 @@ mod test {
|
|||
async fn test_brush_output_size() {
|
||||
let image = brush(
|
||||
(),
|
||||
ImageFrameTable::<Color>::default(),
|
||||
ImageFrameTable::<Color>::default(),
|
||||
ImageFrameTable::<Color>::new(Image::<Color>::default()),
|
||||
vec![BrushStroke {
|
||||
trace: vec![crate::vector::brush_stroke::BrushInputSample { position: DVec2::ZERO }],
|
||||
style: BrushStyle {
|
||||
|
|
@ -366,6 +412,6 @@ mod test {
|
|||
BrushCache::new_proto(),
|
||||
)
|
||||
.await;
|
||||
assert_eq!(image.width(), 20);
|
||||
assert_eq!(image.instance_ref_iter().next().unwrap().instance.width, 20);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ async fn blur(
|
|||
gamma: bool,
|
||||
) -> ImageFrameTable<Color> {
|
||||
let mut result_table = ImageFrameTable::empty();
|
||||
|
||||
for mut image_instance in image_frame.instance_iter() {
|
||||
let image = image_instance.instance.clone();
|
||||
|
||||
|
|
@ -36,6 +37,7 @@ async fn blur(
|
|||
image_instance.source_node_id = None;
|
||||
result_table.push(image_instance);
|
||||
}
|
||||
|
||||
result_table
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ async fn compile_gpu<'a: 'n>(_: impl Ctx, node: &'a DocumentNode, typing_context
|
|||
#[node_macro::node(category("Debug: GPU"))]
|
||||
async fn blend_gpu_image(_: impl Ctx, foreground: ImageFrameTable<Color>, background: ImageFrameTable<Color>, blend_mode: BlendMode, opacity: f64) -> ImageFrameTable<Color> {
|
||||
let mut result_table = ImageFrameTable::empty();
|
||||
|
||||
for (foreground_instance, mut background_instance) in foreground.instance_iter().zip(background.instance_iter()) {
|
||||
let foreground_transform = foreground_instance.transform;
|
||||
let background_transform = background_instance.transform;
|
||||
|
|
@ -94,7 +95,7 @@ async fn blend_gpu_image(_: impl Ctx, foreground: ImageFrameTable<Color>, backgr
|
|||
let proto_networks: Result<Vec<_>, _> = compiler.compile(network.clone()).collect();
|
||||
let Ok(proto_networks_result) = proto_networks else {
|
||||
log::error!("Error compiling network in 'blend_gpu_image()");
|
||||
return ImageFrameTable::one_empty_image();
|
||||
return ImageFrameTable::default();
|
||||
};
|
||||
let proto_networks = proto_networks_result;
|
||||
log::debug!("compiling shader");
|
||||
|
|
@ -215,6 +216,7 @@ async fn blend_gpu_image(_: impl Ctx, foreground: ImageFrameTable<Color>, backgr
|
|||
background_instance.source_node_id = None;
|
||||
result_table.push(background_instance);
|
||||
}
|
||||
|
||||
result_table
|
||||
}
|
||||
|
||||
|
|
@ -241,7 +243,7 @@ async fn blend_gpu_image(_: impl Ctx, foreground: ImageFrameTable<Color>, backgr
|
|||
// #[node_macro::old_node_impl(MapGpuNode)]
|
||||
// async fn map_gpu<'a: 'input>(image: ImageFrameTable<Color>, node: DocumentNode, editor_api: &'a graphene_core::application_io::EditorApi<WasmApplicationIo>) -> ImageFrameTable<Color> {
|
||||
// let image_frame_table = ℑ
|
||||
// let image = image.one_instance_ref().instance;
|
||||
// let image = image.instance_ref_iter().next().unwrap().instance;
|
||||
|
||||
// log::debug!("Executing gpu node");
|
||||
// let executor = &editor_api.application_io.as_ref().and_then(|io| io.gpu_executor()).unwrap();
|
||||
|
|
@ -256,7 +258,7 @@ async fn blend_gpu_image(_: impl Ctx, foreground: ImageFrameTable<Color>, backgr
|
|||
// let name = "placeholder".to_string();
|
||||
// let Ok(compute_pass_descriptor) = create_compute_pass_descriptor(node, image_frame_table, executor).await else {
|
||||
// log::error!("Error creating compute pass descriptor in 'map_gpu()");
|
||||
// return ImageFrameTable::one_empty_image();
|
||||
// return ImageFrameTable::default();
|
||||
// };
|
||||
// self.cache.lock().as_mut().unwrap().insert(name, compute_pass_descriptor.clone());
|
||||
// log::error!("created compute pass");
|
||||
|
|
@ -292,7 +294,7 @@ async fn blend_gpu_image(_: impl Ctx, foreground: ImageFrameTable<Color>, backgr
|
|||
// };
|
||||
// let mut result = ImageFrameTable::new(new_image);
|
||||
// *result.transform_mut() = image_frame_table.transform();
|
||||
// *result.one_instance_mut().alpha_blending = *image_frame_table.one_instance_ref().alpha_blending;
|
||||
// *result.instance_mut_iter().next().unwrap().alpha_blending = *image_frame_table.instance_ref_iter().next().unwrap().alpha_blending;
|
||||
|
||||
// result
|
||||
// }
|
||||
|
|
@ -312,7 +314,7 @@ async fn blend_gpu_image(_: impl Ctx, foreground: ImageFrameTable<Color>, backgr
|
|||
// GraphicElement: From<Image<T>>,
|
||||
// T::Static: Pixel,
|
||||
// {
|
||||
// let image = image.one_instance_ref().instance;
|
||||
// let image = image.instance_ref_iter().next().unwrap().instance;
|
||||
|
||||
// let compiler = graph_craft::graphene_compiler::Compiler {};
|
||||
// let inner_network = NodeNetwork::value_network(node);
|
||||
|
|
|
|||
|
|
@ -1,13 +1,12 @@
|
|||
use dyn_any::DynAny;
|
||||
use fastnoise_lite;
|
||||
use glam::{DAffine2, DVec2, Vec2};
|
||||
use graphene_core::instances::Instance;
|
||||
use graphene_core::raster::bbox::Bbox;
|
||||
use graphene_core::raster::image::{Image, ImageFrameTable};
|
||||
use graphene_core::raster::{
|
||||
Alpha, AlphaMut, Bitmap, BitmapMut, CellularDistanceFunction, CellularReturnType, DomainWarpType, FractalType, LinearChannel, Luminance, NoiseType, Pixel, RGBMut, Sample,
|
||||
};
|
||||
use graphene_core::transform::{Transform, TransformMut};
|
||||
use graphene_core::{AlphaBlending, Color, Ctx, ExtractFootprint, GraphicElement, Node};
|
||||
use graphene_core::raster::{Alpha, AlphaMut, Bitmap, BitmapMut, CellularDistanceFunction, CellularReturnType, Channel, DomainWarpType, FractalType, LinearChannel, Luminance, NoiseType, RGBMut};
|
||||
use graphene_core::transform::Transform;
|
||||
use graphene_core::{AlphaBlending, Color, Ctx, ExtractFootprint};
|
||||
use rand::prelude::*;
|
||||
use rand_chacha::ChaCha8Rng;
|
||||
use std::fmt::Debug;
|
||||
|
|
@ -89,172 +88,146 @@ fn sample_image(ctx: impl ExtractFootprint + Clone + Send, image_frame: ImageFra
|
|||
result_table.push(image_frame_instance)
|
||||
}
|
||||
|
||||
// TODO: Remove when we've completed part 6 of the instance tables refactor
|
||||
if result_table.is_empty() {
|
||||
return ImageFrameTable::one_empty_image();
|
||||
result_table
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Raster"))]
|
||||
fn combine_channels(
|
||||
_: impl Ctx,
|
||||
_primary: (),
|
||||
#[expose] red: ImageFrameTable<Color>,
|
||||
#[expose] green: ImageFrameTable<Color>,
|
||||
#[expose] blue: ImageFrameTable<Color>,
|
||||
#[expose] alpha: ImageFrameTable<Color>,
|
||||
) -> ImageFrameTable<Color> {
|
||||
let mut result_table = ImageFrameTable::empty();
|
||||
|
||||
let max_len = red.len().max(green.len()).max(blue.len()).max(alpha.len());
|
||||
let red = red.instance_iter().map(Some).chain(std::iter::repeat(None)).take(max_len);
|
||||
let green = green.instance_iter().map(Some).chain(std::iter::repeat(None)).take(max_len);
|
||||
let blue = blue.instance_iter().map(Some).chain(std::iter::repeat(None)).take(max_len);
|
||||
let alpha = alpha.instance_iter().map(Some).chain(std::iter::repeat(None)).take(max_len);
|
||||
|
||||
for (((red, green), blue), alpha) in red.zip(green).zip(blue).zip(alpha) {
|
||||
// Turn any default zero-sized image instances into None
|
||||
let red = red.filter(|i| i.instance.width > 0 && i.instance.height > 0);
|
||||
let green = green.filter(|i| i.instance.width > 0 && i.instance.height > 0);
|
||||
let blue = blue.filter(|i| i.instance.width > 0 && i.instance.height > 0);
|
||||
let alpha = alpha.filter(|i| i.instance.width > 0 && i.instance.height > 0);
|
||||
|
||||
// Get this instance's transform and alpha blending mode from the first non-empty channel
|
||||
let Some((transform, alpha_blending)) = [&red, &green, &blue, &alpha].iter().find_map(|i| i.as_ref()).map(|i| (i.transform, i.alpha_blending)) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
// Get the common width and height of the channels, which must have equal dimensions
|
||||
let channel_dimensions = [
|
||||
red.as_ref().map(|r| (r.instance.width, r.instance.height)),
|
||||
green.as_ref().map(|g| (g.instance.width, g.instance.height)),
|
||||
blue.as_ref().map(|b| (b.instance.width, b.instance.height)),
|
||||
alpha.as_ref().map(|a| (a.instance.width, a.instance.height)),
|
||||
];
|
||||
if channel_dimensions.iter().all(Option::is_none)
|
||||
|| channel_dimensions
|
||||
.iter()
|
||||
.flatten()
|
||||
.any(|&(x, y)| channel_dimensions.iter().flatten().any(|&(other_x, other_y)| x != other_x || y != other_y))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
let Some(&(width, height)) = channel_dimensions.iter().flatten().next() else { continue };
|
||||
|
||||
// Create a new image for this instance output
|
||||
let mut image = Image::new(width, height, Color::TRANSPARENT);
|
||||
|
||||
// Iterate over all pixels in the image and set the color channels
|
||||
for y in 0..image.height() {
|
||||
for x in 0..image.width() {
|
||||
let image_pixel = image.get_pixel_mut(x, y).unwrap();
|
||||
|
||||
if let Some(r) = red.as_ref().and_then(|r| r.instance.get_pixel(x, y)) {
|
||||
image_pixel.set_red(r.l().cast_linear_channel());
|
||||
} else {
|
||||
image_pixel.set_red(Channel::from_linear(0.));
|
||||
}
|
||||
if let Some(g) = green.as_ref().and_then(|g| g.instance.get_pixel(x, y)) {
|
||||
image_pixel.set_green(g.l().cast_linear_channel());
|
||||
} else {
|
||||
image_pixel.set_green(Channel::from_linear(0.));
|
||||
}
|
||||
if let Some(b) = blue.as_ref().and_then(|b| b.instance.get_pixel(x, y)) {
|
||||
image_pixel.set_blue(b.l().cast_linear_channel());
|
||||
} else {
|
||||
image_pixel.set_blue(Channel::from_linear(0.));
|
||||
}
|
||||
if let Some(a) = alpha.as_ref().and_then(|a| a.instance.get_pixel(x, y)) {
|
||||
image_pixel.set_alpha(a.l().cast_linear_channel());
|
||||
} else {
|
||||
image_pixel.set_alpha(Channel::from_linear(1.));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add this instance to the result table
|
||||
result_table.push(Instance {
|
||||
instance: image,
|
||||
transform,
|
||||
alpha_blending,
|
||||
source_node_id: None,
|
||||
});
|
||||
}
|
||||
|
||||
result_table
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Raster"))]
|
||||
fn combine_channels<_I, Red, Green, Blue, Alpha>(
|
||||
_: impl Ctx,
|
||||
_primary: (),
|
||||
#[implementations(ImageFrameTable<Color>)] red: Red,
|
||||
#[implementations(ImageFrameTable<Color>)] green: Green,
|
||||
#[implementations(ImageFrameTable<Color>)] blue: Blue,
|
||||
#[implementations(ImageFrameTable<Color>)] alpha: Alpha,
|
||||
) -> ImageFrameTable<Color>
|
||||
where
|
||||
_I: Pixel + Luminance,
|
||||
Red: Bitmap<Pixel = _I>,
|
||||
Green: Bitmap<Pixel = _I>,
|
||||
Blue: Bitmap<Pixel = _I>,
|
||||
Alpha: Bitmap<Pixel = _I>,
|
||||
{
|
||||
let dimensions = [red.dim(), green.dim(), blue.dim(), alpha.dim()];
|
||||
if dimensions.iter().any(|&(x, y)| x == 0 || y == 0) || dimensions.iter().any(|&(x, y)| dimensions.iter().any(|&(other_x, other_y)| x != other_x || y != other_y)) {
|
||||
return ImageFrameTable::one_empty_image();
|
||||
}
|
||||
|
||||
let mut image = Image::new(red.width(), red.height(), Color::TRANSPARENT);
|
||||
|
||||
for y in 0..image.height() {
|
||||
for x in 0..image.width() {
|
||||
let image_pixel = image.get_pixel_mut(x, y).unwrap();
|
||||
if let Some(r) = red.get_pixel(x, y) {
|
||||
image_pixel.set_red(r.l().cast_linear_channel());
|
||||
}
|
||||
if let Some(g) = green.get_pixel(x, y) {
|
||||
image_pixel.set_green(g.l().cast_linear_channel());
|
||||
}
|
||||
if let Some(b) = blue.get_pixel(x, y) {
|
||||
image_pixel.set_blue(b.l().cast_linear_channel());
|
||||
}
|
||||
if let Some(a) = alpha.get_pixel(x, y) {
|
||||
image_pixel.set_alpha(a.l().cast_linear_channel());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ImageFrameTable::new(image)
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Raster"))]
|
||||
fn mask<_P, _S, Input, Stencil>(
|
||||
fn mask(
|
||||
_: impl Ctx,
|
||||
/// The image to be masked.
|
||||
#[implementations(ImageFrameTable<Color>)]
|
||||
mut image: Input,
|
||||
image: ImageFrameTable<Color>,
|
||||
/// The stencil to be used for masking.
|
||||
#[implementations(ImageFrameTable<Color>)]
|
||||
#[expose]
|
||||
stencil: Stencil,
|
||||
) -> Input
|
||||
where
|
||||
// _P is the color of the input image. It must have an alpha channel because that is going to be modified by the mask.
|
||||
_P: Alpha,
|
||||
// _S is the color of the stencil. It must have a luminance channel because that is used to mask the input image.
|
||||
_S: Luminance,
|
||||
// Input image
|
||||
Input: Transform + BitmapMut<Pixel = _P>,
|
||||
// Stencil
|
||||
Stencil: Transform + Sample<Pixel = _S>,
|
||||
{
|
||||
let image_size = DVec2::new(image.width() as f64, image.height() as f64);
|
||||
let mask_size = stencil.transform().decompose_scale();
|
||||
|
||||
if mask_size == DVec2::ZERO {
|
||||
stencil: ImageFrameTable<Color>,
|
||||
) -> ImageFrameTable<Color> {
|
||||
// TODO: Support multiple stencil instances
|
||||
let Some(stencil_instance) = stencil.instance_iter().next() else {
|
||||
// No stencil provided so we return the original image
|
||||
return image;
|
||||
}
|
||||
};
|
||||
let stencil_size = DVec2::new(stencil_instance.instance.width as f64, stencil_instance.instance.height as f64);
|
||||
|
||||
// Transforms a point from the background image to the foreground image
|
||||
let bg_to_fg = image.transform() * DAffine2::from_scale(1. / image_size);
|
||||
let stencil_transform_inverse = stencil.transform().inverse();
|
||||
let mut result_table = ImageFrameTable::empty();
|
||||
|
||||
let area = bg_to_fg.transform_vector2(DVec2::ONE);
|
||||
for y in 0..image.height() {
|
||||
for x in 0..image.width() {
|
||||
let image_point = DVec2::new(x as f64, y as f64);
|
||||
let mut mask_point = bg_to_fg.transform_point2(image_point);
|
||||
let local_mask_point = stencil_transform_inverse.transform_point2(mask_point);
|
||||
mask_point = stencil.transform().transform_point2(local_mask_point.clamp(DVec2::ZERO, DVec2::ONE));
|
||||
for mut image_instance in image.instance_iter() {
|
||||
let image_size = DVec2::new(image_instance.instance.width as f64, image_instance.instance.height as f64);
|
||||
let mask_size = stencil_instance.transform.decompose_scale();
|
||||
|
||||
let image_pixel = image.get_pixel_mut(x, y).unwrap();
|
||||
if let Some(mask_pixel) = stencil.sample(mask_point, area) {
|
||||
if mask_size == DVec2::ZERO {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Transforms a point from the background image to the foreground image
|
||||
let bg_to_fg = image_instance.transform * DAffine2::from_scale(1. / image_size);
|
||||
let stencil_transform_inverse = stencil_instance.transform.inverse();
|
||||
|
||||
for y in 0..image_instance.instance.height {
|
||||
for x in 0..image_instance.instance.width {
|
||||
let image_point = DVec2::new(x as f64, y as f64);
|
||||
let mask_point = bg_to_fg.transform_point2(image_point);
|
||||
let local_mask_point = stencil_transform_inverse.transform_point2(mask_point);
|
||||
let mask_point = stencil_instance.transform.transform_point2(local_mask_point.clamp(DVec2::ZERO, DVec2::ONE));
|
||||
let mask_point = (DAffine2::from_scale(stencil_size) * stencil_instance.transform.inverse()).transform_point2(mask_point);
|
||||
|
||||
let image_pixel = image_instance.instance.get_pixel_mut(x, y).unwrap();
|
||||
let mask_pixel = stencil_instance.instance.sample(mask_point);
|
||||
*image_pixel = image_pixel.multiplied_alpha(mask_pixel.l().cast_linear_channel());
|
||||
}
|
||||
}
|
||||
|
||||
result_table.push(image_instance);
|
||||
}
|
||||
|
||||
image
|
||||
}
|
||||
|
||||
// #[derive(Debug, Clone, Copy)]
|
||||
// pub struct BlendImageTupleNode<P, Fg, MapFn> {
|
||||
// map_fn: MapFn,
|
||||
// _p: PhantomData<P>,
|
||||
// _fg: PhantomData<Fg>,
|
||||
// }
|
||||
|
||||
#[node_macro::node(skip_impl)]
|
||||
async fn blend_image_tuple<_P, MapFn, _Fg>(images: (ImageFrameTable<_P>, _Fg), map_fn: &'n MapFn) -> ImageFrameTable<_P>
|
||||
where
|
||||
_P: Alpha + Pixel + Debug + Send,
|
||||
MapFn: for<'any_input> Node<'any_input, (_P, _P), Output = _P> + 'n + Clone,
|
||||
_Fg: Sample<Pixel = _P> + Transform + Clone + Send + 'n,
|
||||
GraphicElement: From<Image<_P>>,
|
||||
{
|
||||
let (background, foreground) = images;
|
||||
|
||||
blend_image(foreground, background, map_fn)
|
||||
}
|
||||
|
||||
fn blend_image<'input, _P, MapFn, Frame, Background>(foreground: Frame, background: Background, map_fn: &'input MapFn) -> Background
|
||||
where
|
||||
MapFn: Node<'input, (_P, _P), Output = _P>,
|
||||
_P: Pixel + Alpha + Debug,
|
||||
Frame: Sample<Pixel = _P> + Transform,
|
||||
Background: BitmapMut<Pixel = _P> + Sample<Pixel = _P> + Transform,
|
||||
{
|
||||
blend_image_closure(foreground, background, |a, b| map_fn.eval((a, b)))
|
||||
}
|
||||
|
||||
pub fn blend_image_closure<_P, MapFn, Frame, Background>(foreground: Frame, mut background: Background, map_fn: MapFn) -> Background
|
||||
where
|
||||
MapFn: Fn(_P, _P) -> _P,
|
||||
_P: Pixel + Alpha + Debug,
|
||||
Frame: Sample<Pixel = _P> + Transform,
|
||||
Background: BitmapMut<Pixel = _P> + Sample<Pixel = _P> + Transform,
|
||||
{
|
||||
let background_size = DVec2::new(background.width() as f64, background.height() as f64);
|
||||
|
||||
// Transforms a point from the background image to the foreground image
|
||||
let bg_to_fg = background.transform() * DAffine2::from_scale(1. / background_size);
|
||||
|
||||
// Footprint of the foreground image (0,0) (1, 1) in the background image space
|
||||
let bg_aabb = Bbox::unit().affine_transform(background.transform().inverse() * foreground.transform()).to_axis_aligned_bbox();
|
||||
|
||||
// Clamp the foreground image to the background image
|
||||
let start = (bg_aabb.start * background_size).max(DVec2::ZERO).as_uvec2();
|
||||
let end = (bg_aabb.end * background_size).min(background_size).as_uvec2();
|
||||
|
||||
let area = bg_to_fg.transform_point2(DVec2::new(1., 1.)) - bg_to_fg.transform_point2(DVec2::ZERO);
|
||||
for y in start.y..end.y {
|
||||
for x in start.x..end.x {
|
||||
let bg_point = DVec2::new(x as f64, y as f64);
|
||||
let fg_point = bg_to_fg.transform_point2(bg_point);
|
||||
|
||||
if let Some(src_pixel) = foreground.sample(fg_point, area) {
|
||||
if let Some(dst_pixel) = background.get_pixel_mut(x, y) {
|
||||
*dst_pixel = map_fn(src_pixel, *dst_pixel);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
background
|
||||
result_table
|
||||
}
|
||||
|
||||
#[node_macro::node(category(""))]
|
||||
|
|
@ -469,7 +442,7 @@ fn noise_pattern(
|
|||
|
||||
// If the image would not be visible, return an empty image
|
||||
if size.x <= 0. || size.y <= 0. {
|
||||
return ImageFrameTable::one_empty_image();
|
||||
return ImageFrameTable::default();
|
||||
}
|
||||
|
||||
let footprint_scale = footprint.scale();
|
||||
|
|
@ -513,9 +486,12 @@ fn noise_pattern(
|
|||
}
|
||||
}
|
||||
|
||||
let mut result = ImageFrameTable::new(image);
|
||||
*result.transform_mut() = DAffine2::from_translation(offset) * DAffine2::from_scale(size);
|
||||
*result.one_instance_mut().alpha_blending = AlphaBlending::default();
|
||||
let mut result = ImageFrameTable::empty();
|
||||
result.push(Instance {
|
||||
instance: image,
|
||||
transform: DAffine2::from_translation(offset) * DAffine2::from_scale(size),
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
|
@ -575,9 +551,12 @@ fn noise_pattern(
|
|||
}
|
||||
}
|
||||
|
||||
let mut result = ImageFrameTable::new(image);
|
||||
*result.transform_mut() = DAffine2::from_translation(offset) * DAffine2::from_scale(size);
|
||||
*result.one_instance_mut().alpha_blending = AlphaBlending::default();
|
||||
let mut result = ImageFrameTable::empty();
|
||||
result.push(Instance {
|
||||
instance: image,
|
||||
transform: DAffine2::from_translation(offset) * DAffine2::from_scale(size),
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
result
|
||||
}
|
||||
|
|
@ -595,7 +574,7 @@ fn mandelbrot(ctx: impl ExtractFootprint + Send) -> ImageFrameTable<Color> {
|
|||
|
||||
// If the image would not be visible, return an empty image
|
||||
if size.x <= 0. || size.y <= 0. {
|
||||
return ImageFrameTable::one_empty_image();
|
||||
return ImageFrameTable::default();
|
||||
}
|
||||
|
||||
let scale = footprint.scale();
|
||||
|
|
@ -623,9 +602,12 @@ fn mandelbrot(ctx: impl ExtractFootprint + Send) -> ImageFrameTable<Color> {
|
|||
data,
|
||||
..Default::default()
|
||||
};
|
||||
let mut result = ImageFrameTable::new(image);
|
||||
*result.transform_mut() = DAffine2::from_translation(offset) * DAffine2::from_scale(size);
|
||||
*result.one_instance_mut().alpha_blending = Default::default();
|
||||
let mut result = ImageFrameTable::empty();
|
||||
result.push(Instance {
|
||||
instance: image,
|
||||
transform: DAffine2::from_translation(offset) * DAffine2::from_scale(size),
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
result
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use bezier_rs::{ManipulatorGroup, Subpath};
|
||||
use glam::{DAffine2, DVec2};
|
||||
use graphene_core::transform::Transform;
|
||||
use graphene_core::transform::TransformMut;
|
||||
use graphene_core::RasterFrame;
|
||||
use graphene_core::instances::{Instance, InstanceRef};
|
||||
use graphene_core::vector::misc::BooleanOperation;
|
||||
use graphene_core::vector::style::Fill;
|
||||
pub use graphene_core::vector::*;
|
||||
|
|
@ -10,207 +10,245 @@ pub use path_bool as path_bool_lib;
|
|||
use path_bool::{FillRule, PathBooleanOperation};
|
||||
use std::ops::Mul;
|
||||
|
||||
// TODO: Fix boolean ops to work by removing .transform() and .one_instnace_*() calls,
|
||||
// TODO: since before we used a Vec of single-row tables and now we use a single table
|
||||
// TODO: with multiple rows while still assuming a single row for the boolean operations.
|
||||
|
||||
#[node_macro::node(category(""))]
|
||||
async fn boolean_operation(_: impl Ctx, group_of_paths: GraphicGroupTable, operation: BooleanOperation) -> VectorDataTable {
|
||||
fn flatten_vector_data(graphic_group_table: &GraphicGroupTable) -> Vec<VectorDataTable> {
|
||||
graphic_group_table
|
||||
.instance_ref_iter()
|
||||
.map(|element| match element.instance.clone() {
|
||||
GraphicElement::VectorData(mut vector_data) => {
|
||||
// Apply the parent group's transform to each element of vector data
|
||||
for sub_vector_data in vector_data.instance_mut_iter() {
|
||||
*sub_vector_data.transform = *element.transform * *sub_vector_data.transform;
|
||||
}
|
||||
|
||||
vector_data
|
||||
}
|
||||
GraphicElement::RasterFrame(mut image) => {
|
||||
// Apply the parent group's transform to each element of raster data
|
||||
match &mut image {
|
||||
graphene_core::RasterFrame::ImageFrame(image) => {
|
||||
for instance in image.instance_mut_iter() {
|
||||
*instance.transform = *element.transform * *instance.transform;
|
||||
}
|
||||
}
|
||||
graphene_core::RasterFrame::TextureFrame(image) => {
|
||||
for instance in image.instance_mut_iter() {
|
||||
*instance.transform = *element.transform * *instance.transform;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Convert the image frame into a rectangular subpath with the image's transform
|
||||
let mut subpath = Subpath::new_rect(DVec2::ZERO, DVec2::ONE);
|
||||
subpath.apply_transform(image.transform());
|
||||
|
||||
// Create a vector data table from the rectangular subpath, with a default black fill
|
||||
let mut vector_data = VectorData::from_subpath(subpath);
|
||||
vector_data.style.set_fill(Fill::Solid(Color::BLACK));
|
||||
VectorDataTable::new(vector_data)
|
||||
}
|
||||
GraphicElement::GraphicGroup(mut graphic_group) => {
|
||||
// Apply the parent group's transform to each element of inner group
|
||||
for sub_element in graphic_group.instance_mut_iter() {
|
||||
*sub_element.transform = *element.transform * *sub_element.transform;
|
||||
}
|
||||
|
||||
// Recursively flatten the inner group into vector data
|
||||
boolean_operation_on_vector_data(&flatten_vector_data(&graphic_group), BooleanOperation::Union)
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn subtract<'a>(vector_data: impl Iterator<Item = &'a VectorDataTable>) -> VectorDataTable {
|
||||
let mut vector_data = vector_data.into_iter();
|
||||
let mut result = vector_data.next().cloned().unwrap_or_default();
|
||||
let mut next_vector_data = vector_data.next();
|
||||
|
||||
while let Some(lower_vector_data) = next_vector_data {
|
||||
let transform_of_lower_into_space_of_upper = result.transform().inverse() * lower_vector_data.transform();
|
||||
|
||||
let result = result.one_instance_mut().instance;
|
||||
|
||||
let upper_path_string = to_path(result, DAffine2::IDENTITY);
|
||||
let lower_path_string = to_path(lower_vector_data.one_instance_ref().instance, transform_of_lower_into_space_of_upper);
|
||||
|
||||
#[allow(unused_unsafe)]
|
||||
let boolean_operation_string = unsafe { boolean_subtract(upper_path_string, lower_path_string) };
|
||||
let boolean_operation_result = from_path(&boolean_operation_string);
|
||||
|
||||
result.colinear_manipulators = boolean_operation_result.colinear_manipulators;
|
||||
result.point_domain = boolean_operation_result.point_domain;
|
||||
result.segment_domain = boolean_operation_result.segment_domain;
|
||||
result.region_domain = boolean_operation_result.region_domain;
|
||||
|
||||
next_vector_data = vector_data.next();
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
fn boolean_operation_on_vector_data(vector_data_table: &[VectorDataTable], boolean_operation: BooleanOperation) -> VectorDataTable {
|
||||
match boolean_operation {
|
||||
BooleanOperation::Union => {
|
||||
// Reverse vector data so that the result style is the style of the first vector data
|
||||
let mut vector_data_table = vector_data_table.iter().rev();
|
||||
let mut result_vector_data_table = vector_data_table.next().cloned().unwrap_or_default();
|
||||
|
||||
// Loop over all vector data and union it with the result
|
||||
let default = VectorDataTable::default();
|
||||
let mut second_vector_data = Some(vector_data_table.next().unwrap_or(&default));
|
||||
while let Some(lower_vector_data) = second_vector_data {
|
||||
let transform_of_lower_into_space_of_upper = result_vector_data_table.transform().inverse() * lower_vector_data.transform();
|
||||
|
||||
let result_vector_data = result_vector_data_table.one_instance_mut().instance;
|
||||
|
||||
let upper_path_string = to_path(result_vector_data, DAffine2::IDENTITY);
|
||||
let lower_path_string = to_path(lower_vector_data.one_instance_ref().instance, transform_of_lower_into_space_of_upper);
|
||||
|
||||
#[allow(unused_unsafe)]
|
||||
let boolean_operation_string = unsafe { boolean_union(upper_path_string, lower_path_string) };
|
||||
let boolean_operation_result = from_path(&boolean_operation_string);
|
||||
|
||||
result_vector_data.colinear_manipulators = boolean_operation_result.colinear_manipulators;
|
||||
result_vector_data.point_domain = boolean_operation_result.point_domain;
|
||||
result_vector_data.segment_domain = boolean_operation_result.segment_domain;
|
||||
result_vector_data.region_domain = boolean_operation_result.region_domain;
|
||||
|
||||
second_vector_data = vector_data_table.next();
|
||||
}
|
||||
|
||||
result_vector_data_table
|
||||
}
|
||||
BooleanOperation::SubtractFront => subtract(vector_data_table.iter()),
|
||||
BooleanOperation::SubtractBack => subtract(vector_data_table.iter().rev()),
|
||||
BooleanOperation::Intersect => {
|
||||
let mut vector_data = vector_data_table.iter().rev();
|
||||
let mut result = vector_data.next().cloned().unwrap_or_default();
|
||||
let default = VectorDataTable::default();
|
||||
let mut second_vector_data = Some(vector_data.next().unwrap_or(&default));
|
||||
|
||||
// For each vector data, set the result to the intersection of that data and the result
|
||||
while let Some(lower_vector_data) = second_vector_data {
|
||||
let transform_of_lower_into_space_of_upper = result.transform().inverse() * lower_vector_data.transform();
|
||||
|
||||
let result = result.one_instance_mut().instance;
|
||||
|
||||
let upper_path_string = to_path(result, DAffine2::IDENTITY);
|
||||
let lower_path_string = to_path(lower_vector_data.one_instance_ref().instance, transform_of_lower_into_space_of_upper);
|
||||
|
||||
#[allow(unused_unsafe)]
|
||||
let boolean_operation_string = unsafe { boolean_intersect(upper_path_string, lower_path_string) };
|
||||
let boolean_operation_result = from_path(&boolean_operation_string);
|
||||
|
||||
result.colinear_manipulators = boolean_operation_result.colinear_manipulators;
|
||||
result.point_domain = boolean_operation_result.point_domain;
|
||||
result.segment_domain = boolean_operation_result.segment_domain;
|
||||
result.region_domain = boolean_operation_result.region_domain;
|
||||
second_vector_data = vector_data.next();
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
BooleanOperation::Difference => {
|
||||
let mut vector_data_iter = vector_data_table.iter().rev();
|
||||
let mut any_intersection = VectorDataTable::default();
|
||||
let default = VectorDataTable::default();
|
||||
let mut second_vector_data = Some(vector_data_iter.next().unwrap_or(&default));
|
||||
|
||||
// Find where all vector data intersect at least once
|
||||
while let Some(lower_vector_data) = second_vector_data {
|
||||
let all_other_vector_data = boolean_operation_on_vector_data(&vector_data_table.iter().filter(|v| v != &lower_vector_data).cloned().collect::<Vec<_>>(), BooleanOperation::Union);
|
||||
let all_other_vector_data_instance = all_other_vector_data.one_instance_ref();
|
||||
|
||||
let transform_of_lower_into_space_of_upper = all_other_vector_data.transform().inverse() * lower_vector_data.transform();
|
||||
|
||||
let upper_path_string = to_path(all_other_vector_data_instance.instance, DAffine2::IDENTITY);
|
||||
let lower_path_string = to_path(lower_vector_data.one_instance_ref().instance, transform_of_lower_into_space_of_upper);
|
||||
|
||||
#[allow(unused_unsafe)]
|
||||
let boolean_intersection_string = unsafe { boolean_intersect(upper_path_string, lower_path_string) };
|
||||
let mut boolean_intersection_result = VectorDataTable::new(from_path(&boolean_intersection_string));
|
||||
*boolean_intersection_result.transform_mut() = *all_other_vector_data_instance.transform;
|
||||
|
||||
boolean_intersection_result.one_instance_mut().instance.style = all_other_vector_data_instance.instance.style.clone();
|
||||
*boolean_intersection_result.one_instance_mut().alpha_blending = *all_other_vector_data_instance.alpha_blending;
|
||||
|
||||
let transform_of_lower_into_space_of_upper = boolean_intersection_result.one_instance_mut().transform.inverse() * any_intersection.transform();
|
||||
|
||||
let upper_path_string = to_path(boolean_intersection_result.one_instance_mut().instance, DAffine2::IDENTITY);
|
||||
let lower_path_string = to_path(any_intersection.one_instance_mut().instance, transform_of_lower_into_space_of_upper);
|
||||
|
||||
#[allow(unused_unsafe)]
|
||||
let union_result = from_path(&unsafe { boolean_union(upper_path_string, lower_path_string) });
|
||||
*any_intersection.one_instance_mut().instance = union_result;
|
||||
|
||||
*any_intersection.transform_mut() = boolean_intersection_result.transform();
|
||||
any_intersection.one_instance_mut().instance.style = boolean_intersection_result.one_instance_mut().instance.style.clone();
|
||||
any_intersection.one_instance_mut().alpha_blending = boolean_intersection_result.one_instance_mut().alpha_blending;
|
||||
|
||||
second_vector_data = vector_data_iter.next();
|
||||
}
|
||||
// Subtract the area where they intersect at least once from the union of all vector data
|
||||
let union = boolean_operation_on_vector_data(vector_data_table, BooleanOperation::Union);
|
||||
boolean_operation_on_vector_data(&[union, any_intersection], BooleanOperation::SubtractFront)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The first index is the bottom of the stack
|
||||
let mut result_vector_data_table = boolean_operation_on_vector_data(&flatten_vector_data(&group_of_paths), operation);
|
||||
let mut result_vector_data_table = boolean_operation_on_vector_data_table(flatten_vector_data(&group_of_paths).instance_ref_iter(), operation);
|
||||
|
||||
// Replace the transformation matrix with a mutation of the vector points themselves
|
||||
let result_vector_data_table_transform = result_vector_data_table.transform();
|
||||
*result_vector_data_table.transform_mut() = DAffine2::IDENTITY;
|
||||
let result_vector_data = result_vector_data_table.one_instance_mut().instance;
|
||||
VectorData::transform(result_vector_data, result_vector_data_table_transform);
|
||||
result_vector_data.style.set_stroke_transform(DAffine2::IDENTITY);
|
||||
result_vector_data.upstream_graphic_group = Some(group_of_paths.clone());
|
||||
if let Some(result_vector_data) = result_vector_data_table.instance_mut_iter().next() {
|
||||
let transform = *result_vector_data.transform;
|
||||
*result_vector_data.transform = DAffine2::IDENTITY;
|
||||
|
||||
VectorData::transform(result_vector_data.instance, transform);
|
||||
result_vector_data.instance.style.set_stroke_transform(DAffine2::IDENTITY);
|
||||
result_vector_data.instance.upstream_graphic_group = Some(group_of_paths.clone());
|
||||
}
|
||||
|
||||
result_vector_data_table
|
||||
}
|
||||
|
||||
fn boolean_operation_on_vector_data_table<'a>(vector_data: impl DoubleEndedIterator<Item = InstanceRef<'a, VectorData>> + Clone, boolean_operation: BooleanOperation) -> VectorDataTable {
|
||||
match boolean_operation {
|
||||
BooleanOperation::Union => union(vector_data),
|
||||
BooleanOperation::SubtractFront => subtract(vector_data),
|
||||
BooleanOperation::SubtractBack => subtract(vector_data.rev()),
|
||||
BooleanOperation::Intersect => intersect(vector_data),
|
||||
BooleanOperation::Difference => difference(vector_data),
|
||||
}
|
||||
}
|
||||
|
||||
fn union<'a>(vector_data: impl DoubleEndedIterator<Item = InstanceRef<'a, VectorData>>) -> VectorDataTable {
|
||||
// Reverse vector data so that the result style is the style of the first vector data
|
||||
let mut vector_data_reversed = vector_data.rev();
|
||||
|
||||
let mut result_vector_data_table = VectorDataTable::empty();
|
||||
result_vector_data_table.push(vector_data_reversed.next().map(|x| x.to_instance_cloned()).unwrap_or_default());
|
||||
let mut first_instance = result_vector_data_table.instance_mut_iter().next().expect("Expected the one instance we just pushed");
|
||||
|
||||
// Loop over all vector data and union it with the result
|
||||
let default = Instance::default();
|
||||
let mut second_vector_data = Some(vector_data_reversed.next().unwrap_or(default.to_instance_ref()));
|
||||
while let Some(lower_vector_data) = second_vector_data {
|
||||
let transform_of_lower_into_space_of_upper = first_instance.transform.inverse() * *lower_vector_data.transform;
|
||||
|
||||
let result = &mut first_instance.instance;
|
||||
|
||||
let upper_path_string = to_path(result, DAffine2::IDENTITY);
|
||||
let lower_path_string = to_path(lower_vector_data.instance, transform_of_lower_into_space_of_upper);
|
||||
|
||||
#[allow(unused_unsafe)]
|
||||
let boolean_operation_string = unsafe { boolean_union(upper_path_string, lower_path_string) };
|
||||
let boolean_operation_result = from_path(&boolean_operation_string);
|
||||
|
||||
result.colinear_manipulators = boolean_operation_result.colinear_manipulators;
|
||||
result.point_domain = boolean_operation_result.point_domain;
|
||||
result.segment_domain = boolean_operation_result.segment_domain;
|
||||
result.region_domain = boolean_operation_result.region_domain;
|
||||
|
||||
second_vector_data = vector_data_reversed.next();
|
||||
}
|
||||
|
||||
result_vector_data_table
|
||||
}
|
||||
|
||||
fn subtract<'a>(vector_data: impl Iterator<Item = InstanceRef<'a, VectorData>>) -> VectorDataTable {
|
||||
let mut vector_data = vector_data.into_iter();
|
||||
|
||||
let mut result_vector_data_table = VectorDataTable::empty();
|
||||
result_vector_data_table.push(vector_data.next().map(|x| x.to_instance_cloned()).unwrap_or_default());
|
||||
let mut first_instance = result_vector_data_table.instance_mut_iter().next().expect("Expected the one instance we just pushed");
|
||||
|
||||
let mut next_vector_data = vector_data.next();
|
||||
|
||||
while let Some(lower_vector_data) = next_vector_data {
|
||||
let transform_of_lower_into_space_of_upper = first_instance.transform.inverse() * *lower_vector_data.transform;
|
||||
|
||||
let result = &mut first_instance.instance;
|
||||
|
||||
let upper_path_string = to_path(result, DAffine2::IDENTITY);
|
||||
let lower_path_string = to_path(lower_vector_data.instance, transform_of_lower_into_space_of_upper);
|
||||
|
||||
#[allow(unused_unsafe)]
|
||||
let boolean_operation_string = unsafe { boolean_subtract(upper_path_string, lower_path_string) };
|
||||
let boolean_operation_result = from_path(&boolean_operation_string);
|
||||
|
||||
result.colinear_manipulators = boolean_operation_result.colinear_manipulators;
|
||||
result.point_domain = boolean_operation_result.point_domain;
|
||||
result.segment_domain = boolean_operation_result.segment_domain;
|
||||
result.region_domain = boolean_operation_result.region_domain;
|
||||
|
||||
next_vector_data = vector_data.next();
|
||||
}
|
||||
|
||||
result_vector_data_table
|
||||
}
|
||||
|
||||
fn intersect<'a>(vector_data: impl DoubleEndedIterator<Item = InstanceRef<'a, VectorData>>) -> VectorDataTable {
|
||||
let mut vector_data = vector_data.rev();
|
||||
|
||||
let mut result_vector_data_table = VectorDataTable::empty();
|
||||
result_vector_data_table.push(vector_data.next().map(|x| x.to_instance_cloned()).unwrap_or_default());
|
||||
let mut first_instance = result_vector_data_table.instance_mut_iter().next().expect("Expected the one instance we just pushed");
|
||||
|
||||
let default = Instance::default();
|
||||
let mut second_vector_data = Some(vector_data.next().unwrap_or(default.to_instance_ref()));
|
||||
|
||||
// For each vector data, set the result to the intersection of that data and the result
|
||||
while let Some(lower_vector_data) = second_vector_data {
|
||||
let transform_of_lower_into_space_of_upper = first_instance.transform.inverse() * *lower_vector_data.transform;
|
||||
|
||||
let result = &mut first_instance.instance;
|
||||
|
||||
let upper_path_string = to_path(result, DAffine2::IDENTITY);
|
||||
let lower_path_string = to_path(lower_vector_data.instance, transform_of_lower_into_space_of_upper);
|
||||
|
||||
#[allow(unused_unsafe)]
|
||||
let boolean_operation_string = unsafe { boolean_intersect(upper_path_string, lower_path_string) };
|
||||
let boolean_operation_result = from_path(&boolean_operation_string);
|
||||
|
||||
result.colinear_manipulators = boolean_operation_result.colinear_manipulators;
|
||||
result.point_domain = boolean_operation_result.point_domain;
|
||||
result.segment_domain = boolean_operation_result.segment_domain;
|
||||
result.region_domain = boolean_operation_result.region_domain;
|
||||
second_vector_data = vector_data.next();
|
||||
}
|
||||
|
||||
result_vector_data_table
|
||||
}
|
||||
|
||||
fn difference<'a>(vector_data: impl DoubleEndedIterator<Item = InstanceRef<'a, VectorData>> + Clone) -> VectorDataTable {
|
||||
let mut vector_data_iter = vector_data.clone().rev();
|
||||
let mut any_intersection = Instance::default();
|
||||
let default = Instance::default();
|
||||
let mut second_vector_data = Some(vector_data_iter.next().unwrap_or(default.to_instance_ref()));
|
||||
|
||||
// Find where all vector data intersect at least once
|
||||
while let Some(lower_vector_data) = second_vector_data {
|
||||
let filtered_vector_data = vector_data.clone().filter(|v| *v != lower_vector_data).collect::<Vec<_>>().into_iter();
|
||||
let unioned = boolean_operation_on_vector_data_table(filtered_vector_data, BooleanOperation::Union);
|
||||
let first_instance = unioned.instance_ref_iter().next().expect("Expected at least one instance after the boolean union");
|
||||
|
||||
let transform_of_lower_into_space_of_upper = first_instance.transform.inverse() * *lower_vector_data.transform;
|
||||
|
||||
let upper_path_string = to_path(first_instance.instance, DAffine2::IDENTITY);
|
||||
let lower_path_string = to_path(lower_vector_data.instance, transform_of_lower_into_space_of_upper);
|
||||
|
||||
#[allow(unused_unsafe)]
|
||||
let boolean_intersection_string = unsafe { boolean_intersect(upper_path_string, lower_path_string) };
|
||||
let mut instance = from_path(&boolean_intersection_string);
|
||||
instance.style = first_instance.instance.style.clone();
|
||||
let boolean_intersection_result = Instance {
|
||||
instance,
|
||||
transform: *first_instance.transform,
|
||||
alpha_blending: *first_instance.alpha_blending,
|
||||
source_node_id: *first_instance.source_node_id,
|
||||
};
|
||||
|
||||
let transform_of_lower_into_space_of_upper = boolean_intersection_result.transform.inverse() * any_intersection.transform;
|
||||
|
||||
let upper_path_string = to_path(&boolean_intersection_result.instance, DAffine2::IDENTITY);
|
||||
let lower_path_string = to_path(&any_intersection.instance, transform_of_lower_into_space_of_upper);
|
||||
|
||||
#[allow(unused_unsafe)]
|
||||
let union_result = from_path(&unsafe { boolean_union(upper_path_string, lower_path_string) });
|
||||
any_intersection.instance = union_result;
|
||||
|
||||
any_intersection.transform = boolean_intersection_result.transform;
|
||||
any_intersection.instance.style = boolean_intersection_result.instance.style.clone();
|
||||
any_intersection.alpha_blending = boolean_intersection_result.alpha_blending;
|
||||
|
||||
second_vector_data = vector_data_iter.next();
|
||||
}
|
||||
|
||||
// Subtract the area where they intersect at least once from the union of all vector data
|
||||
let union = boolean_operation_on_vector_data_table(vector_data, BooleanOperation::Union);
|
||||
boolean_operation_on_vector_data_table(union.instance_ref_iter().chain(std::iter::once(any_intersection.to_instance_ref())), BooleanOperation::SubtractFront)
|
||||
}
|
||||
|
||||
fn flatten_vector_data(graphic_group_table: &GraphicGroupTable) -> VectorDataTable {
|
||||
let mut result_table = VectorDataTable::empty();
|
||||
|
||||
for element in graphic_group_table.instance_ref_iter() {
|
||||
match element.instance.clone() {
|
||||
GraphicElement::VectorData(vector_data) => {
|
||||
// Apply the parent group's transform to each element of vector data
|
||||
for mut sub_vector_data in vector_data.instance_iter() {
|
||||
sub_vector_data.transform = *element.transform * sub_vector_data.transform;
|
||||
|
||||
result_table.push(sub_vector_data);
|
||||
}
|
||||
}
|
||||
GraphicElement::RasterFrame(image) => {
|
||||
let make_instance = |transform| {
|
||||
// Convert the image frame into a rectangular subpath with the image's transform
|
||||
let mut subpath = Subpath::new_rect(DVec2::ZERO, DVec2::ONE);
|
||||
subpath.apply_transform(transform);
|
||||
|
||||
// Create a vector data table row from the rectangular subpath, with a default black fill
|
||||
let mut instance = VectorData::from_subpath(subpath);
|
||||
instance.style.set_fill(Fill::Solid(Color::BLACK));
|
||||
|
||||
Instance { instance, ..Default::default() }
|
||||
};
|
||||
|
||||
// Apply the parent group's transform to each element of raster data
|
||||
match image {
|
||||
RasterFrame::ImageFrame(image) => {
|
||||
for instance in image.instance_ref_iter() {
|
||||
result_table.push(make_instance(*element.transform * *instance.transform));
|
||||
}
|
||||
}
|
||||
RasterFrame::TextureFrame(image) => {
|
||||
for instance in image.instance_ref_iter() {
|
||||
result_table.push(make_instance(*element.transform * *instance.transform));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
GraphicElement::GraphicGroup(mut graphic_group) => {
|
||||
// Apply the parent group's transform to each element of inner group
|
||||
for sub_element in graphic_group.instance_mut_iter() {
|
||||
*sub_element.transform = *element.transform * *sub_element.transform;
|
||||
}
|
||||
|
||||
// Recursively flatten the inner group into vector data
|
||||
let unioned = boolean_operation_on_vector_data_table(flatten_vector_data(&graphic_group).instance_ref_iter(), BooleanOperation::Union);
|
||||
|
||||
for element in unioned.instance_iter() {
|
||||
result_table.push(element);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result_table
|
||||
}
|
||||
|
||||
fn to_path(vector: &VectorData, transform: DAffine2) -> Vec<path_bool::PathSegment> {
|
||||
let mut path = Vec::new();
|
||||
for subpath in vector.stroke_bezier_paths() {
|
||||
|
|
|
|||
|
|
@ -12,8 +12,6 @@ use graphene_core::raster::image::{Image, ImageFrameTable};
|
|||
use graphene_core::renderer::RenderMetadata;
|
||||
use graphene_core::renderer::{GraphicElementRendered, RenderParams, RenderSvgSegmentList, SvgRender, format_transform_matrix};
|
||||
use graphene_core::transform::Footprint;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use graphene_core::transform::TransformMut;
|
||||
use graphene_core::vector::VectorDataTable;
|
||||
use graphene_core::{Color, Context, Ctx, ExtractFootprint, GraphicGroupTable, OwnedContextImpl, WasmNotSend};
|
||||
|
||||
|
|
@ -44,7 +42,7 @@ async fn create_surface<'a: 'n>(_: impl Ctx, editor: &'a WasmEditorApi) -> Arc<W
|
|||
// image: ImageFrameTable<graphene_core::raster::SRGBA8>,
|
||||
// surface_handle: Arc<WasmSurfaceHandle>,
|
||||
// ) -> graphene_core::application_io::SurfaceHandleFrame<HtmlCanvasElement> {
|
||||
// let image = image.one_instance_ref().instance;
|
||||
// let image = image.instance_ref_iter().next().unwrap().instance;
|
||||
// let image_data = image.image.data;
|
||||
// let array: Clamped<&[u8]> = Clamped(bytemuck::cast_slice(image_data.as_slice()));
|
||||
// if image.image.width > 0 && image.image.height > 0 {
|
||||
|
|
@ -80,7 +78,7 @@ async fn load_resource<'a: 'n>(_: impl Ctx, _primary: (), #[scope("editor-api")]
|
|||
#[node_macro::node(category("Network"))]
|
||||
fn decode_image(_: impl Ctx, data: Arc<[u8]>) -> ImageFrameTable<Color> {
|
||||
let Some(image) = image::load_from_memory(data.as_ref()).ok() else {
|
||||
return ImageFrameTable::one_empty_image();
|
||||
return ImageFrameTable::default();
|
||||
};
|
||||
let image = image.to_rgba32f();
|
||||
let image = Image {
|
||||
|
|
@ -177,6 +175,8 @@ async fn rasterize<T: WasmNotSend + 'n>(
|
|||
where
|
||||
Instances<T>: GraphicElementRendered,
|
||||
{
|
||||
use graphene_core::instances::Instance;
|
||||
|
||||
if footprint.transform.matrix2.determinant() == 0. {
|
||||
log::trace!("Invalid footprint received for rasterization");
|
||||
return ImageFrameTable::empty();
|
||||
|
|
@ -218,8 +218,12 @@ where
|
|||
|
||||
let rasterized = context.get_image_data(0., 0., resolution.x as f64, resolution.y as f64).unwrap();
|
||||
|
||||
let mut result = ImageFrameTable::new(Image::from_image_data(&rasterized.data().0, resolution.x as u32, resolution.y as u32));
|
||||
*result.transform_mut() = footprint.transform;
|
||||
let mut result = ImageFrameTable::empty();
|
||||
result.push(Instance {
|
||||
instance: Image::from_image_data(&rasterized.data().0, resolution.x as u32, resolution.y as u32),
|
||||
transform: footprint.transform,
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
result
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ use futures::Future;
|
|||
use glam::{DAffine2, UVec2};
|
||||
use gpu_executor::{ComputePassDimensions, GPUConstant, StorageBufferOptions, TextureBufferOptions, TextureBufferType, ToStorageBuffer, ToUniformBuffer};
|
||||
use graphene_core::application_io::{ApplicationIo, EditorApi, ImageTexture, SurfaceHandle, TextureFrameTable};
|
||||
use graphene_core::instances::Instance;
|
||||
use graphene_core::raster::image::ImageFrameTable;
|
||||
use graphene_core::raster::{Image, SRGBA8};
|
||||
use graphene_core::transform::{Footprint, Transform};
|
||||
|
|
@ -911,36 +912,32 @@ async fn render_texture<'a: 'n>(
|
|||
|
||||
#[node_macro::node(category(""))]
|
||||
async fn upload_texture<'a: 'n>(_: impl ExtractFootprint + Ctx, input: ImageFrameTable<Color>, executor: &'a WgpuExecutor) -> TextureFrameTable {
|
||||
// let new_data: Vec<RGBA16F> = input.image.data.into_iter().map(|c| c.into()).collect();
|
||||
|
||||
let image = input.one_instance_ref().instance;
|
||||
let new_data: Vec<SRGBA8> = image.data.iter().map(|x| (*x).into()).collect();
|
||||
let new_image = Image {
|
||||
width: image.width,
|
||||
height: image.height,
|
||||
data: new_data,
|
||||
base64_string: None,
|
||||
};
|
||||
|
||||
let shader_input = executor.create_texture_buffer(new_image, TextureBufferOptions::Texture).unwrap();
|
||||
let texture = match shader_input {
|
||||
ShaderInput::TextureBuffer(buffer, _) => buffer,
|
||||
ShaderInput::StorageTextureBuffer(buffer, _) => buffer,
|
||||
_ => unreachable!("Unsupported ShaderInput type"),
|
||||
};
|
||||
|
||||
let texture = ImageTexture {
|
||||
texture: texture.into(),
|
||||
// TODO: Find an alternate way to encode the transform and alpha_blend now that these fields have been moved up out of ImageTexture
|
||||
// transform: input.transform,
|
||||
// alpha_blend: Default::default(),
|
||||
};
|
||||
let mut result_table = TextureFrameTable::empty();
|
||||
result_table.push(graphene_core::instances::Instance {
|
||||
instance: texture,
|
||||
transform: input.transform(),
|
||||
alpha_blending: *input.one_instance_ref().alpha_blending,
|
||||
source_node_id: *input.one_instance_ref().source_node_id,
|
||||
});
|
||||
|
||||
for instance in input.instance_ref_iter() {
|
||||
let image = instance.instance;
|
||||
let new_data: Vec<SRGBA8> = image.data.iter().map(|x| (*x).into()).collect();
|
||||
let new_image = Image {
|
||||
width: image.width,
|
||||
height: image.height,
|
||||
data: new_data,
|
||||
base64_string: None,
|
||||
};
|
||||
|
||||
let shader_input = executor.create_texture_buffer(new_image, TextureBufferOptions::Texture).unwrap();
|
||||
let texture = match shader_input {
|
||||
ShaderInput::TextureBuffer(buffer, _) => buffer,
|
||||
ShaderInput::StorageTextureBuffer(buffer, _) => buffer,
|
||||
_ => unreachable!("Unsupported ShaderInput type"),
|
||||
};
|
||||
|
||||
result_table.push(Instance {
|
||||
instance: ImageTexture { texture: texture.into() },
|
||||
transform: *instance.transform,
|
||||
alpha_blending: *instance.alpha_blending,
|
||||
source_node_id: *instance.source_node_id,
|
||||
});
|
||||
}
|
||||
|
||||
result_table
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue