mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-26 02:37:01 -06:00
Merge pull request #3061 from nrq6993-a11y/stream/vc-feat1-g2-pathfinder-hard-mix
G2: Effect › Pathfinder › Hard Mix live effect
This commit is contained in:
@@ -13,12 +13,13 @@
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use vectorcraft_doc::color::{Color, Paint};
|
||||
use vectorcraft_doc::{Appearance, AppearanceItem, Node, NodeId, NodeKind, StrokeLayer};
|
||||
use vectorcraft_geom::{FillRule, PathData, Rect};
|
||||
use vectorcraft_pathops::{self as po, PathfinderOp};
|
||||
|
||||
/// Effect ids of the Pathfinder submenu with their operation, in Illustrator's menu order.
|
||||
pub const PATHFINDER_EFFECTS: [(&str, &str, PathfinderOp); 10] = [
|
||||
pub const PATHFINDER_EFFECTS: [(&str, &str, PathfinderOp); 11] = [
|
||||
("pathfinder.add", "Add", PathfinderOp::Unite),
|
||||
("pathfinder.intersect", "Intersect", PathfinderOp::Intersect),
|
||||
("pathfinder.exclude", "Exclude", PathfinderOp::Exclude),
|
||||
@@ -29,6 +30,7 @@ pub const PATHFINDER_EFFECTS: [(&str, &str, PathfinderOp); 10] = [
|
||||
("pathfinder.merge", "Merge", PathfinderOp::Merge),
|
||||
("pathfinder.crop", "Crop", PathfinderOp::Crop),
|
||||
("pathfinder.outline", "Outline", PathfinderOp::Outline),
|
||||
("pathfinder.hardMix", "Hard Mix", PathfinderOp::HardMix),
|
||||
];
|
||||
|
||||
/// Is `id` an Effect → Pathfinder effect?
|
||||
@@ -138,15 +140,56 @@ pub fn pathfinder_children(group: &Node, hook: Option<OutlineHook>) -> Option<Ve
|
||||
if shapes.is_empty() {
|
||||
return Some(vec![]);
|
||||
}
|
||||
stack = po::pathfinder(op, &shapes)
|
||||
.into_iter()
|
||||
.filter(|r| !r.path.is_empty())
|
||||
.map(|r| Arc::new(styled(&sources[r.key as usize], r.path, op == PathfinderOp::Outline)))
|
||||
.collect();
|
||||
stack = if op == PathfinderOp::HardMix {
|
||||
po::regions(&shapes)
|
||||
.into_iter()
|
||||
.filter(|r| !r.path.is_empty())
|
||||
.map(|r| Arc::new(hard_mix_face(r.path, &r.sources, &shapes, &sources)))
|
||||
.collect()
|
||||
} else {
|
||||
po::pathfinder(op, &shapes)
|
||||
.into_iter()
|
||||
.filter(|r| !r.path.is_empty())
|
||||
.map(|r| Arc::new(styled(&sources[r.key as usize], r.path, op == PathfinderOp::Outline)))
|
||||
.collect()
|
||||
};
|
||||
}
|
||||
Some(stack)
|
||||
}
|
||||
|
||||
/// One face of a Hard Mix: the max of each CMYK channel over every covering shape's fill; no stroke.
|
||||
fn hard_mix_face(path: PathData, covering: &[usize], shapes: &[po::Shape], sources: &[Node]) -> Node {
|
||||
let mut cmyk = [0.0f32; 4];
|
||||
let mut painted = false;
|
||||
for &si in covering {
|
||||
let src = &sources[shapes[si].key as usize];
|
||||
let paint = src.appearance.fill_paint();
|
||||
if let Some(color) = paint.color() {
|
||||
let tinted = match paint {
|
||||
Paint::Solid { tint, .. } => color.tinted(tint),
|
||||
_ => color,
|
||||
};
|
||||
let [c, m, y, k] = tinted.to_cmyk();
|
||||
for (out, v) in cmyk.iter_mut().zip([c, m, y, k]) {
|
||||
*out = out.max(v);
|
||||
}
|
||||
painted = true;
|
||||
}
|
||||
}
|
||||
let fill = if painted {
|
||||
Paint::solid(Color::cmyk(cmyk[0], cmyk[1], cmyk[2], cmyk[3]))
|
||||
} else {
|
||||
Paint::None
|
||||
};
|
||||
let top = covering.last().and_then(|&si| sources.get(shapes[si].key as usize));
|
||||
let mut n = Node::path(top.map_or(NodeId(0), |s| s.id), path, Appearance::basic(fill, Paint::None, 0.0));
|
||||
if let Some(src) = top {
|
||||
n.opacity = src.opacity;
|
||||
n.blend = src.blend;
|
||||
}
|
||||
n
|
||||
}
|
||||
|
||||
fn is_container(n: &Node) -> bool {
|
||||
matches!(n.kind, NodeKind::Group { .. } | NodeKind::Layer { .. })
|
||||
}
|
||||
@@ -320,6 +363,32 @@ mod tests {
|
||||
po::area(n.path_data().unwrap(), FillRule::NonZero).abs()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hard_mix_takes_the_highest_of_each_cmyk_channel_and_drops_strokes() {
|
||||
let a = Appearance::basic(Paint::solid(Color::cmyk(0.2, 0.66, 0.4, 0.0)), Paint::solid(Color::BLACK), 2.0);
|
||||
let b = Appearance::basic(Paint::solid(Color::cmyk(0.4, 0.2, 0.3, 0.1)), Paint::solid(Color::BLACK), 2.0);
|
||||
let mut g = Node::group(
|
||||
NodeId(10),
|
||||
vec![
|
||||
Arc::new(Node::path(NodeId(1), shapes::rectangle(Rect::new(0.0, 0.0, 10.0, 10.0)), a)),
|
||||
Arc::new(Node::path(NodeId(2), shapes::rectangle(Rect::new(5.0, 0.0, 15.0, 10.0)), b)),
|
||||
],
|
||||
);
|
||||
g.appearance.effects.push(Effect { id: "pathfinder.hardMix".into(), params: serde_json::json!({}), visible: true });
|
||||
let faces = pathfinder_children(&g, None).unwrap();
|
||||
assert_eq!(faces.len(), 3);
|
||||
let overlap = faces
|
||||
.iter()
|
||||
.find(|n| {
|
||||
let b = n.geometric_bounds().unwrap();
|
||||
b.x0 >= 4.9 && b.x1 <= 10.1 && b.y0 <= 0.1 && b.y1 >= 9.9
|
||||
})
|
||||
.expect("overlap face");
|
||||
assert!(overlap.appearance.stroke_paint().is_none());
|
||||
let [c, m, y, k] = overlap.appearance.fill_paint().color().unwrap().to_cmyk();
|
||||
assert!((c - 0.4).abs() < 0.02 && (m - 0.66).abs() < 0.02 && (y - 0.4).abs() < 0.02 && (k - 0.1).abs() < 0.02, "{c} {m} {y} {k}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn shape_modes_follow_the_pathfinder_panel() {
|
||||
let add = pathfinder_children(&group_with("pathfinder.add"), None).unwrap();
|
||||
|
||||
@@ -381,6 +381,7 @@ fn pf_label(op: PathfinderOp) -> &'static str {
|
||||
PathfinderOp::Crop => "Crop",
|
||||
PathfinderOp::Outline => "Outline",
|
||||
PathfinderOp::MinusBack => "Minus Back",
|
||||
PathfinderOp::HardMix => "Hard Mix",
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -50,6 +50,9 @@ pub enum PathfinderOp {
|
||||
Outline,
|
||||
/// Front-most minus everything behind; keeps the front-most key.
|
||||
MinusBack,
|
||||
/// Effect only: split into faces like Divide; overlapping faces take the highest of each CMYK
|
||||
/// channel from every shape covering them (Illustrator Effect → Pathfinder → Hard Mix).
|
||||
HardMix,
|
||||
}
|
||||
|
||||
/// One face of the planar arrangement of a set of shapes (Shape Builder / Live Paint face).
|
||||
@@ -172,6 +175,13 @@ pub fn pathfinder(op: PathfinderOp, shapes: &[Shape]) -> Vec<Shape> {
|
||||
.collect()
|
||||
}
|
||||
PathfinderOp::Outline => outline_edges(&arr, shapes),
|
||||
PathfinderOp::HardMix => regions_of(&arr)
|
||||
.into_iter()
|
||||
.filter_map(|r| {
|
||||
let key = shapes.get(r.top()?)?.key;
|
||||
Some(Shape::new(r.path, FillRule::NonZero, key))
|
||||
})
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1284,6 +1284,7 @@
|
||||
Hand-Drawn Ръчно рисувани
|
||||
Handles Дръжки
|
||||
Hard Light Твърда светлина
|
||||
Hard Mix Твърдо смесване
|
||||
Harmony Rules: Правила за хармония:
|
||||
Harmony Sets Набори от хармонии
|
||||
Height Височина
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -2906,6 +2906,7 @@ lighting Light Světlo
|
||||
Darken Ztmavit
|
||||
Exclusion Vyloučit
|
||||
Hard Light Tvrdé světlo
|
||||
Hard Mix Tvrdé míchání
|
||||
Lighten Zesvětlit
|
||||
Luminosity Světlost
|
||||
Overlay Překrýt
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -2679,6 +2679,7 @@ lighting Light Lys
|
||||
Darken Mørkere
|
||||
Exclusion Udelukkelse
|
||||
Hard Light Hårdt lys
|
||||
Hard Mix Hård blanding
|
||||
Lighten Lysere
|
||||
Luminosity Luminans
|
||||
Overlay Overlejring
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -1310,6 +1310,7 @@ file-filter Templates Vorlagen
|
||||
Hand-Drawn Handgezeichnet
|
||||
Handles Griffe
|
||||
Hard Light Hartes Licht
|
||||
Hard Mix Hart mischen
|
||||
Harmony Rules: Harmonieregeln:
|
||||
Harmony Sets Harmoniesätze
|
||||
Height Höhe
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -1247,6 +1247,7 @@
|
||||
Hand-Drawn Σχεδιασμένο στο χέρι
|
||||
Handles Λαβές
|
||||
Hard Light Έντονο φως
|
||||
Hard Mix Έντονη ανάμειξη
|
||||
Harmony Rules: Κανόνες χρωματικής αρμονίας:
|
||||
Harmony Sets Σύνολα χρωματικής αρμονίας
|
||||
Height Ύψος
|
||||
|
||||
|
@@ -1316,6 +1316,7 @@
|
||||
Hand-Drawn Dibujado a mano
|
||||
Handles Manejadores
|
||||
Hard Light Luz fuerte
|
||||
Hard Mix Mezcla definida
|
||||
Harmony Rules: Reglas de armonía:
|
||||
Harmony Sets Conjuntos de armonía
|
||||
Height Altura
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -2852,6 +2852,7 @@ lighting Light Valo
|
||||
Darken Tummenna
|
||||
Exclusion Poissulkeminen
|
||||
Hard Light Kova valo
|
||||
Hard Mix Kova sekoitus
|
||||
Lighten Vaalenna
|
||||
Luminosity Luminanssi
|
||||
Overlay Sulautus
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -1315,6 +1315,7 @@
|
||||
Hand-Drawn Dessiné à la main
|
||||
Handles Poignées
|
||||
Hard Light Lumière crue
|
||||
Hard Mix Mélange maximal
|
||||
Harmony Rules: Règles d’harmonie :
|
||||
Harmony Sets Ensembles d’harmonies
|
||||
Height Hauteur
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -1684,6 +1684,7 @@ file-filter Templates Sablonok
|
||||
Handles Fogantyúk
|
||||
Hard Szigorú
|
||||
Hard Light Kemény fény
|
||||
Hard Mix Kemény keverés
|
||||
Harmony Rules Harmóniaszabályok
|
||||
Harmony Rules: Harmóniaszabályok:
|
||||
Harmony Sets Harmóniakészletek
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -2679,6 +2679,7 @@ lighting Light Cahaya
|
||||
Darken Menggelapkan
|
||||
Exclusion Pengecualian
|
||||
Hard Light Cahaya keras
|
||||
Hard Mix Campuran keras
|
||||
Lighten Menerangkan
|
||||
Luminosity Luminositas
|
||||
Overlay Menimpa
|
||||
|
||||
|
@@ -1326,6 +1326,7 @@ file-filter Templates Modelli
|
||||
Hand-Drawn Disegnato a mano
|
||||
Handles Maniglie
|
||||
Hard Light Luce intensa
|
||||
Hard Mix Miscela intensa
|
||||
Harmony Rules: Regole di armonia:
|
||||
Harmony Sets Gruppi di armonia
|
||||
Height Altezza
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -1598,6 +1598,7 @@
|
||||
Hand Tool 手のひらツール
|
||||
Handles ハンドル
|
||||
Hard Light ハードライト
|
||||
Hard Mix ハードミックス
|
||||
Harmony Rules: ハーモニールール:
|
||||
Height 高さ
|
||||
Height: 高さ:
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -2491,6 +2491,7 @@ lighting Light სინათლე
|
||||
Darken ჩამუქება
|
||||
Exclusion გამორიცხვა
|
||||
Hard Light მკვეთრი სინათლე
|
||||
Hard Mix მკაცრი შერევა
|
||||
Lighten გაღიავება
|
||||
Luminosity ნათება
|
||||
Overlay გადაფარვა
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -1593,6 +1593,7 @@
|
||||
Hand Tool 손 도구
|
||||
Handles 핸들
|
||||
Hard Light 하드 라이트
|
||||
Hard Mix 하드 믹스
|
||||
Harmony Rules: 하모니 규칙:
|
||||
Height 높이
|
||||
Height: 높이:
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -2693,6 +2693,7 @@ lighting Light Licht
|
||||
Darken Donkerder
|
||||
Exclusion Uitsluiting
|
||||
Hard Light Hard licht
|
||||
Hard Mix Hard mengen
|
||||
Lighten Lichter
|
||||
Luminosity Luminositeit
|
||||
Overlay Bedekken
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -2784,6 +2784,7 @@ lighting Light Światło
|
||||
Darken Ciemniej
|
||||
Exclusion Wykluczenie
|
||||
Hard Light Ostre światło
|
||||
Hard Mix Mieszanie twarde
|
||||
Lighten Jaśniej
|
||||
Luminosity Jasność
|
||||
Overlay Nakładka
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -1137,6 +1137,7 @@
|
||||
Hand-Drawn Desenhado à Mão
|
||||
Handles Alças
|
||||
Hard Light Luz Forte
|
||||
Hard Mix Mistura definida
|
||||
Harmony Rules: Regras de Harmonia:
|
||||
Harmony Sets Conjuntos de Harmonia
|
||||
Height Altura
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -1324,6 +1324,7 @@ file-filter Templates Шаблоны
|
||||
Hand-Drawn Нарисованный от руки
|
||||
Handles Маркеры
|
||||
Hard Light Жёсткий свет
|
||||
Hard Mix Жёсткое наложение
|
||||
Harmony Rules: Правила гармонии:
|
||||
Harmony Sets Наборы гармонии
|
||||
Height Высота
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -1286,6 +1286,7 @@
|
||||
Hand-Drawn Handritad
|
||||
Handles Handtag
|
||||
Hard Light Hårt ljus
|
||||
Hard Mix Hård blandning
|
||||
Harmony Rules: Harmoniregler:
|
||||
Harmony Sets Harmoniuppsättningar
|
||||
Height Höjd
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 5 and column 25.
|
@@ -1320,6 +1320,7 @@ file-filter Templates Шаблони
|
||||
Hand-Drawn Намальований від руки
|
||||
Handles Маркери
|
||||
Hard Light Жорстке світло
|
||||
Hard Mix Жорстке накладання
|
||||
Harmony Rules: Правила гармонії:
|
||||
Harmony Sets Набори гармонії
|
||||
Height Висота
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 726 and column 40.
|
@@ -1525,6 +1525,7 @@ file-filter Templates Mẫu
|
||||
Handles Tay nắm
|
||||
Hard Cứng
|
||||
Hard Light Ánh sáng mạnh
|
||||
Hard Mix Trộn cứng
|
||||
Harmony Rules: Quy tắc hài hòa màu:
|
||||
Harmony Sets Bộ hài hòa màu
|
||||
Height Chiều cao
|
||||
|
||||
|
@@ -1272,6 +1272,7 @@ file-filter Templates 模板
|
||||
Hand-Drawn 手绘
|
||||
Handles 手柄
|
||||
Hard Light 强光
|
||||
Hard Mix 实色混合
|
||||
Harmony Rules: 协调规则:
|
||||
Harmony Sets 色彩调和组
|
||||
Height 高度
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -1298,6 +1298,7 @@ file-filter Templates 範本
|
||||
Hand-Drawn 手繪
|
||||
Handles 控制把手
|
||||
Hard Light 實光
|
||||
Hard Mix 實色混合
|
||||
Harmony Rules: 色彩調和規則:
|
||||
Harmony Sets 色彩調和組
|
||||
Height 高度
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
Reference in New Issue
Block a user