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() + } +}