mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-16 06:38:03 +08:00
Replace Footprint/() call arguments with dynamically-bound Contexts (#2232)
* Implement experimental Context struct and traits * Add Ctx super trait * Checkpoint * Return Any instead of DynAny * Fix send implementation for inputs with lifetimes * Port more nodes * Uncomment nodes * Port more nodes * Port vector nodes * Partial progress (the stuff I'm more sure about) * Partial progress (the stuff that's not compiling and I'm not sure about) * Fix more errors * First pass of fixing errors introduced by rebase * Port wasm application io * Fix brush node types * Add type annotation * Fix warnings and wasm compilation * Change types for Document Node definitions * Improve debugging for footprint not found errors * Forward context in append artboard node * Fix thumbnails * Fix loading most demo artwork * Wrap output type of all nodes in future * Encode futures as part of the type * Fix document node definitions for future types * Remove Clippy warnings * Fix more things * Fix opening demo art with manual composition upgrading * Set correct type for manual composition * Fix brush * Fix tests * Update docs for deps * Fix up some node signature issues * Code review --------- Co-authored-by: Keavon Chambers <keavon@keavon.com> Co-authored-by: hypercube <0hypercube@gmail.com>
This commit is contained in:
committed by
Keavon Chambers
parent
0c1e96b9c6
commit
4ff2bdb04f
307
node-graph/gcore/src/context.rs
Normal file
307
node-graph/gcore/src/context.rs
Normal file
@@ -0,0 +1,307 @@
|
||||
use crate::transform::Footprint;
|
||||
|
||||
use core::{any::Any, borrow::Borrow, panic::Location};
|
||||
use std::sync::Arc;
|
||||
|
||||
pub trait Ctx: Clone + Send {}
|
||||
|
||||
pub trait ExtractFootprint {
|
||||
#[track_caller]
|
||||
fn try_footprint(&self) -> Option<&Footprint>;
|
||||
#[track_caller]
|
||||
fn footprint(&self) -> &Footprint {
|
||||
self.try_footprint().unwrap_or_else(|| {
|
||||
log::error!("Context did not have a footprint, called from: {}", Location::caller());
|
||||
&const { Footprint::empty() }
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ExtractTime {
|
||||
fn try_time(&self) -> Option<f64>;
|
||||
}
|
||||
|
||||
pub trait ExtractIndex {
|
||||
fn try_index(&self) -> Option<usize>;
|
||||
}
|
||||
|
||||
// Consider returning a slice or something like that
|
||||
pub trait ExtractVarArgs {
|
||||
// Call this lifetime 'b so it is less likely to coflict when auto generating the function signature for implementation
|
||||
fn vararg(&self, index: usize) -> Result<DynRef<'_>, VarArgsResult>;
|
||||
fn varargs_len(&self) -> Result<usize, VarArgsResult>;
|
||||
}
|
||||
// Consider returning a slice or something like that
|
||||
pub trait CloneVarArgs: ExtractVarArgs {
|
||||
// fn box_clone(&self) -> Vec<DynBox>;
|
||||
fn arc_clone(&self) -> Option<Arc<dyn ExtractVarArgs + Send + Sync>>;
|
||||
}
|
||||
|
||||
pub trait ExtractAll: ExtractFootprint + ExtractIndex + ExtractTime + ExtractVarArgs {}
|
||||
|
||||
impl<T: ?Sized + ExtractFootprint + ExtractIndex + ExtractTime + ExtractVarArgs> ExtractAll for T {}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum VarArgsResult {
|
||||
IndexOutOfBounds,
|
||||
NoVarArgs,
|
||||
}
|
||||
impl<T: Ctx> Ctx for Option<T> {}
|
||||
impl<T: Ctx + Sync> Ctx for &T {}
|
||||
impl Ctx for () {}
|
||||
impl Ctx for Footprint {}
|
||||
impl ExtractFootprint for () {
|
||||
fn try_footprint(&self) -> Option<&Footprint> {
|
||||
log::error!("tried to extract footprint form (), {}", Location::caller());
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ExtractFootprint + Ctx + Sync + Send> ExtractFootprint for &T {
|
||||
fn try_footprint(&self) -> Option<&Footprint> {
|
||||
(*self).try_footprint()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ExtractFootprint + Sync> ExtractFootprint for Option<T> {
|
||||
fn try_footprint(&self) -> Option<&Footprint> {
|
||||
self.as_ref().and_then(|x| x.try_footprint())
|
||||
}
|
||||
#[track_caller]
|
||||
fn footprint(&self) -> &Footprint {
|
||||
self.try_footprint().unwrap_or_else(|| {
|
||||
log::warn!("trying to extract footprint from context None {} ", Location::caller());
|
||||
&const { Footprint::empty() }
|
||||
})
|
||||
}
|
||||
}
|
||||
impl<T: ExtractTime + Sync> ExtractTime for Option<T> {
|
||||
fn try_time(&self) -> Option<f64> {
|
||||
self.as_ref().and_then(|x| x.try_time())
|
||||
}
|
||||
}
|
||||
impl<T: ExtractIndex> ExtractIndex for Option<T> {
|
||||
fn try_index(&self) -> Option<usize> {
|
||||
self.as_ref().and_then(|x| x.try_index())
|
||||
}
|
||||
}
|
||||
impl<T: ExtractVarArgs + Sync> ExtractVarArgs for Option<T> {
|
||||
fn vararg(&self, index: usize) -> Result<DynRef<'_>, VarArgsResult> {
|
||||
let Some(ref inner) = self else { return Err(VarArgsResult::NoVarArgs) };
|
||||
inner.vararg(index)
|
||||
}
|
||||
|
||||
fn varargs_len(&self) -> Result<usize, VarArgsResult> {
|
||||
let Some(ref inner) = self else { return Err(VarArgsResult::NoVarArgs) };
|
||||
inner.varargs_len()
|
||||
}
|
||||
}
|
||||
impl<T: ExtractFootprint + Sync> ExtractFootprint for Arc<T> {
|
||||
fn try_footprint(&self) -> Option<&Footprint> {
|
||||
(**self).try_footprint()
|
||||
}
|
||||
}
|
||||
impl<T: ExtractTime + Sync> ExtractTime for Arc<T> {
|
||||
fn try_time(&self) -> Option<f64> {
|
||||
(**self).try_time()
|
||||
}
|
||||
}
|
||||
impl<T: ExtractIndex> ExtractIndex for Arc<T> {
|
||||
fn try_index(&self) -> Option<usize> {
|
||||
(**self).try_index()
|
||||
}
|
||||
}
|
||||
impl<T: ExtractVarArgs + Sync> ExtractVarArgs for Arc<T> {
|
||||
fn vararg(&self, index: usize) -> Result<DynRef<'_>, VarArgsResult> {
|
||||
(**self).vararg(index)
|
||||
}
|
||||
|
||||
fn varargs_len(&self) -> Result<usize, VarArgsResult> {
|
||||
(**self).varargs_len()
|
||||
}
|
||||
}
|
||||
impl<T: CloneVarArgs + Sync> CloneVarArgs for Option<T> {
|
||||
fn arc_clone(&self) -> Option<Arc<dyn ExtractVarArgs + Send + Sync>> {
|
||||
self.as_ref().and_then(CloneVarArgs::arc_clone)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ExtractVarArgs + Sync> ExtractVarArgs for &T {
|
||||
fn vararg(&self, index: usize) -> Result<DynRef<'_>, VarArgsResult> {
|
||||
(*self).vararg(index)
|
||||
}
|
||||
|
||||
fn varargs_len(&self) -> Result<usize, VarArgsResult> {
|
||||
(*self).varargs_len()
|
||||
}
|
||||
}
|
||||
impl<T: CloneVarArgs + Sync> CloneVarArgs for Arc<T> {
|
||||
fn arc_clone(&self) -> Option<Arc<dyn ExtractVarArgs + Send + Sync>> {
|
||||
(**self).arc_clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl Ctx for ContextImpl<'_> {}
|
||||
impl Ctx for Arc<OwnedContextImpl> {}
|
||||
|
||||
impl ExtractFootprint for ContextImpl<'_> {
|
||||
fn try_footprint(&self) -> Option<&Footprint> {
|
||||
self.footprint
|
||||
}
|
||||
}
|
||||
impl ExtractTime for ContextImpl<'_> {
|
||||
fn try_time(&self) -> Option<f64> {
|
||||
self.time
|
||||
}
|
||||
}
|
||||
impl ExtractIndex for ContextImpl<'_> {
|
||||
fn try_index(&self) -> Option<usize> {
|
||||
self.index
|
||||
}
|
||||
}
|
||||
impl ExtractVarArgs for ContextImpl<'_> {
|
||||
fn vararg(&self, index: usize) -> Result<DynRef<'_>, VarArgsResult> {
|
||||
let Some(inner) = self.varargs else { return Err(VarArgsResult::NoVarArgs) };
|
||||
inner.get(index).ok_or(VarArgsResult::IndexOutOfBounds).copied()
|
||||
}
|
||||
|
||||
fn varargs_len(&self) -> Result<usize, VarArgsResult> {
|
||||
let Some(inner) = self.varargs else { return Err(VarArgsResult::NoVarArgs) };
|
||||
Ok(inner.len())
|
||||
}
|
||||
}
|
||||
|
||||
impl ExtractFootprint for OwnedContextImpl {
|
||||
fn try_footprint(&self) -> Option<&Footprint> {
|
||||
self.footprint.as_ref()
|
||||
}
|
||||
}
|
||||
impl ExtractTime for OwnedContextImpl {
|
||||
fn try_time(&self) -> Option<f64> {
|
||||
self.time
|
||||
}
|
||||
}
|
||||
impl ExtractIndex for OwnedContextImpl {
|
||||
fn try_index(&self) -> Option<usize> {
|
||||
self.index
|
||||
}
|
||||
}
|
||||
impl ExtractVarArgs for OwnedContextImpl {
|
||||
fn vararg(&self, index: usize) -> Result<DynRef<'_>, VarArgsResult> {
|
||||
let Some(ref inner) = self.varargs else {
|
||||
let Some(ref parent) = self.parent else {
|
||||
return Err(VarArgsResult::NoVarArgs);
|
||||
};
|
||||
return parent.vararg(index);
|
||||
};
|
||||
inner.get(index).map(|x| x.as_ref()).ok_or(VarArgsResult::IndexOutOfBounds)
|
||||
}
|
||||
|
||||
fn varargs_len(&self) -> Result<usize, VarArgsResult> {
|
||||
let Some(ref inner) = self.varargs else {
|
||||
let Some(ref parent) = self.parent else {
|
||||
return Err(VarArgsResult::NoVarArgs);
|
||||
};
|
||||
return parent.varargs_len();
|
||||
};
|
||||
Ok(inner.len())
|
||||
}
|
||||
}
|
||||
|
||||
impl CloneVarArgs for Arc<OwnedContextImpl> {
|
||||
fn arc_clone(&self) -> Option<Arc<dyn ExtractVarArgs + Send + Sync>> {
|
||||
Some(self.clone())
|
||||
}
|
||||
}
|
||||
|
||||
pub type Context<'a> = Option<Arc<OwnedContextImpl>>;
|
||||
type DynRef<'a> = &'a (dyn Any + Send + Sync);
|
||||
type DynBox = Box<dyn Any + Send + Sync>;
|
||||
|
||||
#[derive(dyn_any::DynAny)]
|
||||
pub struct OwnedContextImpl {
|
||||
footprint: Option<crate::transform::Footprint>,
|
||||
varargs: Option<Arc<[DynBox]>>,
|
||||
parent: Option<Arc<dyn ExtractVarArgs + Sync + Send>>,
|
||||
// This could be converted into a single enum to save extra bytes
|
||||
index: Option<usize>,
|
||||
time: Option<f64>,
|
||||
}
|
||||
|
||||
impl Default for OwnedContextImpl {
|
||||
#[track_caller]
|
||||
fn default() -> Self {
|
||||
Self::empty()
|
||||
}
|
||||
}
|
||||
|
||||
impl core::hash::Hash for OwnedContextImpl {
|
||||
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
|
||||
self.footprint.hash(state);
|
||||
self.index.hash(state);
|
||||
self.time.map(|x| x.to_bits()).hash(state);
|
||||
self.parent.as_ref().map(|x| Arc::as_ptr(x).addr()).hash(state);
|
||||
self.varargs.as_ref().map(|x| Arc::as_ptr(x).addr()).hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl OwnedContextImpl {
|
||||
#[track_caller]
|
||||
pub fn from<T: ExtractAll + CloneVarArgs>(value: T) -> Self {
|
||||
let footprint = value.try_footprint().copied();
|
||||
let index = value.try_index();
|
||||
let time = value.try_time();
|
||||
let parent = value.arc_clone();
|
||||
OwnedContextImpl {
|
||||
footprint,
|
||||
varargs: None,
|
||||
parent,
|
||||
index,
|
||||
time,
|
||||
}
|
||||
}
|
||||
pub const fn empty() -> Self {
|
||||
OwnedContextImpl {
|
||||
footprint: None,
|
||||
varargs: None,
|
||||
parent: None,
|
||||
index: None,
|
||||
time: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl OwnedContextImpl {
|
||||
pub fn set_footprint(&mut self, footprint: Footprint) {
|
||||
self.footprint = Some(footprint);
|
||||
}
|
||||
pub fn with_footprint(mut self, footprint: Footprint) -> Self {
|
||||
self.footprint = Some(footprint);
|
||||
self
|
||||
}
|
||||
pub fn into_context(self) -> Option<Arc<Self>> {
|
||||
Some(Arc::new(self))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Clone, Copy, dyn_any::DynAny)]
|
||||
pub struct ContextImpl<'a> {
|
||||
pub(crate) footprint: Option<&'a crate::transform::Footprint>,
|
||||
varargs: Option<&'a [DynRef<'a>]>,
|
||||
// This could be converted into a single enum to save extra bytes
|
||||
index: Option<usize>,
|
||||
time: Option<f64>,
|
||||
}
|
||||
|
||||
impl<'a> ContextImpl<'a> {
|
||||
pub fn with_footprint<'f>(&self, new_footprint: &'f Footprint, varargs: Option<&'f impl (Borrow<[DynRef<'f>]>)>) -> ContextImpl<'f>
|
||||
where
|
||||
'a: 'f,
|
||||
{
|
||||
ContextImpl {
|
||||
footprint: Some(new_footprint),
|
||||
varargs: varargs.map(|x| x.borrow()),
|
||||
..*self
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
use core::marker::PhantomData;
|
||||
|
||||
use crate::Node;
|
||||
#[derive(Clone)]
|
||||
pub struct FnNode<T: Fn(I) -> O, I, O>(T, PhantomData<(I, O)>);
|
||||
|
||||
impl<'i, T: Fn(I) -> O + 'i, O: 'i, I: 'i> Node<'i, I> for FnNode<T, I, O> {
|
||||
|
||||
@@ -2,10 +2,10 @@ use crate::application_io::{TextureFrame, TextureFrameTable};
|
||||
use crate::instances::Instances;
|
||||
use crate::raster::image::{ImageFrame, ImageFrameTable};
|
||||
use crate::raster::BlendMode;
|
||||
use crate::transform::{ApplyTransform, Footprint, Transform, TransformMut};
|
||||
use crate::transform::{Transform, TransformMut};
|
||||
use crate::uuid::NodeId;
|
||||
use crate::vector::{VectorData, VectorDataTable};
|
||||
use crate::Color;
|
||||
use crate::{CloneVarArgs, Color, Context, Ctx, ExtractAll, OwnedContextImpl};
|
||||
|
||||
use dyn_any::DynAny;
|
||||
|
||||
@@ -280,28 +280,8 @@ impl ArtboardGroup {
|
||||
}
|
||||
|
||||
#[node_macro::node(category(""))]
|
||||
async fn layer<F: 'n + Send + Copy>(
|
||||
#[implementations(
|
||||
(),
|
||||
Footprint,
|
||||
)]
|
||||
footprint: F,
|
||||
#[implementations(
|
||||
() -> GraphicGroupTable,
|
||||
Footprint -> GraphicGroupTable,
|
||||
)]
|
||||
stack: impl Node<F, Output = GraphicGroupTable>,
|
||||
#[implementations(
|
||||
() -> GraphicElement,
|
||||
Footprint -> GraphicElement,
|
||||
)]
|
||||
element: impl Node<F, Output = GraphicElement>,
|
||||
node_path: Vec<NodeId>,
|
||||
) -> GraphicGroupTable {
|
||||
let mut element = element.eval(footprint).await;
|
||||
let stack = stack.eval(footprint).await;
|
||||
let stack = stack.one_item();
|
||||
let mut stack = stack.clone();
|
||||
async fn layer(_: impl Ctx, stack: GraphicGroupTable, mut element: GraphicElement, node_path: Vec<NodeId>) -> GraphicGroupTable {
|
||||
let mut stack = stack.one_item().clone();
|
||||
|
||||
if stack.transform.matrix2.determinant() != 0. {
|
||||
*element.transform_mut() = stack.transform.inverse() * element.transform();
|
||||
@@ -318,72 +298,36 @@ async fn layer<F: 'n + Send + Copy>(
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Debug"))]
|
||||
async fn to_element<F: 'n + Send, Data: Into<GraphicElement> + 'n>(
|
||||
async fn to_element<Data: Into<GraphicElement> + 'n>(
|
||||
_: impl Ctx,
|
||||
#[implementations(
|
||||
(),
|
||||
(),
|
||||
(),
|
||||
(),
|
||||
Footprint,
|
||||
GraphicGroupTable,
|
||||
VectorDataTable,
|
||||
ImageFrameTable<Color>,
|
||||
TextureFrameTable,
|
||||
)]
|
||||
footprint: F,
|
||||
#[implementations(
|
||||
() -> GraphicGroupTable,
|
||||
() -> VectorDataTable,
|
||||
() -> ImageFrameTable<Color>,
|
||||
() -> TextureFrameTable,
|
||||
Footprint -> GraphicGroupTable,
|
||||
Footprint -> VectorDataTable,
|
||||
Footprint -> ImageFrameTable<Color>,
|
||||
Footprint -> TextureFrameTable,
|
||||
)]
|
||||
data: impl Node<F, Output = Data>,
|
||||
data: Data,
|
||||
) -> GraphicElement {
|
||||
data.eval(footprint).await.into()
|
||||
data.into()
|
||||
}
|
||||
|
||||
#[node_macro::node(category("General"))]
|
||||
async fn to_group<F: 'n + Send, Data: Into<GraphicGroupTable> + 'n>(
|
||||
async fn to_group<Data: Into<GraphicGroupTable> + 'n>(
|
||||
_: impl Ctx,
|
||||
#[implementations(
|
||||
(),
|
||||
(),
|
||||
(),
|
||||
(),
|
||||
Footprint,
|
||||
GraphicGroupTable,
|
||||
VectorDataTable,
|
||||
ImageFrameTable<Color>,
|
||||
TextureFrameTable,
|
||||
)]
|
||||
footprint: F,
|
||||
#[implementations(
|
||||
() -> GraphicGroupTable,
|
||||
() -> VectorDataTable,
|
||||
() -> ImageFrameTable<Color>,
|
||||
() -> TextureFrameTable,
|
||||
Footprint -> GraphicGroupTable,
|
||||
Footprint -> VectorDataTable,
|
||||
Footprint -> ImageFrameTable<Color>,
|
||||
Footprint -> TextureFrameTable,
|
||||
)]
|
||||
element: impl Node<F, Output = Data>,
|
||||
element: Data,
|
||||
) -> GraphicGroupTable {
|
||||
element.eval(footprint).await.into()
|
||||
element.into()
|
||||
}
|
||||
|
||||
#[node_macro::node(category("General"))]
|
||||
async fn flatten_group<F: 'n + Send>(
|
||||
#[implementations(
|
||||
(),
|
||||
Footprint,
|
||||
)]
|
||||
footprint: F,
|
||||
#[implementations(
|
||||
() -> GraphicGroupTable,
|
||||
Footprint -> GraphicGroupTable,
|
||||
)]
|
||||
group: impl Node<F, Output = GraphicGroupTable>,
|
||||
fully_flatten: bool,
|
||||
) -> GraphicGroupTable {
|
||||
let nested_group = group.eval(footprint).await;
|
||||
let nested_group = nested_group.one_item();
|
||||
let nested_group = nested_group.clone();
|
||||
async fn flatten_group(_: impl Ctx, group: GraphicGroupTable, fully_flatten: bool) -> GraphicGroupTable {
|
||||
let nested_group = group.one_item().clone();
|
||||
|
||||
let mut flat_group = GraphicGroup::default();
|
||||
|
||||
@@ -420,34 +364,28 @@ async fn flatten_group<F: 'n + Send>(
|
||||
}
|
||||
|
||||
#[node_macro::node(category(""))]
|
||||
async fn to_artboard<F: 'n + Send + ApplyTransform, Data: Into<GraphicGroupTable> + 'n>(
|
||||
async fn to_artboard<Data: Into<GraphicGroupTable> + 'n>(
|
||||
ctx: impl ExtractAll + CloneVarArgs + Ctx,
|
||||
#[implementations(
|
||||
(),
|
||||
(),
|
||||
(),
|
||||
(),
|
||||
Footprint,
|
||||
Context -> GraphicGroupTable,
|
||||
Context -> VectorDataTable,
|
||||
Context -> ImageFrameTable<Color>,
|
||||
Context -> TextureFrameTable,
|
||||
)]
|
||||
mut footprint: F,
|
||||
#[implementations(
|
||||
() -> GraphicGroupTable,
|
||||
() -> VectorDataTable,
|
||||
() -> ImageFrameTable<Color>,
|
||||
() -> TextureFrame,
|
||||
Footprint -> GraphicGroupTable,
|
||||
Footprint -> VectorDataTable,
|
||||
Footprint -> ImageFrameTable<Color>,
|
||||
Footprint -> TextureFrame,
|
||||
)]
|
||||
contents: impl Node<F, Output = Data>,
|
||||
contents: impl Node<Context<'static>, Output = Data>,
|
||||
label: String,
|
||||
location: IVec2,
|
||||
dimensions: IVec2,
|
||||
background: Color,
|
||||
clip: bool,
|
||||
) -> Artboard {
|
||||
footprint.apply_transform(&DAffine2::from_translation(location.as_dvec2()));
|
||||
let graphic_group = contents.eval(footprint).await;
|
||||
let footprint = ctx.try_footprint().copied();
|
||||
let mut new_ctx = OwnedContextImpl::from(ctx);
|
||||
if let Some(mut footprint) = footprint {
|
||||
footprint.translate(location.as_dvec2());
|
||||
new_ctx = new_ctx.with_footprint(footprint);
|
||||
}
|
||||
let graphic_group = contents.eval(new_ctx.into_context()).await;
|
||||
|
||||
Artboard {
|
||||
graphic_group: graphic_group.into(),
|
||||
@@ -460,28 +398,16 @@ async fn to_artboard<F: 'n + Send + ApplyTransform, Data: Into<GraphicGroupTable
|
||||
}
|
||||
|
||||
#[node_macro::node(category(""))]
|
||||
async fn append_artboard<F: 'n + Send + Copy>(
|
||||
#[implementations(
|
||||
(),
|
||||
Footprint,
|
||||
)]
|
||||
footprint: F,
|
||||
#[implementations(
|
||||
() -> ArtboardGroup,
|
||||
Footprint -> ArtboardGroup,
|
||||
)]
|
||||
artboards: impl Node<F, Output = ArtboardGroup>,
|
||||
#[implementations(
|
||||
() -> Artboard,
|
||||
Footprint -> Artboard,
|
||||
)]
|
||||
artboard: impl Node<F, Output = Artboard>,
|
||||
node_path: Vec<NodeId>,
|
||||
) -> ArtboardGroup {
|
||||
let artboard = artboard.eval(footprint).await;
|
||||
let mut artboards = artboards.eval(footprint).await;
|
||||
|
||||
async fn append_artboard(ctx: impl Ctx, mut artboards: ArtboardGroup, artboard: Artboard, node_path: Vec<NodeId>) -> ArtboardGroup {
|
||||
// let mut artboards = artboards.eval(ctx.clone()).await;
|
||||
// let artboard = artboard.eval(ctx).await;
|
||||
// let foot = ctx.footprint();
|
||||
// log::debug!("{:?}", foot);
|
||||
// Get the penultimate element of the node path, or None if the path is too short
|
||||
|
||||
// TODO: Delete this line
|
||||
let _ctx = ctx;
|
||||
|
||||
let encapsulating_node_id = node_path.get(node_path.len().wrapping_sub(2)).copied();
|
||||
artboards.append_artboard(artboard, encapsulating_node_id);
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ pub use crate as graphene_core;
|
||||
pub use ctor;
|
||||
|
||||
pub mod consts;
|
||||
pub mod context;
|
||||
pub mod generic;
|
||||
pub mod instances;
|
||||
pub mod logic;
|
||||
@@ -48,6 +49,7 @@ pub mod application_io;
|
||||
#[cfg(feature = "reflections")]
|
||||
pub mod registry;
|
||||
|
||||
pub use context::*;
|
||||
use core::any::TypeId;
|
||||
pub use memo::MemoHash;
|
||||
pub use raster::Color;
|
||||
@@ -56,7 +58,7 @@ pub use types::Cow;
|
||||
// pub trait Node: for<'n> NodeIO<'n> {
|
||||
/// The node trait allows for defining any node. Nodes can only take one call argument input, however they can store references to other nodes inside the struct.
|
||||
/// See `node-graph/README.md` for information on how to define a new node.
|
||||
pub trait Node<'i, Input: 'i>: 'i {
|
||||
pub trait Node<'i, Input> {
|
||||
type Output: 'i;
|
||||
/// Evaluates the node with the single specified input.
|
||||
fn eval(&'i self, input: Input) -> Self::Output;
|
||||
@@ -79,10 +81,10 @@ mod types;
|
||||
#[cfg(feature = "alloc")]
|
||||
pub use types::*;
|
||||
|
||||
pub trait NodeIO<'i, Input: 'i>: 'i + Node<'i, Input>
|
||||
pub trait NodeIO<'i, Input>: Node<'i, Input>
|
||||
where
|
||||
Self::Output: 'i + StaticTypeSized,
|
||||
Input: 'i + StaticTypeSized,
|
||||
Input: StaticTypeSized,
|
||||
{
|
||||
fn input_type(&self) -> TypeId {
|
||||
TypeId::of::<Input::Static>()
|
||||
@@ -112,8 +114,7 @@ where
|
||||
{
|
||||
NodeIOTypes {
|
||||
call_argument: concrete!(<Input as StaticTypeSized>::Static),
|
||||
// TODO return actual future type
|
||||
return_value: concrete!(<<Self::Output as Future>::Output as StaticTypeSized>::Static),
|
||||
return_value: future!(<<Self::Output as Future>::Output as StaticTypeSized>::Static),
|
||||
inputs,
|
||||
}
|
||||
}
|
||||
@@ -122,7 +123,7 @@ where
|
||||
impl<'i, N: Node<'i, I>, I> NodeIO<'i, I> for N
|
||||
where
|
||||
N::Output: 'i + StaticTypeSized,
|
||||
I: 'i + StaticTypeSized,
|
||||
I: StaticTypeSized,
|
||||
{
|
||||
}
|
||||
|
||||
@@ -152,13 +153,13 @@ use dyn_any::StaticTypeSized;
|
||||
use core::pin::Pin;
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
impl<'i, I: 'i, O: 'i> Node<'i, I> for Pin<Box<dyn Node<'i, I, Output = O> + 'i>> {
|
||||
impl<'i, I, O: 'i> Node<'i, I> for Pin<Box<dyn Node<'i, I, Output = O> + 'i>> {
|
||||
type Output = O;
|
||||
fn eval(&'i self, input: I) -> O {
|
||||
(**self).eval(input)
|
||||
}
|
||||
}
|
||||
impl<'i, I: 'i, O: 'i> Node<'i, I> for Pin<&'i (dyn NodeIO<'i, I, Output = O> + 'i)> {
|
||||
impl<'i, I, O: 'i> Node<'i, I> for Pin<&'i (dyn NodeIO<'i, I, Output = O> + 'i)> {
|
||||
type Output = O;
|
||||
fn eval(&'i self, input: I) -> O {
|
||||
(**self).eval(input)
|
||||
|
||||
@@ -1,57 +1,40 @@
|
||||
use crate::transform::Footprint;
|
||||
use crate::vector::VectorDataTable;
|
||||
|
||||
use crate::Context;
|
||||
use crate::Ctx;
|
||||
use glam::{DAffine2, DVec2};
|
||||
|
||||
#[node_macro::node(category("Debug"))]
|
||||
async fn log_to_console<T: core::fmt::Debug, F: Send + 'n>(
|
||||
#[implementations((), (), (), (), (), (), (), (), Footprint)] footprint: F,
|
||||
#[implementations(
|
||||
() -> String, () -> bool, () -> f64, () -> u32, () -> u64, () -> DVec2, () -> VectorDataTable, () -> DAffine2,
|
||||
Footprint -> String, Footprint -> bool, Footprint -> f64, Footprint -> u32, Footprint -> u64, Footprint -> DVec2, Footprint -> VectorDataTable, Footprint -> DAffine2,
|
||||
)]
|
||||
value: impl Node<F, Output = T>,
|
||||
) -> T {
|
||||
fn log_to_console<T: core::fmt::Debug>(_: impl Ctx, #[implementations(String, bool, f64, u32, u64, DVec2, VectorDataTable, DAffine2)] value: T) -> T {
|
||||
#[cfg(not(target_arch = "spirv"))]
|
||||
// KEEP THIS `debug!()` - It acts as the output for the debug node itself
|
||||
let value = value.eval(footprint).await;
|
||||
debug!("{:#?}", value);
|
||||
value
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Debug"))]
|
||||
async fn to_string<T: core::fmt::Debug + 'n, F: Send + 'n>(
|
||||
#[implementations((), (), (), (), (), (), Footprint)] footprint: F,
|
||||
#[implementations(
|
||||
() -> String, () -> bool, () -> f64, () -> u32, () -> u64, () -> DVec2,
|
||||
Footprint -> String, Footprint -> bool, Footprint -> f64, Footprint -> u32, Footprint -> u64, Footprint -> DVec2,
|
||||
)]
|
||||
value: impl Node<F, Output = T>,
|
||||
) -> String {
|
||||
let value = value.eval(footprint).await;
|
||||
#[node_macro::node(category("Debug"), skip_impl)]
|
||||
fn to_string<T: core::fmt::Debug>(_: impl Ctx, #[implementations(String, bool, f64, u32, u64, DVec2, VectorDataTable, DAffine2)] value: T) -> String {
|
||||
format!("{:?}", value)
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Debug"))]
|
||||
async fn switch<T, F: Send + 'n>(
|
||||
#[implementations((), (), (), (), (), (), (), (), Footprint)] footprint: F,
|
||||
async fn switch<T, C: Send + 'n + Clone>(
|
||||
#[implementations(Context)] ctx: C,
|
||||
condition: bool,
|
||||
#[expose]
|
||||
#[implementations(
|
||||
() -> String, () -> bool, () -> f64, () -> u32, () -> u64, () -> DVec2, () -> VectorDataTable, () -> DAffine2,
|
||||
Footprint -> String, Footprint -> bool, Footprint -> f64, Footprint -> u32, Footprint -> u64, Footprint -> DVec2, Footprint -> VectorDataTable, Footprint -> DAffine2
|
||||
Context -> String, Context -> bool, Context -> f64, Context -> u32, Context -> u64, Context -> DVec2, Context -> VectorDataTable, Context -> DAffine2,
|
||||
)]
|
||||
if_true: impl Node<F, Output = T>,
|
||||
if_true: impl Node<C, Output = T>,
|
||||
#[expose]
|
||||
#[implementations(
|
||||
() -> String, () -> bool, () -> f64, () -> u32, () -> u64, () -> DVec2, () -> VectorDataTable, () -> DAffine2,
|
||||
Footprint -> String, Footprint -> bool, Footprint -> f64, Footprint -> u32, Footprint -> u64, Footprint -> DVec2, Footprint -> VectorDataTable, Footprint -> DAffine2
|
||||
Context -> String, Context -> bool, Context -> f64, Context -> u32, Context -> u64, Context -> DVec2, Context -> VectorDataTable, Context -> DAffine2,
|
||||
)]
|
||||
if_false: impl Node<F, Output = T>,
|
||||
if_false: impl Node<C, Output = T>,
|
||||
) -> T {
|
||||
if condition {
|
||||
if_true.eval(footprint).await
|
||||
// We can't remove these calls because we only want to evaluate the branch that we actually need
|
||||
if_true.eval(ctx).await
|
||||
} else {
|
||||
if_false.eval(footprint).await
|
||||
if_false.eval(ctx).await
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ use crate::raster::image::ImageFrameTable;
|
||||
use crate::raster::BlendMode;
|
||||
use crate::registry::types::Percentage;
|
||||
use crate::vector::style::GradientStops;
|
||||
use crate::Ctx;
|
||||
use crate::{Color, Node};
|
||||
|
||||
use math_parser::ast;
|
||||
@@ -39,7 +40,7 @@ impl ValueProvider for MathNodeContext {
|
||||
/// Calculates a mathematical expression with input values "A" and "B"
|
||||
#[node_macro::node(category("General"), properties("math_properties"))]
|
||||
fn math<U: num_traits::float::Float>(
|
||||
_: (),
|
||||
_: impl Ctx,
|
||||
/// The value of "A" when calculating the expression
|
||||
#[implementations(f64, f32)]
|
||||
operand_a: U,
|
||||
@@ -84,7 +85,7 @@ fn math<U: num_traits::float::Float>(
|
||||
/// The addition operation (+) calculates the sum of two numbers.
|
||||
#[node_macro::node(category("Math: Arithmetic"))]
|
||||
fn add<U: Add<T>, T>(
|
||||
_: (),
|
||||
_: impl Ctx,
|
||||
#[implementations(f64, &f64, f64, &f64, f32, &f32, f32, &f32, u32, &u32, u32, &u32, DVec2, f64, DVec2)] augend: U,
|
||||
#[implementations(f64, f64, &f64, &f64, f32, f32, &f32, &f32, u32, u32, &u32, &u32, DVec2, DVec2, f64)] addend: T,
|
||||
) -> <U as Add<T>>::Output {
|
||||
@@ -94,7 +95,7 @@ fn add<U: Add<T>, T>(
|
||||
/// The subtraction operation (-) calculates the difference between two numbers.
|
||||
#[node_macro::node(category("Math: Arithmetic"))]
|
||||
fn subtract<U: Sub<T>, T>(
|
||||
_: (),
|
||||
_: impl Ctx,
|
||||
#[implementations(f64, &f64, f64, &f64, f32, &f32, f32, &f32, u32, &u32, u32, &u32, DVec2, f64, DVec2)] minuend: U,
|
||||
#[implementations(f64, f64, &f64, &f64, f32, f32, &f32, &f32, u32, u32, &u32, &u32, DVec2, DVec2, f64)] subtrahend: T,
|
||||
) -> <U as Sub<T>>::Output {
|
||||
@@ -104,7 +105,7 @@ fn subtract<U: Sub<T>, T>(
|
||||
/// The multiplication operation (×) calculates the product of two numbers.
|
||||
#[node_macro::node(category("Math: Arithmetic"))]
|
||||
fn multiply<U: Mul<T>, T>(
|
||||
_: (),
|
||||
_: impl Ctx,
|
||||
#[implementations(f64, &f64, f64, &f64, f32, &f32, f32, &f32, u32, &u32, u32, &u32, DVec2, f64, DVec2)] multiplier: U,
|
||||
#[default(1.)]
|
||||
#[implementations(f64, f64, &f64, &f64, f32, f32, &f32, &f32, u32, u32, &u32, &u32, DVec2, DVec2, f64)]
|
||||
@@ -116,7 +117,7 @@ fn multiply<U: Mul<T>, T>(
|
||||
/// The division operation (÷) calculates the quotient of two numbers.
|
||||
#[node_macro::node(category("Math: Arithmetic"))]
|
||||
fn divide<U: Div<T>, T>(
|
||||
_: (),
|
||||
_: impl Ctx,
|
||||
#[implementations(f64, &f64, f64, &f64, f32, &f32, f32, &f32, u32, &u32, u32, &u32, DVec2, DVec2, f64)] numerator: U,
|
||||
#[default(1.)]
|
||||
#[implementations(f64, f64, &f64, &f64, f32, f32, &f32, &f32, u32, u32, &u32, &u32, DVec2, f64, DVec2)]
|
||||
@@ -128,7 +129,7 @@ fn divide<U: Div<T>, T>(
|
||||
/// The modulo operation (%) calculates the remainder from the division of two numbers. The sign of the result shares the sign of the numerator unless "Always Positive" is enabled.
|
||||
#[node_macro::node(category("Math: Arithmetic"))]
|
||||
fn modulo<U: Rem<T, Output: Add<T, Output: Rem<T, Output = U::Output>>>, T: Copy>(
|
||||
_: (),
|
||||
_: impl Ctx,
|
||||
#[implementations(f64, &f64, f64, &f64, f32, &f32, f32, &f32, u32, &u32, u32, &u32, DVec2, DVec2, f64)] numerator: U,
|
||||
#[default(2.)]
|
||||
#[implementations(f64, f64, &f64, &f64, f32, f32, &f32, &f32, u32, u32, &u32, &u32, DVec2, f64, DVec2)]
|
||||
@@ -145,7 +146,7 @@ fn modulo<U: Rem<T, Output: Add<T, Output: Rem<T, Output = U::Output>>>, T: Copy
|
||||
/// The exponent operation (^) calculates the result of raising a number to a power.
|
||||
#[node_macro::node(category("Math: Arithmetic"))]
|
||||
fn exponent<U: Pow<T>, T>(
|
||||
_: (),
|
||||
_: impl Ctx,
|
||||
#[implementations(f64, &f64, f64, &f64, f32, &f32, f32, &f32, u32, &u32, u32, &u32)] base: U,
|
||||
#[default(2.)]
|
||||
#[implementations(f64, f64, &f64, &f64, f32, f32, &f32, &f32, u32, u32, &u32, &u32)]
|
||||
@@ -157,7 +158,7 @@ fn exponent<U: Pow<T>, T>(
|
||||
/// The square root operation (√) calculates the nth root of a number, equivalent to raising the number to the power of 1/n.
|
||||
#[node_macro::node(category("Math: Arithmetic"))]
|
||||
fn root<U: num_traits::float::Float>(
|
||||
_: (),
|
||||
_: impl Ctx,
|
||||
#[default(2.)]
|
||||
#[implementations(f64, f32)]
|
||||
radicand: U,
|
||||
@@ -177,7 +178,7 @@ fn root<U: num_traits::float::Float>(
|
||||
/// The logarithmic function (log) calculates the logarithm of a number with a specified base. If the natural logarithm function (ln) is desired, set the base to "e".
|
||||
#[node_macro::node(category("Math: Arithmetic"))]
|
||||
fn logarithm<U: num_traits::float::Float>(
|
||||
_: (),
|
||||
_: impl Ctx,
|
||||
#[implementations(f64, f32)] value: U,
|
||||
#[default(2.)]
|
||||
#[implementations(f64, f32)]
|
||||
@@ -196,7 +197,7 @@ fn logarithm<U: num_traits::float::Float>(
|
||||
|
||||
/// The sine trigonometric function (sin) calculates the ratio of the angle's opposite side length to its hypotenuse length.
|
||||
#[node_macro::node(category("Math: Trig"))]
|
||||
fn sine<U: num_traits::float::Float>(_: (), #[implementations(f64, f32)] theta: U, radians: bool) -> U {
|
||||
fn sine<U: num_traits::float::Float>(_: impl Ctx, #[implementations(f64, f32)] theta: U, radians: bool) -> U {
|
||||
if radians {
|
||||
theta.sin()
|
||||
} else {
|
||||
@@ -206,7 +207,7 @@ fn sine<U: num_traits::float::Float>(_: (), #[implementations(f64, f32)] theta:
|
||||
|
||||
/// The cosine trigonometric function (cos) calculates the ratio of the angle's adjacent side length to its hypotenuse length.
|
||||
#[node_macro::node(category("Math: Trig"))]
|
||||
fn cosine<U: num_traits::float::Float>(_: (), #[implementations(f64, f32)] theta: U, radians: bool) -> U {
|
||||
fn cosine<U: num_traits::float::Float>(_: impl Ctx, #[implementations(f64, f32)] theta: U, radians: bool) -> U {
|
||||
if radians {
|
||||
theta.cos()
|
||||
} else {
|
||||
@@ -216,7 +217,7 @@ fn cosine<U: num_traits::float::Float>(_: (), #[implementations(f64, f32)] theta
|
||||
|
||||
/// The tangent trigonometric function (tan) calculates the ratio of the angle's opposite side length to its adjacent side length.
|
||||
#[node_macro::node(category("Math: Trig"))]
|
||||
fn tangent<U: num_traits::float::Float>(_: (), #[implementations(f64, f32)] theta: U, radians: bool) -> U {
|
||||
fn tangent<U: num_traits::float::Float>(_: impl Ctx, #[implementations(f64, f32)] theta: U, radians: bool) -> U {
|
||||
if radians {
|
||||
theta.tan()
|
||||
} else {
|
||||
@@ -226,7 +227,7 @@ fn tangent<U: num_traits::float::Float>(_: (), #[implementations(f64, f32)] thet
|
||||
|
||||
/// The inverse sine trigonometric function (asin) calculates the angle whose sine is the specified value.
|
||||
#[node_macro::node(category("Math: Trig"))]
|
||||
fn sine_inverse<U: num_traits::float::Float>(_: (), #[implementations(f64, f32)] value: U, radians: bool) -> U {
|
||||
fn sine_inverse<U: num_traits::float::Float>(_: impl Ctx, #[implementations(f64, f32)] value: U, radians: bool) -> U {
|
||||
if radians {
|
||||
value.asin()
|
||||
} else {
|
||||
@@ -236,7 +237,7 @@ fn sine_inverse<U: num_traits::float::Float>(_: (), #[implementations(f64, f32)]
|
||||
|
||||
/// The inverse cosine trigonometric function (acos) calculates the angle whose cosine is the specified value.
|
||||
#[node_macro::node(category("Math: Trig"))]
|
||||
fn cosine_inverse<U: num_traits::float::Float>(_: (), #[implementations(f64, f32)] value: U, radians: bool) -> U {
|
||||
fn cosine_inverse<U: num_traits::float::Float>(_: impl Ctx, #[implementations(f64, f32)] value: U, radians: bool) -> U {
|
||||
if radians {
|
||||
value.acos()
|
||||
} else {
|
||||
@@ -246,7 +247,7 @@ fn cosine_inverse<U: num_traits::float::Float>(_: (), #[implementations(f64, f32
|
||||
|
||||
/// The inverse tangent trigonometric function (atan) calculates the angle whose tangent is the specified value.
|
||||
#[node_macro::node(category("Math: Trig"))]
|
||||
fn tangent_inverse<U: num_traits::float::Float>(_: (), #[implementations(f64, f32)] value: U, radians: bool) -> U {
|
||||
fn tangent_inverse<U: num_traits::float::Float>(_: impl Ctx, #[implementations(f64, f32)] value: U, radians: bool) -> U {
|
||||
if radians {
|
||||
value.atan()
|
||||
} else {
|
||||
@@ -257,7 +258,7 @@ fn tangent_inverse<U: num_traits::float::Float>(_: (), #[implementations(f64, f3
|
||||
/// The inverse tangent trigonometric function (atan2) calculates the angle whose tangent is the ratio of the two specified values.
|
||||
#[node_macro::node(name("Tangent Inverse 2-Argument"), category("Math: Trig"))]
|
||||
fn tangent_inverse_2_argument<U: num_traits::float::Float>(
|
||||
_: (),
|
||||
_: impl Ctx,
|
||||
#[implementations(f64, f32)] y: U,
|
||||
#[expose]
|
||||
#[implementations(f64, f32)]
|
||||
@@ -274,7 +275,7 @@ fn tangent_inverse_2_argument<U: num_traits::float::Float>(
|
||||
/// The random function (rand) converts a seed into a random number within the specified range, inclusive of the minimum and exclusive of the maximum. The minimum and maximum values are automatically swapped if they are reversed.
|
||||
#[node_macro::node(category("Math: Numeric"))]
|
||||
fn random<U: num_traits::float::Float>(
|
||||
_: (),
|
||||
_: impl Ctx,
|
||||
_primary: (),
|
||||
seed: u64,
|
||||
#[implementations(f64, f32)]
|
||||
@@ -293,45 +294,45 @@ fn random<U: num_traits::float::Float>(
|
||||
|
||||
/// Convert a number to an integer of the type u32, which may be the required type for certain node inputs. This will be removed in the future when automatic type conversion is implemented.
|
||||
#[node_macro::node(name("To u32"), category("Math: Numeric"))]
|
||||
fn to_u32<U: num_traits::float::Float>(_: (), #[implementations(f64, f32)] value: U) -> u32 {
|
||||
fn to_u32<U: num_traits::float::Float>(_: impl Ctx, #[implementations(f64, f32)] value: U) -> u32 {
|
||||
let value = U::clamp(value, U::from(0.).unwrap(), U::from(u32::MAX as f64).unwrap());
|
||||
value.to_u32().unwrap()
|
||||
}
|
||||
|
||||
/// Convert a number to an integer of the type u64, which may be the required type for certain node inputs. This will be removed in the future when automatic type conversion is implemented.
|
||||
#[node_macro::node(name("To u64"), category("Math: Numeric"))]
|
||||
fn to_u64<U: num_traits::float::Float>(_: (), #[implementations(f64, f32)] value: U) -> u64 {
|
||||
fn to_u64<U: num_traits::float::Float>(_: impl Ctx, #[implementations(f64, f32)] value: U) -> u64 {
|
||||
let value = U::clamp(value, U::from(0.).unwrap(), U::from(u64::MAX as f64).unwrap());
|
||||
value.to_u64().unwrap()
|
||||
}
|
||||
|
||||
/// The rounding function (round) maps an input value to its nearest whole number. Halfway values are rounded away from zero.
|
||||
#[node_macro::node(category("Math: Numeric"))]
|
||||
fn round<U: num_traits::float::Float>(_: (), #[implementations(f64, f32)] value: U) -> U {
|
||||
fn round<U: num_traits::float::Float>(_: impl Ctx, #[implementations(f64, f32)] value: U) -> U {
|
||||
value.round()
|
||||
}
|
||||
|
||||
/// The floor function (floor) reduces an input value to its nearest larger whole number, unless the input number is already whole.
|
||||
#[node_macro::node(category("Math: Numeric"))]
|
||||
fn floor<U: num_traits::float::Float>(_: (), #[implementations(f64, f32)] value: U) -> U {
|
||||
fn floor<U: num_traits::float::Float>(_: impl Ctx, #[implementations(f64, f32)] value: U) -> U {
|
||||
value.floor()
|
||||
}
|
||||
|
||||
/// The ceiling function (ceil) increases an input value to its nearest smaller whole number, unless the input number is already whole.
|
||||
#[node_macro::node(category("Math: Numeric"))]
|
||||
fn ceiling<U: num_traits::float::Float>(_: (), #[implementations(f64, f32)] value: U) -> U {
|
||||
fn ceiling<U: num_traits::float::Float>(_: impl Ctx, #[implementations(f64, f32)] value: U) -> U {
|
||||
value.ceil()
|
||||
}
|
||||
|
||||
/// The absolute value function (abs) removes the negative sign from an input value, if present.
|
||||
#[node_macro::node(category("Math: Numeric"))]
|
||||
fn absolute_value<U: num_traits::float::Float>(_: (), #[implementations(f64, f32)] value: U) -> U {
|
||||
fn absolute_value<U: num_traits::float::Float>(_: impl Ctx, #[implementations(f64, f32)] value: U) -> U {
|
||||
value.abs()
|
||||
}
|
||||
|
||||
/// The minimum function (min) picks the smaller of two numbers.
|
||||
#[node_macro::node(category("Math: Numeric"))]
|
||||
fn min<T: core::cmp::PartialOrd>(_: (), #[implementations(f64, &f64, f32, &f32, u32, &u32, &str)] value: T, #[implementations(f64, &f64, f32, &f32, u32, &u32, &str)] other_value: T) -> T {
|
||||
fn min<T: core::cmp::PartialOrd>(_: impl Ctx, #[implementations(f64, &f64, f32, &f32, u32, &u32, &str)] value: T, #[implementations(f64, &f64, f32, &f32, u32, &u32, &str)] other_value: T) -> T {
|
||||
match value < other_value {
|
||||
true => value,
|
||||
false => other_value,
|
||||
@@ -340,7 +341,7 @@ fn min<T: core::cmp::PartialOrd>(_: (), #[implementations(f64, &f64, f32, &f32,
|
||||
|
||||
/// The maximum function (max) picks the larger of two numbers.
|
||||
#[node_macro::node(category("Math: Numeric"))]
|
||||
fn max<T: core::cmp::PartialOrd>(_: (), #[implementations(f64, &f64, f32, &f32, u32, &u32, &str)] value: T, #[implementations(f64, &f64, f32, &f32, u32, &u32, &str)] other_value: T) -> T {
|
||||
fn max<T: core::cmp::PartialOrd>(_: impl Ctx, #[implementations(f64, &f64, f32, &f32, u32, &u32, &str)] value: T, #[implementations(f64, &f64, f32, &f32, u32, &u32, &str)] other_value: T) -> T {
|
||||
match value > other_value {
|
||||
true => value,
|
||||
false => other_value,
|
||||
@@ -350,7 +351,7 @@ fn max<T: core::cmp::PartialOrd>(_: (), #[implementations(f64, &f64, f32, &f32,
|
||||
/// The clamp function (clamp) restricts a number to a specified range between a minimum and maximum value. The minimum and maximum values are automatically swapped if they are reversed.
|
||||
#[node_macro::node(category("Math: Numeric"))]
|
||||
fn clamp<T: core::cmp::PartialOrd>(
|
||||
_: (),
|
||||
_: impl Ctx,
|
||||
#[implementations(f64, &f64, f32, &f32, u32, &u32, &str)] value: T,
|
||||
#[implementations(f64, &f64, f32, &f32, u32, &u32, &str)] min: T,
|
||||
#[implementations(f64, &f64, f32, &f32, u32, &u32, &str)] max: T,
|
||||
@@ -368,7 +369,7 @@ fn clamp<T: core::cmp::PartialOrd>(
|
||||
/// The equality operation (==) compares two values and returns true if they are equal, or false if they are not.
|
||||
#[node_macro::node(category("Math: Logic"))]
|
||||
fn equals<U: core::cmp::PartialEq<T>, T>(
|
||||
_: (),
|
||||
_: impl Ctx,
|
||||
#[implementations(f64, &f64, f32, &f32, u32, &u32, DVec2, &DVec2, &str)] value: T,
|
||||
#[implementations(f64, &f64, f32, &f32, u32, &u32, DVec2, &DVec2, &str)]
|
||||
#[min(100.)]
|
||||
@@ -381,7 +382,7 @@ fn equals<U: core::cmp::PartialEq<T>, T>(
|
||||
/// The inequality operation (!=) compares two values and returns true if they are not equal, or false if they are.
|
||||
#[node_macro::node(category("Math: Logic"))]
|
||||
fn not_equals<U: core::cmp::PartialEq<T>, T>(
|
||||
_: (),
|
||||
_: impl Ctx,
|
||||
#[implementations(f64, &f64, f32, &f32, u32, &u32, DVec2, &DVec2, &str)] value: T,
|
||||
#[implementations(f64, &f64, f32, &f32, u32, &u32, DVec2, &DVec2, &str)]
|
||||
#[min(100.)]
|
||||
@@ -393,98 +394,98 @@ fn not_equals<U: core::cmp::PartialEq<T>, T>(
|
||||
|
||||
/// The logical or operation (||) returns true if either of the two inputs are true, or false if both are false.
|
||||
#[node_macro::node(category("Math: Logic"))]
|
||||
fn logical_or(_: (), value: bool, other_value: bool) -> bool {
|
||||
fn logical_or(_: impl Ctx, value: bool, other_value: bool) -> bool {
|
||||
value || other_value
|
||||
}
|
||||
|
||||
/// The logical and operation (&&) returns true if both of the two inputs are true, or false if any are false.
|
||||
#[node_macro::node(category("Math: Logic"))]
|
||||
fn logical_and(_: (), value: bool, other_value: bool) -> bool {
|
||||
fn logical_and(_: impl Ctx, value: bool, other_value: bool) -> bool {
|
||||
value && other_value
|
||||
}
|
||||
|
||||
/// The logical not operation (!) reverses true and false value of the input.
|
||||
#[node_macro::node(category("Math: Logic"))]
|
||||
fn logical_not(_: (), input: bool) -> bool {
|
||||
fn logical_not(_: impl Ctx, input: bool) -> bool {
|
||||
!input
|
||||
}
|
||||
|
||||
/// Constructs a bool value which may be set to true or false.
|
||||
#[node_macro::node(category("Value"))]
|
||||
fn bool_value(_: (), _primary: (), #[name("Bool")] bool_value: bool) -> bool {
|
||||
fn bool_value(_: impl Ctx, _primary: (), #[name("Bool")] bool_value: bool) -> bool {
|
||||
bool_value
|
||||
}
|
||||
|
||||
/// Constructs a number value which may be set to any real number.
|
||||
#[node_macro::node(category("Value"))]
|
||||
fn number_value(_: (), _primary: (), number: f64) -> f64 {
|
||||
fn number_value(_: impl Ctx, _primary: (), number: f64) -> f64 {
|
||||
number
|
||||
}
|
||||
|
||||
/// Constructs a number value which may be set to any value from 0% to 100% by dragging the slider.
|
||||
#[node_macro::node(category("Value"))]
|
||||
fn percentage_value(_: (), _primary: (), percentage: Percentage) -> f64 {
|
||||
fn percentage_value(_: impl Ctx, _primary: (), percentage: Percentage) -> f64 {
|
||||
percentage
|
||||
}
|
||||
|
||||
/// Constructs a two-dimensional vector value which may be set to any XY coordinate.
|
||||
#[node_macro::node(category("Value"))]
|
||||
fn vector2_value(_: (), _primary: (), x: f64, y: f64) -> DVec2 {
|
||||
#[node_macro::node(name("Vector2 Value"), category("Value"))]
|
||||
fn vector2_value(_: impl Ctx, _primary: (), x: f64, y: f64) -> DVec2 {
|
||||
DVec2::new(x, y)
|
||||
}
|
||||
|
||||
/// Constructs a color value which may be set to any color, or no color.
|
||||
#[node_macro::node(category("Value"))]
|
||||
fn color_value(_: (), _primary: (), #[default(Color::BLACK)] color: Option<Color>) -> Option<Color> {
|
||||
fn color_value(_: impl Ctx, _primary: (), #[default(Color::BLACK)] color: Option<Color>) -> Option<Color> {
|
||||
color
|
||||
}
|
||||
|
||||
/// Constructs a gradient value which may be set to any sequence of color stops to represent the transition between colors.
|
||||
#[node_macro::node(category("Value"))]
|
||||
fn gradient_value(_: (), _primary: (), gradient: GradientStops) -> GradientStops {
|
||||
fn gradient_value(_: impl Ctx, _primary: (), gradient: GradientStops) -> GradientStops {
|
||||
gradient
|
||||
}
|
||||
|
||||
/// Constructs a blend mode choice value which may be set to any of the available blend modes in order to tell another node which blending operation to use.
|
||||
#[node_macro::node(category("Value"))]
|
||||
fn blend_mode_value(_: (), _primary: (), blend_mode: BlendMode) -> BlendMode {
|
||||
fn blend_mode_value(_: impl Ctx, _primary: (), blend_mode: BlendMode) -> BlendMode {
|
||||
blend_mode
|
||||
}
|
||||
|
||||
/// Meant for debugging purposes, not general use. Returns the size of the input type in bytes.
|
||||
#[cfg(feature = "std")]
|
||||
#[node_macro::node(category("Debug"))]
|
||||
fn size_of(_: (), ty: crate::Type) -> Option<usize> {
|
||||
fn size_of(_: impl Ctx, ty: crate::Type) -> Option<usize> {
|
||||
ty.size()
|
||||
}
|
||||
|
||||
/// Meant for debugging purposes, not general use. Wraps the input value in the Some variant of an Option.
|
||||
#[node_macro::node(category("Debug"))]
|
||||
fn some<T>(_: (), #[implementations(f64, f32, u32, u64, String, Color)] input: T) -> Option<T> {
|
||||
fn some<T>(_: impl Ctx, #[implementations(f64, f32, u32, u64, String, Color)] input: T) -> Option<T> {
|
||||
Some(input)
|
||||
}
|
||||
|
||||
/// Meant for debugging purposes, not general use. Unwraps the input value from an Option, returning the default value if the input is None.
|
||||
#[node_macro::node(category("Debug"))]
|
||||
fn unwrap<T: Default>(_: (), #[implementations(Option<f64>, Option<f32>, Option<u32>, Option<u64>, Option<String>, Option<Color>)] input: Option<T>) -> T {
|
||||
fn unwrap<T: Default>(_: impl Ctx, #[implementations(Option<f64>, Option<f32>, Option<u32>, Option<u64>, Option<String>, Option<Color>)] input: Option<T>) -> T {
|
||||
input.unwrap_or_default()
|
||||
}
|
||||
|
||||
/// Meant for debugging purposes, not general use. Clones the input value.
|
||||
#[node_macro::node(category("Debug"))]
|
||||
fn clone<'i, T: Clone + 'i>(_: (), #[implementations(&ImageFrameTable<Color>)] value: &'i T) -> T {
|
||||
fn clone<'i, T: Clone + 'i>(_: impl Ctx, #[implementations(&ImageFrameTable<Color>)] value: &'i T) -> T {
|
||||
value.clone()
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Math: Vector"))]
|
||||
fn dot_product(vector_a: DVec2, vector_b: DVec2) -> f64 {
|
||||
fn dot_product(_: impl Ctx, vector_a: DVec2, vector_b: DVec2) -> f64 {
|
||||
vector_a.dot(vector_b)
|
||||
}
|
||||
|
||||
// TODO: Rename to "Passthrough"
|
||||
/// Passes-through the input value without changing it. This is useful for rerouting wires for organization purposes.
|
||||
#[node_macro::node(skip_impl)]
|
||||
fn identity<'i, T: 'i>(value: T) -> T {
|
||||
fn identity<'i, T: 'i + Send>(value: T) -> T {
|
||||
value
|
||||
}
|
||||
|
||||
@@ -537,13 +538,13 @@ where
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use crate::{generic::*, structural::*, value::*};
|
||||
use crate::generic::*;
|
||||
|
||||
#[test]
|
||||
pub fn dot_product_function() {
|
||||
let vector_a = glam::DVec2::new(1., 2.);
|
||||
let vector_b = glam::DVec2::new(3., 4.);
|
||||
assert_eq!(dot_product(vector_a, vector_b), 11.);
|
||||
assert_eq!(dot_product((), vector_a, vector_b), 11.);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -572,8 +573,7 @@ mod test {
|
||||
|
||||
#[test]
|
||||
pub fn identity_node() {
|
||||
let value = ValueNode(4u32).then(IdentityNode::new());
|
||||
assert_eq!(value.eval(()), &4);
|
||||
assert_eq!(identity(&4), &4);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
pub use self::color::{Color, Luma, SRGBA8};
|
||||
use crate::raster::image::ImageFrameTable;
|
||||
use crate::registry::types::Percentage;
|
||||
use crate::transform::Footprint;
|
||||
use crate::vector::VectorDataTable;
|
||||
use crate::Ctx;
|
||||
use crate::GraphicGroupTable;
|
||||
|
||||
use bytemuck::{Pod, Zeroable};
|
||||
@@ -182,6 +182,9 @@ pub trait Alpha {
|
||||
}
|
||||
fn multiplied_alpha(&self, alpha: Self::AlphaChannel) -> Self;
|
||||
}
|
||||
pub trait AlphaMut: Alpha {
|
||||
fn set_alpha(&mut self, value: Self::AlphaChannel);
|
||||
}
|
||||
|
||||
pub trait Depth {
|
||||
type DepthChannel: Channel;
|
||||
@@ -228,6 +231,12 @@ pub trait Bitmap {
|
||||
type Pixel: Pixel;
|
||||
fn width(&self) -> u32;
|
||||
fn height(&self) -> u32;
|
||||
fn dimensions(&self) -> (u32, u32) {
|
||||
(self.width(), self.height())
|
||||
}
|
||||
fn dim(&self) -> (u32, u32) {
|
||||
self.dimensions()
|
||||
}
|
||||
fn get_pixel(&self, x: u32, y: u32) -> Option<Self::Pixel>;
|
||||
}
|
||||
|
||||
@@ -316,51 +325,31 @@ impl SetBlendMode for ImageFrameTable<Color> {
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Style"))]
|
||||
async fn blend_mode<F: 'n + Send, T: SetBlendMode>(
|
||||
fn blend_mode<T: SetBlendMode>(
|
||||
_: impl Ctx,
|
||||
#[implementations(
|
||||
(),
|
||||
(),
|
||||
(),
|
||||
Footprint,
|
||||
GraphicGroupTable,
|
||||
VectorDataTable,
|
||||
ImageFrameTable<Color>,
|
||||
)]
|
||||
footprint: F,
|
||||
#[implementations(
|
||||
() -> GraphicGroupTable,
|
||||
() -> VectorDataTable,
|
||||
() -> ImageFrameTable<Color>,
|
||||
Footprint -> GraphicGroupTable,
|
||||
Footprint -> VectorDataTable,
|
||||
Footprint -> ImageFrameTable<Color>,
|
||||
)]
|
||||
value: impl Node<F, Output = T>,
|
||||
mut value: T,
|
||||
blend_mode: BlendMode,
|
||||
) -> T {
|
||||
let mut value = value.eval(footprint).await;
|
||||
value.set_blend_mode(blend_mode);
|
||||
value
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Style"))]
|
||||
async fn opacity<F: 'n + Send, T: MultiplyAlpha>(
|
||||
fn opacity<T: MultiplyAlpha>(
|
||||
_: impl Ctx,
|
||||
#[implementations(
|
||||
(),
|
||||
(),
|
||||
(),
|
||||
Footprint,
|
||||
GraphicGroupTable,
|
||||
VectorDataTable,
|
||||
ImageFrameTable<Color>,
|
||||
)]
|
||||
footprint: F,
|
||||
#[implementations(
|
||||
() -> GraphicGroupTable,
|
||||
() -> VectorDataTable,
|
||||
() -> ImageFrameTable<Color>,
|
||||
Footprint -> GraphicGroupTable,
|
||||
Footprint -> VectorDataTable,
|
||||
Footprint -> ImageFrameTable<Color>,
|
||||
)]
|
||||
value: impl Node<F, Output = T>,
|
||||
mut value: T,
|
||||
#[default(100.)] factor: Percentage,
|
||||
) -> T {
|
||||
let mut value = value.eval(footprint).await;
|
||||
let opacity_multiplier = factor / 100.;
|
||||
value.multiply_alpha(opacity_multiplier);
|
||||
value
|
||||
|
||||
@@ -6,9 +6,9 @@ use crate::raster::curve::{Curve, CurveManipulatorGroup, ValueMapperNode};
|
||||
use crate::raster::image::{ImageFrame, ImageFrameTable};
|
||||
use crate::raster::{Channel, Color, Pixel};
|
||||
use crate::registry::types::{Angle, Percentage, SignedPercentage};
|
||||
use crate::transform::Footprint;
|
||||
use crate::vector::style::GradientStops;
|
||||
use crate::vector::VectorDataTable;
|
||||
use crate::{Ctx, Node};
|
||||
use crate::{GraphicElement, GraphicGroupTable};
|
||||
|
||||
use dyn_any::DynAny;
|
||||
@@ -284,26 +284,16 @@ impl From<BlendMode> for vello::peniko::Mix {
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Raster: Adjustment"))]
|
||||
async fn luminance<F: 'n + Send, T: Adjust<Color>>(
|
||||
fn luminance<T: Adjust<Color>>(
|
||||
_: impl Ctx,
|
||||
#[implementations(
|
||||
(),
|
||||
(),
|
||||
(),
|
||||
Footprint,
|
||||
Color,
|
||||
ImageFrameTable<Color>,
|
||||
GradientStops,
|
||||
)]
|
||||
footprint: F,
|
||||
#[implementations(
|
||||
() -> Color,
|
||||
() -> ImageFrameTable<Color>,
|
||||
() -> GradientStops,
|
||||
Footprint -> Color,
|
||||
Footprint -> ImageFrameTable<Color>,
|
||||
Footprint -> GradientStops,
|
||||
)]
|
||||
input: impl Node<F, Output = T>,
|
||||
mut input: T,
|
||||
luminance_calc: LuminanceCalculation,
|
||||
) -> T {
|
||||
let mut input = input.eval(footprint).await;
|
||||
input.adjust(|color| {
|
||||
let luminance = match luminance_calc {
|
||||
LuminanceCalculation::SRGB => color.luminance_srgb(),
|
||||
@@ -318,26 +308,16 @@ async fn luminance<F: 'n + Send, T: Adjust<Color>>(
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Raster"))]
|
||||
async fn extract_channel<F: 'n + Send, T: Adjust<Color>>(
|
||||
fn extract_channel<T: Adjust<Color>>(
|
||||
_: impl Ctx,
|
||||
#[implementations(
|
||||
(),
|
||||
(),
|
||||
(),
|
||||
Footprint,
|
||||
Color,
|
||||
ImageFrameTable<Color>,
|
||||
GradientStops,
|
||||
)]
|
||||
footprint: F,
|
||||
#[implementations(
|
||||
() -> Color,
|
||||
() -> ImageFrameTable<Color>,
|
||||
() -> GradientStops,
|
||||
Footprint -> Color,
|
||||
Footprint -> ImageFrameTable<Color>,
|
||||
Footprint -> GradientStops,
|
||||
)]
|
||||
input: impl Node<F, Output = T>,
|
||||
mut input: T,
|
||||
channel: RedGreenBlueAlpha,
|
||||
) -> T {
|
||||
let mut input = input.eval(footprint).await;
|
||||
input.adjust(|color| {
|
||||
let extracted_value = match channel {
|
||||
RedGreenBlueAlpha::Red => color.r(),
|
||||
@@ -351,25 +331,15 @@ async fn extract_channel<F: 'n + Send, T: Adjust<Color>>(
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Raster"))]
|
||||
async fn make_opaque<F: 'n + Send, T: Adjust<Color>>(
|
||||
fn make_opaque<T: Adjust<Color>>(
|
||||
_: impl Ctx,
|
||||
#[implementations(
|
||||
(),
|
||||
(),
|
||||
(),
|
||||
Footprint,
|
||||
Color,
|
||||
ImageFrameTable<Color>,
|
||||
GradientStops,
|
||||
)]
|
||||
footprint: F,
|
||||
#[implementations(
|
||||
() -> Color,
|
||||
() -> ImageFrameTable<Color>,
|
||||
() -> GradientStops,
|
||||
Footprint -> Color,
|
||||
Footprint -> ImageFrameTable<Color>,
|
||||
Footprint -> GradientStops,
|
||||
)]
|
||||
input: impl Node<F, Output = T>,
|
||||
mut input: T,
|
||||
) -> T {
|
||||
let mut input = input.eval(footprint).await;
|
||||
input.adjust(|color| {
|
||||
if color.a() == 0. {
|
||||
return color.with_alpha(1.);
|
||||
@@ -385,31 +355,21 @@ async fn make_opaque<F: 'n + Send, T: Adjust<Color>>(
|
||||
// Algorithm from:
|
||||
// https://stackoverflow.com/questions/39510072/algorithm-for-adjustment-of-image-levels
|
||||
#[node_macro::node(category("Raster: Adjustment"))]
|
||||
async fn levels<F: 'n + Send, T: Adjust<Color>>(
|
||||
fn levels<T: Adjust<Color>>(
|
||||
_: impl Ctx,
|
||||
#[implementations(
|
||||
(),
|
||||
(),
|
||||
(),
|
||||
Footprint,
|
||||
Color,
|
||||
ImageFrameTable<Color>,
|
||||
GradientStops,
|
||||
)]
|
||||
footprint: F,
|
||||
#[implementations(
|
||||
() -> Color,
|
||||
() -> ImageFrameTable<Color>,
|
||||
() -> GradientStops,
|
||||
Footprint -> Color,
|
||||
Footprint -> ImageFrameTable<Color>,
|
||||
Footprint -> GradientStops,
|
||||
)]
|
||||
image: impl Node<F, Output = T>,
|
||||
mut image: T,
|
||||
#[default(0.)] shadows: Percentage,
|
||||
#[default(50.)] midtones: Percentage,
|
||||
#[default(100.)] highlights: Percentage,
|
||||
#[default(0.)] output_minimums: Percentage,
|
||||
#[default(100.)] output_maximums: Percentage,
|
||||
) -> T {
|
||||
let mut input = image.eval(footprint).await;
|
||||
input.adjust(|color| {
|
||||
image.adjust(|color| {
|
||||
let color = color.to_gamma_srgb();
|
||||
|
||||
// Input Range (Range: 0-1)
|
||||
@@ -451,7 +411,7 @@ async fn levels<F: 'n + Send, T: Adjust<Color>>(
|
||||
|
||||
color.to_linear_srgb()
|
||||
});
|
||||
input
|
||||
image
|
||||
}
|
||||
|
||||
// Aims for interoperable compatibility with:
|
||||
@@ -462,23 +422,14 @@ async fn levels<F: 'n + Send, T: Adjust<Color>>(
|
||||
// https://stackoverflow.com/a/55233732/775283
|
||||
// Works the same for gamma and linear color
|
||||
#[node_macro::node(name("Black & White"), category("Raster: Adjustment"))]
|
||||
async fn black_and_white<F: 'n + Send, T: Adjust<Color>>(
|
||||
async fn black_and_white<T: Adjust<Color>>(
|
||||
_: impl Ctx,
|
||||
#[implementations(
|
||||
(),
|
||||
(),
|
||||
(),
|
||||
Footprint,
|
||||
Color,
|
||||
ImageFrameTable<Color>,
|
||||
GradientStops,
|
||||
)]
|
||||
footprint: F,
|
||||
#[implementations(
|
||||
() -> Color,
|
||||
() -> ImageFrameTable<Color>,
|
||||
() -> GradientStops,
|
||||
Footprint -> Color,
|
||||
Footprint -> ImageFrameTable<Color>,
|
||||
Footprint -> GradientStops,
|
||||
)]
|
||||
image: impl Node<F, Output = T>,
|
||||
mut image: T,
|
||||
#[default(Color::BLACK)] tint: Color,
|
||||
#[default(40.)]
|
||||
#[range((-200., 300.))]
|
||||
@@ -499,8 +450,7 @@ async fn black_and_white<F: 'n + Send, T: Adjust<Color>>(
|
||||
#[range((-200., 300.))]
|
||||
magentas: Percentage,
|
||||
) -> T {
|
||||
let mut input = image.eval(footprint).await;
|
||||
input.adjust(|color| {
|
||||
image.adjust(|color| {
|
||||
let color = color.to_gamma_srgb();
|
||||
|
||||
let reds = reds as f32 / 100.;
|
||||
@@ -537,35 +487,25 @@ async fn black_and_white<F: 'n + Send, T: Adjust<Color>>(
|
||||
|
||||
color.to_linear_srgb()
|
||||
});
|
||||
input
|
||||
image
|
||||
}
|
||||
|
||||
// Aims for interoperable compatibility with:
|
||||
// https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=%27hue%20%27%20%3D%20Old,saturation%2C%20Photoshop%205.0
|
||||
// https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=0%20%3D%20Use%20other.-,Hue/Saturation,-Hue/Saturation%20settings
|
||||
#[node_macro::node(name("Hue/Saturation"), category("Raster: Adjustment"))]
|
||||
async fn hue_saturation<F: 'n + Send, T: Adjust<Color>>(
|
||||
async fn hue_saturation<T: Adjust<Color>>(
|
||||
_: impl Ctx,
|
||||
#[implementations(
|
||||
(),
|
||||
(),
|
||||
(),
|
||||
Footprint,
|
||||
Color,
|
||||
ImageFrameTable<Color>,
|
||||
GradientStops,
|
||||
)]
|
||||
footprint: F,
|
||||
#[implementations(
|
||||
() -> Color,
|
||||
() -> ImageFrameTable<Color>,
|
||||
() -> GradientStops,
|
||||
Footprint -> Color,
|
||||
Footprint -> ImageFrameTable<Color>,
|
||||
Footprint -> GradientStops,
|
||||
)]
|
||||
input: impl Node<F, Output = T>,
|
||||
mut input: T,
|
||||
hue_shift: Angle,
|
||||
saturation_shift: SignedPercentage,
|
||||
lightness_shift: SignedPercentage,
|
||||
) -> T {
|
||||
let mut input = input.eval(footprint).await;
|
||||
input.adjust(|color| {
|
||||
let color = color.to_gamma_srgb();
|
||||
|
||||
@@ -588,25 +528,15 @@ async fn hue_saturation<F: 'n + Send, T: Adjust<Color>>(
|
||||
// Aims for interoperable compatibility with:
|
||||
// https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=%27%20%3D%20Color%20Lookup-,%27nvrt%27%20%3D%20Invert,-%27post%27%20%3D%20Posterize
|
||||
#[node_macro::node(category("Raster: Adjustment"))]
|
||||
async fn invert<F: 'n + Send, T: Adjust<Color>>(
|
||||
async fn invert<T: Adjust<Color>>(
|
||||
_: impl Ctx,
|
||||
#[implementations(
|
||||
(),
|
||||
(),
|
||||
(),
|
||||
Footprint,
|
||||
Color,
|
||||
ImageFrameTable<Color>,
|
||||
GradientStops,
|
||||
)]
|
||||
footprint: F,
|
||||
#[implementations(
|
||||
() -> Color,
|
||||
() -> ImageFrameTable<Color>,
|
||||
() -> GradientStops,
|
||||
Footprint -> Color,
|
||||
Footprint -> ImageFrameTable<Color>,
|
||||
Footprint -> GradientStops,
|
||||
)]
|
||||
input: impl Node<F, Output = T>,
|
||||
mut input: T,
|
||||
) -> T {
|
||||
let mut input = input.eval(footprint).await;
|
||||
input.adjust(|color| {
|
||||
let color = color.to_gamma_srgb();
|
||||
|
||||
@@ -620,29 +550,19 @@ async fn invert<F: 'n + Send, T: Adjust<Color>>(
|
||||
// Aims for interoperable compatibility with:
|
||||
// https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=post%27%20%3D%20Posterize-,%27thrs%27%20%3D%20Threshold,-%27grdm%27%20%3D%20Gradient
|
||||
#[node_macro::node(category("Raster: Adjustment"))]
|
||||
async fn threshold<F: 'n + Send, T: Adjust<Color>>(
|
||||
async fn threshold<T: Adjust<Color>>(
|
||||
_: impl Ctx,
|
||||
#[implementations(
|
||||
(),
|
||||
(),
|
||||
(),
|
||||
Footprint,
|
||||
Color,
|
||||
ImageFrameTable<Color>,
|
||||
GradientStops,
|
||||
)]
|
||||
footprint: F,
|
||||
#[implementations(
|
||||
() -> Color,
|
||||
() -> ImageFrameTable<Color>,
|
||||
() -> GradientStops,
|
||||
Footprint -> Color,
|
||||
Footprint -> ImageFrameTable<Color>,
|
||||
Footprint -> GradientStops,
|
||||
)]
|
||||
image: impl Node<F, Output = T>,
|
||||
mut image: T,
|
||||
#[default(50.)] min_luminance: Percentage,
|
||||
#[default(100.)] max_luminance: Percentage,
|
||||
luminance_calc: LuminanceCalculation,
|
||||
) -> T {
|
||||
let mut input = image.eval(footprint).await;
|
||||
input.adjust(|color| {
|
||||
image.adjust(|color| {
|
||||
let min_luminance = Color::srgb_to_linear(min_luminance as f32 / 100.);
|
||||
let max_luminance = Color::srgb_to_linear(max_luminance as f32 / 100.);
|
||||
|
||||
@@ -660,7 +580,7 @@ async fn threshold<F: 'n + Send, T: Adjust<Color>>(
|
||||
Color::BLACK
|
||||
}
|
||||
});
|
||||
input
|
||||
image
|
||||
}
|
||||
|
||||
trait Blend<P: Pixel> {
|
||||
@@ -723,44 +643,35 @@ impl Blend<Color> for GradientStops {
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Raster"))]
|
||||
async fn blend<F: 'n + Send + Copy, T: Blend<Color> + Send>(
|
||||
async fn blend<T: Blend<Color> + Send>(
|
||||
_: impl Ctx,
|
||||
#[implementations(
|
||||
(),
|
||||
(),
|
||||
(),
|
||||
Footprint,
|
||||
Color,
|
||||
ImageFrameTable<Color>,
|
||||
GradientStops,
|
||||
)]
|
||||
footprint: F,
|
||||
#[implementations(
|
||||
() -> Color,
|
||||
() -> ImageFrameTable<Color>,
|
||||
() -> GradientStops,
|
||||
Footprint -> Color,
|
||||
Footprint -> ImageFrameTable<Color>,
|
||||
Footprint -> GradientStops,
|
||||
)]
|
||||
over: impl Node<F, Output = T>,
|
||||
over: T,
|
||||
#[expose]
|
||||
#[implementations(
|
||||
() -> Color,
|
||||
() -> ImageFrameTable<Color>,
|
||||
() -> GradientStops,
|
||||
Footprint -> Color,
|
||||
Footprint -> ImageFrameTable<Color>,
|
||||
Footprint -> GradientStops,
|
||||
Color,
|
||||
ImageFrameTable<Color>,
|
||||
GradientStops,
|
||||
)]
|
||||
under: impl Node<F, Output = T>,
|
||||
under: T,
|
||||
blend_mode: BlendMode,
|
||||
#[default(100.)] opacity: Percentage,
|
||||
) -> T {
|
||||
let over = over.eval(footprint).await;
|
||||
let under = under.eval(footprint).await;
|
||||
|
||||
over.blend(&under, |a, b| blend_colors(a, b, blend_mode, opacity / 100.))
|
||||
}
|
||||
|
||||
#[node_macro::node(category(""))]
|
||||
fn blend_color_pair(input: (Color, Color), blend_mode: BlendMode, opacity: Percentage) -> Color {
|
||||
#[node_macro::node(category(""), skip_impl)]
|
||||
fn blend_color_pair<BlendModeNode, OpacityNode>(input: (Color, Color), blend_mode: &'n BlendModeNode, opacity: &'n OpacityNode) -> Color
|
||||
where
|
||||
BlendModeNode: Node<'n, (), Output = BlendMode> + 'n,
|
||||
OpacityNode: Node<'n, (), Output = Percentage> + 'n,
|
||||
{
|
||||
let blend_mode = blend_mode.eval(());
|
||||
let opacity = opacity.eval(());
|
||||
blend_colors(input.0, input.1, blend_mode, opacity / 100.)
|
||||
}
|
||||
|
||||
@@ -857,35 +768,24 @@ pub fn blend_colors(foreground: Color, background: Color, blend_mode: BlendMode,
|
||||
// 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<F: 'n + Send, T: Adjust<Color>>(
|
||||
async fn gradient_map<T: Adjust<Color>>(
|
||||
_: impl Ctx,
|
||||
#[implementations(
|
||||
(),
|
||||
(),
|
||||
(),
|
||||
Footprint,
|
||||
Color,
|
||||
ImageFrameTable<Color>,
|
||||
GradientStops,
|
||||
)]
|
||||
footprint: F,
|
||||
#[implementations(
|
||||
() -> Color,
|
||||
() -> ImageFrameTable<Color>,
|
||||
() -> GradientStops,
|
||||
Footprint -> Color,
|
||||
Footprint -> ImageFrameTable<Color>,
|
||||
Footprint -> GradientStops,
|
||||
)]
|
||||
image: impl Node<F, Output = T>,
|
||||
mut image: T,
|
||||
gradient: GradientStops,
|
||||
reverse: bool,
|
||||
) -> T {
|
||||
let mut input = image.eval(footprint).await;
|
||||
|
||||
input.adjust(|color| {
|
||||
image.adjust(|color| {
|
||||
let intensity = color.luminance_srgb();
|
||||
let intensity = if reverse { 1. - intensity } else { intensity };
|
||||
gradient.evalute(intensity as f64)
|
||||
});
|
||||
|
||||
input
|
||||
image
|
||||
}
|
||||
|
||||
// Aims for interoperable compatibility with:
|
||||
@@ -896,27 +796,17 @@ async fn gradient_map<F: 'n + Send, T: Adjust<Color>>(
|
||||
// https://stackoverflow.com/questions/33966121/what-is-the-algorithm-for-vibrance-filters
|
||||
// The results of this implementation are very close to correct, but not quite perfect
|
||||
#[node_macro::node(category("Raster: Adjustment"))]
|
||||
async fn vibrance<F: 'n + Send, T: Adjust<Color>>(
|
||||
async fn vibrance<T: Adjust<Color>>(
|
||||
_: impl Ctx,
|
||||
#[implementations(
|
||||
(),
|
||||
(),
|
||||
(),
|
||||
Footprint,
|
||||
Color,
|
||||
ImageFrameTable<Color>,
|
||||
GradientStops,
|
||||
)]
|
||||
footprint: F,
|
||||
#[implementations(
|
||||
() -> Color,
|
||||
() -> ImageFrameTable<Color>,
|
||||
() -> GradientStops,
|
||||
Footprint -> Color,
|
||||
Footprint -> ImageFrameTable<Color>,
|
||||
Footprint -> GradientStops,
|
||||
)]
|
||||
image: impl Node<F, Output = T>,
|
||||
mut image: T,
|
||||
vibrance: SignedPercentage,
|
||||
) -> T {
|
||||
let mut input = image.eval(footprint).await;
|
||||
input.adjust(|color| {
|
||||
image.adjust(|color| {
|
||||
let vibrance = vibrance as f32 / 100.;
|
||||
// Slow the effect down by half when it's negative, since artifacts begin appearing past -50%.
|
||||
// So this scales the 0% to -50% range to 0% to -100%.
|
||||
@@ -963,7 +853,7 @@ async fn vibrance<F: 'n + Send, T: Adjust<Color>>(
|
||||
altered_color.map_rgb(|c| c * (1. - factor) + luminance * factor)
|
||||
}
|
||||
});
|
||||
input
|
||||
image
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
@@ -1196,23 +1086,14 @@ impl DomainWarpType {
|
||||
// https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=%27mixr%27%20%3D%20Channel%20Mixer
|
||||
// https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=Lab%20color%20only-,Channel%20Mixer,-Key%20is%20%27mixr
|
||||
#[node_macro::node(category("Raster: Adjustment"), properties("channel_mixer_properties"))]
|
||||
async fn channel_mixer<F: 'n + Send, T: Adjust<Color>>(
|
||||
async fn channel_mixer<T: Adjust<Color>>(
|
||||
_: impl Ctx,
|
||||
#[implementations(
|
||||
(),
|
||||
(),
|
||||
(),
|
||||
Footprint,
|
||||
Color,
|
||||
ImageFrameTable<Color>,
|
||||
GradientStops,
|
||||
)]
|
||||
footprint: F,
|
||||
#[implementations(
|
||||
() -> Color,
|
||||
() -> ImageFrameTable<Color>,
|
||||
() -> GradientStops,
|
||||
Footprint -> Color,
|
||||
Footprint -> ImageFrameTable<Color>,
|
||||
Footprint -> GradientStops,
|
||||
)]
|
||||
image: impl Node<F, Output = T>,
|
||||
mut image: T,
|
||||
|
||||
monochrome: bool,
|
||||
#[default(40.)]
|
||||
@@ -1270,8 +1151,7 @@ async fn channel_mixer<F: 'n + Send, T: Adjust<Color>>(
|
||||
// Display-only properties (not used within the node)
|
||||
_output_channel: RedGreenBlue,
|
||||
) -> T {
|
||||
let mut input = image.eval(footprint).await;
|
||||
input.adjust(|color| {
|
||||
image.adjust(|color| {
|
||||
let color = color.to_gamma_srgb();
|
||||
|
||||
let (r, g, b, a) = color.components();
|
||||
@@ -1296,7 +1176,7 @@ async fn channel_mixer<F: 'n + Send, T: Adjust<Color>>(
|
||||
|
||||
color.to_linear_srgb()
|
||||
});
|
||||
input
|
||||
image
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
@@ -1356,24 +1236,15 @@ impl core::fmt::Display for SelectiveColorChoice {
|
||||
//
|
||||
// Algorithm based on:
|
||||
// https://blog.pkh.me/p/22-understanding-selective-coloring-in-adobe-photoshop.html
|
||||
#[node_macro::node(category("Raster: Adjustment"))]
|
||||
async fn selective_color<F: 'n + Send, T: Adjust<Color>>(
|
||||
#[node_macro::node(category("Raster: Adjustment"), properties("selective_color_properties"))]
|
||||
async fn selective_color<T: Adjust<Color>>(
|
||||
_: impl Ctx,
|
||||
#[implementations(
|
||||
(),
|
||||
(),
|
||||
(),
|
||||
Footprint,
|
||||
Color,
|
||||
ImageFrameTable<Color>,
|
||||
GradientStops,
|
||||
)]
|
||||
footprint: F,
|
||||
#[implementations(
|
||||
() -> Color,
|
||||
() -> ImageFrameTable<Color>,
|
||||
() -> GradientStops,
|
||||
Footprint -> Color,
|
||||
Footprint -> ImageFrameTable<Color>,
|
||||
Footprint -> GradientStops,
|
||||
)]
|
||||
image: impl Node<F, Output = T>,
|
||||
mut image: T,
|
||||
mode: RelativeAbsolute,
|
||||
#[name("(Reds) Cyan")] r_c: f64,
|
||||
#[name("(Reds) Magenta")] r_m: f64,
|
||||
@@ -1413,8 +1284,7 @@ async fn selective_color<F: 'n + Send, T: Adjust<Color>>(
|
||||
#[name("(Blacks) Black")] k_k: f64,
|
||||
_colors: SelectiveColorChoice,
|
||||
) -> T {
|
||||
let mut input = image.eval(footprint).await;
|
||||
input.adjust(|color| {
|
||||
image.adjust(|color| {
|
||||
let color = color.to_gamma_srgb();
|
||||
|
||||
let (r, g, b, a) = color.components();
|
||||
@@ -1488,7 +1358,7 @@ async fn selective_color<F: 'n + Send, T: Adjust<Color>>(
|
||||
|
||||
color.to_linear_srgb()
|
||||
});
|
||||
input
|
||||
image
|
||||
}
|
||||
|
||||
pub(super) trait MultiplyAlpha {
|
||||
@@ -1534,28 +1404,18 @@ where
|
||||
// https://www.axiomx.com/posterize.htm
|
||||
// This algorithm produces fully accurate output in relation to the industry standard.
|
||||
#[node_macro::node(category("Raster: Adjustment"))]
|
||||
async fn posterize<F: 'n + Send, T: Adjust<Color>>(
|
||||
async fn posterize<T: Adjust<Color>>(
|
||||
_: impl Ctx,
|
||||
#[implementations(
|
||||
(),
|
||||
(),
|
||||
(),
|
||||
Footprint,
|
||||
Color,
|
||||
ImageFrameTable<Color>,
|
||||
GradientStops,
|
||||
)]
|
||||
footprint: F,
|
||||
#[implementations(
|
||||
() -> Color,
|
||||
() -> ImageFrameTable<Color>,
|
||||
() -> GradientStops,
|
||||
Footprint -> Color,
|
||||
Footprint -> ImageFrameTable<Color>,
|
||||
Footprint -> GradientStops,
|
||||
)]
|
||||
input: impl Node<F, Output = T>,
|
||||
mut input: T,
|
||||
#[default(4)]
|
||||
#[min(2.)]
|
||||
levels: u32,
|
||||
) -> T {
|
||||
let mut input = input.eval(footprint).await;
|
||||
input.adjust(|color| {
|
||||
let color = color.to_gamma_srgb();
|
||||
|
||||
@@ -1577,30 +1437,20 @@ async fn posterize<F: 'n + Send, T: Adjust<Color>>(
|
||||
// Algorithm based on:
|
||||
// https://geraldbakker.nl/psnumbers/exposure.html
|
||||
#[node_macro::node(category("Raster: Adjustment"), properties("exposure_properties"))]
|
||||
async fn exposure<F: 'n + Send, T: Adjust<Color>>(
|
||||
async fn exposure<T: Adjust<Color>>(
|
||||
_: impl Ctx,
|
||||
#[implementations(
|
||||
(),
|
||||
(),
|
||||
(),
|
||||
Footprint,
|
||||
Color,
|
||||
ImageFrameTable<Color>,
|
||||
GradientStops,
|
||||
)]
|
||||
footprint: F,
|
||||
#[implementations(
|
||||
() -> Color,
|
||||
() -> ImageFrameTable<Color>,
|
||||
() -> GradientStops,
|
||||
Footprint -> Color,
|
||||
Footprint -> ImageFrameTable<Color>,
|
||||
Footprint -> GradientStops,
|
||||
)]
|
||||
input: impl Node<F, Output = T>,
|
||||
mut input: T,
|
||||
exposure: f64,
|
||||
offset: f64,
|
||||
#[default(1.)]
|
||||
#[range((0.01, 10.))]
|
||||
gamma_correction: f64,
|
||||
) -> T {
|
||||
let mut input = input.eval(footprint).await;
|
||||
input.adjust(|color| {
|
||||
let adjusted = color
|
||||
// Exposure
|
||||
@@ -1619,7 +1469,7 @@ const WINDOW_SIZE: usize = 1024;
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
#[node_macro::node(category(""))]
|
||||
fn generate_curves<C: Channel + super::Linear>(_: (), curve: Curve, #[implementations(f32, f64)] _target_format: C) -> ValueMapperNode<C> {
|
||||
fn generate_curves<C: Channel + super::Linear>(_: impl Ctx, curve: Curve, #[implementations(f32, f64)] _target_format: C) -> ValueMapperNode<C> {
|
||||
use bezier_rs::{Bezier, TValue};
|
||||
|
||||
let [mut pos, mut param]: [[f32; 2]; 2] = [[0.; 2], curve.first_handle];
|
||||
@@ -1660,31 +1510,21 @@ fn generate_curves<C: Channel + super::Linear>(_: (), curve: Curve, #[implementa
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
#[node_macro::node(category("Raster: Adjustment"))]
|
||||
async fn color_overlay<F: 'n + Send, T: Adjust<Color>>(
|
||||
fn color_overlay<T: Adjust<Color>>(
|
||||
_: impl Ctx,
|
||||
#[implementations(
|
||||
(),
|
||||
(),
|
||||
(),
|
||||
Footprint,
|
||||
Color,
|
||||
ImageFrameTable<Color>,
|
||||
GradientStops,
|
||||
)]
|
||||
footprint: F,
|
||||
#[implementations(
|
||||
() -> Color,
|
||||
() -> ImageFrameTable<Color>,
|
||||
() -> GradientStops,
|
||||
Footprint -> Color,
|
||||
Footprint -> ImageFrameTable<Color>,
|
||||
Footprint -> GradientStops,
|
||||
)]
|
||||
image: impl Node<F, Output = T>,
|
||||
mut image: T,
|
||||
#[default(Color::BLACK)] color: Color,
|
||||
blend_mode: BlendMode,
|
||||
#[default(100.)] opacity: Percentage,
|
||||
) -> T {
|
||||
let opacity = (opacity as f32 / 100.).clamp(0., 1.);
|
||||
|
||||
let mut input = image.eval(footprint).await;
|
||||
input.adjust(|pixel| {
|
||||
image.adjust(|pixel| {
|
||||
let image = pixel.map_rgb(|channel| channel * (1. - opacity));
|
||||
|
||||
// The apply blend mode function divides rgb by the alpha channel for the background. This undoes that.
|
||||
@@ -1693,7 +1533,7 @@ async fn color_overlay<F: 'n + Send, T: Adjust<Color>>(
|
||||
|
||||
Color::from_rgbaf32_unchecked(image.r() + overlay.r(), image.g() + overlay.g(), image.b() + overlay.b(), pixel.a())
|
||||
});
|
||||
input
|
||||
image
|
||||
}
|
||||
|
||||
// #[cfg(feature = "alloc")]
|
||||
@@ -1702,10 +1542,11 @@ async fn color_overlay<F: 'n + Send, T: Adjust<Color>>(
|
||||
// #[cfg(feature = "alloc")]
|
||||
// mod index_node {
|
||||
// use crate::raster::{Color, ImageFrame};
|
||||
// use crate::Ctx;
|
||||
|
||||
// #[node_macro::node(category(""))]
|
||||
// pub fn index<T: Default + Clone>(
|
||||
// _: (),
|
||||
// _: impl Ctx,
|
||||
// #[implementations(Vec<ImageFrame<Color>>, Vec<Color>)]
|
||||
// #[widget(ParsedWidgetOverride::Hidden)]
|
||||
// input: Vec<T>,
|
||||
@@ -1752,7 +1593,7 @@ mod test {
|
||||
// 100% of the output should come from the multiplied value
|
||||
let opacity = 100_f64;
|
||||
|
||||
let result = super::color_overlay((), &FutureWrapperNode(ImageFrameTable::new(image.clone())), overlay_color, BlendMode::Multiply, opacity).await;
|
||||
let result = super::color_overlay((), ImageFrameTable::new(image.clone()), overlay_color, BlendMode::Multiply, opacity);
|
||||
let result = result.one_item();
|
||||
|
||||
// The output should just be the original green and alpha channels (as we multiply them by 1 and other channels by 0)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use super::discrete_srgb::{float_to_srgb_u8, srgb_u8_to_float};
|
||||
use super::{Alpha, AssociatedAlpha, Luminance, LuminanceMut, Pixel, RGBMut, Rec709Primaries, RGB, SRGB};
|
||||
use super::{Alpha, AlphaMut, AssociatedAlpha, Luminance, LuminanceMut, Pixel, RGBMut, Rec709Primaries, RGB, SRGB};
|
||||
|
||||
use dyn_any::DynAny;
|
||||
#[cfg(feature = "serde")]
|
||||
@@ -257,6 +257,11 @@ impl RGBMut for Color {
|
||||
self.blue = blue;
|
||||
}
|
||||
}
|
||||
impl AlphaMut for Color {
|
||||
fn set_alpha(&mut self, value: Self::AlphaChannel) {
|
||||
self.alpha = value;
|
||||
}
|
||||
}
|
||||
|
||||
impl Pixel for Color {
|
||||
#[cfg(not(target_arch = "spirv"))]
|
||||
|
||||
@@ -132,14 +132,15 @@ impl<'i, Root: Node<'i, I>, I: 'i + From<()>> ConsNode<I, Root> {
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use crate::{ops::IdentityNode, value::ValueNode};
|
||||
use crate::generic::FnNode;
|
||||
use crate::value::ValueNode;
|
||||
|
||||
#[test]
|
||||
fn compose() {
|
||||
let value = ValueNode::new(4u32);
|
||||
let compose = value.then(IdentityNode::new());
|
||||
let compose = value.then(FnNode::new(|x| x));
|
||||
assert_eq!(compose.eval(()), &4u32);
|
||||
let type_erased = &compose as &dyn for<'i> Node<'i, (), Output = &'i u32>;
|
||||
let type_erased = &compose as &dyn Node<'_, (), Output = &'_ u32>;
|
||||
assert_eq!(type_erased.eval(()), &4u32);
|
||||
}
|
||||
|
||||
@@ -148,7 +149,7 @@ mod test {
|
||||
let value = ValueNode::new(5);
|
||||
|
||||
assert_eq!(value.eval(()), &5);
|
||||
let id = IdentityNode::new();
|
||||
let id = FnNode::new(|x| x);
|
||||
|
||||
let compose = ComposeNode::new(&value, &id);
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
use crate::application_io::{TextureFrame, TextureFrameTable};
|
||||
use crate::application_io::TextureFrameTable;
|
||||
use crate::raster::bbox::AxisAlignedBbox;
|
||||
use crate::raster::image::{ImageFrame, ImageFrameTable};
|
||||
use crate::raster::Pixel;
|
||||
use crate::vector::{VectorData, VectorDataTable};
|
||||
use crate::{Artboard, ArtboardGroup, Color, GraphicElement, GraphicGroup, GraphicGroupTable};
|
||||
use crate::{Artboard, ArtboardGroup, CloneVarArgs, Color, Context, Ctx, ExtractAll, GraphicElement, GraphicGroup, GraphicGroupTable, OwnedContextImpl};
|
||||
|
||||
use glam::{DAffine2, DVec2};
|
||||
|
||||
@@ -244,6 +244,12 @@ pub struct Footprint {
|
||||
|
||||
impl Default for Footprint {
|
||||
fn default() -> Self {
|
||||
Self::empty()
|
||||
}
|
||||
}
|
||||
|
||||
impl Footprint {
|
||||
pub const fn empty() -> Self {
|
||||
Self {
|
||||
transform: DAffine2::IDENTITY,
|
||||
resolution: glam::UVec2::new(1920, 1080),
|
||||
@@ -251,9 +257,6 @@ impl Default for Footprint {
|
||||
ignore_modifications: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Footprint {
|
||||
pub fn viewport_bounds_in_local_space(&self) -> AxisAlignedBbox {
|
||||
let inverse = self.transform.inverse();
|
||||
let start = inverse.transform_point2((0., 0.).into());
|
||||
@@ -277,7 +280,7 @@ impl From<()> for Footprint {
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Debug"))]
|
||||
fn cull<T>(_footprint: Footprint, #[implementations(VectorDataTable, GraphicGroupTable, Artboard, ImageFrameTable<Color>, ArtboardGroup)] data: T) -> T {
|
||||
fn cull<T>(_: impl Ctx, #[implementations(VectorDataTable, GraphicGroupTable, Artboard, ImageFrameTable<Color>, ArtboardGroup)] data: T) -> T {
|
||||
data
|
||||
}
|
||||
|
||||
@@ -301,26 +304,15 @@ impl ApplyTransform for () {
|
||||
}
|
||||
|
||||
#[node_macro::node(category(""))]
|
||||
async fn transform<I: Into<Footprint> + 'n + ApplyTransform + Clone + Send + Sync, T: 'n + TransformMut>(
|
||||
async fn transform<T: 'n + TransformMut + 'static>(
|
||||
ctx: impl Ctx + CloneVarArgs + ExtractAll,
|
||||
#[implementations(
|
||||
(),
|
||||
(),
|
||||
(),
|
||||
(),
|
||||
Footprint,
|
||||
Context -> VectorDataTable,
|
||||
Context -> GraphicGroupTable,
|
||||
Context -> ImageFrameTable<Color>,
|
||||
Context -> TextureFrameTable,
|
||||
)]
|
||||
mut input: I,
|
||||
#[implementations(
|
||||
() -> VectorDataTable,
|
||||
() -> GraphicGroupTable,
|
||||
() -> ImageFrameTable<Color>,
|
||||
() -> TextureFrame,
|
||||
Footprint -> VectorDataTable,
|
||||
Footprint -> GraphicGroupTable,
|
||||
Footprint -> ImageFrameTable<Color>,
|
||||
Footprint -> TextureFrame,
|
||||
)]
|
||||
transform_target: impl Node<I, Output = T>,
|
||||
transform_target: impl Node<Context<'static>, Output = T>,
|
||||
translate: DVec2,
|
||||
rotate: f64,
|
||||
scale: DVec2,
|
||||
@@ -328,22 +320,27 @@ async fn transform<I: Into<Footprint> + 'n + ApplyTransform + Clone + Send + Syn
|
||||
_pivot: DVec2,
|
||||
) -> T {
|
||||
let modification = DAffine2::from_scale_angle_translation(scale, rotate, translate) * DAffine2::from_cols_array(&[1., shear.y, shear.x, 1., 0., 0.]);
|
||||
let footprint = input.clone().into();
|
||||
if !footprint.ignore_modifications {
|
||||
input.apply_transform(&modification);
|
||||
let footprint = ctx.try_footprint().copied();
|
||||
|
||||
let mut ctx = OwnedContextImpl::from(ctx);
|
||||
if let Some(mut footprint) = footprint {
|
||||
if !footprint.ignore_modifications {
|
||||
footprint.apply_transform(&modification);
|
||||
}
|
||||
ctx = ctx.with_footprint(footprint);
|
||||
}
|
||||
|
||||
let mut data = transform_target.eval(input).await;
|
||||
let mut transform_target = transform_target.eval(ctx.into_context()).await;
|
||||
|
||||
let data_transform = data.transform_mut();
|
||||
let data_transform = transform_target.transform_mut();
|
||||
*data_transform = modification * (*data_transform);
|
||||
|
||||
data
|
||||
transform_target
|
||||
}
|
||||
|
||||
#[node_macro::node(category(""))]
|
||||
fn replace_transform<Data: TransformMut, TransformInput: Transform>(
|
||||
_: (),
|
||||
_: impl Ctx,
|
||||
#[implementations(VectorDataTable, ImageFrameTable<Color>, GraphicGroupTable)] mut data: Data,
|
||||
#[implementations(DAffine2)] transform: TransformInput,
|
||||
) -> Data {
|
||||
|
||||
@@ -53,6 +53,9 @@ macro_rules! future {
|
||||
($type:ty) => {{
|
||||
$crate::Type::Future(Box::new(concrete!($type)))
|
||||
}};
|
||||
($type:ty, $name:ty) => {
|
||||
$crate::Type::Future(Box::new(concrete!($type, $name)))
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
@@ -67,6 +70,18 @@ macro_rules! fn_type {
|
||||
$crate::Type::Fn(Box::new(concrete!($in_type)), Box::new(concrete!($type)))
|
||||
};
|
||||
}
|
||||
#[macro_export]
|
||||
macro_rules! fn_type_fut {
|
||||
($type:ty) => {
|
||||
$crate::Type::Fn(Box::new(concrete!(())), Box::new(future!($type)))
|
||||
};
|
||||
($in_type:ty, $type:ty, alias: $outname:ty) => {
|
||||
$crate::Type::Fn(Box::new(concrete!($in_type)), Box::new(future!($type, $outname)))
|
||||
};
|
||||
($in_type:ty, $type:ty) => {
|
||||
$crate::Type::Fn(Box::new(concrete!($in_type)), Box::new(future!($type)))
|
||||
};
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Hash, Default)]
|
||||
pub struct NodeIOTypes {
|
||||
@@ -134,6 +149,7 @@ fn migrate_type_descriptor_names<'de, D: serde::Deserializer<'de>>(deserializer:
|
||||
let name = String::deserialize(deserializer)?;
|
||||
let name = match name.as_str() {
|
||||
"f32" => "f64".to_string(),
|
||||
"graphene_core::transform::Footprint" => "core::option::Option<alloc::sync::Arc<graphene_core::context::OwnedContextImpl>>".to_string(),
|
||||
"graphene_core::graphic_element::GraphicGroup" => "graphene_core::graphic_element::Instances<graphene_core::graphic_element::GraphicGroup>".to_string(),
|
||||
"graphene_core::vector::vector_data::VectorData" => "graphene_core::graphic_element::Instances<graphene_core::vector::vector_data::VectorData>".to_string(),
|
||||
"graphene_core::raster::image::ImageFrame<Color>" => "graphene_core::graphic_element::Instances<graphene_core::raster::image::ImageFrame<Color>>".to_string(),
|
||||
@@ -189,7 +205,7 @@ pub enum Type {
|
||||
/// Runtime type information for a function. Given some input, gives some output.
|
||||
/// See the example and explanation in the `ComposeNode` implementation within the node registry for more info.
|
||||
Fn(Box<Type>, Box<Type>),
|
||||
/// Not used at the moment.
|
||||
/// Represents a future which promises to return the inner type.
|
||||
Future(Box<Type>),
|
||||
}
|
||||
|
||||
@@ -280,7 +296,7 @@ impl Type {
|
||||
Self::Generic(_) => self,
|
||||
Self::Concrete(_) => self,
|
||||
Self::Fn(_, output) => output.nested_type(),
|
||||
Self::Future(_) => self,
|
||||
Self::Future(output) => output.nested_type(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,10 +8,10 @@ use core::{
|
||||
#[derive(Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
|
||||
pub struct IntNode<const N: u32>;
|
||||
|
||||
impl<'i, const N: u32> Node<'i, ()> for IntNode<N> {
|
||||
impl<'i, const N: u32, I> Node<'i, I> for IntNode<N> {
|
||||
type Output = u32;
|
||||
#[inline(always)]
|
||||
fn eval(&'i self, _input: ()) -> Self::Output {
|
||||
fn eval(&'i self, _input: I) -> Self::Output {
|
||||
N
|
||||
}
|
||||
}
|
||||
@@ -19,10 +19,10 @@ impl<'i, const N: u32> Node<'i, ()> for IntNode<N> {
|
||||
#[derive(Default, Debug, Clone, Copy)]
|
||||
pub struct ValueNode<T>(pub T);
|
||||
|
||||
impl<'i, T: 'i> Node<'i, ()> for ValueNode<T> {
|
||||
impl<'i, T: 'i, I> Node<'i, I> for ValueNode<T> {
|
||||
type Output = &'i T;
|
||||
#[inline(always)]
|
||||
fn eval(&'i self, _input: ()) -> Self::Output {
|
||||
fn eval(&'i self, _input: I) -> Self::Output {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
@@ -77,10 +77,10 @@ impl<T> RefCellMutNode<T> {
|
||||
#[derive(Default)]
|
||||
pub struct OnceCellNode<T>(pub Cell<T>);
|
||||
|
||||
impl<'i, T: Default + 'i> Node<'i, ()> for OnceCellNode<T> {
|
||||
impl<'i, T: Default + 'i, I> Node<'i, I> for OnceCellNode<T> {
|
||||
type Output = T;
|
||||
#[inline(always)]
|
||||
fn eval(&'i self, _input: ()) -> Self::Output {
|
||||
fn eval(&'i self, _input: I) -> Self::Output {
|
||||
self.0.replace(T::default())
|
||||
}
|
||||
}
|
||||
@@ -94,10 +94,10 @@ impl<T> OnceCellNode<T> {
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct ClonedNode<T: Clone>(pub T);
|
||||
|
||||
impl<'i, T: Clone + 'i> Node<'i, ()> for ClonedNode<T> {
|
||||
impl<'i, T: Clone + 'i, I> Node<'i, I> for ClonedNode<T> {
|
||||
type Output = T;
|
||||
#[inline(always)]
|
||||
fn eval(&'i self, _input: ()) -> Self::Output {
|
||||
fn eval(&'i self, _input: I) -> Self::Output {
|
||||
self.0.clone()
|
||||
}
|
||||
}
|
||||
@@ -140,10 +140,10 @@ impl<T: Clone> DebugClonedNode<T> {
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct CopiedNode<T: Copy>(pub T);
|
||||
|
||||
impl<'i, T: Copy + 'i> Node<'i, ()> for CopiedNode<T> {
|
||||
impl<'i, T: Copy + 'i, I> Node<'i, I> for CopiedNode<T> {
|
||||
type Output = T;
|
||||
#[inline(always)]
|
||||
fn eval(&'i self, _input: ()) -> Self::Output {
|
||||
fn eval(&'i self, _input: I) -> Self::Output {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
@@ -157,9 +157,9 @@ impl<T: Copy> CopiedNode<T> {
|
||||
#[derive(Default)]
|
||||
pub struct DefaultNode<T>(PhantomData<T>);
|
||||
|
||||
impl<'i, T: Default + 'i> Node<'i, ()> for DefaultNode<T> {
|
||||
impl<'i, T: Default + 'i, I> Node<'i, I> for DefaultNode<T> {
|
||||
type Output = T;
|
||||
fn eval(&'i self, _input: ()) -> Self::Output {
|
||||
fn eval(&'i self, _input: I) -> Self::Output {
|
||||
T::default()
|
||||
}
|
||||
}
|
||||
@@ -205,7 +205,7 @@ mod test {
|
||||
#[test]
|
||||
fn test_default_node() {
|
||||
let node = DefaultNode::<u32>::new();
|
||||
assert_eq!(node.eval(()), 0);
|
||||
assert_eq!(node.eval(42), 0);
|
||||
}
|
||||
#[test]
|
||||
#[allow(clippy::unit_cmp)]
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use crate::transform::Footprint;
|
||||
use crate::vector::{HandleId, PointId, VectorData, VectorDataTable};
|
||||
use crate::Ctx;
|
||||
|
||||
use bezier_rs::Subpath;
|
||||
use glam::DVec2;
|
||||
@@ -35,12 +35,12 @@ impl CornerRadius for [f64; 4] {
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Vector: Shape"))]
|
||||
fn circle<F: 'n + Send>(#[implementations((), Footprint)] _footprint: F, _primary: (), #[default(50.)] radius: f64) -> VectorDataTable {
|
||||
fn circle(_: impl Ctx, _primary: (), #[default(50.)] radius: f64) -> VectorDataTable {
|
||||
VectorDataTable::new(VectorData::from_subpath(Subpath::new_ellipse(DVec2::splat(-radius), DVec2::splat(radius))))
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Vector: Shape"))]
|
||||
fn ellipse<F: 'n + Send>(#[implementations((), Footprint)] _footprint: F, _primary: (), #[default(50)] radius_x: f64, #[default(25)] radius_y: f64) -> VectorDataTable {
|
||||
fn ellipse(_: impl Ctx, _primary: (), #[default(50)] radius_x: f64, #[default(25)] radius_y: f64) -> VectorDataTable {
|
||||
let radius = DVec2::new(radius_x, radius_y);
|
||||
let corner1 = -radius;
|
||||
let corner2 = radius;
|
||||
@@ -58,8 +58,8 @@ fn ellipse<F: 'n + Send>(#[implementations((), Footprint)] _footprint: F, _prima
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Vector: Shape"), properties("rectangle_properties"))]
|
||||
fn rectangle<F: 'n + Send, T: CornerRadius>(
|
||||
#[implementations((), Footprint)] _footprint: F,
|
||||
fn rectangle<T: CornerRadius>(
|
||||
_: impl Ctx,
|
||||
_primary: (),
|
||||
#[default(100)] width: f64,
|
||||
#[default(100)] height: f64,
|
||||
@@ -71,8 +71,8 @@ fn rectangle<F: 'n + Send, T: CornerRadius>(
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Vector: Shape"))]
|
||||
fn regular_polygon<F: 'n + Send>(
|
||||
#[implementations((), Footprint)] _footprint: F,
|
||||
fn regular_polygon(
|
||||
_: impl Ctx,
|
||||
_primary: (),
|
||||
#[default(6)]
|
||||
#[min(3.)]
|
||||
@@ -85,8 +85,8 @@ fn regular_polygon<F: 'n + Send>(
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Vector: Shape"))]
|
||||
fn star<F: 'n + Send>(
|
||||
#[implementations((), Footprint)] _footprint: F,
|
||||
fn star(
|
||||
_: impl Ctx,
|
||||
_primary: (),
|
||||
#[default(5)]
|
||||
#[min(2.)]
|
||||
@@ -102,15 +102,14 @@ fn star<F: 'n + Send>(
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Vector: Shape"))]
|
||||
fn line<F: 'n + Send>(#[implementations((), Footprint)] _footprint: F, _primary: (), #[default((0., -50.))] start: DVec2, #[default((0., 50.))] end: DVec2) -> VectorDataTable {
|
||||
fn line(_: impl Ctx, _primary: (), #[default((0., -50.))] start: DVec2, #[default((0., 50.))] end: DVec2) -> VectorDataTable {
|
||||
VectorDataTable::new(VectorData::from_subpath(Subpath::new_line(start, end)))
|
||||
}
|
||||
|
||||
// TODO(TrueDoctor): I removed the Arc requirement we should think about when it makes sense to use it vs making a generic value node
|
||||
#[node_macro::node(category(""))]
|
||||
fn path<F: 'n + Send>(#[implementations((), Footprint)] _footprint: F, path_data: Vec<Subpath<PointId>>, colinear_manipulators: Vec<PointId>) -> VectorDataTable {
|
||||
fn path(_: impl Ctx, path_data: Vec<Subpath<PointId>>, colinear_manipulators: Vec<PointId>) -> VectorDataTable {
|
||||
let mut vector_data = VectorData::from_subpaths(path_data, false);
|
||||
|
||||
vector_data.colinear_manipulators = colinear_manipulators
|
||||
.iter()
|
||||
.filter_map(|&point| super::ManipulatorPointId::Anchor(point).get_handle_pair(&vector_data))
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use super::*;
|
||||
use crate::transform::Footprint;
|
||||
use crate::uuid::generate_uuid;
|
||||
use crate::Ctx;
|
||||
|
||||
use bezier_rs::BezierHandles;
|
||||
use dyn_any::DynAny;
|
||||
@@ -424,20 +424,7 @@ impl core::hash::Hash for VectorModification {
|
||||
|
||||
/// A node that applies a procedural modification to some [`VectorData`].
|
||||
#[node_macro::node(category(""))]
|
||||
async fn path_modify<F: 'n + Send + Sync + Clone>(
|
||||
#[implementations(
|
||||
(),
|
||||
Footprint,
|
||||
)]
|
||||
input: F,
|
||||
#[implementations(
|
||||
() -> VectorDataTable,
|
||||
Footprint -> VectorDataTable,
|
||||
)]
|
||||
vector_data: impl Node<F, Output = VectorDataTable>,
|
||||
modification: Box<VectorModification>,
|
||||
) -> VectorDataTable {
|
||||
let mut vector_data = vector_data.eval(input).await;
|
||||
async fn path_modify(_ctx: impl Ctx, mut vector_data: VectorDataTable, modification: Box<VectorModification>) -> VectorDataTable {
|
||||
let vector_data = vector_data.one_item_mut();
|
||||
|
||||
modification.apply(vector_data);
|
||||
|
||||
@@ -6,7 +6,7 @@ use crate::renderer::GraphicElementRendered;
|
||||
use crate::transform::{Footprint, Transform, TransformMut};
|
||||
use crate::vector::style::LineJoin;
|
||||
use crate::vector::PointDomain;
|
||||
use crate::{Color, GraphicElement, GraphicGroup, GraphicGroupTable};
|
||||
use crate::{CloneVarArgs, Color, Context, Ctx, ExtractAll, GraphicElement, GraphicGroup, GraphicGroupTable, OwnedContextImpl};
|
||||
|
||||
use bezier_rs::{Cap, Join, Subpath, SubpathTValue, TValue};
|
||||
use glam::{DAffine2, DVec2};
|
||||
@@ -43,21 +43,11 @@ impl VectorIterMut for VectorDataTable {
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Vector: Style"), path(graphene_core::vector))]
|
||||
async fn assign_colors<F: 'n + Send, T: VectorIterMut>(
|
||||
#[implementations(
|
||||
(),
|
||||
(),
|
||||
Footprint,
|
||||
)]
|
||||
footprint: F,
|
||||
#[implementations(
|
||||
() -> GraphicGroupTable,
|
||||
() -> VectorDataTable,
|
||||
Footprint -> GraphicGroupTable,
|
||||
Footprint -> VectorDataTable,
|
||||
)]
|
||||
async fn assign_colors<T: VectorIterMut>(
|
||||
_: impl Ctx,
|
||||
#[implementations(GraphicGroupTable, VectorDataTable)]
|
||||
#[widget(ParsedWidgetOverride::Hidden)]
|
||||
vector_group: impl Node<F, Output = T>,
|
||||
mut vector_group: T,
|
||||
#[default(true)] fill: bool,
|
||||
stroke: bool,
|
||||
#[widget(ParsedWidgetOverride::Custom = "assign_colors_gradient")] gradient: GradientStops,
|
||||
@@ -66,8 +56,6 @@ async fn assign_colors<F: 'n + Send, T: VectorIterMut>(
|
||||
#[widget(ParsedWidgetOverride::Custom = "assign_colors_seed")] seed: SeedValue,
|
||||
#[widget(ParsedWidgetOverride::Custom = "assign_colors_repeat_every")] repeat_every: u32,
|
||||
) -> T {
|
||||
let mut vector_group = vector_group.eval(footprint).await;
|
||||
|
||||
let length = vector_group.vector_iter_mut().count();
|
||||
let gradient = if reverse { gradient.reversed() } else { gradient };
|
||||
|
||||
@@ -99,54 +87,20 @@ async fn assign_colors<F: 'n + Send, T: VectorIterMut>(
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Vector: Style"), path(graphene_core::vector), properties("fill_properties"))]
|
||||
async fn fill<F: 'n + Send, FillTy: Into<Fill> + 'n + Send, TargetTy: VectorIterMut + 'n + Send>(
|
||||
async fn fill<FillTy: Into<Fill> + 'n + Send, TargetTy: VectorIterMut + 'n + Send>(
|
||||
_: impl Ctx,
|
||||
#[implementations(
|
||||
(),
|
||||
(),
|
||||
(),
|
||||
(),
|
||||
(),
|
||||
(),
|
||||
(),
|
||||
(),
|
||||
Footprint,
|
||||
Footprint,
|
||||
Footprint,
|
||||
Footprint,
|
||||
Footprint,
|
||||
Footprint,
|
||||
Footprint,
|
||||
Footprint,
|
||||
VectorDataTable,
|
||||
VectorDataTable,
|
||||
VectorDataTable,
|
||||
VectorDataTable,
|
||||
GraphicGroupTable,
|
||||
GraphicGroupTable,
|
||||
GraphicGroupTable,
|
||||
GraphicGroupTable
|
||||
)]
|
||||
footprint: F,
|
||||
mut vector_data: TargetTy,
|
||||
#[implementations(
|
||||
() -> VectorDataTable,
|
||||
() -> VectorDataTable,
|
||||
() -> VectorDataTable,
|
||||
() -> VectorDataTable,
|
||||
() -> GraphicGroupTable,
|
||||
() -> GraphicGroupTable,
|
||||
() -> GraphicGroupTable,
|
||||
() -> GraphicGroupTable,
|
||||
Footprint -> VectorDataTable,
|
||||
Footprint -> VectorDataTable,
|
||||
Footprint -> VectorDataTable,
|
||||
Footprint -> VectorDataTable,
|
||||
Footprint -> GraphicGroupTable,
|
||||
Footprint -> GraphicGroupTable,
|
||||
Footprint -> GraphicGroupTable,
|
||||
Footprint -> GraphicGroupTable,
|
||||
)]
|
||||
vector_data: impl Node<F, Output = TargetTy>,
|
||||
#[implementations(
|
||||
Fill,
|
||||
Option<Color>,
|
||||
Color,
|
||||
Gradient,
|
||||
Fill,
|
||||
Option<Color>,
|
||||
Color,
|
||||
Gradient,
|
||||
Fill,
|
||||
Option<Color>,
|
||||
Color,
|
||||
@@ -161,44 +115,19 @@ async fn fill<F: 'n + Send, FillTy: Into<Fill> + 'n + Send, TargetTy: VectorIter
|
||||
_backup_color: Option<Color>,
|
||||
_backup_gradient: Gradient,
|
||||
) -> TargetTy {
|
||||
let mut target = vector_data.eval(footprint).await;
|
||||
let fill: Fill = fill.into();
|
||||
for (target, _transform) in target.vector_iter_mut() {
|
||||
for (target, _transform) in vector_data.vector_iter_mut() {
|
||||
target.style.set_fill(fill.clone());
|
||||
}
|
||||
|
||||
target
|
||||
vector_data
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Vector: Style"), path(graphene_core::vector), properties("stroke_properties"))]
|
||||
async fn stroke<F: 'n + Send, ColorTy: Into<Option<Color>> + 'n + Send, TargetTy: VectorIterMut + 'n + Send>(
|
||||
async fn stroke<ColorTy: Into<Option<Color>> + 'n + Send, TargetTy: VectorIterMut + 'n + Send>(
|
||||
_: impl Ctx,
|
||||
#[implementations(VectorDataTable, VectorDataTable, GraphicGroupTable, GraphicGroupTable)] mut vector_data: TargetTy,
|
||||
#[implementations(
|
||||
(),
|
||||
(),
|
||||
(),
|
||||
(),
|
||||
Footprint,
|
||||
Footprint,
|
||||
Footprint,
|
||||
Footprint,
|
||||
)]
|
||||
footprint: F,
|
||||
#[implementations(
|
||||
() -> VectorDataTable,
|
||||
() -> VectorDataTable,
|
||||
() -> GraphicGroupTable,
|
||||
() -> GraphicGroupTable,
|
||||
Footprint -> VectorDataTable,
|
||||
Footprint -> VectorDataTable,
|
||||
Footprint -> GraphicGroupTable,
|
||||
Footprint -> GraphicGroupTable,
|
||||
)]
|
||||
vector_data: impl Node<F, Output = TargetTy>,
|
||||
#[implementations(
|
||||
Option<Color>,
|
||||
Color,
|
||||
Option<Color>,
|
||||
Color,
|
||||
Option<Color>,
|
||||
Color,
|
||||
Option<Color>,
|
||||
@@ -213,7 +142,6 @@ async fn stroke<F: 'n + Send, ColorTy: Into<Option<Color>> + 'n + Send, TargetTy
|
||||
line_join: LineJoin,
|
||||
#[default(4.)] miter_limit: f64,
|
||||
) -> TargetTy {
|
||||
let mut target = vector_data.eval(footprint).await;
|
||||
let stroke = Stroke {
|
||||
color: color.into(),
|
||||
weight,
|
||||
@@ -224,37 +152,24 @@ async fn stroke<F: 'n + Send, ColorTy: Into<Option<Color>> + 'n + Send, TargetTy
|
||||
line_join_miter_limit: miter_limit,
|
||||
transform: DAffine2::IDENTITY,
|
||||
};
|
||||
for (target, transform) in target.vector_iter_mut() {
|
||||
for (target, transform) in vector_data.vector_iter_mut() {
|
||||
target.style.set_stroke(Stroke { transform, ..stroke.clone() });
|
||||
}
|
||||
|
||||
target
|
||||
vector_data
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Vector"), path(graphene_core::vector))]
|
||||
async fn repeat<F: 'n + Send + Copy, I: 'n + GraphicElementRendered + Transform + TransformMut + Send>(
|
||||
#[implementations(
|
||||
(),
|
||||
(),
|
||||
Footprint,
|
||||
Footprint,
|
||||
)]
|
||||
footprint: F,
|
||||
async fn repeat<I: 'n + GraphicElementRendered + Transform + TransformMut + Send>(
|
||||
_: impl Ctx,
|
||||
// TODO: Implement other GraphicElementRendered types.
|
||||
#[implementations(
|
||||
() -> VectorDataTable,
|
||||
() -> GraphicGroupTable,
|
||||
Footprint -> VectorDataTable,
|
||||
Footprint -> GraphicGroupTable,
|
||||
)]
|
||||
instance: impl Node<F, Output = I>,
|
||||
#[implementations(VectorDataTable, GraphicGroupTable)] instance: I,
|
||||
#[default(100., 100.)]
|
||||
// TODO: When using a custom Properties panel layout in document_node_definitions.rs and this default is set, the widget weirdly doesn't show up in the Properties panel. Investigation is needed.
|
||||
direction: DVec2,
|
||||
angle: Angle,
|
||||
#[default(4)] instances: IntegerCount,
|
||||
) -> GraphicGroupTable {
|
||||
let instance = instance.eval(footprint).await;
|
||||
let first_vector_transform = instance.transform();
|
||||
|
||||
let angle = angle.to_radians();
|
||||
@@ -285,27 +200,14 @@ async fn repeat<F: 'n + Send + Copy, I: 'n + GraphicElementRendered + Transform
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Vector"), path(graphene_core::vector))]
|
||||
async fn circular_repeat<F: 'n + Send + Copy, I: 'n + GraphicElementRendered + Transform + TransformMut + Send>(
|
||||
#[implementations(
|
||||
(),
|
||||
(),
|
||||
Footprint,
|
||||
Footprint,
|
||||
)]
|
||||
footprint: F,
|
||||
async fn circular_repeat<I: 'n + GraphicElementRendered + Transform + TransformMut + Send>(
|
||||
_: impl Ctx,
|
||||
// TODO: Implement other GraphicElementRendered types.
|
||||
#[implementations(
|
||||
() -> VectorDataTable,
|
||||
() -> GraphicGroupTable,
|
||||
Footprint -> VectorDataTable,
|
||||
Footprint -> GraphicGroupTable,
|
||||
)]
|
||||
instance: impl Node<F, Output = I>,
|
||||
#[implementations(VectorDataTable, GraphicGroupTable)] instance: I,
|
||||
angle_offset: Angle,
|
||||
#[default(5)] radius: f64,
|
||||
#[default(5)] instances: IntegerCount,
|
||||
) -> GraphicGroupTable {
|
||||
let instance = instance.eval(footprint).await;
|
||||
let first_vector_transform = instance.transform();
|
||||
let instances = instances.max(1);
|
||||
|
||||
@@ -334,27 +236,12 @@ async fn circular_repeat<F: 'n + Send + Copy, I: 'n + GraphicElementRendered + T
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Vector"), path(graphene_core::vector))]
|
||||
async fn copy_to_points<F: 'n + Send + Copy, I: GraphicElementRendered + ConcatElement + TransformMut + Send + 'n>(
|
||||
#[implementations(
|
||||
(),
|
||||
(),
|
||||
Footprint,
|
||||
)]
|
||||
footprint: F,
|
||||
#[implementations(
|
||||
() -> VectorDataTable,
|
||||
() -> VectorDataTable,
|
||||
Footprint -> VectorDataTable,
|
||||
)]
|
||||
points: impl Node<F, Output = VectorDataTable>,
|
||||
async fn copy_to_points<I: GraphicElementRendered + TransformMut + Send + 'n>(
|
||||
_: impl Ctx,
|
||||
points: VectorDataTable,
|
||||
#[expose]
|
||||
#[implementations(
|
||||
() -> VectorDataTable,
|
||||
() -> GraphicGroupTable,
|
||||
Footprint -> VectorDataTable,
|
||||
Footprint -> GraphicGroupTable,
|
||||
)]
|
||||
instance: impl Node<F, Output = I>,
|
||||
#[implementations(VectorDataTable, GraphicGroupTable)]
|
||||
instance: I,
|
||||
#[default(1)] random_scale_min: f64,
|
||||
#[default(1)] random_scale_max: f64,
|
||||
random_scale_bias: f64,
|
||||
@@ -362,11 +249,8 @@ async fn copy_to_points<F: 'n + Send + Copy, I: GraphicElementRendered + ConcatE
|
||||
random_rotation: Angle,
|
||||
random_rotation_seed: SeedValue,
|
||||
) -> GraphicGroupTable {
|
||||
let points = points.eval(footprint).await;
|
||||
let points = points.one_item();
|
||||
|
||||
let instance = instance.eval(footprint).await;
|
||||
|
||||
let instance_transform = instance.transform();
|
||||
|
||||
let random_scale_difference = random_scale_max - random_scale_min;
|
||||
@@ -421,19 +305,7 @@ async fn copy_to_points<F: 'n + Send + Copy, I: GraphicElementRendered + ConcatE
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Vector"), path(graphene_core::vector))]
|
||||
async fn bounding_box<F: 'n + Send>(
|
||||
#[implementations(
|
||||
(),
|
||||
Footprint,
|
||||
)]
|
||||
footprint: F,
|
||||
#[implementations(
|
||||
() -> VectorDataTable,
|
||||
Footprint -> VectorDataTable,
|
||||
)]
|
||||
vector_data: impl Node<F, Output = VectorDataTable>,
|
||||
) -> VectorDataTable {
|
||||
let vector_data = vector_data.eval(footprint).await;
|
||||
async fn bounding_box(_: impl Ctx, vector_data: VectorDataTable) -> VectorDataTable {
|
||||
let vector_data = vector_data.one_item();
|
||||
|
||||
let bounding_box = vector_data.bounding_box_with_transform(vector_data.transform).unwrap();
|
||||
@@ -445,30 +317,16 @@ async fn bounding_box<F: 'n + Send>(
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Vector"), path(graphene_core::vector), properties("offset_path_properties"))]
|
||||
async fn offset_path<F: 'n + Send>(
|
||||
#[implementations(
|
||||
(),
|
||||
Footprint,
|
||||
)]
|
||||
footprint: F,
|
||||
#[implementations(
|
||||
() -> VectorDataTable,
|
||||
Footprint -> VectorDataTable,
|
||||
)]
|
||||
vector_data: impl Node<F, Output = VectorDataTable>,
|
||||
distance: f64,
|
||||
line_join: LineJoin,
|
||||
#[default(4.)] miter_limit: f64,
|
||||
) -> VectorDataTable {
|
||||
let vector_data = vector_data.eval(footprint).await;
|
||||
async fn offset_path(_: impl Ctx, vector_data: VectorDataTable, distance: f64, line_join: LineJoin, #[default(4.)] miter_limit: f64) -> VectorDataTable {
|
||||
let vector_data = vector_data.one_item();
|
||||
|
||||
let subpaths = vector_data.stroke_bezier_paths();
|
||||
let mut result = VectorData::empty();
|
||||
result.style = vector_data.style.clone();
|
||||
result.style.set_stroke_transform(DAffine2::IDENTITY);
|
||||
|
||||
// Perform operation on all subpaths in this shape.
|
||||
for mut subpath in vector_data.stroke_bezier_paths() {
|
||||
for mut subpath in subpaths {
|
||||
subpath.apply_transform(vector_data.transform);
|
||||
|
||||
// Taking the existing stroke data and passing it to Bezier-rs to generate new paths.
|
||||
@@ -489,19 +347,7 @@ async fn offset_path<F: 'n + Send>(
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Vector"), path(graphene_core::vector))]
|
||||
async fn solidify_stroke<F: 'n + Send>(
|
||||
#[implementations(
|
||||
(),
|
||||
Footprint,
|
||||
)]
|
||||
footprint: F,
|
||||
#[implementations(
|
||||
() -> VectorDataTable,
|
||||
Footprint -> VectorDataTable,
|
||||
)]
|
||||
vector_data: impl Node<F, Output = VectorDataTable>,
|
||||
) -> VectorDataTable {
|
||||
let vector_data = vector_data.eval(footprint).await;
|
||||
async fn solidify_stroke(_: impl Ctx, vector_data: VectorDataTable) -> VectorDataTable {
|
||||
let vector_data = vector_data.one_item();
|
||||
|
||||
let transform = &vector_data.transform;
|
||||
@@ -551,20 +397,8 @@ async fn solidify_stroke<F: 'n + Send>(
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Vector"), path(graphene_core::vector))]
|
||||
async fn flatten_vector_elements<F: 'n + Send>(
|
||||
#[implementations(
|
||||
(),
|
||||
Footprint,
|
||||
)]
|
||||
footprint: F,
|
||||
#[implementations(
|
||||
() -> GraphicGroupTable,
|
||||
Footprint -> GraphicGroupTable,
|
||||
)]
|
||||
graphic_group_input: impl Node<F, Output = GraphicGroupTable>,
|
||||
) -> VectorDataTable {
|
||||
let graphic_group = graphic_group_input.eval(footprint).await;
|
||||
let graphic_group = graphic_group.one_item();
|
||||
async fn flatten_vector_elements(_: impl Ctx, graphic_group_input: GraphicGroupTable) -> VectorDataTable {
|
||||
let graphic_group_input = graphic_group_input.one_item();
|
||||
|
||||
// A node based solution to support passing through vector data could be a network node with a cache node connected to
|
||||
// a flatten vector elements connected to an if else node, another connection from the cache directly
|
||||
@@ -587,7 +421,7 @@ async fn flatten_vector_elements<F: 'n + Send>(
|
||||
}
|
||||
|
||||
let mut result = VectorData::empty();
|
||||
concat_group(graphic_group, DAffine2::IDENTITY, &mut result);
|
||||
concat_group(graphic_group_input, DAffine2::IDENTITY, &mut result);
|
||||
// TODO: This leads to incorrect stroke widths when flattening groups with different transforms.
|
||||
result.style.set_stroke_transform(DAffine2::IDENTITY);
|
||||
|
||||
@@ -614,34 +448,10 @@ impl ConcatElement for GraphicGroupTable {
|
||||
}
|
||||
|
||||
#[node_macro::node(category(""), path(graphene_core::vector))]
|
||||
async fn sample_points<F: 'n + Send + Copy>(
|
||||
#[implementations(
|
||||
(),
|
||||
Footprint,
|
||||
)]
|
||||
footprint: F,
|
||||
#[implementations(
|
||||
() -> VectorDataTable,
|
||||
Footprint -> VectorDataTable,
|
||||
)]
|
||||
vector_data: impl Node<F, Output = VectorDataTable>,
|
||||
spacing: f64,
|
||||
start_offset: f64,
|
||||
stop_offset: f64,
|
||||
adaptive_spacing: bool,
|
||||
#[implementations(
|
||||
() -> Vec<f64>,
|
||||
Footprint -> Vec<f64>,
|
||||
)]
|
||||
subpath_segment_lengths: impl Node<F, Output = Vec<f64>>,
|
||||
) -> VectorDataTable {
|
||||
async fn sample_points(_: impl Ctx, vector_data: VectorDataTable, spacing: f64, start_offset: f64, stop_offset: f64, adaptive_spacing: bool, subpath_segment_lengths: Vec<f64>) -> VectorDataTable {
|
||||
// Limit the smallest spacing to something sensible to avoid freezing the application.
|
||||
let spacing = spacing.max(0.01);
|
||||
|
||||
// Evaluate vector data and subpath segment lengths asynchronously.
|
||||
let vector_data = vector_data.eval(footprint).await;
|
||||
let vector_data = vector_data.one_item();
|
||||
let subpath_segment_lengths = subpath_segment_lengths.eval(footprint).await;
|
||||
|
||||
// Create an iterator over the bezier segments with enumeration and peeking capability.
|
||||
let mut bezier = vector_data.segment_bezier_iter().enumerate().peekable();
|
||||
@@ -786,23 +596,14 @@ async fn sample_points<F: 'n + Send + Copy>(
|
||||
}
|
||||
|
||||
#[node_macro::node(category(""), path(graphene_core::vector))]
|
||||
async fn poisson_disk_points<F: 'n + Send>(
|
||||
#[implementations(
|
||||
(),
|
||||
Footprint,
|
||||
)]
|
||||
footprint: F,
|
||||
#[implementations(
|
||||
() -> VectorDataTable,
|
||||
Footprint -> VectorDataTable,
|
||||
)]
|
||||
vector_data: impl Node<F, Output = VectorDataTable>,
|
||||
async fn poisson_disk_points(
|
||||
_: impl Ctx,
|
||||
vector_data: VectorDataTable,
|
||||
#[default(10.)]
|
||||
#[min(0.01)]
|
||||
separation_disk_diameter: f64,
|
||||
seed: SeedValue,
|
||||
) -> VectorDataTable {
|
||||
let vector_data = vector_data.eval(footprint).await;
|
||||
let vector_data = vector_data.one_item();
|
||||
|
||||
let mut rng = rand::rngs::StdRng::seed_from_u64(seed.into());
|
||||
@@ -846,19 +647,7 @@ async fn poisson_disk_points<F: 'n + Send>(
|
||||
}
|
||||
|
||||
#[node_macro::node(category(""), path(graphene_core::vector))]
|
||||
async fn subpath_segment_lengths<F: 'n + Send>(
|
||||
#[implementations(
|
||||
(),
|
||||
Footprint,
|
||||
)]
|
||||
footprint: F,
|
||||
#[implementations(
|
||||
() -> VectorDataTable,
|
||||
Footprint -> VectorDataTable,
|
||||
)]
|
||||
vector_data: impl Node<F, Output = VectorDataTable>,
|
||||
) -> Vec<f64> {
|
||||
let vector_data = vector_data.eval(footprint).await;
|
||||
async fn subpath_segment_lengths(_: impl Ctx, vector_data: VectorDataTable) -> Vec<f64> {
|
||||
let vector_data = vector_data.one_item();
|
||||
|
||||
vector_data
|
||||
@@ -868,20 +657,7 @@ async fn subpath_segment_lengths<F: 'n + Send>(
|
||||
}
|
||||
|
||||
#[node_macro::node(name("Spline"), category("Vector"), path(graphene_core::vector))]
|
||||
async fn spline<F: 'n + Send>(
|
||||
#[implementations(
|
||||
(),
|
||||
Footprint,
|
||||
)]
|
||||
footprint: F,
|
||||
#[implementations(
|
||||
() -> VectorDataTable,
|
||||
Footprint -> VectorDataTable,
|
||||
)]
|
||||
vector_data: impl Node<F, Output = VectorDataTable>,
|
||||
) -> VectorDataTable {
|
||||
// Evaluate the vector data within the given footprint.
|
||||
let mut vector_data = vector_data.eval(footprint).await;
|
||||
async fn spline(_: impl Ctx, mut vector_data: VectorDataTable) -> VectorDataTable {
|
||||
let vector_data = vector_data.one_item_mut();
|
||||
|
||||
// Exit early if there are no points to generate splines from.
|
||||
@@ -923,21 +699,7 @@ async fn spline<F: 'n + Send>(
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Vector"), path(graphene_core::vector))]
|
||||
async fn jitter_points<F: 'n + Send>(
|
||||
#[implementations(
|
||||
(),
|
||||
Footprint,
|
||||
)]
|
||||
footprint: F,
|
||||
#[implementations(
|
||||
() -> VectorDataTable,
|
||||
Footprint -> VectorDataTable,
|
||||
)]
|
||||
vector_data: impl Node<F, Output = VectorDataTable>,
|
||||
#[default(5.)] amount: f64,
|
||||
seed: SeedValue,
|
||||
) -> VectorDataTable {
|
||||
let vector_data = vector_data.eval(footprint).await;
|
||||
async fn jitter_points(_: impl Ctx, vector_data: VectorDataTable, #[default(5.)] amount: f64, seed: SeedValue) -> VectorDataTable {
|
||||
let mut vector_data = vector_data.one_item().clone();
|
||||
|
||||
let mut rng = rand::rngs::StdRng::seed_from_u64(seed.into());
|
||||
@@ -986,31 +748,16 @@ async fn jitter_points<F: 'n + Send>(
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Vector"), path(graphene_core::vector))]
|
||||
async fn morph<F: 'n + Send + Copy>(
|
||||
#[implementations(
|
||||
(),
|
||||
Footprint,
|
||||
)]
|
||||
footprint: F,
|
||||
#[implementations(
|
||||
() -> VectorDataTable,
|
||||
Footprint -> VectorDataTable,
|
||||
)]
|
||||
source: impl Node<F, Output = VectorDataTable>,
|
||||
#[expose]
|
||||
#[implementations(
|
||||
() -> VectorDataTable,
|
||||
Footprint -> VectorDataTable,
|
||||
)]
|
||||
target: impl Node<F, Output = VectorDataTable>,
|
||||
async fn morph(
|
||||
_: impl Ctx,
|
||||
source: VectorDataTable,
|
||||
#[expose] target: VectorDataTable,
|
||||
#[range((0., 1.))]
|
||||
#[default(0.5)]
|
||||
time: Fraction,
|
||||
#[min(0.)] start_index: IntegerCount,
|
||||
) -> VectorDataTable {
|
||||
let source = source.eval(footprint).await;
|
||||
let source = source.one_item();
|
||||
let target = target.eval(footprint).await;
|
||||
let target = target.one_item();
|
||||
|
||||
let mut result = VectorData::empty();
|
||||
@@ -1202,30 +949,16 @@ fn bevel_algorithm(mut vector_data: VectorData, distance: f64) -> VectorData {
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Vector"), path(graphene_core::vector))]
|
||||
async fn bevel<F: 'n + Send + Copy>(
|
||||
#[implementations(
|
||||
(),
|
||||
Footprint,
|
||||
)]
|
||||
footprint: F,
|
||||
#[implementations(
|
||||
() -> VectorDataTable,
|
||||
Footprint -> VectorDataTable,
|
||||
)]
|
||||
source: impl Node<F, Output = VectorDataTable>,
|
||||
#[default(10.)] distance: Length,
|
||||
) -> VectorDataTable {
|
||||
let source = source.eval(footprint).await;
|
||||
fn bevel(_: impl Ctx, source: VectorDataTable, #[default(10.)] distance: Length) -> VectorDataTable {
|
||||
let source = source.one_item();
|
||||
|
||||
let result = bevel_algorithm(source.clone(), distance);
|
||||
|
||||
VectorDataTable::new(result)
|
||||
VectorDataTable::new(bevel_algorithm(source.clone(), distance))
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Vector"), path(graphene_core::vector))]
|
||||
async fn area(_: (), vector_data: impl Node<Footprint, Output = VectorDataTable>) -> f64 {
|
||||
let vector_data = vector_data.eval(Footprint::default()).await;
|
||||
async fn area(ctx: impl Ctx + CloneVarArgs + ExtractAll, vector_data: impl Node<Context<'static>, Output = VectorDataTable>) -> f64 {
|
||||
let new_ctx = OwnedContextImpl::from(ctx).with_footprint(Footprint::default()).into_context();
|
||||
let vector_data = vector_data.eval(new_ctx).await;
|
||||
let vector_data = vector_data.one_item();
|
||||
|
||||
let mut area = 0.;
|
||||
@@ -1237,8 +970,9 @@ async fn area(_: (), vector_data: impl Node<Footprint, Output = VectorDataTable>
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Vector"), path(graphene_core::vector))]
|
||||
async fn centroid(_: (), vector_data: impl Node<Footprint, Output = VectorDataTable>, centroid_type: CentroidType) -> DVec2 {
|
||||
let vector_data = vector_data.eval(Footprint::default()).await;
|
||||
async fn centroid(ctx: impl Ctx + CloneVarArgs + ExtractAll, vector_data: impl Node<Context<'static>, Output = VectorDataTable>, centroid_type: CentroidType) -> DVec2 {
|
||||
let new_ctx = OwnedContextImpl::from(ctx).with_footprint(Footprint::default()).into_context();
|
||||
let vector_data = vector_data.eval(new_ctx).await;
|
||||
let vector_data = vector_data.one_item();
|
||||
|
||||
if centroid_type == CentroidType::Area {
|
||||
@@ -1303,16 +1037,16 @@ mod test {
|
||||
}
|
||||
}
|
||||
|
||||
fn vector_node(data: Subpath<PointId>) -> FutureWrapperNode<VectorDataTable> {
|
||||
FutureWrapperNode(VectorDataTable::new(VectorData::from_subpath(data)))
|
||||
fn vector_node(data: Subpath<PointId>) -> VectorDataTable {
|
||||
VectorDataTable::new(VectorData::from_subpath(data))
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn repeat() {
|
||||
let direction = DVec2::X * 1.5;
|
||||
let instances = 3;
|
||||
let repeated = super::repeat(Footprint::default(), &vector_node(Subpath::new_rect(DVec2::ZERO, DVec2::ONE)), direction, 0., instances).await;
|
||||
let vector_data = super::flatten_vector_elements(Footprint::default(), &FutureWrapperNode(repeated)).await;
|
||||
let repeated = super::repeat(Footprint::default(), vector_node(Subpath::new_rect(DVec2::ZERO, DVec2::ONE)), direction, 0., instances).await;
|
||||
let vector_data = super::flatten_vector_elements(Footprint::default(), repeated).await;
|
||||
let vector_data = vector_data.one_item();
|
||||
assert_eq!(vector_data.region_bezier_paths().count(), 3);
|
||||
for (index, (_, subpath)) in vector_data.region_bezier_paths().enumerate() {
|
||||
@@ -1323,8 +1057,8 @@ mod test {
|
||||
async fn repeat_transform_position() {
|
||||
let direction = DVec2::new(12., 10.);
|
||||
let instances = 8;
|
||||
let repeated = super::repeat(Footprint::default(), &vector_node(Subpath::new_rect(DVec2::ZERO, DVec2::ONE)), direction, 0., instances).await;
|
||||
let vector_data = super::flatten_vector_elements(Footprint::default(), &FutureWrapperNode(repeated)).await;
|
||||
let repeated = super::repeat(Footprint::default(), vector_node(Subpath::new_rect(DVec2::ZERO, DVec2::ONE)), direction, 0., instances).await;
|
||||
let vector_data = super::flatten_vector_elements(Footprint::default(), repeated).await;
|
||||
let vector_data = vector_data.one_item();
|
||||
assert_eq!(vector_data.region_bezier_paths().count(), 8);
|
||||
for (index, (_, subpath)) in vector_data.region_bezier_paths().enumerate() {
|
||||
@@ -1333,8 +1067,8 @@ mod test {
|
||||
}
|
||||
#[tokio::test]
|
||||
async fn circle_repeat() {
|
||||
let repeated = super::circular_repeat(Footprint::default(), &vector_node(Subpath::new_rect(DVec2::NEG_ONE, DVec2::ONE)), 45., 4., 8).await;
|
||||
let vector_data = super::flatten_vector_elements(Footprint::default(), &FutureWrapperNode(repeated)).await;
|
||||
let repeated = super::circular_repeat(Footprint::default(), vector_node(Subpath::new_rect(DVec2::NEG_ONE, DVec2::ONE)), 45., 4., 8).await;
|
||||
let vector_data = super::flatten_vector_elements(Footprint::default(), repeated).await;
|
||||
let vector_data = vector_data.one_item();
|
||||
assert_eq!(vector_data.region_bezier_paths().count(), 8);
|
||||
for (index, (_, subpath)) in vector_data.region_bezier_paths().enumerate() {
|
||||
@@ -1346,10 +1080,7 @@ mod test {
|
||||
}
|
||||
#[tokio::test]
|
||||
async fn bounding_box() {
|
||||
let bounding_box = BoundingBoxNode {
|
||||
vector_data: vector_node(Subpath::new_rect(DVec2::NEG_ONE, DVec2::ONE)),
|
||||
};
|
||||
let bounding_box = bounding_box.eval(Footprint::default()).await;
|
||||
let bounding_box = super::bounding_box((), vector_node(Subpath::new_rect(DVec2::NEG_ONE, DVec2::ONE))).await;
|
||||
let bounding_box = bounding_box.one_item();
|
||||
assert_eq!(bounding_box.region_bezier_paths().count(), 1);
|
||||
let subpath = bounding_box.region_bezier_paths().next().unwrap().1;
|
||||
@@ -1375,8 +1106,8 @@ mod test {
|
||||
let points = Subpath::new_rect(DVec2::NEG_ONE * 10., DVec2::ONE * 10.);
|
||||
let instance = Subpath::new_rect(DVec2::NEG_ONE, DVec2::ONE);
|
||||
let expected_points = VectorData::from_subpath(points.clone()).point_domain.positions().to_vec();
|
||||
let copy_to_points = super::copy_to_points(Footprint::default(), &vector_node(points), &vector_node(instance), 1., 1., 0., 0, 0., 0).await;
|
||||
let flattened_copy_to_points = super::flatten_vector_elements(Footprint::default(), &FutureWrapperNode(copy_to_points)).await;
|
||||
let copy_to_points = super::copy_to_points(Footprint::default(), vector_node(points), vector_node(instance), 1., 1., 0., 0, 0., 0).await;
|
||||
let flattened_copy_to_points = super::flatten_vector_elements(Footprint::default(), copy_to_points).await;
|
||||
let flattened_copy_to_points = flattened_copy_to_points.one_item();
|
||||
assert_eq!(flattened_copy_to_points.region_bezier_paths().count(), expected_points.len());
|
||||
for (index, (_, subpath)) in flattened_copy_to_points.region_bezier_paths().enumerate() {
|
||||
@@ -1390,7 +1121,7 @@ mod test {
|
||||
#[tokio::test]
|
||||
async fn sample_points() {
|
||||
let path = Subpath::from_bezier(&Bezier::from_cubic_dvec2(DVec2::ZERO, DVec2::ZERO, DVec2::X * 100., DVec2::X * 100.));
|
||||
let sample_points = super::sample_points(Footprint::default(), &vector_node(path), 30., 0., 0., false, &FutureWrapperNode(vec![100.])).await;
|
||||
let sample_points = super::sample_points(Footprint::default(), vector_node(path), 30., 0., 0., false, vec![100.]).await;
|
||||
let sample_points = sample_points.one_item();
|
||||
assert_eq!(sample_points.point_domain.positions().len(), 4);
|
||||
for (pos, expected) in sample_points.point_domain.positions().iter().zip([DVec2::X * 0., DVec2::X * 30., DVec2::X * 60., DVec2::X * 90.]) {
|
||||
@@ -1400,7 +1131,7 @@ mod test {
|
||||
#[tokio::test]
|
||||
async fn adaptive_spacing() {
|
||||
let path = Subpath::from_bezier(&Bezier::from_cubic_dvec2(DVec2::ZERO, DVec2::ZERO, DVec2::X * 100., DVec2::X * 100.));
|
||||
let sample_points = super::sample_points(Footprint::default(), &vector_node(path), 18., 45., 10., true, &FutureWrapperNode(vec![100.])).await;
|
||||
let sample_points = super::sample_points(Footprint::default(), vector_node(path), 18., 45., 10., true, vec![100.]).await;
|
||||
let sample_points = sample_points.one_item();
|
||||
assert_eq!(sample_points.point_domain.positions().len(), 4);
|
||||
for (pos, expected) in sample_points.point_domain.positions().iter().zip([DVec2::X * 45., DVec2::X * 60., DVec2::X * 75., DVec2::X * 90.]) {
|
||||
@@ -1411,7 +1142,7 @@ mod test {
|
||||
async fn poisson() {
|
||||
let sample_points = super::poisson_disk_points(
|
||||
Footprint::default(),
|
||||
&vector_node(Subpath::new_ellipse(DVec2::NEG_ONE * 50., DVec2::ONE * 50.)),
|
||||
vector_node(Subpath::new_ellipse(DVec2::NEG_ONE * 50., DVec2::ONE * 50.)),
|
||||
10. * std::f64::consts::SQRT_2,
|
||||
0,
|
||||
)
|
||||
@@ -1429,12 +1160,12 @@ mod test {
|
||||
#[tokio::test]
|
||||
async fn lengths() {
|
||||
let subpath = Subpath::from_bezier(&Bezier::from_cubic_dvec2(DVec2::ZERO, DVec2::ZERO, DVec2::X * 100., DVec2::X * 100.));
|
||||
let lengths = subpath_segment_lengths(Footprint::default(), &vector_node(subpath)).await;
|
||||
let lengths = subpath_segment_lengths(Footprint::default(), vector_node(subpath)).await;
|
||||
assert_eq!(lengths, vec![100.]);
|
||||
}
|
||||
#[tokio::test]
|
||||
async fn spline() {
|
||||
let spline = super::spline(Footprint::default(), &vector_node(Subpath::new_rect(DVec2::ZERO, DVec2::ONE * 100.))).await;
|
||||
let spline = super::spline(Footprint::default(), vector_node(Subpath::new_rect(DVec2::ZERO, DVec2::ONE * 100.))).await;
|
||||
let spline = spline.one_item();
|
||||
assert_eq!(spline.stroke_bezier_paths().count(), 1);
|
||||
assert_eq!(spline.point_domain.positions(), &[DVec2::ZERO, DVec2::new(100., 0.), DVec2::new(100., 100.), DVec2::new(0., 100.)]);
|
||||
@@ -1443,7 +1174,7 @@ mod test {
|
||||
async fn morph() {
|
||||
let source = Subpath::new_rect(DVec2::ZERO, DVec2::ONE * 100.);
|
||||
let target = Subpath::new_ellipse(DVec2::NEG_ONE * 100., DVec2::ZERO);
|
||||
let sample_points = super::morph(Footprint::default(), &vector_node(source), &vector_node(target), 0.5, 0).await;
|
||||
let sample_points = super::morph(Footprint::default(), vector_node(source), vector_node(target), 0.5, 0).await;
|
||||
let sample_points = sample_points.one_item();
|
||||
assert_eq!(
|
||||
&sample_points.point_domain.positions()[..4],
|
||||
@@ -1452,7 +1183,7 @@ mod test {
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn contains_segment(vector: &VectorData, target: bezier_rs::Bezier) {
|
||||
fn contains_segment(vector: VectorData, target: bezier_rs::Bezier) {
|
||||
let segments = vector.segment_bezier_iter().map(|x| x.1);
|
||||
let count = segments.filter(|bezier| bezier.abs_diff_eq(&target, 0.01) || bezier.reversed().abs_diff_eq(&target, 0.01)).count();
|
||||
assert_eq!(count, 1, "Incorrect number of {target:#?} in {:#?}", vector.segment_bezier_iter().collect::<Vec<_>>());
|
||||
@@ -1461,42 +1192,42 @@ mod test {
|
||||
#[tokio::test]
|
||||
async fn bevel_rect() {
|
||||
let source = Subpath::new_rect(DVec2::ZERO, DVec2::ONE * 100.);
|
||||
let beveled = super::bevel(Footprint::default(), &vector_node(source), 5.).await;
|
||||
let beveled = super::bevel(Footprint::default(), vector_node(source), 5.);
|
||||
let beveled = beveled.one_item();
|
||||
|
||||
assert_eq!(beveled.point_domain.positions().len(), 8);
|
||||
assert_eq!(beveled.segment_domain.ids().len(), 8);
|
||||
|
||||
// Segments
|
||||
contains_segment(&beveled, bezier_rs::Bezier::from_linear_dvec2(DVec2::new(5., 0.), DVec2::new(95., 0.)));
|
||||
contains_segment(&beveled, bezier_rs::Bezier::from_linear_dvec2(DVec2::new(5., 100.), DVec2::new(95., 100.)));
|
||||
contains_segment(&beveled, bezier_rs::Bezier::from_linear_dvec2(DVec2::new(0., 5.), DVec2::new(0., 95.)));
|
||||
contains_segment(&beveled, bezier_rs::Bezier::from_linear_dvec2(DVec2::new(100., 5.), DVec2::new(100., 95.)));
|
||||
contains_segment(beveled.clone(), bezier_rs::Bezier::from_linear_dvec2(DVec2::new(5., 0.), DVec2::new(95., 0.)));
|
||||
contains_segment(beveled.clone(), bezier_rs::Bezier::from_linear_dvec2(DVec2::new(5., 100.), DVec2::new(95., 100.)));
|
||||
contains_segment(beveled.clone(), bezier_rs::Bezier::from_linear_dvec2(DVec2::new(0., 5.), DVec2::new(0., 95.)));
|
||||
contains_segment(beveled.clone(), bezier_rs::Bezier::from_linear_dvec2(DVec2::new(100., 5.), DVec2::new(100., 95.)));
|
||||
|
||||
// Joins
|
||||
contains_segment(&beveled, bezier_rs::Bezier::from_linear_dvec2(DVec2::new(5., 0.), DVec2::new(0., 5.)));
|
||||
contains_segment(&beveled, bezier_rs::Bezier::from_linear_dvec2(DVec2::new(95., 0.), DVec2::new(100., 5.)));
|
||||
contains_segment(&beveled, bezier_rs::Bezier::from_linear_dvec2(DVec2::new(100., 95.), DVec2::new(95., 100.)));
|
||||
contains_segment(&beveled, bezier_rs::Bezier::from_linear_dvec2(DVec2::new(5., 100.), DVec2::new(0., 95.)));
|
||||
contains_segment(beveled.clone(), bezier_rs::Bezier::from_linear_dvec2(DVec2::new(5., 0.), DVec2::new(0., 5.)));
|
||||
contains_segment(beveled.clone(), bezier_rs::Bezier::from_linear_dvec2(DVec2::new(95., 0.), DVec2::new(100., 5.)));
|
||||
contains_segment(beveled.clone(), bezier_rs::Bezier::from_linear_dvec2(DVec2::new(100., 95.), DVec2::new(95., 100.)));
|
||||
contains_segment(beveled.clone(), bezier_rs::Bezier::from_linear_dvec2(DVec2::new(5., 100.), DVec2::new(0., 95.)));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn bevel_open_curve() {
|
||||
let curve = Bezier::from_cubic_dvec2(DVec2::ZERO, DVec2::new(10., 0.), DVec2::new(10., 100.), DVec2::X * 100.);
|
||||
let source = Subpath::from_beziers(&[Bezier::from_linear_dvec2(DVec2::X * -100., DVec2::ZERO), curve], false);
|
||||
let beveled = super::bevel(Footprint::default(), &vector_node(source), 5.).await;
|
||||
let beveled = super::bevel((), vector_node(source), 5.);
|
||||
let beveled = beveled.one_item();
|
||||
|
||||
assert_eq!(beveled.point_domain.positions().len(), 4);
|
||||
assert_eq!(beveled.segment_domain.ids().len(), 3);
|
||||
|
||||
// Segments
|
||||
contains_segment(&beveled, bezier_rs::Bezier::from_linear_dvec2(DVec2::new(-5., 0.), DVec2::new(-100., 0.)));
|
||||
contains_segment(beveled.clone(), bezier_rs::Bezier::from_linear_dvec2(DVec2::new(-5., 0.), DVec2::new(-100., 0.)));
|
||||
let trimmed = curve.trim(bezier_rs::TValue::Euclidean(5. / curve.length(Some(0.00001))), bezier_rs::TValue::Parametric(1.));
|
||||
contains_segment(&beveled, trimmed);
|
||||
contains_segment(beveled.clone(), trimmed);
|
||||
|
||||
// Join
|
||||
contains_segment(&beveled, bezier_rs::Bezier::from_linear_dvec2(DVec2::new(-5., 0.), trimmed.start));
|
||||
contains_segment(beveled.clone(), bezier_rs::Bezier::from_linear_dvec2(DVec2::new(-5., 0.), trimmed.start));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -1506,7 +1237,7 @@ mod test {
|
||||
let mut vector_data = VectorData::from_subpath(source);
|
||||
let transform = DAffine2::from_scale_angle_translation(DVec2::splat(10.), 1., DVec2::new(99., 77.));
|
||||
vector_data.transform = transform;
|
||||
let beveled = super::bevel(Footprint::default(), &FutureWrapperNode(VectorDataTable::new(vector_data)), 5.).await;
|
||||
let beveled = super::bevel((), VectorDataTable::new(vector_data), 5.);
|
||||
let beveled = beveled.one_item();
|
||||
|
||||
assert_eq!(beveled.point_domain.positions().len(), 4);
|
||||
@@ -1514,31 +1245,31 @@ mod test {
|
||||
assert_eq!(beveled.transform, transform);
|
||||
|
||||
// Segments
|
||||
contains_segment(&beveled, bezier_rs::Bezier::from_linear_dvec2(DVec2::new(-0.5, 0.), DVec2::new(-10., 0.)));
|
||||
contains_segment(beveled.clone(), bezier_rs::Bezier::from_linear_dvec2(DVec2::new(-0.5, 0.), DVec2::new(-10., 0.)));
|
||||
let trimmed = curve.trim(bezier_rs::TValue::Euclidean(0.5 / curve.length(Some(0.00001))), bezier_rs::TValue::Parametric(1.));
|
||||
contains_segment(&beveled, trimmed);
|
||||
contains_segment(beveled.clone(), trimmed);
|
||||
|
||||
// Join
|
||||
contains_segment(&beveled, bezier_rs::Bezier::from_linear_dvec2(DVec2::new(-0.5, 0.), trimmed.start));
|
||||
contains_segment(beveled.clone(), bezier_rs::Bezier::from_linear_dvec2(DVec2::new(-0.5, 0.), trimmed.start));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn bevel_too_high() {
|
||||
let source = Subpath::from_anchors([DVec2::ZERO, DVec2::new(100., 0.), DVec2::new(100., 100.), DVec2::new(0., 100.)], false);
|
||||
let beveled = super::bevel(Footprint::default(), &vector_node(source), 999.).await;
|
||||
let beveled = super::bevel(Footprint::default(), vector_node(source), 999.);
|
||||
let beveled = beveled.one_item();
|
||||
|
||||
assert_eq!(beveled.point_domain.positions().len(), 6);
|
||||
assert_eq!(beveled.segment_domain.ids().len(), 5);
|
||||
|
||||
// Segments
|
||||
contains_segment(&beveled, bezier_rs::Bezier::from_linear_dvec2(DVec2::new(0., 0.), DVec2::new(50., 0.)));
|
||||
contains_segment(&beveled, bezier_rs::Bezier::from_linear_dvec2(DVec2::new(100., 50.), DVec2::new(100., 50.)));
|
||||
contains_segment(&beveled, bezier_rs::Bezier::from_linear_dvec2(DVec2::new(100., 50.), DVec2::new(50., 100.)));
|
||||
contains_segment(beveled.clone(), bezier_rs::Bezier::from_linear_dvec2(DVec2::new(0., 0.), DVec2::new(50., 0.)));
|
||||
contains_segment(beveled.clone(), bezier_rs::Bezier::from_linear_dvec2(DVec2::new(100., 50.), DVec2::new(100., 50.)));
|
||||
contains_segment(beveled.clone(), bezier_rs::Bezier::from_linear_dvec2(DVec2::new(100., 50.), DVec2::new(50., 100.)));
|
||||
|
||||
// Joins
|
||||
contains_segment(&beveled, bezier_rs::Bezier::from_linear_dvec2(DVec2::new(50., 0.), DVec2::new(100., 50.)));
|
||||
contains_segment(&beveled, bezier_rs::Bezier::from_linear_dvec2(DVec2::new(100., 50.), DVec2::new(50., 100.)));
|
||||
contains_segment(beveled.clone(), bezier_rs::Bezier::from_linear_dvec2(DVec2::new(50., 0.), DVec2::new(100., 50.)));
|
||||
contains_segment(beveled.clone(), bezier_rs::Bezier::from_linear_dvec2(DVec2::new(100., 50.), DVec2::new(50., 100.)));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -1546,18 +1277,18 @@ mod test {
|
||||
let curve = Bezier::from_cubic_dvec2(DVec2::ZERO, DVec2::new(10., 0.), DVec2::new(10., 100.), DVec2::X * 100.);
|
||||
let point = Bezier::from_cubic_dvec2(DVec2::ZERO, DVec2::ZERO, DVec2::ZERO, DVec2::ZERO);
|
||||
let source = Subpath::from_beziers(&[Bezier::from_linear_dvec2(DVec2::X * -100., DVec2::ZERO), point, curve], false);
|
||||
let beveled = super::bevel(Footprint::default(), &vector_node(source), 5.).await;
|
||||
let beveled = super::bevel(Footprint::default(), vector_node(source), 5.);
|
||||
let beveled = beveled.one_item();
|
||||
|
||||
assert_eq!(beveled.point_domain.positions().len(), 6);
|
||||
assert_eq!(beveled.segment_domain.ids().len(), 5);
|
||||
|
||||
// Segments
|
||||
contains_segment(&beveled, bezier_rs::Bezier::from_linear_dvec2(DVec2::new(-100., 0.), DVec2::new(-5., 0.)));
|
||||
contains_segment(&beveled, bezier_rs::Bezier::from_linear_dvec2(DVec2::new(-5., 0.), DVec2::new(0., 0.)));
|
||||
contains_segment(&beveled, point);
|
||||
contains_segment(beveled.clone(), bezier_rs::Bezier::from_linear_dvec2(DVec2::new(-100., 0.), DVec2::new(-5., 0.)));
|
||||
contains_segment(beveled.clone(), bezier_rs::Bezier::from_linear_dvec2(DVec2::new(-5., 0.), DVec2::new(0., 0.)));
|
||||
contains_segment(beveled.clone(), point);
|
||||
let [start, end] = curve.split(bezier_rs::TValue::Euclidean(5. / curve.length(Some(0.00001))));
|
||||
contains_segment(&beveled, bezier_rs::Bezier::from_linear_dvec2(start.start, start.end));
|
||||
contains_segment(&beveled, end);
|
||||
contains_segment(beveled.clone(), bezier_rs::Bezier::from_linear_dvec2(start.start, start.end));
|
||||
contains_segment(beveled.clone(), end);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user