135 lines
4.6 KiB
Rust
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;
|
|
}
|
|
}
|
|
}
|
|
}
|