mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-17 15:28:04 +08:00
Clean up document message wrappers around proto nodes so they're now used directly (#4101)
* Rename the 'Identity' node to 'Passthrough' internally * Rename the 'Memoize' node to 'Cache' internally * Let skip_impl proto nodes auto-generate as document node definitions * Remove the wrapper 'Passthrough' node from document_node_definitions.rs * Remove the wrapper 'Cache' node from document_node_definitions.rs * Remove the wrapper 'Monitor' node from document_node_definitions.rs * Remove the wrapper 'Noise Pattern' node from document_node_definitions.rs * Remove the wrapper 'Brush' node from document_node_definitions.rs * Remove the wrapper 'Transform' node from document_node_definitions.rs * Code review improvements * Rename Cache node back to Memoize * More code review
This commit is contained in:
@@ -187,30 +187,34 @@ pub fn blend_with_mode(background: TableRow<Raster<CPU>>, foreground: TableRow<R
|
||||
|
||||
/// Generates the brush strokes painted with the Brush tool as a raster image.
|
||||
/// If an input image is supplied, strokes are drawn on top of it, expanding bounds as needed.
|
||||
#[node_macro::node(category(""))]
|
||||
#[node_macro::node(category("Raster"))]
|
||||
async fn brush(
|
||||
_: impl Ctx,
|
||||
/// Optional raster content that may be drawn onto.
|
||||
mut image: Table<Raster<CPU>>,
|
||||
mut background: Table<Raster<CPU>>,
|
||||
/// The list of brush stroke paths drawn by the Brush tool, with each including both its coordinates and styles.
|
||||
strokes: Table<BrushStroke>,
|
||||
trace: Table<BrushStroke>,
|
||||
/// Internal cache data used to accelerate rendering of the brush content.
|
||||
cache: BrushCache,
|
||||
) -> Table<Raster<CPU>> {
|
||||
if image.is_empty() {
|
||||
image.push(TableRow::default());
|
||||
if background.is_empty() {
|
||||
background.push(TableRow::default());
|
||||
}
|
||||
// TODO: Find a way to handle more than one item
|
||||
let table_row = image.clone_row(0).expect("Expected the one item we just pushed");
|
||||
let table_row = background.clone_row(0).expect("Expected the one item we just pushed");
|
||||
|
||||
let bounds = Table::new_from_row(table_row.clone()).bounding_box(DAffine2::IDENTITY, false);
|
||||
let [start, end] = if let RenderBoundingBox::Rectangle(rect) = bounds { rect } else { [DVec2::ZERO, DVec2::ZERO] };
|
||||
let image_bbox = AxisAlignedBbox { start, end };
|
||||
let stroke_bbox = strokes.iter_element_values().map(|s| s.bounding_box()).reduce(|a, b| a.union(&b)).unwrap_or(AxisAlignedBbox::ZERO);
|
||||
let bbox = if image_bbox.size().length() < 0.1 { stroke_bbox } else { stroke_bbox.union(&image_bbox) };
|
||||
let background_bbox = AxisAlignedBbox { start, end };
|
||||
let stroke_bbox = trace.iter_element_values().map(|s| s.bounding_box()).reduce(|a, b| a.union(&b)).unwrap_or(AxisAlignedBbox::ZERO);
|
||||
let bbox = if background_bbox.size().length() < 0.1 {
|
||||
stroke_bbox
|
||||
} else {
|
||||
stroke_bbox.union(&background_bbox)
|
||||
};
|
||||
let background_bounds = bbox.to_transform();
|
||||
|
||||
let mut draw_strokes: Vec<_> = strokes
|
||||
let mut draw_strokes: Vec<_> = trace
|
||||
.iter_element_values()
|
||||
.filter(|&s| !matches!(s.style.blend_mode, BlendMode::Erase | BlendMode::Restore))
|
||||
.cloned()
|
||||
@@ -278,12 +282,12 @@ async fn brush(
|
||||
actual_image = blend_with_mode(actual_image, stroke_texture, stroke.style.blend_mode, (stroke.style.color.a() * 100.) as f64);
|
||||
}
|
||||
|
||||
let has_erase_or_restore_strokes = strokes.iter_element_values().any(|s| matches!(s.style.blend_mode, BlendMode::Erase | BlendMode::Restore));
|
||||
let has_erase_or_restore_strokes = trace.iter_element_values().any(|s| matches!(s.style.blend_mode, BlendMode::Erase | BlendMode::Restore));
|
||||
if has_erase_or_restore_strokes {
|
||||
let opaque_image = Image::new(bbox.size().x as u32, bbox.size().y as u32, Color::WHITE);
|
||||
let mut erase_restore_mask = TableRow::new_from_element(Raster::new_cpu(opaque_image)).with_attribute(ATTR_TRANSFORM, background_bounds);
|
||||
|
||||
for stroke in strokes.into_iter().map(|row| row.into_element()) {
|
||||
for stroke in trace.into_iter().map(|row| row.into_element()) {
|
||||
let mut brush_texture = cache.get_cached_brush(&stroke.style);
|
||||
if brush_texture.is_none() {
|
||||
let tex = create_brush_texture(&stroke.style).await;
|
||||
@@ -320,15 +324,15 @@ async fn brush(
|
||||
let clip: bool = actual_image.attribute_cloned_or_default(ATTR_CLIPPING_MASK);
|
||||
let layer: Table<NodeId> = actual_image.attribute_cloned_or_default(ATTR_EDITOR_LAYER_PATH);
|
||||
|
||||
*image.element_mut(0).unwrap() = actual_image.into_element();
|
||||
image.set_attribute(ATTR_TRANSFORM, 0, transform);
|
||||
image.set_attribute(ATTR_BLEND_MODE, 0, blend_mode);
|
||||
image.set_attribute(ATTR_OPACITY, 0, opacity);
|
||||
image.set_attribute(ATTR_OPACITY_FILL, 0, fill);
|
||||
image.set_attribute(ATTR_CLIPPING_MASK, 0, clip);
|
||||
image.set_attribute(ATTR_EDITOR_LAYER_PATH, 0, layer);
|
||||
*background.element_mut(0).unwrap() = actual_image.into_element();
|
||||
background.set_attribute(ATTR_TRANSFORM, 0, transform);
|
||||
background.set_attribute(ATTR_BLEND_MODE, 0, blend_mode);
|
||||
background.set_attribute(ATTR_OPACITY, 0, opacity);
|
||||
background.set_attribute(ATTR_OPACITY_FILL, 0, fill);
|
||||
background.set_attribute(ATTR_CLIPPING_MASK, 0, clip);
|
||||
background.set_attribute(ATTR_EDITOR_LAYER_PATH, 0, layer);
|
||||
|
||||
image
|
||||
background
|
||||
}
|
||||
|
||||
pub fn blend_image_closure(foreground: TableRow<Raster<CPU>>, mut background: TableRow<Raster<CPU>>, map_fn: impl Fn(Color, Color) -> Color) -> TableRow<Raster<CPU>> {
|
||||
|
||||
@@ -6,15 +6,19 @@ use std::hash::Hasher;
|
||||
use std::sync::Arc;
|
||||
use std::sync::Mutex;
|
||||
|
||||
/// Caches the output of a given node called with a specific input.
|
||||
/// Helps speed up repeated renders in a computationally-heavy part of the node graph.
|
||||
///
|
||||
/// A cache miss occurs when the Option is None. In this case, the node evaluates the inner node and memoizes (stores) the result.
|
||||
///
|
||||
/// A cache hit occurs when the Option is Some and has a stored hash matching the hash of the call argument. In this case, the node returns the cached value without re-evaluating the inner node.
|
||||
///
|
||||
/// Currently, only one input-output pair is cached. Subsequent calls with different inputs will overwrite the previous cache.
|
||||
#[node_macro::node(category(""), path(graphene_core::memo), skip_impl)]
|
||||
async fn memo<I: CacheHash + Send + 'n, T: Clone + WasmNotSend>(input: I, #[data] cache: Arc<Mutex<Option<(u64, T)>>>, node: impl Node<I, Output = T>) -> T {
|
||||
/// Stores the last evaluated data that flowed through this node and immediately returns that data on subsequent renders if the context has not changed.
|
||||
#[node_macro::node(category("General"), path(graphene_core::memo), skip_impl)]
|
||||
async fn memoize<I: CacheHash + Send + 'n, T: Clone + WasmNotSend>(input: I, #[data] cache: Arc<Mutex<Option<(u64, T)>>>, content: impl Node<I, Output = T>) -> T {
|
||||
// Caches the output of a given node called with a specific input.
|
||||
//
|
||||
// A cache miss occurs when the Option is None. In this case, the node evaluates the inner node and memoizes (stores) the result.
|
||||
//
|
||||
// A cache hit occurs when the Option is Some and has a stored hash matching the hash of the call argument. In this case, the node returns the cached value without re-evaluating the inner node.
|
||||
//
|
||||
// Currently, only one input-output pair is cached. Subsequent calls with different inputs will overwrite the previous cache.
|
||||
|
||||
let mut hasher = DefaultHasher::new();
|
||||
input.cache_hash(&mut hasher);
|
||||
let hash = hasher.finish();
|
||||
@@ -23,23 +27,23 @@ async fn memo<I: CacheHash + Send + 'n, T: Clone + WasmNotSend>(input: I, #[data
|
||||
return data;
|
||||
}
|
||||
|
||||
let value = node.eval(input).await;
|
||||
let value = content.eval(input).await;
|
||||
*cache.lock().unwrap() = Some((hash, value.clone()));
|
||||
value
|
||||
}
|
||||
|
||||
type MonitorValue<I, T> = Arc<Mutex<Option<Arc<IORecord<I, T>>>>>;
|
||||
|
||||
/// Caches the output of the last graph evaluation for introspection.
|
||||
#[node_macro::node(category(""), path(graphene_core::memo), serialize(serialize_monitor), skip_impl)]
|
||||
/// The Monitor node is used by the editor to access the data flowing through it.
|
||||
#[node_macro::node(category(""), path(graphene_core::memo), serialize(serialize_monitor), properties("monitor_properties"), skip_impl)]
|
||||
async fn monitor<I: Clone + 'static + Send + Sync, T: Clone + 'static + Send + Sync>(
|
||||
input: I,
|
||||
#[allow(clippy::type_complexity)]
|
||||
#[data]
|
||||
io: MonitorValue<I, T>,
|
||||
node: impl Node<I, Output = T>,
|
||||
content: impl Node<I, Output = T>,
|
||||
) -> T {
|
||||
let output = node.eval(input.clone()).await;
|
||||
let output = content.eval(input.clone()).await;
|
||||
*io.lock().unwrap() = Some(Arc::new(IORecord { input, output: output.clone() }));
|
||||
output
|
||||
}
|
||||
|
||||
@@ -4,12 +4,10 @@ use std::marker::PhantomData;
|
||||
// Re-export TypeNode from core-types for convenience
|
||||
pub use core_types::ops::TypeNode;
|
||||
|
||||
// TODO: Rename to "Passthrough" and make this the node that users use, not the one defined in document_node_definitions.rs
|
||||
/// Passes-through the input value without changing it.
|
||||
/// This is useful for rerouting wires for organization purposes.
|
||||
#[node_macro::node(category(""), skip_impl)]
|
||||
fn identity<'i, T: 'i + Send>(value: T) -> T {
|
||||
value
|
||||
/// Passes-through the input value without changing it. This is useful for rerouting wires for organization purposes.
|
||||
#[node_macro::node(category("General"), skip_impl)]
|
||||
fn passthrough<'i, T: 'i + Send>(_: impl Ctx, content: T) -> T {
|
||||
content
|
||||
}
|
||||
|
||||
#[node_macro::node(category(""), skip_impl)]
|
||||
@@ -27,7 +25,7 @@ mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
pub fn identity_node() {
|
||||
assert_eq!(identity(&4), &4);
|
||||
pub fn passthrough_node() {
|
||||
assert_eq!(passthrough((), &4), &4);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ const TX: f32 = 0.1;
|
||||
// Paper: <https://www.researchgate.net/publication/220182411_Single_Image_Haze_Removal_Using_Dark_Channel_Prior>
|
||||
// TODO: Make this algorithm work with negative strength values
|
||||
fn dehaze_image(image: DynamicImage, strength: f64) -> DynamicImage {
|
||||
// TODO: Break out this pair of steps into its own node, with a memoize node which caches the pair of outputs, so the strength can be adjusted without recomputing these two steps.
|
||||
// TODO: Break out this pair of steps into its own node, with a Memoize node which caches the pair of outputs, so the strength can be adjusted without recomputing these two steps.
|
||||
let dark_channel = compute_dark_channel(&image);
|
||||
let atmospheric_light = estimate_atmospheric_light(&image, &dark_channel);
|
||||
|
||||
|
||||
@@ -293,25 +293,40 @@ pub fn image(_: impl Ctx, _primary: (), image: Image<Color>) -> Table<Raster<CPU
|
||||
Table::new_from_element(Raster::new_cpu(image))
|
||||
}
|
||||
|
||||
/// Generates customizable procedural noise patterns.
|
||||
#[node_macro::node(category("Raster: Pattern"))]
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn noise_pattern(
|
||||
ctx: impl ExtractFootprint + Ctx,
|
||||
_primary: (),
|
||||
clip: bool,
|
||||
#[default(true)] clip: bool,
|
||||
seed: u32,
|
||||
#[widget(ParsedWidgetOverride::Custom = "noise_properties_scale")]
|
||||
#[default(10.)]
|
||||
scale: f64,
|
||||
noise_type: NoiseType,
|
||||
domain_warp_type: DomainWarpType,
|
||||
#[widget(ParsedWidgetOverride::Custom = "noise_properties_noise_type")] noise_type: NoiseType,
|
||||
#[widget(ParsedWidgetOverride::Custom = "noise_properties_domain_warp_type")] domain_warp_type: DomainWarpType,
|
||||
#[widget(ParsedWidgetOverride::Custom = "noise_properties_domain_warp_amplitude")]
|
||||
#[default(100.)]
|
||||
domain_warp_amplitude: f64,
|
||||
fractal_type: FractalType,
|
||||
#[widget(ParsedWidgetOverride::Custom = "noise_properties_fractal_type")] fractal_type: FractalType,
|
||||
#[widget(ParsedWidgetOverride::Custom = "noise_properties_fractal_octaves")]
|
||||
#[default(3)]
|
||||
fractal_octaves: u32,
|
||||
#[widget(ParsedWidgetOverride::Custom = "noise_properties_fractal_lacunarity")]
|
||||
#[default(2.)]
|
||||
fractal_lacunarity: f64,
|
||||
#[widget(ParsedWidgetOverride::Custom = "noise_properties_fractal_gain")]
|
||||
#[default(0.5)]
|
||||
fractal_gain: f64,
|
||||
fractal_weighted_strength: f64,
|
||||
#[widget(ParsedWidgetOverride::Custom = "noise_properties_fractal_weighted_strength")] fractal_weighted_strength: f64,
|
||||
#[widget(ParsedWidgetOverride::Custom = "noise_properties_ping_pong_strength")]
|
||||
#[default(2.)]
|
||||
fractal_ping_pong_strength: f64,
|
||||
cellular_distance_function: CellularDistanceFunction,
|
||||
cellular_return_type: CellularReturnType,
|
||||
#[widget(ParsedWidgetOverride::Custom = "noise_properties_cellular_distance_function")] cellular_distance_function: CellularDistanceFunction,
|
||||
#[widget(ParsedWidgetOverride::Custom = "noise_properties_cellular_return_type")] cellular_return_type: CellularReturnType,
|
||||
#[widget(ParsedWidgetOverride::Custom = "noise_properties_cellular_jitter")]
|
||||
#[default(1.)]
|
||||
cellular_jitter: f64,
|
||||
) -> Table<Raster<CPU>> {
|
||||
let footprint = ctx.footprint();
|
||||
|
||||
@@ -10,7 +10,7 @@ use graphic_types::raster_types::{CPU, GPU, Raster};
|
||||
use vector_types::GradientStops;
|
||||
|
||||
/// Applies the specified transform to the input value, which may be a graphic type or another transform.
|
||||
#[node_macro::node(category(""))]
|
||||
#[node_macro::node(category("Math: Transform"))]
|
||||
async fn transform<T: ApplyTransform + 'n + 'static>(
|
||||
ctx: impl Ctx + CloneVarArgs + ExtractAll + ModifyFootprint,
|
||||
#[implementations(
|
||||
@@ -24,10 +24,12 @@ async fn transform<T: ApplyTransform + 'n + 'static>(
|
||||
Context -> Table<GradientStops>,
|
||||
)]
|
||||
content: impl Node<Context<'static>, Output = T>,
|
||||
translation: DVec2,
|
||||
rotation: f64,
|
||||
#[widget(ParsedWidgetOverride::Custom = "transform_translation")] translation: DVec2,
|
||||
#[widget(ParsedWidgetOverride::Custom = "transform_rotation")] rotation: f64,
|
||||
#[widget(ParsedWidgetOverride::Custom = "transform_scale")]
|
||||
#[default(1., 1.)]
|
||||
scale: DVec2,
|
||||
skew: DVec2,
|
||||
#[widget(ParsedWidgetOverride::Custom = "transform_skew")] skew: DVec2,
|
||||
) -> T {
|
||||
let trs = DAffine2::from_scale_angle_translation(scale, rotation.to_radians(), translation);
|
||||
let skew = DAffine2::from_cols_array(&[1., skew.y.to_radians().tan(), skew.x.to_radians().tan(), 1., 0., 0.]);
|
||||
|
||||
Reference in New Issue
Block a user