use super::super::rendering::commit_undo_region;
use super::super::{DarklyEngine, ReadbackContext};
use crate::coord::{CanvasRect, WindowRect};
use crate::document::SelectionMode;
use crate::gpu::flood_fill::{self, LayerFloodFillExtent};
use crate::gpu::overlay::{OverlayPrimitive, FLAG_CANVAS_SPACE, KIND_DASHED_LINE};
use crate::gpu::readback;
use crate::gpu::selection::{CombineMode, MorphOp};
use crate::layer::LayerId;
use crate::mask::RasterizedMask;
use crate::undo::SelectionAction;
fn emit_marching_ants(
polylines: &[Vec<[f32; 2]>],
win_ox: f32,
win_oy: f32,
canvas_origin: crate::coord::CanvasPoint,
out: &mut Vec<OverlayPrimitive>,
) {
let ox = win_ox + canvas_origin.x as f32;
let oy = win_oy + canvas_origin.y as f32;
for polyline in polylines {
for w in polyline.windows(2) {
let a = [w[0][0] + ox, w[0][1] + oy];
let b = [w[1][0] + ox, w[1][1] + oy];
let mut bg = OverlayPrimitive::new(KIND_DASHED_LINE, FLAG_CANVAS_SPACE, a, b);
bg.color = [0.0, 0.0, 0.0, 1.0];
bg.thickness = 1.5;
bg.dash_len = 0.0;
out.push(bg);
}
}
for polyline in polylines {
let mut arc = 0.0_f32;
for w in polyline.windows(2) {
let a = [w[0][0] + ox, w[0][1] + oy];
let b = [w[1][0] + ox, w[1][1] + oy];
let mut fg = OverlayPrimitive::new(KIND_DASHED_LINE, FLAG_CANVAS_SPACE, a, b);
fg.color = [1.0, 1.0, 1.0, 1.0];
fg.thickness = 1.0;
fg.dash_len = 8.0;
fg.dash_offset = arc;
out.push(fg);
let dx = b[0] - a[0];
let dy = b[1] - a[1];
arc += (dx * dx + dy * dy).sqrt();
}
}
}
fn selection_undo_frame_rect(b: WindowRect) -> CanvasRect {
CanvasRect::from_xywh(b.x0(), b.y0(), b.width, b.height)
}
impl DarklyEngine {
pub fn has_selection(&self) -> bool {
self.doc.selection_active()
}
pub fn selection_cpu_cache(&self) -> Option<&[u8]> {
let id = self.doc.selection?;
self.doc
.find_filter(id)
.and_then(|m| m.as_selection())
.and_then(|s| s.cpu_cache.data.as_deref())
}
pub(crate) fn selection_pixel_bounds(&self) -> Option<WindowRect> {
let id = self.doc.selection?;
self.doc
.find_filter(id)
.and_then(|m| m.as_selection())
.and_then(|s| s.pixel_bounds)
}
pub(crate) fn selection_modifier_id(&self) -> Option<LayerId> {
self.doc.selection_id()
}
pub(crate) fn set_selection_pixel_bounds(&mut self, bounds: Option<WindowRect>) {
let id = match self.doc.selection {
Some(id) => id,
None => return,
};
if let Some(s) = self
.doc
.find_filter_mut(id)
.and_then(|m| m.as_selection_mut())
{
s.pixel_bounds = bounds;
}
}
pub(crate) fn set_selection_cpu_cache(&mut self, data: Vec<u8>) {
let id = match self.doc.selection {
Some(id) => id,
None => return,
};
if let Some(s) = self
.doc
.find_filter_mut(id)
.and_then(|m| m.as_selection_mut())
{
s.cpu_cache.set(data);
}
}
pub(crate) fn invalidate_selection_cpu_cache(&mut self) {
let id = match self.doc.selection {
Some(id) => id,
None => return,
};
if let Some(s) = self
.doc
.find_filter_mut(id)
.and_then(|m| m.as_selection_mut())
{
s.cpu_cache.invalidate();
}
}
pub(crate) fn set_selection_active(&mut self, active: bool) {
let id = match self.doc.selection {
Some(id) => id,
None => return,
};
if let Some(filter) = self.doc.find_filter_mut(id) {
filter.common.visible = active;
}
}
pub fn select_rect(
&mut self,
x: f32,
y: f32,
w: f32,
h: f32,
mode: SelectionMode,
antialias: bool,
feather: f32,
) {
let (x, y) = self.plane_to_window_local(x, y);
let cx = x + w * 0.5;
let cy = y + h * 0.5;
let half_w = w * 0.5;
let half_h = h * 0.5;
let mask = crate::mask::rasterize_sdf_r8(
self.doc.width,
self.doc.height,
(x as i32, y as i32, w.ceil() as i32, h.ceil() as i32),
|px, py| crate::sdf::sdf_rect(px, py, cx, cy, half_w, half_h),
antialias,
feather,
);
self.apply_selection_mask(mask, mode);
}
pub fn select_ellipse(
&mut self,
x: f32,
y: f32,
w: f32,
h: f32,
mode: SelectionMode,
antialias: bool,
feather: f32,
) {
let (x, y) = self.plane_to_window_local(x, y);
let cx = x + w * 0.5;
let cy = y + h * 0.5;
let rx = w * 0.5;
let ry = h * 0.5;
let mask = crate::mask::rasterize_sdf_r8(
self.doc.width,
self.doc.height,
(x as i32, y as i32, w.ceil() as i32, h.ceil() as i32),
|px, py| crate::sdf::sdf_ellipse(px, py, cx, cy, rx, ry),
antialias,
feather,
);
self.apply_selection_mask(mask, mode);
}
pub fn select_lasso(
&mut self,
vertices: &[[f32; 2]],
mode: SelectionMode,
antialias: bool,
_feather: f32,
) {
if vertices.len() < 3 {
return;
}
let local: Vec<[f32; 2]> = vertices
.iter()
.map(|&[vx, vy]| {
let (lx, ly) = self.plane_to_window_local(vx, vy);
[lx, ly]
})
.collect();
let mask =
crate::mask::rasterize_polygon_r8(self.doc.width, self.doc.height, &local, antialias);
self.apply_selection_mask(mask, mode);
}
fn plane_to_window_local(&self, x: f32, y: f32) -> (f32, f32) {
let o = self.doc.canvas_origin;
(x - o.x as f32, y - o.y as f32)
}
pub fn select_magic_wand(
&mut self,
layer_id: LayerId,
seed_canvas: crate::coord::CanvasPoint,
tolerance: u8,
mode: SelectionMode,
) {
if self.paint_target(layer_id).is_none() {
return;
}
let was_active = self.has_selection();
let rect = self.selection_full_canvas_rect();
self.save_selection_for_undo(rect);
let pt = self.paint_target(layer_id).unwrap();
let mut encoder = self
.gpu
.device
.create_command_encoder(&wgpu::CommandEncoderDescriptor {
label: Some("magic-wand-readback"),
});
let (request, extent) =
flood_fill::request_layer_flood_fill_readback(&self.gpu.device, &mut encoder, &pt);
self.gpu.queue.submit([encoder.finish()]);
self.readbacks.submit(
request,
ReadbackContext::MagicWand {
was_active,
node_id: layer_id,
seed_canvas,
tolerance,
mode,
extent,
},
);
}
pub(crate) fn complete_magic_wand(
&mut self,
was_active: bool,
_node_id: LayerId,
seed_canvas: crate::coord::CanvasPoint,
tolerance: u8,
mode: SelectionMode,
extent: LayerFloodFillExtent,
pixels: Vec<u8>,
) {
let fill_mask = extent.flood_fill_to_canvas_mask(&pixels, seed_canvas, tolerance);
self.apply_selection_full(fill_mask, mode, was_active);
}
pub fn clear_selection(&mut self) {
if let Some((was_active, entry)) = self.clear_selection_collecting_undo() {
self.push_undo(Box::new(SelectionAction::new(was_active, entry)));
}
}
pub(crate) fn clear_selection_collecting_undo(
&mut self,
) -> Option<(bool, crate::gpu::region_store::UndoRegionEntry)> {
if !self.has_selection() {
return None;
}
let rect = self
.selection_pixel_bounds()
.map(selection_undo_frame_rect)
.unwrap_or_else(|| self.selection_full_canvas_rect());
self.save_selection_for_undo(rect);
let was_active = self.has_selection();
let bounds = self.selection_pixel_bounds();
if let Some(state) = self.compositor.selection_state_mut() {
state.clear_region(&self.gpu.queue, bounds);
}
self.set_selection_pixel_bounds(None);
self.set_selection_active(false);
self.invalidate_selection_cpu_cache();
let entry = self.commit_selection_undo_entry(rect)?;
self.selection_overlay.clear();
self.push_merged_overlay();
Some((was_active, entry))
}
pub fn select_all(&mut self) {
let rect = self.selection_full_canvas_rect();
self.save_selection_for_undo(rect);
let was_active = self.has_selection();
let w = self.doc.width;
let h = self.doc.height;
let mask = RasterizedMask {
data: vec![255u8; (w * h) as usize],
x: 0,
y: 0,
width: w,
height: h,
};
self.upload_selection_replace(&mask);
self.commit_selection_undo(was_active, rect);
self.generate_contours_from_mask(&mask);
}
pub fn invert_selection(&mut self) {
if !self.has_selection() {
return;
}
let rect = self.selection_full_canvas_rect();
self.save_selection_for_undo(rect);
let was_active = self.has_selection();
if let Some(state) = self.compositor.selection_state_mut() {
self.gpu.encode("invert-sel", |encoder| {
self.selection_pipelines
.invert(encoder, &self.gpu.device, &self.gpu.queue, state);
});
}
self.set_selection_pixel_bounds(None);
self.invalidate_selection_cpu_cache();
self.commit_selection_undo(was_active, rect);
self.kick_selection_readback();
}
pub fn grow_selection(&mut self, radius: u32) {
self.morph_selection(MorphOp::Dilate, radius);
}
pub fn shrink_selection(&mut self, radius: u32) {
self.morph_selection(MorphOp::Erode, radius);
}
pub fn border_selection(&mut self, radius: u32) {
if radius == 0 || !self.has_selection() {
return;
}
let rect = self.selection_full_canvas_rect();
self.save_selection_for_undo(rect);
let was_active = self.has_selection();
if let Some(state) = self.compositor.selection_state_mut() {
self.selection_pipelines.border(&self.gpu, state, radius);
}
self.finish_selection_edit(was_active, rect);
}
pub fn smooth_selection(&mut self, radius: u32) {
if radius == 0 || !self.has_selection() {
return;
}
let rect = self.selection_full_canvas_rect();
self.save_selection_for_undo(rect);
let was_active = self.has_selection();
self.run_morph_steps(MorphOp::Erode, radius);
self.run_morph_steps(MorphOp::Dilate, 2 * radius);
self.run_morph_steps(MorphOp::Erode, radius);
self.finish_selection_edit(was_active, rect);
}
pub fn feather_selection(&mut self, radius: f32) {
if radius <= 0.0 || !self.has_selection() {
return;
}
self.blur_selection(radius);
}
pub fn antialias_selection(&mut self) {
if !self.has_selection() {
return;
}
self.blur_selection(1.0);
}
fn morph_selection(&mut self, op: MorphOp, radius: u32) {
if radius == 0 || !self.has_selection() {
return;
}
let rect = self.selection_full_canvas_rect();
self.save_selection_for_undo(rect);
let was_active = self.has_selection();
self.run_morph_steps(op, radius);
self.finish_selection_edit(was_active, rect);
}
fn run_morph_steps(&mut self, op: MorphOp, count: u32) {
for i in 0..count {
let eight = i % 2 == 1;
if let Some(state) = self.compositor.selection_state_mut() {
self.gpu.encode("sel-morph", |encoder| {
self.selection_pipelines.morph_step(
encoder,
&self.gpu.device,
&self.gpu.queue,
state,
op,
eight,
);
});
}
}
}
fn blur_selection(&mut self, radius: f32) {
let rect = self.selection_full_canvas_rect();
self.save_selection_for_undo(rect);
let was_active = self.has_selection();
let sigma = radius * 0.5;
let krad = radius.ceil() as i32;
for horizontal in [true, false] {
if let Some(state) = self.compositor.selection_state_mut() {
self.gpu.encode("sel-blur", |encoder| {
self.selection_pipelines.blur_pass(
encoder,
&self.gpu.device,
&self.gpu.queue,
state,
horizontal,
krad,
sigma,
);
});
}
}
self.finish_selection_edit(was_active, rect);
}
fn finish_selection_edit(&mut self, was_active: bool, rect: CanvasRect) {
self.set_selection_pixel_bounds(None);
self.invalidate_selection_cpu_cache();
self.commit_selection_undo(was_active, rect);
self.kick_selection_readback();
}
pub fn clear_selection_contents(&mut self, layer_id: LayerId) {
self.auto_commit_floating();
if !self.has_selection() {
return;
}
self.gpu_clear_selection(layer_id);
}
pub(crate) fn apply_selection_mask(&mut self, mask: RasterizedMask, mode: SelectionMode) {
let was_active = self.has_selection();
let rect = self.selection_undo_rect_for_shape([mask.x, mask.y, mask.width, mask.height]);
self.save_selection_for_undo(rect);
match mode {
SelectionMode::Replace => {
self.upload_selection_replace(&mask);
self.generate_contours_from_mask(&mask);
}
_ => {
let cw = self.doc.width;
let ch = self.doc.height;
let mut full = vec![0u8; (cw * ch) as usize];
for y in 0..mask.height {
let src = (y * mask.width) as usize;
let dst = ((mask.y + y) * cw + mask.x) as usize;
full[dst..dst + mask.width as usize]
.copy_from_slice(&mask.data[src..src + mask.width as usize]);
}
self.apply_combine(&full, mode);
self.kick_selection_readback();
}
}
self.commit_selection_undo(was_active, rect);
}
fn generate_contours_from_mask(&mut self, mask: &RasterizedMask) {
self.selection_overlay.clear();
if mask.width == 0 || mask.height == 0 {
self.push_merged_overlay();
return;
}
let pad = 1u32;
let bw = mask.width + 2 * pad;
let bh = mask.height + 2 * pad;
let mut buf = vec![0u8; (bw * bh) as usize];
for y in 0..mask.height {
let src = (y * mask.width) as usize;
let dst = ((y + pad) * bw + pad) as usize;
buf[dst..dst + mask.width as usize]
.copy_from_slice(&mask.data[src..src + mask.width as usize]);
}
let polylines = crate::mask::contour_polylines_r8(&buf, bw, bh, 127);
let ox = mask.x as f32 - pad as f32;
let oy = mask.y as f32 - pad as f32;
let origin = self.doc.canvas_origin;
emit_marching_ants(&polylines, ox, oy, origin, &mut self.selection_overlay);
self.push_merged_overlay();
}
pub(crate) fn apply_selection_full(
&mut self,
shape_pixels: Vec<u8>,
mode: SelectionMode,
was_active: bool,
) {
match mode {
SelectionMode::Replace => {
self.upload_selection_replace_full(&shape_pixels);
}
_ => {
self.apply_combine(&shape_pixels, mode);
}
}
let rect = self.selection_full_canvas_rect();
self.commit_selection_undo(was_active, rect);
self.kick_selection_readback();
}
fn apply_combine(&mut self, shape_pixels: &[u8], mode: SelectionMode) {
let combine_mode = CombineMode::from_selection_mode(&mode);
if let Some(state) = self.compositor.selection_state_mut() {
self.gpu.encode("sel-combine", |encoder| {
self.selection_pipelines.combine(
encoder,
&self.gpu.device,
&self.gpu.queue,
state,
shape_pixels,
combine_mode,
);
});
}
self.set_selection_pixel_bounds(None);
self.invalidate_selection_cpu_cache();
self.set_selection_active(true);
}
fn upload_selection_replace(&mut self, mask: &RasterizedMask) {
let old_bounds = self.selection_pixel_bounds();
if let Some(state) = self.compositor.selection_state_mut() {
state.upload_replace(&self.gpu.device, &self.gpu.queue, old_bounds, mask);
}
let tight_bounds = crate::mask::pixel_bounds_r8(&mask.data, mask.width, mask.height).map(
|[bx, by, bw, bh]| {
WindowRect::from_xywh((mask.x + bx) as i32, (mask.y + by) as i32, bw, bh)
},
);
self.set_selection_pixel_bounds(tight_bounds);
self.set_selection_active(true);
let cw = self.doc.width;
let mut cache = vec![0u8; (cw * self.doc.height) as usize];
for y in 0..mask.height {
let src = (y * mask.width) as usize;
let dst = ((mask.y + y) * cw + mask.x) as usize;
cache[dst..dst + mask.width as usize]
.copy_from_slice(&mask.data[src..src + mask.width as usize]);
}
self.set_selection_cpu_cache(cache);
}
pub(crate) fn upload_selection_replace_full(&mut self, data: &[u8]) {
if let Some(state) = self.compositor.selection_state_mut() {
state.upload_replace_full(&self.gpu.device, &self.gpu.queue, data);
}
let bounds = crate::mask::pixel_bounds_r8(data, self.doc.width, self.doc.height)
.map(|[x, y, w, h]| WindowRect::from_xywh(x as i32, y as i32, w, h));
self.set_selection_pixel_bounds(bounds);
self.set_selection_active(true);
self.set_selection_cpu_cache(data.to_vec());
}
pub(crate) fn save_selection_for_undo(&mut self, rect: CanvasRect) {
let frame = match self.compositor.selection_state() {
Some(s) => s.canvas_frame(),
None => return,
};
let snap = self.gpu.encode_ret("sel-undo-save", |encoder| {
self.region_scratch.save_region(
&self.gpu.device,
encoder,
&frame,
wgpu::TextureFormat::R8Unorm,
rect,
)
});
self.pending_selection_snapshot = Some(snap);
}
pub(crate) fn commit_selection_undo(&mut self, was_active: bool, rect: CanvasRect) {
if let Some(entry) = self.commit_selection_undo_entry(rect) {
self.push_undo(Box::new(SelectionAction::new(was_active, entry)));
}
}
pub(crate) fn commit_selection_undo_entry(
&mut self,
rect: CanvasRect,
) -> Option<crate::gpu::region_store::UndoRegionEntry> {
let Some(snap) = self.pending_selection_snapshot.take() else {
debug_assert!(false, "commit_selection_undo without a paired save");
return None;
};
let filter_id = match self.selection_modifier_id() {
Some(id) => id,
None => {
debug_assert!(false, "commit_selection_undo without a selection filter");
return None;
}
};
let frame = self.compositor.selection_state()?.canvas_frame();
Some(commit_undo_region(
&self.gpu,
&self.region_scratch,
&mut self.readbacks,
"sel-undo-commit",
filter_id,
&frame,
&snap,
rect,
))
}
pub(crate) fn selection_full_canvas_rect(&self) -> CanvasRect {
CanvasRect::from_xywh(0, 0, self.doc.width, self.doc.height)
}
pub(crate) fn selection_undo_rect_for_shape(&self, shape: [u32; 4]) -> CanvasRect {
let cw = self.doc.width;
let ch = self.doc.height;
let [sx, sy, sw, sh] = shape;
let [sx, sy, sw, sh] = [
sx.min(cw),
sy.min(ch),
sw.min(cw - sx.min(cw)),
sh.min(ch - sy.min(ch)),
];
let shape_rect = CanvasRect::from_xywh(sx as i32, sy as i32, sw, sh);
let old_frame = self.selection_pixel_bounds().map(selection_undo_frame_rect);
match old_frame {
Some(old) if sw > 0 && sh > 0 => old.union(shape_rect),
Some(old) => old,
None => CanvasRect::from_xywh(0, 0, cw, ch),
}
}
pub(crate) fn kick_selection_readback(&mut self) {
let w = self.doc.width;
let h = self.doc.height;
let texture = match self.compositor.selection_state() {
Some(s) => s.texture(),
None => return,
};
self.gpu.encode("sel-readback", |encoder| {
let request = readback::request_readback(
&self.gpu.device,
encoder,
texture,
wgpu::TextureFormat::R8Unorm,
crate::coord::LayerRect::from_xywh(0, 0, w, h),
);
self.readbacks
.submit(request, ReadbackContext::SelectionReadback);
});
}
pub(crate) fn update_selection_overlay_from_readback(&mut self, pixels: Vec<u8>) {
self.selection_overlay.clear();
if !self.has_selection() {
self.push_merged_overlay();
return;
}
if self.selection_pixel_bounds().is_none() {
let bounds = crate::mask::pixel_bounds_r8(&pixels, self.doc.width, self.doc.height)
.map(|[x, y, w, h]| WindowRect::from_xywh(x as i32, y as i32, w, h));
self.set_selection_pixel_bounds(bounds);
}
self.set_selection_cpu_cache(pixels.clone());
let polylines =
crate::mask::contour_polylines_r8(&pixels, self.doc.width, self.doc.height, 127);
let origin = self.doc.canvas_origin;
emit_marching_ants(&polylines, 0.0, 0.0, origin, &mut self.selection_overlay);
self.push_merged_overlay();
}
pub(crate) fn push_merged_overlay(&mut self) {
let mut merged = Vec::with_capacity(self.selection_overlay.len() + self.tool_overlay.len());
merged.extend_from_slice(&self.selection_overlay);
merged.extend_from_slice(&self.tool_overlay);
if merged.is_empty() {
self.compositor.tool_overlay_mut().clear_primitives();
} else {
self.compositor.tool_overlay_mut().set_primitives(merged);
}
self.compositor.mark_needs_present();
}
pub fn set_overlay_primitives(&mut self, prims: Vec<OverlayPrimitive>) {
self.tool_overlay = prims;
self.push_merged_overlay();
}
pub fn clear_overlay(&mut self) {
self.tool_overlay.clear();
self.push_merged_overlay();
}
pub fn overlay_hit_test(&self, screen_x: f32, screen_y: f32) -> Option<usize> {
self.compositor.tool_overlay().hit_test(screen_x, screen_y)
}
pub fn set_overlay_mask(&mut self, width: u32, height: u32, rgba: &[u8]) {
self.compositor.tool_overlay_mut().set_mask_texture(
&self.gpu.device,
&self.gpu.queue,
width,
height,
rgba,
);
self.compositor.mark_needs_present();
}
pub fn clear_overlay_mask(&mut self) {
self.compositor.tool_overlay_mut().clear_mask_texture();
self.compositor.mark_needs_present();
}
}