Add blur/sharpen tool
This commit is contained in:
parent
922e8f78b6
commit
e7641edd0d
|
|
@ -307,6 +307,7 @@ impl BrushEngine {
|
|||
RasterBlendMode::PatternStamp => 5u32,
|
||||
RasterBlendMode::DodgeBurn => 6u32,
|
||||
RasterBlendMode::Sponge => 7u32,
|
||||
RasterBlendMode::BlurSharpen => 8u32,
|
||||
};
|
||||
|
||||
let push_dab = |dabs: &mut Vec<GpuDab>,
|
||||
|
|
@ -371,6 +372,8 @@ impl BrushEngine {
|
|||
(cr, cg, cb, tp[0], tp[1]),
|
||||
RasterBlendMode::DodgeBurn | RasterBlendMode::Sponge =>
|
||||
(tp[0], 0.0, 0.0, 0.0, 0.0),
|
||||
RasterBlendMode::BlurSharpen =>
|
||||
(tp[0], 0.0, 0.0, tp[1], 0.0),
|
||||
_ => (cr, cg, cb, 0.0, 0.0),
|
||||
};
|
||||
push_dab(&mut dabs, &mut bbox, ex, ey, r, o, cr2, cg2, cb2,
|
||||
|
|
@ -494,6 +497,8 @@ impl BrushEngine {
|
|||
(cr, cg, cb, tp[0], tp[1]),
|
||||
RasterBlendMode::DodgeBurn | RasterBlendMode::Sponge =>
|
||||
(tp[0], 0.0, 0.0, 0.0, 0.0),
|
||||
RasterBlendMode::BlurSharpen =>
|
||||
(tp[0], 0.0, 0.0, tp[1], 0.0),
|
||||
_ => (cr, cg, cb, 0.0, 0.0),
|
||||
};
|
||||
push_dab(&mut dabs, &mut bbox,
|
||||
|
|
@ -535,6 +540,8 @@ impl BrushEngine {
|
|||
(cr, cg, cb, tp[0], tp[1]),
|
||||
RasterBlendMode::DodgeBurn | RasterBlendMode::Sponge =>
|
||||
(tp[0], 0.0, 0.0, 0.0, 0.0),
|
||||
RasterBlendMode::BlurSharpen =>
|
||||
(tp[0], 0.0, 0.0, tp[1], 0.0),
|
||||
_ => (cr, cg, cb, 0.0, 0.0),
|
||||
};
|
||||
push_dab(&mut dabs, &mut bbox, ex, ey, r, o, cr2, cg2, cb2,
|
||||
|
|
|
|||
|
|
@ -28,6 +28,8 @@ pub enum RasterBlendMode {
|
|||
DodgeBurn,
|
||||
/// Sponge: saturate or desaturate existing pixels
|
||||
Sponge,
|
||||
/// Blur / Sharpen: soften or crisp up existing pixels
|
||||
BlurSharpen,
|
||||
}
|
||||
|
||||
impl Default for RasterBlendMode {
|
||||
|
|
@ -41,7 +43,7 @@ impl RasterBlendMode {
|
|||
/// use the brush color at all (clone, heal, dodge/burn, sponge).
|
||||
/// Used by brush_engine.rs to decide whether color_a should be 1.0 or stroke.color[3].
|
||||
pub fn uses_brush_color(self) -> bool {
|
||||
!matches!(self, Self::CloneStamp | Self::Healing | Self::DodgeBurn | Self::Sponge)
|
||||
!matches!(self, Self::CloneStamp | Self::Healing | Self::DodgeBurn | Self::Sponge | Self::BlurSharpen)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -278,6 +278,52 @@ fn apply_dab(current: vec4<f32>, dab: GpuDab, px: i32, py: i32) -> vec4<f32> {
|
|||
adjusted = mix(current.rgb, luma_vec, s);
|
||||
}
|
||||
return vec4<f32>(adjusted, current.a);
|
||||
} else if dab.blend_mode == 8u {
|
||||
// Blur / Sharpen: 5×5 separable Gaussian kernel.
|
||||
// color_r: 0.0 = blur, 1.0 = sharpen
|
||||
// ndx: kernel radius in canvas pixels (> 0)
|
||||
//
|
||||
// Samples are placed on a grid at ±step and ±2*step per axis, where step = kr/2.
|
||||
// Weights are exp(-x²/2σ²) with σ = step, factored as a separable product.
|
||||
// This gives a true Gaussian falloff rather than a flat ring, so edges blend
|
||||
// into a smooth gradient rather than a flat averaged zone.
|
||||
let s = opa_weight * dab.opacity;
|
||||
if s <= 0.0 { return current; }
|
||||
|
||||
let kr = max(dab.ndx, 1.0);
|
||||
let cx2 = f32(px) + 0.5;
|
||||
let cy2 = f32(py) + 0.5;
|
||||
let step = kr * 0.5;
|
||||
|
||||
// 1-D Gaussian weights at distances 0, ±step, ±2*step (σ = step):
|
||||
// exp(0) = 1.0, exp(-0.5) ≈ 0.6065, exp(-2.0) ≈ 0.1353
|
||||
var gauss = array<f32, 5>(0.1353, 0.6065, 1.0, 0.6065, 0.1353);
|
||||
|
||||
var blur_sum = vec4<f32>(0.0);
|
||||
var blur_w = 0.0;
|
||||
for (var iy = 0; iy < 5; iy++) {
|
||||
for (var ix = 0; ix < 5; ix++) {
|
||||
let w = gauss[ix] * gauss[iy];
|
||||
let spx = cx2 + (f32(ix) - 2.0) * step;
|
||||
let spy = cy2 + (f32(iy) - 2.0) * step;
|
||||
blur_sum += bilinear_sample(spx, spy) * w;
|
||||
blur_w += w;
|
||||
}
|
||||
}
|
||||
let blurred = blur_sum / blur_w;
|
||||
|
||||
let c = textureLoad(canvas_src, vec2<i32>(px, py), 0);
|
||||
var result: vec4<f32>;
|
||||
if dab.color_r < 0.5 {
|
||||
// Blur: blend current toward the Gaussian-weighted local average.
|
||||
result = mix(current, blurred, s);
|
||||
} else {
|
||||
// Sharpen: unsharp mask — push pixel away from the local average.
|
||||
// sharpened = 2*src - blurred → highlights diverge, shadows diverge.
|
||||
let sharpened = clamp(c * 2.0 - blurred, vec4<f32>(0.0), vec4<f32>(1.0));
|
||||
result = mix(current, sharpened, s);
|
||||
}
|
||||
return result;
|
||||
} else {
|
||||
return current;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4904,6 +4904,10 @@ impl StagePane {
|
|||
// smudge_dist = radius * exp(smudge_radius_log), so log(strength) gives the ratio.
|
||||
b.smudge_radius_log = shared.raster_settings.smudge_strength; // linear [0,1] strength
|
||||
}
|
||||
if matches!(blend_mode, lightningbeam_core::raster_layer::RasterBlendMode::BlurSharpen) {
|
||||
// Zero dabs_per_actual_radius so the spacing slider is the sole density control.
|
||||
b.dabs_per_actual_radius = 0.0;
|
||||
}
|
||||
b
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,60 @@
|
|||
use super::{BrushParams, RasterToolDef, RasterToolSettings};
|
||||
use eframe::egui;
|
||||
use lightningbeam_core::{brush_settings::BrushSettings, raster_layer::RasterBlendMode};
|
||||
|
||||
pub struct BlurSharpenTool;
|
||||
pub static BLUR_SHARPEN: BlurSharpenTool = BlurSharpenTool;
|
||||
|
||||
impl RasterToolDef for BlurSharpenTool {
|
||||
fn blend_mode(&self) -> RasterBlendMode { RasterBlendMode::BlurSharpen }
|
||||
fn header_label(&self) -> &'static str { "Blur / Sharpen" }
|
||||
fn brush_params(&self, s: &RasterToolSettings) -> BrushParams {
|
||||
BrushParams {
|
||||
base_settings: BrushSettings::default(),
|
||||
radius: s.blur_sharpen_radius,
|
||||
opacity: s.blur_sharpen_strength,
|
||||
hardness: s.blur_sharpen_hardness,
|
||||
spacing: s.blur_sharpen_spacing,
|
||||
}
|
||||
}
|
||||
fn tool_params(&self, s: &RasterToolSettings) -> [f32; 4] {
|
||||
[s.blur_sharpen_mode as f32, s.blur_sharpen_kernel, 0.0, 0.0]
|
||||
}
|
||||
fn show_brush_preset_picker(&self) -> bool { false }
|
||||
fn render_ui(&self, ui: &mut egui::Ui, s: &mut RasterToolSettings) {
|
||||
ui.horizontal(|ui| {
|
||||
if ui.selectable_label(s.blur_sharpen_mode == 0, "Blur").clicked() {
|
||||
s.blur_sharpen_mode = 0;
|
||||
}
|
||||
if ui.selectable_label(s.blur_sharpen_mode == 1, "Sharpen").clicked() {
|
||||
s.blur_sharpen_mode = 1;
|
||||
}
|
||||
});
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("Size:");
|
||||
ui.add(egui::Slider::new(&mut s.blur_sharpen_radius, 1.0_f32..=500.0).logarithmic(true).suffix(" px"));
|
||||
});
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("Strength:");
|
||||
ui.add(egui::Slider::new(&mut s.blur_sharpen_strength, 0.0_f32..=1.0)
|
||||
.custom_formatter(|v, _| format!("{:.0}%", v * 100.0)));
|
||||
});
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("Hardness:");
|
||||
ui.add(egui::Slider::new(&mut s.blur_sharpen_hardness, 0.0_f32..=1.0)
|
||||
.custom_formatter(|v, _| format!("{:.0}%", v * 100.0)));
|
||||
});
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("Kernel:");
|
||||
ui.add(egui::Slider::new(&mut s.blur_sharpen_kernel, 1.0_f32..=20.0)
|
||||
.logarithmic(true)
|
||||
.custom_formatter(|v, _| format!("{:.1} px", v)));
|
||||
});
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("Spacing:");
|
||||
ui.add(egui::Slider::new(&mut s.blur_sharpen_spacing, 0.5_f32..=20.0)
|
||||
.logarithmic(true)
|
||||
.custom_formatter(|v, _| format!("{:.1}", v)));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -20,6 +20,7 @@ pub mod healing_brush;
|
|||
pub mod pattern_stamp;
|
||||
pub mod dodge_burn;
|
||||
pub mod sponge;
|
||||
pub mod blur_sharpen;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Shared settings struct (replaces 20+ individual SharedPaneState / EditorApp fields)
|
||||
|
|
@ -67,6 +68,15 @@ pub struct RasterToolSettings {
|
|||
pub sponge_flow: f32,
|
||||
/// 0 = saturate, 1 = desaturate
|
||||
pub sponge_mode: u32,
|
||||
// --- Blur / Sharpen ---
|
||||
pub blur_sharpen_radius: f32,
|
||||
pub blur_sharpen_hardness: f32,
|
||||
pub blur_sharpen_spacing: f32,
|
||||
pub blur_sharpen_strength: f32,
|
||||
/// Neighborhood kernel radius in canvas pixels (1–20)
|
||||
pub blur_sharpen_kernel: f32,
|
||||
/// 0 = blur, 1 = sharpen
|
||||
pub blur_sharpen_mode: u32,
|
||||
}
|
||||
|
||||
impl Default for RasterToolSettings {
|
||||
|
|
@ -104,6 +114,12 @@ impl Default for RasterToolSettings {
|
|||
sponge_spacing: 3.0,
|
||||
sponge_flow: 0.5,
|
||||
sponge_mode: 0,
|
||||
blur_sharpen_radius: 30.0,
|
||||
blur_sharpen_hardness: 0.5,
|
||||
blur_sharpen_spacing: 3.0,
|
||||
blur_sharpen_strength: 0.5,
|
||||
blur_sharpen_kernel: 5.0,
|
||||
blur_sharpen_mode: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -158,6 +174,7 @@ pub fn raster_tool_def(tool: &Tool) -> Option<&'static dyn RasterToolDef> {
|
|||
Tool::PatternStamp => Some(&pattern_stamp::PATTERN_STAMP),
|
||||
Tool::DodgeBurn => Some(&dodge_burn::DODGE_BURN),
|
||||
Tool::Sponge => Some(&sponge::SPONGE),
|
||||
Tool::BlurSharpen => Some(&blur_sharpen::BLUR_SHARPEN),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue