diff --git a/crates/io/src/abr_map.rs b/crates/io/src/abr_map.rs index e7fd290f..c21800f1 100644 --- a/crates/io/src/abr_map.rs +++ b/crates/io/src/abr_map.rs @@ -429,7 +429,12 @@ impl Mapper<'_> { } dual.mode = mask_mode(enum_value(db, "BlnM")).unwrap_or(MaskMode::Multiply); dual.flip = boolean(db, "Flip").unwrap_or(false); - if let Some(v) = pct(db, "Spcn") { + // The dual tip's own spacing (`Brsh › Spcn`) is what Photoshop's Dual Brush panel + // shows. The `Spcn` beside it on the `dualBrush` object reads 100 % in Photoshop CC + // presets whatever the panel says (#4809), so it only stands in for a tip without one. + if obj(db, "Brsh").and_then(|t| pct(t, "Spcn")).is_none() + && let Some(v) = pct(db, "Spcn") + { dual.spacing = v.clamp(0.01, 10.0); } dual.scatter = self.dynamic(obj(db, "scatterDynamics")).jitter; diff --git a/crates/io/src/abr_map/tests.rs b/crates/io/src/abr_map/tests.rs index a9719345..8bea7638 100644 --- a/crates/io/src/abr_map/tests.rs +++ b/crates/io/src/abr_map/tests.rs @@ -399,3 +399,32 @@ fn low_depth_height_texture_brush_paints() { assert_eq!(covered, 36, "{mode}: the stroke painted {covered} of 36 pixels"); } } + +/// #4809: Photoshop CC presets store the dual tip's spacing in `dualBrush › Brsh › Spcn` and a +/// `Spcn` of 100 % on the `dualBrush` object beside it; the tip's value is the panel's. A tip +/// without one still takes the outer value. +#[test] +fn dual_brush_spacing_comes_from_its_tip() { + let dual = |tip: Descriptor| { + Descriptor::new("brushPreset") + .with("Nm ", t("SPACING 10%")) + .with("Brsh", Value::Descriptor(Descriptor::new("computedBrush").with("Dmtr", u(b"#Pxl", 30.0)))) + .with( + "dualBrush", + Value::Descriptor( + Descriptor::new("dualBrush") + .with("useDualBrush", Value::Boolean(true)) + .with("Brsh", Value::Descriptor(tip)) + .with("BlnM", en("BlnM", "Ovrl")) + .with("Spcn", prc(100.0)), + ), + ) + }; + let spacing = |preset: Descriptor| { + let bytes = write_v6(2, &[], &[], &[preset], false).unwrap(); + read_abr(&bytes, "Test").unwrap().presets[0].brush.dual_brush.spacing + }; + let tip = Descriptor::new("computedBrush").with("Dmtr", u(b"#Pxl", 40.0)); + assert!((spacing(dual(tip.clone().with("Spcn", prc(10.0)))) - 0.10).abs() < 1e-6, "the tip's 10 %, not the outer 100 %"); + assert!((spacing(dual(tip)) - 1.0).abs() < 1e-6, "no tip spacing: the outer value"); +}