From 9727ef82cafe1f50280ee2d097df23dd974f2053 Mon Sep 17 00:00:00 2001 From: Schrottkatze Date: Fri, 22 Mar 2024 16:05:36 +0100 Subject: [PATCH] svg-filters: implement feComponentTransfer rendering --- crates/svg-filters/src/tests.rs | 39 +++++++- .../svg-filters/src/types/nodes/primitives.rs | 31 ++++++- .../nodes/primitives/component_transfer.rs | 93 +++++++++++++++++++ 3 files changed, 160 insertions(+), 3 deletions(-) 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/nodes/primitives.rs b/crates/svg-filters/src/types/nodes/primitives.rs index f3c101a..0b6e758 100644 --- a/crates/svg-filters/src/types/nodes/primitives.rs +++ b/crates/svg-filters/src/types/nodes/primitives.rs @@ -84,7 +84,7 @@ 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::ConvolveMatrix(_) => todo!(), FePrimitive::DiffuseLighting(_) => todo!(), @@ -105,7 +105,7 @@ 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::ConvolveMatrix(_) => todo!(), FePrimitive::DiffuseLighting(_) => todo!(), @@ -121,4 +121,31 @@ impl WriteElement for FePrimitive { FePrimitive::Turbulence(el) => el.tag_name(), } } + + 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::ConvolveMatrix(_) => todo!(), + FePrimitive::DiffuseLighting(_) => todo!(), + FePrimitive::DisplacementMap(_) => todo!(), + FePrimitive::Flood(_) => todo!(), + FePrimitive::GaussianBlur(el) => el.element_writer(writer, common, inputs, output), + FePrimitive::Image(_) => todo!(), + FePrimitive::Merge(_) => todo!(), + FePrimitive::Morphology(_) => todo!(), + FePrimitive::Offset(el) => el.element_writer(writer, common, inputs, output), + FePrimitive::SpecularLighting(_) => todo!(), + FePrimitive::Tile(_) => todo!(), + FePrimitive::Turbulence(el) => el.element_writer(writer, common, inputs, output), + } + } } 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..1c37c50 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,6 +19,44 @@ 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) @@ -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() + } +}