mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-15 22:28:10 +08:00
Convert blur, median_filter, color_overlay, and image_color_palette to record kernels
This commit is contained in:
@@ -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<T: Blend<Color> + Clone + Send + Sync + core_types::CacheHash + 'static>(
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Raster: Adjustment"), shader_node(PerPixelAdjust))]
|
||||
fn color_overlay<T: Adjust<Color>>(
|
||||
fn color_overlay<T: Adjust<Color> + Clone + Send + Sync + no_std_types::context::CacheHash + 'static>(
|
||||
_: impl Ctx,
|
||||
#[implementations(
|
||||
List<Raster<CPU>>,
|
||||
List<Color>,
|
||||
List<GradientStops>,
|
||||
Raster<CPU>,
|
||||
Color,
|
||||
GradientStops,
|
||||
)]
|
||||
#[gpu_image]
|
||||
mut image: T,
|
||||
|
||||
@@ -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<PremultipliedGammaPixel>) -> Imag
|
||||
fn blur(
|
||||
_: impl Ctx,
|
||||
/// The image to be blurred.
|
||||
image_frame: List<Raster<CPU>>,
|
||||
image_frame: Raster<CPU>,
|
||||
/// 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<Raster<CPU>> {
|
||||
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<CPU> {
|
||||
// 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<Raster<CPU>>,
|
||||
image_frame: Raster<CPU>,
|
||||
/// The radius of the filter kernel. Larger values remove more noise but may blur fine details.
|
||||
#[range]
|
||||
#[hard(0..)]
|
||||
#[soft(..50)]
|
||||
radius: PixelLength,
|
||||
) -> List<Raster<CPU>> {
|
||||
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<CPU> {
|
||||
// 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
|
||||
|
||||
@@ -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<Raster<CPU>>,
|
||||
ctx: impl Ctx + ExtractIndex + InjectIndex + Copy,
|
||||
image: IList<Raster<CPU>>,
|
||||
#[default(4)]
|
||||
#[hard(1..)]
|
||||
count: u32,
|
||||
) -> List<Color> {
|
||||
) -> Result<IList<Color>, 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<[f32; 4]>> = 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::<Vec<usize>>();
|
||||
|
||||
shorted
|
||||
let palette: Vec<Color> = 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::<Raster<CPU>>::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());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user