mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-15 22:28:10 +08:00
Replace Footprint/() call arguments with dynamically-bound Contexts (#2232)
* Implement experimental Context struct and traits * Add Ctx super trait * Checkpoint * Return Any instead of DynAny * Fix send implementation for inputs with lifetimes * Port more nodes * Uncomment nodes * Port more nodes * Port vector nodes * Partial progress (the stuff I'm more sure about) * Partial progress (the stuff that's not compiling and I'm not sure about) * Fix more errors * First pass of fixing errors introduced by rebase * Port wasm application io * Fix brush node types * Add type annotation * Fix warnings and wasm compilation * Change types for Document Node definitions * Improve debugging for footprint not found errors * Forward context in append artboard node * Fix thumbnails * Fix loading most demo artwork * Wrap output type of all nodes in future * Encode futures as part of the type * Fix document node definitions for future types * Remove Clippy warnings * Fix more things * Fix opening demo art with manual composition upgrading * Set correct type for manual composition * Fix brush * Fix tests * Update docs for deps * Fix up some node signature issues * Code review --------- Co-authored-by: Keavon Chambers <keavon@keavon.com> Co-authored-by: hypercube <0hypercube@gmail.com>
This commit is contained in:
committed by
Keavon Chambers
parent
0c1e96b9c6
commit
4ff2bdb04f
@@ -1,21 +1,23 @@
|
||||
use crate::raster::{blend_image_closure, BlendImageTupleNode, EmptyImageNode, ExtendImageToBoundsNode};
|
||||
use crate::raster::{blend_image_closure, BlendImageTupleNode, ExtendImageToBoundsNode};
|
||||
|
||||
use graph_craft::generic::FnNode;
|
||||
use graph_craft::proto::FutureWrapperNode;
|
||||
use graphene_core::raster::adjustments::blend_colors;
|
||||
use graphene_core::raster::bbox::{AxisAlignedBbox, Bbox};
|
||||
use graphene_core::raster::brush_cache::BrushCache;
|
||||
use graphene_core::raster::image::{ImageFrame, ImageFrameTable};
|
||||
use graphene_core::raster::BlendMode;
|
||||
use graphene_core::raster::{Alpha, BlendColorPairNode, Color, Image, Pixel, Sample};
|
||||
use graphene_core::transform::{Footprint, Transform, TransformMut};
|
||||
use graphene_core::raster::{Alpha, Color, Image, Pixel, Sample};
|
||||
use graphene_core::transform::{Transform, TransformMut};
|
||||
use graphene_core::value::{ClonedNode, CopiedNode, ValueNode};
|
||||
use graphene_core::vector::brush_stroke::{BrushStroke, BrushStyle};
|
||||
use graphene_core::vector::VectorDataTable;
|
||||
use graphene_core::Node;
|
||||
use graphene_core::{Ctx, Node};
|
||||
|
||||
use glam::{DAffine2, DVec2};
|
||||
|
||||
#[node_macro::node(category("Debug"))]
|
||||
fn vector_points(_: (), vector_data: VectorDataTable) -> Vec<DVec2> {
|
||||
fn vector_points(_: impl Ctx, vector_data: VectorDataTable) -> Vec<DVec2> {
|
||||
let vector_data = vector_data.one_item();
|
||||
|
||||
vector_data.point_domain.positions().to_vec()
|
||||
@@ -131,14 +133,21 @@ where
|
||||
target
|
||||
}
|
||||
|
||||
pub fn create_brush_texture(brush_style: &BrushStyle) -> Image<Color> {
|
||||
let stamp = BrushStampGeneratorNode::new(CopiedNode::new(brush_style.color), CopiedNode::new(brush_style.hardness), CopiedNode::new(brush_style.flow));
|
||||
let stamp = stamp.eval(brush_style.diameter);
|
||||
pub async fn create_brush_texture(brush_style: &BrushStyle) -> Image<Color> {
|
||||
let stamp = brush_stamp_generator(brush_style.diameter, brush_style.color, brush_style.hardness, brush_style.flow);
|
||||
let transform = DAffine2::from_scale_angle_translation(DVec2::splat(brush_style.diameter), 0., -DVec2::splat(brush_style.diameter / 2.));
|
||||
let blank_texture = EmptyImageNode::new(CopiedNode::new(transform), CopiedNode::new(Color::TRANSPARENT)).eval(());
|
||||
let normal_blend = BlendColorPairNode::new(CopiedNode::new(BlendMode::Normal), CopiedNode::new(100.));
|
||||
let blend_executor = BlendImageTupleNode::new(ValueNode::new(normal_blend));
|
||||
blend_executor.eval((blank_texture, stamp)).image
|
||||
use crate::raster::empty_image;
|
||||
let blank_texture = empty_image((), transform, Color::TRANSPARENT);
|
||||
// let normal_blend = BlendColorPairNode::new(
|
||||
// FutureWrapperNode::new(ValueNode::new(CopiedNode::new(BlendMode::Normal))),
|
||||
// FutureWrapperNode::new(ValueNode::new(CopiedNode::new(100.))),
|
||||
// );
|
||||
// normal_blend.eval((Color::default(), Color::default()));
|
||||
// use crate::raster::blend_image_tuple;
|
||||
// blend_image_tuple((blank_texture, stamp), &normal_blend).await.image;
|
||||
crate::raster::blend_image_closure(stamp, blank_texture, |a, b| blend_colors(a, b, BlendMode::Normal, 1.)).image
|
||||
// let blend_executoc = BlendImageTupleNode::new(FutureWrapperNode::new(ValueNode::new(normal_blend)));
|
||||
// blend_executor.eval((blank_texture, stamp)).image
|
||||
}
|
||||
|
||||
macro_rules! inline_blend_funcs {
|
||||
@@ -202,7 +211,7 @@ pub fn blend_with_mode(background: ImageFrame<Color>, foreground: ImageFrame<Col
|
||||
}
|
||||
|
||||
#[node_macro::node(category(""))]
|
||||
fn brush(_: Footprint, image: ImageFrameTable<Color>, bounds: ImageFrameTable<Color>, strokes: Vec<BrushStroke>, cache: BrushCache) -> ImageFrameTable<Color> {
|
||||
async fn brush(_: impl Ctx, image: ImageFrameTable<Color>, bounds: ImageFrameTable<Color>, strokes: Vec<BrushStroke>, cache: BrushCache) -> ImageFrameTable<Color> {
|
||||
let image = image.one_item().clone();
|
||||
|
||||
let stroke_bbox = strokes.iter().map(|s| s.bounding_box()).reduce(|a, b| a.union(&b)).unwrap_or(AxisAlignedBbox::ZERO);
|
||||
@@ -225,11 +234,13 @@ fn brush(_: Footprint, image: ImageFrameTable<Color>, bounds: ImageFrameTable<Co
|
||||
for (idx, stroke) in brush_plan.strokes.into_iter().enumerate() {
|
||||
// Create brush texture.
|
||||
// TODO: apply rotation from layer to stamp for non-rotationally-symmetric brushes.
|
||||
let brush_texture = cache.get_cached_brush(&stroke.style).unwrap_or_else(|| {
|
||||
let tex = create_brush_texture(&stroke.style);
|
||||
let mut brush_texture = cache.get_cached_brush(&stroke.style);
|
||||
if brush_texture.is_none() {
|
||||
let tex = create_brush_texture(&stroke.style).await;
|
||||
cache.store_brush(stroke.style.clone(), tex.clone());
|
||||
tex
|
||||
});
|
||||
brush_texture = Some(tex);
|
||||
}
|
||||
let brush_texture = brush_texture.unwrap();
|
||||
|
||||
// Compute transformation from stroke texture space into layer space, and create the stroke texture.
|
||||
let skip = if idx == 0 { brush_plan.first_stroke_point_skip } else { 0 };
|
||||
@@ -246,16 +257,23 @@ fn brush(_: Footprint, image: ImageFrameTable<Color>, bounds: ImageFrameTable<Co
|
||||
let stroke_origin_in_layer = bbox.start - snap_offset - DVec2::splat(stroke.style.diameter / 2.);
|
||||
let stroke_to_layer = DAffine2::from_translation(stroke_origin_in_layer) * DAffine2::from_scale(stroke_size);
|
||||
|
||||
let normal_blend = BlendColorPairNode::new(CopiedNode::new(BlendMode::Normal), CopiedNode::new(100.));
|
||||
let blit_node = BlitNode::new(ClonedNode::new(brush_texture), ClonedNode::new(positions), ClonedNode::new(normal_blend));
|
||||
// let normal_blend = BlendColorPairNode::new(ValueNode::new(CopiedNode::new(BlendMode::Normal)), ValueNode::new(CopiedNode::new(100.)));
|
||||
let normal_blend = FnNode::new(|(a, b)| blend_colors(a, b, BlendMode::Normal, 1.));
|
||||
let blit_node = BlitNode::new(
|
||||
FutureWrapperNode::new(ClonedNode::new(brush_texture)),
|
||||
FutureWrapperNode::new(ClonedNode::new(positions)),
|
||||
FutureWrapperNode::new(ClonedNode::new(normal_blend)),
|
||||
);
|
||||
let blit_target = if idx == 0 {
|
||||
let target = core::mem::take(&mut brush_plan.first_stroke_texture);
|
||||
ExtendImageToBoundsNode::new(CopiedNode::new(stroke_to_layer)).eval(target)
|
||||
} else {
|
||||
EmptyImageNode::new(CopiedNode::new(stroke_to_layer), CopiedNode::new(Color::TRANSPARENT)).eval(())
|
||||
use crate::raster::empty_image;
|
||||
empty_image((), stroke_to_layer, Color::TRANSPARENT)
|
||||
// EmptyImageNode::new(CopiedNode::new(stroke_to_layer), CopiedNode::new(Color::TRANSPARENT)).eval(())
|
||||
};
|
||||
|
||||
blit_node.eval(blit_target)
|
||||
blit_node.eval(blit_target).await
|
||||
};
|
||||
|
||||
// Cache image before doing final blend, and store final stroke texture.
|
||||
@@ -277,34 +295,44 @@ fn brush(_: Footprint, image: ImageFrameTable<Color>, bounds: ImageFrameTable<Co
|
||||
let mut erase_restore_mask = opaque_image;
|
||||
|
||||
for stroke in erase_restore_strokes {
|
||||
let brush_texture = cache.get_cached_brush(&stroke.style).unwrap_or_else(|| {
|
||||
let tex = create_brush_texture(&stroke.style);
|
||||
let mut brush_texture = cache.get_cached_brush(&stroke.style);
|
||||
if brush_texture.is_none() {
|
||||
let tex = create_brush_texture(&stroke.style).await;
|
||||
cache.store_brush(stroke.style.clone(), tex.clone());
|
||||
tex
|
||||
});
|
||||
brush_texture = Some(tex);
|
||||
}
|
||||
let brush_texture = brush_texture.unwrap();
|
||||
let positions: Vec<_> = stroke.compute_blit_points().into_iter().collect();
|
||||
|
||||
match stroke.style.blend_mode {
|
||||
BlendMode::Erase => {
|
||||
let blend_params = BlendColorPairNode::new(CopiedNode::new(BlendMode::Erase), CopiedNode::new(100.));
|
||||
let blit_node = BlitNode::new(ClonedNode::new(brush_texture), ClonedNode::new(positions), ClonedNode::new(blend_params));
|
||||
erase_restore_mask = blit_node.eval(erase_restore_mask);
|
||||
let blend_params = FnNode::new(|(a, b)| blend_colors(a, b, BlendMode::Erase, 1.));
|
||||
let blit_node = BlitNode::new(
|
||||
FutureWrapperNode::new(ClonedNode::new(brush_texture)),
|
||||
FutureWrapperNode::new(ClonedNode::new(positions)),
|
||||
FutureWrapperNode::new(ClonedNode::new(blend_params)),
|
||||
);
|
||||
erase_restore_mask = blit_node.eval(erase_restore_mask).await;
|
||||
}
|
||||
|
||||
// Yes, this is essentially the same as the above, but we duplicate to inline the blend mode.
|
||||
BlendMode::Restore => {
|
||||
let blend_params = BlendColorPairNode::new(CopiedNode::new(BlendMode::Restore), CopiedNode::new(100.));
|
||||
let blit_node = BlitNode::new(ClonedNode::new(brush_texture), ClonedNode::new(positions), ClonedNode::new(blend_params));
|
||||
erase_restore_mask = blit_node.eval(erase_restore_mask);
|
||||
let blend_params = FnNode::new(|(a, b)| blend_colors(a, b, BlendMode::Restore, 1.));
|
||||
let blit_node = BlitNode::new(
|
||||
FutureWrapperNode::new(ClonedNode::new(brush_texture)),
|
||||
FutureWrapperNode::new(ClonedNode::new(positions)),
|
||||
FutureWrapperNode::new(ClonedNode::new(blend_params)),
|
||||
);
|
||||
erase_restore_mask = blit_node.eval(erase_restore_mask).await;
|
||||
}
|
||||
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
let blend_params = BlendColorPairNode::new(CopiedNode::new(BlendMode::MultiplyAlpha), CopiedNode::new(100.));
|
||||
let blend_executor = BlendImageTupleNode::new(ValueNode::new(blend_params));
|
||||
actual_image = blend_executor.eval((actual_image, erase_restore_mask));
|
||||
let blend_params = FnNode::new(|(a, b)| blend_colors(a, b, BlendMode::MultiplyAlpha, 1.));
|
||||
let blend_executor = BlendImageTupleNode::new(FutureWrapperNode::new(ValueNode::new(blend_params)));
|
||||
actual_image = blend_executor.eval((actual_image, erase_restore_mask)).await;
|
||||
}
|
||||
|
||||
ImageFrameTable::new(actual_image)
|
||||
@@ -315,15 +343,13 @@ mod test {
|
||||
use super::*;
|
||||
|
||||
use graphene_core::transform::Transform;
|
||||
use graphene_core::value::ClonedNode;
|
||||
|
||||
use glam::DAffine2;
|
||||
|
||||
#[test]
|
||||
fn test_brush_texture() {
|
||||
let brush_texture_node = BrushStampGeneratorNode::new(ClonedNode::new(Color::BLACK), ClonedNode::new(100.), ClonedNode::new(100.));
|
||||
let size = 20.;
|
||||
let image = brush_texture_node.eval(size);
|
||||
let image = brush_stamp_generator(size, Color::BLACK, 100., 100.);
|
||||
assert_eq!(image.transform(), DAffine2::from_scale_angle_translation(DVec2::splat(size.ceil()), 0., -DVec2::splat(size / 2.)));
|
||||
// center pixel should be BLACK
|
||||
assert_eq!(image.sample(DVec2::splat(0.), DVec2::ONE), Some(Color::BLACK));
|
||||
|
||||
@@ -1,28 +1,14 @@
|
||||
use graph_craft::proto::types::Percentage;
|
||||
use graphene_core::raster::image::{ImageFrame, ImageFrameTable};
|
||||
use graphene_core::raster::Image;
|
||||
use graphene_core::transform::Footprint;
|
||||
use graphene_core::Color;
|
||||
use graphene_core::{Color, Ctx};
|
||||
|
||||
use image::{DynamicImage, GenericImage, GenericImageView, GrayImage, ImageBuffer, Luma, Rgba, RgbaImage};
|
||||
use ndarray::{Array2, ArrayBase, Dim, OwnedRepr};
|
||||
use std::cmp::{max, min};
|
||||
|
||||
#[node_macro::node(category("Raster: Filter"))]
|
||||
async fn dehaze<F: 'n + Send + Sync>(
|
||||
#[implementations(
|
||||
(),
|
||||
Footprint,
|
||||
)]
|
||||
footprint: F,
|
||||
#[implementations(
|
||||
() -> ImageFrameTable<Color>,
|
||||
Footprint -> ImageFrameTable<Color>,
|
||||
)]
|
||||
image_frame: impl Node<F, Output = ImageFrameTable<Color>>,
|
||||
strength: Percentage,
|
||||
) -> ImageFrameTable<Color> {
|
||||
let image_frame = image_frame.eval(footprint).await;
|
||||
#[node_macro::node(category("Raster"))]
|
||||
async fn dehaze(_: impl Ctx, image_frame: ImageFrameTable<Color>, strength: Percentage) -> ImageFrameTable<Color> {
|
||||
let image_frame = image_frame.one_item();
|
||||
|
||||
// Prepare the image data for processing
|
||||
|
||||
@@ -19,7 +19,7 @@ use crate::wasm_application_io::WasmApplicationIo;
|
||||
|
||||
// TODO: Move to graph-craft
|
||||
#[node_macro::node(category("Debug: GPU"))]
|
||||
async fn compile_gpu<'a: 'n>(_: (), node: &'a DocumentNode, typing_context: TypingContext, io: ShaderIO) -> Result<compilation_client::Shader, String> {
|
||||
async fn compile_gpu<'a: 'n>(_: impl Ctx, node: &'a DocumentNode, typing_context: TypingContext, io: ShaderIO) -> Result<compilation_client::Shader, String> {
|
||||
let mut typing_context = typing_context;
|
||||
let compiler = graph_craft::graphene_compiler::Compiler {};
|
||||
let DocumentNodeImplementation::Network(ref network) = node.implementation else { panic!() };
|
||||
@@ -279,7 +279,7 @@ where
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Debug: GPU"))]
|
||||
async fn blend_gpu_image(_: (), foreground: ImageFrameTable<Color>, background: ImageFrameTable<Color>, blend_mode: BlendMode, opacity: f64) -> ImageFrameTable<Color> {
|
||||
async fn blend_gpu_image(_: impl Ctx, foreground: ImageFrameTable<Color>, background: ImageFrameTable<Color>, blend_mode: BlendMode, opacity: f64) -> ImageFrameTable<Color> {
|
||||
let foreground = foreground.one_item();
|
||||
let background = background.one_item();
|
||||
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
use graphene_core::Ctx;
|
||||
|
||||
#[node_macro::node(category("Network"))]
|
||||
async fn get_request(_: (), url: String) -> reqwest::Response {
|
||||
async fn get_request(_: impl Ctx, url: String) -> reqwest::Response {
|
||||
reqwest::get(url).await.unwrap()
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Network"))]
|
||||
async fn post_request(_: (), url: String, body: String) -> reqwest::Response {
|
||||
async fn post_request(_: impl Ctx, url: String, body: String) -> reqwest::Response {
|
||||
reqwest::Client::new().post(url).body(body).send().await.unwrap()
|
||||
}
|
||||
|
||||
@@ -1,19 +1,10 @@
|
||||
use graphene_core::raster::image::ImageFrameTable;
|
||||
use graphene_core::transform::Footprint;
|
||||
use graphene_core::Color;
|
||||
use graphene_core::{Color, Ctx};
|
||||
|
||||
#[node_macro::node(category("Raster"))]
|
||||
async fn image_color_palette<F: 'n + Send>(
|
||||
#[implementations(
|
||||
(),
|
||||
Footprint,
|
||||
)]
|
||||
footprint: F,
|
||||
#[implementations(
|
||||
() -> ImageFrameTable<Color>,
|
||||
Footprint -> ImageFrameTable<Color>,
|
||||
)]
|
||||
image: impl Node<F, Output = ImageFrameTable<Color>>,
|
||||
async fn image_color_palette(
|
||||
_: impl Ctx,
|
||||
image: ImageFrameTable<Color>,
|
||||
#[min(1.)]
|
||||
#[max(28.)]
|
||||
max_size: u32,
|
||||
@@ -25,7 +16,6 @@ async fn image_color_palette<F: 'n + Send>(
|
||||
let mut histogram: Vec<usize> = vec![0; (bins + 1.) as usize];
|
||||
let mut colors: Vec<Vec<Color>> = vec![vec![]; (bins + 1.) as usize];
|
||||
|
||||
let image = image.eval(footprint).await;
|
||||
let image = image.one_item();
|
||||
|
||||
for pixel in image.image.data.iter() {
|
||||
@@ -75,30 +65,24 @@ async fn image_color_palette<F: 'n + Send>(
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
use graph_craft::generic::FnNode;
|
||||
use graphene_core::raster::image::{ImageFrame, ImageFrameTable};
|
||||
use graphene_core::raster::Image;
|
||||
use graphene_core::value::CopiedNode;
|
||||
use graphene_core::Node;
|
||||
|
||||
#[test]
|
||||
fn test_image_color_palette() {
|
||||
let node = ImageColorPaletteNode {
|
||||
max_size: CopiedNode(1u32),
|
||||
image: FnNode::new(|_| {
|
||||
Box::pin(async move {
|
||||
ImageFrameTable::new(ImageFrame {
|
||||
image: Image {
|
||||
width: 100,
|
||||
height: 100,
|
||||
data: vec![Color::from_rgbaf32(0., 0., 0., 1.).unwrap(); 10000],
|
||||
base64_string: None,
|
||||
},
|
||||
..Default::default()
|
||||
})
|
||||
})
|
||||
let result = image_color_palette(
|
||||
(),
|
||||
ImageFrameTable::new(ImageFrame {
|
||||
image: Image {
|
||||
width: 100,
|
||||
height: 100,
|
||||
data: vec![Color::from_rgbaf32(0., 0., 0., 1.).unwrap(); 10000],
|
||||
base64_string: None,
|
||||
},
|
||||
..Default::default()
|
||||
}),
|
||||
};
|
||||
assert_eq!(futures::executor::block_on(node.eval(())), [Color::from_rgbaf32(0., 0., 0., 1.).unwrap()]);
|
||||
1,
|
||||
);
|
||||
assert_eq!(futures::executor::block_on(result), [Color::from_rgbaf32(0., 0., 0., 1.).unwrap()]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,10 +2,11 @@ use dyn_any::DynAny;
|
||||
use graphene_core::raster::bbox::Bbox;
|
||||
use graphene_core::raster::image::{ImageFrame, ImageFrameTable};
|
||||
use graphene_core::raster::{
|
||||
Alpha, Bitmap, BitmapMut, CellularDistanceFunction, CellularReturnType, DomainWarpType, FractalType, Image, Linear, LinearChannel, Luminance, NoiseType, Pixel, RGBMut, RedGreenBlue, Sample,
|
||||
Alpha, AlphaMut, Bitmap, BitmapMut, CellularDistanceFunction, CellularReturnType, DomainWarpType, FractalType, Image, Linear, LinearChannel, Luminance, NoiseType, Pixel, RGBMut, RedGreenBlue,
|
||||
Sample,
|
||||
};
|
||||
use graphene_core::transform::{Footprint, Transform};
|
||||
use graphene_core::{AlphaBlending, Color, Node};
|
||||
use graphene_core::transform::Transform;
|
||||
use graphene_core::{AlphaBlending, Color, Ctx, ExtractFootprint, Node};
|
||||
|
||||
use fastnoise_lite;
|
||||
use glam::{DAffine2, DVec2, Vec2};
|
||||
@@ -28,13 +29,14 @@ impl From<std::io::Error> for Error {
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Debug: Raster"))]
|
||||
fn sample_image(footprint: Footprint, image_frame: ImageFrameTable<Color>) -> ImageFrameTable<Color> {
|
||||
fn sample_image(ctx: impl ExtractFootprint + Clone + Send, image_frame: ImageFrameTable<Color>) -> ImageFrameTable<Color> {
|
||||
let image_frame = image_frame.one_item();
|
||||
|
||||
// Resize the image using the image crate
|
||||
let image = &image_frame.image;
|
||||
let data = bytemuck::cast_vec(image.data.clone());
|
||||
|
||||
let footprint = ctx.footprint();
|
||||
let viewport_bounds = footprint.viewport_bounds_in_local_space();
|
||||
let image_bounds = Bbox::from_transform(image_frame.transform).to_axis_aligned_bbox();
|
||||
let intersection = viewport_bounds.intersect(&image_bounds);
|
||||
@@ -107,15 +109,7 @@ where
|
||||
image
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct InsertChannelNode<P, S, Insertion, TargetChannel> {
|
||||
insertion: Insertion,
|
||||
target_channel: TargetChannel,
|
||||
_p: PhantomData<P>,
|
||||
_s: PhantomData<S>,
|
||||
}
|
||||
|
||||
#[node_macro::old_node_fn(InsertChannelNode<_P, _S>)]
|
||||
#[node_macro::node]
|
||||
fn insert_channel<
|
||||
// _P is the color of the input image.
|
||||
_P: RGBMut,
|
||||
@@ -124,8 +118,9 @@ fn insert_channel<
|
||||
Input: BitmapMut<Pixel = _P>,
|
||||
Insertion: Bitmap<Pixel = _S>,
|
||||
>(
|
||||
mut image: Input,
|
||||
insertion: Insertion,
|
||||
_: impl Ctx,
|
||||
#[implementations(ImageFrameTable<Color>)] mut image: Input,
|
||||
#[implementations(ImageFrameTable<Color>)] insertion: Insertion,
|
||||
target_channel: RedGreenBlue,
|
||||
) -> Input
|
||||
where
|
||||
@@ -154,15 +149,60 @@ where
|
||||
|
||||
image
|
||||
}
|
||||
#[node_macro::node]
|
||||
fn combine_channels<
|
||||
// _P is the color of the input image.
|
||||
_P: RGBMut + AlphaMut,
|
||||
_S: Pixel + Luminance,
|
||||
// Input image
|
||||
Input: BitmapMut<Pixel = _P>,
|
||||
Red: Bitmap<Pixel = _S>,
|
||||
Green: Bitmap<Pixel = _S>,
|
||||
Blue: Bitmap<Pixel = _S>,
|
||||
Alpha: Bitmap<Pixel = _S>,
|
||||
>(
|
||||
_: impl Ctx,
|
||||
#[implementations(ImageFrameTable<Color>)] mut image: Input,
|
||||
#[implementations(ImageFrameTable<Color>)] red: Red,
|
||||
#[implementations(ImageFrameTable<Color>)] green: Green,
|
||||
#[implementations(ImageFrameTable<Color>)] blue: Blue,
|
||||
#[implementations(ImageFrameTable<Color>)] alpha: Alpha,
|
||||
) -> Input
|
||||
where
|
||||
_P::ColorChannel: Linear,
|
||||
{
|
||||
let dimensions = [red.dim(), green.dim(), blue.dim(), alpha.dim()];
|
||||
if dimensions.iter().all(|&(x, _)| x == 0) {
|
||||
return image;
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct MaskImageNode<P, S, Stencil> {
|
||||
stencil: Stencil,
|
||||
_p: PhantomData<P>,
|
||||
_s: PhantomData<S>,
|
||||
if dimensions.iter().any(|&(x, y)| x != image.width() || y != image.height()) {
|
||||
log::warn!("Stencil and image have different sizes. This is not supported.");
|
||||
return image;
|
||||
}
|
||||
|
||||
for y in 0..image.height() {
|
||||
for x in 0..image.width() {
|
||||
let image_pixel = image.get_pixel_mut(x, y).unwrap();
|
||||
if let Some(r) = red.get_pixel(x, y) {
|
||||
image_pixel.set_red(r.l().cast_linear_channel());
|
||||
}
|
||||
if let Some(g) = green.get_pixel(x, y) {
|
||||
image_pixel.set_green(g.l().cast_linear_channel());
|
||||
}
|
||||
if let Some(b) = blue.get_pixel(x, y) {
|
||||
image_pixel.set_blue(b.l().cast_linear_channel());
|
||||
}
|
||||
if let Some(a) = alpha.get_pixel(x, y) {
|
||||
image_pixel.set_alpha(a.l().cast_linear_channel());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
image
|
||||
}
|
||||
|
||||
#[node_macro::old_node_fn(MaskImageNode<_P, _S>)]
|
||||
#[node_macro::node()]
|
||||
fn mask_image<
|
||||
// _P is the color of the input image. It must have an alpha channel because that is going to
|
||||
// be modified by the mask
|
||||
@@ -175,8 +215,9 @@ fn mask_image<
|
||||
// Stencil
|
||||
Stencil: Transform + Sample<Pixel = _S>,
|
||||
>(
|
||||
mut image: Input,
|
||||
stencil: Stencil,
|
||||
_: impl Ctx,
|
||||
#[implementations(ImageFrameTable<Color>)] mut image: Input,
|
||||
#[implementations(ImageFrameTable<Color>)] stencil: Stencil,
|
||||
) -> Input {
|
||||
let image_size = DVec2::new(image.width() as f64, image.height() as f64);
|
||||
let mask_size = stencil.transform().decompose_scale();
|
||||
@@ -207,17 +248,17 @@ fn mask_image<
|
||||
image
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct BlendImageTupleNode<P, Fg, MapFn> {
|
||||
map_fn: MapFn,
|
||||
_p: PhantomData<P>,
|
||||
_fg: PhantomData<Fg>,
|
||||
}
|
||||
// #[derive(Debug, Clone, Copy)]
|
||||
// pub struct BlendImageTupleNode<P, Fg, MapFn> {
|
||||
// map_fn: MapFn,
|
||||
// _p: PhantomData<P>,
|
||||
// _fg: PhantomData<Fg>,
|
||||
// }
|
||||
|
||||
#[node_macro::old_node_fn(BlendImageTupleNode<_P, _Fg>)]
|
||||
fn blend_image_tuple<_P: Alpha + Pixel + Debug, MapFn, _Fg: Sample<Pixel = _P> + Transform>(images: (ImageFrame<_P>, _Fg), map_fn: &'input MapFn) -> ImageFrame<_P>
|
||||
#[node_macro::node(skip_impl)]
|
||||
async fn blend_image_tuple<_P: Alpha + Pixel + Debug + Send, MapFn, _Fg: Sample<Pixel = _P> + Transform + Clone + Send + 'n>(images: (ImageFrame<_P>, _Fg), map_fn: &'n MapFn) -> ImageFrame<_P>
|
||||
where
|
||||
MapFn: for<'any_input> Node<'any_input, (_P, _P), Output = _P> + 'input + Clone,
|
||||
MapFn: for<'any_input> Node<'any_input, (_P, _P), Output = _P> + 'n + Clone,
|
||||
{
|
||||
let (background, foreground) = images;
|
||||
|
||||
@@ -319,7 +360,7 @@ fn extend_image_to_bounds(image: ImageFrame<Color>, bounds: DAffine2) -> ImageFr
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Debug: Raster"))]
|
||||
fn empty_image<P: Pixel>(_: (), transform: DAffine2, #[implementations(Color)] color: P) -> ImageFrame<P> {
|
||||
fn empty_image<P: Pixel>(_: impl Ctx, transform: DAffine2, #[implementations(Color)] color: P) -> ImageFrame<P> {
|
||||
let width = transform.transform_vector2(DVec2::new(1., 0.)).length() as u32;
|
||||
let height = transform.transform_vector2(DVec2::new(0., 1.)).length() as u32;
|
||||
|
||||
@@ -431,10 +472,10 @@ fn empty_image<P: Pixel>(_: (), transform: DAffine2, #[implementations(Color)] c
|
||||
// tiling: Tiling: bool,
|
||||
// }
|
||||
|
||||
#[node_macro::node(category("Raster: Generator"))]
|
||||
#[node_macro::node(category("Raster"))]
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn noise_pattern(
|
||||
footprint: Footprint,
|
||||
ctx: impl ExtractFootprint + Ctx,
|
||||
_primary: (),
|
||||
clip: bool,
|
||||
seed: u32,
|
||||
@@ -452,6 +493,7 @@ fn noise_pattern(
|
||||
cellular_return_type: CellularReturnType,
|
||||
cellular_jitter: f64,
|
||||
) -> ImageFrameTable<Color> {
|
||||
let footprint = ctx.footprint();
|
||||
let viewport_bounds = footprint.viewport_bounds_in_local_space();
|
||||
|
||||
let mut size = viewport_bounds.size();
|
||||
@@ -585,8 +627,9 @@ fn noise_pattern(
|
||||
ImageFrameTable::new(result)
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Raster: Generator"))]
|
||||
fn mandelbrot(footprint: Footprint) -> ImageFrameTable<Color> {
|
||||
#[node_macro::node(category("Raster"))]
|
||||
fn mandelbrot(ctx: impl ExtractFootprint + Send) -> ImageFrameTable<Color> {
|
||||
let footprint = ctx.footprint();
|
||||
let viewport_bounds = footprint.viewport_bounds_in_local_space();
|
||||
|
||||
let image_bounds = Bbox::from_transform(DAffine2::IDENTITY).to_axis_aligned_bbox();
|
||||
|
||||
@@ -3,10 +3,11 @@ use crate::vector::{VectorData, VectorDataTable};
|
||||
use graph_craft::wasm_application_io::WasmEditorApi;
|
||||
use graphene_core::text::TypesettingConfig;
|
||||
pub use graphene_core::text::{bounding_box, load_face, to_path, Font, FontCache};
|
||||
use graphene_core::Ctx;
|
||||
|
||||
#[node_macro::node(category(""))]
|
||||
fn text<'i: 'n>(
|
||||
_: (),
|
||||
_: impl Ctx,
|
||||
editor: &'i WasmEditorApi,
|
||||
text: String,
|
||||
font_name: Font,
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
use crate::transform::Footprint;
|
||||
|
||||
use bezier_rs::{ManipulatorGroup, Subpath};
|
||||
use graphene_core::vector::misc::BooleanOperation;
|
||||
use graphene_core::vector::style::Fill;
|
||||
pub use graphene_core::vector::*;
|
||||
use graphene_core::{transform::Transform, GraphicGroup};
|
||||
use graphene_core::{Color, GraphicElement, GraphicGroupTable};
|
||||
use graphene_core::{Color, Ctx, GraphicElement, GraphicGroupTable};
|
||||
pub use path_bool as path_bool_lib;
|
||||
use path_bool::{FillRule, PathBooleanOperation};
|
||||
|
||||
@@ -13,22 +11,7 @@ use glam::{DAffine2, DVec2};
|
||||
use std::ops::Mul;
|
||||
|
||||
#[node_macro::node(category(""))]
|
||||
async fn boolean_operation<F: 'n + Send>(
|
||||
#[implementations(
|
||||
(),
|
||||
Footprint,
|
||||
)]
|
||||
footprint: F,
|
||||
#[implementations(
|
||||
() -> GraphicGroupTable,
|
||||
Footprint -> GraphicGroupTable,
|
||||
)]
|
||||
group_of_paths: impl Node<F, Output = GraphicGroupTable>,
|
||||
operation: BooleanOperation,
|
||||
) -> VectorDataTable {
|
||||
let group_of_paths = group_of_paths.eval(footprint).await;
|
||||
let group_of_paths = group_of_paths.one_item();
|
||||
|
||||
async fn boolean_operation(_: impl Ctx, group_of_paths: GraphicGroupTable, operation: BooleanOperation) -> VectorDataTable {
|
||||
fn vector_from_image<T: Transform>(image_frame: T) -> VectorData {
|
||||
let corner1 = DVec2::ZERO;
|
||||
let corner2 = DVec2::new(1., 1.);
|
||||
@@ -193,6 +176,7 @@ async fn boolean_operation<F: 'n + Send>(
|
||||
}
|
||||
}
|
||||
|
||||
let group_of_paths = group_of_paths.one_item();
|
||||
// The first index is the bottom of the stack
|
||||
let mut boolean_operation_result = boolean_operation_on_vector_data(&collect_vector_data(group_of_paths), operation);
|
||||
|
||||
|
||||
@@ -12,8 +12,7 @@ use graphene_core::renderer::RenderMetadata;
|
||||
use graphene_core::renderer::{format_transform_matrix, GraphicElementRendered, ImageRenderMode, RenderParams, RenderSvgSegmentList, SvgRender};
|
||||
use graphene_core::transform::Footprint;
|
||||
use graphene_core::vector::VectorDataTable;
|
||||
use graphene_core::GraphicGroupTable;
|
||||
use graphene_core::{Color, WasmNotSend};
|
||||
use graphene_core::{Color, Context, Ctx, ExtractFootprint, GraphicGroupTable, OwnedContextImpl, WasmNotSend};
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use base64::Engine;
|
||||
@@ -27,7 +26,7 @@ use wasm_bindgen::JsCast;
|
||||
use web_sys::{CanvasRenderingContext2d, HtmlCanvasElement};
|
||||
|
||||
#[node_macro::node(category("Debug: GPU"))]
|
||||
async fn create_surface<'a: 'n>(_: (), editor: &'a WasmEditorApi) -> Arc<WasmSurfaceHandle> {
|
||||
async fn create_surface<'a: 'n>(_: impl Ctx, editor: &'a WasmEditorApi) -> Arc<WasmSurfaceHandle> {
|
||||
Arc::new(editor.application_io.as_ref().unwrap().create_window())
|
||||
}
|
||||
|
||||
@@ -37,7 +36,7 @@ async fn create_surface<'a: 'n>(_: (), editor: &'a WasmEditorApi) -> Arc<WasmSur
|
||||
// #[node_macro::node(category("Debug: GPU"))]
|
||||
// #[cfg(target_arch = "wasm32")]
|
||||
// async fn draw_image_frame(
|
||||
// _: (),
|
||||
// _: impl Ctx,
|
||||
// image: ImageFrameTable<graphene_core::raster::SRGBA8>,
|
||||
// surface_handle: Arc<WasmSurfaceHandle>,
|
||||
// ) -> graphene_core::application_io::SurfaceHandleFrame<HtmlCanvasElement> {
|
||||
@@ -60,7 +59,7 @@ async fn create_surface<'a: 'n>(_: (), editor: &'a WasmEditorApi) -> Arc<WasmSur
|
||||
// }
|
||||
|
||||
#[node_macro::node(category("Network"))]
|
||||
async fn load_resource<'a: 'n>(_: (), _primary: (), #[scope("editor-api")] editor: &'a WasmEditorApi, url: String) -> Arc<[u8]> {
|
||||
async fn load_resource<'a: 'n>(_: impl Ctx, _primary: (), #[scope("editor-api")] editor: &'a WasmEditorApi, #[name("URL")] url: String) -> Arc<[u8]> {
|
||||
let Some(api) = editor.application_io.as_ref() else {
|
||||
return Arc::from(include_bytes!("../../graph-craft/src/null.png").to_vec());
|
||||
};
|
||||
@@ -74,8 +73,8 @@ async fn load_resource<'a: 'n>(_: (), _primary: (), #[scope("editor-api")] edito
|
||||
data
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Raster"))]
|
||||
fn decode_image(_: (), data: Arc<[u8]>) -> ImageFrameTable<Color> {
|
||||
#[node_macro::node(category("Network"))]
|
||||
fn decode_image(_: impl Ctx, data: Arc<[u8]>) -> ImageFrameTable<Color> {
|
||||
let Some(image) = image::load_from_memory(data.as_ref()).ok() else {
|
||||
return ImageFrameTable::default();
|
||||
};
|
||||
@@ -156,13 +155,13 @@ async fn render_canvas(render_config: RenderConfig, data: impl GraphicElementRen
|
||||
#[node_macro::node(category(""))]
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
async fn rasterize<T: GraphicElementRendered + graphene_core::transform::TransformMut + WasmNotSend + 'n>(
|
||||
_: (),
|
||||
_: impl Ctx,
|
||||
#[implementations(
|
||||
Footprint -> VectorDataTable,
|
||||
Footprint -> ImageFrameTable<Color>,
|
||||
Footprint -> GraphicGroupTable,
|
||||
VectorDataTable,
|
||||
ImageFrameTable<Color>,
|
||||
GraphicGroupTable,
|
||||
)]
|
||||
data: impl Node<Footprint, Output = T>,
|
||||
mut data: T,
|
||||
footprint: Footprint,
|
||||
surface_handle: Arc<SurfaceHandle<HtmlCanvasElement>>,
|
||||
) -> ImageFrameTable<Color> {
|
||||
@@ -171,7 +170,6 @@ async fn rasterize<T: GraphicElementRendered + graphene_core::transform::Transfo
|
||||
return ImageFrameTable::default();
|
||||
}
|
||||
|
||||
let mut data = data.eval(footprint).await;
|
||||
let mut render = SvgRender::new();
|
||||
let aabb = Bbox::from_transform(footprint.transform).to_axis_aligned_bbox();
|
||||
let size = aabb.size();
|
||||
@@ -218,31 +216,34 @@ async fn rasterize<T: GraphicElementRendered + graphene_core::transform::Transfo
|
||||
#[node_macro::node(category(""))]
|
||||
async fn render<'a: 'n, T: 'n + GraphicElementRendered + WasmNotSend>(
|
||||
render_config: RenderConfig,
|
||||
editor_api: &'a WasmEditorApi,
|
||||
editor_api: impl Node<Context<'static>, Output = &'a WasmEditorApi>,
|
||||
#[implementations(
|
||||
Footprint -> VectorDataTable,
|
||||
Footprint -> ImageFrameTable<Color>,
|
||||
Footprint -> GraphicGroupTable,
|
||||
Footprint -> graphene_core::Artboard,
|
||||
Footprint -> graphene_core::ArtboardGroup,
|
||||
Footprint -> Option<Color>,
|
||||
Footprint -> Vec<Color>,
|
||||
Footprint -> bool,
|
||||
Footprint -> f32,
|
||||
Footprint -> f64,
|
||||
Footprint -> String,
|
||||
Context -> VectorDataTable,
|
||||
Context -> ImageFrameTable<Color>,
|
||||
Context -> GraphicGroupTable,
|
||||
Context -> graphene_core::Artboard,
|
||||
Context -> graphene_core::ArtboardGroup,
|
||||
Context -> Option<Color>,
|
||||
Context -> Vec<Color>,
|
||||
Context -> bool,
|
||||
Context -> f32,
|
||||
Context -> f64,
|
||||
Context -> String,
|
||||
)]
|
||||
data: impl Node<Footprint, Output = T>,
|
||||
_surface_handle: impl Node<(), Output = Option<wgpu_executor::WgpuSurface>>,
|
||||
data: impl Node<Context<'static>, Output = T>,
|
||||
_surface_handle: impl Node<Context<'static>, Output = Option<wgpu_executor::WgpuSurface>>,
|
||||
) -> RenderOutput {
|
||||
let footprint = render_config.viewport;
|
||||
let ctx = OwnedContextImpl::default().with_footprint(footprint).into_context();
|
||||
ctx.footprint();
|
||||
|
||||
let RenderConfig { hide_artboards, for_export, .. } = render_config;
|
||||
let render_params = RenderParams::new(render_config.view_mode, ImageRenderMode::Base64, None, false, hide_artboards, for_export);
|
||||
|
||||
let data = data.eval(footprint).await;
|
||||
let data = data.eval(ctx.clone()).await;
|
||||
let editor_api = editor_api.eval(ctx.clone()).await;
|
||||
#[cfg(all(feature = "vello", target_arch = "wasm32"))]
|
||||
let surface_handle = _surface_handle.eval(()).await;
|
||||
let surface_handle = _surface_handle.eval(ctx.clone()).await;
|
||||
let use_vello = editor_api.editor_preferences.use_vello();
|
||||
#[cfg(all(feature = "vello", target_arch = "wasm32"))]
|
||||
let use_vello = use_vello && surface_handle.is_some();
|
||||
|
||||
Reference in New Issue
Block a user