feat(iced): implement selection menu operations

- Add Inverse selection (Shift+Ctrl+I) with engine.invert_selection()
- Add Shrink, Feather, Border, Smooth menu items with parameter dialogs
- Implement selection_border() and selection_smooth() in engine API
- Implement border_mask() and smooth_mask() in hcie-selection crate
- Update SelectionOpApply handler for all selection operations
This commit is contained in:
2026-07-15 03:26:07 +03:00
parent 017f6f3dd3
commit 611a6655da
4 changed files with 226 additions and 72 deletions
+83
View File
@@ -282,4 +282,87 @@ pub fn lasso_fill_mask(mask: &mut [u8], w: u32, h: u32, points: &[(u32, u32)]) {
k += 2;
}
}
}
/// Create a border from a selection mask by keeping only edge pixels.
///
/// **Purpose:** Extracts the outline/border of a selection by keeping pixels
/// that are selected but have at least one unselected neighbor.
///
/// **Arguments:**
/// - `mask`: The selection mask to process (modified in place).
/// - `w`, `h`: Canvas dimensions.
/// - `thickness`: Border thickness in pixels.
pub fn border_mask(mask: &mut [u8], w: u32, h: u32, thickness: u32) {
if thickness == 0 { return; }
let original = mask.to_vec();
// First, create the outer border by eroding
let mut eroded = original.clone();
for _ in 0..thickness {
let old = eroded.clone();
for y in 0..h {
for x in 0..w {
let i = (y * w + x) as usize;
if old[i] == 0 { continue; }
let neighbors = [
(x.wrapping_sub(1), y), (x + 1, y),
(x, y.wrapping_sub(1)), (x, y + 1),
];
let mut has_zero = false;
for (nx, ny) in neighbors {
if nx >= w || ny >= h || old[(ny * w + nx) as usize] == 0 {
has_zero = true;
break;
}
}
if has_zero {
eroded[i] = 0;
}
}
}
}
// Border = original - eroded (pixels in original but not in eroded)
for i in 0..mask.len() {
if original[i] > 0 && eroded[i] == 0 {
mask[i] = original[i];
} else {
mask[i] = 0;
}
}
}
/// Smooth a selection mask using a box blur.
///
/// **Purpose:** Softens jagged edges of a selection by applying a box blur
/// within the specified radius, creating smoother transitions.
///
/// **Arguments:**
/// - `mask`: The selection mask to process (modified in place).
/// - `w`, `h`: Canvas dimensions.
/// - `radius`: Blur radius in pixels.
pub fn smooth_mask(mask: &mut [u8], w: u32, h: u32, radius: u32) {
if radius == 0 { return; }
let r = radius as i32;
let size = (w * h) as usize;
let mut result = vec![0u8; size];
for y in 0..h {
for x in 0..w {
let mut sum = 0u32;
let mut count = 0u32;
for dy in -r..=r {
for dx in -r..=r {
let nx = (x as i32 + dx).clamp(0, w as i32 - 1) as u32;
let ny = (y as i32 + dy).clamp(0, h as i32 - 1) as u32;
sum += mask[(ny * w + nx) as usize] as u32;
count += 1;
}
}
result[(y * w + x) as usize] = (sum / count) as u8;
}
}
mask.copy_from_slice(&result);
}