Fix Drop Shadow Darkness in source color space

This commit is contained in:
dotCooCoo
2026-10-29 14:36:41 +07:00
committed by bflatastic
parent f55cbf313c
commit dfa9f883c0
21 changed files with 587 additions and 118 deletions
+106
View File
@@ -778,3 +778,109 @@ fn a_path_with_its_own_transform_paints_each_copy_with_its_own_gradient() {
}
}
}
#[test]
fn darkness_subtracts_from_rgb_before_shadow_opacity() {
for opacity in [50.0, 100.0] {
let mut n = Node::path(
NodeId(0),
shapes::rectangle(Rect::new(10.0, 10.0, 30.0, 30.0)),
Appearance::basic(Paint::solid(Color::rgb(0.8, 0.4, 0.2)), Paint::None, 0.0),
);
n.appearance
.effects
.push(fx("stylize.dropShadow", json!({"shade":"darkness", "darkness":25, "x":40, "y":0, "blur":0, "opacity":opacity, "mode":"normal"})));
let d = doc_with(Document::new(100.0, 100.0), n);
for threads in [0, 2] {
let p = render_threads(&d, threads).pixel(60, 20);
let expected = if opacity == 100.0 { [140u8, 38, 0, 255] } else { [198, 147, 127, 255] };
assert!(p.iter().zip(expected).all(|(a, b)| a.abs_diff(b) <= 2), "{p:?}, expected {expected:?}");
}
}
}
#[test]
fn darkness_adds_black_without_changing_cmy_inks() {
let mut n = Node::path(
NodeId(0),
shapes::rectangle(Rect::new(10.0, 10.0, 30.0, 30.0)),
Appearance::basic(Paint::solid(Color::cmyk(0.15, 0.3, 0.4, 0.2)), Paint::None, 0.0),
);
n.appearance
.effects
.push(fx("stylize.dropShadow", json!({"shade":"darkness", "darkness":25, "x":40, "y":0, "blur":0, "opacity":100, "mode":"normal"})));
let mut d = Document::new(100.0, 100.0);
d.color_mode = vectorcraft_doc::ColorMode::Cmyk;
let d = doc_with(d, n);
let want = Color::cmyk(0.15, 0.3, 0.4, 0.45).to_rgba8(1.0);
for threads in [0, 2] {
let got = render_threads(&d, threads).pixel(60, 20);
assert!(got.iter().zip(want).all(|(a, b)| a.abs_diff(b) <= 3), "{got:?}, expected {want:?}");
}
}
#[test]
fn darkness_uses_the_source_color_model_before_document_conversion() {
for (mode, source, dark) in [
(vectorcraft_doc::ColorMode::Rgb, Color::cmyk(0.15, 0.3, 0.4, 0.2), Color::cmyk(0.15, 0.3, 0.4, 0.45)),
(vectorcraft_doc::ColorMode::Cmyk, Color::rgb(0.8, 0.4, 0.2), Color::rgb(0.55, 0.15, 0.0)),
] {
let mut d = Document::new(100.0, 100.0);
d.color_mode = mode;
let mut n =
Node::path(NodeId(0), shapes::rectangle(Rect::new(10.0, 10.0, 30.0, 30.0)), Appearance::basic(Paint::solid(source), Paint::None, 0.0));
n.appearance
.effects
.push(fx("stylize.dropShadow", json!({"shade":"darkness","darkness":25,"x":40,"y":0,"blur":0,"opacity":100,"mode":"normal"})));
let actual = doc_with(d.clone(), n);
let mut control =
Node::path(NodeId(0), shapes::rectangle(Rect::new(50.0, 10.0, 70.0, 30.0)), Appearance::basic(Paint::solid(dark), Paint::None, 0.0));
// The neutral effect selects the same CMYK compositing pipeline without changing paint.
control.appearance.effects.push(fx("blur.gaussian", json!({"radius":0})));
let expected = doc_with(d, control);
for threads in [0, 2] {
let got = render_threads(&actual, threads).pixel(60, 20);
let want = render_threads(&expected, threads).pixel(60, 20);
assert!(got.iter().zip(want).all(|(a, b)| a.abs_diff(b) <= 3), "{mode:?}, {source:?}: {got:?} vs {want:?}");
}
}
}
#[test]
fn darkness_adjusts_gradient_stop_colors_before_interpolation() {
use vectorcraft_color::GradientMethod;
for method in [GradientMethod::Classic, GradientMethod::Perceptual] {
for dither in [false, true] {
let ramp = |end: Color| {
let mut g = Gradient::new(GradientKind::Linear, vec![GradientStop::new(0.0, Color::BLACK), GradientStop::new(1.0, end)]);
g.method = method;
g.dither = dither;
Paint::Gradient(Box::new(GradientPaint::new(g)))
};
let mut n = Node::path(
NodeId(0),
shapes::rectangle(Rect::new(10.0, 10.0, 30.0, 30.0)),
Appearance::basic(ramp(Color::rgb(0.8, 0.4, 0.2)), Paint::None, 0.0),
);
n.appearance
.effects
.push(fx("stylize.dropShadow", json!({"shade":"darkness","darkness":25,"x":40,"y":0,"blur":0,"opacity":100,"mode":"normal"})));
let actual = doc_with(Document::new(100.0, 100.0), n);
let control = Node::path(
NodeId(0),
shapes::rectangle(Rect::new(50.0, 10.0, 70.0, 30.0)),
Appearance::basic(ramp(Color::rgb(0.55, 0.15, 0.0)), Paint::None, 0.0),
);
let expected = doc_with(Document::new(100.0, 100.0), control);
let (got, want) = (render(&actual), render(&expected));
for x in 52..68 {
assert!(
got.pixel(x, 20).iter().zip(want.pixel(x, 20)).all(|(a, b)| a.abs_diff(b) <= if dither { 8 } else { 3 }),
"{method:?} dither={dither} x={x}: {:?} vs {:?}",
got.pixel(x, 20),
want.pixel(x, 20)
);
}
}
}
}