Instance tables refactor part 2: move the transform and alpha_blending fields up a level (#2249)

* Fix domain data structure field plural naming

* Rename method one_item to one_instance

Rename method one_item to one_instance

* Move the Instance<T> methods over to providing an Instance<T>/InstanceMut<T>

Move the Instance<T> methods over to providing an Instance<T>/InstanceMut<T>

* Add transform and alpha_blending fields to Instances<T>

* Finish the refactor (Brush tool is broken though)

* Add test for brush node

* Fix brush node

* Fix default empty images being 1x1 instead of 0x0 as they should be

* Fix tests

* Fix path transform

* Add correct upgrading to move the transform/blending up a level

---------

Co-authored-by: hypercube <0hypercube@gmail.com>
This commit is contained in:
Keavon Chambers
2025-03-02 01:26:36 -08:00
parent 4ff2bdb04f
commit f1160e1ca6
33 changed files with 1099 additions and 984 deletions

View File

@@ -5,8 +5,8 @@ use graphene_core::raster::{
Alpha, AlphaMut, Bitmap, BitmapMut, CellularDistanceFunction, CellularReturnType, DomainWarpType, FractalType, Image, Linear, LinearChannel, Luminance, NoiseType, Pixel, RGBMut, RedGreenBlue,
Sample,
};
use graphene_core::transform::Transform;
use graphene_core::{AlphaBlending, Color, Ctx, ExtractFootprint, Node};
use graphene_core::transform::{Transform, TransformMut};
use graphene_core::{AlphaBlending, Color, Ctx, ExtractFootprint, GraphicElement, Node};
use fastnoise_lite;
use glam::{DAffine2, DVec2, Vec2};
@@ -30,7 +30,10 @@ impl From<std::io::Error> for Error {
#[node_macro::node(category("Debug: Raster"))]
fn sample_image(ctx: impl ExtractFootprint + Clone + Send, image_frame: ImageFrameTable<Color>) -> ImageFrameTable<Color> {
let image_frame = image_frame.one_item();
let image_frame_transform = image_frame.transform();
let image_frame_alpha_blending = image_frame.one_instance().alpha_blending;
let image_frame = image_frame.one_instance().instance;
// Resize the image using the image crate
let image = &image_frame.image;
@@ -38,7 +41,7 @@ fn sample_image(ctx: impl ExtractFootprint + Clone + Send, image_frame: ImageFra
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 image_bounds = Bbox::from_transform(image_frame_transform).to_axis_aligned_bbox();
let intersection = viewport_bounds.intersect(&image_bounds);
let image_size = DAffine2::from_scale(DVec2::new(image.width as f64, image.height as f64));
let size = intersection.size();
@@ -46,7 +49,7 @@ fn sample_image(ctx: impl ExtractFootprint + Clone + Send, image_frame: ImageFra
// If the image would not be visible, return an empty image
if size.x <= 0. || size.y <= 0. {
return ImageFrameTable::default();
return ImageFrameTable::empty();
}
let image_buffer = image::Rgba32FImage::from_raw(image.width, image.height, data).expect("Failed to convert internal image format into image-rs data type.");
@@ -81,15 +84,13 @@ fn sample_image(ctx: impl ExtractFootprint + Clone + Send, image_frame: ImageFra
};
// 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);
let new_transform = image_frame_transform * DAffine2::from_translation(offset) * DAffine2::from_scale(size);
let result = ImageFrame {
image,
transform: new_transform,
alpha_blending: image_frame.alpha_blending,
};
let mut result = ImageFrameTable::new(ImageFrame { image });
*result.transform_mut() = new_transform;
*result.one_instance_mut().alpha_blending = *image_frame_alpha_blending;
ImageFrameTable::new(result)
result
}
#[derive(Debug, Clone, Copy)]
@@ -256,33 +257,35 @@ fn mask_image<
// }
#[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>
async fn blend_image_tuple<_P, MapFn, _Fg>(images: (ImageFrameTable<_P>, _Fg), map_fn: &'n MapFn) -> ImageFrameTable<_P>
where
_P: Alpha + Pixel + Debug + Send + dyn_any::StaticType,
_P::Static: Pixel,
MapFn: for<'any_input> Node<'any_input, (_P, _P), Output = _P> + 'n + Clone,
_Fg: Sample<Pixel = _P> + Transform + Clone + Send + 'n,
GraphicElement: From<ImageFrame<_P>>,
{
let (background, foreground) = images;
blend_image(foreground, background, map_fn)
}
fn blend_image<'input, _P: Alpha + Pixel + Debug, MapFn, Frame: Sample<Pixel = _P> + Transform, Background: BitmapMut<Pixel = _P> + Transform + Sample<Pixel = _P>>(
foreground: Frame,
background: Background,
map_fn: &'input MapFn,
) -> Background
fn blend_image<'input, _P, MapFn, Frame, Background>(foreground: Frame, background: Background, map_fn: &'input MapFn) -> Background
where
MapFn: Node<'input, (_P, _P), Output = _P>,
_P: Pixel + Alpha + Debug,
Frame: Sample<Pixel = _P> + Transform,
Background: BitmapMut<Pixel = _P> + Sample<Pixel = _P> + Transform,
{
blend_image_closure(foreground, background, |a, b| map_fn.eval((a, b)))
}
pub fn blend_image_closure<_P: Alpha + Pixel + Debug, MapFn, Frame: Sample<Pixel = _P> + Transform, Background: BitmapMut<Pixel = _P> + Transform + Sample<Pixel = _P>>(
foreground: Frame,
mut background: Background,
map_fn: MapFn,
) -> Background
pub fn blend_image_closure<_P, MapFn, Frame, Background>(foreground: Frame, mut background: Background, map_fn: MapFn) -> Background
where
MapFn: Fn(_P, _P) -> _P,
_P: Pixel + Alpha + Debug,
Frame: Sample<Pixel = _P> + Transform,
Background: BitmapMut<Pixel = _P> + Sample<Pixel = _P> + Transform,
{
let background_size = DVec2::new(background.width() as f64, background.height() as f64);
@@ -319,19 +322,20 @@ pub struct ExtendImageToBoundsNode<Bounds> {
}
#[node_macro::old_node_fn(ExtendImageToBoundsNode)]
fn extend_image_to_bounds(image: ImageFrame<Color>, bounds: DAffine2) -> ImageFrame<Color> {
fn extend_image_to_bounds(image: ImageFrameTable<Color>, bounds: DAffine2) -> ImageFrameTable<Color> {
let image_aabb = Bbox::unit().affine_transform(image.transform()).to_axis_aligned_bbox();
let bounds_aabb = Bbox::unit().affine_transform(bounds.transform()).to_axis_aligned_bbox();
if image_aabb.contains(bounds_aabb.start) && image_aabb.contains(bounds_aabb.end) {
return image;
}
if image.image.width == 0 || image.image.height == 0 {
let image_instance = image.one_instance().instance;
if image_instance.image.width == 0 || image_instance.image.height == 0 {
return empty_image((), bounds, Color::TRANSPARENT);
}
let orig_image_scale = DVec2::new(image.image.width as f64, image.image.height as f64);
let layer_to_image_space = DAffine2::from_scale(orig_image_scale) * image.transform.inverse();
let orig_image_scale = DVec2::new(image_instance.image.width as f64, image_instance.image.height as f64);
let layer_to_image_space = DAffine2::from_scale(orig_image_scale) * image.transform().inverse();
let bounds_in_image_space = Bbox::unit().affine_transform(layer_to_image_space * bounds).to_axis_aligned_bbox();
let new_start = bounds_in_image_space.start.floor().min(DVec2::ZERO);
@@ -341,36 +345,37 @@ fn extend_image_to_bounds(image: ImageFrame<Color>, bounds: DAffine2) -> ImageFr
// Copy over original image into enlarged image.
let mut new_img = Image::new(new_scale.x as u32, new_scale.y as u32, Color::TRANSPARENT);
let offset_in_new_image = (-new_start).as_uvec2();
for y in 0..image.image.height {
let old_start = y * image.image.width;
for y in 0..image_instance.image.height {
let old_start = y * image_instance.image.width;
let new_start = (y + offset_in_new_image.y) * new_img.width + offset_in_new_image.x;
let old_row = &image.image.data[old_start as usize..(old_start + image.image.width) as usize];
let new_row = &mut new_img.data[new_start as usize..(new_start + image.image.width) as usize];
let old_row = &image_instance.image.data[old_start as usize..(old_start + image_instance.image.width) as usize];
let new_row = &mut new_img.data[new_start as usize..(new_start + image_instance.image.width) as usize];
new_row.copy_from_slice(old_row);
}
// Compute new transform.
// let layer_to_new_texture_space = (DAffine2::from_scale(1. / new_scale) * DAffine2::from_translation(new_start) * layer_to_image_space).inverse();
let new_texture_to_layer_space = image.transform * DAffine2::from_scale(1. / orig_image_scale) * DAffine2::from_translation(new_start) * DAffine2::from_scale(new_scale);
ImageFrame {
image: new_img,
transform: new_texture_to_layer_space,
alpha_blending: image.alpha_blending,
}
let new_texture_to_layer_space = image.transform() * DAffine2::from_scale(1. / orig_image_scale) * DAffine2::from_translation(new_start) * DAffine2::from_scale(new_scale);
let mut result = ImageFrameTable::new(ImageFrame { image: new_img });
*result.transform_mut() = new_texture_to_layer_space;
*result.one_instance_mut().alpha_blending = *image.one_instance().alpha_blending;
result
}
#[node_macro::node(category("Debug: Raster"))]
fn empty_image<P: Pixel>(_: impl Ctx, transform: DAffine2, #[implementations(Color)] color: P) -> ImageFrame<P> {
fn empty_image(_: impl Ctx, transform: DAffine2, color: Color) -> ImageFrameTable<Color> {
let width = transform.transform_vector2(DVec2::new(1., 0.)).length() as u32;
let height = transform.transform_vector2(DVec2::new(0., 1.)).length() as u32;
let image = Image::new(width, height, color);
ImageFrame {
image,
transform,
alpha_blending: AlphaBlending::default(),
}
let mut result = ImageFrameTable::new(ImageFrame { image });
*result.transform_mut() = transform;
*result.one_instance_mut().alpha_blending = AlphaBlending::default();
result
}
// #[cfg(feature = "serde")]
@@ -510,7 +515,7 @@ fn noise_pattern(
// If the image would not be visible, return an empty image
if size.x <= 0. || size.y <= 0. {
return ImageFrameTable::default();
return ImageFrameTable::empty();
}
let footprint_scale = footprint.scale();
@@ -554,13 +559,11 @@ fn noise_pattern(
}
}
let result = ImageFrame {
image,
transform: DAffine2::from_translation(offset) * DAffine2::from_scale(size),
alpha_blending: AlphaBlending::default(),
};
let mut result = ImageFrameTable::new(ImageFrame { image });
*result.transform_mut() = DAffine2::from_translation(offset) * DAffine2::from_scale(size);
*result.one_instance_mut().alpha_blending = AlphaBlending::default();
return ImageFrameTable::new(result);
return result;
}
};
noise.set_noise_type(Some(noise_type));
@@ -618,13 +621,11 @@ fn noise_pattern(
}
}
let result = ImageFrame {
image,
transform: DAffine2::from_translation(offset) * DAffine2::from_scale(size),
alpha_blending: AlphaBlending::default(),
};
let mut result = ImageFrameTable::new(ImageFrame { image });
*result.transform_mut() = DAffine2::from_translation(offset) * DAffine2::from_scale(size);
*result.one_instance_mut().alpha_blending = AlphaBlending::default();
ImageFrameTable::new(result)
result
}
#[node_macro::node(category("Raster"))]
@@ -640,7 +641,7 @@ fn mandelbrot(ctx: impl ExtractFootprint + Send) -> ImageFrameTable<Color> {
// If the image would not be visible, return an empty image
if size.x <= 0. || size.y <= 0. {
return ImageFrameTable::default();
return ImageFrameTable::empty();
}
let scale = footprint.scale();
@@ -662,18 +663,17 @@ fn mandelbrot(ctx: impl ExtractFootprint + Send) -> ImageFrameTable<Color> {
}
}
let result = ImageFrame {
image: Image {
width,
height,
data,
..Default::default()
},
transform: DAffine2::from_translation(offset) * DAffine2::from_scale(size),
alpha_blending: Default::default(),
let image = Image {
width,
height,
data,
..Default::default()
};
let mut result = ImageFrameTable::new(ImageFrame { image });
*result.transform_mut() = DAffine2::from_translation(offset) * DAffine2::from_scale(size);
*result.one_instance_mut().alpha_blending = Default::default();
ImageFrameTable::new(result)
result
}
#[inline(always)]