mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-15 22:28:10 +08:00
Convert the remaining nodes and mark genuine async sources
This commit is contained in:
291
node-graph/interpreted-executor/tests/graphene_spike.rs
Normal file
291
node-graph/interpreted-executor/tests/graphene_spike.rs
Normal file
@@ -0,0 +1,291 @@
|
||||
use std::any::Any;
|
||||
use std::mem::MaybeUninit;
|
||||
use std::ops::Add;
|
||||
|
||||
use core_types::arena::{Arena, ArenaCell};
|
||||
use core_types::context::{ContextImpl, Ctx, EvalScope, ExtractArena, InjectIndex};
|
||||
use core_types::gnode::{BatchStatus, GNode, StatusCell};
|
||||
use core_types::gpoll::{ErrorKind, Finality, GPoll, Interrupt};
|
||||
|
||||
fn add<A: Add<B>, B, C: Ctx>(_ctx: &C, augend: A, addend: B) -> <A as Add<B>>::Output {
|
||||
augend + addend
|
||||
}
|
||||
|
||||
struct AddNode<Node0, Node1> {
|
||||
augend: Node0,
|
||||
addend: Node1,
|
||||
}
|
||||
|
||||
impl<Node0, Node1> AddNode<Node0, Node1> {
|
||||
fn new(augend: Node0, addend: Node1) -> Self {
|
||||
Self { augend, addend }
|
||||
}
|
||||
}
|
||||
|
||||
impl<A, B, Input, Node0, Node1> GNode<Input> for AddNode<Node0, Node1>
|
||||
where
|
||||
A: Add<B>,
|
||||
Input: Ctx,
|
||||
Node0: GNode<Input, Output = A>,
|
||||
Node1: GNode<Input, Output = B>,
|
||||
{
|
||||
type Output = <A as Add<B>>::Output;
|
||||
|
||||
fn eval(&self, input: &Input) -> GPoll<Self::Output> {
|
||||
let cell = StatusCell::new();
|
||||
let augend = match cell.eval_input(0, &self.augend, input) {
|
||||
Ok(value) => value,
|
||||
Err(interrupt) => return interrupt.into(),
|
||||
};
|
||||
let addend = match cell.eval_input(1, &self.addend, input) {
|
||||
Ok(value) => value,
|
||||
Err(interrupt) => return interrupt.into(),
|
||||
};
|
||||
cell.finish(add(input, augend, addend))
|
||||
}
|
||||
}
|
||||
|
||||
struct ValueNode<T>(T);
|
||||
|
||||
impl<T: Clone, Input> GNode<Input> for ValueNode<T> {
|
||||
type Output = T;
|
||||
|
||||
fn eval(&self, _input: &Input) -> GPoll<T> {
|
||||
GPoll::Final(self.0.clone())
|
||||
}
|
||||
}
|
||||
|
||||
struct ReadIndexNode;
|
||||
|
||||
impl<Input: InjectIndex + Copy + ExtractIndexValue> GNode<Input> for ReadIndexNode {
|
||||
type Output = f64;
|
||||
|
||||
fn eval(&self, input: &Input) -> GPoll<f64> {
|
||||
GPoll::Final(input.index_value() as f64)
|
||||
}
|
||||
}
|
||||
|
||||
trait ExtractIndexValue {
|
||||
fn index_value(&self) -> u64;
|
||||
}
|
||||
|
||||
impl ExtractIndexValue for ContextImpl<'_> {
|
||||
fn index_value(&self) -> u64 {
|
||||
self.index_head().index
|
||||
}
|
||||
}
|
||||
|
||||
fn string_length<C: Ctx>(_ctx: &C, value: &String) -> f64 {
|
||||
value.len() as f64
|
||||
}
|
||||
|
||||
struct LendStringNode {
|
||||
value: String,
|
||||
cell: ArenaCell<String>,
|
||||
}
|
||||
|
||||
impl LendStringNode {
|
||||
fn new(value: String) -> Self {
|
||||
Self {
|
||||
value,
|
||||
cell: ArenaCell::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'e, Input> GNode<Input> for LendStringNode
|
||||
where
|
||||
Input: Ctx + ExtractArena<ArenaRef = &'e Arena>,
|
||||
{
|
||||
type Output = &'e String;
|
||||
|
||||
fn eval(&self, input: &Input) -> GPoll<&'e String> {
|
||||
let arena = input.arena();
|
||||
if let Some(value) = self.cell.load(arena) {
|
||||
return GPoll::Final(value);
|
||||
}
|
||||
match arena.alloc(self.value.clone()) {
|
||||
Some((value, weak)) => {
|
||||
self.cell.store(weak);
|
||||
GPoll::Final(value)
|
||||
}
|
||||
None => GPoll::arena_exhausted(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct StringLengthNode<Node0> {
|
||||
value: Node0,
|
||||
}
|
||||
|
||||
impl<Node0> StringLengthNode<Node0> {
|
||||
fn new(value: Node0) -> Self {
|
||||
Self { value }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'e, Input, Node0> GNode<Input> for StringLengthNode<Node0>
|
||||
where
|
||||
Input: Ctx,
|
||||
Node0: GNode<Input, Output = &'e String>,
|
||||
{
|
||||
type Output = f64;
|
||||
|
||||
fn eval(&self, input: &Input) -> GPoll<f64> {
|
||||
let cell = StatusCell::new();
|
||||
let value = match cell.eval_input(0, &self.value, input) {
|
||||
Ok(value) => value,
|
||||
Err(interrupt) => return interrupt.into(),
|
||||
};
|
||||
cell.finish(string_length(input, value))
|
||||
}
|
||||
}
|
||||
|
||||
type ErasedGNode<T> = dyn for<'c> GNode<ContextImpl<'c>, Output = T>;
|
||||
type ErasedLendEdge = dyn for<'c> GNode<ContextImpl<'c>, Output = &'c String>;
|
||||
|
||||
fn string_length_constructor(args: Vec<Box<dyn Any>>) -> Result<Box<ErasedGNode<f64>>, &'static str> {
|
||||
let mut args = args.into_iter();
|
||||
let value = *args.next().ok_or("arity")?.downcast::<Box<ErasedLendEdge>>().map_err(|_| "type")?;
|
||||
Ok(Box::new(StringLengthNode::new(value)))
|
||||
}
|
||||
|
||||
fn add_constructor_f64(args: Vec<Box<dyn Any>>) -> Result<Box<ErasedGNode<f64>>, &'static str> {
|
||||
let mut args = args.into_iter();
|
||||
let augend = *args.next().ok_or("arity")?.downcast::<Box<ErasedGNode<f64>>>().map_err(|_| "type")?;
|
||||
let addend = *args.next().ok_or("arity")?.downcast::<Box<ErasedGNode<f64>>>().map_err(|_| "type")?;
|
||||
Ok(Box::new(AddNode::new(augend, addend)))
|
||||
}
|
||||
|
||||
fn scope_fixture<'a>(generations: &'a [(u64, u64)], arena: &'a Arena) -> EvalScope<'a> {
|
||||
EvalScope::new(Some(0.5), None, None, generations, arena)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hand_expansion_evaluates_through_typed_erased_edges() {
|
||||
let arena = Arena::new(1024);
|
||||
let generations = [];
|
||||
let scope = scope_fixture(&generations, &arena);
|
||||
let ctx = ContextImpl::root(&scope);
|
||||
|
||||
let augend: Box<dyn Any> = Box::new(Box::new(ValueNode(1.0f64)) as Box<ErasedGNode<f64>>);
|
||||
let addend: Box<dyn Any> = Box::new(Box::new(ValueNode(2.0f64)) as Box<ErasedGNode<f64>>);
|
||||
let wired = add_constructor_f64(vec![augend, addend]).unwrap();
|
||||
|
||||
assert_eq!(wired.eval(&ctx), GPoll::Final(3.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wiring_rejects_type_and_arity_mismatches() {
|
||||
let augend: Box<dyn Any> = Box::new(Box::new(ValueNode(1.0f64)) as Box<ErasedGNode<f64>>);
|
||||
let addend: Box<dyn Any> = Box::new(Box::new(ValueNode(2u32)) as Box<ErasedGNode<u32>>);
|
||||
assert_eq!(add_constructor_f64(vec![augend, addend]).map(|_| ()), Err("type"));
|
||||
|
||||
let augend: Box<dyn Any> = Box::new(Box::new(ValueNode(1.0f64)) as Box<ErasedGNode<f64>>);
|
||||
assert_eq!(add_constructor_f64(vec![augend]).map(|_| ()), Err("arity"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn spec_loop_batches_through_the_erased_edge() {
|
||||
let arena = Arena::new(1024);
|
||||
let generations = [];
|
||||
let scope = scope_fixture(&generations, &arena);
|
||||
let ctx = ContextImpl::root(&scope);
|
||||
|
||||
let graph: Box<ErasedGNode<f64>> = Box::new(AddNode::new(ReadIndexNode, ValueNode(10.0f64)));
|
||||
let mut scratch = [const { MaybeUninit::uninit() }; 4];
|
||||
let status = graph.eval_batch(&ctx, 2..6, Some(&mut scratch));
|
||||
let BatchStatus::Filled(lanes, finality) = status else {
|
||||
panic!("expected filled, got {status:?}");
|
||||
};
|
||||
assert_eq!(lanes, &[12.0, 13.0, 14.0, 15.0]);
|
||||
assert_eq!(finality, Finality::AllFinal);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lending_kernel_clones_once_per_generation_and_lends_after() {
|
||||
let arena = Arena::new(1024);
|
||||
let generations = [];
|
||||
let scope = scope_fixture(&generations, &arena);
|
||||
let ctx = ContextImpl::root(&scope);
|
||||
|
||||
let node = LendStringNode::new("lend me".to_string());
|
||||
let GPoll::Final(first) = node.eval(&ctx) else {
|
||||
panic!("first eval must clone into the arena and lend");
|
||||
};
|
||||
let GPoll::Final(second) = node.eval(&ctx) else {
|
||||
panic!("second eval must hit the cell");
|
||||
};
|
||||
assert_eq!(first, "lend me");
|
||||
assert!(std::ptr::eq(first, second));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn exhausted_arena_reports_the_operational_error() {
|
||||
let arena = Arena::new(0);
|
||||
let generations = [];
|
||||
let scope = scope_fixture(&generations, &arena);
|
||||
let ctx = ContextImpl::root(&scope);
|
||||
|
||||
let node = LendStringNode::new("too big".to_string());
|
||||
let GPoll::Error(error) = node.eval(&ctx) else {
|
||||
panic!("exhaustion must surface as an operational error");
|
||||
};
|
||||
assert_eq!(error.kind, ErrorKind::ArenaExhausted);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lending_edges_erase_and_wire_like_owned_edges() {
|
||||
let arena = Arena::new(1024);
|
||||
let generations = [];
|
||||
let scope = scope_fixture(&generations, &arena);
|
||||
let ctx = ContextImpl::root(&scope);
|
||||
|
||||
let value: Box<dyn Any> = Box::new(Box::new(LendStringNode::new("across the boundary".to_string())) as Box<ErasedLendEdge>);
|
||||
let wired = string_length_constructor(vec![value]).unwrap();
|
||||
|
||||
assert_eq!(wired.eval(&ctx), GPoll::Final(19.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn spec_loop_batches_through_the_erased_lending_edge() {
|
||||
let arena = Arena::new(1024);
|
||||
let generations = [];
|
||||
let scope = scope_fixture(&generations, &arena);
|
||||
let ctx = ContextImpl::root(&scope);
|
||||
|
||||
let graph: Box<ErasedLendEdge> = Box::new(LendStringNode::new("batched".to_string()));
|
||||
let mut scratch = [const { MaybeUninit::uninit() }; 3];
|
||||
let status = graph.eval_batch(&ctx, 0..3, Some(&mut scratch));
|
||||
let BatchStatus::Filled(lanes, finality) = status else {
|
||||
panic!("expected filled, got {status:?}");
|
||||
};
|
||||
assert_eq!(lanes.len(), 3);
|
||||
assert!(lanes.iter().all(|lane| std::ptr::eq(*lane, lanes[0])));
|
||||
assert_eq!(*lanes[0], "batched");
|
||||
assert_eq!(finality, Finality::AllFinal);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fallback_input_records_partiality_invisibly() {
|
||||
struct FallbackNode;
|
||||
impl<Input> GNode<Input> for FallbackNode {
|
||||
type Output = f64;
|
||||
fn eval(&self, _input: &Input) -> GPoll<f64> {
|
||||
GPoll::fallback(0.0, "upstream failed")
|
||||
}
|
||||
}
|
||||
|
||||
let arena = Arena::new(1024);
|
||||
let generations = [];
|
||||
let scope = scope_fixture(&generations, &arena);
|
||||
let ctx = ContextImpl::root(&scope);
|
||||
|
||||
let graph = AddNode::new(FallbackNode, ValueNode(5.0f64));
|
||||
let GPoll::Fallback(boxed) = graph.eval(&ctx) else {
|
||||
panic!("fallback must propagate with the computed stand-in");
|
||||
};
|
||||
assert_eq!(boxed.0, 5.0);
|
||||
assert!(boxed.1.kind == "upstream failed");
|
||||
assert_eq!(boxed.1.trace, vec![0]);
|
||||
}
|
||||
@@ -3,15 +3,12 @@ use crate::brush_stroke::{BrushStroke, BrushStyle};
|
||||
use core_types::blending::BlendMode;
|
||||
use core_types::bounds::{BoundingBox, RenderBoundingBox};
|
||||
use core_types::color::{Alpha, Color, Pixel, Sample};
|
||||
use core_types::generic::FnNode;
|
||||
use core_types::list::{Item, List};
|
||||
use core_types::math::bbox::{AxisAlignedBbox, Bbox};
|
||||
use core_types::registry::FutureWrapperNode;
|
||||
use core_types::transform::Transform;
|
||||
use core_types::uuid::NodeId;
|
||||
use core_types::value::ClonedNode;
|
||||
use core_types::{ATTR_BLEND_MODE, ATTR_CLIPPING_MASK, ATTR_EDITOR_LAYER_PATH, ATTR_OPACITY, ATTR_OPACITY_FILL, ATTR_TRANSFORM};
|
||||
use core_types::{Ctx, Node};
|
||||
use core_types::Ctx;
|
||||
use glam::{DAffine2, DVec2};
|
||||
use raster_nodes::blending_nodes::blend_colors;
|
||||
use raster_nodes::std_nodes::{empty_image, extend_image_to_bounds};
|
||||
@@ -63,7 +60,7 @@ impl<P: Pixel + Alpha> Sample for BrushStampGenerator<P> {
|
||||
/// The feather exponent is calculated from hardness to determine edge softness.
|
||||
/// Used internally to create the brush texture before stamping it repeatedly along a stroke path.
|
||||
#[node_macro::node(category(""), skip_impl)]
|
||||
fn brush_stamp_generator(#[unit(" px")] diameter: f64, color: Color, hardness: f64, flow: f64) -> BrushStampGenerator<Color> {
|
||||
fn brush_stamp_generator(_: impl Ctx, #[unit(" px")] diameter: f64, color: Color, hardness: f64, flow: f64) -> BrushStampGenerator<Color> {
|
||||
// Diameter
|
||||
let radius = diameter / 2.;
|
||||
|
||||
@@ -83,9 +80,9 @@ fn brush_stamp_generator(#[unit(" px")] diameter: f64, color: Color, hardness: f
|
||||
|
||||
/// Used to efficiently paint brush strokes. Applies the same texture repeatedly at different positions with proper blending and boundary handling.
|
||||
#[node_macro::node(category(""), skip_impl)]
|
||||
fn blit<BlendFn>(mut target: List<Raster<CPU>>, texture: Raster<CPU>, positions: Vec<DVec2>, blend_mode: BlendFn) -> List<Raster<CPU>>
|
||||
fn blit<BlendFn>(_: impl Ctx, mut target: List<Raster<CPU>>, texture: Raster<CPU>, positions: Vec<DVec2>, blend_mode: BlendFn) -> List<Raster<CPU>>
|
||||
where
|
||||
BlendFn: for<'any_input> Node<'any_input, (Color, Color), Output = Color>,
|
||||
BlendFn: Fn(Color, Color) -> Color,
|
||||
{
|
||||
if positions.is_empty() {
|
||||
return target;
|
||||
@@ -125,7 +122,7 @@ where
|
||||
for x in blit_area_offset.x..blit_area_offset.x + blit_area_dimensions.x {
|
||||
let src_pixel = texture.data[texture_index(x, y)];
|
||||
let dst_pixel = &mut element.data_mut().data[target_index(x + clamp_start.x, y + clamp_start.y)];
|
||||
*dst_pixel = blend_mode.eval((src_pixel, *dst_pixel));
|
||||
*dst_pixel = blend_mode(src_pixel, *dst_pixel);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -134,10 +131,10 @@ where
|
||||
target
|
||||
}
|
||||
|
||||
pub async fn create_brush_texture(brush_style: &BrushStyle) -> Raster<CPU> {
|
||||
let stamp = brush_stamp_generator(brush_style.diameter, brush_style.color, brush_style.hardness, brush_style.flow);
|
||||
pub fn create_brush_texture(brush_style: &BrushStyle) -> Raster<CPU> {
|
||||
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.));
|
||||
let blank_texture = empty_image((), transform, List::new_from_element(Color::TRANSPARENT)).into_iter().next().unwrap_or_default();
|
||||
let blank_texture = empty_image(&(), transform, List::new_from_element(Color::TRANSPARENT)).into_iter().next().unwrap_or_default();
|
||||
let image = blend_stamp_closure(stamp, blank_texture, |a, b| blend_colors(a, b, BlendMode::Normal, 1.));
|
||||
|
||||
image.into_element()
|
||||
@@ -188,7 +185,7 @@ pub fn blend_with_mode(background: Item<Raster<CPU>>, foreground: Item<Raster<CP
|
||||
/// Generates the brush strokes painted with the Brush tool as a raster image.
|
||||
/// If an input image is supplied, strokes are drawn on top of it, expanding bounds as needed.
|
||||
#[node_macro::node(category("Raster"))]
|
||||
async fn brush(
|
||||
fn brush(
|
||||
_: impl Ctx,
|
||||
/// Optional raster content that may be drawn onto.
|
||||
mut background: List<Raster<CPU>>,
|
||||
@@ -224,7 +221,7 @@ async fn brush(
|
||||
let mut brush_plan = cache.compute_brush_plan(list_item, &draw_strokes);
|
||||
|
||||
// TODO: Find a way to handle more than one item
|
||||
let Some(mut actual_image) = extend_image_to_bounds((), List::new_from_item(brush_plan.background), background_bounds).into_iter().next() else {
|
||||
let Some(mut actual_image) = extend_image_to_bounds(&(), List::new_from_item(brush_plan.background), background_bounds).into_iter().next() else {
|
||||
return List::new();
|
||||
};
|
||||
|
||||
@@ -234,7 +231,7 @@ async fn brush(
|
||||
// TODO: apply rotation from layer to stamp for non-rotationally-symmetric brushes.
|
||||
let mut brush_texture = cache.get_cached_brush(&stroke.style);
|
||||
if brush_texture.is_none() {
|
||||
let tex = create_brush_texture(&stroke.style).await;
|
||||
let tex = create_brush_texture(&stroke.style);
|
||||
cache.store_brush(stroke.style.clone(), tex.clone());
|
||||
brush_texture = Some(tex);
|
||||
}
|
||||
@@ -255,21 +252,14 @@ async fn brush(
|
||||
let stroke_origin_in_layer = bbox.start - snap_offset - DVec2::splat(stroke.style.diameter / 2.);
|
||||
let stroke_to_layer = DAffine2::from_translation(stroke_origin_in_layer) * DAffine2::from_scale(stroke_size);
|
||||
|
||||
let normal_blend = FnNode::new(|(a, b)| blend_colors(a, b, BlendMode::Normal, 1.));
|
||||
let blit_node = BlitNode::new(
|
||||
FutureWrapperNode::new(ClonedNode::new(brush_texture)),
|
||||
FutureWrapperNode::new(ClonedNode::new(positions)),
|
||||
FutureWrapperNode::new(ClonedNode::new(normal_blend)),
|
||||
);
|
||||
let blit_target = if idx == 0 {
|
||||
let target = core::mem::take(&mut brush_plan.first_stroke_texture);
|
||||
extend_image_to_bounds((), List::new_from_item(target), stroke_to_layer)
|
||||
extend_image_to_bounds(&(), List::new_from_item(target), stroke_to_layer)
|
||||
} else {
|
||||
empty_image((), stroke_to_layer, List::new_from_element(Color::TRANSPARENT))
|
||||
// EmptyImageNode::new(CopiedNode::new(stroke_to_layer), CopiedNode::new(Color::TRANSPARENT)).eval(())
|
||||
empty_image(&(), stroke_to_layer, List::new_from_element(Color::TRANSPARENT))
|
||||
};
|
||||
|
||||
let list = blit_node.eval(blit_target).await;
|
||||
let list = blit(&(), blit_target, brush_texture, positions, |a, b| blend_colors(a, b, BlendMode::Normal, 1.));
|
||||
assert_eq!(list.len(), 1);
|
||||
list.into_iter().next().unwrap_or_default()
|
||||
};
|
||||
@@ -291,7 +281,7 @@ async fn brush(
|
||||
for stroke in trace.into_iter().map(|row| row.into_element()) {
|
||||
let mut brush_texture = cache.get_cached_brush(&stroke.style);
|
||||
if brush_texture.is_none() {
|
||||
let tex = create_brush_texture(&stroke.style).await;
|
||||
let tex = create_brush_texture(&stroke.style);
|
||||
cache.store_brush(stroke.style.clone(), tex.clone());
|
||||
brush_texture = Some(tex);
|
||||
}
|
||||
@@ -305,17 +295,13 @@ async fn brush(
|
||||
_ => BlendMode::Restore,
|
||||
};
|
||||
|
||||
let blend_params = FnNode::new(move |(a, b)| blend_colors(a, b, mask_blend_mode, 1.));
|
||||
let blit_node = BlitNode::new(
|
||||
FutureWrapperNode::new(ClonedNode::new(brush_texture)),
|
||||
FutureWrapperNode::new(ClonedNode::new(positions)),
|
||||
FutureWrapperNode::new(ClonedNode::new(blend_params)),
|
||||
);
|
||||
erase_restore_mask = blit_node.eval(List::new_from_item(erase_restore_mask)).await.into_iter().next().unwrap_or_default();
|
||||
erase_restore_mask = blit(&(), List::new_from_item(erase_restore_mask), brush_texture, positions, move |a, b| blend_colors(a, b, mask_blend_mode, 1.))
|
||||
.into_iter()
|
||||
.next()
|
||||
.unwrap_or_default();
|
||||
}
|
||||
|
||||
let blend_params = FnNode::new(|(a, b)| blend_colors(a, b, BlendMode::MultiplyAlpha, 1.));
|
||||
actual_image = blend_image_closure(erase_restore_mask, actual_image, |a, b| blend_params.eval((a, b)));
|
||||
actual_image = blend_image_closure(erase_restore_mask, actual_image, |a, b| blend_colors(a, b, BlendMode::MultiplyAlpha, 1.));
|
||||
}
|
||||
|
||||
let transform: DAffine2 = actual_image.attribute_cloned_or_default(ATTR_TRANSFORM);
|
||||
@@ -410,16 +396,16 @@ mod test {
|
||||
#[test]
|
||||
fn test_brush_texture() {
|
||||
let size = 20.;
|
||||
let image = brush_stamp_generator(size, Color::BLACK, 100., 100.);
|
||||
let image = brush_stamp_generator(&(), size, Color::BLACK, 100., 100.);
|
||||
assert_eq!(image.transform(), DAffine2::from_scale_angle_translation(DVec2::splat(size.ceil()), 0., -DVec2::splat(size / 2.)));
|
||||
// center pixel should be BLACK
|
||||
assert_eq!(image.sample(DVec2::splat(0.), DVec2::ONE), Some(Color::BLACK));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_brush_output_size() {
|
||||
#[test]
|
||||
fn test_brush_output_size() {
|
||||
let image = brush(
|
||||
(),
|
||||
&(),
|
||||
&BrushCache::default(),
|
||||
List::new_from_element(Raster::new_cpu(Image::<Color>::default())),
|
||||
List::new_from_element(BrushStroke {
|
||||
@@ -433,8 +419,7 @@ mod test {
|
||||
blend_mode: BlendMode::Normal,
|
||||
},
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
);
|
||||
assert_eq!(image.element(0).unwrap().width, 20);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ fn read_gradient(ctx: impl Ctx + ExtractVarArgs) -> List<GradientStops> {
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Context"), path(core_types::vector))]
|
||||
async fn read_position(
|
||||
fn read_position(
|
||||
ctx: impl Ctx + ExtractPosition,
|
||||
_primary: (),
|
||||
/// The number of nested loops to traverse outwards (from the innermost loop) to get the position from. The most upstream loop is level 0, and downstream loops add levels.
|
||||
@@ -64,7 +64,7 @@ async fn read_position(
|
||||
///
|
||||
/// Nested loops can enable 2D or higher-dimensional iteration by using the *Loop Level* parameter to read the index from outer levels of loops.
|
||||
#[node_macro::node(category("Context"), path(core_types::vector))]
|
||||
async fn read_index(
|
||||
fn read_index(
|
||||
ctx: impl Ctx + ExtractIndex,
|
||||
_primary: (),
|
||||
/// The number of nested loops to traverse outwards (from the innermost loop) to get the index from. The most upstream loop is level 0, and downstream loops add levels.
|
||||
|
||||
@@ -569,7 +569,7 @@ pub fn wrap_graphic<T: Into<Graphic>>(
|
||||
/// Converts a list of graphical content into a `Graphic[]` by placing it into an element of a new wrapper `Graphic[]`.
|
||||
/// If it is already a `Graphic[]`, it is not wrapped again. Use the 'Wrap Graphic' node if wrapping is always desired.
|
||||
#[node_macro::node(category("General"))]
|
||||
pub async fn to_graphic<T: IntoGraphicList>(
|
||||
pub fn to_graphic<T: IntoGraphicList>(
|
||||
_: impl Ctx,
|
||||
#[implementations(
|
||||
List<Graphic>,
|
||||
@@ -587,7 +587,7 @@ pub async fn to_graphic<T: IntoGraphicList>(
|
||||
|
||||
/// Removes a level of nesting from a `Graphic[]`, or all nesting if "Fully Flatten" is enabled.
|
||||
#[node_macro::node(category("General"))]
|
||||
pub async fn flatten_graphic(_: impl Ctx, content: List<Graphic>, fully_flatten: bool) -> List<Graphic> {
|
||||
pub fn flatten_graphic(_: impl Ctx, content: List<Graphic>, fully_flatten: bool) -> List<Graphic> {
|
||||
// TODO: Avoid mutable reference, instead return a new List<Graphic>?
|
||||
fn flatten_list(output_graphic_list: &mut List<Graphic>, current_graphic_list: List<Graphic>, fully_flatten: bool, recursion_depth: usize) {
|
||||
for index in 0..current_graphic_list.len() {
|
||||
@@ -624,7 +624,7 @@ pub async fn flatten_graphic(_: impl Ctx, content: List<Graphic>, fully_flatten:
|
||||
|
||||
/// Converts a `Graphic[]` into a `Vector[]` by deeply flattening any vector content it contains, and discarding any non-vector content.
|
||||
#[node_macro::node(category("Vector"))]
|
||||
pub async fn flatten_vector<T: IntoGraphicList>(_: impl Ctx, #[implementations(List<Graphic>, List<Vector>)] content: T) -> List<Vector> {
|
||||
pub fn flatten_vector<T: IntoGraphicList>(_: impl Ctx, #[implementations(List<Graphic>, List<Vector>)] content: T) -> List<Vector> {
|
||||
let graphic_list = content.into_graphic_list();
|
||||
let mut output: List<Vector> = graphic_list.clone().into_flattened_list();
|
||||
|
||||
@@ -657,19 +657,19 @@ pub async fn flatten_vector<T: IntoGraphicList>(_: impl Ctx, #[implementations(L
|
||||
|
||||
/// Converts a `Graphic[]` into a `Raster[]` by deeply flattening any raster content it contains, and discarding any non-raster content.
|
||||
#[node_macro::node(category("Raster"))]
|
||||
pub async fn flatten_raster<T: IntoGraphicList>(_: impl Ctx, #[implementations(List<Graphic>, List<Raster<CPU>>)] content: T) -> List<Raster<CPU>> {
|
||||
pub fn flatten_raster<T: IntoGraphicList>(_: impl Ctx, #[implementations(List<Graphic>, List<Raster<CPU>>)] content: T) -> List<Raster<CPU>> {
|
||||
content.into_flattened_list()
|
||||
}
|
||||
|
||||
/// Converts a `Graphic[]` into a `Color[]` by deeply flattening any color content it contains, and discarding any non-color content.
|
||||
#[node_macro::node(category("General"))]
|
||||
pub async fn flatten_color<T: IntoGraphicList>(_: impl Ctx, #[implementations(List<Graphic>, List<Color>)] content: T) -> List<Color> {
|
||||
pub fn flatten_color<T: IntoGraphicList>(_: impl Ctx, #[implementations(List<Graphic>, List<Color>)] content: T) -> List<Color> {
|
||||
content.into_flattened_list()
|
||||
}
|
||||
|
||||
/// Converts a `Graphic[]` into a `GradientStops[]` by deeply flattening any gradient content it contains, and discarding any non-gradient content.
|
||||
#[node_macro::node(category("General"))]
|
||||
pub async fn flatten_gradient<T: IntoGraphicList>(_: impl Ctx, #[implementations(List<Graphic>, List<GradientStops>)] content: T) -> List<GradientStops> {
|
||||
pub fn flatten_gradient<T: IntoGraphicList>(_: impl Ctx, #[implementations(List<Graphic>, List<GradientStops>)] content: T) -> List<GradientStops> {
|
||||
content.into_flattened_list()
|
||||
}
|
||||
|
||||
|
||||
@@ -137,7 +137,7 @@ fn image_to_bytes(_: impl Ctx, image: List<Raster<CPU>>) -> List<u8> {
|
||||
|
||||
/// Loads binary from URLs and local asset paths. Returns a transparent placeholder if the resource fails to load, allowing rendering to continue.
|
||||
#[node_macro::node(category("Web Request"))]
|
||||
async fn load_resource<'a: 'n>(_: impl Ctx, _primary: (), #[name("URL")] url: String) -> Arc<[u8]> {
|
||||
async fn load_resource(_: impl Ctx, _primary: (), #[name("URL")] url: String) -> Arc<[u8]> {
|
||||
let placeholder = || -> Arc<[u8]> { Arc::from(Vec::<u8>::new()) };
|
||||
|
||||
let response = match reqwest::Client::new().get(&url).send().await {
|
||||
@@ -185,14 +185,14 @@ fn decode_image(_: impl Ctx, data: Arc<[u8]>) -> List<Raster<CPU>> {
|
||||
|
||||
#[cfg(target_family = "wasm")]
|
||||
#[node_macro::node(category(""))]
|
||||
async fn create_canvas(_: impl Ctx) -> CanvasHandle {
|
||||
fn create_canvas(_: impl Ctx) -> CanvasHandle {
|
||||
CanvasHandle::new()
|
||||
}
|
||||
|
||||
/// Renders a view of the input graphic within an area defined by the *Footprint*.
|
||||
#[cfg(target_family = "wasm")]
|
||||
#[node_macro::node(category(""))]
|
||||
async fn rasterize<T: WasmNotSend + Clone + 'n>(
|
||||
fn rasterize<T: WasmNotSend + Clone>(
|
||||
_: impl Ctx,
|
||||
#[implementations(
|
||||
List<Vector>,
|
||||
@@ -262,12 +262,12 @@ where
|
||||
}
|
||||
|
||||
#[node_macro::node(category(""), inject_scope)]
|
||||
pub async fn editor_api<'a: 'n>(_: impl Ctx, #[scope("editor-api")] editor_api: &'a PlatformEditorApi) -> &'a PlatformEditorApi {
|
||||
pub fn editor_api<'a>(_: impl Ctx, #[scope("editor-api")] editor_api: &'a PlatformEditorApi) -> &'a PlatformEditorApi {
|
||||
editor_api
|
||||
}
|
||||
|
||||
#[node_macro::node(category(""))]
|
||||
pub async fn resource<'a: 'n>(_: impl Ctx, hash: ResourceHash, #[scope(editor_api::IDENTIFIER)] editor_api: &'a PlatformEditorApi) -> Resource {
|
||||
pub async fn resource<'a>(_: impl Ctx, hash: ResourceHash, #[scope(editor_api::IDENTIFIER)] editor_api: &'a PlatformEditorApi) -> Resource {
|
||||
let application_io = editor_api.application_io.as_ref().expect("ApplicationIo must be available when using resources");
|
||||
application_io.load_resource(hash).await.unwrap_or_else(|| {
|
||||
panic!("Resource {hash} not found");
|
||||
@@ -275,7 +275,7 @@ pub async fn resource<'a: 'n>(_: impl Ctx, hash: ResourceHash, #[scope(editor_ap
|
||||
}
|
||||
|
||||
#[node_macro::node(category(""), inject_scope)]
|
||||
pub async fn wgpu_executor<'a: 'n>(_: impl Ctx, #[scope(editor_api::IDENTIFIER)] editor_api: &'a PlatformEditorApi) -> &'a ::wgpu_executor::WgpuExecutor {
|
||||
pub fn wgpu_executor<'a>(_: impl Ctx, #[scope(editor_api::IDENTIFIER)] editor_api: &'a PlatformEditorApi) -> &'a ::wgpu_executor::WgpuExecutor {
|
||||
editor_api
|
||||
.application_io
|
||||
.as_ref()
|
||||
@@ -285,6 +285,16 @@ pub async fn wgpu_executor<'a: 'n>(_: impl Ctx, #[scope(editor_api::IDENTIFIER)]
|
||||
}
|
||||
|
||||
#[node_macro::node(category(""), inject_scope)]
|
||||
pub async fn try_wgpu_executor<'a: 'n>(_: impl Ctx, #[scope(editor_api::IDENTIFIER)] editor_api: &'a PlatformEditorApi) -> Option<&'a ::wgpu_executor::WgpuExecutor> {
|
||||
pub fn try_wgpu_executor<'a>(_: impl Ctx, #[scope(editor_api::IDENTIFIER)] editor_api: &'a PlatformEditorApi) -> Option<&'a ::wgpu_executor::WgpuExecutor> {
|
||||
editor_api.application_io.as_ref()?.gpu_executor()
|
||||
}
|
||||
|
||||
#[node_macro::node(category(""), inject_scope)]
|
||||
pub fn wgpu_executor_arc<'a>(_: impl Ctx, #[scope(editor_api::IDENTIFIER)] editor_api: &'a PlatformEditorApi) -> std::sync::Arc<::wgpu_executor::WgpuExecutor> {
|
||||
editor_api
|
||||
.application_io
|
||||
.as_ref()
|
||||
.expect("ApplicationIo not available")
|
||||
.gpu_executor_arc()
|
||||
.expect("GPU executor not available")
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ use wgpu::util::DeviceExt;
|
||||
use wgpu_executor::{AsyncWgpuPipeline, WgpuExecutor, WgpuPipelineCache};
|
||||
|
||||
#[node_macro::node(category(""))]
|
||||
async fn render_background<'a: 'n>(
|
||||
fn render_background<'a>(
|
||||
ctx: impl Ctx + ExtractFootprint + ExtractVarArgs,
|
||||
#[scope(composite_background_pipeline::IDENTIFIER)] pipeline: WgpuPipelineCache,
|
||||
data: RenderOutput,
|
||||
@@ -121,7 +121,7 @@ async fn render_background<'a: 'n>(
|
||||
}
|
||||
|
||||
#[node_macro::node(category(""), inject_scope)]
|
||||
async fn composite_background_pipeline<'a: 'n>(
|
||||
fn composite_background_pipeline<'a>(
|
||||
_ctx: impl Ctx,
|
||||
#[scope(crate::platform_application_io::try_wgpu_executor::IDENTIFIER)] executor: Option<&'a WgpuExecutor>,
|
||||
#[data] pipeline: WgpuPipelineCache,
|
||||
|
||||
@@ -321,7 +321,7 @@ fn flood_fill(start: &TileCoord, tile_set: &HashSet<TileCoord>, visited: &mut Ha
|
||||
}
|
||||
|
||||
#[node_macro::node(category(""))]
|
||||
pub async fn render_output_cache<'a: 'n>(
|
||||
pub async fn render_output_cache<'a>(
|
||||
ctx: impl Ctx + ExtractAll + CloneVarArgs + ExtractRealTime + ExtractAnimationTime + ExtractPointerPosition + Sync,
|
||||
#[scope(crate::platform_application_io::try_wgpu_executor::IDENTIFIER)] executor: Option<&'a WgpuExecutor>,
|
||||
#[scope(crate::platform_application_io::editor_api::IDENTIFIER)] editor_api: &'a PlatformEditorApi,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use core_types::gpoll::Interrupt;
|
||||
use core_types::list::List;
|
||||
use core_types::transform::{Footprint, Transform};
|
||||
use core_types::{CloneVarArgs, ExtractAll, ExtractVarArgs};
|
||||
use core_types::{Color, Context, Ctx, ExtractFootprint, OwnedContextImpl, WasmNotSend};
|
||||
use core_types::{Color, Context, Ctx, DeriveCtx, ExtractFootprint, ExtractVarArgs, OwnedContextImpl, WasmNotSend};
|
||||
use graph_craft::document::value::{RenderOutput, RenderOutputType};
|
||||
use graphene_application_io::{ExportFormat, RenderConfig};
|
||||
use graphic_types::raster_types::{CPU, Raster};
|
||||
@@ -23,8 +23,8 @@ pub struct RenderIntermediate {
|
||||
}
|
||||
|
||||
#[node_macro::node(category(""))]
|
||||
async fn render_intermediate<'a: 'n, T: 'static + Render + WasmNotSend + Send + Sync>(
|
||||
ctx: impl Ctx + ExtractVarArgs + ExtractAll + CloneVarArgs,
|
||||
fn render_intermediate<T: 'static + Render + WasmNotSend + Send + Sync>(
|
||||
ctx: impl Ctx + ExtractVarArgs + DeriveCtx,
|
||||
#[implementations(
|
||||
Context -> List<Artboard>,
|
||||
Context -> List<Graphic>,
|
||||
@@ -35,20 +35,18 @@ async fn render_intermediate<'a: 'n, T: 'static + Render + WasmNotSend + Send +
|
||||
Context -> List<String>,
|
||||
)]
|
||||
data: impl Node<Context<'_>, Output = T>,
|
||||
) -> RenderIntermediate {
|
||||
) -> Result<RenderIntermediate, Interrupt> {
|
||||
let data = data.eval(&ctx.derived())?;
|
||||
let render_params = ctx
|
||||
.vararg(0)
|
||||
.expect("Did not find var args")
|
||||
.downcast_ref::<RenderParams>()
|
||||
.expect("Downcasting render params yielded invalid type");
|
||||
|
||||
let ctx = OwnedContextImpl::from(ctx.clone()).into_context();
|
||||
let data = data.eval(ctx).await;
|
||||
|
||||
let footprint = Footprint::default();
|
||||
let mut metadata = RenderMetadata::default();
|
||||
data.collect_metadata(&mut metadata, footprint, None);
|
||||
match &render_params.render_output_type {
|
||||
Ok(match &render_params.render_output_type {
|
||||
RenderOutputTypeRequest::Vello => {
|
||||
let mut scene = vello::Scene::new();
|
||||
|
||||
@@ -70,11 +68,11 @@ async fn render_intermediate<'a: 'n, T: 'static + Render + WasmNotSend + Send +
|
||||
metadata,
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[node_macro::node(category(""))]
|
||||
async fn render<'a: 'n>(
|
||||
fn render<'a>(
|
||||
ctx: impl Ctx + ExtractFootprint + ExtractVarArgs,
|
||||
#[scope(crate::platform_application_io::try_wgpu_executor::IDENTIFIER)] executor: Option<&'a WgpuExecutor>,
|
||||
data: RenderIntermediate,
|
||||
@@ -144,7 +142,7 @@ async fn render<'a: 'n>(
|
||||
}
|
||||
|
||||
#[node_macro::node(category(""))]
|
||||
async fn create_context<'a: 'n>(
|
||||
fn create_context<'a>(
|
||||
// Context injections are defined in the wrap_network_in_scope function
|
||||
render_config: RenderConfig,
|
||||
data: impl Node<Context<'_>, Output = RenderOutput>,
|
||||
|
||||
@@ -8,7 +8,7 @@ use vector_types::vector::style::RenderMode;
|
||||
use wgpu_executor::{AsyncWgpuPipeline, WgpuExecutor, WgpuPipelineCache};
|
||||
|
||||
#[node_macro::node(category(""))]
|
||||
pub async fn render_pixel_preview<'a: 'n>(
|
||||
pub async fn render_pixel_preview<'a>(
|
||||
ctx: impl Ctx + ExtractAll + CloneVarArgs + Sync,
|
||||
#[scope(pixel_preview_pipeline::IDENTIFIER)] pipeline: WgpuPipelineCache,
|
||||
data: impl Node<Context<'_>, Output = RenderOutput> + Send + Sync,
|
||||
@@ -75,7 +75,7 @@ pub async fn render_pixel_preview<'a: 'n>(
|
||||
}
|
||||
|
||||
#[node_macro::node(category(""), inject_scope)]
|
||||
async fn pixel_preview_pipeline<'a: 'n>(
|
||||
fn pixel_preview_pipeline<'a>(
|
||||
_ctx: impl Ctx,
|
||||
#[scope(crate::platform_application_io::try_wgpu_executor::IDENTIFIER)] executor: Option<&'a WgpuExecutor>,
|
||||
#[data] pipeline: WgpuPipelineCache,
|
||||
|
||||
@@ -23,7 +23,7 @@ pub use vector_types::vector::misc::BooleanOperation;
|
||||
|
||||
/// Combines the geometric forms of one or more closed paths into a new vector path that results from cutting or joining the paths by the chosen method.
|
||||
#[node_macro::node(category("Vector: Modifier"), memoize)]
|
||||
async fn boolean_operation<I: graphic_types::IntoGraphicList>(
|
||||
fn boolean_operation<I: graphic_types::IntoGraphicList>(
|
||||
_: impl Ctx,
|
||||
/// The `List` of vector paths to perform the boolean operation on. Nested `List`s are automatically flattened.
|
||||
#[implementations(List<Graphic>, List<Vector>)]
|
||||
|
||||
@@ -8,7 +8,7 @@ use raster_types::{CPU, Raster};
|
||||
use std::cmp::{max, min};
|
||||
|
||||
#[node_macro::node(category("Raster: Filter"))]
|
||||
async fn dehaze(_: impl Ctx, image_frame: List<Raster<CPU>>, strength: Percentage) -> List<Raster<CPU>> {
|
||||
fn dehaze(_: impl Ctx, image_frame: List<Raster<CPU>>, strength: Percentage) -> List<Raster<CPU>> {
|
||||
image_frame
|
||||
.into_iter()
|
||||
.map(|mut row| {
|
||||
|
||||
@@ -87,7 +87,7 @@ fn unpremultiply_gamma_to_linear(buffer: Image<PremultipliedGammaPixel>) -> Imag
|
||||
|
||||
/// Blurs the image with a Gaussian or box blur kernel filter.
|
||||
#[node_macro::node(category("Raster: Filter"))]
|
||||
async fn blur(
|
||||
fn blur(
|
||||
_: impl Ctx,
|
||||
/// The image to be blurred.
|
||||
image_frame: List<Raster<CPU>>,
|
||||
@@ -124,7 +124,7 @@ async fn blur(
|
||||
|
||||
/// Applies a median filter to reduce noise while preserving edges.
|
||||
#[node_macro::node(category("Raster: Filter"))]
|
||||
async fn median_filter(
|
||||
fn median_filter(
|
||||
_: impl Ctx,
|
||||
/// The image to be filtered.
|
||||
image_frame: List<Raster<CPU>>,
|
||||
|
||||
@@ -10,7 +10,7 @@ use vector_types::GradientStops;
|
||||
// https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=%27grdm%27%20%3D%20Gradient%20Map
|
||||
// https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=Gradient%20settings%20(Photoshop%206.0)
|
||||
#[node_macro::node(category("Raster: Adjustment"))]
|
||||
async fn gradient_map<T: Adjust<Color>>(
|
||||
fn gradient_map<T: Adjust<Color>>(
|
||||
_: impl Ctx,
|
||||
#[implementations(
|
||||
List<Raster<CPU>>,
|
||||
|
||||
@@ -4,7 +4,7 @@ use core_types::list::{Item, List};
|
||||
use raster_types::{CPU, Raster};
|
||||
|
||||
#[node_macro::node(category("Color"))]
|
||||
async fn image_color_palette(
|
||||
fn image_color_palette(
|
||||
_: impl Ctx,
|
||||
image: List<Raster<CPU>>,
|
||||
#[default(4)]
|
||||
|
||||
@@ -108,7 +108,7 @@ fn replace_transform<T>(
|
||||
// TODO: Figure out how this node should behave once #2982 is implemented.
|
||||
/// Obtains the transform of the first item in the input `List`, if present.
|
||||
#[node_macro::node(category("Math: Transform"), path(core_types::vector))]
|
||||
async fn extract_transform(_: impl Ctx, content: ListDyn) -> DAffine2 {
|
||||
fn extract_transform(_: impl Ctx, content: ListDyn) -> DAffine2 {
|
||||
content.attribute::<DAffine2>(ATTR_TRANSFORM, 0).copied().unwrap_or_default()
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ use vector_types::vector::VectorModification;
|
||||
|
||||
/// Applies a differential modification to a vector path, associating changes made by the Pen and Path tools to indices of edited points and segments.
|
||||
#[node_macro::node(category(""))]
|
||||
async fn path_modify(_ctx: impl Ctx, mut vector: List<Vector>, modification: Box<VectorModification>, node_path: List<NodeId>) -> List<Vector> {
|
||||
fn path_modify(_ctx: impl Ctx, mut vector: List<Vector>, modification: Box<VectorModification>, node_path: List<NodeId>) -> List<Vector> {
|
||||
use core_types::list::Item;
|
||||
|
||||
if vector.is_empty() {
|
||||
@@ -35,7 +35,7 @@ async fn path_modify(_ctx: impl Ctx, mut vector: List<Vector>, modification: Box
|
||||
|
||||
/// Applies the vector path's local transformation to its geometry and resets the transform to the identity.
|
||||
#[node_macro::node(category("Vector"))]
|
||||
async fn apply_transform(_ctx: impl Ctx, mut vector: List<Vector>) -> List<Vector> {
|
||||
fn apply_transform(_ctx: impl Ctx, mut vector: List<Vector>) -> List<Vector> {
|
||||
let (elements, transforms) = vector.element_and_attribute_slices_mut::<DAffine2>(ATTR_TRANSFORM);
|
||||
for (element, transform) in elements.iter_mut().zip(transforms.iter_mut()) {
|
||||
for (_, point) in element.point_domain.positions_mut() {
|
||||
|
||||
@@ -89,7 +89,7 @@ impl VectorListIterMut for List<Vector> {
|
||||
|
||||
/// Uniquely sets the fill and/or stroke style of every vector element to individual colors sampled along a chosen gradient.
|
||||
#[node_macro::node(category("Vector: Style"), path(graphene_core::vector))]
|
||||
async fn assign_colors<T>(
|
||||
fn assign_colors<T>(
|
||||
_: impl Ctx,
|
||||
/// The content with vector paths to apply the fill and/or stroke style to.
|
||||
#[implementations(List<Graphic>, List<Vector>)]
|
||||
@@ -157,7 +157,7 @@ where
|
||||
|
||||
/// Applies a fill style to the vector content, giving an appearance to the area within the interior of the geometry.
|
||||
#[node_macro::node(category("Vector: Style"), path(graphene_core::vector), properties("fill_properties"))]
|
||||
async fn fill<V: VectorListIterMut+ Send, F: IntoGraphicList+ Send + 'static>(
|
||||
fn fill<V: VectorListIterMut+ Send, F: IntoGraphicList+ Send + 'static>(
|
||||
_: impl Ctx,
|
||||
/// The content with vector paths to apply the fill style to.
|
||||
#[implementations(
|
||||
@@ -252,7 +252,7 @@ impl IntoF64Vec for String {
|
||||
|
||||
/// Applies a stroke style to the vector content, giving an appearance to the area within the outline of the geometry.
|
||||
#[node_macro::node(category("Vector: Style"), path(graphene_core::vector), properties("stroke_properties"))]
|
||||
async fn stroke<V, L: IntoF64Vec, P: IntoGraphicList+ Send + 'static>(
|
||||
fn stroke<V, L: IntoF64Vec, P: IntoGraphicList+ Send + 'static>(
|
||||
_: impl Ctx,
|
||||
/// The content with vector paths to apply the stroke style to.
|
||||
#[implementations(
|
||||
@@ -357,7 +357,7 @@ where
|
||||
}
|
||||
|
||||
#[node_macro::node(name("Copy to Points"), category("Repeat"), path(core_types::vector))]
|
||||
async fn copy_to_points<I: Send + Clone>(
|
||||
fn copy_to_points<I: Send + Clone>(
|
||||
_: impl Ctx,
|
||||
points: List<Vector>,
|
||||
/// Artwork to be copied and placed at each point.
|
||||
@@ -441,7 +441,7 @@ async fn copy_to_points<I: Send + Clone>(
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Vector: Modifier"), path(core_types::vector))]
|
||||
async fn round_corners(
|
||||
fn round_corners(
|
||||
_: impl Ctx,
|
||||
source: List<Vector>,
|
||||
#[hard(0..)]
|
||||
@@ -778,7 +778,7 @@ pub mod extrude_algorithms {
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Vector: Modifier"), path(core_types::vector))]
|
||||
async fn extrude(_: impl Ctx, mut source: List<Vector>, direction: DVec2, joining_algorithm: ExtrudeJoiningAlgorithm) -> List<Vector> {
|
||||
fn extrude(_: impl Ctx, mut source: List<Vector>, direction: DVec2, joining_algorithm: ExtrudeJoiningAlgorithm) -> List<Vector> {
|
||||
for vector in source.iter_element_values_mut() {
|
||||
extrude_algorithms::extrude(vector, direction, joining_algorithm);
|
||||
}
|
||||
@@ -786,7 +786,7 @@ async fn extrude(_: impl Ctx, mut source: List<Vector>, direction: DVec2, joinin
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Vector: Modifier"), path(core_types::vector))]
|
||||
async fn box_warp(_: impl Ctx, content: List<Vector>, #[expose] rectangle: List<Vector>) -> List<Vector> {
|
||||
fn box_warp(_: impl Ctx, content: List<Vector>, #[expose] rectangle: List<Vector>) -> List<Vector> {
|
||||
let Some(target) = rectangle.element(0).cloned() else { return content };
|
||||
let target_transform: DAffine2 = rectangle.attribute_cloned_or_default(ATTR_TRANSFORM, 0);
|
||||
|
||||
@@ -871,7 +871,7 @@ fn bilinear_interpolate(t: DVec2, quad: &[DVec2; 4]) -> DVec2 {
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Vector"), path(graphene_core::vector))]
|
||||
async fn pack_strips<T: Send + Clone>(
|
||||
fn pack_strips<T: Send + Clone>(
|
||||
_: impl Ctx,
|
||||
#[implementations(
|
||||
List<Graphic>,
|
||||
@@ -992,7 +992,7 @@ where
|
||||
|
||||
/// Automatically constructs tangents (Bézier handles) for anchor points in a vector path.
|
||||
#[node_macro::node(category("Vector: Modifier"), name("Auto-Tangents"), path(core_types::vector))]
|
||||
async fn auto_tangents(
|
||||
fn auto_tangents(
|
||||
_: impl Ctx,
|
||||
source: List<Vector>,
|
||||
/// The amount of spread for the auto-tangents, from 0 (sharp corner) to 1 (full spread).
|
||||
@@ -1146,7 +1146,7 @@ async fn auto_tangents(
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Vector: Modifier"), path(core_types::vector))]
|
||||
async fn bounding_box(_: impl Ctx, content: List<Vector>) -> List<Vector> {
|
||||
fn bounding_box(_: impl Ctx, content: List<Vector>) -> List<Vector> {
|
||||
content
|
||||
.into_iter()
|
||||
.map(|mut row| {
|
||||
@@ -1171,7 +1171,7 @@ async fn bounding_box(_: impl Ctx, content: List<Vector>) -> List<Vector> {
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Vector: Measure"), path(core_types::vector))]
|
||||
async fn dimensions(_: impl Ctx, content: List<Vector>) -> DVec2 {
|
||||
fn dimensions(_: impl Ctx, content: List<Vector>) -> DVec2 {
|
||||
(0..content.len())
|
||||
.filter_map(|index| content.element(index).unwrap().bounding_box_with_transform(content.attribute_cloned_or_default(ATTR_TRANSFORM, index)))
|
||||
.reduce(|[acc_top_left, acc_bottom_right], [top_left, bottom_right]| [acc_top_left.min(top_left), acc_bottom_right.max(bottom_right)])
|
||||
@@ -1187,7 +1187,7 @@ fn as_vector(_: impl Ctx, value: List<Vector>) -> List<Vector> {
|
||||
|
||||
/// Creates a polyline from a series of vector points, replacing any existing segments and regions that may already exist.
|
||||
#[node_macro::node(category("Vector"), name("Points to Polyline"), path(core_types::vector))]
|
||||
async fn points_to_polyline(_: impl Ctx, mut points: List<Vector>, #[default(true)] closed: bool) -> List<Vector> {
|
||||
fn points_to_polyline(_: impl Ctx, mut points: List<Vector>, #[default(true)] closed: bool) -> List<Vector> {
|
||||
for vector in points.iter_element_values_mut() {
|
||||
let mut segment_domain = SegmentDomain::new();
|
||||
let mut next_id = SegmentId::ZERO;
|
||||
@@ -1215,7 +1215,7 @@ async fn points_to_polyline(_: impl Ctx, mut points: List<Vector>, #[default(tru
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Vector: Modifier"), path(core_types::vector), properties("offset_path_properties"))]
|
||||
async fn offset_path(_: impl Ctx, content: List<Vector>, distance: f64, join: StrokeJoin, #[default(4.)] miter_limit: f64) -> List<Vector> {
|
||||
fn offset_path(_: impl Ctx, content: List<Vector>, distance: f64, join: StrokeJoin, #[default(4.)] miter_limit: f64) -> List<Vector> {
|
||||
content
|
||||
.into_iter()
|
||||
.map(|mut row| {
|
||||
@@ -1259,7 +1259,7 @@ async fn offset_path(_: impl Ctx, content: List<Vector>, distance: f64, join: St
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Vector: Modifier"), path(core_types::vector))]
|
||||
async fn solidify_stroke<T: IntoGraphicList>(_: impl Ctx, #[implementations(List<Graphic>, List<Vector>)] content: T) -> List<Vector> {
|
||||
fn solidify_stroke<T: IntoGraphicList>(_: impl Ctx, #[implementations(List<Graphic>, List<Vector>)] content: T) -> List<Vector> {
|
||||
// TODO: Make this node support stroke align, which it currently ignores
|
||||
|
||||
let graphic_list = content.into_graphic_list();
|
||||
@@ -1367,7 +1367,7 @@ async fn solidify_stroke<T: IntoGraphicList>(_: impl Ctx, #[implementations(List
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Vector: Modifier"), path(core_types::vector))]
|
||||
async fn separate_subpaths(_: impl Ctx, content: List<Vector>) -> List<Vector> {
|
||||
fn separate_subpaths(_: impl Ctx, content: List<Vector>) -> List<Vector> {
|
||||
content
|
||||
.into_iter()
|
||||
.flat_map(|row| {
|
||||
@@ -1398,7 +1398,7 @@ async fn separate_subpaths(_: impl Ctx, content: List<Vector>) -> List<Vector> {
|
||||
|
||||
/// Determines if the subpath at the given index (across all vector element subpaths) is closed, meaning its ends are connected together forming a loop.
|
||||
#[node_macro::node(name("Path is Closed"), category("Vector: Measure"), path(core_types::vector))]
|
||||
async fn path_is_closed(
|
||||
fn path_is_closed(
|
||||
_: impl Ctx,
|
||||
/// The vector content whose subpaths are inspected.
|
||||
content: List<Vector>,
|
||||
@@ -1431,7 +1431,7 @@ fn map_points(ctx: impl Ctx + DeriveCtx, content: List<Vector>, mapped: impl Nod
|
||||
|
||||
// TODO: Rename to "Combine Paths" and make this happen per-element instead of flattening every element into a single path. The migration for this should then become a Flatten Vector -> Combine Paths pair of nodes.
|
||||
#[node_macro::node(category("Vector"), path(graphene_core::vector))]
|
||||
pub async fn flatten_path<T: IntoGraphicList>(_: impl Ctx, #[implementations(List<Graphic>, List<Vector>)] content: T) -> List<Vector> {
|
||||
pub fn flatten_path<T: IntoGraphicList>(_: impl Ctx, #[implementations(List<Graphic>, List<Vector>)] content: T) -> List<Vector> {
|
||||
let graphic_list = content.into_graphic_list();
|
||||
let flattened = graphic_list.clone().into_flattened_list::<Vector>();
|
||||
|
||||
@@ -1487,7 +1487,7 @@ pub async fn flatten_path<T: IntoGraphicList>(_: impl Ctx, #[implementations(Lis
|
||||
|
||||
/// Convert vector geometry into a polyline composed of evenly spaced points.
|
||||
#[node_macro::node(category("Vector: Modifier"), path(core_types::vector), properties("sample_polyline_properties"), memoize)]
|
||||
async fn sample_polyline(
|
||||
fn sample_polyline(
|
||||
_: impl Ctx,
|
||||
content: List<Vector>,
|
||||
spacing: PointSpacingType,
|
||||
@@ -1573,7 +1573,7 @@ async fn sample_polyline(
|
||||
|
||||
/// Simplifies vector paths by reducing the number of curve segments while preserving the overall shape within the given tolerance.
|
||||
#[node_macro::node(category("Vector: Modifier"), path(core_types::vector))]
|
||||
async fn simplify(
|
||||
fn simplify(
|
||||
_: impl Ctx,
|
||||
/// The vector paths to simplify.
|
||||
content: List<Vector>,
|
||||
@@ -1617,7 +1617,7 @@ async fn simplify(
|
||||
|
||||
/// Decimates vector paths into polylines by sampling any curves into line segments, then removing points that don't significantly contribute to the shape using the Ramer-Douglas-Peucker algorithm.
|
||||
#[node_macro::node(category("Vector: Modifier"), path(core_types::vector))]
|
||||
async fn decimate(
|
||||
fn decimate(
|
||||
_: impl Ctx,
|
||||
/// The vector paths to decimate.
|
||||
content: List<Vector>,
|
||||
@@ -1745,7 +1745,7 @@ async fn decimate(
|
||||
///
|
||||
/// If multiple subpaths make up the path, the whole number part of the progression value selects the subpath and the decimal part determines the position along it.
|
||||
#[node_macro::node(category("Vector: Modifier"), path(graphene_core::vector))]
|
||||
async fn cut_path(
|
||||
fn cut_path(
|
||||
_: impl Ctx,
|
||||
/// The path to insert a cut into.
|
||||
mut content: List<Vector>,
|
||||
@@ -1796,7 +1796,7 @@ async fn cut_path(
|
||||
|
||||
/// Cuts path segments into separate disconnected pieces where each is a distinct subpath.
|
||||
#[node_macro::node(category("Vector: Modifier"), path(core_types::vector))]
|
||||
async fn cut_segments(_: impl Ctx, mut content: List<Vector>) -> List<Vector> {
|
||||
fn cut_segments(_: impl Ctx, mut content: List<Vector>) -> List<Vector> {
|
||||
// Iterate through every segment and make a copy of each of its endpoints, then reassign each segment's endpoints to its own unique point copy
|
||||
for vector in content.iter_element_values_mut() {
|
||||
let points_count = vector.point_domain.ids().len();
|
||||
@@ -1855,7 +1855,7 @@ async fn cut_segments(_: impl Ctx, mut content: List<Vector>) -> List<Vector> {
|
||||
///
|
||||
/// If multiple subpaths make up the path, the whole number part of the progression value selects the subpath and the decimal part determines the position along it.
|
||||
#[node_macro::node(name("Position on Path"), category("Vector: Measure"), path(graphene_core::vector))]
|
||||
async fn position_on_path(
|
||||
fn position_on_path(
|
||||
_: impl Ctx,
|
||||
/// The path to traverse.
|
||||
content: List<Vector>,
|
||||
@@ -1893,7 +1893,7 @@ async fn position_on_path(
|
||||
///
|
||||
/// If multiple subpaths make up the path, the whole number part of the progression value selects the subpath and the decimal part determines the position along it.
|
||||
#[node_macro::node(name("Tangent on Path"), category("Vector: Measure"), path(graphene_core::vector))]
|
||||
async fn tangent_on_path(
|
||||
fn tangent_on_path(
|
||||
_: impl Ctx,
|
||||
/// The path to traverse.
|
||||
content: List<Vector>,
|
||||
@@ -1941,7 +1941,7 @@ async fn tangent_on_path(
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Vector: Modifier"), path(core_types::vector), memoize)]
|
||||
async fn scatter_points(
|
||||
fn scatter_points(
|
||||
_: impl Ctx,
|
||||
content: List<Vector>,
|
||||
#[unit(" px")]
|
||||
@@ -1991,7 +1991,7 @@ async fn scatter_points(
|
||||
}
|
||||
|
||||
#[node_macro::node(name("Spline"), category("Vector: Modifier"), path(core_types::vector))]
|
||||
async fn spline(_: impl Ctx, content: List<Vector>) -> List<Vector> {
|
||||
fn spline(_: impl Ctx, content: List<Vector>) -> List<Vector> {
|
||||
content
|
||||
.into_iter()
|
||||
.filter_map(|mut row| {
|
||||
@@ -2091,7 +2091,7 @@ fn apply_point_deltas(element: &mut Vector, deltas: &[DVec2], transform: DAffine
|
||||
|
||||
/// Perturbs the positions of anchor points in vector geometry by random amounts and directions.
|
||||
#[node_macro::node(category("Vector: Modifier"), path(core_types::vector))]
|
||||
async fn jitter_points(
|
||||
fn jitter_points(
|
||||
_: impl Ctx,
|
||||
/// The vector geometry with points to be jittered.
|
||||
content: List<Vector>,
|
||||
@@ -2141,7 +2141,7 @@ async fn jitter_points(
|
||||
/// Displaces anchor points along their normal direction (perpendicular to the path) by a set distance.
|
||||
/// Points with 0 or 3+ segment connections have no well-defined normal and are left in place.
|
||||
#[node_macro::node(category("Vector: Modifier"), path(core_types::vector))]
|
||||
async fn offset_points(
|
||||
fn offset_points(
|
||||
_: impl Ctx,
|
||||
/// The vector geometry with points to be offset.
|
||||
content: List<Vector>,
|
||||
@@ -2178,7 +2178,7 @@ async fn offset_points(
|
||||
///
|
||||
/// *Progression* morphs through all objects. Interpolation is linear unless *Path* geometry is provided to control the trajectory between key objects. The **Origins to Polyline** node may be used to create a path with anchor points corresponding to each object. Other nodes can modify its path segments.
|
||||
#[node_macro::node(category("Vector: Modifier"), path(core_types::vector))]
|
||||
async fn morph<I: IntoGraphicList>(
|
||||
fn morph<I: IntoGraphicList>(
|
||||
_: impl Ctx,
|
||||
/// The vector objects to interpolate between. Mixed graphic content is deeply flattened to keep only vector elements.
|
||||
#[implementations(List<Graphic>, List<Vector>)]
|
||||
@@ -3125,19 +3125,19 @@ fn point_inside(_: impl Ctx, source: List<Vector>, point: DVec2) -> bool {
|
||||
// TODO: Return u32, u64, or usize instead of f64 after #1621 is resolved and has allowed us to implement automatic type conversion in the node graph for nodes with generic type inputs.
|
||||
// TODO: (Currently automatic type conversion only works for concrete types, via the Graphene preprocessor and not the full Graphene type system.)
|
||||
#[node_macro::node(category("General"), path(graphene_core::vector))]
|
||||
async fn count_elements(_: impl Ctx, content: ListDyn) -> f64 {
|
||||
fn count_elements(_: impl Ctx, content: ListDyn) -> f64 {
|
||||
content.len() as f64
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Vector: Measure"), path(graphene_core::vector))]
|
||||
async fn count_points(_: impl Ctx, content: List<Vector>) -> f64 {
|
||||
fn count_points(_: impl Ctx, content: List<Vector>) -> f64 {
|
||||
content.iter_element_values().map(|vector| vector.point_domain.positions().len() as f64).sum()
|
||||
}
|
||||
|
||||
/// Retrieves the vec2 position (in local space) of the anchor point at the specified index in a `List` of vector elements.
|
||||
/// If no value exists at that index, the position (0, 0) is returned.
|
||||
#[node_macro::node(category("Vector: Measure"), path(graphene_core::vector))]
|
||||
async fn index_points(
|
||||
fn index_points(
|
||||
_: impl Ctx,
|
||||
/// The vector element or elements containing the anchor points to be retrieved.
|
||||
content: List<Vector>,
|
||||
@@ -3171,7 +3171,7 @@ async fn index_points(
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Vector: Measure"), path(core_types::vector))]
|
||||
async fn path_length(_: impl Ctx, source: List<Vector>) -> f64 {
|
||||
fn path_length(_: impl Ctx, source: List<Vector>) -> f64 {
|
||||
(0..source.len())
|
||||
.map(|index| {
|
||||
let transform: DAffine2 = source.attribute_cloned_or_default(ATTR_TRANSFORM, index);
|
||||
|
||||
Reference in New Issue
Block a user