Files
hcie-rust-v3.05/hcie-iced-app/crates/hcie-iced-gui/src/selection/clipboard.rs
T

135 lines
4.6 KiB
Rust

//! Selection-aware clipboard operations.
//!
//! Provides functions for extracting pixels within a selection mask
//! and compositing floating pixels back onto a layer with alpha blending.
use super::transform::SelectionTransform;
/// Extract pixels from a layer using a selection mask.
///
/// # Arguments
/// * `layer_pixels` - RGBA pixel data of the source layer (row-major, 4 bytes per pixel)
/// * `mask` - Selection mask as alpha channel values (1 byte per pixel, same dimensions as layer)
/// * `canvas_w` - Width of the canvas in pixels
/// * `canvas_h` - Height of the canvas in pixels
///
/// # Returns
/// `Some(SelectionTransform)` containing the extracted pixels and their position,
/// or `None` if the mask is empty or dimensions are invalid.
pub fn extract_masked_pixels(
layer_pixels: &[u8],
mask: &[u8],
canvas_w: u32,
canvas_h: u32,
) -> Option<SelectionTransform> {
if mask.is_empty() || canvas_w == 0 || canvas_h == 0 {
return None;
}
// Find bounding box of non-zero mask pixels
let mut min_x = canvas_w;
let mut min_y = canvas_h;
let mut max_x = 0u32;
let mut max_y = 0u32;
let mut has_selection = false;
for y in 0..canvas_h {
for x in 0..canvas_w {
if mask[(y * canvas_w + x) as usize] > 0 {
min_x = min_x.min(x);
min_y = min_y.min(y);
max_x = max_x.max(x);
max_y = max_y.max(y);
has_selection = true;
}
}
}
if !has_selection {
return None;
}
let bw = max_x - min_x + 1;
let bh = max_y - min_y + 1;
let mut pixels = vec![0u8; (bw * bh * 4) as usize];
let layer_w = canvas_w as usize;
for y in 0..bh as usize {
for x in 0..bw as usize {
let src_x = min_x as usize + x;
let src_y = min_y as usize + y;
let mask_idx = src_y * canvas_w as usize + src_x;
if mask_idx < mask.len() && mask[mask_idx] > 0 {
let px_idx = (src_y * layer_w + src_x) * 4;
if px_idx + 3 < layer_pixels.len() {
let dst_idx = (y * bw as usize + x) * 4;
pixels[dst_idx] = layer_pixels[px_idx];
pixels[dst_idx + 1] = layer_pixels[px_idx + 1];
pixels[dst_idx + 2] = layer_pixels[px_idx + 2];
pixels[dst_idx + 3] = layer_pixels[px_idx + 3];
}
}
}
}
Some(SelectionTransform {
pixels,
width: bw,
height: bh,
pos: iced::Vector::new(min_x as f32, min_y as f32),
size: iced::Vector::new(bw as f32, bh as f32),
rotation: 0.0,
})
}
/// Composite floating pixels back onto a layer.
///
/// Uses alpha blending to combine the floating pixels with the existing layer data.
/// Pixels outside the canvas bounds are clipped.
///
/// # Arguments
/// * `layer_pixels` - Mutable RGBA pixel data of the target layer (row-major, 4 bytes per pixel)
/// * `floating` - The floating selection transform containing pixels to composite
/// * `canvas_w` - Width of the canvas in pixels
/// * `canvas_h` - Height of the canvas in pixels
pub fn composite_floating_to_layer(
layer_pixels: &mut [u8],
floating: &SelectionTransform,
canvas_w: u32,
canvas_h: u32,
) {
if floating.is_empty() {
return;
}
let x = floating.pos.x as i32;
let y = floating.pos.y as i32;
let w = floating.width as i32;
let h = floating.height as i32;
for fy in 0..h {
for fx in 0..w {
let dst_x = x + fx;
let dst_y = y + fy;
if dst_x < 0 || dst_y < 0 || dst_x >= canvas_w as i32 || dst_y >= canvas_h as i32 {
continue;
}
let src_idx = ((fy * w + fx) * 4) as usize;
let dst_idx = ((dst_y * canvas_w as i32 + dst_x) * 4) as usize;
if src_idx + 3 < floating.pixels.len() && dst_idx + 3 < layer_pixels.len() {
let src_a = floating.pixels[src_idx + 3] as f32 / 255.0;
let inv_a = 1.0 - src_a;
layer_pixels[dst_idx] = (floating.pixels[src_idx] as f32 * src_a + layer_pixels[dst_idx] as f32 * inv_a) as u8;
layer_pixels[dst_idx + 1] = (floating.pixels[src_idx + 1] as f32 * src_a + layer_pixels[dst_idx + 1] as f32 * inv_a) as u8;
layer_pixels[dst_idx + 2] = (floating.pixels[src_idx + 2] as f32 * src_a + layer_pixels[dst_idx + 2] as f32 * inv_a) as u8;
layer_pixels[dst_idx + 3] = ((src_a * 255.0 + layer_pixels[dst_idx + 3] as f32 * inv_a).min(255.0)) as u8;
}
}
}
}