Merge pull request #884 from storytold/fix/827-raster-effect-blend
Fix #827: shadows and glows keep their blend modes in PDF
This commit is contained in:
commit
1b398978ba
|
|
@ -52,6 +52,13 @@ impl RasterFx {
|
|||
pub fn is_below(&self) -> bool {
|
||||
matches!(self, RasterFx::DropShadow { .. } | RasterFx::OuterGlow { .. })
|
||||
}
|
||||
/// The blend mode it paints with (shadows and glows).
|
||||
pub fn mode(&self) -> Option<BlendMode> {
|
||||
match self {
|
||||
RasterFx::DropShadow { mode, .. } | RasterFx::OuterGlow { mode, .. } | RasterFx::InnerGlow { mode, .. } => Some(*mode),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
/// How far this effect reaches beyond `bounds`, what it paints around (Radial Blur reaches
|
||||
/// further around larger objects).
|
||||
pub fn outset(&self, bounds: Rect) -> f64 {
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use serde_json::{Map, Value, json};
|
||||
use vectorcraft_color::Paint;
|
||||
use vectorcraft_color::{BlendMode, Paint};
|
||||
use vectorcraft_doc::rastersettings::MAX_ADD_AROUND;
|
||||
use vectorcraft_doc::{
|
||||
Appearance, AppearanceItem, ColorMode, Document, Effect, ImageBlob, ImageObject, Node, NodeId, NodeKind, RasterColorModel, StrokeLayer,
|
||||
|
|
@ -217,18 +217,55 @@ pub(crate) fn raster_fx(n: &Node) -> Vec<RasterFx> {
|
|||
/// Remove the raster effects of `n` (its own and its fills' and strokes') and, with `members`, of
|
||||
/// everything inside it.
|
||||
pub(crate) fn strip_raster(n: &mut Node, members: bool) {
|
||||
let keep = |e: &Effect| !effects::is_raster(&e.id);
|
||||
retain_effects(n, members, &|e| !effects::is_raster(&e.id));
|
||||
}
|
||||
|
||||
/// Keep the effects of `n` (its own and its fills' and strokes') that `keep` accepts and, with
|
||||
/// `members`, of everything inside it.
|
||||
fn retain_effects(n: &mut Node, members: bool, keep: &dyn Fn(&Effect) -> bool) {
|
||||
n.appearance.effects.retain(keep);
|
||||
for it in &mut n.appearance.items {
|
||||
it.effects_mut().retain(keep);
|
||||
}
|
||||
if members {
|
||||
for c in n.children_mut().into_iter().flatten() {
|
||||
strip_raster(Arc::make_mut(c), true);
|
||||
retain_effects(Arc::make_mut(c), true, keep);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The blend mode raster effect `e` paints with (`None`: not a raster effect, or one without).
|
||||
fn effect_mode(e: &Effect) -> Option<BlendMode> {
|
||||
effects::raster_effects(std::slice::from_ref(e)).first().and_then(RasterFx::mode)
|
||||
}
|
||||
|
||||
/// The images of the shadows and outer glows of `n`, one per blend mode they use (in the order
|
||||
/// the modes first come), each composited with its mode: rendered alone, over nothing, they can't
|
||||
/// blend with the art below the object, so the image does.
|
||||
fn below_images(d: &mut Document, n: &Node) -> Option<Vec<Node>> {
|
||||
let mut modes: Vec<BlendMode> = vec![];
|
||||
for mode in raster_fx(n).iter().filter_map(RasterFx::mode) {
|
||||
if !modes.contains(&mode) {
|
||||
modes.push(mode);
|
||||
}
|
||||
}
|
||||
if modes.len() < 2 {
|
||||
let mut image = raster_image(d, n, true, false)?;
|
||||
image.blend = modes.first().copied().unwrap_or_default();
|
||||
return Some(vec![image]);
|
||||
}
|
||||
modes
|
||||
.into_iter()
|
||||
.map(|mode| {
|
||||
let mut only = n.clone();
|
||||
retain_effects(&mut only, false, &|e| effect_mode(e).is_none_or(|m| m == mode));
|
||||
let mut image = raster_image(d, &only, true, false)?;
|
||||
image.blend = mode;
|
||||
Some(image)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// The raster effects of `n` (its own, its fills' and strokes') as an embedded image of `d`
|
||||
/// rendered at the document's raster effects resolution, without `n`'s transparency (that stays
|
||||
/// on the object). With `below` (they all paint below it: shadows and outer glows) the image holds
|
||||
|
|
@ -405,15 +442,18 @@ fn walk(out: &mut Document, n: &Node, which: Which) -> Option<Node> {
|
|||
return Some(walk(out, &g, which).unwrap_or(g));
|
||||
}
|
||||
// An image that can't be made leaves the object as it is (the writer reports its effects).
|
||||
let image = raster_image(out, n, below, false)?;
|
||||
let mut v = n.clone();
|
||||
strip_raster(&mut v, false);
|
||||
let mut m = if below {
|
||||
let images = below_images(out, n)?;
|
||||
let mut m = walk(out, &v, which).unwrap_or(v);
|
||||
put_below(out, &mut m, image);
|
||||
// Each goes in first: the last one put is the lowest.
|
||||
for image in images.into_iter().rev() {
|
||||
put_below(out, &mut m, image);
|
||||
}
|
||||
m
|
||||
} else {
|
||||
replace_with_image(v, image)
|
||||
replace_with_image(v, raster_image(out, n, false, false)?)
|
||||
};
|
||||
walk_mask(out, &mut m, which);
|
||||
Some(m)
|
||||
|
|
|
|||
|
|
@ -150,3 +150,35 @@ fn live_objects_and_single_fills_and_strokes_keep_their_raster_effects() {
|
|||
assert_eq!(pieces, 2, "item {item}: one piece per fill and stroke");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn shadows_and_glows_keep_their_blend_modes() {
|
||||
// #827: a red Screen glow over grey can't darken it, as Normal would.
|
||||
let glow = Effect {
|
||||
id: "stylize.outerGlow".into(),
|
||||
params: json!({"mode": "screen", "color": "#ff0000", "opacity": 100.0, "blur": 20.0}),
|
||||
visible: true,
|
||||
};
|
||||
let mut d = Document::new(200.0, 200.0);
|
||||
let back = rect(&mut d, Rect::new(0.0, 0.0, 200.0, 200.0), Color::rgb(0.5, 0.5, 0.5));
|
||||
add(&mut d, back);
|
||||
let dot = rect(&mut d, Rect::new(70.0, 70.0, 130.0, 130.0), Color::rgb(0.12, 0.12, 0.12));
|
||||
add(&mut d, with(dot, glow.clone()));
|
||||
assert_written("screen glow", &d);
|
||||
let r = crate::cmd::rasterfx::export_pdf_with_report(&d, &Default::default()).unwrap();
|
||||
let back = render(&vectorcraft_pdf::import(&r.bytes).unwrap());
|
||||
for x in [133, 138, 145] {
|
||||
let i = (100 * back.width as usize + x) * 4;
|
||||
let (red, green, blue) = (back.pixels[i], back.pixels[i + 1], back.pixels[i + 2]);
|
||||
assert!(red > green && green >= 124 && blue >= 124, "x {x}: {:?}", (red, green, blue));
|
||||
}
|
||||
|
||||
// A Multiply shadow and the Screen glow: an image each, composited with its mode.
|
||||
let mut d = Document::new(200.0, 200.0);
|
||||
let dot = rect(&mut d, Rect::new(70.0, 70.0, 130.0, 130.0), Color::rgb(0.12, 0.12, 0.12));
|
||||
add(&mut d, with(with(dot, shadow()), glow));
|
||||
let flat = crate::flatten_raster_effects(&d).unwrap();
|
||||
let modes: Vec<_> =
|
||||
flat.layers[0].children().unwrap()[0].children().unwrap().iter().filter(|c| matches!(c.kind, NodeKind::Image(_))).map(|c| c.blend).collect();
|
||||
assert_eq!(modes, [vectorcraft_color::BlendMode::Multiply, vectorcraft_color::BlendMode::Screen]);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue