diff --git a/node-graph/nodes/raster/src/blending_nodes.rs b/node-graph/nodes/raster/src/blending_nodes.rs index f4f9a7845c..fe3b8c533e 100644 --- a/node-graph/nodes/raster/src/blending_nodes.rs +++ b/node-graph/nodes/raster/src/blending_nodes.rs @@ -1,6 +1,4 @@ use crate::adjust::Adjust; -#[cfg(feature = "std")] -use core_types::list::List; use no_std_types::Ctx; use no_std_types::blending::BlendMode; use no_std_types::color::{Color, Pixel}; @@ -179,12 +177,12 @@ fn mix + Clone + Send + Sync + core_types::CacheHash + 'static>( } #[node_macro::node(category("Raster: Adjustment"), shader_node(PerPixelAdjust))] -fn color_overlay>( +fn color_overlay + Clone + Send + Sync + no_std_types::context::CacheHash + 'static>( _: impl Ctx, #[implementations( - List>, - List, - List, + Raster, + Color, + GradientStops, )] #[gpu_image] mut image: T, diff --git a/node-graph/nodes/raster/src/filter.rs b/node-graph/nodes/raster/src/filter.rs index cfa29435d0..34e9069663 100644 --- a/node-graph/nodes/raster/src/filter.rs +++ b/node-graph/nodes/raster/src/filter.rs @@ -1,7 +1,6 @@ use bytemuck::{Pod, Zeroable}; use core_types::color::{Alpha, Color, Pixel, RGB}; use core_types::context::Ctx; -use core_types::list::List; use core_types::registry::types::PixelLength; use raster_types::Image; use raster_types::{Bitmap, BitmapMut}; @@ -90,7 +89,7 @@ fn unpremultiply_gamma_to_linear(buffer: Image) -> Imag fn blur( _: impl Ctx, /// The image to be blurred. - image_frame: List>, + image_frame: Raster, /// The radius of the blur kernel. #[range] #[hard(0..)] @@ -100,26 +99,16 @@ fn blur( box_blur: bool, /// Opt to incorrectly apply the filter with color calculations in gamma space for compatibility with the results from other software. gamma: bool, -) -> List> { - image_frame - .into_iter() - .map(|mut row| { - let image = row.element().clone(); - - // Run blur algorithm - let blurred_image = if radius < 0.1 { - // Minimum blur radius - image.clone() - } else if box_blur { - Raster::new_cpu(box_blur_algorithm(image.into_data(), radius, gamma)) - } else { - Raster::new_cpu(gaussian_blur_algorithm(image.into_data(), radius, gamma)) - }; - - *row.element_mut() = blurred_image; - row - }) - .collect() +) -> Raster { + // Run blur algorithm + if radius < 0.1 { + // Minimum blur radius + image_frame + } else if box_blur { + Raster::new_cpu(box_blur_algorithm(image_frame.into_data(), radius, gamma)) + } else { + Raster::new_cpu(gaussian_blur_algorithm(image_frame.into_data(), radius, gamma)) + } } /// Applies a median filter to reduce noise while preserving edges. @@ -127,30 +116,20 @@ fn blur( fn median_filter( _: impl Ctx, /// The image to be filtered. - image_frame: List>, + image_frame: Raster, /// The radius of the filter kernel. Larger values remove more noise but may blur fine details. #[range] #[hard(0..)] #[soft(..50)] radius: PixelLength, -) -> List> { - image_frame - .into_iter() - .map(|mut row| { - let image = row.element().clone(); - - // Apply median filter - let filtered_image = if radius < 0.5 { - // Minimum filter radius - image.clone() - } else { - Raster::new_cpu(median_filter_algorithm(image.into_data(), radius as u32)) - }; - - *row.element_mut() = filtered_image; - row - }) - .collect() +) -> Raster { + // Apply median filter + if radius < 0.5 { + // Minimum filter radius + image_frame + } else { + Raster::new_cpu(median_filter_algorithm(image_frame.into_data(), radius as u32)) + } } // 1D gaussian kernel diff --git a/node-graph/nodes/raster/src/image_color_palette.rs b/node-graph/nodes/raster/src/image_color_palette.rs index f881bb77ed..3726f9a57c 100644 --- a/node-graph/nodes/raster/src/image_color_palette.rs +++ b/node-graph/nodes/raster/src/image_color_palette.rs @@ -1,16 +1,16 @@ use core_types::color::Color; -use core_types::context::Ctx; -use core_types::list::{Item, List}; +use core_types::context::{Ctx, ExtractIndex, InjectIndex}; +use core_types::gpoll::{GraphError, Interrupt}; use raster_types::{CPU, Raster}; #[node_macro::node(category("Color"))] fn image_color_palette( - _: impl Ctx, - image: &List>, + ctx: impl Ctx + ExtractIndex + InjectIndex + Copy, + image: IList>, #[default(4)] #[hard(1..)] count: u32, -) -> List { +) -> Result, Interrupt> { const GRID: f32 = 3.; let bins = GRID * GRID * GRID; @@ -19,7 +19,8 @@ fn image_color_palette( // Each bin stores `(red, green, blue, alpha)` tuples in sRGB gamma space; averaging in gamma space gives perceptually-uniform binning. let mut color_bins: Vec> = vec![Vec::new(); (bins + 1.) as usize]; - for element in image.iter_element_values() { + for row in 0..image.len() { + let element = image.element_ref(row); for pixel in element.data.iter() { let r = pixel.r() * GRID; let g = pixel.g() * GRID; @@ -34,7 +35,7 @@ fn image_color_palette( let shorted = histogram.iter().enumerate().filter(|&(_, &count)| count > 0).map(|(i, _)| i).collect::>(); - shorted + let palette: Vec = shorted .iter() .take(count as usize) .flat_map(|&i| { @@ -54,9 +55,11 @@ fn image_color_palette( // Reject NaN/out-of-range averages, then lift the gamma-space bin centroid to linear-light let in_gamut = a <= 1. && ![r, g, b, a].iter().any(|c| c.is_sign_negative() || !c.is_finite()); - in_gamut.then(|| Color::from_gamma_srgb_channels(r, g, b, a)).map(Item::new_from_element).into_iter() + in_gamut.then(|| Color::from_gamma_srgb_channels(r, g, b, a)).into_iter() }) - .collect() + .collect(); + + palette.get(ctx.innermost_index() as usize).copied().ok_or_else(|| GraphError::past_end().into()) } #[cfg(test)] @@ -67,16 +70,26 @@ mod test { #[test] fn test_image_color_palette() { - let result = image_color_palette( - &(), - &List::new_from_element(Raster::new_cpu(Image { - width: 100, - height: 100, - data: vec![Color::from_rgbaf32(0., 0., 0., 1.).unwrap(); 10000], - base64_string: None, - })), - 1, - ); - assert_eq!(result, List::new_from_element(Color::from_rgbaf32(0., 0., 0., 1.).unwrap())); + core_types::record::stack::reserve(1 << 16); + let arena = core_types::arena::Arena::new(1 << 22).unwrap(); + let generations = []; + let scope = core_types::context::EvalScope::new(None, None, None, &generations, &arena); + let ctx = core_types::context::ContextImpl::root(&scope); + + let raster = Raster::new_cpu(Image { + width: 100, + height: 100, + data: vec![Color::from_rgbaf32(0., 0., 0., 1.).unwrap(); 10000], + base64_string: None, + }); + let source = core_types::value::LeveledValueSource::new(vec![raster]); + let core_types::record::LevelStatus::Batch(batch, _) = core_types::record::materialize_level(&source, &ctx, &arena) else { + panic!("materialize failed") + }; + let image = unsafe { core_types::node::List::>::new(batch) }; + + // The root context addresses lane 0, the palette's first color + let color = image_color_palette(&ctx, image, 1).unwrap(); + assert_eq!(color, Color::from_rgbaf32(0., 0., 0., 1.).unwrap()); } }