1
//! SVG2 filter function shortcuts - `blur()`, `brightness()`, etc.
2
//!
3
//! The `<filter>` element from SVG1.1 (also present in SVG2) uses some verbose XML to
4
//! define chains of filter primitives.  In SVG2, there is a shortcut form of the `filter`
5
//! attribute and property, where one can simply say `filter="blur(5)"` and get the
6
//! equivalent of writing a full `<filter>` with a `<feGaussianBlur>` element.
7
//!
8
//! This module has a type for each of the filter functions in SVG2 with the function's
9
//! parameters, for example [`Blur`] stores the blur's standard deviation parameter.
10
//!
11
//! Those types get aggregated in the [`FilterFunction`] enum.  A [`FilterFunction`] can
12
//! then convert itself into a [`FilterSpec`], which is ready to be rendered on a surface.
13

            
14
use cssparser::{Color, Parser};
15

            
16
use crate::angle::Angle;
17
use crate::error::*;
18
use crate::filter::Filter;
19
use crate::filters::{
20
    color_matrix::ColorMatrix,
21
    component_transfer::{self, FeFuncA, FeFuncB, FeFuncCommon, FeFuncG, FeFuncR},
22
    composite::{Composite, Operator},
23
    flood::Flood,
24
    gaussian_blur::GaussianBlur,
25
    merge::{Merge, MergeNode},
26
    offset::Offset,
27
    FilterSpec, Input, Primitive, PrimitiveParams, ResolvedPrimitive, UserSpacePrimitive,
28
};
29
use crate::length::*;
30
use crate::paint_server::resolve_color;
31
use crate::parsers::{CustomIdent, NumberOptionalNumber, NumberOrPercentage, Parse};
32
use crate::unit_interval::UnitInterval;
33

            
34
/// CSS Filter functions from the Filter Effects Module Level 1
35
///
36
/// Filter Effects 1: <https://www.w3.org/TR/filter-effects/#filter-functions>
37
#[derive(Debug, Clone, PartialEq)]
38
pub enum FilterFunction {
39
    Blur(Blur),
40
    Brightness(Brightness),
41
    Contrast(Contrast),
42
    DropShadow(DropShadow),
43
    Grayscale(Grayscale),
44
    HueRotate(HueRotate),
45
    Invert(Invert),
46
    Opacity(Opacity),
47
    Saturate(Saturate),
48
    Sepia(Sepia),
49
}
50

            
51
/// Parameters for the `blur()` filter function
52
///
53
/// Filter Effects 1: <https://www.w3.org/TR/filter-effects/#funcdef-filter-blur>
54
#[derive(Debug, Clone, PartialEq)]
55
pub struct Blur {
56
    std_deviation: Option<Length<Both>>,
57
}
58

            
59
/// Parameters for the `brightness()` filter function
60
///
61
/// Filter Effects 1: <https://www.w3.org/TR/filter-effects/#funcdef-filter-brightness>
62
#[derive(Debug, Clone, PartialEq)]
63
pub struct Brightness {
64
    proportion: Option<f64>,
65
}
66

            
67
/// Parameters for the `contrast()` filter function
68
///
69
/// Filter Effects 1: <https://www.w3.org/TR/filter-effects/#funcdef-filter-contrast>
70
#[derive(Debug, Clone, PartialEq)]
71
pub struct Contrast {
72
    proportion: Option<f64>,
73
}
74

            
75
/// Parameters for the `drop-shadow()` filter function
76
///
77
/// Filter Effects 1: <https://www.w3.org/TR/filter-effects/#funcdef-filter-drop-shadow>
78
#[derive(Debug, Clone, PartialEq)]
79
pub struct DropShadow {
80
    color: Option<Color>,
81
    dx: Option<Length<Horizontal>>,
82
    dy: Option<Length<Vertical>>,
83
    std_deviation: Option<ULength<Both>>,
84
}
85

            
86
/// Parameters for the `grayscale()` filter function
87
///
88
/// Filter Effects 1: <https://www.w3.org/TR/filter-effects/#funcdef-filter-grayscale>
89
#[derive(Debug, Clone, PartialEq)]
90
pub struct Grayscale {
91
    proportion: Option<f64>,
92
}
93

            
94
/// Parameters for the `hue-rotate()` filter function
95
///
96
/// Filter Effects 1: <https://www.w3.org/TR/filter-effects/#funcdef-filter-huerotate>
97
#[derive(Debug, Clone, PartialEq)]
98
pub struct HueRotate {
99
    angle: Option<Angle>,
100
}
101

            
102
/// Parameters for the `invert()` filter function
103
///
104
/// Filter Effects 1: <https://www.w3.org/TR/filter-effects/#funcdef-filter-invert>
105
#[derive(Debug, Clone, PartialEq)]
106
pub struct Invert {
107
    proportion: Option<f64>,
108
}
109

            
110
/// Parameters for the `opacity()` filter function
111
///
112
/// Filter Effects 1: <https://www.w3.org/TR/filter-effects/#funcdef-filter-opacity>
113
#[derive(Debug, Clone, PartialEq)]
114
pub struct Opacity {
115
    proportion: Option<f64>,
116
}
117

            
118
/// Parameters for the `saturate()` filter function
119
///
120
/// Filter Effects 1: <https://www.w3.org/TR/filter-effects/#funcdef-filter-saturate>
121
#[derive(Debug, Clone, PartialEq)]
122
pub struct Saturate {
123
    proportion: Option<f64>,
124
}
125

            
126
/// Parameters for the `sepia()` filter function
127
///
128
/// Filter Effects 1: <https://www.w3.org/TR/filter-effects/#funcdef-filter-sepia>
129
#[derive(Debug, Clone, PartialEq)]
130
pub struct Sepia {
131
    proportion: Option<f64>,
132
}
133

            
134
/// Reads an optional number or percentage from the parser.
135
/// Negative numbers are not allowed.
136
233
fn parse_num_or_percentage(parser: &mut Parser<'_, '_>) -> Option<f64> {
137
233
    match parser.try_parse(|p| NumberOrPercentage::parse(p)) {
138
219
        Ok(NumberOrPercentage { value }) if value < 0.0 => None,
139
218
        Ok(NumberOrPercentage { value }) => Some(value),
140
14
        Err(_) => None,
141
    }
142
233
}
143

            
144
/// Reads an optional number or percentage from the parser, returning a value clamped to [0, 1].
145
/// Negative numbers are not allowed.
146
167
fn parse_num_or_percentage_clamped(parser: &mut Parser<'_, '_>) -> Option<f64> {
147
167
    parse_num_or_percentage(parser).map(|value| value.clamp(0.0, 1.0))
148
167
}
149

            
150
57759
fn parse_function<'i, F>(
151
57759
    parser: &mut Parser<'i, '_>,
