1use std::rc::Rc;
4
5use inset_embedder::{ImageFilter, Rect};
6
7#[derive(Clone, Copy, Debug, PartialEq)]
9pub struct ImageFilterContext {
10 pub bounds: Rect,
12}
13
14#[derive(Clone, Debug, PartialEq)]
17#[non_exhaustive]
18pub enum ImageFilterConfig {
19 Direct(ImageFilter),
21 Blur {
23 sigma_x: f64,
25 sigma_y: f64,
27 bounded: bool,
29 },
30 Compose {
32 outer: Rc<ImageFilterConfig>,
34 inner: Rc<ImageFilterConfig>,
36 },
37}
38
39impl ImageFilterConfig {
40 pub const fn new(filter: ImageFilter) -> ImageFilterConfig {
45 ImageFilterConfig::Direct(filter)
46 }
47
48 pub const fn blur() -> ImageFilterConfig {
50 ImageFilterConfig::Blur {
51 sigma_x: 0.0,
52 sigma_y: 0.0,
53 bounded: false,
54 }
55 }
56
57 pub fn compose(outer: ImageFilterConfig, inner: ImageFilterConfig) -> ImageFilterConfig {
62 ImageFilterConfig::Compose {
63 outer: Rc::new(outer),
64 inner: Rc::new(inner),
65 }
66 }
67
68 pub fn sigma_x(self, value: f64) -> ImageFilterConfig {
70 match self {
71 ImageFilterConfig::Blur {
72 sigma_y, bounded, ..
73 } => ImageFilterConfig::Blur {
74 sigma_x: value,
75 sigma_y,
76 bounded,
77 },
78 other => other,
79 }
80 }
81
82 pub fn sigma_y(self, value: f64) -> ImageFilterConfig {
84 match self {
85 ImageFilterConfig::Blur {
86 sigma_x, bounded, ..
87 } => ImageFilterConfig::Blur {
88 sigma_x,
89 sigma_y: value,
90 bounded,
91 },
92 other => other,
93 }
94 }
95
96 pub fn bounded(self, value: bool) -> ImageFilterConfig {
98 match self {
99 ImageFilterConfig::Blur {
100 sigma_x, sigma_y, ..
101 } => ImageFilterConfig::Blur {
102 sigma_x,
103 sigma_y,
104 bounded: value,
105 },
106 other => other,
107 }
108 }
109
110 pub fn resolve(&self, context: ImageFilterContext) -> ImageFilter {
112 match self {
113 ImageFilterConfig::Direct(filter) => filter.clone(),
114 ImageFilterConfig::Blur {
115 sigma_x,
116 sigma_y,
117 bounded,
118 } => {
119 let filter = ImageFilter::blur(*sigma_x, *sigma_y);
120 if *bounded {
121 filter.bounds(context.bounds)
122 } else {
123 filter
124 }
125 }
126 ImageFilterConfig::Compose { outer, inner } => {
127 ImageFilter::compose(outer.resolve(context), inner.resolve(context))
128 }
129 }
130 }
131
132 pub fn filter(&self) -> Option<&ImageFilter> {
134 match self {
135 ImageFilterConfig::Direct(filter) => Some(filter),
136 ImageFilterConfig::Blur { .. } | ImageFilterConfig::Compose { .. } => None,
137 }
138 }
139
140 pub fn debug_short_description(&self) -> String {
142 match self {
143 ImageFilterConfig::Direct(filter) => filter.debug_short_description(),
144 ImageFilterConfig::Blur {
145 sigma_x,
146 sigma_y,
147 bounded,
148 } => {
149 let bounded = if *bounded { "bounded" } else { "unbounded" };
150 format!("blur({sigma_x}, {sigma_y}, {bounded})")
151 }
152 ImageFilterConfig::Compose { outer, inner } => format!(
153 "{} -> {}",
154 inner.debug_short_description(),
155 outer.debug_short_description()
156 ),
157 }
158 }
159}
160
161impl std::fmt::Display for ImageFilterConfig {
162 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
163 match self {
164 ImageFilterConfig::Direct(filter) => {
165 write!(f, "ImageFilterConfig({})", filter.debug_short_description())
166 }
167 ImageFilterConfig::Blur { .. } => {
168 write!(f, "ImageFilterConfig.{}", self.debug_short_description())
169 }
170 ImageFilterConfig::Compose { .. } => write!(
171 f,
172 "ImageFilterConfig.compose(source -> {} -> result)",
173 self.debug_short_description()
174 ),
175 }
176 }
177}
178
179#[cfg(test)]
180mod tests {
181 use inset_embedder::{BlendMode, Color, ColorFilter};
182
183 use super::*;
184
185 const CONTEXT: ImageFilterContext = ImageFilterContext {
186 bounds: Rect::from_ltwh(0.0, 0.0, 10.0, 20.0),
187 };
188
189 #[test]
190 fn a_bounded_blur_resolves_against_the_context_bounds() {
191 let config = ImageFilterConfig::blur().sigma_x(3.0).sigma_y(4.0);
192 assert_eq!(
193 config.clone().resolve(CONTEXT),
194 ImageFilter::blur(3.0, 4.0),
195 "an unbounded blur carries no bounds"
196 );
197 assert_eq!(
198 config.bounded(true).resolve(CONTEXT),
199 ImageFilter::blur(3.0, 4.0).bounds(CONTEXT.bounds)
200 );
201 }
202
203 #[test]
204 fn compose_resolves_both_sides_and_only_a_direct_config_reports_its_filter() {
205 let saturation = ImageFilter::Color(ColorFilter::saturation(1.8));
206 let config = ImageFilterConfig::compose(
207 ImageFilterConfig::new(saturation.clone()),
208 ImageFilterConfig::blur().sigma_x(5.0).bounded(true),
209 );
210 assert_eq!(
211 config.resolve(CONTEXT),
212 ImageFilter::compose(
213 saturation,
214 ImageFilter::blur(5.0, 0.0).bounds(CONTEXT.bounds),
215 )
216 );
217 assert_eq!(config.filter(), None);
218 let direct = ImageFilterConfig::new(ImageFilter::blur(1.0, 1.0));
219 assert_eq!(direct.filter(), Some(&ImageFilter::blur(1.0, 1.0)));
220 }
221
222 #[test]
223 fn a_composed_description_reads_inner_then_outer() {
224 let config = ImageFilterConfig::compose(
225 ImageFilterConfig::new(ImageFilter::Color(ColorFilter::mode(
226 Color::new(0xFF112233),
227 BlendMode::SrcOver,
228 ))),
229 ImageFilterConfig::blur().sigma_x(5.0).sigma_y(5.0),
230 );
231 assert_eq!(
232 config.debug_short_description(),
233 "blur(5, 5, unbounded) -> ColorFilter.mode(Color(alpha: 1.0000, red: 0.0667, \
234 green: 0.1333, blue: 0.2000, colorSpace: ColorSpace.sRGB), SrcOver)"
235 );
236 }
237}