Files
vectorcraft/crates/doc/src/node/extras.rs
T
dotCooCoo 0779da299b Image Tools: Color Mask hides chosen colors of an image while its pixels stay
Object › Plug-ins › Image Tools gains Color Mask… (`addon.image.colorMask`),
Remove Color Mask and Apply Color Mask. Color Mask hides colors of the
selected images without changing their pixels. It matches colors as Remove
Background does: the edge color, the corner color or picked colors, with
Contiguous, Match, Tolerance and Softness. The mask is kept with the image
object and saved with the document: its settings, and a PNG of the image's
pixels with the hidden colors made clear, at the source's depth with its
color profile and resolution. It is a VectorCraft addition.

- The canvas (clip groups and white-shape opacity masks included),
  printing and separations, PDF (soft masks of CMYK images included), SVG,
  EPS, the raster formats, the legacy .ai and .ai editing data, EMF/WMF,
  DXF and the flattener draw the masked pixels. A linked image stays
  linked, and an output that would refer to its file writes the masked
  pixels instead.
- Copy and paste, placing a document and placed documents carry the masked
  pixels with the image, under the names they are given there.
- The dialog opens on the selected image's mask settings, with a Preview.
  Without settings, the command returns them and changes nothing.
- Setting, removing and applying a mask are each one undo step, and no
  step is added when nothing changes. Apply makes the masked pixels the
  image's own and embeds a linked image. A mask that hides nothing is only
  taken off, and the image keeps its pixels and its link.
- When an image's own pixels are replaced (Update Link, Relink, Crop
  Image, a recolor, a linked file read again on open), its mask is made
  again for the new pixels, in every art tree and in opacity-mask art.
  Crop Image cuts the image's own pixels. When the new pixels can't be
  masked, the mask keeps its settings and hides nothing.
- Objects in `ids` that are not images, CMYK images, linked images that
  show their preview, images past the decode allowance, masks that would
  hide every pixel, and grayscale images colorized with a process color
  are refused. The Eyedropper, Object Mosaic, Create Gradient Mesh, Crop
  Image's trim and Image Trace read the image's own pixels.
- An image is selected by a click anywhere in its frame, as before.

Remove Background now also refuses a grayscale image colorized with a
process color. Its result has an alpha channel, and a process color is
refused on a grayscale image with transparency.

Docs: docs/mcp.md (Image Tools add-on › Color Mask),
docs/target-app-parity.md.
2026-11-12 05:17:01 -08:00