152
57759
    name: &str,
153
57759
    f: F,
154
57759
) -> Result<FilterFunction, ParseError<'i>>
155
57759
where
156
57759
    F: for<'tt> FnOnce(&mut Parser<'i, 'tt>) -> Result<FilterFunction, ParseError<'i>>,
157
57759
{
158
57759
    parser.expect_function_matching(name)?;
159
345
    parser.parse_nested_block(f)
160
57759
}
161

            
162
// This function doesn't fail, but returns a Result like the other parsers, so tell Clippy
163
// about that.
164
#[allow(clippy::unnecessary_wraps)]
165
61
fn parse_blur<'i>(parser: &mut Parser<'i, '_>) -> Result<FilterFunction, ParseError<'i>> {
166
61
    let length = parser.try_parse(|p| Length::parse(p)).ok();
167
61

            
168
61
    Ok(FilterFunction::Blur(Blur {
169
61
        std_deviation: length,
170
61
    }))
171
61
}
172

            
173
#[allow(clippy::unnecessary_wraps)]
174
22
fn parse_brightness<'i>(parser: &mut Parser<'i, '_>) -> Result<FilterFunction, ParseError<'i>> {
175
22
    let proportion = parse_num_or_percentage(parser);
176
22

            
177
22
    Ok(FilterFunction::Brightness(Brightness { proportion }))
178
22
}
179

            
180
#[allow(clippy::unnecessary_wraps)]
181
22
fn parse_contrast<'i>(parser: &mut Parser<'i, '_>) -> Result<FilterFunction, ParseError<'i>> {
182
22
    let proportion = parse_num_or_percentage(parser);
183
22

            
184
22
    Ok(FilterFunction::Contrast(Contrast { proportion }))
185
22
}
186
#[allow(clippy::unnecessary_wraps)]
187
28
fn parse_dropshadow<'i>(parser: &mut Parser<'i, '_>) -> Result<FilterFunction, ParseError<'i>> {
188
28
    let mut result = DropShadow {
189
28
        color: None,
190
28
        dx: None,
191
28
        dy: None,
192
28
        std_deviation: None,
193
28
    };
194
28

            
195
28
    result.color = parser.try_parse(Color::parse).ok();
196

            
197
    // if dx is provided, dy must follow and an optional std_dev must follow that.
198
28
    if let Ok(dx) = parser.try_parse(Length::parse) {
199
28
        result.dx = Some(dx);
200
28
        result.dy = Some(parser.try_parse(Length::parse)?);
201
26
        result.std_deviation = parser.try_parse(ULength::parse).ok();
202
    }
203

            
204
26
    let loc = parser.current_source_location();
205

            
206
    // because the color and length arguments can be provided in either order,
207
    // check again after potentially parsing lengths if the color is now provided.
208
    // if a color is provided both before and after, that is an error.
209
26
    if let Ok(c) = parser.try_parse(Color::parse) {
210
3
        if result.color.is_some() {
211
1
            return Err(
212
1
                loc.new_custom_error(ValueErrorKind::Value("color already specified".to_string()))
213
1
            );
214
2
        } else {
215
2
            result.color = Some(c);
216
2
        }
217
23
    }
218

            
219
25
    Ok(FilterFunction::DropShadow(result))
220
28
}
221

            
222
#[allow(clippy::unnecessary_wraps)]
223
22
fn parse_grayscale<'i>(parser: &mut Parser<'i, '_>) -> Result<FilterFunction, ParseError<'i>> {
224
22
    let proportion = parse_num_or_percentage_clamped(parser);
225
22

            
226
22
    Ok(FilterFunction::Grayscale(Grayscale { proportion }))
227
22
}
228

            
229
#[allow(clippy::unnecessary_wraps)]
230
23
fn parse_huerotate<'i>(parser: &mut Parser<'i, '_>) -> Result<FilterFunction, ParseError<'i>> {
231
23
    let angle = parser.try_parse(|p| Angle::parse(p)).ok();
232
23

            
233
23
    Ok(FilterFunction::HueRotate(HueRotate { angle }))
234
23
}
235

            
236
#[allow(clippy::unnecessary_wraps)]
237
22
fn parse_invert<'i>(parser: &mut Parser<'i, '_>) -> Result<FilterFunction, ParseError<'i>> {
238
22
    let proportion = parse_num_or_percentage_clamped(parser);
239
22

            
240
22
    Ok(FilterFunction::Invert(Invert { proportion }))
241
22
}
242

            
243
#[allow(clippy::unnecessary_wraps)]
244
98
fn parse_opacity<'i>(parser: &mut Parser<'i, '_>) -> Result<FilterFunction, ParseError<'i>> {
245
98
    let proportion = parse_num_or_percentage_clamped(parser);
246
98

            
247
98
    Ok(FilterFunction::Opacity(Opacity { proportion }))
248
98
}
249

            
250
#[allow(clippy::unnecessary_wraps)]
251
22
fn parse_saturate<'i>(parser: &mut Parser<'i, '_>) -> Result<FilterFunction, ParseError<'i>> {
252
22
    let proportion = parse_num_or_percentage(parser);
253
22

            
254
22
    Ok(FilterFunction::Saturate(Saturate { proportion }))
255
22
}
256

            
257
#[allow(clippy::unnecessary_wraps)]
258
25
fn parse_sepia<'i>(parser: &mut Parser<'i, '_>) -> Result<FilterFunction, ParseError<'i>> {
259
25
    let proportion = parse_num_or_percentage_clamped(parser);
260
25

            
261
25
    Ok(FilterFunction::Sepia(Sepia { proportion }))
262
25
}
263

            
264
impl Blur {
265
57
    fn to_filter_spec(&self, params: &NormalizeParams) -> FilterSpec {
266
57
        // The 0.0 default is from the spec
267
57
        let std_dev = self.std_deviation.map(|l| l.to_user(params)).unwrap_or(0.0);
268
57

            
269
57
        let user_space_filter = Filter::default().to_user_space(params);
270
57

            
271
57
        let gaussian_blur = ResolvedPrimitive {
272
57
            primitive: Primitive::default(),
273
57
            params: PrimitiveParams::GaussianBlur(GaussianBlur {
274
57
                std_deviation: NumberOptionalNumber(std_dev, std_dev),
275
57
                ..GaussianBlur::default()
276
57
            }),
277
57
        }
278
57
        .into_user_space(params);
279
57

            
280
57
        FilterSpec {
281
57
            name: "blur()".to_string(),
282
57
            user_space_filter,
283
57
            primitives: vec![gaussian_blur],
284
57
        }
285
57
    }
286
}
287

            
288
impl Brightness {
289
19
    fn to_filter_spec(&self, params: &NormalizeParams) -> FilterSpec {
290
19
        let user_space_filter = Filter::default().to_user_space(params);
291
19
        let slope = self.proportion.unwrap_or(1.0);
292
19

            
293
19
        let brightness = ResolvedPrimitive {
294
19
            primitive: Primitive::default(),
295
19
            params: PrimitiveParams::ComponentTransfer(component_transfer::ComponentTransfer {
296
19
                functions: component_transfer::Functions {
297
19
                    r: FeFuncR(FeFuncCommon {
298
19
                        function_type: component_transfer::FunctionType::Linear,
299
19
                        slope,
300
19
                        ..FeFuncCommon::default()
301
19
                    }),
302
19
                    g: FeFuncG(FeFuncCommon {
303
19
                        function_type: component_transfer::FunctionType::Linear,
304
19
                        slope,
305
19
                        ..FeFuncCommon::default()
306
19
                    }),
307
19
                    b: FeFuncB(FeFuncCommon {
308
19
                        function_type: component_transfer::FunctionType::Linear,
309
19
                        slope,
310
19
                        ..FeFuncCommon::default()
311
19
                    }),
312
19
                    a: FeFuncA::default(),
313
19
                },
314
19
                ..component_transfer::ComponentTransfer::default()
315
19
            }),
316
19
        }
317
19
        .into_user_space(params);
318
19

            
319
19
        FilterSpec {
320
19
            name: "brightness()".to_string(),
321
19
            user_space_filter,
322
19
            primitives: vec![brightness],
323
19
        }
324
19
    }
325
}
326

            
327
impl Contrast {
328
19
    fn to_filter_spec(&self, params: &NormalizeParams) -> FilterSpec {
329
19
        let user_space_filter = Filter::default().to_user_space(params);
330
19
        let slope = self.proportion.unwrap_or(1.0);
331
19
        let intercept = -(0.5 * slope) + 0.5;
332
19

            
333
19
        let contrast = ResolvedPrimitive {
334
19
            primitive: Primitive::default(),
335
19
            params: PrimitiveParams::ComponentTransfer(component_transfer::ComponentTransfer {
336
19
                functions: component_transfer::Functions {
337
19
                    r: FeFuncR(FeFuncCommon {
338
19
                        function_type: component_transfer::FunctionType::Linear,
339
19
                        slope,
340
19
                        intercept,
341
19
                        ..FeFuncCommon::default()
342
19
                    }),
343
19
                    g: FeFuncG(FeFuncCommon {
344
19
                        function_type: component_transfer::FunctionType::Linear,
345
19
                        slope,
346
19
                        intercept,
347
19
                        ..FeFuncCommon::default()
348
19
                    }),
349
19
                    b: FeFuncB(FeFuncCommon {
350
19
                        function_type: component_transfer::FunctionType::Linear,
351
19
                        slope,
352
19
                        intercept,
353
19
                        ..FeFuncCommon::default()
354
19
                    }),
355
19
                    a: FeFuncA::default(),
356
19
                },
357
19
                ..component_transfer::ComponentTransfer::default()
358
19
            }),
359
19
        }
360
19
        .into_user_space(params);
361
19

            
362
19
        FilterSpec {
363
19
            name: "contrast()".to_string(),
364
19
            user_space_filter,
365
19
            primitives: vec![contrast],
366
19
        }
367
19
    }
368
}
369

            
370
/// Creates the filter primitives required for a `feDropShadow` effect.
371
///
372
/// Both the `drop-shadow()` filter function and the `feDropShadow` element need to create
373
/// a sequence of filter primitives (blur, offset, etc.) to build the drop shadow.  This
374
/// function builds that sequence.
375
38
pub fn drop_shadow_primitives(
376
38
    dx: f64,
377
38
    dy: f64,
378
38
    std_deviation: NumberOptionalNumber<f64>,
379
38
    color: Color,
380
38
) -> Vec<ResolvedPrimitive> {
381
38
    let offsetblur = CustomIdent("offsetblur".to_string());
382
38

            
383
38
    let gaussian_blur = ResolvedPrimitive {
384
38
        primitive: Primitive::default(),
385
38
        params: PrimitiveParams::GaussianBlur(GaussianBlur {
386
38
            in1: Input::SourceAlpha,
387
38
            std_deviation,
388
38
            ..GaussianBlur::default()
389
38
        }),
390
38
    };
391
38

            
392
38
    let offset = ResolvedPrimitive {
393
38
        primitive: Primitive {
394
38
            result: Some(offsetblur.clone()),
395
38
            ..Primitive::default()
396
38
        },
397
38
        params: PrimitiveParams::Offset(Offset {
398
38
            in1: Input::default(),
399
38
            dx,
400
38
            dy,
401
38
        }),
402
38
    };
403
38

            
404
38
    let flood = ResolvedPrimitive {
405
38
        primitive: Primitive::default(),
406
38
        params: PrimitiveParams::Flood(Flood { color }),
407
38
    };
408
38

            
409
38
    let composite = ResolvedPrimitive {
410
38
        primitive: Primitive::default(),
411
38
        params: PrimitiveParams::Composite(Composite {
412
38
            in2: Input::FilterOutput(offsetblur),
413
38
            operator: Operator::In,
414
38
            ..Composite::default()
415
38
        }),
416
38
    };
417
38

            
418
38
    let merge = ResolvedPrimitive {
419
38
        primitive: Primitive::default(),
420
38
        params: PrimitiveParams::Merge(Merge {
421
38
            merge_nodes: vec![
422
38
                MergeNode::default(),
423
38
                MergeNode {
424
38
                    in1: Input::SourceGraphic,
425
38
                    ..MergeNode::default()
426
38
                },
427
38
            ],
428
38
        }),
429
38
    };
430
38

            
431
38
    vec![gaussian_blur, offset, flood, composite, merge]
432
38
}
433

            
434
impl DropShadow {
435
    /// Converts a DropShadow into the set of filter element primitives.
436
    ///
437
    /// See <https://www.w3.org/TR/filter-effects/#dropshadowEquivalent>.
438
19
    fn to_filter_spec(&self, params: &NormalizeParams, default_color: Color) -> FilterSpec {
439
19
        let user_space_filter = Filter::default().to_user_space(params);
440
19
        let dx = self.dx.map(|l| l.to_user(params)).unwrap_or(0.0);
441
19
        let dy = self.dy.map(|l| l.to_user(params)).unwrap_or(0.0);
442
19
        let std_dev = self.std_deviation.map(|l| l.to_user(params)).unwrap_or(0.0);
443
19
        let std_deviation = NumberOptionalNumber(std_dev, std_dev);
444
19
        let color = self
445
19
            .color
446
19
            .as_ref()
447
19
            .map(|c| resolve_color(c, UnitInterval::clamp(1.0), &default_color))
448
19
            .unwrap_or(default_color);
449
19

            
450
19
        let resolved_primitives = drop_shadow_primitives(dx, dy, std_deviation, color);
451
19

            
452
19
        let primitives = resolved_primitives
453
19
            .into_iter()
454
95
            .map(|p| p.into_user_space(params))
455
19
            .collect();
456
19

            
457
19
        FilterSpec {
458
19
            name: "drop-shadow()".to_string(),
459
19
            user_space_filter,
460
19
            primitives,
461
19
        }
462
19
    }
463
}
464

            
465
impl Grayscale {
466
19
    fn to_filter_spec(&self, params: &NormalizeParams) -> FilterSpec {
467
19
        // grayscale is implemented as the inverse of a saturate operation,
468
19
        // with the input clamped to the range [0, 1] by the parser.
469
19
        let p = 1.0 - self.proportion.unwrap_or(1.0);
470
19
        let saturate = Saturate {
471
19
            proportion: Some(p),
472
19
        };
473
19

            
474
19
        let user_space_filter = Filter::default().to_user_space(params);
475
19
        let primitive = saturate.to_user_space_primitive(params);
476
19

            
477
19
        FilterSpec {
478
19
            name: "grayscale".to_string(),
479
19
            user_space_filter,
480
19
            primitives: vec![primitive],
481
19
        }
482
19
    }
483
}
484

            
485
impl HueRotate {
486
19
    fn to_filter_spec(&self, params: &NormalizeParams) -> FilterSpec {
487
19
        let rads = self.angle.map(|a| a.radians()).unwrap_or(0.0);
488
19
        let user_space_filter = Filter::default().to_user_space(params);
489
19

            
490
19
        let huerotate = ResolvedPrimitive {
491
19
            primitive: Primitive::default(),
492
19
            params: PrimitiveParams::ColorMatrix(ColorMatrix {
493
19
                matrix: ColorMatrix::hue_rotate_matrix(rads),
494
19
                ..ColorMatrix::default()
495
19
            }),
496
19
        }
497
19
        .into_user_space(params);
498
19

            
499
19
        FilterSpec {
500
19
            name: "hue-rotate".to_string(),
501
19
            user_space_filter,
502
19
            primitives: vec![huerotate],
503
19
        }
504
19
    }
505
}
506

            
507
impl Invert {
508
19
    fn to_filter_spec(&self, params: &NormalizeParams) -> FilterSpec {
509
19
        let p = self.proportion.unwrap_or(1.0);
510
19
        let user_space_filter = Filter::default().to_user_space(params);
511
19

            
512
19
        let invert = ResolvedPrimitive {
513
19
            primitive: Primitive::default(),
514
19
            params: PrimitiveParams::ComponentTransfer(component_transfer::ComponentTransfer {
515
19
                functions: component_transfer::Functions {
516
19
                    r: FeFuncR(FeFuncCommon {
517
19
                        function_type: component_transfer::FunctionType::Table,
518
19
                        table_values: vec![p, 1.0 - p],
519
19
                        ..FeFuncCommon::default()
520
19
                    }),
521
19
                    g: FeFuncG(FeFuncCommon {
522
19
                        function_type: component_transfer::FunctionType::Table,
523
19
                        table_values: vec![p, 1.0 - p],
524
19
                        ..FeFuncCommon::default()
525
19
                    }),
526
19
                    b: FeFuncB(FeFuncCommon {
527
19
                        function_type: component_transfer::FunctionType::Table,
528
19
                        table_values: vec![p, 1.0 - p],
529
19
                        ..FeFuncCommon::default()
530
19
                    }),
531
19
                    a: FeFuncA::default(),
532
19
                },
533
19
                ..component_transfer::ComponentTransfer::default()
534
19
            }),
535
19
        }
536
19
        .into_user_space(params);
537
19

            
538
19
        FilterSpec {
539
19
            name: "invert".to_string(),
540
19
            user_space_filter,
541
19
            primitives: vec![invert],
542
19
        }
543
19
    }
544
}
545

            
546
impl Opacity {
547
95
    fn to_filter_spec(&self, params: &NormalizeParams) -> FilterSpec {
548
95
        let p = self.proportion.unwrap_or(1.0);
549
95
        let user_space_filter = Filter::default().to_user_space(params);
550
95

            
551
95
        let opacity = ResolvedPrimitive {
552
95
            primitive: Primitive::default(),
553
95
            params: PrimitiveParams::ComponentTransfer(component_transfer::ComponentTransfer {
554
95
                functions: component_transfer::Functions {
555
95
                    a: FeFuncA(FeFuncCommon {
556
95
                        function_type: component_transfer::FunctionType::Table,
557
95
                        table_values: vec![0.0, p],
558
95
                        ..FeFuncCommon::default()
559
95
                    }),
560
95
                    ..component_transfer::Functions::default()
561
95
                },
562
95
                ..component_transfer::ComponentTransfer::default()
563
95
            }),
564
95
        }
565
95
        .into_user_space(params);
566
95

            
567
95
        FilterSpec {
568
95
            name: "opacity".to_string(),
569
95
            user_space_filter,
570
95
            primitives: vec![opacity],
571
95
        }
572
95
    }
573
}
574

            
575
impl Saturate {
576
    #[rustfmt::skip]
577
38
    fn matrix(&self) -> nalgebra::Matrix5<f64> {
578
38
        let p = self.proportion.unwrap_or(1.0);
579
38

            
580
38
        nalgebra::Matrix5::new(
581
38
            0.213 + 0.787 * p, 0.715 - 0.715 * p, 0.072 - 0.072 * p, 0.0, 0.0,
582
38
            0.213 - 0.213 * p, 0.715 + 0.285 * p, 0.072 - 0.072 * p, 0.0, 0.0,
583
38
            0.213 - 0.213 * p, 0.715 - 0.715 * p, 0.072 + 0.928 * p, 0.0, 0.0,
584
38
            0.0,               0.0,               0.0,               1.0, 0.0,
585
38
            0.0,               0.0,               0.0,               0.0, 1.0,
586
38
        )
587
38
    }
588

            
589
38
    fn to_user_space_primitive(&self, params: &NormalizeParams) -> UserSpacePrimitive {
590
38
        ResolvedPrimitive {
591
38
            primitive: Primitive::default(),
592
38
            params: PrimitiveParams::ColorMatrix(ColorMatrix {
593
38
                matrix: self.matrix(),
594
38
                ..ColorMatrix::default()
595
38
            }),
596
38
        }
597
38
        .into_user_space(params)
598
38
    }
599

            
600
19
    fn to_filter_spec(&self, params: &NormalizeParams) -> FilterSpec {
601
19
        let user_space_filter = Filter::default().to_user_space(params);
602
19

            
603
19
        let saturate = self.to_user_space_primitive(params);
604
19

            
605
19
        FilterSpec {
606
19
            name: "saturate".to_string(),
607
19
            user_space_filter,
608
19
            primitives: vec![saturate],
609
19
        }
610
19
    }
611
}
612

            
613
impl Sepia {
614
    #[rustfmt::skip]
615
19
    fn matrix(&self) -> nalgebra::Matrix5<f64> {
616
19
        let p = self.proportion.unwrap_or(1.0);
617
19

            
618
19
        nalgebra::Matrix5::new(
619
19
            0.393 + 0.607 * (1.0 - p), 0.769 - 0.769 * (1.0 - p), 0.189 - 0.189 * (1.0 - p), 0.0, 0.0,
620
19
            0.349 - 0.349 * (1.0 - p), 0.686 + 0.314 * (1.0 - p), 0.168 - 0.168 * (1.0 - p), 0.0, 0.0,
621
19
            0.272 - 0.272 * (1.0 - p), 0.534 - 0.534 * (1.0 - p), 0.131 + 0.869 * (1.0 - p), 0.0, 0.0,
622
19
            0.0,                       0.0,                       0.0,                       1.0, 0.0,
623
19
            0.0,                       0.0,                       0.0,                       0.0, 1.0,
624
19
        )
625
19
    }
626

            
627
19
    fn to_filter_spec(&self, params: &NormalizeParams) -> FilterSpec {
628
19
        let user_space_filter = Filter::default().to_user_space(params);
629
19

            
630
19
        let sepia = ResolvedPrimitive {
631
19
            primitive: Primitive::default(),
632
19
            params: PrimitiveParams::ColorMatrix(ColorMatrix {
633
19
                matrix: self.matrix(),
634
19
                ..ColorMatrix::default()
635
19
            }),
636
19
        }
637
19
        .into_user_space(params);
638
19

            
639
19
        FilterSpec {
640
19
            name: "sepia".to_string(),
641
19
            user_space_filter,
642
19
            primitives: vec![sepia],
643
19
        }
644
19
    }
645
}
646

            
647
impl Parse for FilterFunction {
648
    #[allow(clippy::type_complexity)]
649
    #[rustfmt::skip]
650
5922
    fn parse<'i>(parser: &mut Parser<'i, '_>) -> Result<Self, crate::error::ParseError<'i>> {
651
5922
        let loc = parser.current_source_location();
652
5922
        let fns: Vec<(&str, &dyn Fn(&mut Parser<'i, '_>) -> _)> = vec![
653
5922
            ("blur",        &parse_blur),
654
5922
            ("brightness",  &parse_brightness),
655
5922
            ("contrast",    &parse_contrast),
656
5922
            ("drop-shadow", &parse_dropshadow),
657
5922
            ("grayscale",   &parse_grayscale),
658
5922
            ("hue-rotate",  &parse_huerotate),
659
5922
            ("invert",      &parse_invert),
660
5922
            ("opacity",     &parse_opacity),
661
5922
            ("saturate",    &parse_saturate),
662
5922
            ("sepia",       &parse_sepia),
663
5922
        ];
664

            
665
63350
        for (filter_name, parse_fn) in fns {
666
57759
            if let Ok(func) = parser.try_parse(|p| parse_function(p, filter_name, parse_fn)) {
667
331
                return Ok(func);
668
57428
            }
669
        }
670

            
671
5591
        Err(loc.new_custom_error(ValueErrorKind::parse_error("expected filter function")))
672
5922
    }
673
}
674

            
675
impl FilterFunction {
676
    // If this function starts actually returning an Err, remove this Clippy exception:
677
    #[allow(clippy::unnecessary_wraps)]
678
304
    pub fn to_filter_spec(&self, params: &NormalizeParams, current_color: Color) -> FilterSpec {
679
304
        match self {
680
57
            FilterFunction::Blur(v) => v.to_filter_spec(params),
681
19
            FilterFunction::Brightness(v) => v.to_filter_spec(params),
682
19
            FilterFunction::Contrast(v) => v.to_filter_spec(params),
683
19
            FilterFunction::DropShadow(v) => v.to_filter_spec(params, current_color),
684
19
            FilterFunction::Grayscale(v) => v.to_filter_spec(params),
685
19
            FilterFunction::HueRotate(v) => v.to_filter_spec(params),
686
19
            FilterFunction::Invert(v) => v.to_filter_spec(params),
687
95
            FilterFunction::Opacity(v) => v.to_filter_spec(params),
688
19
            FilterFunction::Saturate(v) => v.to_filter_spec(params),
689
19
            FilterFunction::Sepia(v) => v.to_filter_spec(params),
690
        }
691
304
    }
692
}
693

            
694
#[cfg(test)]
695
mod tests {
696
    use cssparser::RGBA;
697

            
698
    use super::*;
699

            
700
    #[test]
701
1
    fn parses_blur() {
702
1
        assert_eq!(
703
1
            FilterFunction::parse_str("blur()").unwrap(),
704
1
            FilterFunction::Blur(Blur {
705
1
                std_deviation: None
706
1
            })
707
1
        );
708

            
709
1
        assert_eq!(
710
1
            FilterFunction::parse_str("blur(5px)").unwrap(),
711
1
            FilterFunction::Blur(Blur {
712
1
                std_deviation: Some(Length::new(5.0, LengthUnit::Px))
713
1
            })
714
1
        );
715
1
    }
716

            
717
    #[test]
718
1
    fn parses_brightness() {
719
1
        assert_eq!(
720
1
            FilterFunction::parse_str("brightness()").unwrap(),
721
1
            FilterFunction::Brightness(Brightness { proportion: None })
722
1
        );
723

            
724
1
        assert_eq!(
725
1
            FilterFunction::parse_str("brightness(50%)").unwrap(),
726
1
            FilterFunction::Brightness(Brightness {
727
1
                proportion: Some(0.50_f32.into()),
728
1
            })
729
1
        );
730
1
    }
731

            
732
    #[test]
733
1
    fn parses_contrast() {
734
1
        assert_eq!(
735
1
            FilterFunction::parse_str("contrast()").unwrap(),
736
1
            FilterFunction::Contrast(Contrast { proportion: None })
737
1
        );
738

            
739
1
        assert_eq!(
740
1
            FilterFunction::parse_str("contrast(50%)").unwrap(),
741
1
            FilterFunction::Contrast(Contrast {
742
1
                proportion: Some(0.50_f32.into()),
743
1
            })
744
1
        );
745
1
    }
746

            
747
    #[test]
748
1
    fn parses_dropshadow() {
749
1
        assert_eq!(
750
1
            FilterFunction::parse_str("drop-shadow(4px 5px)").unwrap(),
751
1
            FilterFunction::DropShadow(DropShadow {
752
1
                color: None,
753
1
                dx: Some(Length::new(4.0, LengthUnit::Px)),
754
1
                dy: Some(Length::new(5.0, LengthUnit::Px)),
755
1
                std_deviation: None,
756
1
            })
757
1
        );
758

            
759
1
        assert_eq!(
760
1
            FilterFunction::parse_str("drop-shadow(#ff0000 4px 5px 32px)").unwrap(),
761
1
            FilterFunction::DropShadow(DropShadow {
762
1
                color: Some(Color::Rgba(RGBA {
763
1
                    red: Some(255),
764
1
                    green: Some(0),
765
1
                    blue: Some(0),
766
1
                    alpha: Some(1.0)
767
1
                })),
768
1
                dx: Some(Length::new(4.0, LengthUnit::Px)),
769
1
                dy: Some(Length::new(5.0, LengthUnit::Px)),
770
1
                std_deviation: Some(ULength::new(32.0, LengthUnit::Px)),
771
1
            })
772
1
        );
773

            
774
1
        assert_eq!(
775
1
            FilterFunction::parse_str("drop-shadow(1px 2px blue)").unwrap(),
776
1
            FilterFunction::DropShadow(DropShadow {
777
1
                color: Some(Color::Rgba(RGBA {
778
1
                    red: Some(0),
779
1
                    green: Some(0),
780
1
                    blue: Some(255),
781
1
                    alpha: Some(1.0)
782
1
                })),
783
1
                dx: Some(Length::new(1.0, LengthUnit::Px)),
784
1
                dy: Some(Length::new(2.0, LengthUnit::Px)),
785
1
                std_deviation: None,
786
1
            })
787
1
        );
788

            
789
1
        assert_eq!(
790
1
            FilterFunction::parse_str("drop-shadow(1px 2px 3px currentColor)").unwrap(),
791
1
            FilterFunction::DropShadow(DropShadow {
792
1
                color: Some(Color::CurrentColor),
793
1
                dx: Some(Length::new(1.0, LengthUnit::Px)),
794
1
                dy: Some(Length::new(2.0, LengthUnit::Px)),
795
1
                std_deviation: Some(ULength::new(3.0, LengthUnit::Px)),
796
1
            })
797
1
        );
798

            
799
1
        assert_eq!(
800
1
            FilterFunction::parse_str("drop-shadow(1 2 3)").unwrap(),
801
1
            FilterFunction::DropShadow(DropShadow {
802
1
                color: None,
803
1
                dx: Some(Length::new(1.0, LengthUnit::Px)),
804
1
                dy: Some(Length::new(2.0, LengthUnit::Px)),
805
1
                std_deviation: Some(ULength::new(3.0, LengthUnit::Px)),
806
1
            })
807
1
        );
808
1
    }
809

            
810
    #[test]
811
1
    fn parses_grayscale() {
812
1
        assert_eq!(
813
1
            FilterFunction::parse_str("grayscale()").unwrap(),
814
1
            FilterFunction::Grayscale(Grayscale { proportion: None })
815
1
        );
816

            
817
1
        assert_eq!(
818
1
            FilterFunction::parse_str("grayscale(50%)").unwrap(),
819
1
            FilterFunction::Grayscale(Grayscale {
820
1
                proportion: Some(0.50_f32.into()),
821
1
            })
822
1
        );
823
1
    }
824

            
825
    #[test]
826
1
    fn parses_huerotate() {
827
1
        assert_eq!(
828
1
            FilterFunction::parse_str("hue-rotate()").unwrap(),
829
1
            FilterFunction::HueRotate(HueRotate { angle: None })
830
1
        );
831

            
832
1
        assert_eq!(
833
1
            FilterFunction::parse_str("hue-rotate(0)").unwrap(),
834
1
            FilterFunction::HueRotate(HueRotate {
835
1
                angle: Some(Angle::new(0.0))
836
1
            })
837
1
        );
838

            
839
1
        assert_eq!(
840
1
            FilterFunction::parse_str("hue-rotate(128deg)").unwrap(),
841
1
            FilterFunction::HueRotate(HueRotate {
842
1
                angle: Some(Angle::from_degrees(128.0))
843
1
            })
844
1
        );
845
1
    }
846

            
847
    #[test]
848
1
    fn parses_invert() {
849
1
        assert_eq!(
850
1
            FilterFunction::parse_str("invert()").unwrap(),
851
1
            FilterFunction::Invert(Invert { proportion: None })
852
1
        );
853

            
854
1
        assert_eq!(
855
1
            FilterFunction::parse_str("invert(50%)").unwrap(),
856
1
            FilterFunction::Invert(Invert {
857
1
                proportion: Some(0.50_f32.into()),
858
1
            })
859
1
        );
860
1
    }
861

            
862
    #[test]
863
1
    fn parses_opacity() {
864
1
        assert_eq!(
865
1
            FilterFunction::parse_str("opacity()").unwrap(),
866
1
            FilterFunction::Opacity(Opacity { proportion: None })
867
1
        );
868

            
869
1
        assert_eq!(
870
1
            FilterFunction::parse_str("opacity(50%)").unwrap(),
871
1
            FilterFunction::Opacity(Opacity {
872
1
                proportion: Some(0.50_f32.into()),
873
1
            })
874
1
        );
875
1
    }
876

            
877
    #[test]
878
1
    fn parses_saturate() {
879
1
        assert_eq!(
880
1
            FilterFunction::parse_str("saturate()").unwrap(),
881
1
            FilterFunction::Saturate(Saturate { proportion: None })
882
1
        );
883

            
884
1
        assert_eq!(
885
1
            FilterFunction::parse_str("saturate(50%)").unwrap(),
886
1
            FilterFunction::Saturate(Saturate {
887
1
                proportion: Some(0.50_f32.into()),
888
1
            })
889
1
        );
890
1
    }
891

            
892
    #[test]
893
1
    fn parses_sepia() {
894
1
        assert_eq!(
895
1
            FilterFunction::parse_str("sepia()").unwrap(),
896
1
            FilterFunction::Sepia(Sepia { proportion: None })
897
1
        );
898

            
899
1
        assert_eq!(
900
1
            FilterFunction::parse_str("sepia(80%)").unwrap(),
901
1
            FilterFunction::Sepia(Sepia {
902
1
                proportion: Some(0.80_f32.into())
903
1
            })
904
1
        );
905

            
906
1
        assert_eq!(
907
1
            FilterFunction::parse_str("sepia(0.52)").unwrap(),
908
1
            FilterFunction::Sepia(Sepia {
909
1
                proportion: Some(0.52_f32.into())
910
1
            })
911
1
        );
912

            
913
        // values > 1.0 should be clamped to 1.0
914
1
        assert_eq!(
915
1
            FilterFunction::parse_str("sepia(1.5)").unwrap(),
916
1
            FilterFunction::Sepia(Sepia {
917
1
                proportion: Some(1.0)
918
1
            })
919
1
        );
920

            
921
        // negative numbers are invalid.
922
1
        assert_eq!(
923
1
            FilterFunction::parse_str("sepia(-1)").unwrap(),
924
1
            FilterFunction::Sepia(Sepia { proportion: None }),
925
1
        );
926
1
    }
927

            
928
    #[test]
929
1
    fn invalid_blur_yields_error() {
930
1
        assert!(FilterFunction::parse_str("blur(foo)").is_err());
931
1
        assert!(FilterFunction::parse_str("blur(42 43)").is_err());
932
1
    }
933

            
934
    #[test]
935
1
    fn invalid_brightness_yields_error() {
936
1
        assert!(FilterFunction::parse_str("brightness(foo)").is_err());
937
1
    }
938

            
939
    #[test]
940
1
    fn invalid_contrast_yields_error() {
941
1
        assert!(FilterFunction::parse_str("contrast(foo)").is_err());
942
1
    }
943

            
944
    #[test]
945
1
    fn invalid_dropshadow_yields_error() {
946
1
        assert!(FilterFunction::parse_str("drop-shadow(blue 5px green)").is_err());
947
1
        assert!(FilterFunction::parse_str("drop-shadow(blue 5px 5px green)").is_err());
948
1
        assert!(FilterFunction::parse_str("drop-shadow(blue 1px)").is_err());
949
1
        assert!(FilterFunction::parse_str("drop-shadow(1 2 3 4 blue)").is_err());
950
1
    }
951

            
952
    #[test]
953
1
    fn invalid_grayscale_yields_error() {
954
1
        assert!(FilterFunction::parse_str("grayscale(foo)").is_err());
955
1
    }
956

            
957
    #[test]
958
1
    fn invalid_huerotate_yields_error() {
959
1
        assert!(FilterFunction::parse_str("hue-rotate(foo)").is_err());
960
1
    }
961

            
962
    #[test]
963
1
    fn invalid_invert_yields_error() {
964
1
        assert!(FilterFunction::parse_str("invert(foo)").is_err());
965
1
    }
966

            
967
    #[test]
968
1
    fn invalid_opacity_yields_error() {
969
1
        assert!(FilterFunction::parse_str("opacity(foo)").is_err());
970
1
    }
971

            
972
    #[test]
973
1
    fn invalid_saturate_yields_error() {
974
1
        assert!(FilterFunction::parse_str("saturate(foo)").is_err());
975
1
    }
976

            
977
    #[test]
978
1
    fn invalid_sepia_yields_error() {
979
1
        assert!(FilterFunction::parse_str("sepia(foo)").is_err());
980
1
    }
981
}