mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-10-01 12:18:12 +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;
|
use crate::adjust::Adjust;
|
||||||
#[cfg(feature = "std")]
|
|
||||||
use core_types::list::List;
|
|
||||||
use no_std_types::Ctx;
|
use no_std_types::Ctx;
|
||||||
use no_std_types::blending::BlendMode;
|
use no_std_types::blending::BlendMode;
|
||||||
use no_std_types::color::{Color, Pixel};
|
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))]
|
#[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,
|
_: impl Ctx,
|
||||||
#[implementations(
|
#[implementations(
|
||||||
List<Raster<CPU>>,
|
Raster<CPU>,
|
||||||
List<Color>,
|
Color,
|
||||||
List<GradientStops>,
|
GradientStops,
|
||||||
)]
|
)]
|
||||||
#[gpu_image]
|
#[gpu_image]
|
||||||
mut image: T,
|
mut image: T,
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
use bytemuck::{Pod, Zeroable};
|
use bytemuck::{Pod, Zeroable};
|
||||||
use core_types::color::{Alpha, Color, Pixel, RGB};
|
use core_types::color::{Alpha, Color, Pixel, RGB};
|
||||||
use core_types::context::Ctx;
|
use core_types::context::Ctx;
|
||||||
use core_types::list::List;
|
|
||||||
use core_types::registry::types::PixelLength;
|
use core_types::registry::types::PixelLength;
|
||||||
use raster_types::Image;
|
use raster_types::Image;
|
||||||
use raster_types::{Bitmap, BitmapMut};
|
use raster_types::{Bitmap, BitmapMut};
|
||||||
@@ -90,7 +89,7 @@ fn unpremultiply_gamma_to_linear(buffer: Image<PremultipliedGammaPixel>) -> Imag
|
|||||||
fn blur(
|
fn blur(
|
||||||
_: impl Ctx,
|
_: impl Ctx,
|
||||||
/// The image to be blurred.
|
/// The image to be blurred.
|
||||||
image_frame: List<Raster<CPU>>,
|
image_frame: Raster<CPU>,
|
||||||
/// The radius of the blur kernel.
|
/// The radius of the blur kernel.
|
||||||
#[range]
|
#[range]
|
||||||
#[hard(0..)]
|
#[hard(0..)]
|
||||||
@@ -100,26 +99,16 @@ fn blur(
|
|||||||
box_blur: bool,
|
box_blur: bool,
|
||||||
/// Opt to incorrectly apply the filter with color calculations in gamma space for compatibility with the results from other software.
|
/// Opt to incorrectly apply the filter with color calculations in gamma space for compatibility with the results from other software.
|
||||||
gamma: bool,
|
gamma: bool,
|
||||||
) -> List<Raster<CPU>> {
|
) -> Raster<CPU> {
|
||||||
image_frame
|
// Run blur algorithm
|
||||||
.into_iter()
|
if radius < 0.1 {
|
||||||
.map(|mut row| {
|
// Minimum blur radius
|
||||||
let image = row.element().clone();
|
image_frame
|
||||||
|
} else if box_blur {
|
||||||
// Run blur algorithm
|
Raster::new_cpu(box_blur_algorithm(image_frame.into_data(), radius, gamma))
|
||||||
let blurred_image = if radius < 0.1 {
|
} else {
|
||||||
// Minimum blur radius
|
Raster::new_cpu(gaussian_blur_algorithm(image_frame.into_data(), radius, gamma))
|
||||||
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()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Applies a median filter to reduce noise while preserving edges.
|
/// Applies a median filter to reduce noise while preserving edges.
|
||||||
@@ -127,30 +116,20 @@ fn blur(
|
|||||||
fn median_filter(
|
fn median_filter(
|
||||||
_: impl Ctx,
|
_: impl Ctx,
|
||||||
/// The image to be filtered.
|
/// 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.
|
/// The radius of the filter kernel. Larger values remove more noise but may blur fine details.
|
||||||
#[range]
|
#[range]
|
||||||
#[hard(0..)]
|
#[hard(0..)]
|
||||||
#[soft(..50)]
|
#[soft(..50)]
|
||||||
radius: PixelLength,
|
radius: PixelLength,
|
||||||
) -> List<Raster<CPU>> {
|
) -> Raster<CPU> {
|
||||||
image_frame
|
// Apply median filter
|
||||||
.into_iter()
|
if radius < 0.5 {
|
||||||
.map(|mut row| {
|
// Minimum filter radius
|
||||||
let image = row.element().clone();
|
image_frame
|
||||||
|
} else {
|
||||||
// Apply median filter
|
Raster::new_cpu(median_filter_algorithm(image_frame.into_data(), radius as u32))
|
||||||
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()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 1D gaussian kernel
|
// 1D gaussian kernel
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
use core_types::color::Color;
|
use core_types::color::Color;
|
||||||
use core_types::context::Ctx;
|
use core_types::context::{Ctx, ExtractIndex, InjectIndex};
|
||||||
use core_types::list::{Item, List};
|
use core_types::gpoll::{GraphError, Interrupt};
|
||||||
use raster_types::{CPU, Raster};
|
use raster_types::{CPU, Raster};
|
||||||
|
|
||||||
#[node_macro::node(category("Color"))]
|
#[node_macro::node(category("Color"))]
|
||||||
fn image_color_palette(
|
fn image_color_palette(
|
||||||
_: impl Ctx,
|
ctx: impl Ctx + ExtractIndex + InjectIndex + Copy,
|
||||||
image: &List<Raster<CPU>>,
|
image: IList<Raster<CPU>>,
|
||||||
#[default(4)]
|
#[default(4)]
|
||||||
#[hard(1..)]
|
#[hard(1..)]
|
||||||
count: u32,
|
count: u32,
|
||||||
) -> List<Color> {
|
) -> Result<IList<Color>, Interrupt> {
|
||||||
const GRID: f32 = 3.;
|
const GRID: f32 = 3.;
|
||||||
|
|
||||||
let bins = GRID * GRID * GRID;
|
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.
|
// 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];
|
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() {
|
for pixel in element.data.iter() {
|
||||||
let r = pixel.r() * GRID;
|
let r = pixel.r() * GRID;
|
||||||
let g = pixel.g() * 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>>();
|
let shorted = histogram.iter().enumerate().filter(|&(_, &count)| count > 0).map(|(i, _)| i).collect::<Vec<usize>>();
|
||||||
|
|
||||||
shorted
|
let palette: Vec<Color> = shorted
|
||||||
.iter()
|
.iter()
|
||||||
.take(count as usize)
|
.take(count as usize)
|
||||||
.flat_map(|&i| {
|
.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
|
// 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());
|
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)]
|
#[cfg(test)]
|
||||||
@@ -67,16 +70,26 @@ mod test {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_image_color_palette() {
|
fn test_image_color_palette() {
|
||||||
let result = image_color_palette(
|
core_types::record::stack::reserve(1 << 16);
|
||||||
&(),
|
let arena = core_types::arena::Arena::new(1 << 22).unwrap();
|
||||||
&List::new_from_element(Raster::new_cpu(Image {
|
let generations = [];
|
||||||
width: 100,
|
let scope = core_types::context::EvalScope::new(None, None, None, &generations, &arena);
|
||||||
height: 100,
|
let ctx = core_types::context::ContextImpl::root(&scope);
|
||||||
data: vec![Color::from_rgbaf32(0., 0., 0., 1.).unwrap(); 10000],
|
|
||||||
base64_string: None,
|
let raster = Raster::new_cpu(Image {
|
||||||
})),
|
width: 100,
|
||||||
1,
|
height: 100,
|
||||||
);
|
data: vec![Color::from_rgbaf32(0., 0., 0., 1.).unwrap(); 10000],
|
||||||
assert_eq!(result, List::new_from_element(Color::from_rgbaf32(0., 0., 0., 1.).unwrap()));
|
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