[librsvg: 1/4] Implement opacity filter function
- From: Federico Mena Quintero <federico src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [librsvg: 1/4] Implement opacity filter function
- Date: Thu, 13 May 2021 16:04:18 +0000 (UTC)
commit 6c37a0ac21acd39b5e0d4cb8f417f686a6c6288d
Author: John Ledbetter <john ledbetter gmail com>
Date: Wed May 12 10:19:39 2021 -0400
Implement opacity filter function
`opacity()` takes a single optional `number-percentage` argument,
similar to `sepia`(); the value must be clamped to 0-1 and values
less than 0 are invalid.
Opacity is implemented as a `FeComponentTransfer` that modifies the
alpha component.
See:
* https://www.w3.org/TR/filter-effects/#funcdef-filter-opacity
* https://www.w3.org/TR/filter-effects/#opacityEquivalent
src/filter_func.rs | 88 +++++++++++++++++++++++++++++++++++----
src/filters/component_transfer.rs | 36 ++++++++--------
tests/src/filters.rs | 59 ++++++++++++++++++++++++++
3 files changed, 157 insertions(+), 26 deletions(-)
---
diff --git a/src/filter_func.rs b/src/filter_func.rs
index a0ebeea1..ca7bc7c6 100644
--- a/src/filter_func.rs
+++ b/src/filter_func.rs
@@ -1,6 +1,5 @@
use cssparser::Parser;
-use crate::drawing_ctx::DrawingCtx;
use crate::error::*;
use crate::filter::Filter;
use crate::filters::{
@@ -11,6 +10,7 @@ use crate::length::*;
use crate::parsers::{NumberOrPercentage, Parse};
use crate::properties::ComputedValues;
use crate::{coord_units::CoordUnits, filters::color_matrix::ColorMatrix};
+use crate::{drawing_ctx::DrawingCtx, filters::component_transfer};
/// CSS Filter functions from the Filter Effects Module Level 1
///
@@ -18,6 +18,7 @@ use crate::{coord_units::CoordUnits, filters::color_matrix::ColorMatrix};
#[derive(Debug, Clone, PartialEq)]
pub enum FilterFunction {
Blur(Blur),
+ Opacity(Opacity),
Sepia(Sepia),
}
@@ -29,6 +30,14 @@ pub struct Blur {
std_deviation: Option<Length<Both>>,
}
+/// Parameters for the `opacity()` filter function
+///
+/// https://www.w3.org/TR/filter-effects/#funcdef-filter-opacity
+#[derive(Debug, Clone, PartialEq)]
+pub struct Opacity {
+ proportion: Option<f64>,
+}
+
/// Parameters for the `sepia()` filter function
///
/// https://www.w3.org/TR/filter-effects/#funcdef-filter-sepia
@@ -64,6 +73,17 @@ fn parse_blur<'i>(parser: &mut Parser<'i, '_>) -> Result<FilterFunction, ParseEr
}))
}
+#[allow(clippy::unnecessary_wraps)]
+fn parse_opacity<'i>(parser: &mut Parser<'i, '_>) -> Result<FilterFunction, ParseError<'i>> {
+ let proportion = match parser.try_parse(|p| NumberOrPercentage::parse(p)) {
+ Ok(NumberOrPercentage { value }) if value < 0.0 => None,
+ Ok(NumberOrPercentage { value }) => Some(value.clamp(0.0, 1.0)),
+ Err(_) => None,
+ };
+
+ Ok(FilterFunction::Opacity(Opacity { proportion }))
+}
+
#[allow(clippy::unnecessary_wraps)]
fn parse_sepia<'i>(parser: &mut Parser<'i, '_>) -> Result<FilterFunction, ParseError<'i>> {
let proportion = match parser.try_parse(|p| NumberOrPercentage::parse(p)) {
@@ -98,6 +118,34 @@ impl Blur {
}
}
+impl Opacity {
+ fn to_filter_spec(&self, params: &NormalizeParams) -> FilterSpec {
+ let p = self.proportion.unwrap_or(1.0);
+ let user_space_filter = Filter::default().to_user_space(params);
+
+ let opacity = ResolvedPrimitive {
+ primitive: Primitive::default(),
+ params: PrimitiveParams::ComponentTransfer(component_transfer::ComponentTransfer {
+ functions: crate::filters::component_transfer::Functions {
+ a: component_transfer::FeFuncA {
+ function_type: component_transfer::FunctionType::Table,
+ table_values: vec![0.0, p],
+ ..component_transfer::FeFuncA::default()
+ },
+ ..component_transfer::Functions::default()
+ },
+ ..component_transfer::ComponentTransfer::default()
+ }),
+ }
+ .into_user_space(params);
+
+ FilterSpec {
+ user_space_filter,
+ primitives: vec![opacity],
+ }
+ }
+}
+
impl Sepia {
#[rustfmt::skip]
fn matrix(&self) -> nalgebra::Matrix5<f64> {
@@ -134,13 +182,16 @@ impl Sepia {
impl Parse for FilterFunction {
fn parse<'i>(parser: &mut Parser<'i, '_>) -> Result<Self, crate::error::ParseError<'i>> {
let loc = parser.current_source_location();
-
- if let Ok(func) = parser.try_parse(|p| parse_function(p, "blur", parse_blur)) {
- return Ok(func);
- }
-
- if let Ok(func) = parser.try_parse(|p| parse_function(p, "sepia", parse_sepia)) {
- return Ok(func);
+ let fns: Vec<(&str, &dyn Fn(&mut Parser<'i, '_>) -> _)> = vec![
+ ("blur", &parse_blur),
+ ("opacity", &parse_opacity),
+ ("sepia", &parse_sepia),
+ ];
+
+ for (filter_name, parse_fn) in fns {
+ if let Ok(func) = parser.try_parse(|p| parse_function(p, filter_name, parse_fn)) {
+ return Ok(func);
+ }
}
return Err(loc.new_custom_error(ValueErrorKind::parse_error("expected filter function")));
@@ -161,6 +212,7 @@ impl FilterFunction {
match self {
FilterFunction::Blur(v) => Ok(v.to_filter_spec(¶ms)),
+ FilterFunction::Opacity(v) => Ok(v.to_filter_spec(¶ms)),
FilterFunction::Sepia(v) => Ok(v.to_filter_spec(¶ms)),
}
}
@@ -187,6 +239,21 @@ mod tests {
);
}
+ #[test]
+ fn parses_opacity() {
+ assert_eq!(
+ FilterFunction::parse_str("opacity()").unwrap(),
+ FilterFunction::Opacity(Opacity { proportion: None })
+ );
+
+ assert_eq!(
+ FilterFunction::parse_str("opacity(50%)").unwrap(),
+ FilterFunction::Opacity(Opacity {
+ proportion: Some(0.50_f32.into()),
+ })
+ );
+ }
+
#[test]
fn parses_sepia() {
assert_eq!(
@@ -229,6 +296,11 @@ mod tests {
assert!(FilterFunction::parse_str("blur(42 43)").is_err());
}
+ #[test]
+ fn invalid_opacity_yields_error() {
+ assert!(FilterFunction::parse_str("opacity(foo)").is_err());
+ }
+
#[test]
fn invalid_sepia_yields_error() {
assert!(FilterFunction::parse_str("sepia(foo)").is_err());
diff --git a/src/filters/component_transfer.rs b/src/filters/component_transfer.rs
index f879f1f5..799306c8 100644
--- a/src/filters/component_transfer.rs
+++ b/src/filters/component_transfer.rs
@@ -34,9 +34,9 @@ pub struct FeComponentTransfer {
/// Resolved `feComponentTransfer` primitive for rendering.
#[derive(Clone, Default)]
pub struct ComponentTransfer {
- in1: Input,
- functions: Functions,
- color_interpolation_filters: ColorInterpolationFilters,
+ pub in1: Input,
+ pub functions: Functions,
+ pub color_interpolation_filters: ColorInterpolationFilters,
}
impl SetAttributes for FeComponentTransfer {
@@ -48,7 +48,7 @@ impl SetAttributes for FeComponentTransfer {
/// Pixel components that can be influenced by `feComponentTransfer`.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
-enum Channel {
+pub enum Channel {
R,
G,
B,
@@ -57,7 +57,7 @@ enum Channel {
/// Component transfer function types.
#[derive(Clone, Debug, PartialEq)]
-enum FunctionType {
+pub enum FunctionType {
Identity,
Table,
Discrete,
@@ -89,11 +89,11 @@ struct FunctionParameters {
}
#[derive(Clone, Debug, Default, PartialEq)]
-struct Functions {
- r: FeFuncR,
- g: FeFuncG,
- b: FeFuncB,
- a: FeFuncA,
+pub struct Functions {
+ pub r: FeFuncR,
+ pub g: FeFuncG,
+ pub b: FeFuncB,
+ pub a: FeFuncA,
}
/// The compute function type.
@@ -156,14 +156,14 @@ macro_rules! func_x {
($func_name:ident, $channel:expr) => {
#[derive(Clone, Debug, PartialEq)]
pub struct $func_name {
- channel: Channel,
- function_type: FunctionType,
- table_values: Vec<f64>,
- slope: f64,
- intercept: f64,
- amplitude: f64,
- exponent: f64,
- offset: f64,
+ pub channel: Channel,
+ pub function_type: FunctionType,
+ pub table_values: Vec<f64>,
+ pub slope: f64,
+ pub intercept: f64,
+ pub amplitude: f64,
+ pub exponent: f64,
+ pub offset: f64,
}
impl Default for $func_name {
diff --git a/tests/src/filters.rs b/tests/src/filters.rs
index ca4a8fa4..66fac65a 100644
--- a/tests/src/filters.rs
+++ b/tests/src/filters.rs
@@ -156,6 +156,65 @@ fn blur_filter_func() {
.evaluate(&output_surf, "blur_filter_func");
}
+#[test]
+fn opacity_filter_func() {
+ // Create an element with a filter function, and compare it to the
+ // supposed equivalent using the <filter> element.
+ let svg = load_svg(
+ br##"<?xml version="1.0" encoding="UTF-8"?>
+<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="400"
height="400">
+ <rect x="100" y="100" width="200" height="200" fill="lime" filter="opacity(0.75)"/>
+</svg>
+"##,
+ ).unwrap();
+
+ let output_surf = render_document(
+ &svg,
+ SurfaceSize(400, 400),
+ |_| (),
+ cairo::Rectangle {
+ x: 0.0,
+ y: 0.0,
+ width: 400.0,
+ height: 400.0,
+ },
+ )
+ .unwrap();
+
+ let reference = load_svg(
+ br##"<?xml version="1.0" encoding="UTF-8"?>
+<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="400"
height="400">
+ <defs>
+ <filter id="filter">
+ <feComponentTransfer>
+ <feFuncA type="table" tableValues="0 0.75" />
+ </feComponentTransfer>
+ </filter>
+ </defs>
+
+ <rect x="100" y="100" width="200" height="200" fill="lime" filter="url(#filter)"/>
+</svg>
+"##,
+ ).unwrap();
+
+ let reference_surf = render_document(
+ &reference,
+ SurfaceSize(400, 400),
+ |_| (),
+ cairo::Rectangle {
+ x: 0.0,
+ y: 0.0,
+ width: 400.0,
+ height: 400.0,
+ },
+ )
+ .unwrap();
+
+ Reference::from_surface(reference_surf.into_image_surface().unwrap())
+ .compare(&output_surf)
+ .evaluate(&output_surf, "opacity_filter_func");
+}
+
#[test]
fn sepia_filter_func() {
// Create an element with a filter function, and compare it to the
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]