mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-18 18:28:08 +08:00
Lay groundwork for adaptive resolution system (#1395)
* Make transform node accept footprint as input and pass it along to its input use f32 instead of f64 and add default to document node definition * Add cull node * Fix types for Transform and Cull Nodes * Add render config struct * Add Render Node skeleton * Add Render Node to node_registry * Make types macro use macro hygiene * Place Render Node as output * Start making DownresNode footprint aware * Correctly calculate footprint in Transform Node * Add cropping and resizing to downres node * Fix Output node declaration * Fix image transform * Fix Vector Data rendering * Add concept of ImageRenderMode * Take base image size into account when calculating the final image size * Supply viewport transform to the node graph * Start adapting document graph to resolution agnosticism * Make document node short circuting not shift the input index * Apply clippy lints
This commit is contained in:
committed by
Keavon Chambers
parent
239ca698e5
commit
d82f133514
@@ -3,11 +3,9 @@ use gpu_executor::{ShaderIO, ShaderInput};
|
||||
use graph_craft::concrete;
|
||||
use graph_craft::document::value::TaggedValue;
|
||||
use graph_craft::document::*;
|
||||
use graph_craft::*;
|
||||
use graphene_core::raster::adjustments::BlendMode;
|
||||
use graphene_core::Color;
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::time::Duration;
|
||||
|
||||
fn main() {
|
||||
|
||||
@@ -9,10 +9,25 @@ license = "MIT OR Apache-2.0"
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[features]
|
||||
std = ["dyn-any", "dyn-any/std", "alloc", "glam/std", "specta", "num-traits/std", "rustybuzz"]
|
||||
std = [
|
||||
"dyn-any",
|
||||
"dyn-any/std",
|
||||
"alloc",
|
||||
"glam/std",
|
||||
"specta",
|
||||
"num-traits/std",
|
||||
"rustybuzz",
|
||||
"image",
|
||||
]
|
||||
default = ["async", "serde", "kurbo", "log", "std", "rand_chacha", "wasm"]
|
||||
log = ["dep:log"]
|
||||
serde = ["dep:serde", "glam/serde", "bezier-rs/serde", "bezier-rs/serde", "base64"]
|
||||
serde = [
|
||||
"dep:serde",
|
||||
"glam/serde",
|
||||
"bezier-rs/serde",
|
||||
"bezier-rs/serde",
|
||||
"base64",
|
||||
]
|
||||
gpu = ["spirv-std", "glam/bytemuck", "dyn-any", "glam/libm"]
|
||||
async = ["async-trait", "alloc"]
|
||||
nightly = []
|
||||
@@ -46,6 +61,9 @@ glam = { version = "0.24", default-features = false, features = [
|
||||
] }
|
||||
node-macro = { path = "../node-macro" }
|
||||
base64 = { version = "0.21", optional = true }
|
||||
image = { version = "0.24", optional = true, default-features = false, features = [
|
||||
"png",
|
||||
] }
|
||||
specta.workspace = true
|
||||
specta.optional = true
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::raster::ImageFrame;
|
||||
use crate::text::FontCache;
|
||||
use crate::transform::{Transform, TransformMut};
|
||||
use crate::transform::{Footprint, Transform, TransformMut};
|
||||
use crate::{Color, Node};
|
||||
|
||||
use dyn_any::{StaticType, StaticTypeSized};
|
||||
@@ -43,7 +43,7 @@ impl<S> From<SurfaceHandleFrame<S>> for SurfaceFrame {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct SurfaceHandle<Surface> {
|
||||
pub surface_id: SurfaceId,
|
||||
pub surface: Surface,
|
||||
@@ -53,7 +53,7 @@ unsafe impl<T: 'static> StaticType for SurfaceHandle<T> {
|
||||
type Static = SurfaceHandle<T>;
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct SurfaceHandleFrame<Surface> {
|
||||
pub surface_handle: Arc<SurfaceHandle<Surface>>,
|
||||
pub transform: DAffine2,
|
||||
@@ -136,12 +136,30 @@ pub trait GetImaginatePreferences {
|
||||
fn get_host_name(&self) -> &str;
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum ExportFormat {
|
||||
#[default]
|
||||
Svg,
|
||||
Png {
|
||||
transparent: bool,
|
||||
},
|
||||
Jpeg,
|
||||
Canvas,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq)]
|
||||
pub struct RenderConfig {
|
||||
pub viewport: Footprint,
|
||||
pub export_format: ExportFormat,
|
||||
}
|
||||
|
||||
pub struct EditorApi<'a, Io> {
|
||||
pub image_frame: Option<ImageFrame<Color>>,
|
||||
pub font_cache: &'a FontCache,
|
||||
pub application_io: &'a Io,
|
||||
pub node_graph_message_sender: &'a dyn NodeGraphUpdateSender,
|
||||
pub imaginate_preferences: &'a dyn GetImaginatePreferences,
|
||||
pub render_config: RenderConfig,
|
||||
}
|
||||
|
||||
impl<'a, Io> Clone for EditorApi<'a, Io> {
|
||||
@@ -152,6 +170,7 @@ impl<'a, Io> Clone for EditorApi<'a, Io> {
|
||||
application_io: self.application_io,
|
||||
node_graph_message_sender: self.node_graph_message_sender,
|
||||
imaginate_preferences: self.imaginate_preferences,
|
||||
render_config: self.render_config,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ use crate::{Color, Node};
|
||||
use dyn_any::{DynAny, StaticType};
|
||||
use node_macro::node_fn;
|
||||
|
||||
use core::future::Future;
|
||||
use core::ops::{Deref, DerefMut};
|
||||
use glam::IVec2;
|
||||
|
||||
@@ -40,6 +41,20 @@ pub struct GraphicElement {
|
||||
pub graphic_element_data: GraphicElementData,
|
||||
}
|
||||
|
||||
impl Default for GraphicElement {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
name: "".to_owned(),
|
||||
blend_mode: BlendMode::Normal,
|
||||
opacity: 1.,
|
||||
visible: true,
|
||||
locked: false,
|
||||
collapsed: false,
|
||||
graphic_element_data: GraphicElementData::VectorShape(Box::new(VectorData::empty())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Some [`ArtboardData`] with some optional clipping bounds that can be exported.
|
||||
/// Similar to an Inkscape page: https://media.inkscape.org/media/doc/release_notes/1.2/Inkscape_1.2.html#Page_tool
|
||||
#[derive(Clone, Debug, Hash, PartialEq, DynAny)]
|
||||
@@ -64,7 +79,8 @@ impl Artboard {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ConstructLayerNode<Name, BlendMode, Opacity, Visible, Locked, Collapsed, Stack> {
|
||||
pub struct ConstructLayerNode<GraphicElementData, Name, BlendMode, Opacity, Visible, Locked, Collapsed, Stack> {
|
||||
graphic_element_data: GraphicElementData,
|
||||
name: Name,
|
||||
blend_mode: BlendMode,
|
||||
opacity: Opacity,
|
||||
@@ -75,16 +91,19 @@ pub struct ConstructLayerNode<Name, BlendMode, Opacity, Visible, Locked, Collaps
|
||||
}
|
||||
|
||||
#[node_fn(ConstructLayerNode)]
|
||||
fn construct_layer<Data: Into<GraphicElementData>>(
|
||||
graphic_element_data: Data,
|
||||
async fn construct_layer<Data: Into<GraphicElementData>, Fut1: Future<Output = Data>, Fut2: Future<Output = GraphicGroup>>(
|
||||
footprint: crate::transform::Footprint,
|
||||
graphic_element_data: impl Node<crate::transform::Footprint, Output = Fut1>,
|
||||
name: String,
|
||||
blend_mode: BlendMode,
|
||||
opacity: f32,
|
||||
visible: bool,
|
||||
locked: bool,
|
||||
collapsed: bool,
|
||||
mut stack: GraphicGroup,
|
||||
mut stack: impl Node<crate::transform::Footprint, Output = Fut2>,
|
||||
) -> GraphicGroup {
|
||||
let graphic_element_data = self.graphic_element_data.eval(footprint).await;
|
||||
let mut stack = self.stack.eval(footprint).await;
|
||||
stack.push(GraphicElement {
|
||||
name,
|
||||
blend_mode,
|
||||
@@ -137,7 +156,6 @@ impl From<GraphicGroup> for GraphicElementData {
|
||||
GraphicElementData::GraphicGroup(graphic_group)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Artboard> for GraphicElementData {
|
||||
fn from(artboard: Artboard) -> Self {
|
||||
GraphicElementData::Artboard(artboard)
|
||||
@@ -156,6 +174,28 @@ impl DerefMut for GraphicGroup {
|
||||
}
|
||||
}
|
||||
|
||||
/// This is a helper trait used for the Into Implementation.
|
||||
/// We can't just implement this for all for which from is implemented
|
||||
/// as that would conflict with the implementation for `Self`
|
||||
trait ToGraphicElement: Into<GraphicElementData> {}
|
||||
|
||||
impl ToGraphicElement for VectorData {}
|
||||
impl ToGraphicElement for ImageFrame<Color> {}
|
||||
impl ToGraphicElement for Artboard {}
|
||||
|
||||
impl<T> From<T> for GraphicGroup
|
||||
where
|
||||
T: ToGraphicElement,
|
||||
{
|
||||
fn from(value: T) -> Self {
|
||||
let element = GraphicElement {
|
||||
graphic_element_data: value.into(),
|
||||
..Default::default()
|
||||
};
|
||||
Self(vec![element])
|
||||
}
|
||||
}
|
||||
|
||||
impl GraphicGroup {
|
||||
pub const EMPTY: Self = Self(Vec::new());
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
use crate::raster::{Image, ImageFrame};
|
||||
use crate::uuid::{generate_uuid, ManipulatorGroupId};
|
||||
use crate::{vector::VectorData, Artboard, Color, GraphicElementData, GraphicGroup};
|
||||
use base64::Engine;
|
||||
use bezier_rs::Subpath;
|
||||
use image::ImageEncoder;
|
||||
pub use quad::Quad;
|
||||
|
||||
use glam::{DAffine2, DVec2};
|
||||
@@ -124,16 +126,28 @@ impl Default for SvgRender {
|
||||
}
|
||||
}
|
||||
|
||||
pub enum ImageRenderMode {
|
||||
BlobUrl,
|
||||
Canvas,
|
||||
Base64,
|
||||
}
|
||||
|
||||
/// Static state used whilst rendering
|
||||
pub struct RenderParams {
|
||||
pub view_mode: crate::vector::style::ViewMode,
|
||||
pub image_render_mode: ImageRenderMode,
|
||||
pub culling_bounds: Option<[DVec2; 2]>,
|
||||
pub thumbnail: bool,
|
||||
}
|
||||
|
||||
impl RenderParams {
|
||||
pub fn new(view_mode: crate::vector::style::ViewMode, culling_bounds: Option<[DVec2; 2]>, thumbnail: bool) -> Self {
|
||||
Self { view_mode, culling_bounds, thumbnail }
|
||||
pub fn new(view_mode: crate::vector::style::ViewMode, image_render_mode: ImageRenderMode, culling_bounds: Option<[DVec2; 2]>, thumbnail: bool) -> Self {
|
||||
Self {
|
||||
view_mode,
|
||||
image_render_mode,
|
||||
culling_bounds,
|
||||
thumbnail,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -263,17 +277,48 @@ impl GraphicElementRendered for Artboard {
|
||||
}
|
||||
|
||||
impl GraphicElementRendered for ImageFrame<Color> {
|
||||
fn render_svg(&self, render: &mut SvgRender, _render_params: &RenderParams) {
|
||||
fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) {
|
||||
let transform: String = format_transform_matrix(self.transform * render.transform);
|
||||
let uuid = generate_uuid();
|
||||
render.leaf_tag("image", |attributes| {
|
||||
attributes.push("width", 1.to_string());
|
||||
attributes.push("height", 1.to_string());
|
||||
attributes.push("preserveAspectRatio", "none");
|
||||
attributes.push("transform", transform);
|
||||
attributes.push("href", SvgSegment::BlobUrl(uuid))
|
||||
});
|
||||
render.image_data.push((uuid, self.image.clone()))
|
||||
|
||||
match render_params.image_render_mode {
|
||||
ImageRenderMode::BlobUrl => {
|
||||
render.leaf_tag("image", move |attributes| {
|
||||
attributes.push("width", 1.to_string());
|
||||
attributes.push("height", 1.to_string());
|
||||
attributes.push("preserveAspectRatio", "none");
|
||||
attributes.push("transform", transform);
|
||||
attributes.push("href", SvgSegment::BlobUrl(uuid))
|
||||
});
|
||||
render.image_data.push((uuid, self.image.clone()))
|
||||
}
|
||||
ImageRenderMode::Base64 => {
|
||||
let image = &self.image;
|
||||
let (flat_data, _, _) = image.clone().into_flat_u8();
|
||||
let mut output = Vec::new();
|
||||
let encoder = image::codecs::png::PngEncoder::new(&mut output);
|
||||
encoder
|
||||
.write_image(&flat_data, image.width, image.height, image::ColorType::Rgba8)
|
||||
.expect("failed to encode image as png");
|
||||
let preamble = "data:image/png;base64,";
|
||||
let mut base64_string = String::with_capacity(preamble.len() + output.len() * 4);
|
||||
base64_string.push_str(preamble);
|
||||
log::debug!("len: {}", image.data.len());
|
||||
base64::engine::general_purpose::STANDARD.encode_string(output, &mut base64_string);
|
||||
|
||||
render.leaf_tag("image", |attributes| {
|
||||
attributes.push("width", image.width.to_string());
|
||||
|
||||
attributes.push("height", image.height.to_string());
|
||||
attributes.push("preserveAspectRatio", "none");
|
||||
attributes.push("transform", transform);
|
||||
attributes.push("href", base64_string)
|
||||
});
|
||||
}
|
||||
ImageRenderMode::Canvas => {
|
||||
todo!("Canvas rendering is not yet implemented")
|
||||
}
|
||||
}
|
||||
}
|
||||
fn bounding_box(&self, transform: DAffine2) -> Option<[DVec2; 2]> {
|
||||
let transform = self.transform * transform;
|
||||
|
||||
@@ -43,6 +43,7 @@ pub mod quantization;
|
||||
|
||||
use core::any::TypeId;
|
||||
pub use raster::Color;
|
||||
pub use types::Cow;
|
||||
|
||||
// pub trait Node: for<'n> NodeIO<'n> {
|
||||
pub trait Node<'i, Input: 'i>: 'i {
|
||||
|
||||
@@ -616,7 +616,7 @@ fn dimensions_node<_P>(input: ImageSlice<'input, _P>) -> (u32, u32) {
|
||||
}
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
pub use image::{CollectNode, Image, ImageFrame, ImageRefNode, MapImageSliceNode};
|
||||
pub use self::image::{CollectNode, Image, ImageFrame, ImageRefNode, MapImageSliceNode};
|
||||
#[cfg(feature = "alloc")]
|
||||
pub(crate) mod image;
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ pub struct AxisAlignedBbox {
|
||||
|
||||
impl AxisAlignedBbox {
|
||||
pub const ZERO: Self = Self { start: DVec2::ZERO, end: DVec2::ZERO };
|
||||
pub const ONE: Self = Self { start: DVec2::ZERO, end: DVec2::ONE };
|
||||
|
||||
pub fn size(&self) -> DVec2 {
|
||||
self.end - self.start
|
||||
@@ -44,6 +45,13 @@ impl AxisAlignedBbox {
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn intersect(&self, other: &AxisAlignedBbox) -> AxisAlignedBbox {
|
||||
AxisAlignedBbox {
|
||||
start: DVec2::new(self.start.x.max(other.start.x), self.start.y.max(other.start.y)),
|
||||
end: DVec2::new(self.end.x.min(other.end.x), self.end.y.min(other.end.y)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(not(target_arch = "spirv"), derive(Debug))]
|
||||
|
||||
@@ -145,7 +145,7 @@ where
|
||||
/// Flattens each channel cast to a u8
|
||||
pub fn into_flat_u8(self) -> (Vec<u8>, u32, u32) {
|
||||
let Image { width, height, data } = self;
|
||||
assert!(data.len() == width as usize * height as usize);
|
||||
assert_eq!(data.len(), width as usize * height as usize);
|
||||
|
||||
// Cache the last sRGB value we computed, speeds up fills.
|
||||
let mut last_r = 0.;
|
||||
|
||||
0
node-graph/gcore/src/raster/image/base64_serde.rs
Normal file
0
node-graph/gcore/src/raster/image/base64_serde.rs
Normal file
@@ -1,7 +1,11 @@
|
||||
use core::future::Future;
|
||||
|
||||
use dyn_any::StaticType;
|
||||
use glam::DAffine2;
|
||||
|
||||
use glam::DVec2;
|
||||
|
||||
use crate::raster::bbox::AxisAlignedBbox;
|
||||
use crate::raster::ImageFrame;
|
||||
use crate::raster::Pixel;
|
||||
use crate::vector::VectorData;
|
||||
@@ -116,7 +120,8 @@ impl TransformMut for DAffine2 {
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct TransformNode<Translation, Rotation, Scale, Shear, Pivot> {
|
||||
pub struct TransformNode<TransformTarget, Translation, Rotation, Scale, Shear, Pivot> {
|
||||
pub(crate) transform_target: TransformTarget,
|
||||
pub(crate) translate: Translation,
|
||||
pub(crate) rotate: Rotation,
|
||||
pub(crate) scale: Scale,
|
||||
@@ -124,11 +129,107 @@ pub struct TransformNode<Translation, Rotation, Scale, Shear, Pivot> {
|
||||
pub(crate) pivot: Pivot,
|
||||
}
|
||||
|
||||
#[node_macro::node_fn(TransformNode)]
|
||||
pub(crate) fn transform_vector_data<Data: TransformMut>(mut data: Data, translate: DVec2, rotate: f32, scale: DVec2, shear: DVec2, pivot: DVec2) -> Data {
|
||||
let pivot = DAffine2::from_translation(data.local_pivot(pivot));
|
||||
#[derive(Debug, Clone, Copy, dyn_any::DynAny, PartialEq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub enum RenderQuality {
|
||||
/// Low quality, fast rendering
|
||||
Preview,
|
||||
/// Ensure that the render is available with at least the specified quality
|
||||
/// A value of 0.5 means that the render is available with at least 50% of the final image resolution
|
||||
Scale(f32),
|
||||
/// Flip a coin to decide if the render should be available with the current quality or done at full quality
|
||||
/// This should be used to gradually update the render quality of a cached node
|
||||
Probabilty(f32),
|
||||
/// Render at full quality
|
||||
Full,
|
||||
}
|
||||
#[derive(Debug, Clone, Copy, dyn_any::DynAny, PartialEq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct Footprint {
|
||||
/// Inverse of the transform which will be applied to the node output during the rendering process
|
||||
pub transform: DAffine2,
|
||||
/// Resolution of the target output area in pixels
|
||||
pub resolution: glam::UVec2,
|
||||
/// Quality of the render, this may be used by caching nodes to decide if the cached render is sufficient
|
||||
pub quality: RenderQuality,
|
||||
/// When the transform is set downstream, all upsream modifications have to be ignored
|
||||
pub ignore_modifications: bool,
|
||||
}
|
||||
|
||||
let modification = pivot * DAffine2::from_scale_angle_translation(scale, rotate as f64, translate) * DAffine2::from_cols_array(&[1., shear.y, shear.x, 1., 0., 0.]) * pivot.inverse();
|
||||
impl Default for Footprint {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
transform: DAffine2::IDENTITY,
|
||||
resolution: glam::UVec2::new(1920, 1080),
|
||||
quality: RenderQuality::Full,
|
||||
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());
|
||||
let end = inverse.transform_point2(self.resolution.as_dvec2());
|
||||
AxisAlignedBbox { start, end }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct CullNode<VectorData> {
|
||||
pub(crate) vector_data: VectorData,
|
||||
}
|
||||
|
||||
#[node_macro::node_fn(CullNode)]
|
||||
fn cull_vector_data<T>(footprint: Footprint, vector_data: T) -> T {
|
||||
// TODO: Implement culling
|
||||
vector_data
|
||||
}
|
||||
|
||||
impl core::hash::Hash for Footprint {
|
||||
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
|
||||
self.transform.to_cols_array().iter().for_each(|x| x.to_le_bytes().hash(state));
|
||||
self.resolution.hash(state)
|
||||
}
|
||||
}
|
||||
|
||||
impl Transform for Footprint {
|
||||
fn transform(&self) -> DAffine2 {
|
||||
self.transform
|
||||
}
|
||||
}
|
||||
impl TransformMut for Footprint {
|
||||
fn transform_mut(&mut self) -> &mut DAffine2 {
|
||||
&mut self.transform
|
||||
}
|
||||
}
|
||||
|
||||
#[node_macro::node_fn(TransformNode)]
|
||||
pub(crate) async fn transform_vector_data<Fut: Future>(
|
||||
mut footprint: Footprint,
|
||||
transform_target: impl Node<Footprint, Output = Fut>,
|
||||
translate: DVec2,
|
||||
rotate: f32,
|
||||
scale: DVec2,
|
||||
shear: DVec2,
|
||||
pivot: DVec2,
|
||||
) -> Fut::Output
|
||||
where
|
||||
Fut::Output: TransformMut,
|
||||
{
|
||||
// TOOD: This is hack and might break for Vector data because the pivot may be incorrect
|
||||
let transform = DAffine2::from_scale_angle_translation(scale, rotate as f64, translate) * DAffine2::from_cols_array(&[1., shear.y, shear.x, 1., 0., 0.]);
|
||||
if !footprint.ignore_modifications {
|
||||
let pivot_transform = DAffine2::from_translation(pivot);
|
||||
let modification = pivot_transform * transform * pivot_transform.inverse();
|
||||
*footprint.transform_mut() = footprint.transform() * modification;
|
||||
}
|
||||
|
||||
let mut data = self.transform_target.eval(footprint).await;
|
||||
let pivot_transform = DAffine2::from_translation(data.local_pivot(pivot));
|
||||
|
||||
let modification = pivot_transform * transform * pivot_transform.inverse();
|
||||
let data_transform = data.transform_mut();
|
||||
*data_transform = modification * (*data_transform);
|
||||
|
||||
|
||||
@@ -26,9 +26,9 @@ impl NodeIOTypes {
|
||||
#[macro_export]
|
||||
macro_rules! concrete {
|
||||
($type:ty) => {
|
||||
Type::Concrete(TypeDescriptor {
|
||||
$crate::Type::Concrete($crate::TypeDescriptor {
|
||||
id: Some(core::any::TypeId::of::<$type>()),
|
||||
name: Cow::Borrowed(core::any::type_name::<$type>()),
|
||||
name: $crate::Cow::Borrowed(core::any::type_name::<$type>()),
|
||||
size: core::mem::size_of::<$type>(),
|
||||
align: core::mem::align_of::<$type>(),
|
||||
})
|
||||
@@ -38,9 +38,9 @@ macro_rules! concrete {
|
||||
#[macro_export]
|
||||
macro_rules! concrete_with_name {
|
||||
($type:ty, $name:expr) => {
|
||||
Type::Concrete(TypeDescriptor {
|
||||
$crate::Type::Concrete($crate::TypeDescriptor {
|
||||
id: Some(core::any::TypeId::of::<$type>()),
|
||||
name: Cow::Borrowed($name),
|
||||
name: $crate::Cow::Borrowed($name),
|
||||
size: core::mem::size_of::<$type>(),
|
||||
align: core::mem::align_of::<$type>(),
|
||||
})
|
||||
@@ -50,17 +50,17 @@ macro_rules! concrete_with_name {
|
||||
#[macro_export]
|
||||
macro_rules! generic {
|
||||
($type:ty) => {{
|
||||
Type::Generic(Cow::Borrowed(stringify!($type)))
|
||||
$crate::Type::Generic($crate::Cow::Borrowed(stringify!($type)))
|
||||
}};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! fn_type {
|
||||
($type:ty) => {
|
||||
Type::Fn(Box::new(concrete!(())), Box::new(concrete!($type)))
|
||||
$crate::Type::Fn(Box::new(concrete!(())), Box::new(concrete!($type)))
|
||||
};
|
||||
($in_type:ty, $type:ty) => {
|
||||
Type::Fn(Box::new(concrete!(($in_type))), Box::new(concrete!($type)))
|
||||
$crate::Type::Fn(Box::new(concrete!(($in_type))), Box::new(concrete!($type)))
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -5,8 +5,6 @@ use graphene_core::{NodeIdentifier, Type};
|
||||
use dyn_any::{DynAny, StaticType};
|
||||
use glam::IVec2;
|
||||
pub use graphene_core::uuid::generate_uuid;
|
||||
use graphene_core::TypeDescriptor;
|
||||
use std::borrow::Cow;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
pub mod value;
|
||||
@@ -38,6 +36,7 @@ impl DocumentNodeMetadata {
|
||||
pub struct DocumentNode {
|
||||
pub name: String,
|
||||
pub inputs: Vec<NodeInput>,
|
||||
pub manual_composition: Option<Type>,
|
||||
pub implementation: DocumentNodeImplementation,
|
||||
pub metadata: DocumentNodeMetadata,
|
||||
#[serde(default)]
|
||||
@@ -51,7 +50,7 @@ impl DocumentNode {
|
||||
.inputs
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter(|(_, input)| matches!(input, NodeInput::Network(_) | NodeInput::ShortCircut(_)))
|
||||
.filter(|(_, input)| matches!(input, NodeInput::Network(_)))
|
||||
.nth(offset)
|
||||
.unwrap_or_else(|| panic!("no network input found for {self:#?} and offset: {offset}"));
|
||||
|
||||
@@ -59,10 +58,16 @@ impl DocumentNode {
|
||||
}
|
||||
|
||||
fn resolve_proto_node(mut self) -> ProtoNode {
|
||||
assert_ne!(self.inputs.len(), 0, "Resolving document node {:#?} with no inputs", self);
|
||||
let first = self.inputs.remove(0);
|
||||
if let DocumentNodeImplementation::Unresolved(fqn) = self.implementation {
|
||||
let (input, mut args) = match first {
|
||||
assert!(!self.inputs.is_empty() || self.manual_composition.is_some(), "Resolving document node {:#?} with no inputs", self);
|
||||
let DocumentNodeImplementation::Unresolved(fqn) = self.implementation
|
||||
else {
|
||||
unreachable!("tried to resolve not flattened node on resolved node {:?}", self);
|
||||
};
|
||||
let (input, mut args) = if let Some(ty) = self.manual_composition {
|
||||
(ProtoNodeInput::ShortCircut(ty), ConstructionArgs::Nodes(vec![]))
|
||||
} else {
|
||||
let first = self.inputs.remove(0);
|
||||
match first {
|
||||
NodeInput::Value { tagged_value, .. } => {
|
||||
assert_eq!(self.inputs.len(), 0, "{}, {:?}", &self.name, &self.inputs);
|
||||
(ProtoNodeInput::None, ConstructionArgs::Value(tagged_value))
|
||||
@@ -72,37 +77,33 @@ impl DocumentNode {
|
||||
(ProtoNodeInput::Node(node_id, lambda), ConstructionArgs::Nodes(vec![]))
|
||||
}
|
||||
NodeInput::Network(ty) => (ProtoNodeInput::Network(ty), ConstructionArgs::Nodes(vec![])),
|
||||
NodeInput::ShortCircut(ty) => (ProtoNodeInput::ShortCircut(ty), ConstructionArgs::Nodes(vec![])),
|
||||
NodeInput::Inline(inline) => (ProtoNodeInput::None, ConstructionArgs::Inline(inline)),
|
||||
};
|
||||
assert!(!self.inputs.iter().any(|input| matches!(input, NodeInput::Network(_))), "recieved non resolved parameter");
|
||||
assert!(!self.inputs.iter().any(|input| matches!(input, NodeInput::ShortCircut(_))), "recieved non resolved parameter");
|
||||
assert!(
|
||||
!self.inputs.iter().any(|input| matches!(input, NodeInput::Value { .. })),
|
||||
"recieved value as parameter. inupts: {:#?}, construction_args: {:#?}",
|
||||
&self.inputs,
|
||||
&args
|
||||
);
|
||||
}
|
||||
};
|
||||
assert!(!self.inputs.iter().any(|input| matches!(input, NodeInput::Network(_))), "recieved non resolved parameter");
|
||||
assert!(
|
||||
!self.inputs.iter().any(|input| matches!(input, NodeInput::Value { .. })),
|
||||
"recieved value as parameter. inupts: {:#?}, construction_args: {:#?}",
|
||||
&self.inputs,
|
||||
&args
|
||||
);
|
||||
|
||||
// If we have one parameter of the type inline, set it as the construction args
|
||||
if let &[NodeInput::Inline(ref inline)] = &self.inputs[..] {
|
||||
args = ConstructionArgs::Inline(inline.clone());
|
||||
}
|
||||
if let ConstructionArgs::Nodes(nodes) = &mut args {
|
||||
nodes.extend(self.inputs.iter().map(|input| match input {
|
||||
NodeInput::Node { node_id, lambda, .. } => (*node_id, *lambda),
|
||||
_ => unreachable!(),
|
||||
}));
|
||||
}
|
||||
ProtoNode {
|
||||
identifier: fqn,
|
||||
input,
|
||||
construction_args: args,
|
||||
document_node_path: self.path.unwrap_or(Vec::new()),
|
||||
skip_deduplication: self.skip_deduplication,
|
||||
}
|
||||
} else {
|
||||
unreachable!("tried to resolve not flattened node on resolved node {:?}", self);
|
||||
// If we have one parameter of the type inline, set it as the construction args
|
||||
if let &[NodeInput::Inline(ref inline)] = &self.inputs[..] {
|
||||
args = ConstructionArgs::Inline(inline.clone());
|
||||
}
|
||||
if let ConstructionArgs::Nodes(nodes) = &mut args {
|
||||
nodes.extend(self.inputs.iter().map(|input| match input {
|
||||
NodeInput::Node { node_id, lambda, .. } => (*node_id, *lambda),
|
||||
_ => unreachable!(),
|
||||
}));
|
||||
}
|
||||
ProtoNode {
|
||||
identifier: fqn,
|
||||
input,
|
||||
construction_args: args,
|
||||
document_node_path: self.path.unwrap_or(Vec::new()),
|
||||
skip_deduplication: self.skip_deduplication,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -185,7 +186,7 @@ pub enum NodeInput {
|
||||
/// A short circuting input represents an input that is not resolved through function composition
|
||||
/// but actually consuming the provided input instead of passing it to its predecessor.
|
||||
/// See [NodeInput] docs for more explanation.
|
||||
ShortCircut(Type),
|
||||
// TODO: Update
|
||||
Inline(InlineRust),
|
||||
}
|
||||
|
||||
@@ -226,7 +227,6 @@ impl NodeInput {
|
||||
NodeInput::Node { .. } => true,
|
||||
NodeInput::Value { exposed, .. } => *exposed,
|
||||
NodeInput::Network(_) => false,
|
||||
NodeInput::ShortCircut(_) => false,
|
||||
NodeInput::Inline(_) => false,
|
||||
}
|
||||
}
|
||||
@@ -235,7 +235,6 @@ impl NodeInput {
|
||||
NodeInput::Node { .. } => unreachable!("ty() called on NodeInput::Node"),
|
||||
NodeInput::Value { tagged_value, .. } => tagged_value.ty(),
|
||||
NodeInput::Network(ty) => ty.clone(),
|
||||
NodeInput::ShortCircut(ty) => ty.clone(),
|
||||
NodeInput::Inline(_) => panic!("ty() called on NodeInput::Inline"),
|
||||
}
|
||||
}
|
||||
@@ -350,7 +349,7 @@ impl NodeNetwork {
|
||||
0,
|
||||
DocumentNode {
|
||||
name: "Input Frame".into(),
|
||||
inputs: vec![NodeInput::ShortCircut(concrete!(u32))],
|
||||
manual_composition: Some(concrete!(u32)),
|
||||
implementation: DocumentNodeImplementation::Unresolved("graphene_core::ops::IdNode".into()),
|
||||
metadata: DocumentNodeMetadata { position: (8, 4).into() },
|
||||
..Default::default()
|
||||
@@ -363,7 +362,7 @@ impl NodeNetwork {
|
||||
}
|
||||
|
||||
/// Appends a new node to the network after the output node and sets it as the new output
|
||||
pub fn push_node(&mut self, mut node: DocumentNode, connect_to_previous: bool) -> NodeId {
|
||||
pub fn push_node(&mut self, mut node: DocumentNode) -> NodeId {
|
||||
let id = self.nodes.len().try_into().expect("Too many nodes in network");
|
||||
// Set the correct position for the new node
|
||||
if node.metadata.position == IVec2::default() {
|
||||
@@ -371,7 +370,7 @@ impl NodeNetwork {
|
||||
node.metadata.position = pos + IVec2::new(8, 0);
|
||||
}
|
||||
}
|
||||
if connect_to_previous && !self.outputs.is_empty() {
|
||||
if !self.outputs.is_empty() {
|
||||
let input = NodeInput::node(self.outputs[0].node_id, self.outputs[0].node_output_index);
|
||||
if node.inputs.is_empty() {
|
||||
node.inputs.push(input);
|
||||
@@ -392,7 +391,7 @@ impl NodeNetwork {
|
||||
implementation: DocumentNodeImplementation::Unresolved("graphene_core::ops::IdNode".into()),
|
||||
..Default::default()
|
||||
};
|
||||
self.push_node(node, true)
|
||||
self.push_node(node)
|
||||
}
|
||||
|
||||
/// Adds a Cache and a Clone node to the network
|
||||
@@ -408,7 +407,8 @@ impl NodeNetwork {
|
||||
0,
|
||||
DocumentNode {
|
||||
name: "MemoNode".to_string(),
|
||||
inputs: vec![NodeInput::ShortCircut(concrete!(())), NodeInput::Network(ty)],
|
||||
manual_composition: Some(concrete!(())),
|
||||
inputs: vec![NodeInput::Network(ty)],
|
||||
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::memo::MemoNode")),
|
||||
..Default::default()
|
||||
},
|
||||
@@ -430,7 +430,7 @@ impl NodeNetwork {
|
||||
metadata: DocumentNodeMetadata { position: (0, 0).into() },
|
||||
..Default::default()
|
||||
};
|
||||
self.push_node(node, true)
|
||||
self.push_node(node)
|
||||
}
|
||||
|
||||
/// Get the nested network given by the path of node ids
|
||||
@@ -543,6 +543,14 @@ impl NodeNetwork {
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
pub fn output_as_input(&self, arg: usize) -> NodeInput {
|
||||
NodeInput::Node {
|
||||
node_id: self.outputs[arg].node_id,
|
||||
output_index: self.outputs[arg].node_output_index,
|
||||
lambda: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct FlowIter<'a> {
|
||||
@@ -693,9 +701,8 @@ impl NodeNetwork {
|
||||
break;
|
||||
}
|
||||
|
||||
let mut dummy_input = NodeInput::ShortCircut(concrete!(()));
|
||||
std::mem::swap(&mut dummy_input, input);
|
||||
if let NodeInput::Value { tagged_value, exposed } = dummy_input {
|
||||
let previous_input = std::mem::replace(input, NodeInput::Network(concrete!(())));
|
||||
if let NodeInput::Value { tagged_value, exposed } = previous_input {
|
||||
let value_node_id = gen_id();
|
||||
let merged_node_id = map_ids(id, value_node_id);
|
||||
let path = if let Some(mut new_path) = node.path.clone() {
|
||||
@@ -721,7 +728,7 @@ impl NodeNetwork {
|
||||
lambda: false,
|
||||
};
|
||||
} else {
|
||||
*input = dummy_input;
|
||||
*input = previous_input;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -744,6 +751,11 @@ impl NodeNetwork {
|
||||
node.inputs,
|
||||
inner_network.inputs
|
||||
);
|
||||
assert_eq!(
|
||||
node.inputs.len(),
|
||||
inner_network.inputs.len(),
|
||||
"Document Nodes with a Network implementation should have the same number of inner network inputs as inputs declared on the Document Node"
|
||||
);
|
||||
// Match the document node input and the inputs of the inner network
|
||||
for (document_input, network_input) in node.inputs.into_iter().zip(inner_network.inputs.iter()) {
|
||||
// Keep track of how many network inputs we have already connected for each node
|
||||
@@ -760,7 +772,6 @@ impl NodeNetwork {
|
||||
self.inputs[index] = *network_input;
|
||||
}
|
||||
}
|
||||
NodeInput::ShortCircut(_) => (),
|
||||
NodeInput::Value { .. } => unreachable!("Value inputs should have been replaced with value nodes"),
|
||||
NodeInput::Inline(_) => (),
|
||||
}
|
||||
|
||||
@@ -65,6 +65,8 @@ pub enum TaggedValue {
|
||||
Curve(graphene_core::raster::curve::Curve),
|
||||
IVec2(glam::IVec2),
|
||||
SurfaceFrame(graphene_core::SurfaceFrame),
|
||||
Footprint(graphene_core::transform::Footprint),
|
||||
RenderOutput(RenderOutput),
|
||||
}
|
||||
|
||||
#[allow(clippy::derived_hash_with_manual_eq)]
|
||||
@@ -134,6 +136,8 @@ impl Hash for TaggedValue {
|
||||
Self::Curve(curve) => curve.hash(state),
|
||||
Self::IVec2(v) => v.hash(state),
|
||||
Self::SurfaceFrame(surface_id) => surface_id.hash(state),
|
||||
Self::Footprint(footprint) => footprint.hash(state),
|
||||
Self::RenderOutput(render_output) => render_output.hash(state),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -190,6 +194,8 @@ impl<'a> TaggedValue {
|
||||
TaggedValue::Curve(x) => Box::new(x),
|
||||
TaggedValue::IVec2(x) => Box::new(x),
|
||||
TaggedValue::SurfaceFrame(x) => Box::new(x),
|
||||
TaggedValue::Footprint(x) => Box::new(x),
|
||||
TaggedValue::RenderOutput(x) => Box::new(x),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -208,8 +214,6 @@ impl<'a> TaggedValue {
|
||||
}
|
||||
|
||||
pub fn ty(&self) -> Type {
|
||||
use graphene_core::TypeDescriptor;
|
||||
use std::borrow::Cow;
|
||||
match self {
|
||||
TaggedValue::None => concrete!(()),
|
||||
TaggedValue::String(_) => concrete!(String),
|
||||
@@ -259,6 +263,8 @@ impl<'a> TaggedValue {
|
||||
TaggedValue::Curve(_) => concrete!(graphene_core::raster::curve::Curve),
|
||||
TaggedValue::IVec2(_) => concrete!(glam::IVec2),
|
||||
TaggedValue::SurfaceFrame(_) => concrete!(graphene_core::SurfaceFrame),
|
||||
TaggedValue::Footprint(_) => concrete!(graphene_core::transform::Footprint),
|
||||
TaggedValue::RenderOutput(_) => concrete!(RenderOutput),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -314,10 +320,12 @@ impl<'a> TaggedValue {
|
||||
x if x == TypeId::of::<graphene_core::Artboard>() => Ok(TaggedValue::Artboard(*downcast(input).unwrap())),
|
||||
x if x == TypeId::of::<glam::IVec2>() => Ok(TaggedValue::IVec2(*downcast(input).unwrap())),
|
||||
x if x == TypeId::of::<graphene_core::SurfaceFrame>() => Ok(TaggedValue::SurfaceFrame(*downcast(input).unwrap())),
|
||||
x if x == TypeId::of::<RenderOutput>() => Ok(TaggedValue::RenderOutput(*downcast(input).unwrap())),
|
||||
x if x == TypeId::of::<graphene_core::WasmSurfaceHandleFrame>() => {
|
||||
let frame = *downcast::<graphene_core::WasmSurfaceHandleFrame>(input).unwrap();
|
||||
Ok(TaggedValue::SurfaceFrame(frame.into()))
|
||||
}
|
||||
x if x == TypeId::of::<graphene_core::transform::Footprint>() => Ok(TaggedValue::Footprint(*downcast(input).unwrap())),
|
||||
_ => Err(format!("Cannot convert {:?} to TaggedValue", DynAny::type_name(input.as_ref()))),
|
||||
}
|
||||
}
|
||||
@@ -338,3 +346,11 @@ impl UpcastNode {
|
||||
Self { value }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, dyn_any::DynAny, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub enum RenderOutput {
|
||||
CanvasFrame(graphene_core::SurfaceFrame),
|
||||
Svg(String),
|
||||
Raster(Vec<u8>),
|
||||
}
|
||||
|
||||
@@ -428,7 +428,7 @@ impl ProtoNetwork {
|
||||
let mut visited = HashSet::new();
|
||||
|
||||
let inwards_edges = self.collect_inwards_edges();
|
||||
for (id, _node) in &self.nodes {
|
||||
for (id, _) in &self.nodes {
|
||||
for &dependency in inwards_edges.get(id).unwrap_or(&Vec::new()) {
|
||||
if !visited.contains(&dependency) {
|
||||
dbg!(id, dependency);
|
||||
@@ -580,7 +580,10 @@ impl TypingContext {
|
||||
input.output.clone()
|
||||
}
|
||||
};
|
||||
let impls = self.lookup.get(&node.identifier).ok_or(format!("No implementations found for {:?}", node.identifier))?;
|
||||
let impls = self
|
||||
.lookup
|
||||
.get(&node.identifier)
|
||||
.ok_or(format!("No implementations found for {:?}. Other implementations found {:?}", node.identifier, self.lookup))?;
|
||||
|
||||
if matches!(input, Type::Generic(_)) {
|
||||
return Err(format!("Generic types are not supported as inputs yet {:?} occurred in {:?}", &input, node.identifier));
|
||||
|
||||
@@ -8,12 +8,11 @@ use graph_craft::{
|
||||
document::*,
|
||||
graphene_compiler::{Compiler, Executor},
|
||||
imaginate_input::ImaginatePreferences,
|
||||
NodeIdentifier, Type, TypeDescriptor,
|
||||
NodeIdentifier,
|
||||
};
|
||||
use graphene_core::{
|
||||
application_io::{ApplicationIo, NodeGraphUpdateSender},
|
||||
text::FontCache,
|
||||
Cow,
|
||||
};
|
||||
use graphene_std::wasm_application_io::{WasmApplicationIo, WasmEditorApi};
|
||||
use interpreted_executor::dynamic_executor::DynamicExecutor;
|
||||
@@ -55,6 +54,7 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
||||
application_io: &application_io,
|
||||
node_graph_message_sender: &UpdateLogger {},
|
||||
imaginate_preferences: &ImaginatePreferences::default(),
|
||||
render_config: graphene_core::application_io::RenderConfig::default(),
|
||||
};
|
||||
|
||||
loop {
|
||||
@@ -163,7 +163,7 @@ fn begin_scope() -> DocumentNode {
|
||||
nodes: [
|
||||
DocumentNode {
|
||||
name: "SetNode".to_string(),
|
||||
inputs: vec![NodeInput::ShortCircut(concrete!(WasmEditorApi))],
|
||||
manual_composition: Some(concrete!(WasmEditorApi)),
|
||||
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::SomeNode")),
|
||||
..Default::default()
|
||||
},
|
||||
@@ -175,7 +175,8 @@ fn begin_scope() -> DocumentNode {
|
||||
},
|
||||
DocumentNode {
|
||||
name: "RefNode".to_string(),
|
||||
inputs: vec![NodeInput::ShortCircut(concrete!(())), NodeInput::lambda(1, 0)],
|
||||
manual_composition: Some(concrete!(WasmEditorApi)),
|
||||
inputs: vec![NodeInput::lambda(1, 0)],
|
||||
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::memo::RefNode<_, _>")),
|
||||
..Default::default()
|
||||
},
|
||||
@@ -207,6 +208,7 @@ mod test {
|
||||
application_io: &block_on(WasmApplicationIo::new()),
|
||||
node_graph_message_sender: &UpdateLogger {},
|
||||
imaginate_preferences: &ImaginatePreferences::default(),
|
||||
render_config: graphene_core::application_io::RenderConfig::default(),
|
||||
};
|
||||
let result = (&executor).execute(editor_api.clone()).await.unwrap();
|
||||
println!("result: {:?}", result);
|
||||
@@ -223,6 +225,7 @@ mod test {
|
||||
application_io: &block_on(WasmApplicationIo::new()),
|
||||
node_graph_message_sender: &UpdateLogger {},
|
||||
imaginate_preferences: &ImaginatePreferences::default(),
|
||||
render_config: graphene_core::application_io::RenderConfig::default(),
|
||||
};
|
||||
let result = (&executor).execute(editor_api.clone()).await.unwrap();
|
||||
println!("result: {:?}", result);
|
||||
|
||||
@@ -241,6 +241,9 @@ impl ComposeTypeErased {
|
||||
}
|
||||
|
||||
pub fn input_node<O: StaticType>(n: SharedNodeContainer) -> DowncastBothNode<(), O> {
|
||||
downcast_node(n)
|
||||
}
|
||||
pub fn downcast_node<I: StaticType, O: StaticType>(n: SharedNodeContainer) -> DowncastBothNode<I, O> {
|
||||
DowncastBothNode::new(n)
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ use glam::{DAffine2, DVec2};
|
||||
use graph_craft::imaginate_input::{ImaginateController, ImaginateMaskStartingFill, ImaginateSamplingMethod};
|
||||
use graph_craft::proto::DynFuture;
|
||||
use graphene_core::raster::{Alpha, BlendMode, BlendNode, Image, ImageFrame, Linear, LinearChannel, Luminance, NoiseType, Pixel, RGBMut, Raster, RasterMut, RedGreenBlue, Sample};
|
||||
use graphene_core::transform::Transform;
|
||||
use graphene_core::transform::{Footprint, Transform};
|
||||
|
||||
use crate::wasm_application_io::WasmEditorApi;
|
||||
use graphene_core::raster::bbox::{AxisAlignedBbox, Bbox};
|
||||
@@ -58,29 +58,50 @@ fn buffer_node<R: std::io::Read>(reader: R) -> Result<Vec<u8>, Error> {
|
||||
Ok(std::io::Read::bytes(reader).collect::<Result<Vec<_>, _>>()?)
|
||||
}
|
||||
|
||||
pub struct DownresNode<P> {
|
||||
_p: PhantomData<P>,
|
||||
pub struct DownresNode<ImageFrame> {
|
||||
image_frame: ImageFrame,
|
||||
}
|
||||
|
||||
#[node_macro::node_fn(DownresNode<_P>)]
|
||||
fn downres<_P: Pixel>(image_frame: ImageFrame<_P>) -> ImageFrame<_P> {
|
||||
let target_width = (image_frame.transform.transform_vector2((1., 0.).into()).length() as usize).min(image_frame.image.width as usize);
|
||||
let target_height = (image_frame.transform.transform_vector2((0., 1.).into()).length() as usize).min(image_frame.image.height as usize);
|
||||
#[node_macro::node_fn(DownresNode)]
|
||||
fn downres(footprint: Footprint, image_frame: ImageFrame<Color>) -> ImageFrame<Color> {
|
||||
// resize the image using the image crate
|
||||
let image = image_frame.image;
|
||||
let data = bytemuck::cast_vec(image.data);
|
||||
|
||||
let mut image = Image {
|
||||
width: target_width as u32,
|
||||
height: target_height as u32,
|
||||
data: Vec::with_capacity(target_width * target_height),
|
||||
let viewport_bounds = footprint.viewport_bounds_in_local_space();
|
||||
log::debug!("viewport_bounds: {viewport_bounds:?}");
|
||||
let bbox = Bbox::from_transform(image_frame.transform * DAffine2::from_scale(DVec2::new(image.width as f64, image.height as f64)));
|
||||
log::debug!("local_bounds: {bbox:?}");
|
||||
let bounds = viewport_bounds.intersect(&bbox.to_axis_aligned_bbox());
|
||||
log::debug!("intersection: {bounds:?}");
|
||||
let union = viewport_bounds.union(&bbox.to_axis_aligned_bbox());
|
||||
log::debug!("union: {union:?}");
|
||||
let size = bounds.size();
|
||||
|
||||
let image_buffer = image::Rgba32FImage::from_raw(image.width, image.height, data).expect("Failed to convert internal ImageFrame into image-rs data type.");
|
||||
|
||||
let dynamic_image: image::DynamicImage = image_buffer.into();
|
||||
let offset = (bounds.start - viewport_bounds.start).as_uvec2();
|
||||
let cropped = dynamic_image.crop_imm(offset.x, offset.y, size.x as u32, size.y as u32);
|
||||
|
||||
log::debug!("transform: {:?}", footprint.transform);
|
||||
log::debug!("size: {size:?}");
|
||||
let viewport_resolution_x = footprint.transform.transform_vector2(DVec2::X * size.x).length();
|
||||
let viewport_resolution_y = footprint.transform.transform_vector2(DVec2::Y * size.y).length();
|
||||
let nwidth = viewport_resolution_x as u32;
|
||||
let nheight = viewport_resolution_y as u32;
|
||||
log::debug!("x: {viewport_resolution_x}, y: {viewport_resolution_y}");
|
||||
|
||||
let resized = cropped.resize_exact(nwidth, nheight, image::imageops::Lanczos3);
|
||||
let buffer = resized.to_rgba32f();
|
||||
let buffer = buffer.into_raw();
|
||||
let vec = bytemuck::cast_vec(buffer);
|
||||
let image = Image {
|
||||
width: nwidth,
|
||||
height: nheight,
|
||||
data: vec,
|
||||
};
|
||||
|
||||
let scale_factor = DVec2::new(image_frame.image.width as f64, image_frame.image.height as f64) / DVec2::new(target_width as f64, target_height as f64);
|
||||
for y in 0..target_height {
|
||||
for x in 0..target_width {
|
||||
let pixel = image_frame.sample(DVec2::new(x as f64, y as f64) * scale_factor);
|
||||
image.data.push(pixel);
|
||||
}
|
||||
}
|
||||
|
||||
ImageFrame {
|
||||
image,
|
||||
transform: image_frame.transform,
|
||||
|
||||
@@ -2,13 +2,16 @@ use std::cell::RefCell;
|
||||
|
||||
use core::future::Future;
|
||||
use dyn_any::StaticType;
|
||||
use graphene_core::application_io::{ApplicationError, ApplicationIo, ResourceFuture, SurfaceHandle, SurfaceHandleFrame, SurfaceId};
|
||||
use graphene_core::application_io::{ApplicationError, ApplicationIo, ExportFormat, ResourceFuture, SurfaceHandle, SurfaceHandleFrame, SurfaceId};
|
||||
use graphene_core::raster::Image;
|
||||
use graphene_core::Color;
|
||||
use graphene_core::renderer::{GraphicElementRendered, RenderParams, SvgRender};
|
||||
use graphene_core::transform::Footprint;
|
||||
use graphene_core::vector::style::ViewMode;
|
||||
use graphene_core::{
|
||||
raster::{color::SRGBA8, ImageFrame},
|
||||
Node,
|
||||
};
|
||||
use graphene_core::{Color, GraphicGroup};
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use js_sys::{Object, Reflect};
|
||||
use std::collections::HashMap;
|
||||
@@ -280,3 +283,32 @@ fn decode_image_node<'a: 'input>(data: Arc<[u8]>) -> ImageFrame<Color> {
|
||||
};
|
||||
image
|
||||
}
|
||||
pub use graph_craft::document::value::RenderOutput;
|
||||
|
||||
pub struct RenderNode<Data, Surface> {
|
||||
data: Data,
|
||||
surface_handle: Surface,
|
||||
}
|
||||
|
||||
#[node_macro::node_fn(RenderNode)]
|
||||
async fn render_node<'a: 'input, F: Future<Output = GraphicGroup>>(
|
||||
editor: WasmEditorApi<'a>,
|
||||
data: impl Node<'input, Footprint, Output = F>,
|
||||
surface_handle: Arc<SurfaceHandle<HtmlCanvasElement>>,
|
||||
) -> RenderOutput {
|
||||
let footprint = editor.render_config.viewport;
|
||||
let data = self.data.eval(footprint).await;
|
||||
let mut render = SvgRender::new();
|
||||
let render_params = RenderParams::new(ViewMode::Normal, graphene_core::renderer::ImageRenderMode::Base64, None, false);
|
||||
let output_format = editor.render_config.export_format;
|
||||
|
||||
match output_format {
|
||||
ExportFormat::Svg => {
|
||||
data.render_svg(&mut render, &render_params);
|
||||
// TODO: reenable once we switch to full node graph
|
||||
//render.format_svg((0., 0.).into(), (1., 1.).into());
|
||||
RenderOutput::Svg(render.svg.to_string())
|
||||
}
|
||||
_ => todo!("Non svg render output"),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,13 +6,14 @@ use graphene_core::quantization::{PackedPixel, QuantizationChannels};
|
||||
use graphene_core::raster::brush_cache::BrushCache;
|
||||
use graphene_core::raster::color::Color;
|
||||
use graphene_core::structural::Then;
|
||||
use graphene_core::transform::Footprint;
|
||||
use graphene_core::value::{ClonedNode, CopiedNode, ValueNode};
|
||||
use graphene_core::vector::brush_stroke::BrushStroke;
|
||||
use graphene_core::vector::VectorData;
|
||||
use graphene_core::{application_io::SurfaceHandle, SurfaceFrame, WasmSurfaceHandleFrame};
|
||||
use graphene_core::{concrete, generic};
|
||||
use graphene_core::{concrete, generic, GraphicGroup};
|
||||
use graphene_core::{fn_type, raster::*};
|
||||
use graphene_core::{Cow, NodeIdentifier, Type, TypeDescriptor};
|
||||
use graphene_core::{Cow, NodeIdentifier, Type};
|
||||
use graphene_core::{Node, NodeIO, NodeIOTypes};
|
||||
use graphene_std::any::{ComposeTypeErased, DowncastBothNode, DynAnyNode, FutureWrapperNode, IntoTypeErasedNode};
|
||||
use graphene_std::wasm_application_io::*;
|
||||
@@ -31,12 +32,12 @@ use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
|
||||
macro_rules! construct_node {
|
||||
($args: ident, $path:ty, [$($type:ty),*]) => { async move {
|
||||
($args: ident, $path:ty, [$($arg:ty => $type:ty),*]) => { async move {
|
||||
let mut args = $args.clone();
|
||||
args.reverse();
|
||||
let node = <$path>::new($(
|
||||
{
|
||||
let node = graphene_std::any::input_node::<$type>(args.pop().expect("Not enough arguments provided to construct node"));
|
||||
let node = graphene_std::any::downcast_node::<$arg, $type>(args.pop().expect("Not enough arguments provided to construct node"));
|
||||
let value = node.eval(()).await;
|
||||
graphene_core::value::ClonedNode::new(value)
|
||||
}
|
||||
@@ -49,12 +50,15 @@ macro_rules! construct_node {
|
||||
|
||||
macro_rules! register_node {
|
||||
($path:ty, input: $input:ty, params: [ $($type:ty),*]) => {
|
||||
register_node!($path, input: $input, fn_params: [ $(() => $type),*])
|
||||
};
|
||||
($path:ty, input: $input:ty, fn_params: [ $($arg:ty => $type:ty),*]) => {
|
||||
vec![
|
||||
(
|
||||
NodeIdentifier::new(stringify!($path)),
|
||||
|args| {
|
||||
Box::pin(async move {
|
||||
let node = construct_node!(args, $path, [$($type),*]).await;
|
||||
let node = construct_node!(args, $path, [$($arg => $type),*]).await;
|
||||
let node = graphene_std::any::FutureWrapperNode::new(node);
|
||||
let any: DynAnyNode<$input, _, _> = graphene_std::any::DynAnyNode::new(node);
|
||||
Box::new(any) as TypeErasedBox
|
||||
@@ -77,24 +81,27 @@ macro_rules! async_node {
|
||||
// TODO: we currently need to annotate the type here because the compiler would otherwise (correctly)
|
||||
// assign a Pin<Box<dyn Fututure<Output=T>>> type to the node, which is not what we want for now.
|
||||
($path:ty, input: $input:ty, output: $output:ty, params: [ $($type:ty),*]) => {
|
||||
async_node!($path, input: $input, output: $output, fn_params: [ $(() => $type),*])
|
||||
};
|
||||
($path:ty, input: $input:ty, output: $output:ty, fn_params: [ $($arg:ty => $type:ty),*]) => {
|
||||
vec![
|
||||
(
|
||||
NodeIdentifier::new(stringify!($path)),
|
||||
|mut args| {
|
||||
Box::pin(async move {
|
||||
args.reverse();
|
||||
let node = <$path>::new($(graphene_std::any::input_node::<$type>(args.pop().expect("Not enough arguments provided to construct node"))),*);
|
||||
let node = <$path>::new($(graphene_std::any::downcast_node::<$arg, $type>(args.pop().expect("Not enough arguments provided to construct node"))),*);
|
||||
let any: DynAnyNode<$input, _, _> = graphene_std::any::DynAnyNode::new(node);
|
||||
Box::new(any) as TypeErasedBox
|
||||
})
|
||||
},
|
||||
{
|
||||
let node = <$path>::new($(
|
||||
graphene_std::any::PanicNode::<(), core::pin::Pin<Box<dyn core::future::Future<Output = $type>>>>::new()
|
||||
graphene_std::any::PanicNode::<$arg, core::pin::Pin<Box<dyn core::future::Future<Output = $type>>>>::new()
|
||||
),*);
|
||||
// TODO: Propagate the future type through the node graph
|
||||
//let params = vec![$(Type::Fn(Box::new(concrete!(())), Box::new(Type::Future(Box::new(concrete!($type)))))),*];
|
||||
let params = vec![$(Type::Fn(Box::new(concrete!(())), Box::new(concrete!($type)))),*];
|
||||
let params = vec![$(fn_type!($arg, $type)),*];
|
||||
let mut node_io = NodeIO::<'_, $input>::to_node_io(&node, params);
|
||||
node_io.input = concrete!(<$input as StaticType>::Static);
|
||||
node_io.input = concrete!(<$input as StaticType>::Static);
|
||||
@@ -121,7 +128,7 @@ macro_rules! raster_node {
|
||||
NodeIdentifier::new(stringify!($path)),
|
||||
|args| {
|
||||
Box::pin(async move {
|
||||
let node = construct_node!(args, $path, [$($type),*]).await;
|
||||
let node = construct_node!(args, $path, [$(() => $type),*]).await;
|
||||
let node = graphene_std::any::FutureWrapperNode::new(node);
|
||||
let any: DynAnyNode<Color, _, _> = graphene_std::any::DynAnyNode::new(node);
|
||||
any.into_type_erased()
|
||||
@@ -136,7 +143,7 @@ macro_rules! raster_node {
|
||||
NodeIdentifier::new(stringify!($path)),
|
||||
|args| {
|
||||
Box::pin(async move {
|
||||
let node = construct_node!(args, $path, [$($type),*]).await;
|
||||
let node = construct_node!(args, $path, [$(() => $type),*]).await;
|
||||
let map_node = graphene_std::raster::MapImageNode::new(graphene_core::value::ValueNode::new(node));
|
||||
let map_node = graphene_std::any::FutureWrapperNode::new(map_node);
|
||||
let any: DynAnyNode<Image<Color>, _, _> = graphene_std::any::DynAnyNode::new(map_node);
|
||||
@@ -152,7 +159,7 @@ macro_rules! raster_node {
|
||||
NodeIdentifier::new(stringify!($path)),
|
||||
|args| {
|
||||
Box::pin(async move {
|
||||
let node = construct_node!(args, $path, [$($type),*]).await;
|
||||
let node = construct_node!(args, $path, [$(() => $type),*]).await;
|
||||
let map_node = graphene_std::raster::MapImageNode::new(graphene_core::value::ValueNode::new(node));
|
||||
let map_node = graphene_std::any::FutureWrapperNode::new(map_node);
|
||||
let any: DynAnyNode<ImageFrame<Color>, _, _> = graphene_std::any::DynAnyNode::new(map_node);
|
||||
@@ -266,9 +273,12 @@ fn node_registry() -> HashMap<NodeIdentifier, HashMap<NodeIOTypes, NodeConstruct
|
||||
register_node!(graphene_core::logic::LogicNotNode, input: bool, params: []),
|
||||
async_node!(graphene_core::ops::IntoNode<_, ImageFrame<SRGBA8>>, input: ImageFrame<Color>, output: ImageFrame<SRGBA8>, params: []),
|
||||
async_node!(graphene_core::ops::IntoNode<_, ImageFrame<Color>>, input: ImageFrame<SRGBA8>, output: ImageFrame<Color>, params: []),
|
||||
async_node!(graphene_core::ops::IntoNode<_, GraphicGroup>, input: ImageFrame<Color>, output: GraphicGroup, params: []),
|
||||
async_node!(graphene_core::ops::IntoNode<_, GraphicGroup>, input: VectorData, output: GraphicGroup, params: []),
|
||||
async_node!(graphene_core::ops::IntoNode<_, GraphicGroup>, input: GraphicGroup, output: GraphicGroup, params: []),
|
||||
async_node!(graphene_core::ops::IntoNode<_, GraphicGroup>, input: graphene_core::Artboard, output: GraphicGroup, params: []),
|
||||
#[cfg(feature = "gpu")]
|
||||
async_node!(graphene_core::ops::IntoNode<_, &WgpuExecutor>, input: WasmEditorApi, output: &WgpuExecutor, params: []),
|
||||
register_node!(graphene_std::raster::DownresNode<_>, input: ImageFrame<Color>, params: []),
|
||||
register_node!(graphene_std::raster::MaskImageNode<_, _, _>, input: ImageFrame<Color>, params: [ImageFrame<Color>]),
|
||||
register_node!(graphene_std::raster::MaskImageNode<_, _, _>, input: ImageFrame<Color>, params: [ImageFrame<Luma>]),
|
||||
register_node!(graphene_std::raster::InsertChannelNode<_, _, _, _>, input: ImageFrame<Color>, params: [ImageFrame<Color>, RedGreenBlue]),
|
||||
@@ -535,6 +545,7 @@ fn node_registry() -> HashMap<NodeIdentifier, HashMap<NodeIOTypes, NodeConstruct
|
||||
register_node!(graphene_core::memo::LetNode<_>, input: Option<WasmEditorApi>, params: []),
|
||||
async_node!(graphene_core::memo::EndLetNode<_>, input: WasmEditorApi, output: ImageFrame<Color>, params: [ImageFrame<Color>]),
|
||||
async_node!(graphene_core::memo::EndLetNode<_>, input: WasmEditorApi, output: VectorData, params: [VectorData]),
|
||||
async_node!(graphene_core::memo::EndLetNode<_>, input: WasmEditorApi, output: RenderOutput, params: [RenderOutput]),
|
||||
async_node!(
|
||||
graphene_core::memo::EndLetNode<_>,
|
||||
input: WasmEditorApi,
|
||||
@@ -616,6 +627,7 @@ fn node_registry() -> HashMap<NodeIdentifier, HashMap<NodeIOTypes, NodeConstruct
|
||||
#[cfg(feature = "gpu")]
|
||||
async_node!(graphene_core::memo::MemoNode<_, _>, input: (), output: wgpu_executor::WgpuSurface, params: [wgpu_executor::WgpuSurface]),
|
||||
async_node!(graphene_core::memo::MemoNode<_, _>, input: (), output: SurfaceFrame, params: [SurfaceFrame]),
|
||||
async_node!(graphene_core::memo::MemoNode<_, _>, input: (), output: RenderOutput, params: [RenderOutput]),
|
||||
register_node!(graphene_core::structural::ConsNode<_, _>, input: Image<Color>, params: [&str]),
|
||||
register_node!(graphene_std::raster::ImageFrameNode<_, _>, input: Image<Color>, params: [DAffine2]),
|
||||
register_node!(graphene_std::raster::PixelNoiseNode<_, _, _>, input: u32, params: [u32, u32, NoiseType]),
|
||||
@@ -624,9 +636,90 @@ fn node_registry() -> HashMap<NodeIdentifier, HashMap<NodeIOTypes, NodeConstruct
|
||||
register_node!(graphene_core::quantization::QuantizeNode<_>, input: Color, params: [QuantizationChannels]),
|
||||
register_node!(graphene_core::quantization::DeQuantizeNode<_>, input: PackedPixel, params: [QuantizationChannels]),
|
||||
register_node!(graphene_core::ops::CloneNode<_>, input: &QuantizationChannels, params: []),
|
||||
register_node!(graphene_core::transform::TransformNode<_, _, _, _, _>, input: VectorData, params: [DVec2, f32, DVec2, DVec2, DVec2]),
|
||||
register_node!(graphene_core::transform::TransformNode<_, _, _, _, _>, input: ImageFrame<Color>, params: [DVec2, f32, DVec2, DVec2, DVec2]),
|
||||
register_node!(graphene_core::transform::TransformNode<_, _, _, _, _>, input: WasmSurfaceHandleFrame, params: [DVec2, f32, DVec2, DVec2, DVec2]),
|
||||
async_node!(graphene_std::wasm_application_io::RenderNode<_, _>, input: WasmEditorApi, output: RenderOutput, fn_params: [Footprint => GraphicGroup, () => Arc<WasmSurfaceHandle>]),
|
||||
//register_node!(graphene_core::transform::TranformNode<_, _, _, _, _, _>, input: , output: RenderOutput, fn_params: [Footprint => GraphicGroup, () => Arc<WasmSurfaceHandle>]),
|
||||
vec![
|
||||
(
|
||||
NodeIdentifier::new("graphene_core::transform::TransformNode<_, _, _, _, _, _>"),
|
||||
|mut args| {
|
||||
Box::pin(async move {
|
||||
args.reverse();
|
||||
let node = <graphene_core::transform::TransformNode<_, _, _, _, _, _>>::new(
|
||||
DowncastBothNode::<Footprint, VectorData>::new(args.pop().expect("Not enough arguments provided to construct node")),
|
||||
graphene_std::any::input_node::<DVec2>(args.pop().expect("Not enough arguments provided to construct node")),
|
||||
graphene_std::any::input_node::<f32>(args.pop().expect("Not enough arguments provided to construct node")),
|
||||
graphene_std::any::input_node::<DVec2>(args.pop().expect("Not enough arguments provided to construct node")),
|
||||
graphene_std::any::input_node::<DVec2>(args.pop().expect("Not enough arguments provided to construct node")),
|
||||
graphene_std::any::input_node::<DVec2>(args.pop().expect("Not enough arguments provided to construct node")),
|
||||
);
|
||||
let any: DynAnyNode<Footprint, _, _> = graphene_std::any::DynAnyNode::new(node);
|
||||
Box::new(any) as TypeErasedBox
|
||||
})
|
||||
},
|
||||
{
|
||||
let params = vec![fn_type!(Footprint, VectorData), fn_type!(DVec2), fn_type!(f32), fn_type!(DVec2), fn_type!(DVec2), fn_type!(DVec2)];
|
||||
NodeIOTypes::new(concrete!(Footprint), concrete!(VectorData), params)
|
||||
},
|
||||
),
|
||||
(
|
||||
NodeIdentifier::new("graphene_core::transform::TransformNode<_, _, _, _, _, _>"),
|
||||
|mut args| {
|
||||
Box::pin(async move {
|
||||
args.reverse();
|
||||
let node = <graphene_core::transform::TransformNode<_, _, _, _, _, _>>::new(
|
||||
DowncastBothNode::<Footprint, WasmSurfaceHandleFrame>::new(args.pop().expect("Not enough arguments provided to construct node")),
|
||||
graphene_std::any::input_node::<DVec2>(args.pop().expect("Not enough arguments provided to construct node")),
|
||||
graphene_std::any::input_node::<f32>(args.pop().expect("Not enough arguments provided to construct node")),
|
||||
graphene_std::any::input_node::<DVec2>(args.pop().expect("Not enough arguments provided to construct node")),
|
||||
graphene_std::any::input_node::<DVec2>(args.pop().expect("Not enough arguments provided to construct node")),
|
||||
graphene_std::any::input_node::<DVec2>(args.pop().expect("Not enough arguments provided to construct node")),
|
||||
);
|
||||
let any: DynAnyNode<Footprint, _, _> = graphene_std::any::DynAnyNode::new(node);
|
||||
Box::new(any) as TypeErasedBox
|
||||
})
|
||||
},
|
||||
{
|
||||
let params = vec![
|
||||
fn_type!(Footprint, WasmSurfaceHandleFrame),
|
||||
fn_type!(DVec2),
|
||||
fn_type!(f32),
|
||||
fn_type!(DVec2),
|
||||
fn_type!(DVec2),
|
||||
fn_type!(DVec2),
|
||||
];
|
||||
NodeIOTypes::new(concrete!(Footprint), concrete!(WasmSurfaceHandleFrame), params)
|
||||
},
|
||||
),
|
||||
(
|
||||
NodeIdentifier::new("graphene_core::transform::TransformNode<_, _, _, _, _, _>"),
|
||||
|mut args| {
|
||||
Box::pin(async move {
|
||||
args.reverse();
|
||||
let node = <graphene_core::transform::TransformNode<_, _, _, _, _, _>>::new(
|
||||
DowncastBothNode::<Footprint, ImageFrame<Color>>::new(args.pop().expect("Not enough arguments provided to construct node")),
|
||||
graphene_std::any::input_node::<DVec2>(args.pop().expect("Not enough arguments provided to construct node")),
|
||||
graphene_std::any::input_node::<f32>(args.pop().expect("Not enough arguments provided to construct node")),
|
||||
graphene_std::any::input_node::<DVec2>(args.pop().expect("Not enough arguments provided to construct node")),
|
||||
graphene_std::any::input_node::<DVec2>(args.pop().expect("Not enough arguments provided to construct node")),
|
||||
graphene_std::any::input_node::<DVec2>(args.pop().expect("Not enough arguments provided to construct node")),
|
||||
);
|
||||
let any: DynAnyNode<Footprint, _, _> = graphene_std::any::DynAnyNode::new(node);
|
||||
Box::new(any) as TypeErasedBox
|
||||
})
|
||||
},
|
||||
{
|
||||
let params = vec![
|
||||
fn_type!(Footprint, ImageFrame<Color>),
|
||||
fn_type!(DVec2),
|
||||
fn_type!(f32),
|
||||
fn_type!(DVec2),
|
||||
fn_type!(DVec2),
|
||||
fn_type!(DVec2),
|
||||
];
|
||||
NodeIOTypes::new(concrete!(Footprint), concrete!(ImageFrame<Color>), params)
|
||||
},
|
||||
),
|
||||
],
|
||||
register_node!(graphene_core::transform::SetTransformNode<_>, input: VectorData, params: [VectorData]),
|
||||
register_node!(graphene_core::transform::SetTransformNode<_>, input: ImageFrame<Color>, params: [ImageFrame<Color>]),
|
||||
register_node!(graphene_core::transform::SetTransformNode<_>, input: VectorData, params: [DAffine2]),
|
||||
@@ -636,6 +729,10 @@ fn node_registry() -> HashMap<NodeIdentifier, HashMap<NodeIOTypes, NodeConstruct
|
||||
register_node!(graphene_core::vector::RepeatNode<_, _>, input: VectorData, params: [DVec2, u32]),
|
||||
register_node!(graphene_core::vector::BoundingBoxNode, input: VectorData, params: []),
|
||||
register_node!(graphene_core::vector::CircularRepeatNode<_, _, _>, input: VectorData, params: [f32, f32, u32]),
|
||||
register_node!(graphene_core::transform::CullNode<_>, input: Footprint, params: [VectorData]),
|
||||
register_node!(graphene_core::transform::CullNode<_>, input: Footprint, params: [graphene_core::Artboard]),
|
||||
register_node!(graphene_core::transform::CullNode<_>, input: Footprint, params: [graphene_core::GraphicGroup]),
|
||||
register_node!(graphene_std::raster::DownresNode<_>, input: Footprint, params: [ImageFrame<Color>]),
|
||||
register_node!(graphene_core::vector::ResamplePoints<_>, input: VectorData, params: [f64]),
|
||||
register_node!(graphene_core::vector::SplineFromPointsNode, input: VectorData, params: []),
|
||||
register_node!(graphene_core::vector::generator_nodes::CircleGenerator<_>, input: (), params: [f32]),
|
||||
@@ -653,7 +750,7 @@ fn node_registry() -> HashMap<NodeIdentifier, HashMap<NodeIOTypes, NodeConstruct
|
||||
register_node!(graphene_core::text::TextGenerator<_, _, _>, input: WasmEditorApi, params: [String, graphene_core::text::Font, f64]),
|
||||
register_node!(graphene_std::brush::VectorPointsNode, input: VectorData, params: []),
|
||||
register_node!(graphene_core::ExtractImageFrame, input: WasmEditorApi, params: []),
|
||||
register_node!(graphene_core::ConstructLayerNode<_, _, _, _, _, _, _>, input: graphene_core::GraphicElementData, params: [String, BlendMode, f32, bool, bool, bool, graphene_core::GraphicGroup]),
|
||||
async_node!(graphene_core::ConstructLayerNode<_, _, _, _, _, _, _, _>, input: Footprint, output: GraphicGroup, fn_params: [Footprint => graphene_core::GraphicElementData, () => String, () => BlendMode, () => f32, () => bool, () => bool, () => bool, Footprint => graphene_core::GraphicGroup]),
|
||||
register_node!(graphene_core::ToGraphicElementData, input: graphene_core::vector::VectorData, params: []),
|
||||
register_node!(graphene_core::ToGraphicElementData, input: ImageFrame<Color>, params: []),
|
||||
register_node!(graphene_core::ToGraphicElementData, input: graphene_core::GraphicGroup, params: []),
|
||||
|
||||
Reference in New Issue
Block a user