mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-03 06:30:00 -06:00
Control bar: image controls (Image Trace, Embed, Crop, Mask) when an image is selected
Discord feedback: Image Trace and the other image options were hidden when an image was
selected; copy, paste, Image Trace should work from the Control bar as in Illustrator.
- Control bar, one image selected: Linked File/Embedded, file name, colour mode and PPI, then
Embed (+ Edit Original) or Unembed…, an Image Trace split button (click: Default preset;
arrow: trace with any preset), Mask and Crop Image, in place of Fill and Stroke.
- Control bar, one Image Trace object selected: "Image Tracing", its Preset (choosing another
traces it again), the Image Trace panel and Expand.
- Properties panel: Image Trace (split), Crop Image and Mask in Quick Actions for an image; an
Image Trace section (Preset, Expand, Release) for an Image Trace object. Embed/Unembed buttons
are shared with the Control bar (place::link_buttons).
- New engine command object.maskImage ("Mask"): clips the selected image with a clipping path
on its outline (rotated with it) and selects that path so its handles crop; one undo step.
- widgets::split_button (flat button with a menu arrow).
- Translations for the new strings (es, ja, zh-hans, zh-hant; "Mask" also pt-br, cs).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
87ba4f6454
commit
d6f8cc879f
@@ -1,5 +1,5 @@
|
||||
//! Object menu long tail: Lock/Hide All Artwork Above & Other Layers, Transform Each, Reset
|
||||
//! Bounding Box, Rasterize, Crop Image, Create Trim Marks, Convert to Shape and
|
||||
//! Bounding Box, Rasterize, Crop Image, Mask (an image), Create Trim Marks, Convert to Shape and
|
||||
//! Artboards → Convert / Rearrange. (Live blends are in `live.rs`.)
|
||||
|
||||
use std::sync::Arc;
|
||||
@@ -115,6 +115,15 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
has_image,
|
||||
crop_image
|
||||
),
|
||||
cmd!(
|
||||
"object.maskImage",
|
||||
"Mask",
|
||||
[],
|
||||
None,
|
||||
"{} clip the selected image with a rectangle around it (its own outline, rotated with it) and select that clipping path, whose handles then crop it; one undo step → {id: the clip group, path: the clipping path}",
|
||||
has_image,
|
||||
mask_image
|
||||
),
|
||||
cmd!(
|
||||
"object.createTrimMarks",
|
||||
"Create Trim Marks",
|
||||
@@ -585,6 +594,36 @@ fn crop_image(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
Ok(json!({ "id": id.0, "width": w, "height": h }))
|
||||
}
|
||||
|
||||
/// The Control bar's Mask for an image: a clip group of the image and a clipping path on its
|
||||
/// outline, with that path selected so dragging its handles crops the image.
|
||||
fn mask_image(s: &mut Session, _: &Value) -> Result<Value> {
|
||||
let st = s.doc()?;
|
||||
let (id, outline) = st
|
||||
.selection
|
||||
.objects
|
||||
.iter()
|
||||
.find_map(|id| match st.doc.node(*id).map(|n| &n.kind) {
|
||||
Some(NodeKind::Image(im)) => {
|
||||
Some((*id, vectorcraft_geom::shapes::rectangle(Rect::new(0.0, 0.0, im.width as f64, im.height as f64)).transformed(im.xf)))
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
.ok_or_else(|| bad("object.maskImage", "select an image"))?;
|
||||
let (gid, pid) = s.edit("Mask", |d, sel| {
|
||||
let mut clip = super::pathops::shape_node(d, outline, None);
|
||||
super::object::as_clipping_path(&mut clip)?;
|
||||
let pid = clip.id;
|
||||
let (par, idx, _) = d.position(id).ok_or(EngineError::NoNode(id))?;
|
||||
let gid = d.alloc_id();
|
||||
d.insert(par, idx + 1, Node::new(gid, NodeKind::Group { children: vec![], clip: true }))?;
|
||||
d.insert(Some(gid), 0, clip)?;
|
||||
d.move_node(id, Some(gid), usize::MAX)?;
|
||||
sel.set([pid]);
|
||||
Ok((gid, pid))
|
||||
})?;
|
||||
Ok(json!({ "id": gid.0, "path": pid.0 }))
|
||||
}
|
||||
|
||||
// ---------- Trim marks ----------
|
||||
|
||||
impl Session {
|
||||
|
||||
Reference in New Issue
Block a user