Gradients: an outlined stroke leaves dither behind, and paint.setStroke refuses it

Review follow-up (CodeRabbit): paint.setStroke kept `dither: true` from its
gradient, and a stroke can hold dither anyway (swapped from a fill). Outline
Stroke and appearance expansion copied the stroke's paint into the outline's
fill, which then drew dithered although the stroke never did. The outline's
fill now goes without dither (Paint::stroke_as_fill), and paint.setStroke
refuses a gradient with `dither: true`, as paint.editGradient does.
This commit is contained in:
Taha-Mahmoodi
2026-10-23 00:08:53 +07:00
committed by bflatastic
parent 159e20053c
commit 7c9edfaf7f
29 changed files with 80 additions and 2 deletions
+12
View File
@@ -378,6 +378,18 @@ impl Paint {
_ => None,
}
}
/// This stroke paint as the fill of the stroke's outline, so the outline looks as the stroke
/// did: without dither, which strokes ignore ([`GradientPaint::draws_as_pixels`]).
pub fn stroke_as_fill(&self) -> Paint {
match self {
Paint::Gradient(g) if g.gradient.dither => {
let mut g = g.clone();
g.gradient.dither = false;
Paint::Gradient(g)
}
p => p.clone(),
}
}
/// The Registration colour: 100% of every ink, linked to [`swatch::REGISTRATION`].
pub fn registration() -> Self {
Paint::Solid { color: Color::cmyk(1.0, 1.0, 1.0, 1.0), swatch: Some(swatch::REGISTRATION.into()), tint: 1.0 }
+17 -1
View File
@@ -565,7 +565,7 @@ pub fn bake_appearance(n: &Node) -> Option<Node> {
}
None => {
let effects = st.effects.clone();
let fill = FillLayer { opacity: st.opacity, blend: st.blend, effects, ..FillLayer::new(st.paint.clone()) };
let fill = FillLayer { opacity: st.opacity, blend: st.blend, effects, ..FillLayer::new(st.paint.stroke_as_fill()) };
piece(outline, FillRule::NonZero, AppearanceItem::Fill(fill))
}
}
@@ -673,6 +673,22 @@ mod tests {
assert_eq!(f.effects.iter().map(|e| e.id.as_str()).collect::<Vec<_>>(), ["stylize.dropShadow"]);
}
#[test]
fn a_baked_stroke_leaves_dither_behind() {
// Strokes ignore dither, so the filled outline goes without it and looks as the stroke did.
let mut n = Node::path(NodeId(50), shapes::rectangle(Rect::new(0.0, 0.0, 100.0, 100.0)), Appearance::default_art());
if let AppearanceItem::Stroke(s) = &mut n.appearance.items[1] {
let mut g = vectorcraft_color::GradientPaint::new(Default::default());
g.gradient.dither = true;
s.paint = Paint::Gradient(Box::new(g));
}
let m = bake_appearance(&n).unwrap();
let ch = m.children().unwrap();
let Some(AppearanceItem::Fill(f)) = ch[1].appearance.items.first() else { panic!("the outline is filled") };
let Paint::Gradient(g) = &f.paint else { panic!("a gradient fill") };
assert!(!g.gradient.dither);
}
#[test]
fn a_baked_stroke_with_a_gradient_along_it_becomes_meshes_clipped_to_its_outline() {
let mut n = Node::path(NodeId(50), shapes::rectangle(Rect::new(0.0, 0.0, 100.0, 100.0)), Appearance::default_art());
+4
View File
@@ -403,6 +403,10 @@ fn set_paint(s: &mut Session, p: &Value, fill: bool) -> Result<Value> {
}
check_keep_model(p, cmd)?;
let paint = paint_from(s, p)?.ok_or_else(|| bad(cmd, "give color, none, swatch or gradient"))?;
// As `paint.editGradient`: a `dither` given applies to gradient fills only.
if !fill && matches!(&paint, Paint::Gradient(g) if g.gradient.dither) && p.get("swatch").is_none() {
return Err(bad(cmd, "`dither` applies to gradient fills (paint.setFill)"));
}
apply_paint(s, p, paint, fill)
}
+1 -1
View File
@@ -635,7 +635,7 @@ pub(crate) fn outlined_stroke(
blend: st.blend,
effects: st.effects.clone(),
overprint: st.overprint,
..vectorcraft_doc::appearance::FillLayer::new(st.paint.clone())
..vectorcraft_doc::appearance::FillLayer::new(st.paint.stroke_as_fill())
};
n.appearance = Appearance { items: vec![AppearanceItem::Fill(fill)], ..Default::default() }.into();
Some(n)
+22
View File
@@ -440,7 +440,29 @@ fn dither_is_a_fill_gradient_option_that_saves_and_loads() {
s.execute("paint.setStroke", &json!({"gradient": {}})).unwrap();
let before = node(&s, id).appearance.clone();
assert!(s.execute("paint.editGradient", &json!({"dither": true, "stroke": true})).is_err());
assert!(s.execute("paint.setStroke", &json!({"gradient": {"dither": true}})).is_err());
assert!(s.execute("paint.editGradient", &json!({"dither": "yes"})).is_err());
assert!(s.execute("paint.setFill", &json!({"gradient": {"dither": 1}})).is_err());
assert_eq!(node(&s, id).appearance, before);
}
/// A stroke ignores dither, so one that holds it anyway (swapped from a fill) outlines to a fill
/// without it: Outline Stroke keeps the look.
#[test]
fn an_outlined_stroke_leaves_dither_behind() {
let mut s = session();
let id = rect(&mut s, 0.0, 0.0, 100.0, 100.0);
s.execute("paint.setStroke", &json!({"gradient": {}})).unwrap();
s.execute("paint.setFill", &json!({"gradient": {"dither": true}})).unwrap();
s.execute("paint.swap", &json!({})).unwrap();
let Paint::Gradient(st) = node(&s, id).appearance.stroke_paint() else { panic!("a gradient stroke") };
assert!(st.gradient.dither, "the swapped stroke keeps the option");
s.execute("object.path.outlineStroke", &json!({})).unwrap();
let mut fills = vec![];
s.doc().unwrap().doc.walk(|n| {
if let Paint::Gradient(g) = n.appearance.fill_paint() {
fills.push(g.gradient.dither);
}
});
assert_eq!(fills, [false, false], "the fill and the outline, neither dithered");
}
+1
View File
@@ -5386,4 +5386,5 @@ axis Y Axis Y ос
Dither Дитъринг
Fine noise hides banding in the gradient fill Фин шум скрива ивиците в градиентната запълка
@msg `dither` applies to gradient fills (stroke: false) `dither` се прилага към градиентни запълки (stroke: false)
@msg `dither` applies to gradient fills (paint.setFill) `dither` се прилага към градиентни запълки (paint.setFill)
@msg `dither` must be true or false `dither` трябва да бъде true или false
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -5431,4 +5431,5 @@ axis Y Axis osa Y
Dither Rozptýlení
Fine noise hides banding in the gradient fill Jemný šum skryje pruhy ve výplni přechodem
@msg `dither` applies to gradient fills (stroke: false) `dither` platí pro výplně přechodem (stroke: false)
@msg `dither` applies to gradient fills (paint.setFill) `dither` platí pro výplně přechodem (paint.setFill)
@msg `dither` must be true or false `dither` musí být true nebo false
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -5402,4 +5402,5 @@ axis Y Axis Y-akse
Dither Dither
Fine noise hides banding in the gradient fill Fin støj skjuler striber i gradientfyldet
@msg `dither` applies to gradient fills (stroke: false) `dither` gælder for gradientfyld (stroke: false)
@msg `dither` applies to gradient fills (paint.setFill) `dither` gælder for gradientfyld (paint.setFill)
@msg `dither` must be true or false `dither` skal være true eller false
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -5013,4 +5013,5 @@ axis Y Axis Y-Achse
Dither Dithering
Fine noise hides banding in the gradient fill Feines Rauschen verbirgt Streifen in der Verlaufsfläche
@msg `dither` applies to gradient fills (stroke: false) `dither` gilt für Verlaufsflächen (stroke: false)
@msg `dither` applies to gradient fills (paint.setFill) `dither` gilt für Verlaufsflächen (paint.setFill)
@msg `dither` must be true or false `dither` muss true oder false sein
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -5383,4 +5383,5 @@ axis Y Axis Άξονας Y
Dither Πρόσθεση θορύβου
Fine noise hides banding in the gradient fill Ο λεπτός θόρυβος κρύβει τις λωρίδες στο γέμισμα ντεγκραντέ
@msg `dither` applies to gradient fills (stroke: false) Το `dither` ισχύει για γεμίσματα ντεγκραντέ (stroke: false)
@msg `dither` applies to gradient fills (paint.setFill) Το `dither` ισχύει για γεμίσματα ντεγκραντέ (paint.setFill)
@msg `dither` must be true or false Το `dither` πρέπει να είναι true ή false
Can't render this file because it contains an unexpected character in line 2045 and column 19.
+1
View File
@@ -5373,4 +5373,5 @@ axis Y Axis Eje Y
Dither Tramado
Fine noise hides banding in the gradient fill Un ruido fino oculta las bandas del relleno de degradado
@msg `dither` applies to gradient fills (stroke: false) `dither` se aplica a rellenos de degradado (stroke: false)
@msg `dither` applies to gradient fills (paint.setFill) `dither` se aplica a rellenos de degradado (paint.setFill)
@msg `dither` must be true or false `dither` debe ser true o false
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -5462,4 +5462,5 @@ axis Y Axis Y-akseli
Dither Rasterointi
Fine noise hides banding in the gradient fill Hieno kohina piilottaa raidat liukuväritäytöstä
@msg `dither` applies to gradient fills (stroke: false) `dither` koskee liukuväritäyttöjä (stroke: false)
@msg `dither` applies to gradient fills (paint.setFill) `dither` koskee liukuväritäyttöjä (paint.setFill)
@msg `dither` must be true or false `dither` on oltava true tai false
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -5430,4 +5430,5 @@ axis Y Axis Axe Y
Dither Simulation
Fine noise hides banding in the gradient fill Un bruit fin masque les bandes du fond en dégradé
@msg `dither` applies to gradient fills (stroke: false) `dither` s’applique aux fonds en dégradé (stroke: false)
@msg `dither` applies to gradient fills (paint.setFill) `dither` s’applique aux fonds en dégradé (paint.setFill)
@msg `dither` must be true or false `dither` doit valoir true ou false
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -5417,4 +5417,5 @@ axis Y Axis Y tengely
Dither Árnyalatkeverés
Fine noise hides banding in the gradient fill A finom zaj elrejti a sávokat a színátmenetes kitöltésben
@msg `dither` applies to gradient fills (stroke: false) A `dither` színátmenetes kitöltésekre vonatkozik (stroke: false)
@msg `dither` applies to gradient fills (paint.setFill) A `dither` színátmenetes kitöltésekre vonatkozik (paint.setFill)
@msg `dither` must be true or false `dither`: true vagy false kell
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -5402,4 +5402,5 @@ axis Y Axis Sumbu Y
Dither Dither
Fine noise hides banding in the gradient fill Derau halus menyembunyikan pita warna pada isian gradien
@msg `dither` applies to gradient fills (stroke: false) `dither` berlaku untuk isian gradien (stroke: false)
@msg `dither` applies to gradient fills (paint.setFill) `dither` berlaku untuk isian gradien (paint.setFill)
@msg `dither` must be true or false `dither` harus true atau false
Can't render this file because it contains an unexpected character in line 3816 and column 19.
+1
View File
@@ -5047,4 +5047,5 @@ axis Y Axis Asse Y
Dither Dithering
Fine noise hides banding in the gradient fill Un rumore fine nasconde le bande nel riempimento sfumato
@msg `dither` applies to gradient fills (stroke: false) `dither` si applica ai riempimenti sfumati (stroke: false)
@msg `dither` applies to gradient fills (paint.setFill) `dither` si applica ai riempimenti sfumati (paint.setFill)
@msg `dither` must be true or false `dither` deve essere true o false
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -5109,4 +5109,5 @@ axis Y Axis Y 軸
Dither ディザ
Fine noise hides banding in the gradient fill 細かいノイズでグラデーションの塗りの縞を目立たなくします
@msg `dither` applies to gradient fills (stroke: false) `dither` はグラデーションの塗りに適用されます (stroke: false)
@msg `dither` applies to gradient fills (paint.setFill) `dither` はグラデーションの塗りに適用されます (paint.setFill)
@msg `dither` must be true or false `dither` には true または false を指定してください
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -5347,4 +5347,5 @@ axis Y Axis Y ღერძი
Dither დიზერინგი
Fine noise hides banding in the gradient fill წვრილი ხმაური მალავს ზოლებს გრადიენტულ შევსებაში
@msg `dither` applies to gradient fills (stroke: false) `dither` ვრცელდება გრადიენტულ შევსებებზე (stroke: false)
@msg `dither` applies to gradient fills (paint.setFill) `dither` ვრცელდება გრადიენტულ შევსებებზე (paint.setFill)
@msg `dither` must be true or false `dither` უნდა იყოს true ან false
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -5010,4 +5010,5 @@ axis Y Axis Y축
Dither 디더
Fine noise hides banding in the gradient fill 미세한 노이즈로 그레이디언트 칠의 띠를 감춥니다
@msg `dither` applies to gradient fills (stroke: false) `dither`는 그레이디언트 칠에 적용됩니다 (stroke: false)
@msg `dither` applies to gradient fills (paint.setFill) `dither`는 그레이디언트 칠에 적용됩니다 (paint.setFill)
@msg `dither` must be true or false `dither`은(는) true 또는 false여야 합니다
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -5445,4 +5445,5 @@ axis Y Axis Y-as
Dither Dither
Fine noise hides banding in the gradient fill Fijne ruis verbergt banden in de verloopvulling
@msg `dither` applies to gradient fills (stroke: false) `dither` geldt voor verloopvullingen (stroke: false)
@msg `dither` applies to gradient fills (paint.setFill) `dither` geldt voor verloopvullingen (paint.setFill)
@msg `dither` must be true or false `dither` moet true of false zijn
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -5283,4 +5283,5 @@ axis Y Axis Oś Y
Dither Rozpraszanie
Fine noise hides banding in the gradient fill Drobny szum ukrywa pasy w wypełnieniu gradientem
@msg `dither` applies to gradient fills (stroke: false) `dither` dotyczy wypełnień gradientem (stroke: false)
@msg `dither` applies to gradient fills (paint.setFill) `dither` dotyczy wypełnień gradientem (paint.setFill)
@msg `dither` must be true or false `dither` musi mieć wartość true lub false
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -5348,4 +5348,5 @@ axis Y Axis Eixo Y
Dither Dither
Fine noise hides banding in the gradient fill Um ruído fino esconde as faixas do preenchimento em gradiente
@msg `dither` applies to gradient fills (stroke: false) `dither` se aplica a preenchimentos em gradiente (stroke: false)
@msg `dither` applies to gradient fills (paint.setFill) `dither` se aplica a preenchimentos em gradiente (paint.setFill)
@msg `dither` must be true or false `dither` deve ser true ou false
Can't render this file because it contains an unexpected character in line 6 and column 47.
+1
View File
@@ -5043,4 +5043,5 @@ axis Y Axis Ось Y
Dither Дизеринг
Fine noise hides banding in the gradient fill Мелкий шум скрывает полосы в градиентной заливке
@msg `dither` applies to gradient fills (stroke: false) `dither` применяется к градиентным заливкам (stroke: false)
@msg `dither` applies to gradient fills (paint.setFill) `dither` применяется к градиентным заливкам (paint.setFill)
@msg `dither` must be true or false `dither` должно быть true или false
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -5387,4 +5387,5 @@ axis Y Axis Y-axel
Dither Dithering
Fine noise hides banding in the gradient fill Fint brus döljer bandningen i övertoningsfyllningen
@msg `dither` applies to gradient fills (stroke: false) `dither` gäller övertoningsfyllningar (stroke: false)
@msg `dither` applies to gradient fills (paint.setFill) `dither` gäller övertoningsfyllningar (paint.setFill)
@msg `dither` must be true or false `dither` måste vara true eller false
Can't render this file because it contains an unexpected character in line 5 and column 25.
+1
View File
@@ -5395,4 +5395,5 @@ axis Y Axis Y ekseni
Dither Titreme
Fine noise hides banding in the gradient fill İnce gürültü, degrade dolgudaki bantları gizler
@msg `dither` applies to gradient fills (stroke: false) `dither` degrade dolgulara uygulanır (stroke: false)
@msg `dither` applies to gradient fills (paint.setFill) `dither` degrade dolgulara uygulanır (paint.setFill)
@msg `dither` must be true or false "dither" doğru veya yanlış olmalıdır
Can't render this file because it contains an unexpected character in line 5 and column 25.
+1
View File
@@ -5027,4 +5027,5 @@ axis Y Axis Вісь Y
Dither Дизеринг
Fine noise hides banding in the gradient fill Дрібний шум приховує смуги в градієнтній заливці
@msg `dither` applies to gradient fills (stroke: false) `dither` застосовується до градієнтних заливок (stroke: false)
@msg `dither` applies to gradient fills (paint.setFill) `dither` застосовується до градієнтних заливок (paint.setFill)
@msg `dither` must be true or false `dither` має бути true або false
Can't render this file because it contains an unexpected character in line 733 and column 40.
+1
View File
@@ -5027,4 +5027,5 @@ axis Y Axis Trục Y
Dither Dither
Fine noise hides banding in the gradient fill Nhiễu mịn che dải màu trong vùng tô chuyển sắc
@msg `dither` applies to gradient fills (stroke: false) `dither` áp dụng cho vùng tô chuyển sắc (stroke: false)
@msg `dither` applies to gradient fills (paint.setFill) `dither` áp dụng cho vùng tô chuyển sắc (paint.setFill)
@msg `dither` must be true or false `dither` phải là true hoặc false
Can't render this file because it contains an unexpected character in line 4028 and column 10.
+1
View File
@@ -5271,4 +5271,5 @@ axis Y Axis Y 轴
Dither 仿色
Fine noise hides banding in the gradient fill 细微杂色可隐藏渐变填色中的色带
@msg `dither` applies to gradient fills (stroke: false) `dither` 适用于渐变填色 (stroke: false)
@msg `dither` applies to gradient fills (paint.setFill) `dither` 适用于渐变填色 (paint.setFill)
@msg `dither` must be true or false `dither` 必须是 true 或 false
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -5066,4 +5066,5 @@ axis Y Axis Y 軸
Dither 仿色
Fine noise hides banding in the gradient fill 細微雜色可隱藏漸層填色中的色帶
@msg `dither` applies to gradient fills (stroke: false) `dither` 適用於漸層填色 (stroke: false)
@msg `dither` applies to gradient fills (paint.setFill) `dither` 適用於漸層填色 (paint.setFill)
@msg `dither` must be true or false `dither` 必須是 true 或 false
Can't render this file because it contains an unexpected character in line 6 and column 47.