Keep only where two selections agree, as a third way to join a mask part
A layer's parts could be added to the mask or taken out of it, and nothing else. The selections that need composing most are the ones that are neither: the sky that is also bright, the subject that is also skin. With union and subtract alone, "this and that" had to be spelled as "this minus everything that is not that", which needs a second part that selects the complement and rarely exists. Join gains Intersect, stored as "intersect" in the part block of a sidecar. It is the product of the two coverages, dst * src, which is one more fixed-function blend state beside union's max and subtract's dst * (1 - src) (mask-editing.md 5.2): the same scratch texture, the same three vertices, no shader arithmetic. The product equals the minimum wherever either side is fully in or out, and is the softer reading where two soft edges overlap. Join::apply spells the three operations on the CPU so the GPU tests can be held to one definition. A layer that intersects with a part covering nothing now reports that it covers nothing, so it is not rasterised as an empty slice. Old sidecars never contain the word, so they read as before; a build from before this reads "intersect" as a union, the existing unknown-join fallback, which keeps the part visible rather than dropping it. Join::ALL keeps union and subtract at indices 0 and 1 so a stored panel index still means the same join.
This commit is contained in:
@@ -1344,6 +1344,28 @@ fn a_correction_painted_onto_a_subject_survives_a_round_trip() {
|
||||
);
|
||||
}
|
||||
|
||||
/// TRACES: FR-DEV-19a
|
||||
/// An intersecting part is written under its own word and reads back as an
|
||||
/// intersection. A build from before intersection read that word as a union
|
||||
/// (see `an_unknown_join_adds_the_part`), which keeps the part visible and
|
||||
/// fixable rather than silently cutting the mask down.
|
||||
#[test]
|
||||
fn an_intersecting_part_survives_a_round_trip() {
|
||||
let mut graph = EditGraph::default_chain();
|
||||
graph.masks_mut().push(corrected("m1", Join::Intersect));
|
||||
|
||||
let mut sidecar = Sidecar::new();
|
||||
sidecar.put(Version::from_graph("default", "Default", &graph));
|
||||
let text = sidecar.to_text();
|
||||
assert!(text.contains("join = intersect"), "{text}");
|
||||
|
||||
let restored = round_trip(&graph);
|
||||
let layer = &restored.masks().layers()[0];
|
||||
assert_eq!(layer.parts().len(), 2);
|
||||
assert_eq!(layer.parts()[1].join, Join::Intersect);
|
||||
assert_eq!(layer.parts()[0].join, Join::Union, "the base is untouched");
|
||||
}
|
||||
|
||||
/// TRACES: FR-DEV-19a
|
||||
/// A part left out of the build comes back left out, and the file says so
|
||||
/// under a word that cannot be confused with the layer's own switch.
|
||||
|
||||
Reference in New Issue
Block a user