vectorcraft/crates/effects/src/raster.rs

126 lines
4.4 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//! Raster (painted) effects: descriptions for the renderer.
use serde_json::Value;
use vectorcraft_doc::Effect;
use vectorcraft_doc::color::{BlendMode, Color};
use crate::merged_params;
use crate::util::*;
/// A painted effect, in document units. `blur` is Illustrator's blur distance; the renderer uses
/// a Gaussian with σ = blur / 2 (so the visible spread is about 1.5 × blur).
#[derive(Clone, Debug, PartialEq)]
pub enum RasterFx {
DropShadow {
mode: BlendMode,
opacity: f32,
dx: f64,
dy: f64,
blur: f64,
color: Color,
},
OuterGlow {
mode: BlendMode,
opacity: f32,
blur: f64,
color: Color,
},
InnerGlow {
mode: BlendMode,
opacity: f32,
blur: f64,
color: Color,
center: bool,
},
/// Soften the object's edges inward over `radius`.
Feather {
radius: f64,
},
/// Blur the whole object (σ = radius / 2).
GaussianBlur {
radius: f64,
},
}
impl RasterFx {
/// Painted below the object (shadows, outer glows)?
pub fn is_below(&self) -> bool {
matches!(self, RasterFx::DropShadow { .. } | RasterFx::OuterGlow { .. })
}
/// How far this effect reaches beyond the geometry.
pub fn outset(&self) -> f64 {
match self {
RasterFx::DropShadow { dx, dy, blur, .. } => dx.abs().max(dy.abs()) + 1.5 * blur,
RasterFx::OuterGlow { blur, .. } => 1.5 * blur,
RasterFx::InnerGlow { .. } | RasterFx::Feather { .. } => 0.0,
RasterFx::GaussianBlur { radius } => 1.5 * radius,
}
}
}
/// Parse a blend mode name (`"multiply"`, `"Screen"`, `"color-burn"`…).
pub fn blend_mode(s: &str) -> BlendMode {
let key: String = s.chars().filter(|c| c.is_ascii_alphanumeric()).collect::<String>().to_ascii_lowercase();
BlendMode::ALL.iter().copied().find(|m| format!("{m:?}").to_ascii_lowercase() == key).unwrap_or(BlendMode::Normal)
}
fn color(p: &Value, default: Color) -> Color {
match p.get("color") {
Some(Value::String(s)) => Color::from_hex(s).unwrap_or(default),
Some(Value::Array(a)) if a.len() >= 3 => {
let f = |i: usize| a[i].as_f64().unwrap_or(0.0) as f32;
Color::rgb(f(0), f(1), f(2))
}
Some(v @ Value::Object(_)) => serde_json::from_value(v.clone()).unwrap_or(default),
_ => default,
}
}
fn opacity(p: &Value) -> f32 {
(num(p, "opacity", 75.0) / 100.0).clamp(0.0, 1.0) as f32
}
/// The visible raster effects of `effects`, in stack order.
pub fn raster_effects(effects: &[Effect]) -> Vec<RasterFx> {
effects
.iter()
.filter(|e| e.visible)
.filter_map(|e| {
let p = merged_params(&e.id, &e.params);
let blur = num(&p, "blur", 5.0).clamp(0.0, 1000.0);
Some(match e.id.as_str() {
"stylize.dropShadow" => RasterFx::DropShadow {
mode: blend_mode(text(&p, "mode", "multiply")),
opacity: opacity(&p),
dx: num(&p, "x", 7.0).clamp(-1e4, 1e4),
dy: num(&p, "y", 7.0).clamp(-1e4, 1e4),
blur,
color: color(&p, Color::BLACK),
},
"stylize.outerGlow" => RasterFx::OuterGlow {
mode: blend_mode(text(&p, "mode", "screen")),
opacity: opacity(&p),
blur,
color: color(&p, Color::rgb(1.0, 1.0, 0.0)),
},
"stylize.innerGlow" => RasterFx::InnerGlow {
mode: blend_mode(text(&p, "mode", "screen")),
opacity: opacity(&p),
blur,
color: color(&p, Color::WHITE),
center: text(&p, "source", "edge").eq_ignore_ascii_case("center"),
},
"stylize.feather" => RasterFx::Feather { radius: num(&p, "radius", 5.0).clamp(0.0, 1000.0) },
"blur.gaussian" => RasterFx::GaussianBlur { radius: num(&p, "radius", 5.0).clamp(0.0, 1000.0) },
_ => return None,
})
})
.collect()
}
/// How far the visible raster effects of `effects` paint beyond the (effected) geometry, in
/// document units. Geometry effects are accounted for by evaluating them.
pub fn outset(effects: &[Effect]) -> f64 {
raster_effects(effects).iter().map(RasterFx::outset).fold(0.0, f64::max)
}