210 lines
8.8 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
use super::attributes::ObjectAttributes;
use super::kind::NodeKind;
use super::live_shape::LiveShape;
use super::opacity::OpacityMask;
use super::{Node, PathData};
/// The parts of a [`Node`] that few objects have: kept out of line, so that the nodes without
/// them stay small ([`Node::extras`]).
#[derive(Clone, Debug, Default, PartialEq)]
pub struct NodeExtras {
/// Opacity mask (Transparency panel). Its art lives here, outside the layer tree.
pub mask: Option<Box<OpacityMask>>,
/// Image Trace object: `{preset, params, view?}` it was traced with (the Image Trace panel shows
/// them; `view` is its [`crate::TraceView`] id, absent for the tracing result).
pub trace: Option<Box<serde_json::Value>>,
/// Object → Text Wrap: area type below this object (in the same layer) flows around it.
pub wrap: Option<crate::text::TextWrap>,
/// Graph object: the group's children are generated from this spec (Object → Graph).
pub graph: Option<Box<crate::graph::GraphSpec>>,
/// Index of the graph series this generated group belongs to. Axes, the legend and other
/// children leave it unset, so a series named "Legend" or "Axes" is not mistaken for them.
pub series_index: Option<u32>,
/// The group holds the glyph outlines of type. Geometry effects that reshape it move and scale
/// the placed gradients in it with the outlines.
pub type_outlines: bool,
/// An image drawn without smoothing: each pixel of the picture takes the color of the image
/// pixel nearest to it (an envelope's distorted image with Envelope Options › Anti-Alias off).
pub pixelated: bool,
/// Editable Shaper composition; the original art is retained in its source child.
pub shaper: Option<Box<crate::shaper::ShaperSpec>>,
/// The [`crate::GraphicStyle::id`] last applied to this object. It stays linked while it keeps
/// that style's look: editing its appearance or transparency breaks the link.
pub graphic_style: Option<u32>,
/// Attributes panel: centre point display, image map, URL and note (`None`: all defaults).
pub attrs: Option<Box<ObjectAttributes>>,
/// Object → Slice → Make: the object is an object slice (its slice follows its bounds) with
/// these options.
pub slice: Option<Box<crate::SliceOptions>>,
/// Object › Perspective: the perspective grid plane the object is attached to.
pub perspective: Option<Box<crate::PerspectiveAttachment>>,
/// Dimension object: the live measurement it draws; its ordinary group children hold its
/// exportable appearance (Object › Dimensions, the Dimension tool).
pub dimension: Option<Box<crate::dimension::Dimension>>,
/// Object › Intertwine: as a member of an Intertwine group, the area (document coordinates,
/// filled non-zero) where this object is drawn over the group's other members.
pub intertwine: Option<Box<PathData>>,
/// Object › Intertwine: as a member of an Intertwine group, the areas (document coordinates,
/// filled non-zero) where this object is drawn over members that are themselves drawn over
/// others there. Item `k` is the area of tier `k + 2`; [`NodeExtras::intertwine`] is tier 1
/// (see [`crate::intertwine`]).
pub intertwine_tiers: Vec<PathData>,
/// Live Paint group: its Gap Options (Object › Live Paint › Gap Options; `None`: the defaults).
pub live_paint_gaps: Option<Box<crate::live_paint::GapOptions>>,
/// Object › Objects on Path: the group's path and the objects attached to it (see
/// [`crate::objects_on_path`]).
pub objects_on_path: Option<Box<crate::objects_on_path::ObjectsOnPath>>,
/// A grayscale or 1-bit image's fill color (`paint.setFill` on the image): a solid paint that
/// colorizes it. Each pixel is drawn in this color, as opaque as the pixel is dark, so white
/// pixels are transparent. `None`: the image shows its own pixels.
pub colorize: Option<Box<vectorcraft_color::Paint>>,
/// Pathfinder panel › Make Compound Shape: this group is a compound shape
/// ([`Node::is_compound_shape`], [`crate::compound_shape`]).
pub compound_shape: bool,
/// As a member of a compound shape, how this object combines with the members below it.
pub shape_mode: crate::ShapeMode,
/// As an instance of a dynamic symbol, its own appearance changes to objects of the symbol's
/// art (see [`crate::symbol_overrides`]); empty for none.
pub symbol_overrides: Vec<crate::SymbolOverride>,
/// As an envelope with margins in Edit Contents, the margins its strokes gave when Edit Contents
/// began ([`crate::live::EnvelopeOptions::bounds_margin`]).
pub envelope_edit_margin: Option<[f32; 4]>,
/// Transform panel › Align to Pixel Grid: the path's or compound path's horizontal and vertical
/// segments stay on the pixel grid when it is transformed.
pub pixel_aligned: bool,
/// Image Tools › Color Mask: the colors an image hides while its pixels stay as they are
/// ([`crate::color_mask`]).
pub color_mask: Option<Box<crate::color_mask::ColorMask>>,
}
/// No extras: what [`Node::extras`] gives for a node without any.
pub(super) static NO_EXTRAS: NodeExtras = NodeExtras {
mask: None,
trace: None,
wrap: None,
graph: None,
series_index: None,
type_outlines: false,
pixelated: false,
shaper: None,
graphic_style: None,
attrs: None,
slice: None,
perspective: None,
dimension: None,
intertwine: None,
intertwine_tiers: Vec::new(),
live_paint_gaps: None,
objects_on_path: None,
colorize: None,
compound_shape: false,
shape_mode: crate::ShapeMode::Add,
symbol_overrides: Vec::new(),
envelope_edit_margin: None,
pixel_aligned: false,
color_mask: None,
};
/// A node's [`NodeExtras`], if it has any ([`Node::extras`]). Extras that are all unset equal
/// none.
#[derive(Clone, Debug, Default)]
pub struct Extras(pub(crate) Option<Box<NodeExtras>>);
impl Extras {
fn get(&self) -> &NodeExtras {
self.0.as_deref().unwrap_or(&NO_EXTRAS)
}
}
impl PartialEq for Extras {
fn eq(&self, other: &Self) -> bool {
match (&self.0, &other.0) {
(Some(a), Some(b)) => a == b,
_ => self.get() == other.get(),
}
}
}
impl Node {
/// The rarely set parts of this node (all unset when it has none).
#[inline]
pub fn extras(&self) -> &NodeExtras {
self.extras.get()
}
/// The rarely set parts of this node, to change: when the change is done (the guard dropped),
/// extras that were set are allocated and extras left all unset are let go. Setting one to
/// `None` on a node without extras allocates nothing.
#[inline]
pub fn extras_mut(&mut self) -> ExtrasMut<'_> {
ExtrasMut { slot: &mut self.extras.0, local: NodeExtras::default() }
}
/// The rarely set parts of this node, if it has any: to change what is there in place.
#[inline]
pub fn extras_if_any(&mut self) -> Option<&mut NodeExtras> {
self.extras.0.as_deref_mut()
}
/// The live shape of a path object, if it is one.
pub fn live_shape(&self) -> Option<&LiveShape> {
match &self.kind {
NodeKind::Path { live, .. } => live.as_deref(),
_ => None,
}
}
/// The live shape of a path object, if it is one, to change.
pub fn live_shape_mut(&mut self) -> Option<&mut LiveShape> {
match &mut self.kind {
NodeKind::Path { live, .. } => live.as_deref_mut(),
_ => None,
}
}
/// This node with opacity mask `mask` (or none).
pub fn with_mask(mut self, mask: Option<Box<OpacityMask>>) -> Self {
self.extras_mut().mask = mask;
self
}
/// The extras, if this node has them allocated (for [`crate::mem`]'s estimate).
pub(crate) fn extras_box(&self) -> Option<&NodeExtras> {
self.extras.0.as_deref()
}
}
/// [`Node::extras_mut`]: the node's extras while they change.
pub struct ExtrasMut<'a> {
slot: &'a mut Option<Box<NodeExtras>>,
/// Stands for the extras of a node without any, boxed into it when dropped if one was set.
local: NodeExtras,
}
impl std::ops::Deref for ExtrasMut<'_> {
type Target = NodeExtras;
fn deref(&self) -> &NodeExtras {
self.slot.as_deref().unwrap_or(&self.local)
}
}
impl std::ops::DerefMut for ExtrasMut<'_> {
fn deref_mut(&mut self) -> &mut NodeExtras {
match self.slot.as_deref_mut() {
Some(e) => e,
None => &mut self.local,
}
}
}
impl Drop for ExtrasMut<'_> {
fn drop(&mut self) {
match self.slot.as_deref() {
Some(e) if *e == NO_EXTRAS => *self.slot = None,
Some(_) => {}
None if self.local != NO_EXTRAS => *self.slot = Some(Box::new(std::mem::take(&mut self.local))),
None => {}
}
}
}