feat(iced): implement selection-aware copy/paste
This commit is contained in:
@@ -16,6 +16,7 @@ use crate::dialogs;
|
||||
use crate::dock::state::{DockState, PaneType};
|
||||
use crate::io::tablet::TabletState;
|
||||
use crate::panels;
|
||||
use crate::selection;
|
||||
use crate::selection::{CropState, SelectionTransform, TransformHandle};
|
||||
use crate::theme::ThemeState;
|
||||
use hcie_engine_api::{BlendMode, BrushTip, BrushStyle, Engine, FilterType, LayerStyle, Tool, ZOOM_MAX, ZOOM_MIN};
|
||||
@@ -2126,6 +2127,18 @@ impl HcieIcedApp {
|
||||
let doc = &self.documents[self.active_doc];
|
||||
let w = doc.engine.canvas_width();
|
||||
let h = doc.engine.canvas_height();
|
||||
|
||||
// Check if there's a selection
|
||||
if let Some(ref mask) = doc.selection_mask {
|
||||
// Copy selected pixels only
|
||||
let pixels = doc.composite_raw.clone();
|
||||
if let Some(transform) = selection::clipboard::extract_masked_pixels(&pixels, mask, w, h) {
|
||||
self.documents[self.active_doc].internal_clipboard = Some(transform);
|
||||
return Task::perform(async {}, |_| Message::NoOp);
|
||||
}
|
||||
}
|
||||
|
||||
// No selection - copy entire canvas
|
||||
let pixels = doc.composite_raw.clone();
|
||||
return Task::perform(
|
||||
async move { crate::io::clipboard::copy_image_to_clipboard(&pixels, w, h) },
|
||||
@@ -2133,6 +2146,17 @@ impl HcieIcedApp {
|
||||
);
|
||||
}
|
||||
Message::PasteImage => {
|
||||
let doc = &self.documents[self.active_doc];
|
||||
|
||||
// Check internal clipboard first
|
||||
if let Some(ref transform) = doc.internal_clipboard {
|
||||
// Enter transform mode with the clipboard content
|
||||
self.documents[self.active_doc].selection_transform = Some(transform.clone());
|
||||
self.documents[self.active_doc].transform_drag_handle = TransformHandle::None;
|
||||
return Task::perform(async {}, |_| Message::CompositeRefresh);
|
||||
}
|
||||
|
||||
// Fall back to system clipboard
|
||||
return Task::perform(
|
||||
async { crate::io::clipboard::paste_image_from_clipboard() },
|
||||
Message::ImagePasted,
|
||||
|
||||
@@ -0,0 +1,134 @@
|
||||
//! 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@
|
||||
//! Contains types for managing floating pixel selections (cut/copy-paste),
|
||||
//! transform handles, and the crop tool.
|
||||
|
||||
pub mod clipboard;
|
||||
pub mod crop;
|
||||
pub mod transform;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user