Instance tables refactor part 1: wrap graphical data in the new Instances<T> struct (#2230)

* Port VectorData to Instances<VectorData>

* Port ImageFrame<P> and TextureFrame to Instances<ImageFrame<P>> and Instances<TextureFrame>

* Avoid mutation with the TransformMut trait

* Port GraphicGroup to Instances<GraphicGroup>

* It compiles!

* Organize debugging

* Document upgrading

* Fix Brush node

* Restore TransformMut in lieu of TransformSet trait

* Fix tests

* Final code review
This commit is contained in:
Keavon Chambers
2025-01-28 23:51:12 -08:00
committed by GitHub
parent 408f9bffa1
commit eb0ff20d3c
43 changed files with 1855 additions and 1221 deletions

View File

@@ -1,12 +1,8 @@
use crate::wasm_application_io::WasmEditorApi;
use dyn_any::DynAny;
use graph_craft::imaginate_input::{ImaginateController, ImaginateMaskStartingFill, ImaginateSamplingMethod};
use graph_craft::proto::DynFuture;
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, ImageFrame, Linear, LinearChannel, Luminance, NoiseType, Pixel, RGBMut, RedGreenBlue,
Sample,
Alpha, 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};
@@ -15,7 +11,6 @@ use fastnoise_lite;
use glam::{DAffine2, DVec2, Vec2};
use rand::prelude::*;
use rand_chacha::ChaCha8Rng;
use std::collections::HashMap;
use std::fmt::Debug;
use std::hash::Hash;
use std::marker::PhantomData;
@@ -33,10 +28,12 @@ impl From<std::io::Error> for Error {
}
#[node_macro::node(category("Debug: Raster"))]
fn sample_image(footprint: Footprint, image_frame: ImageFrame<Color>) -> ImageFrame<Color> {
fn sample_image(footprint: Footprint, 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);
let image = &image_frame.image;
let data = bytemuck::cast_vec(image.data.clone());
let viewport_bounds = footprint.viewport_bounds_in_local_space();
let image_bounds = Bbox::from_transform(image_frame.transform).to_axis_aligned_bbox();
@@ -47,10 +44,10 @@ fn sample_image(footprint: Footprint, image_frame: ImageFrame<Color>) -> ImageFr
// If the image would not be visible, return an empty image
if size.x <= 0. || size.y <= 0. {
return ImageFrame::empty();
return ImageFrameTable::default();
}
let image_buffer = image::Rgba32FImage::from_raw(image.width, image.height, data).expect("Failed to convert internal ImageFrame into image-rs data type.");
let image_buffer = image::Rgba32FImage::from_raw(image.width, image.height, data).expect("Failed to convert internal image format into image-rs data type.");
let dynamic_image: image::DynamicImage = image_buffer.into();
let offset = (intersection.start - image_bounds.start).max(DVec2::ZERO);
@@ -83,11 +80,14 @@ fn sample_image(footprint: Footprint, image_frame: ImageFrame<Color>) -> ImageFr
// 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 {
let result = ImageFrame {
image,
transform: new_transform,
alpha_blending: image_frame.alpha_blending,
}
};
ImageFrameTable::new(result)
}
#[derive(Debug, Clone, Copy)]
@@ -244,6 +244,7 @@ where
MapFn: Fn(_P, _P) -> _P,
{
let background_size = DVec2::new(background.width() as f64, background.height() as f64);
// Transforms a point from the background image to the foreground image
let bg_to_fg = background.transform() * DAffine2::from_scale(1. / background_size);
@@ -331,104 +332,104 @@ fn empty_image<P: Pixel>(_: (), transform: DAffine2, #[implementations(Color)] c
}
}
#[cfg(feature = "serde")]
macro_rules! generate_imaginate_node {
($($val:ident: $t:ident: $o:ty,)*) => {
pub struct ImaginateNode<P: Pixel, E, C, G, $($t,)*> {
editor_api: E,
controller: C,
generation_id: G,
$($val: $t,)*
cache: std::sync::Arc<std::sync::Mutex<HashMap<u64, Image<P>>>>,
last_generation: std::sync::atomic::AtomicU64,
}
// #[cfg(feature = "serde")]
// macro_rules! generate_imaginate_node {
// ($($val:ident: $t:ident: $o:ty,)*) => {
// pub struct ImaginateNode<P: Pixel, E, C, G, $($t,)*> {
// editor_api: E,
// controller: C,
// generation_id: G,
// $($val: $t,)*
// cache: std::sync::Arc<std::sync::Mutex<HashMap<u64, Image<P>>>>,
// last_generation: std::sync::atomic::AtomicU64,
// }
impl<'e, P: Pixel, E, C, G, $($t,)*> ImaginateNode<P, E, C, G, $($t,)*>
where $($t: for<'any_input> Node<'any_input, (), Output = DynFuture<'any_input, $o>>,)*
E: for<'any_input> Node<'any_input, (), Output = DynFuture<'any_input, &'e WasmEditorApi>>,
C: for<'any_input> Node<'any_input, (), Output = DynFuture<'any_input, ImaginateController>>,
G: for<'any_input> Node<'any_input, (), Output = DynFuture<'any_input, u64>>,
{
#[allow(clippy::too_many_arguments)]
pub fn new(editor_api: E, controller: C, $($val: $t,)* generation_id: G ) -> Self {
Self { editor_api, controller, generation_id, $($val,)* cache: Default::default(), last_generation: std::sync::atomic::AtomicU64::new(u64::MAX) }
}
}
// impl<'e, P: Pixel, E, C, G, $($t,)*> ImaginateNode<P, E, C, G, $($t,)*>
// where $($t: for<'any_input> Node<'any_input, (), Output = DynFuture<'any_input, $o>>,)*
// E: for<'any_input> Node<'any_input, (), Output = DynFuture<'any_input, &'e WasmEditorApi>>,
// C: for<'any_input> Node<'any_input, (), Output = DynFuture<'any_input, ImaginateController>>,
// G: for<'any_input> Node<'any_input, (), Output = DynFuture<'any_input, u64>>,
// {
// #[allow(clippy::too_many_arguments)]
// pub fn new(editor_api: E, controller: C, $($val: $t,)* generation_id: G ) -> Self {
// Self { editor_api, controller, generation_id, $($val,)* cache: Default::default(), last_generation: std::sync::atomic::AtomicU64::new(u64::MAX) }
// }
// }
impl<'i, 'e: 'i, P: Pixel + 'i + Hash + Default + Send, E: 'i, C: 'i, G: 'i, $($t: 'i,)*> Node<'i, ImageFrame<P>> for ImaginateNode<P, E, C, G, $($t,)*>
where $($t: for<'any_input> Node<'any_input, (), Output = DynFuture<'any_input, $o>>,)*
E: for<'any_input> Node<'any_input, (), Output = DynFuture<'any_input, &'e WasmEditorApi>>,
C: for<'any_input> Node<'any_input, (), Output = DynFuture<'any_input, ImaginateController>>,
G: for<'any_input> Node<'any_input, (), Output = DynFuture<'any_input, u64>>,
{
type Output = DynFuture<'i, ImageFrame<P>>;
// impl<'i, 'e: 'i, P: Pixel + 'i + Hash + Default + Send, E: 'i, C: 'i, G: 'i, $($t: 'i,)*> Node<'i, ImageFrame<P>> for ImaginateNode<P, E, C, G, $($t,)*>
// where $($t: for<'any_input> Node<'any_input, (), Output = DynFuture<'any_input, $o>>,)*
// E: for<'any_input> Node<'any_input, (), Output = DynFuture<'any_input, &'e WasmEditorApi>>,
// C: for<'any_input> Node<'any_input, (), Output = DynFuture<'any_input, ImaginateController>>,
// G: for<'any_input> Node<'any_input, (), Output = DynFuture<'any_input, u64>>,
// {
// type Output = DynFuture<'i, ImageFrame<P>>;
fn eval(&'i self, frame: ImageFrame<P>) -> Self::Output {
let controller = self.controller.eval(());
$(let $val = self.$val.eval(());)*
// fn eval(&'i self, frame: ImageFrame<P>) -> Self::Output {
// let controller = self.controller.eval(());
// $(let $val = self.$val.eval(());)*
use std::hash::Hasher;
let mut hasher = rustc_hash::FxHasher::default();
frame.image.hash(&mut hasher);
let hash = hasher.finish();
let editor_api = self.editor_api.eval(());
let cache = self.cache.clone();
let generation_future = self.generation_id.eval(());
let last_generation = &self.last_generation;
// use std::hash::Hasher;
// let mut hasher = rustc_hash::FxHasher::default();
// frame.image.hash(&mut hasher);
// let hash = hasher.finish();
// let editor_api = self.editor_api.eval(());
// let cache = self.cache.clone();
// let generation_future = self.generation_id.eval(());
// let last_generation = &self.last_generation;
Box::pin(async move {
let controller: ImaginateController = controller.await;
let generation_id = generation_future.await;
if generation_id != last_generation.swap(generation_id, std::sync::atomic::Ordering::SeqCst) {
let image = super::imaginate::imaginate(frame.image, editor_api, controller, $($val,)*).await;
// Box::pin(async move {
// let controller: ImaginateController = controller.await;
// let generation_id = generation_future.await;
// if generation_id != last_generation.swap(generation_id, std::sync::atomic::Ordering::SeqCst) {
// let image = super::imaginate::imaginate(frame.image, editor_api, controller, $($val,)*).await;
cache.lock().unwrap().insert(hash, image.clone());
// cache.lock().unwrap().insert(hash, image.clone());
return wrap_image_frame(image, frame.transform);
}
let image = cache.lock().unwrap().get(&hash).cloned().unwrap_or_default();
// return wrap_image_frame(image, frame.transform);
// }
// let image = cache.lock().unwrap().get(&hash).cloned().unwrap_or_default();
return wrap_image_frame(image, frame.transform);
})
}
}
}
}
// return wrap_image_frame(image, frame.transform);
// })
// }
// }
// }
// }
fn wrap_image_frame<P: Pixel>(image: Image<P>, transform: DAffine2) -> ImageFrame<P> {
if !transform.decompose_scale().abs_diff_eq(DVec2::ZERO, 0.00001) {
ImageFrame {
image,
transform,
alpha_blending: AlphaBlending::default(),
}
} else {
let resolution = DVec2::new(image.height as f64, image.width as f64);
ImageFrame {
image,
transform: DAffine2::from_scale_angle_translation(resolution, 0., transform.translation),
alpha_blending: AlphaBlending::default(),
}
}
}
// fn wrap_image_frame<P: Pixel>(image: Image<P>, transform: DAffine2) -> ImageFrame<P> {
// if !transform.decompose_scale().abs_diff_eq(DVec2::ZERO, 0.00001) {
// ImageFrame {
// image,
// transform,
// alpha_blending: AlphaBlending::default(),
// }
// } else {
// let resolution = DVec2::new(image.height as f64, image.width as f64);
// ImageFrame {
// image,
// transform: DAffine2::from_scale_angle_translation(resolution, 0., transform.translation),
// alpha_blending: AlphaBlending::default(),
// }
// }
// }
#[cfg(feature = "serde")]
generate_imaginate_node! {
seed: Seed: f64,
res: Res: Option<DVec2>,
samples: Samples: u32,
sampling_method: SamplingMethod: ImaginateSamplingMethod,
prompt_guidance: PromptGuidance: f64,
prompt: Prompt: String,
negative_prompt: NegativePrompt: String,
adapt_input_image: AdaptInputImage: bool,
image_creativity: ImageCreativity: f64,
inpaint: Inpaint: bool,
mask_blur: MaskBlur: f64,
mask_starting_fill: MaskStartingFill: ImaginateMaskStartingFill,
improve_faces: ImproveFaces: bool,
tiling: Tiling: bool,
}
// #[cfg(feature = "serde")]
// generate_imaginate_node! {
// seed: Seed: f64,
// res: Res: Option<DVec2>,
// samples: Samples: u32,
// sampling_method: SamplingMethod: ImaginateSamplingMethod,
// prompt_guidance: PromptGuidance: f64,
// prompt: Prompt: String,
// negative_prompt: NegativePrompt: String,
// adapt_input_image: AdaptInputImage: bool,
// image_creativity: ImageCreativity: f64,
// inpaint: Inpaint: bool,
// mask_blur: MaskBlur: f64,
// mask_starting_fill: MaskStartingFill: ImaginateMaskStartingFill,
// improve_faces: ImproveFaces: bool,
// tiling: Tiling: bool,
// }
#[node_macro::node(category("Raster: Generator"))]
#[allow(clippy::too_many_arguments)]
@@ -450,7 +451,7 @@ fn noise_pattern(
cellular_distance_function: CellularDistanceFunction,
cellular_return_type: CellularReturnType,
cellular_jitter: f64,
) -> ImageFrame<Color> {
) -> ImageFrameTable<Color> {
let viewport_bounds = footprint.viewport_bounds_in_local_space();
let mut size = viewport_bounds.size();
@@ -467,7 +468,7 @@ fn noise_pattern(
// If the image would not be visible, return an empty image
if size.x <= 0. || size.y <= 0. {
return ImageFrame::empty();
return ImageFrameTable::default();
}
let footprint_scale = footprint.scale();
@@ -511,11 +512,13 @@ fn noise_pattern(
}
}
return ImageFrame::<Color> {
let result = ImageFrame {
image,
transform: DAffine2::from_translation(offset) * DAffine2::from_scale(size),
alpha_blending: AlphaBlending::default(),
};
return ImageFrameTable::new(result);
}
};
noise.set_noise_type(Some(noise_type));
@@ -573,16 +576,17 @@ fn noise_pattern(
}
}
// Return the coherent noise image
ImageFrame::<Color> {
let result = ImageFrame {
image,
transform: DAffine2::from_translation(offset) * DAffine2::from_scale(size),
alpha_blending: AlphaBlending::default(),
}
};
ImageFrameTable::new(result)
}
#[node_macro::node(category("Raster: Generator"))]
fn mandelbrot(footprint: Footprint) -> ImageFrame<Color> {
fn mandelbrot(footprint: Footprint) -> ImageFrameTable<Color> {
let viewport_bounds = footprint.viewport_bounds_in_local_space();
let image_bounds = Bbox::from_transform(DAffine2::IDENTITY).to_axis_aligned_bbox();
@@ -593,7 +597,7 @@ fn mandelbrot(footprint: Footprint) -> ImageFrame<Color> {
// If the image would not be visible, return an empty image
if size.x <= 0. || size.y <= 0. {
return ImageFrame::empty();
return ImageFrameTable::default();
}
let scale = footprint.scale();
@@ -614,7 +618,8 @@ fn mandelbrot(footprint: Footprint) -> ImageFrame<Color> {
data.push(map_color(iter, max_iter));
}
}
ImageFrame {
let result = ImageFrame {
image: Image {
width,
height,
@@ -623,7 +628,9 @@ fn mandelbrot(footprint: Footprint) -> ImageFrame<Color> {
},
transform: DAffine2::from_translation(offset) * DAffine2::from_scale(size),
..Default::default()
}
};
ImageFrameTable::new(result)
}
#[inline(always)]