mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-19 19:08:05 +08:00
Add viewing/editing layer names, add Blend Mode node, and clean up Layer node (#1489)
This commit is contained in:
@@ -102,7 +102,7 @@ The `graphene_core::value::CopiedNode` is a node that, when evaluated, copies `1
|
||||
|
||||
## Creating a new protonode
|
||||
|
||||
Instead of manually implementing the `Node` trait with complex generics, one can use the `node_fn` macro, which can be applied to a function like `image_opacity` with an attribute of the name of the node:
|
||||
Instead of manually implementing the `Node` trait with complex generics, one can use the `node_fn` macro, which can be applied to a function like `opacity_node` with an attribute of the name of the node:
|
||||
|
||||
```rs
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
@@ -111,7 +111,7 @@ pub struct OpacityNode<O> {
|
||||
}
|
||||
|
||||
#[node_macro::node_fn(OpacityNode)]
|
||||
fn image_opacity(color: Color, opacity_multiplier: f64) -> Color {
|
||||
fn opacity_node(color: Color, opacity_multiplier: f64) -> Color {
|
||||
let opacity_multiplier = opacity_multiplier as f32 / 100.;
|
||||
Color::from_rgbaf32_unchecked(color.r(), color.g(), color.b(), color.a() * opacity_multiplier)
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ pub mod renderer;
|
||||
pub struct GraphicGroup {
|
||||
elements: Vec<GraphicElement>,
|
||||
pub opacity: f32,
|
||||
pub blend_mode: BlendMode,
|
||||
pub transform: DAffine2,
|
||||
}
|
||||
|
||||
@@ -41,29 +42,16 @@ pub enum GraphicElementData {
|
||||
Artboard(Artboard),
|
||||
}
|
||||
|
||||
/// A named [`GraphicElementData`] with a blend mode, opacity, as well as visibility, locked, and collapsed states.
|
||||
// TODO: Remove this wrapper and directly use GraphicElementData
|
||||
#[derive(Clone, Debug, PartialEq, DynAny)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct GraphicElement {
|
||||
pub name: String,
|
||||
pub blend_mode: BlendMode,
|
||||
/// In range 0..=1
|
||||
pub opacity: f32,
|
||||
pub visible: bool,
|
||||
pub locked: bool,
|
||||
pub collapsed: bool,
|
||||
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())),
|
||||
}
|
||||
}
|
||||
@@ -93,14 +81,8 @@ impl Artboard {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ConstructLayerNode<GraphicElementData, Name, BlendMode, Opacity, Visible, Locked, Collapsed, Stack> {
|
||||
pub struct ConstructLayerNode<GraphicElementData, Stack> {
|
||||
graphic_element_data: GraphicElementData,
|
||||
name: Name,
|
||||
blend_mode: BlendMode,
|
||||
opacity: Opacity,
|
||||
visible: Visible,
|
||||
locked: Locked,
|
||||
collapsed: Collapsed,
|
||||
stack: Stack,
|
||||
}
|
||||
|
||||
@@ -108,23 +90,11 @@ pub struct ConstructLayerNode<GraphicElementData, Name, BlendMode, Opacity, Visi
|
||||
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: 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,
|
||||
opacity: opacity / 100.,
|
||||
visible,
|
||||
locked,
|
||||
collapsed,
|
||||
graphic_element_data: graphic_element_data.into(),
|
||||
});
|
||||
stack
|
||||
@@ -154,7 +124,7 @@ async fn construct_artboard<Fut: Future<Output = GraphicGroup>>(
|
||||
background: Color,
|
||||
clip: bool,
|
||||
) -> Artboard {
|
||||
footprint.transform = footprint.transform * DAffine2::from_translation(location.as_dvec2());
|
||||
footprint.transform *= DAffine2::from_translation(location.as_dvec2());
|
||||
let graphic_group = self.contents.eval(footprint).await;
|
||||
Artboard {
|
||||
graphic_group,
|
||||
@@ -212,13 +182,11 @@ where
|
||||
T: ToGraphicElement,
|
||||
{
|
||||
fn from(value: T) -> Self {
|
||||
let element = GraphicElement {
|
||||
graphic_element_data: value.into(),
|
||||
..Default::default()
|
||||
};
|
||||
let element = GraphicElement { graphic_element_data: value.into() };
|
||||
Self {
|
||||
elements: (vec![element]),
|
||||
opacity: 1.,
|
||||
blend_mode: BlendMode::Normal,
|
||||
transform: DAffine2::IDENTITY,
|
||||
}
|
||||
}
|
||||
@@ -228,6 +196,7 @@ impl GraphicGroup {
|
||||
pub const EMPTY: Self = Self {
|
||||
elements: Vec::new(),
|
||||
opacity: 1.,
|
||||
blend_mode: BlendMode::Normal,
|
||||
transform: DAffine2::IDENTITY,
|
||||
};
|
||||
|
||||
@@ -337,12 +306,6 @@ impl GraphicElement {
|
||||
|
||||
impl core::hash::Hash for GraphicElement {
|
||||
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
|
||||
self.name.hash(state);
|
||||
self.blend_mode.hash(state);
|
||||
self.opacity.to_bits().hash(state);
|
||||
self.visible.hash(state);
|
||||
self.locked.hash(state);
|
||||
self.collapsed.hash(state);
|
||||
self.graphic_element_data.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,8 +59,6 @@ pub struct SvgRender {
|
||||
pub svg: SvgSegmentList,
|
||||
pub svg_defs: String,
|
||||
pub transform: DAffine2,
|
||||
pub opacity: f32,
|
||||
pub blend_mode: BlendMode,
|
||||
pub image_data: Vec<(u64, Image<Color>)>,
|
||||
indent: usize,
|
||||
}
|
||||
@@ -71,8 +69,6 @@ impl SvgRender {
|
||||
svg: SvgSegmentList::default(),
|
||||
svg_defs: String::new(),
|
||||
transform: DAffine2::IDENTITY,
|
||||
opacity: 1.,
|
||||
blend_mode: BlendMode::Normal,
|
||||
image_data: Vec::new(),
|
||||
indent: 0,
|
||||
}
|
||||
@@ -121,6 +117,7 @@ impl SvgRender {
|
||||
self.indent();
|
||||
self.svg.push("<");
|
||||
self.svg.push(name.clone());
|
||||
// Wraps `self` in a newtype (1-tuple) which is then mutated by the `attributes` closure
|
||||
attributes(&mut SvgRenderAttrs(self));
|
||||
self.svg.push(">");
|
||||
let length = self.svg.len();
|
||||
@@ -183,6 +180,7 @@ pub fn format_transform_matrix(transform: DAffine2) -> String {
|
||||
result.push(')');
|
||||
result
|
||||
}
|
||||
|
||||
fn to_transform(transform: DAffine2) -> usvg::Transform {
|
||||
let cols = transform.to_cols_array();
|
||||
usvg::Transform::from_row(cols[0] as f32, cols[1] as f32, cols[2] as f32, cols[3] as f32, cols[4] as f32, cols[5] as f32)
|
||||
@@ -204,6 +202,7 @@ pub trait GraphicElementRendered {
|
||||
let tree = usvg::Tree::from_str(&svg, &opt).expect("Failed to parse SVG");
|
||||
tree.root.clone()
|
||||
}
|
||||
|
||||
fn to_usvg_tree(&self, resolution: glam::UVec2, viewbox: [DVec2; 2]) -> usvg::Tree {
|
||||
let root_node = self.to_usvg_node();
|
||||
usvg::Tree {
|
||||
@@ -219,26 +218,33 @@ pub trait GraphicElementRendered {
|
||||
|
||||
impl GraphicElementRendered for GraphicGroup {
|
||||
fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) {
|
||||
let old_opacity = render.opacity;
|
||||
render.opacity *= self.opacity;
|
||||
render.parent_tag(
|
||||
"g",
|
||||
|attributes| attributes.push("transform", format_transform_matrix(self.transform)),
|
||||
|attributes| {
|
||||
attributes.push("transform", format_transform_matrix(self.transform));
|
||||
|
||||
if self.opacity < 1. {
|
||||
attributes.push("opacity", self.opacity.to_string());
|
||||
}
|
||||
|
||||
if self.blend_mode != BlendMode::default() {
|
||||
attributes.push("style", self.blend_mode.render());
|
||||
}
|
||||
},
|
||||
|render| {
|
||||
for element in self.iter() {
|
||||
render.blend_mode = element.blend_mode;
|
||||
element.graphic_element_data.render_svg(render, render_params);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
render.opacity = old_opacity;
|
||||
}
|
||||
|
||||
fn bounding_box(&self, transform: DAffine2) -> Option<[DVec2; 2]> {
|
||||
self.iter()
|
||||
.filter_map(|element| element.graphic_element_data.bounding_box(transform * self.transform))
|
||||
.reduce(Quad::combine_bounds)
|
||||
}
|
||||
|
||||
fn add_click_targets(&self, _click_targets: &mut Vec<ClickTarget>) {}
|
||||
|
||||
fn to_usvg_node(&self) -> usvg::Node {
|
||||
@@ -260,24 +266,31 @@ impl GraphicElementRendered for VectorData {
|
||||
for subpath in &self.subpaths {
|
||||
let _ = subpath.subpath_to_svg(&mut path, multiplied_transform);
|
||||
}
|
||||
|
||||
render.leaf_tag("path", |attributes| {
|
||||
attributes.push("class", "vector-data");
|
||||
|
||||
attributes.push("d", path);
|
||||
let render = &mut attributes.0;
|
||||
let style = self.style.render(render_params.view_mode, &mut render.svg_defs, multiplied_transform, layer_bounds, transformed_bounds);
|
||||
attributes.push_val(style);
|
||||
if attributes.0.blend_mode != BlendMode::default() {
|
||||
attributes.push_complex("style", |v| {
|
||||
v.svg.push("mix-blend-mode: ");
|
||||
v.svg.push(v.blend_mode.to_svg_style_name());
|
||||
v.svg.push(";");
|
||||
})
|
||||
|
||||
let fill_and_stroke = self
|
||||
.style
|
||||
.render(render_params.view_mode, &mut attributes.0.svg_defs, multiplied_transform, layer_bounds, transformed_bounds);
|
||||
attributes.push_val(fill_and_stroke);
|
||||
|
||||
if self.style.opacity < 1. {
|
||||
attributes.push("opacity", self.style.opacity.to_string());
|
||||
}
|
||||
|
||||
if self.style.blend_mode != BlendMode::default() {
|
||||
attributes.push("style", self.style.blend_mode.render());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn bounding_box(&self, transform: DAffine2) -> Option<[DVec2; 2]> {
|
||||
self.bounding_box_with_transform(self.transform * transform)
|
||||
}
|
||||
|
||||
fn add_click_targets(&self, click_targets: &mut Vec<ClickTarget>) {
|
||||
let stroke_width = self.style.stroke().as_ref().map_or(0., crate::vector::style::Stroke::weight);
|
||||
let update_closed = |mut subpath: bezier_rs::Subpath<ManipulatorGroupId>| {
|
||||
@@ -345,19 +358,24 @@ impl GraphicElementRendered for Artboard {
|
||||
attributes.push("font-size", "14px");
|
||||
},
|
||||
|render| {
|
||||
// TODO: Use the artboard's layer name
|
||||
render.svg.push("Artboard");
|
||||
},
|
||||
);
|
||||
|
||||
// Contents group
|
||||
// Contents group (includes the artwork but not the background)
|
||||
render.parent_tag(
|
||||
// SVG group tag
|
||||
"g",
|
||||
// Group tag attributes
|
||||
|attributes| {
|
||||
attributes.push("class", "artboard");
|
||||
|
||||
attributes.push(
|
||||
"transform",
|
||||
format_transform_matrix(DAffine2::from_translation(self.location.as_dvec2()) * self.graphic_group.transform),
|
||||
);
|
||||
|
||||
if self.clip {
|
||||
let id = format!("artboard-{}", generate_uuid());
|
||||
let selector = format!("url(#{id})");
|
||||
@@ -373,19 +391,15 @@ impl GraphicElementRendered for Artboard {
|
||||
attributes.push("clip-path", selector);
|
||||
}
|
||||
},
|
||||
// Artboard contents
|
||||
|render| {
|
||||
let old_opacity = render.opacity;
|
||||
render.opacity *= self.graphic_group.opacity;
|
||||
|
||||
// Contents
|
||||
for element in self.graphic_group.iter() {
|
||||
render.blend_mode = element.blend_mode;
|
||||
element.graphic_element_data.render_svg(render, render_params);
|
||||
}
|
||||
render.opacity = old_opacity;
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
fn bounding_box(&self, transform: DAffine2) -> Option<[DVec2; 2]> {
|
||||
let artboard_bounds = (transform * Quad::from_box([self.location.as_dvec2(), self.location.as_dvec2() + self.dimensions.as_dvec2()])).bounding_box();
|
||||
if self.clip {
|
||||
@@ -394,6 +408,7 @@ impl GraphicElementRendered for Artboard {
|
||||
[self.graphic_group.bounding_box(transform), Some(artboard_bounds)].into_iter().flatten().reduce(Quad::combine_bounds)
|
||||
}
|
||||
}
|
||||
|
||||
fn add_click_targets(&self, click_targets: &mut Vec<ClickTarget>) {
|
||||
let subpath = Subpath::new_rect(DVec2::ZERO, self.dimensions.as_dvec2());
|
||||
click_targets.push(ClickTarget { stroke_width: 0., subpath });
|
||||
@@ -412,7 +427,10 @@ impl GraphicElementRendered for ImageFrame<Color> {
|
||||
attributes.push("height", 1.to_string());
|
||||
attributes.push("preserveAspectRatio", "none");
|
||||
attributes.push("transform", transform);
|
||||
attributes.push("href", SvgSegment::BlobUrl(uuid))
|
||||
attributes.push("href", SvgSegment::BlobUrl(uuid));
|
||||
if self.blend_mode != BlendMode::default() {
|
||||
attributes.push("style", self.blend_mode.render());
|
||||
}
|
||||
});
|
||||
render.image_data.push((uuid, self.image.clone()))
|
||||
}
|
||||
@@ -429,11 +447,13 @@ impl GraphicElementRendered for ImageFrame<Color> {
|
||||
|
||||
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", base64_string)
|
||||
attributes.push("href", base64_string);
|
||||
if self.blend_mode != BlendMode::default() {
|
||||
attributes.push("style", self.blend_mode.render());
|
||||
}
|
||||
});
|
||||
}
|
||||
ImageRenderMode::Canvas => {
|
||||
@@ -441,10 +461,12 @@ impl GraphicElementRendered for ImageFrame<Color> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn bounding_box(&self, transform: DAffine2) -> Option<[DVec2; 2]> {
|
||||
let transform = self.transform * transform;
|
||||
(transform.matrix2 != glam::DMat2::ZERO).then(|| (transform * Quad::from_box([DVec2::ZERO, DVec2::ONE])).bounding_box())
|
||||
}
|
||||
|
||||
fn add_click_targets(&self, click_targets: &mut Vec<ClickTarget>) {
|
||||
let subpath = Subpath::new_rect(DVec2::ZERO, DVec2::ONE);
|
||||
click_targets.push(ClickTarget { subpath, stroke_width: 0. });
|
||||
@@ -563,6 +585,7 @@ impl GraphicElementRendered for Option<Color> {
|
||||
render.parent_tag("text", |_| {}, |render| render.leaf_node("Empty color"));
|
||||
return;
|
||||
};
|
||||
let color_info = format!("{:?} #{} {:?}", color, color.rgba_hex(), color.to_rgba8_srgb());
|
||||
|
||||
render.leaf_tag("rect", |attributes| {
|
||||
attributes.push("width", "100");
|
||||
@@ -570,7 +593,6 @@ impl GraphicElementRendered for Option<Color> {
|
||||
attributes.push("y", "40");
|
||||
attributes.push("fill", format!("#{}", color.rgba_hex()));
|
||||
});
|
||||
let color_info = format!("{:?} #{} {:?}", color, color.rgba_hex(), color.to_rgba8_srgb());
|
||||
render.parent_tag("text", text_attributes, |render| render.leaf_node(color_info))
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ pub struct LogToConsoleNode;
|
||||
#[node_macro::node_fn(LogToConsoleNode)]
|
||||
fn log_to_console<T: core::fmt::Debug>(value: T) -> T {
|
||||
#[cfg(not(target_arch = "spirv"))]
|
||||
// KEEP THIS `debug!()` - It acts as the output for the debug node itself
|
||||
debug!("{value:#?}");
|
||||
value
|
||||
}
|
||||
|
||||
@@ -90,15 +90,15 @@ pub enum BlendMode {
|
||||
// Not supported by SVG, but we should someday support: Dissolve
|
||||
|
||||
// Darken group
|
||||
Multiply,
|
||||
Darken,
|
||||
Multiply,
|
||||
ColorBurn,
|
||||
LinearBurn,
|
||||
DarkerColor,
|
||||
|
||||
// Lighten group
|
||||
Screen,
|
||||
Lighten,
|
||||
Screen,
|
||||
ColorDodge,
|
||||
LinearDodge,
|
||||
LighterColor,
|
||||
@@ -172,6 +172,7 @@ impl core::fmt::Display for BlendMode {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl BlendMode {
|
||||
/// Convert the enum to the CSS string for the blend mode.
|
||||
/// [Read more](https://developer.mozilla.org/en-US/docs/Web/CSS/blend-mode#values)
|
||||
@@ -206,6 +207,11 @@ impl BlendMode {
|
||||
}
|
||||
}
|
||||
|
||||
/// Renders the blend mode CSS style declaration.
|
||||
pub fn render(&self) -> String {
|
||||
format!(r#" mix-blend-mode: {};"#, self.to_svg_style_name())
|
||||
}
|
||||
|
||||
/// List of all the blend modes in their conventional ordering and grouping.
|
||||
pub fn list_modes_in_groups() -> [&'static [BlendMode]; 6] {
|
||||
[
|
||||
@@ -898,32 +904,55 @@ pub struct OpacityNode<O> {
|
||||
}
|
||||
|
||||
#[node_macro::node_fn(OpacityNode)]
|
||||
fn image_opacity(color: Color, opacity_multiplier: f32) -> Color {
|
||||
fn opacity_node(color: Color, opacity_multiplier: f32) -> Color {
|
||||
let opacity_multiplier = opacity_multiplier / 100.;
|
||||
Color::from_rgbaf32_unchecked(color.r(), color.g(), color.b(), color.a() * opacity_multiplier)
|
||||
}
|
||||
|
||||
#[node_macro::node_impl(OpacityNode)]
|
||||
fn image_opacity(mut vector_data: VectorData, opacity_multiplier: f32) -> VectorData {
|
||||
fn opacity_node(mut vector_data: VectorData, opacity_multiplier: f32) -> VectorData {
|
||||
let opacity_multiplier = opacity_multiplier / 100.;
|
||||
vector_data.style.opacity *= opacity_multiplier;
|
||||
vector_data
|
||||
}
|
||||
|
||||
#[node_macro::node_impl(OpacityNode)]
|
||||
fn image_opacity(mut graphic_group: GraphicGroup, opacity_multiplier: f32) -> GraphicGroup {
|
||||
fn opacity_node(mut graphic_group: GraphicGroup, opacity_multiplier: f32) -> GraphicGroup {
|
||||
let opacity_multiplier = opacity_multiplier / 100.;
|
||||
graphic_group.opacity *= opacity_multiplier;
|
||||
graphic_group
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct BlendModeNode<BM> {
|
||||
blend_mode: BM,
|
||||
}
|
||||
|
||||
#[node_macro::node_fn(BlendModeNode)]
|
||||
fn blend_mode_node(mut vector_data: VectorData, blend_mode: BlendMode) -> VectorData {
|
||||
vector_data.style.blend_mode = blend_mode;
|
||||
vector_data
|
||||
}
|
||||
|
||||
#[node_macro::node_impl(BlendModeNode)]
|
||||
fn blend_mode_node(mut graphic_group: GraphicGroup, blend_mode: BlendMode) -> GraphicGroup {
|
||||
graphic_group.blend_mode = blend_mode;
|
||||
graphic_group
|
||||
}
|
||||
|
||||
#[node_macro::node_impl(BlendModeNode)]
|
||||
fn blend_mode_node(mut image_frame: ImageFrame<Color>, blend_mode: BlendMode) -> ImageFrame<Color> {
|
||||
image_frame.blend_mode = blend_mode;
|
||||
image_frame
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct PosterizeNode<P> {
|
||||
posterize_value: P,
|
||||
}
|
||||
|
||||
// Based on http://www.axiomx.com/posterize.htm
|
||||
// This algorithm is perfectly accurate.
|
||||
// This algorithm produces fully accurate output in relation to the industry standard.
|
||||
#[node_macro::node_fn(PosterizeNode)]
|
||||
fn posterize(color: Color, posterize_value: f32) -> Color {
|
||||
let color = color.to_gamma_srgb();
|
||||
|
||||
@@ -250,7 +250,6 @@ fn map_node<P: Pixel>(input: (u32, u32), data: Vec<P>) -> Image<P> {
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct ImageFrame<P: Pixel> {
|
||||
pub image: Image<P>,
|
||||
|
||||
// The transform that maps image space to layer space.
|
||||
//
|
||||
// Image space is unitless [0, 1] for both axes, with x axis positive
|
||||
@@ -261,6 +260,7 @@ pub struct ImageFrame<P: Pixel> {
|
||||
// positive going right and y axis positive going down, with the origin
|
||||
// being an unspecified quantity.
|
||||
pub transform: DAffine2,
|
||||
pub blend_mode: BlendMode,
|
||||
}
|
||||
|
||||
impl<P: Debug + Copy + Pixel> Sample for ImageFrame<P> {
|
||||
@@ -312,6 +312,7 @@ impl<P: Copy + Pixel> ImageFrame<P> {
|
||||
Self {
|
||||
image: Image::empty(),
|
||||
transform: DAffine2::ZERO,
|
||||
blend_mode: BlendMode::Normal,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -319,6 +320,7 @@ impl<P: Copy + Pixel> ImageFrame<P> {
|
||||
Self {
|
||||
image: Image::empty(),
|
||||
transform: DAffine2::IDENTITY,
|
||||
blend_mode: BlendMode::Normal,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -379,6 +381,7 @@ impl From<ImageFrame<Color>> for ImageFrame<SRGBA8> {
|
||||
height: image.image.height,
|
||||
},
|
||||
transform: image.transform,
|
||||
blend_mode: BlendMode::Normal,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -393,6 +396,7 @@ impl From<ImageFrame<SRGBA8>> for ImageFrame<Color> {
|
||||
height: image.image.height,
|
||||
},
|
||||
transform: image.transform,
|
||||
blend_mode: BlendMode::Normal,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,8 +106,8 @@ impl<'i, T: Clone + 'i> Node<'i, ()> for DebugClonedNode<T> {
|
||||
type Output = T;
|
||||
#[inline(always)]
|
||||
fn eval(&'i self, _input: ()) -> Self::Output {
|
||||
// KEEP THIS `debug!()` - It acts as the output for the debug node itself
|
||||
#[cfg(not(target_arch = "spirv"))]
|
||||
// KEEP THIS `debug!()` - It acts as the output for the debug node itself
|
||||
log::debug!("DebugClonedNode::eval");
|
||||
|
||||
self.0.clone()
|
||||
|
||||
@@ -99,6 +99,7 @@ fn spline_generator(_input: (), positions: Vec<DVec2>) -> VectorData {
|
||||
// TODO(TrueDoctor): I removed the Arc requirement we should think about when it makes sense to use it vs making a generic value node
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PathGenerator<Mirror> {
|
||||
// TODO: Keavon asks: what is this for? Is it dead code? It seems to only be set, never read.
|
||||
mirror: Mirror,
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
//! Contains stylistic options for SVG elements.
|
||||
|
||||
use crate::consts::{LAYER_OUTLINE_STROKE_COLOR, LAYER_OUTLINE_STROKE_WEIGHT};
|
||||
use crate::raster::BlendMode;
|
||||
use crate::Color;
|
||||
|
||||
use dyn_any::{DynAny, StaticType};
|
||||
@@ -12,9 +13,9 @@ use std::fmt::{self, Display, Write};
|
||||
/// A value of 3 would correspond to a precision of 10^-3.
|
||||
const OPACITY_PRECISION: usize = 3;
|
||||
|
||||
fn format_opacity(name: &str, opacity: f32) -> String {
|
||||
fn format_opacity(attribute: &str, opacity: f32) -> String {
|
||||
if (opacity - 1.).abs() > 10_f32.powi(-(OPACITY_PRECISION as i32)) {
|
||||
format!(r#" {name}-opacity="{opacity:.OPACITY_PRECISION$}""#)
|
||||
format!(r#" {attribute}="{opacity:.OPACITY_PRECISION$}""#)
|
||||
} else {
|
||||
String::new()
|
||||
}
|
||||
@@ -64,15 +65,15 @@ impl Gradient {
|
||||
}
|
||||
}
|
||||
|
||||
/// Adds the gradient def, returning the gradient id
|
||||
fn render_defs(&self, svg_defs: &mut String, multiplied_transform: DAffine2, bounds: [DVec2; 2], transformed_bounds: [DVec2; 2], opacity: f32) -> u64 {
|
||||
/// Adds the gradient def through mutating the first argument, returning the gradient ID.
|
||||
fn render_defs(&self, svg_defs: &mut String, multiplied_transform: DAffine2, bounds: [DVec2; 2], transformed_bounds: [DVec2; 2]) -> u64 {
|
||||
let bound_transform = DAffine2::from_scale_angle_translation(bounds[1] - bounds[0], 0., bounds[0]);
|
||||
let transformed_bound_transform = DAffine2::from_scale_angle_translation(transformed_bounds[1] - transformed_bounds[0], 0., transformed_bounds[0]);
|
||||
let updated_transform = multiplied_transform * bound_transform;
|
||||
|
||||
let mut positions = String::new();
|
||||
for (position, color) in self.positions.iter().filter_map(|(pos, color)| color.map(|color| (pos, color))) {
|
||||
let _ = write!(positions, r##"<stop offset="{}" stop-color="#{}" />"##, position, color.with_alpha(color.a() * opacity).rgba_hex());
|
||||
let _ = write!(positions, r##"<stop offset="{}" stop-color="#{}" />"##, position, color.with_alpha(color.a()).rgba_hex());
|
||||
}
|
||||
|
||||
let mod_gradient = transformed_bound_transform.inverse();
|
||||
@@ -178,13 +179,13 @@ impl Fill {
|
||||
}
|
||||
}
|
||||
|
||||
/// Renders the fill, adding necessary defs.
|
||||
pub fn render(&self, svg_defs: &mut String, multiplied_transform: DAffine2, bounds: [DVec2; 2], transformed_bounds: [DVec2; 2], opacity: f32) -> String {
|
||||
/// Renders the fill, adding necessary defs through mutating the first argument.
|
||||
pub fn render(&self, svg_defs: &mut String, multiplied_transform: DAffine2, bounds: [DVec2; 2], transformed_bounds: [DVec2; 2]) -> String {
|
||||
match self {
|
||||
Self::None => r#" fill="none""#.to_string(),
|
||||
Self::Solid(color) => format!(r##" fill="#{}"{}"##, color.rgb_hex(), format_opacity("fill", color.a() * opacity)),
|
||||
Self::Solid(color) => format!(r##" fill="#{}"{}"##, color.rgb_hex(), format_opacity("fill-opacity", color.a())),
|
||||
Self::Gradient(gradient) => {
|
||||
let gradient_id = gradient.render_defs(svg_defs, multiplied_transform, bounds, transformed_bounds, opacity);
|
||||
let gradient_id = gradient.render_defs(svg_defs, multiplied_transform, bounds, transformed_bounds);
|
||||
format!(r##" fill="url('#{gradient_id}')""##)
|
||||
}
|
||||
}
|
||||
@@ -326,12 +327,12 @@ impl Stroke {
|
||||
}
|
||||
|
||||
/// Provide the SVG attributes for the stroke.
|
||||
pub fn render(&self, opacity: f32) -> String {
|
||||
pub fn render(&self) -> String {
|
||||
if let Some(color) = self.color {
|
||||
format!(
|
||||
r##" stroke="#{}"{} stroke-width="{}" stroke-dasharray="{}" stroke-dashoffset="{}" stroke-linecap="{}" stroke-linejoin="{}" stroke-miterlimit="{}" "##,
|
||||
color.rgb_hex(),
|
||||
format_opacity("stroke", opacity * color.a()),
|
||||
format_opacity("stroke-opacity", color.a()),
|
||||
self.weight,
|
||||
self.dash_lengths(),
|
||||
self.dash_offset,
|
||||
@@ -410,6 +411,7 @@ pub struct PathStyle {
|
||||
stroke: Option<Stroke>,
|
||||
fill: Fill,
|
||||
pub opacity: f32,
|
||||
pub blend_mode: BlendMode,
|
||||
}
|
||||
|
||||
impl core::hash::Hash for PathStyle {
|
||||
@@ -417,12 +419,18 @@ impl core::hash::Hash for PathStyle {
|
||||
self.stroke.hash(state);
|
||||
self.fill.hash(state);
|
||||
self.opacity.to_bits().hash(state);
|
||||
self.blend_mode.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl PathStyle {
|
||||
pub const fn new(stroke: Option<Stroke>, fill: Fill) -> Self {
|
||||
Self { stroke, fill, opacity: 1. }
|
||||
Self {
|
||||
stroke,
|
||||
fill,
|
||||
opacity: 1.,
|
||||
blend_mode: BlendMode::Normal,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the current path's [Fill].
|
||||
@@ -529,18 +537,20 @@ impl PathStyle {
|
||||
self.stroke = None;
|
||||
}
|
||||
|
||||
/// Renders the shape's fill and stroke attributes as a string with them concatenated together.
|
||||
pub fn render(&self, view_mode: ViewMode, svg_defs: &mut String, multiplied_transform: DAffine2, bounds: [DVec2; 2], transformed_bounds: [DVec2; 2]) -> String {
|
||||
let fill_attribute = match (view_mode, &self.fill) {
|
||||
(ViewMode::Outline, _) => Fill::None.render(svg_defs, multiplied_transform, bounds, transformed_bounds, self.opacity),
|
||||
(_, fill) => fill.render(svg_defs, multiplied_transform, bounds, transformed_bounds, self.opacity),
|
||||
};
|
||||
let stroke_attribute = match (view_mode, &self.stroke) {
|
||||
(ViewMode::Outline, _) => Stroke::new(Some(LAYER_OUTLINE_STROKE_COLOR), LAYER_OUTLINE_STROKE_WEIGHT).render(self.opacity),
|
||||
(_, Some(stroke)) => stroke.render(self.opacity),
|
||||
(_, None) => String::new(),
|
||||
};
|
||||
|
||||
format!("{fill_attribute}{stroke_attribute}")
|
||||
match view_mode {
|
||||
ViewMode::Outline => {
|
||||
let fill_attribute = Fill::None.render(svg_defs, multiplied_transform, bounds, transformed_bounds);
|
||||
let stroke_attribute = Stroke::new(Some(LAYER_OUTLINE_STROKE_COLOR), LAYER_OUTLINE_STROKE_WEIGHT).render();
|
||||
format!("{fill_attribute}{stroke_attribute}")
|
||||
}
|
||||
_ => {
|
||||
let fill_attribute = self.fill.render(svg_defs, multiplied_transform, bounds, transformed_bounds);
|
||||
let stroke_attribute = self.stroke.as_ref().map(|stroke| stroke.render()).unwrap_or_default();
|
||||
format!("{fill_attribute}{stroke_attribute}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ pub struct VectorData {
|
||||
pub subpaths: Vec<bezier_rs::Subpath<ManipulatorGroupId>>,
|
||||
pub transform: DAffine2,
|
||||
pub style: PathStyle,
|
||||
// TODO: Keavon asks: what is this for? Is it dead code? It seems to only be set, never read.
|
||||
pub mirror_angle: Vec<ManipulatorGroupId>,
|
||||
}
|
||||
|
||||
@@ -47,12 +48,12 @@ impl VectorData {
|
||||
self.subpaths.iter().find_map(|subpath| subpath.manipulator_from_id(id))
|
||||
}
|
||||
|
||||
/// Construct some new vector data from a single subpath with an identy transform and black fill.
|
||||
/// Construct some new vector data from a single subpath with an identity transform and black fill.
|
||||
pub fn from_subpath(subpath: bezier_rs::Subpath<ManipulatorGroupId>) -> Self {
|
||||
Self::from_subpaths(vec![subpath])
|
||||
}
|
||||
|
||||
/// Construct some new vector data from subpaths with an identy transform and black fill.
|
||||
/// Construct some new vector data from subpaths with an identity transform and black fill.
|
||||
pub fn from_subpaths(subpaths: Vec<bezier_rs::Subpath<ManipulatorGroupId>>) -> Self {
|
||||
super::VectorData { subpaths, ..Self::empty() }
|
||||
}
|
||||
|
||||
@@ -43,6 +43,11 @@ fn return_true() -> bool {
|
||||
#[derive(Clone, Debug, PartialEq, Hash, DynAny)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct DocumentNode {
|
||||
// TODO: Rename to "name" (also rename the TODOs in the `DocumentNodeBlueprint` struct)
|
||||
/// A name chosen by the user for this node. Empty indicates no given name, in which case the node's identifier is displayed to the user in italics.
|
||||
#[serde(default)]
|
||||
pub alias: String,
|
||||
// TODO: Rename to "identifier" (also rename the TODOs in the `DocumentNodeBlueprint` struct)
|
||||
/// An identifier used to display in the UI and to display the appropriate properties.
|
||||
pub name: String,
|
||||
/// The inputs to a node, which are either:
|
||||
@@ -157,6 +162,7 @@ pub struct DocumentNode {
|
||||
impl Default for DocumentNode {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
alias: Default::default(),
|
||||
name: Default::default(),
|
||||
inputs: Default::default(),
|
||||
manual_composition: Default::default(),
|
||||
@@ -262,6 +268,12 @@ impl DocumentNode {
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
pub fn is_layer(&self) -> bool {
|
||||
// TODO: Use something more robust than checking against a string.
|
||||
// TODO: Or, more fundamentally separate the concept of a layer from a node.
|
||||
self.name == "Layer"
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents the possible inputs to a node.
|
||||
@@ -405,9 +417,9 @@ pub struct NodeNetwork {
|
||||
pub inputs: Vec<NodeId>,
|
||||
pub outputs: Vec<NodeOutput>,
|
||||
pub nodes: HashMap<NodeId, DocumentNode>,
|
||||
/// These nodes are replaced with identity nodes when flattening
|
||||
/// These nodes are replaced with identity nodes during the graph flattening step
|
||||
pub disabled: Vec<NodeId>,
|
||||
/// In the case where a new node is chosen as output - what was the original
|
||||
/// In the case when a new node is chosen as a temporary output, this stores what it used to be so it can be restored later
|
||||
pub previous_outputs: Option<Vec<NodeOutput>>,
|
||||
}
|
||||
|
||||
@@ -811,9 +823,9 @@ impl NodeNetwork {
|
||||
|
||||
if node.implementation != DocumentNodeImplementation::Unresolved("graphene_core::ops::IdNode".into()) && self.disabled.contains(&id) {
|
||||
node.implementation = DocumentNodeImplementation::Unresolved("graphene_core::ops::IdNode".into());
|
||||
if node.name == "Layer" {
|
||||
if node.is_layer() {
|
||||
// Connect layer node to the graphic group below
|
||||
node.inputs.drain(..7);
|
||||
node.inputs.drain(..1);
|
||||
} else {
|
||||
node.inputs.drain(1..);
|
||||
}
|
||||
@@ -873,8 +885,10 @@ impl NodeNetwork {
|
||||
assert_eq!(
|
||||
node.inputs.len(),
|
||||
inner_network.inputs.len(),
|
||||
"The number of inputs to the node and the inner network must be the same for {}. The node has {:?} inputs, the network has {:?} inputs.",
|
||||
"\n\nThe number of inputs to the node and the inner network must be the same for \"{}\". The node has {} inputs, the network has {} inputs.\n\nNode inputs:\n\n{:?}\n\nNetwork inputs:\n\n{:?}\n",
|
||||
node.name,
|
||||
node.inputs.len(),
|
||||
inner_network.inputs.len(),
|
||||
node.inputs,
|
||||
inner_network.inputs
|
||||
);
|
||||
|
||||
@@ -621,7 +621,7 @@ impl TypingContext {
|
||||
let impls = self
|
||||
.lookup
|
||||
.get(&node.identifier)
|
||||
.ok_or(format!("No implementations found for {:?}. Other implementations found {:?}", node.identifier, self.lookup))?;
|
||||
.ok_or(format!("No implementations found for:\n\n{:?}\n\nOther implementations found:\n\n{:?}", 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));
|
||||
@@ -673,7 +673,7 @@ impl TypingContext {
|
||||
[] => {
|
||||
dbg!(&self.inferred);
|
||||
Err(format!(
|
||||
"No implementations found for {identifier} with \ninput: {input:?} and \nparameters: {parameters:?}.\nOther Implementations found: {:?}",
|
||||
"No implementations found for:\n\n{identifier}\n\nwith input:\n\n{input:?}\n\nand parameters:\n\n{parameters:?}\n\nOther Implementations found:\n\n{:?}",
|
||||
impls.keys().collect::<Vec<_>>(),
|
||||
))
|
||||
}
|
||||
|
||||
@@ -356,6 +356,7 @@ async fn brush(image: ImageFrame<Color>, bounds: ImageFrame<Color>, strokes: Vec
|
||||
let opaque_image = ImageFrame {
|
||||
image: Image::new(bbox.size().x as u32, bbox.size().y as u32, Color::WHITE),
|
||||
transform: background_bounds,
|
||||
blend_mode: BlendMode::Normal,
|
||||
};
|
||||
let mut erase_restore_mask = opaque_image;
|
||||
|
||||
@@ -409,7 +410,11 @@ mod test {
|
||||
#[test]
|
||||
fn test_translate_node() {
|
||||
let image = Image::new(10, 10, Color::TRANSPARENT);
|
||||
let mut image = ImageFrame { image, transform: DAffine2::IDENTITY };
|
||||
let mut image = ImageFrame {
|
||||
image,
|
||||
transform: DAffine2::IDENTITY,
|
||||
blend_mode: BlendMode::Normal,
|
||||
};
|
||||
image.translate(DVec2::new(1., 2.));
|
||||
let translate_node = TranslateNode::new(ClonedNode::new(image));
|
||||
let image = translate_node.eval(DVec2::new(1., 2.));
|
||||
|
||||
@@ -90,6 +90,7 @@ async fn map_gpu<'a: 'input>(image: ImageFrame<Color>, node: DocumentNode, edito
|
||||
height: image.image.height,
|
||||
},
|
||||
transform: image.transform,
|
||||
blend_mode: image.blend_mode,
|
||||
};
|
||||
|
||||
// TODO: The cache should be based on the network topology not the node name
|
||||
@@ -141,6 +142,7 @@ async fn map_gpu<'a: 'input>(image: ImageFrame<Color>, node: DocumentNode, edito
|
||||
height: image.image.height,
|
||||
},
|
||||
transform: image.transform,
|
||||
blend_mode: image.blend_mode,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -586,5 +588,6 @@ async fn blend_gpu_image(foreground: ImageFrame<Color>, background: ImageFrame<C
|
||||
height: background.image.height,
|
||||
},
|
||||
transform: background.transform,
|
||||
blend_mode: background.blend_mode,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
use autoquant::packing::ErrorFunction;
|
||||
use dyn_any::{DynAny, StaticType};
|
||||
use graphene_core::quantization::*;
|
||||
use graphene_core::raster::{Color, ImageFrame};
|
||||
use graphene_core::Node;
|
||||
|
||||
@@ -89,15 +89,15 @@ fn sample(footprint: Footprint, image_frame: ImageFrame<Color>) -> ImageFrame<Co
|
||||
|
||||
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 mut nwidth = size_px.x;
|
||||
let mut nheight = size_px.y;
|
||||
let mut new_width = size_px.x;
|
||||
let mut new_height = size_px.y;
|
||||
|
||||
// Only downscale the image for now
|
||||
let resized = if nwidth < image.width || nheight < image.height {
|
||||
nwidth = viewport_resolution_x as u32;
|
||||
nheight = viewport_resolution_y as u32;
|
||||
// TODO: choose filter based on quality reqirements
|
||||
cropped.resize_exact(nwidth, nheight, image::imageops::Triangle)
|
||||
let resized = if new_width < image.width || new_height < image.height {
|
||||
new_width = viewport_resolution_x as u32;
|
||||
new_height = viewport_resolution_y as u32;
|
||||
// TODO: choose filter based on quality requirements
|
||||
cropped.resize_exact(new_width, new_height, image::imageops::Triangle)
|
||||
} else {
|
||||
cropped
|
||||
};
|
||||
@@ -105,14 +105,18 @@ fn sample(footprint: Footprint, image_frame: ImageFrame<Color>) -> ImageFrame<Co
|
||||
let buffer = buffer.into_raw();
|
||||
let vec = bytemuck::cast_vec(buffer);
|
||||
let image = Image {
|
||||
width: nwidth,
|
||||
height: nheight,
|
||||
width: new_width,
|
||||
height: new_height,
|
||||
data: vec,
|
||||
};
|
||||
// we need to adjust the offset if we truncate the offset calculation
|
||||
|
||||
let new_transform = image_frame.transform * DAffine2::from_translation(offset) * DAffine2::from_scale(size);
|
||||
ImageFrame { image, transform: new_transform }
|
||||
ImageFrame {
|
||||
image,
|
||||
transform: new_transform,
|
||||
blend_mode: image_frame.blend_mode,
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
@@ -305,6 +309,7 @@ where
|
||||
let mut new_background = ImageFrame {
|
||||
image: new_background,
|
||||
transform: transfrom,
|
||||
blend_mode: background.blend_mode,
|
||||
};
|
||||
|
||||
new_background = blend_image(background, new_background, map_fn);
|
||||
@@ -417,6 +422,7 @@ fn extend_image_to_bounds_node(image: ImageFrame<Color>, bounds: DAffine2) -> Im
|
||||
ImageFrame {
|
||||
image: new_img,
|
||||
transform: new_texture_to_layer_space,
|
||||
blend_mode: image.blend_mode,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -450,7 +456,9 @@ fn empty_image<_P: Pixel>(transform: DAffine2, color: _P) -> ImageFrame<_P> {
|
||||
let height = transform.transform_vector2(DVec2::new(0., 1.)).length() as u32;
|
||||
|
||||
let image = Image::new(width, height, color);
|
||||
ImageFrame { image, transform }
|
||||
|
||||
let blend_mode = BlendMode::Normal;
|
||||
ImageFrame { image, transform, blend_mode }
|
||||
}
|
||||
|
||||
macro_rules! generate_imaginate_node {
|
||||
@@ -538,7 +546,11 @@ pub struct ImageFrameNode<P, Transform> {
|
||||
}
|
||||
#[node_macro::node_fn(ImageFrameNode<_P>)]
|
||||
fn image_frame<_P: Pixel>(image: Image<_P>, transform: DAffine2) -> graphene_core::raster::ImageFrame<_P> {
|
||||
graphene_core::raster::ImageFrame { image, transform }
|
||||
graphene_core::raster::ImageFrame {
|
||||
image,
|
||||
transform,
|
||||
blend_mode: BlendMode::Normal,
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
@@ -564,6 +576,7 @@ fn pixel_noise(width: u32, height: u32, seed: u32, noise_type: NoiseType) -> gra
|
||||
ImageFrame::<Color> {
|
||||
image,
|
||||
transform: DAffine2::from_scale(DVec2::new(width as f64, height as f64)),
|
||||
blend_mode: BlendMode::Normal,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -608,6 +621,7 @@ fn mandelbrot_node(footprint: Footprint) -> ImageFrame<Color> {
|
||||
ImageFrame {
|
||||
image: Image { width, height, data },
|
||||
transform: DAffine2::from_translation(offset) * DAffine2::from_scale(size),
|
||||
blend_mode: BlendMode::Normal,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -280,6 +280,7 @@ fn decode_image_node<'a: 'input>(data: Arc<[u8]>) -> ImageFrame<Color> {
|
||||
height: image.height(),
|
||||
},
|
||||
transform: glam::DAffine2::IDENTITY,
|
||||
blend_mode: graphene_core::raster::BlendMode::Normal,
|
||||
};
|
||||
image
|
||||
}
|
||||
|
||||
@@ -79,7 +79,7 @@ macro_rules! register_node {
|
||||
}
|
||||
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.
|
||||
// assign a Pin<Box<dyn Future<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),*])
|
||||
};
|
||||
@@ -312,6 +312,7 @@ fn node_registry() -> HashMap<NodeIdentifier, HashMap<NodeIOTypes, NodeConstruct
|
||||
let empty_image = ImageFrame {
|
||||
image: Image::new(bounds.x, bounds.y, Color::BLACK),
|
||||
transform,
|
||||
blend_mode: BlendMode::Normal,
|
||||
};
|
||||
let final_image = ClonedNode::new(empty_image).then(complete_node);
|
||||
let final_image = FutureWrapperNode::new(final_image);
|
||||
@@ -545,6 +546,9 @@ fn node_registry() -> HashMap<NodeIdentifier, HashMap<NodeIOTypes, NodeConstruct
|
||||
raster_node!(graphene_core::raster::OpacityNode<_>, params: [f32]),
|
||||
register_node!(graphene_core::raster::OpacityNode<_>, input: VectorData, params: [f32]),
|
||||
register_node!(graphene_core::raster::OpacityNode<_>, input: GraphicGroup, params: [f32]),
|
||||
register_node!(graphene_core::raster::BlendModeNode<_>, input: VectorData, params: [BlendMode]),
|
||||
register_node!(graphene_core::raster::BlendModeNode<_>, input: GraphicGroup, params: [BlendMode]),
|
||||
register_node!(graphene_core::raster::BlendModeNode<_>, input: ImageFrame<Color>, params: [BlendMode]),
|
||||
raster_node!(graphene_core::raster::PosterizeNode<_>, params: [f32]),
|
||||
raster_node!(graphene_core::raster::ExposureNode<_, _, _>, params: [f32, f32, f32]),
|
||||
register_node!(graphene_core::memo::LetNode<_>, input: Option<ImageFrame<Color>>, params: []),
|
||||
@@ -597,10 +601,10 @@ fn node_registry() -> HashMap<NodeIdentifier, HashMap<NodeIOTypes, NodeConstruct
|
||||
|args: Vec<graph_craft::proto::SharedNodeContainer>| {
|
||||
Box::pin(async move {
|
||||
use graphene_std::raster::ImaginateNode;
|
||||
macro_rules! instanciate_imaginate_node {
|
||||
macro_rules! instantiate_imaginate_node {
|
||||
($($i:expr,)*) => { ImaginateNode::new($(graphene_std::any::input_node(args[$i].clone()),)* ) };
|
||||
}
|
||||
let node: ImaginateNode<Color, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _> = instanciate_imaginate_node!(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,);
|
||||
let node: ImaginateNode<Color, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _> = instantiate_imaginate_node!(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,);
|
||||
let any = graphene_std::any::DynAnyNode::new(node);
|
||||
any.into_type_erased()
|
||||
})
|
||||
@@ -839,7 +843,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: []),
|
||||
async_node!(graphene_core::ConstructLayerNode<_, _, _, _, _, _, _, _>, input: Footprint, output: GraphicGroup, fn_params: [Footprint => graphene_core::GraphicElementData, () => String, () => BlendMode, () => f32, () => bool, () => bool, () => bool, Footprint => GraphicGroup]),
|
||||
async_node!(graphene_core::ConstructLayerNode<_, _>, input: Footprint, output: GraphicGroup, fn_params: [Footprint => graphene_core::GraphicElementData, Footprint => 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: GraphicGroup, params: []),
|
||||
|
||||
Reference in New Issue
Block a user