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:
Dennis Kobert
2025-03-01 23:54:52 +01:00
committed by Keavon Chambers
parent 0c1e96b9c6
commit 4ff2bdb04f
43 changed files with 1338 additions and 1545 deletions

View File

@@ -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();