pub mod blur;
pub mod drop_shadow;
use kurbo::Affine;
use peniko::color::{AlphaColor, Srgb};
use vello_common::filter::drop_shadow::DropShadow;
use vello_common::filter::gaussian_blur::GaussianBlur;
use vello_common::filter::{FilterData, PreparedFilter};
use vello_common::filter_effects::{EdgeMode, Filter, FilterPrimitive};
use vello_common::geometry::SizeU16;
use vello_common::record::{CommandRecorder, LayerProps, RecordedLayerKind};
use vello_common::util::extract_scales;
use crate::error::EngineError;
const SERVED_FILTERS: &str = "the only filters it renders are a single Gaussian blur or a single, \
shadow-only drop shadow (no original content composited back over it)";
pub const MAX_BLUR_SIGMA: f32 = 4096.0;
pub const SERVED_EDGE_MODE: EdgeMode = EdgeMode::None;
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum LayerFilter {
Blur {
sigma: f32,
},
DropShadow {
offset: (f32, f32),
sigma: f32,
color: AlphaColor<Srgb>,
},
}
impl LayerFilter {
pub fn filter_data(self, transform: Affine) -> Result<FilterData, EngineError> {
if !transform.as_coeffs().iter().all(|coeff| coeff.is_finite()) {
return Err(EngineError::InvalidTransform);
}
match self {
Self::Blur { sigma } => {
if !served_sigmas(sigma, transform) {
return Err(EngineError::InvalidGeometry);
}
let filter = Filter::from_primitive(FilterPrimitive::GaussianBlur {
std_deviation: sigma,
edge_mode: SERVED_EDGE_MODE,
});
Ok(FilterData::new(filter, transform))
}
Self::DropShadow {
offset: (dx, dy),
sigma,
color,
} => {
if !served_sigmas(sigma, transform) || !dx.is_finite() || !dy.is_finite() {
return Err(EngineError::InvalidGeometry);
}
let filter = Filter::from_primitive(FilterPrimitive::DropShadowOnly {
dx,
dy,
std_deviation: sigma,
color,
edge_mode: SERVED_EDGE_MODE,
});
Ok(FilterData::new(filter, transform))
}
}
}
}
pub fn push_filter_layer<D>(
recorder: &mut CommandRecorder<D>,
props: LayerProps,
filter: LayerFilter,
transform: Affine,
) -> Result<(), EngineError> {
let filter_data = filter.filter_data(transform)?;
recorder.push_layer(props, Some(filter_data));
Ok(())
}
#[must_use]
pub fn device_blur_sigma(sigma: f32, transform: Affine) -> f32 {
let (scale_x, scale_y) = extract_scales(&transform);
let uniform_scale = (scale_x + scale_y) / 2.0;
sigma * uniform_scale
}
#[must_use]
fn served_sigmas(sigma: f32, transform: Affine) -> bool {
let served = 0.0..=MAX_BLUR_SIGMA;
served.contains(&sigma) && served.contains(&device_blur_sigma(sigma, transform))
}
#[derive(Debug)]
pub enum ServedFilter {
Blur(GaussianBlur),
DropShadow(DropShadow),
}
pub fn served_filter(id: u32, kind: &RecordedLayerKind) -> Result<ServedFilter, String> {
let RecordedLayerKind::Filter { filter_data, .. } = kind else {
return Err(format!("layer {id} carries no filter"));
};
let transform = filter_data.transform;
if !transform.as_coeffs().iter().all(|coeff| coeff.is_finite()) {
return Err(format!(
"layer {id} carries a filter under a non-finite transform ({:?}), which is what \
scales its σ into the device space the blur is planned in",
transform.as_coeffs()
));
}
let primitives = filter_data.filter.graph.primitives.as_slice();
match primitives {
[
FilterPrimitive::GaussianBlur {
std_deviation,
edge_mode,
},
] => {
served_edge_mode(id, "a Gaussian blur", *edge_mode)?;
served_sigma(id, "a Gaussian blur", *std_deviation, transform)?;
match PreparedFilter::new(&filter_data.filter, &transform) {
PreparedFilter::GaussianBlur(blur) => Ok(ServedFilter::Blur(blur)),
_ => Err(format!(
"layer {id} carries a Gaussian blur that prepared as another filter"
)),
}
}
[
FilterPrimitive::DropShadowOnly {
std_deviation,
edge_mode,
..
},
] => {
served_edge_mode(id, "a drop shadow", *edge_mode)?;
served_sigma(id, "a drop shadow", *std_deviation, transform)?;
match PreparedFilter::new(&filter_data.filter, &transform) {
PreparedFilter::DropShadow(shadow) => Ok(ServedFilter::DropShadow(shadow)),
_ => Err(format!(
"layer {id} carries a drop shadow that prepared as another filter"
)),
}
}
_ => Err(format!(
"layer {id} carries a filter graph of {} primitive(s) the engine serves as neither of \
its two filters; {SERVED_FILTERS}",
primitives.len()
)),
}
}
fn served_edge_mode(id: u32, filter: &str, edge_mode: EdgeMode) -> Result<(), String> {
if edge_mode == SERVED_EDGE_MODE {
return Ok(());
}
Err(format!(
"layer {id} carries {filter} with edge mode {edge_mode:?}, which this engine does not \
render; its filter kernels read transparent black outside the region they filter, which \
is {SERVED_EDGE_MODE:?}, and no other mode"
))
}
fn served_sigma(id: u32, filter: &str, sigma: f32, transform: Affine) -> Result<(), String> {
if served_sigmas(sigma, transform) {
return Ok(());
}
let device = device_blur_sigma(sigma, transform);
Err(format!(
"layer {id} carries {filter} of σ {sigma}, which its own transform scales to a \
device-space σ of {device}; this engine blurs at a standard deviation of 0 to \
{MAX_BLUR_SIGMA} in both spaces"
))
}
pub fn served_blur(id: u32, kind: &RecordedLayerKind) -> Result<GaussianBlur, String> {
match served_filter(id, kind)? {
ServedFilter::Blur(blur) => Ok(blur),
ServedFilter::DropShadow(_) => Err(format!(
"layer {id} carries a shadow-only drop shadow rather than a Gaussian blur"
)),
}
}
pub fn served_drop_shadow(id: u32, kind: &RecordedLayerKind) -> Result<DropShadow, String> {
match served_filter(id, kind)? {
ServedFilter::DropShadow(shadow) => Ok(shadow),
ServedFilter::Blur(_) => Err(format!(
"layer {id} carries a Gaussian blur rather than a shadow-only drop shadow"
)),
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum FilterPassKind {
Copy,
Offset,
Downscale,
BlurH,
BlurV,
Upscale,
Colorize,
}
impl FilterPassKind {
#[must_use]
pub const fn code(self) -> u32 {
match self {
Self::Copy => 0,
Self::Offset => 2,
Self::Downscale => 3,
Self::BlurH => 4,
Self::BlurV => 5,
Self::Upscale => 6,
Self::Colorize => 8,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct FilterStep {
pub kind: FilterPassKind,
pub source: SizeU16,
pub dest: SizeU16,
}