Place: DCS transparency interactions look only at what clipping paths show

Show DCS Transparency Interactions compared full object bounds, so a
transparent object over the part of a DCS image that its clipping path
hides marked the image. Bounds are now cut to the clipping paths of the
clip groups and clipping layers that hold each object.
This commit is contained in:
dotCooCoo
2026-11-12 20:45:43 -08:00
parent f062dd7ed2
commit 149f4243b2
3 changed files with 50 additions and 8 deletions
+22 -7
View File
@@ -1939,15 +1939,21 @@ fn status_rank(status: &str) -> usize {
/// holds them) or overlap an object that is, as Show DCS Transparency Interactions marks them.
fn dcs_transparency(d: &Document) -> BTreeSet<NodeId> {
/// Each visible DCS image's id, bounds and whether it is transparent, and the bounds of each
/// other visible object with its own transparency.
fn walk(d: &Document, n: &Node, held: bool, images: &mut Vec<(NodeId, Rect, bool)>, see_through: &mut Vec<Rect>) {
/// other visible object with its own transparency; bounds are cut to `clip`, the bounds of the
/// clipping paths of the clip groups and layers holding the object.
fn walk(d: &Document, n: &Node, held: bool, clip: Option<Rect>, images: &mut Vec<(NodeId, Rect, bool)>, see_through: &mut Vec<Rect>) {
if !n.visible {
return;
}
let own = n.shows_own_transparency();
// The part of the object that shows through `clip`; none when it is clipped away.
let shown = n.geometric_bounds().and_then(|b| match clip {
Some(c) => Some(c.intersect(b)).filter(|i| i.area() > 0.0),
None => Some(b),
});
match &n.kind {
NodeKind::Image(im) if d.images.get(&im.key).is_some_and(ImageBlob::is_separated) => {
if let Some(b) = n.geometric_bounds() {
if let Some(b) = shown {
images.push((n.id, b, held || own));
// A transparent DCS image is an object with its own transparency for the
// others it overlaps.
@@ -1956,16 +1962,25 @@ fn dcs_transparency(d: &Document) -> BTreeSet<NodeId> {
}
}
}
_ if own => see_through.extend(n.geometric_bounds()),
_ if own => see_through.extend(shown),
_ => {}
}
for c in n.children().into_iter().flatten() {
walk(d, c, held || own, images, see_through);
let Some(children) = n.children() else { return };
// A clip group's or clipping layer's first child clips the others.
let clip = match &n.kind {
NodeKind::Group { clip: true, .. } | NodeKind::Layer { clip: true, .. } => match children.first().and_then(|c| c.geometric_bounds()) {
Some(c) => Some(clip.map_or(c, |o| o.intersect(c))),
None => clip,
},
_ => clip,
};
for c in children {
walk(d, c, held || own, clip, images, see_through);
}
}
let (mut images, mut see_through) = (vec![], vec![]);
for l in &d.layers {
walk(d, l, false, &mut images, &mut see_through);
walk(d, l, false, None, &mut images, &mut see_through);
}
images
.into_iter()
+26
View File
@@ -657,6 +657,32 @@ fn dcs_transparency_marks_transparent_and_overlapped_dcs_images() {
assert!(error(&mut s, "links.list", json!({ "dcsTransparency": "yes" })).contains("dcsTransparency"), "a flag that isn't true or false");
}
#[test]
fn dcs_transparency_looks_only_at_the_part_of_an_image_its_clipping_path_shows() {
let dir = temp_dir("transparency-clipped");
let path = write(&dir, "pic.eps", &two_plates());
let mut s = session();
let r = s.execute("file.place", &json!({ "path": path, "at": [100, 100] })).unwrap();
let group = NodeId(r["ids"][0].as_u64().unwrap());
let image = image_of(&s, &r["ids"][0]).id;
let (clip, all) = {
let doc = &s.doc().unwrap().doc;
let NodeKind::Group { children, clip: true } = &doc.node(group).unwrap().kind else { panic!("a clipping group") };
(children[0].geometric_bounds().unwrap(), doc.node(image).unwrap().geometric_bounds().unwrap())
};
assert!(all.x1 - clip.x1 > 2.0, "the file's clipping path shows the left half: {clip:?} of {all:?}");
// A half-transparent square over the part of the image the clipping path hides.
let x = (clip.x1 + all.x1) / 2.0 - 1.0;
let over = s.execute("shape.rectangle", &json!({"x": x, "y": all.y0, "width": 2, "height": 2})).unwrap()["id"].as_u64().unwrap();
s.execute("transparency.set", &json!({"ids": [over], "opacity": 50})).unwrap();
let marked = |s: &mut Session| s.execute("links.list", &json!({ "dcsTransparency": true })).unwrap()["links"][0]["dcsTransparency"] == true;
assert!(!marked(&mut s), "the square is over the hidden part only");
s.execute("transparency.set", &json!({"ids": [over], "opacity": 100})).unwrap();
let shown = s.execute("shape.rectangle", &json!({"x": clip.x0, "y": all.y0, "width": 2, "height": 2})).unwrap()["id"].as_u64().unwrap();
s.execute("transparency.set", &json!({"ids": [shown], "opacity": 50})).unwrap();
assert!(marked(&mut s), "a square over the part that shows");
}
#[test]
fn dcs_transparency_counts_a_transparent_dcs_image_over_another() {
let dir = temp_dir("transparency-two");
+2 -1
View File
@@ -6705,7 +6705,8 @@ DCS Transparency Interactions, off by default) adds `dcsTransparency: true` to t
and the panel shows a warning mark on them. VectorCraft's rule for it: the image, or a group or layer holding it, has
an opacity below 100%, a blend mode other than Normal, an opacity mask or an effect, or its bounds overlap the
bounds of another visible object that has one of those or a gradient with a transparent stop (in a fill, a stroke or
a character's fill or stroke); a DCS image with transparency of its own counts as such an object. The check looks at
a character's fill or stroke); a DCS image with transparency of its own counts as such an object. Bounds are cut to the
bounds of the clipping paths of the clip groups and clipping layers holding an object. The check looks at
every object in the layers each time the list is read. A `dcsTransparency` that is not true or false is an error.
`links.goTo {id}` selects an image; `links.embed {ids?}` keeps linked files' pixels in the document (an image whose
file is missing stays linked: relink it first); `links.unembed {id, path}` writes an embedded image to a file