diff --git a/crates/svg-filters/src/tests.rs b/crates/svg-filters/src/tests.rs
index 72b970f..771ab3a 100644
--- a/crates/svg-filters/src/tests.rs
+++ b/crates/svg-filters/src/tests.rs
@@ -5,7 +5,44 @@ mod complex;
mod gaussian_blur;
mod offset;
-mod component_transfer {}
+mod component_transfer {
+ use crate::{
+ codegen::SvgDocument,
+ types::nodes::primitives::{
+ component_transfer::{ComponentTransfer, TransferFn},
+ FePrimitive,
+ },
+ Node,
+ };
+
+ #[test]
+ fn test_comp_trans_simple() {
+ let mut doc = SvgDocument::new();
+
+ let comptrans = doc.create_filter("comp_trans");
+
+ comptrans.add_node(Node::simple(FePrimitive::ComponentTransfer(
+ ComponentTransfer {
+ func_r: TransferFn::Table {
+ table_values: vec![0., 0.1, 0.4, 0.9],
+ },
+ func_g: TransferFn::Discrete {
+ table_values: vec![0.1, 0.3, 0.5, 0.7, 0.9],
+ },
+ func_b: TransferFn::Linear {
+ slope: 1.0,
+ intercept: 0.75,
+ },
+ func_a: TransferFn::Identity,
+ },
+ )));
+
+ assert_eq!(
+ doc.generate_svg(),
+ r#""#
+ );
+ }
+}
mod composite {}
mod convolve_matrix {}
mod diffuse_lighting {}
diff --git a/crates/svg-filters/src/types/graph.rs b/crates/svg-filters/src/types/graph.rs
index 05d63c3..524ba92 100644
--- a/crates/svg-filters/src/types/graph.rs
+++ b/crates/svg-filters/src/types/graph.rs
@@ -139,10 +139,12 @@ impl FilterGraph {
}
pub mod abstracted_inputs {
- use petgraph::prelude::NodeIndex;
+ use petgraph::{data::Build, prelude::NodeIndex};
use crate::{
- types::nodes::primitives::{blend::BlendMode, color_matrix::ColorMatrixType},
+ types::nodes::primitives::{
+ blend::BlendMode, color_matrix::ColorMatrixType, component_transfer::TransferFn,
+ },
Node,
};
@@ -211,6 +213,82 @@ pub mod abstracted_inputs {
.add_edge(self.resolve_input(in2.into()), node_idx, ());
node_idx
}
+
+ pub fn component_transfer_rgba(
+ &mut self,
+ r#in: impl Into,
+ r: TransferFn,
+ g: TransferFn,
+ b: TransferFn,
+ a: TransferFn,
+ ) -> NodeIndex {
+ let node_idx = self.dag.add_node(Node::component_transfer_rgba(r, g, b, a));
+ self.dag
+ .add_edge(self.resolve_input(r#in.into()), node_idx, ());
+ node_idx
+ }
+ pub fn component_transfer_rgb(
+ &mut self,
+ r#in: impl Into,
+ r: TransferFn,
+ g: TransferFn,
+ b: TransferFn,
+ ) -> NodeIndex {
+ let node_idx = self.dag.add_node(Node::component_transfer_rgb(r, g, b));
+ self.dag
+ .add_edge(self.resolve_input(r#in.into()), node_idx, ());
+ node_idx
+ }
+ pub fn component_transfer_r(
+ &mut self,
+ r#in: impl Into,
+ func: TransferFn,
+ ) -> NodeIndex {
+ let node_idx = self.dag.add_node(Node::component_transfer_r(func));
+ self.dag
+ .add_edge(self.resolve_input(r#in.into()), node_idx, ());
+ node_idx
+ }
+ pub fn component_transfer_g(
+ &mut self,
+ r#in: impl Into,
+ func: TransferFn,
+ ) -> NodeIndex {
+ let node_idx = self.dag.add_node(Node::component_transfer_g(func));
+ self.dag
+ .add_edge(self.resolve_input(r#in.into()), node_idx, ());
+ node_idx
+ }
+ pub fn component_transfer_b(
+ &mut self,
+ r#in: impl Into,
+ func: TransferFn,
+ ) -> NodeIndex {
+ let node_idx = self.dag.add_node(Node::component_transfer_b(func));
+ self.dag
+ .add_edge(self.resolve_input(r#in.into()), node_idx, ());
+ node_idx
+ }
+ pub fn component_transfer_a(
+ &mut self,
+ r#in: impl Into,
+ func: TransferFn,
+ ) -> NodeIndex {
+ let node_idx = self.dag.add_node(Node::component_transfer_a(func));
+ self.dag
+ .add_edge(self.resolve_input(r#in.into()), node_idx, ());
+ node_idx
+ }
+ pub fn component_transfer_single(
+ &mut self,
+ r#in: impl Into,
+ func: TransferFn,
+ ) -> NodeIndex {
+ let node_idx = self.dag.add_node(Node::component_transfer_single(func));
+ self.dag
+ .add_edge(self.resolve_input(r#in.into()), node_idx, ());
+ node_idx
+ }
}
}
diff --git a/crates/svg-filters/src/types/nodes.rs b/crates/svg-filters/src/types/nodes.rs
index 2edc63b..c9f9334 100644
--- a/crates/svg-filters/src/types/nodes.rs
+++ b/crates/svg-filters/src/types/nodes.rs
@@ -150,4 +150,48 @@ impl Node {
func_a: a,
}))
}
+
+ pub fn component_transfer_rgb(r: TransferFn, g: TransferFn, b: TransferFn) -> Self {
+ Self::component_transfer_rgba(r, g, b, TransferFn::Identity)
+ }
+
+ pub fn component_transfer_r(func: TransferFn) -> Self {
+ Self::component_transfer_rgba(
+ func,
+ TransferFn::Identity,
+ TransferFn::Identity,
+ TransferFn::Identity,
+ )
+ }
+
+ pub fn component_transfer_g(func: TransferFn) -> Self {
+ Self::component_transfer_rgba(
+ TransferFn::Identity,
+ func,
+ TransferFn::Identity,
+ TransferFn::Identity,
+ )
+ }
+
+ pub fn component_transfer_b(func: TransferFn) -> Self {
+ Self::component_transfer_rgba(
+ TransferFn::Identity,
+ TransferFn::Identity,
+ func,
+ TransferFn::Identity,
+ )
+ }
+
+ pub fn component_transfer_a(func: TransferFn) -> Self {
+ Self::component_transfer_rgba(
+ TransferFn::Identity,
+ TransferFn::Identity,
+ TransferFn::Identity,
+ func,
+ )
+ }
+
+ pub fn component_transfer_single(func: TransferFn) -> Self {
+ Self::component_transfer_rgb(func.clone(), func.clone(), func)
+ }
}
diff --git a/crates/svg-filters/src/types/nodes/primitives.rs b/crates/svg-filters/src/types/nodes/primitives.rs
index f3c101a..5cdd232 100644
--- a/crates/svg-filters/src/types/nodes/primitives.rs
+++ b/crates/svg-filters/src/types/nodes/primitives.rs
@@ -1,6 +1,5 @@
-use std::convert::Into;
-
use quick_xml::{events::attributes::Attribute, ElementWriter, Writer};
+use std::convert::Into;
use super::CommonAttrs;
@@ -84,20 +83,20 @@ impl WriteElement for FePrimitive {
match self {
FePrimitive::Blend(el) => el.attrs(),
FePrimitive::ColorMatrix(el) => el.attrs(),
- FePrimitive::ComponentTransfer(_) => todo!(),
+ FePrimitive::ComponentTransfer(el) => el.attrs(),
FePrimitive::Composite(el) => el.attrs(),
+ FePrimitive::GaussianBlur(el) => el.attrs(),
+ FePrimitive::Offset(el) => el.attrs(),
+ FePrimitive::Turbulence(el) => el.attrs(),
FePrimitive::ConvolveMatrix(_) => todo!(),
FePrimitive::DiffuseLighting(_) => todo!(),
FePrimitive::DisplacementMap(_) => todo!(),
FePrimitive::Flood(_) => todo!(),
- FePrimitive::GaussianBlur(el) => el.attrs(),
FePrimitive::Image(_) => todo!(),
FePrimitive::Merge(_) => todo!(),
FePrimitive::Morphology(_) => todo!(),
- FePrimitive::Offset(el) => el.attrs(),
FePrimitive::SpecularLighting(_) => todo!(),
FePrimitive::Tile(_) => todo!(),
- FePrimitive::Turbulence(el) => el.attrs(),
}
}
@@ -105,20 +104,47 @@ impl WriteElement for FePrimitive {
match self {
FePrimitive::Blend(el) => el.tag_name(),
FePrimitive::ColorMatrix(el) => el.tag_name(),
- FePrimitive::ComponentTransfer(_) => todo!(),
+ FePrimitive::ComponentTransfer(el) => el.tag_name(),
FePrimitive::Composite(el) => el.tag_name(),
+ FePrimitive::GaussianBlur(el) => el.tag_name(),
+ FePrimitive::Offset(el) => el.tag_name(),
+ FePrimitive::Turbulence(el) => el.tag_name(),
+ FePrimitive::ConvolveMatrix(_) => todo!(),
+ FePrimitive::DiffuseLighting(_) => todo!(),
+ FePrimitive::DisplacementMap(_) => todo!(),
+ FePrimitive::Flood(_) => todo!(),
+ FePrimitive::Image(_) => todo!(),
+ FePrimitive::Merge(_) => todo!(),
+ FePrimitive::Morphology(_) => todo!(),
+ FePrimitive::SpecularLighting(_) => todo!(),
+ FePrimitive::Tile(_) => todo!(),
+ }
+ }
+
+ fn element_writer<'writer, 'result>(
+ &self,
+ writer: &'writer mut Writer<&'result mut Vec>,
+ common: CommonAttrs,
+ inputs: Vec,
+ output: Option,
+ ) -> quick_xml::Result<&'writer mut quick_xml::Writer<&'result mut Vec>> {
+ match self {
+ FePrimitive::Blend(el) => el.element_writer(writer, common, inputs, output),
+ FePrimitive::ColorMatrix(el) => el.element_writer(writer, common, inputs, output),
+ FePrimitive::ComponentTransfer(el) => el.element_writer(writer, common, inputs, output),
+ FePrimitive::Composite(el) => el.element_writer(writer, common, inputs, output),
+ FePrimitive::Turbulence(el) => el.element_writer(writer, common, inputs, output),
+ FePrimitive::GaussianBlur(el) => el.element_writer(writer, common, inputs, output),
+ FePrimitive::Offset(el) => el.element_writer(writer, common, inputs, output),
FePrimitive::ConvolveMatrix(_) => todo!(),
FePrimitive::DiffuseLighting(_) => todo!(),
FePrimitive::DisplacementMap(_) => todo!(),
FePrimitive::Flood(_) => todo!(),
- FePrimitive::GaussianBlur(el) => el.tag_name(),
FePrimitive::Image(_) => todo!(),
FePrimitive::Merge(_) => todo!(),
FePrimitive::Morphology(_) => todo!(),
- FePrimitive::Offset(el) => el.tag_name(),
FePrimitive::SpecularLighting(_) => todo!(),
FePrimitive::Tile(_) => todo!(),
- FePrimitive::Turbulence(el) => el.tag_name(),
}
}
}
diff --git a/crates/svg-filters/src/types/nodes/primitives/component_transfer.rs b/crates/svg-filters/src/types/nodes/primitives/component_transfer.rs
index d495ecc..3f8a3a3 100644
--- a/crates/svg-filters/src/types/nodes/primitives/component_transfer.rs
+++ b/crates/svg-filters/src/types/nodes/primitives/component_transfer.rs
@@ -1,3 +1,5 @@
+use quick_xml::{events::attributes::Attribute, Writer};
+
use super::WriteElement;
/// [feComponentTransfer](https://www.w3.org/TR/SVG11/filters.html#feComponentTransferElement)
@@ -17,10 +19,48 @@ impl WriteElement for ComponentTransfer {
fn tag_name(&self) -> &'static str {
"feComponentTransfer"
}
+
+ fn element_writer<'writer, 'result>(
+ &self,
+ writer: &'writer mut quick_xml::Writer<&'result mut Vec>,
+ common: crate::types::nodes::CommonAttrs,
+ inputs: Vec,
+ output: Option,
+ ) -> quick_xml::Result<&'writer mut quick_xml::Writer<&'result mut Vec>> {
+ let inputs: Vec<_> = inputs
+ .into_iter()
+ .enumerate()
+ .map(|(i, edge)| {
+ (
+ match i {
+ 0 => "in".to_owned(),
+ n => format!("in{}", n + 1),
+ }
+ .into_bytes(),
+ edge.into_bytes(),
+ )
+ })
+ .collect();
+ let mut el_writer = writer
+ .create_element(self.tag_name())
+ .with_attributes(inputs.iter().map(|(k, v)| (&k[..], &v[..])))
+ .with_attributes(Into::>>::into(common));
+ if let Some(output) = output {
+ el_writer = el_writer.with_attribute(("result", output.as_str()));
+ }
+
+ el_writer.write_inner_content(|writer| {
+ self.func_r.write_self(writer, "feFuncR")?;
+ self.func_g.write_self(writer, "feFuncG")?;
+ self.func_b.write_self(writer, "feFuncB")?;
+ self.func_a.write_self(writer, "feFuncA")?;
+ Ok(())
+ })
+ }
}
/// [transfer functions](https://www.w3.org/TR/SVG11/filters.html#transferFuncElements)
-#[derive(Debug)]
+#[derive(Debug, Clone)]
pub enum TransferFn {
Identity,
Table {
@@ -39,3 +79,56 @@ pub enum TransferFn {
offset: f32,
},
}
+
+impl TransferFn {
+ #[allow(clippy::str_to_string, reason = "inside macro call")]
+ fn write_self<'writer, 'result>(
+ &self,
+ target: &'writer mut Writer<&'result mut Vec>,
+ name: &'static str,
+ ) -> quick_xml::Result<&'writer mut Writer<&'result mut Vec>> {
+ target
+ .create_element(name)
+ .with_attributes(match self {
+ TransferFn::Identity => gen_attrs![b"type": "identity"],
+ TransferFn::Table { table_values } => gen_attrs![
+ b"type": "table",
+ b"tableValues": table_values
+ .iter()
+ .map(std::string::ToString::to_string)
+ .reduce(|mut acc, e| {
+ acc.push(' ');
+ acc.push_str(&e);
+ acc
+ }).expect("empty tables disallowed")
+ ],
+ TransferFn::Discrete { table_values } => gen_attrs![
+ b"type": "discrete",
+ b"tableValues": table_values
+ .iter()
+ .map(std::string::ToString::to_string)
+ .reduce(|mut acc, e| {
+ acc.push(' ');
+ acc.push_str(&e);
+ acc
+ }).expect("empty tables disallowed")
+ ],
+ TransferFn::Linear { slope, intercept } => gen_attrs![
+ b"type": "linear",
+ b"slope": slope,
+ b"intercept": intercept
+ ],
+ TransferFn::Gamma {
+ amplitude,
+ exponent,
+ offset,
+ } => gen_attrs![
+ b"type": "gamma",
+ b"amplitude": amplitude,
+ b"exponent": exponent,
+ b"offset": offset
+ ],
+ })
+ .write_empty()
+ }
+}