use std::rc::Rc;
use inset_embedder::{ImageFilter, Rect};
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct ImageFilterContext {
pub bounds: Rect,
}
#[derive(Clone, Debug, PartialEq)]
#[non_exhaustive]
pub enum ImageFilterConfig {
Direct(ImageFilter),
Blur {
sigma_x: f64,
sigma_y: f64,
bounded: bool,
},
Compose {
outer: Rc<ImageFilterConfig>,
inner: Rc<ImageFilterConfig>,
},
}
impl ImageFilterConfig {
pub const fn new(filter: ImageFilter) -> ImageFilterConfig {
ImageFilterConfig::Direct(filter)
}
pub const fn blur() -> ImageFilterConfig {
ImageFilterConfig::Blur {
sigma_x: 0.0,
sigma_y: 0.0,
bounded: false,
}
}
pub fn compose(outer: ImageFilterConfig, inner: ImageFilterConfig) -> ImageFilterConfig {
ImageFilterConfig::Compose {
outer: Rc::new(outer),
inner: Rc::new(inner),
}
}
pub fn sigma_x(self, value: f64) -> ImageFilterConfig {
match self {
ImageFilterConfig::Blur {
sigma_y, bounded, ..
} => ImageFilterConfig::Blur {
sigma_x: value,
sigma_y,
bounded,
},
other => other,
}
}
pub fn sigma_y(self, value: f64) -> ImageFilterConfig {
match self {
ImageFilterConfig::Blur {
sigma_x, bounded, ..
} => ImageFilterConfig::Blur {
sigma_x,
sigma_y: value,
bounded,
},
other => other,
}
}
pub fn bounded(self, value: bool) -> ImageFilterConfig {
match self {
ImageFilterConfig::Blur {
sigma_x, sigma_y, ..
} => ImageFilterConfig::Blur {
sigma_x,
sigma_y,
bounded: value,
},
other => other,
}
}
pub fn resolve(&self, context: ImageFilterContext) -> ImageFilter {
match self {
ImageFilterConfig::Direct(filter) => filter.clone(),
ImageFilterConfig::Blur {
sigma_x,
sigma_y,
bounded,
} => {
let filter = ImageFilter::blur(*sigma_x, *sigma_y);
if *bounded {
filter.bounds(context.bounds)
} else {
filter
}
}
ImageFilterConfig::Compose { outer, inner } => {
ImageFilter::compose(outer.resolve(context), inner.resolve(context))
}
}
}
pub fn filter(&self) -> Option<&ImageFilter> {
match self {
ImageFilterConfig::Direct(filter) => Some(filter),
ImageFilterConfig::Blur { .. } | ImageFilterConfig::Compose { .. } => None,
}
}
pub fn debug_short_description(&self) -> String {
match self {
ImageFilterConfig::Direct(filter) => filter.debug_short_description(),
ImageFilterConfig::Blur {
sigma_x,
sigma_y,
bounded,
} => {
let bounded = if *bounded { "bounded" } else { "unbounded" };
format!("blur({sigma_x}, {sigma_y}, {bounded})")
}
ImageFilterConfig::Compose { outer, inner } => format!(
"{} -> {}",
inner.debug_short_description(),
outer.debug_short_description()
),
}
}
}
impl std::fmt::Display for ImageFilterConfig {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ImageFilterConfig::Direct(filter) => {
write!(f, "ImageFilterConfig({})", filter.debug_short_description())
}
ImageFilterConfig::Blur { .. } => {
write!(f, "ImageFilterConfig.{}", self.debug_short_description())
}
ImageFilterConfig::Compose { .. } => write!(
f,
"ImageFilterConfig.compose(source -> {} -> result)",
self.debug_short_description()
),
}
}
}
#[cfg(test)]
mod tests {
use inset_embedder::{BlendMode, Color, ColorFilter};
use super::*;
const CONTEXT: ImageFilterContext = ImageFilterContext {
bounds: Rect::from_ltwh(0.0, 0.0, 10.0, 20.0),
};
#[test]
fn a_bounded_blur_resolves_against_the_context_bounds() {
let config = ImageFilterConfig::blur().sigma_x(3.0).sigma_y(4.0);
assert_eq!(
config.clone().resolve(CONTEXT),
ImageFilter::blur(3.0, 4.0),
"an unbounded blur carries no bounds"
);
assert_eq!(
config.bounded(true).resolve(CONTEXT),
ImageFilter::blur(3.0, 4.0).bounds(CONTEXT.bounds)
);
}
#[test]
fn compose_resolves_both_sides_and_only_a_direct_config_reports_its_filter() {
let saturation = ImageFilter::Color(ColorFilter::saturation(1.8));
let config = ImageFilterConfig::compose(
ImageFilterConfig::new(saturation.clone()),
ImageFilterConfig::blur().sigma_x(5.0).bounded(true),
);
assert_eq!(
config.resolve(CONTEXT),
ImageFilter::compose(
saturation,
ImageFilter::blur(5.0, 0.0).bounds(CONTEXT.bounds),
)
);
assert_eq!(config.filter(), None);
let direct = ImageFilterConfig::new(ImageFilter::blur(1.0, 1.0));
assert_eq!(direct.filter(), Some(&ImageFilter::blur(1.0, 1.0)));
}
#[test]
fn a_composed_description_reads_inner_then_outer() {
let config = ImageFilterConfig::compose(
ImageFilterConfig::new(ImageFilter::Color(ColorFilter::mode(
Color::new(0xFF112233),
BlendMode::SrcOver,
))),
ImageFilterConfig::blur().sigma_x(5.0).sigma_y(5.0),
);
assert_eq!(
config.debug_short_description(),
"blur(5, 5, unbounded) -> ColorFilter.mode(Color(alpha: 1.0000, red: 0.0667, \
green: 0.1333, blue: 0.2000, colorSpace: ColorSpace.sRGB), SrcOver)"
);
}
}