From bf60bdd81426536acb5fffac24d99f1c449e9a76 Mon Sep 17 00:00:00 2001 From: Schrottkatze Date: Sat, 16 Mar 2024 23:57:09 +0100 Subject: [PATCH] svg-filters: simplify and refactor a bit --- crates/svg-filters/src/codegen.rs | 134 +++--------- crates/svg-filters/src/main.rs | 61 +++--- crates/svg-filters/src/types.rs | 66 +----- crates/svg-filters/src/types/graph.rs | 202 ++++++++++++++++++ crates/svg-filters/src/types/graph/edge.rs | 19 ++ crates/svg-filters/src/types/length.rs | 6 + crates/svg-filters/src/types/nodes.rs | 39 +++- .../svg-filters/src/types/nodes/primitives.rs | 36 +++- .../src/types/nodes/primitives/blend.rs | 46 ++++ .../types/nodes/primitives/color_matrix.rs | 12 +- .../src/types/nodes/primitives/flood.rs | 24 +++ .../src/types/nodes/primitives/morphology.rs | 34 +++ .../src/types/nodes/primitives/tile.rs | 12 ++ .../src/types/nodes/primitives/turbulence.rs | 66 +++++- .../src/types/nodes/standard_input.rs | 2 +- 15 files changed, 532 insertions(+), 227 deletions(-) create mode 100644 crates/svg-filters/src/types/graph.rs create mode 100644 crates/svg-filters/src/types/graph/edge.rs diff --git a/crates/svg-filters/src/codegen.rs b/crates/svg-filters/src/codegen.rs index ca0baa3..a8e7b23 100644 --- a/crates/svg-filters/src/codegen.rs +++ b/crates/svg-filters/src/codegen.rs @@ -1,7 +1,9 @@ use std::{ - borrow::Cow, cmp, - collections::{BTreeSet, HashMap, HashSet}, + collections::{BTreeSet, HashMap}, + fmt::Display, + io::Read, + ops::Not, }; use indexmap::IndexMap; @@ -10,11 +12,11 @@ use petgraph::{ graph::DiGraph, prelude::{EdgeIndex, NodeIndex}, }; -use quick_xml::{events::attributes::Attribute, name::QName, ElementWriter}; +use quick_xml::ElementWriter; use crate::{ types::{ - graph::{edge::Edge, FilterGraph}, + graph::{edge::Edge, FilterGraph, NodeInput}, nodes::{primitives::WriteElement, CommonAttrs}, }, Node, @@ -43,7 +45,7 @@ impl SvgDocument { pub fn generate_svg(&self) -> String { let mut result = Vec::new(); - let mut doc_writer = quick_xml::Writer::new_with_indent(&mut result, b' ', 4); + let mut doc_writer = quick_xml::Writer::new_with_indent(&mut result, b' ', 2); doc_writer .create_element("svg") @@ -82,121 +84,35 @@ impl SvgDocument { .map(|(primitive, common_attrs)| (node_idx, primitive, common_attrs)) }) .try_fold(writer, |writer, (node_idx, primitive, common_attrs)| { - let mut el_writer = primitive.element_writer(writer); - el_writer = input_attrs(node_idx, &graph.dag).write_into(el_writer); - el_writer = output_attrs(node_idx, &graph.dag).write_into(el_writer); - el_writer = Attrs::from(*common_attrs).write_into(el_writer); - el_writer.write_empty() + primitive.element_writer( + writer, + *common_attrs, + graph + .inputs(node_idx) + .into_iter() + .map(|v| v.to_string()) + .collect(), + graph + .outputs(node_idx) + .is_empty() + .not() + .then_some(format!("r{}", node_idx.index())), + ) })?; Ok(()) } } -struct Attrs(IndexMap); - -impl Attrs { - fn write_into<'w, 'b>( - self, - el_writer: ElementWriter<'w, &'b mut Vec>, - ) -> ElementWriter<'w, &'b mut Vec> { - let attrs = self.0.iter().map(|(k, v)| (k.as_str(), v.as_str())); - el_writer.with_attributes(attrs) - } -} - -impl From for Attrs { - fn from(value: CommonAttrs) -> Self { - let CommonAttrs { - x, - y, - width, - height, - } = value; - Self(IndexMap::from([ - ("x".to_owned(), x.to_string()), - ("y".to_owned(), y.to_string()), - ("width".to_owned(), width.to_string()), - ("height".to_owned(), height.to_string()), - ])) - } -} - -#[allow( - clippy::unwrap_used, - reason = "in all cases of use of unwrap, it's values we got from the graph so we know it's safe" -)] -fn input_attrs(node_idx: NodeIndex, g: &DiGraph) -> Attrs { - let inputs: Vec = g - .neighbors_directed(node_idx, petgraph::Direction::Incoming) - .collect(); - let node = g.node_weight(node_idx).unwrap(); - - if node.input_count() as usize != inputs.len() { - todo!("proper error handling for wrong numbers of inputs") - } - - let mut inputs = inputs - .into_iter() - .map(|input_idx| { - let edge_idx = g.find_edge(input_idx, node_idx).unwrap(); - let edge = g.edge_weight(edge_idx).unwrap(); - let input_node = g.node_weight(input_idx).unwrap(); - - (input_node, edge_idx, edge) - }) - .collect::>(); - inputs.sort_by(|a, b| a.2.cmp(b.2)); - - let mut uniq = BTreeSet::new(); - let no_duplicates = inputs.iter().all(|(_, _, edge)| uniq.insert(edge)); - - if no_duplicates { - Attrs( - inputs - .into_iter() - .map(|(input, edge_idx, edge)| { - let name = match input { - Node::StdInput(s) => format!("{s:?}"), - Node::Primitive { .. } => format_edge_idx(edge_idx), - }; - (edge.to_string(), name) - }) - .collect(), - ) - } else { - todo!("better error handling for inputs with multiple values") - } -} - -#[allow( - clippy::unwrap_used, - reason = "in all cases of use of unwrap, it's values we got from the graph so we know it's safe" -)] -fn output_attrs(node_idx: NodeIndex, g: &DiGraph) -> Attrs { - let outputs: Vec = g - .neighbors_directed(node_idx, petgraph::Direction::Outgoing) - .collect(); - - match outputs.len().cmp(&1) { - cmp::Ordering::Less => Attrs(IndexMap::new()), - cmp::Ordering::Equal => { - let output = outputs.first().unwrap(); - let edge_idx = g.find_edge(node_idx, *output).unwrap(); - Attrs(IndexMap::from([( - "result".to_string(), - format_edge_idx(edge_idx), - )])) - } - cmp::Ordering::Greater => todo!("better error handling for too many outputs"), - } -} - /// convenience method to avoid fuckups during future changes fn format_edge_idx(idx: EdgeIndex) -> String { format!("edge{}", idx.index()) } +fn format_node_idx(node_idx: NodeIndex) -> String { + format!("r{}", node_idx.index()) +} + mod error { use std::{error::Error, fmt::Display}; diff --git a/crates/svg-filters/src/main.rs b/crates/svg-filters/src/main.rs index 7caef27..c5684de 100644 --- a/crates/svg-filters/src/main.rs +++ b/crates/svg-filters/src/main.rs @@ -2,7 +2,10 @@ use std::hint::black_box; use svg_filters::{ codegen::SvgDocument, - types::{graph::edge::Edge, nodes::primitives::color_matrix::ColorMatrixType}, + types::{ + graph::edge::Edge, + nodes::{primitives::color_matrix::ColorMatrixType, standard_input::StandardInput}, + }, Node, }; @@ -25,39 +28,33 @@ fn main() { let mut doc = SvgDocument::new(); let chromabb = doc.create_filter("chromabb_gen"); - let chan_r = chromabb.add_node(Node::color_matrix(ColorMatrixType::Matrix(Box::new([ - 1., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 1., 0., - ])))); - let chan_g = chromabb.add_node(Node::color_matrix(ColorMatrixType::Matrix(Box::new([ - 0., 0., 0., 0., 0., 0., 1., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 1., 0., - ])))); - let chan_b = chromabb.add_node(Node::color_matrix(ColorMatrixType::Matrix(Box::new([ - 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 1., 0., 0., 0., 0., 0., 1., 0., - ])))); + let chan_r = chromabb.color_matrix( + StandardInput::SourceGraphic, + ColorMatrixType::Matrix(Box::new([ + 1., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 1., 0., + ])), + ); + let offset_r = chromabb.offset(chan_r, 25., 0.); + let blur_r = chromabb.gaussian_blur_xy(offset_r, 5, 0); - let offset_r = chromabb.add_node(Node::offset(25., 0.)); - let offset_b = chromabb.add_node(Node::offset(-25., 0.)); - let blur_r = chromabb.add_node(Node::gaussian_blur_xy(5, 0)); - let blur_b = chromabb.add_node(Node::gaussian_blur_xy(5, 0)); + let chan_b = chromabb.color_matrix( + StandardInput::SourceGraphic, + ColorMatrixType::Matrix(Box::new([ + 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 1., 0., 0., 0., 0., 0., 1., 0., + ])), + ); + let offset_b = chromabb.offset(chan_b, -25., 0.); + let blur_b = chromabb.gaussian_blur_xy(offset_b, 5, 0); - let composite_rb = chromabb.add_node(Node::composite_arithmetic(0., 1., 1., 0.)); - let composite_final = chromabb.add_node(Node::composite_arithmetic(0., 1., 1., 0.)); + let composite_rb = chromabb.composite_arithmetic(blur_r, blur_b, 0., 1., 1., 0.); - chromabb.dag.extend_with_edges(&[ - (chromabb.source_graphic(), chan_r), - (chromabb.source_graphic(), chan_b), - (chromabb.source_graphic(), chan_g), - (chan_r, offset_r), - (offset_r, blur_r), - (chan_b, offset_b), - (offset_b, blur_b), - ]); - chromabb.dag.extend_with_edges(&[ - (blur_r, composite_rb, Edge::new(0)), - (blur_b, composite_rb, Edge::new(1)), - (composite_rb, composite_final, Edge::new(0)), - (chan_g, composite_final, Edge::new(1)), - ]); + let chan_g = chromabb.color_matrix( + StandardInput::SourceGraphic, + ColorMatrixType::Matrix(Box::new([ + 0., 0., 0., 0., 0., 0., 1., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 1., 0., + ])), + ); + chromabb.composite_arithmetic(composite_rb, chan_g, 0., 1., 1., 0.); - black_box(doc.generate_svg()); + println!("{}", doc.generate_svg()); } diff --git a/crates/svg-filters/src/types.rs b/crates/svg-filters/src/types.rs index fa3941e..1290907 100644 --- a/crates/svg-filters/src/types.rs +++ b/crates/svg-filters/src/types.rs @@ -3,68 +3,4 @@ pub mod nodes; // pub mod old; -pub mod graph { - use std::iter::Iterator; - - use petgraph::{data::Build, prelude::*}; - - use crate::Node; - - use self::edge::Edge; - - use super::nodes::standard_input::StandardInput; - - #[derive(Debug)] - pub struct FilterGraph { - pub dag: DiGraph, - source_graphic_idx: NodeIndex, - } - - impl FilterGraph { - pub fn new() -> Self { - let mut dag = DiGraph::new(); - let source_graphic_idx = dag.add_node(Node::StdInput(StandardInput::SourceGraphic)); - - Self { - dag, - source_graphic_idx, - } - } - - pub fn add_node(&mut self, node: Node) -> NodeIndex { - self.dag.add_node(node) - } - - pub fn source_graphic(&self) -> NodeIndex { - self.source_graphic_idx - } - } - - pub mod edge { - #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] - pub struct Edge { - input_idx: u8, - } - - impl Edge { - pub fn new(input_idx: u8) -> Self { - Self { input_idx } - } - } - - impl Default for Edge { - fn default() -> Self { - Self { input_idx: 0 } - } - } - - impl ToString for Edge { - fn to_string(&self) -> String { - match self.input_idx { - 0 => "in".to_owned(), - n => format!("in{}", n + 1), - } - } - } - } -} +pub mod graph; diff --git a/crates/svg-filters/src/types/graph.rs b/crates/svg-filters/src/types/graph.rs new file mode 100644 index 0000000..44281d8 --- /dev/null +++ b/crates/svg-filters/src/types/graph.rs @@ -0,0 +1,202 @@ +use std::fmt::{Debug, Display}; + +use petgraph::{data::Build, prelude::NodeIndex, prelude::*, visit::IntoNeighborsDirected}; + +use crate::Node; + +use self::edge::Edge; + +use super::nodes::standard_input::StandardInput; + +#[derive(Debug)] +pub struct FilterGraph { + pub dag: DiGraph, + source_graphic_idx: NodeIndex, + source_alpha_idx: NodeIndex, + background_image_idx: NodeIndex, + background_alpha_idx: NodeIndex, + fill_paint_idx: NodeIndex, + stroke_paint_idx: NodeIndex, +} + +impl Default for FilterGraph { + fn default() -> Self { + Self::new() + } +} + +#[derive(Debug, Clone, Copy)] +pub enum NodeInput { + Standard(StandardInput), + Idx(NodeIndex), +} + +impl Display for NodeInput { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + NodeInput::Standard(s) => Debug::fmt(s, f), + NodeInput::Idx(idx) => write!(f, "r{}", idx.index()), + } + } +} + +impl From for NodeInput { + fn from(value: StandardInput) -> Self { + Self::Standard(value) + } +} + +impl From for NodeInput { + fn from(value: NodeIndex) -> Self { + Self::Idx(value) + } +} + +impl FilterGraph { + pub fn new() -> Self { + let mut dag = DiGraph::new(); + + let source_graphic_idx = dag.add_node(Node::StdInput(StandardInput::SourceGraphic)); + let source_alpha_idx = dag.add_node(Node::StdInput(StandardInput::SourceAlpha)); + let background_image_idx = dag.add_node(Node::StdInput(StandardInput::BackgroundImage)); + let background_alpha_idx = dag.add_node(Node::StdInput(StandardInput::BackgroundAlpha)); + let fill_paint_idx = dag.add_node(Node::StdInput(StandardInput::FillPaint)); + let stroke_paint_idx = dag.add_node(Node::StdInput(StandardInput::StrokePaint)); + + Self { + dag, + source_graphic_idx, + source_alpha_idx, + background_image_idx, + background_alpha_idx, + fill_paint_idx, + stroke_paint_idx, + } + } + + pub fn add_node(&mut self, node: Node) -> NodeIndex { + self.dag.add_node(node) + } + + fn resolve_input(&self, input: NodeInput) -> NodeIndex { + match input { + NodeInput::Standard(StandardInput::SourceGraphic) => self.source_graphic_idx, + NodeInput::Standard(StandardInput::SourceAlpha) => self.source_alpha_idx, + NodeInput::Standard(StandardInput::BackgroundImage) => self.background_image_idx, + NodeInput::Standard(StandardInput::BackgroundAlpha) => self.background_alpha_idx, + NodeInput::Standard(StandardInput::FillPaint) => self.fill_paint_idx, + NodeInput::Standard(StandardInput::StrokePaint) => self.stroke_paint_idx, + NodeInput::Idx(i) => i, + } + } + + #[allow( + clippy::unwrap_used, + reason = "we only operate on values we know exist, so unwrapping is safe" + )] + pub fn inputs(&self, node_idx: NodeIndex) -> Vec { + let mut inputs = self + .dag + .neighbors_directed(node_idx, Direction::Incoming) + .map(|input_idx| (self.dag.find_edge(input_idx, node_idx).unwrap(), input_idx)) + .collect::>(); + + inputs.sort_by(|(a, _), (b, _)| a.cmp(b)); + + inputs + .into_iter() + .map( + |(_, input_idx)| match self.dag.node_weight(input_idx).unwrap() { + Node::StdInput(s) => NodeInput::Standard(*s), + Node::Primitive { .. } => NodeInput::Idx(input_idx), + }, + ) + .collect() + } + + pub fn outputs(&self, node_idx: NodeIndex) -> Vec { + self.dag + .neighbors_directed(node_idx, Direction::Outgoing) + .collect() + } + + pub fn source_graphic(&self) -> NodeIndex { + self.source_graphic_idx + } + pub fn source_alpha(&self) -> NodeIndex { + self.source_alpha_idx + } + pub fn background_image(&self) -> NodeIndex { + self.background_image_idx + } + pub fn background_alpha(&self) -> NodeIndex { + self.background_alpha_idx + } + pub fn fill_paint(&self) -> NodeIndex { + self.fill_paint_idx + } + pub fn stroke_paint(&self) -> NodeIndex { + self.stroke_paint_idx + } +} + +pub mod abstracted_inputs { + use petgraph::{data::Build, prelude::NodeIndex}; + + use crate::{types::nodes::primitives::color_matrix::ColorMatrixType, Node}; + + use super::{FilterGraph, NodeInput}; + + impl FilterGraph { + pub fn color_matrix( + &mut self, + r#in: impl Into, + cm_type: ColorMatrixType, + ) -> NodeIndex { + let node_idx = self.dag.add_node(Node::color_matrix(cm_type)); + self.dag + .add_edge(self.resolve_input(r#in.into()), node_idx, ()); + node_idx + } + + pub fn offset(&mut self, r#in: impl Into, dx: f32, dy: f32) -> NodeIndex { + let node_idx = self.dag.add_node(Node::offset(dx, dy)); + self.dag + .add_edge(self.resolve_input(r#in.into()), node_idx, ()); + node_idx + } + + pub fn gaussian_blur_xy( + &mut self, + r#in: impl Into, + x: u16, + y: u16, + ) -> NodeIndex { + let node_idx = self.dag.add_node(Node::gaussian_blur_xy(x, y)); + self.dag + .add_edge(self.resolve_input(r#in.into()), node_idx, ()); + node_idx + } + + pub fn composite_arithmetic( + &mut self, + r#in: impl Into, + in2: impl Into, + k1: f32, + k2: f32, + k3: f32, + k4: f32, + ) -> NodeIndex { + let node_idx = self + .dag + .add_node(Node::composite_arithmetic(k1, k2, k3, k4)); + self.dag + .add_edge(self.resolve_input(r#in.into()), node_idx, ()); + self.dag + .add_edge(self.resolve_input(in2.into()), node_idx, ()); + node_idx + } + } +} + +pub mod edge; diff --git a/crates/svg-filters/src/types/graph/edge.rs b/crates/svg-filters/src/types/graph/edge.rs new file mode 100644 index 0000000..0783a34 --- /dev/null +++ b/crates/svg-filters/src/types/graph/edge.rs @@ -0,0 +1,19 @@ +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default)] +pub struct Edge { + input_idx: u8, +} + +impl Edge { + pub fn new(input_idx: u8) -> Self { + Self { input_idx } + } +} + +impl ToString for Edge { + fn to_string(&self) -> String { + match self.input_idx { + 0 => "in".to_owned(), + n => format!("in{}", n + 1), + } + } +} diff --git a/crates/svg-filters/src/types/length.rs b/crates/svg-filters/src/types/length.rs index 10ddfcc..ef9d2ef 100644 --- a/crates/svg-filters/src/types/length.rs +++ b/crates/svg-filters/src/types/length.rs @@ -3,6 +3,12 @@ use std::fmt::Display; #[derive(Default, Debug, Clone, Copy)] pub struct Length(f32, Unit); +impl Length { + pub fn is_zero(&self) -> bool { + self.0 == 0. + } +} + impl Display for Length { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}{}", self.0, self.1) diff --git a/crates/svg-filters/src/types/nodes.rs b/crates/svg-filters/src/types/nodes.rs index 80cb5d0..b24251f 100644 --- a/crates/svg-filters/src/types/nodes.rs +++ b/crates/svg-filters/src/types/nodes.rs @@ -1,6 +1,6 @@ -use core::panic; +use std::borrow::Cow; -use quick_xml::events::attributes::Attribute; +use quick_xml::{events::attributes::Attribute, name::QName}; use self::{ primitives::{ @@ -42,6 +42,41 @@ pub(crate) struct CommonAttrs { pub height: Length, } +impl From for Vec> { + fn from(val: CommonAttrs) -> Self { + let mut r = Vec::new(); + if !val.x.is_zero() { + r.push(Attribute { + key: QName(b"x"), + value: Cow::from(val.x.to_string().into_bytes()), + }); + } + + if !val.y.is_zero() { + r.push(Attribute { + key: QName(b"y"), + value: Cow::from(val.y.to_string().into_bytes()), + }); + } + + if !val.width.is_zero() { + r.push(Attribute { + key: QName(b"width"), + value: Cow::from(val.width.to_string().into_bytes()), + }); + } + + if !val.height.is_zero() { + r.push(Attribute { + key: QName(b"height"), + value: Cow::from(val.height.to_string().into_bytes()), + }); + } + + r + } +} + impl Node { pub fn simple(el: FePrimitive) -> Node { Node::Primitive { diff --git a/crates/svg-filters/src/types/nodes/primitives.rs b/crates/svg-filters/src/types/nodes/primitives.rs index 513f1a8..f43c820 100644 --- a/crates/svg-filters/src/types/nodes/primitives.rs +++ b/crates/svg-filters/src/types/nodes/primitives.rs @@ -1,10 +1,8 @@ +use std::convert::Into; + use quick_xml::{events::attributes::Attribute, ElementWriter, Writer}; -use crate::types::length::{Coordinate, Length}; - -use self::blend::BlendMode; - -use super::Node; +use super::CommonAttrs; pub mod blend; pub mod color_matrix; @@ -29,10 +27,34 @@ pub trait WriteElement { fn element_writer<'writer, 'result>( &self, writer: &'writer mut Writer<&'result mut Vec>, - ) -> ElementWriter<'writer, &'result mut Vec> { - writer + common: CommonAttrs, + inputs: Vec, + output: Option, + ) -> quick_xml::Result<&'writer mut quick_xml::Writer<&'result mut Vec>> { + let attrs: 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(Into::>>::into(common)) .with_attributes(self.attrs()) + .with_attributes(attrs.iter().map(|(k, v)| (&k[..], &v[..]))); + if let Some(output) = output { + el_writer = el_writer.with_attribute(("result", output.as_str())); + } + + el_writer.write_empty() } } diff --git a/crates/svg-filters/src/types/nodes/primitives/blend.rs b/crates/svg-filters/src/types/nodes/primitives/blend.rs index 1aba6d0..8df8ad6 100644 --- a/crates/svg-filters/src/types/nodes/primitives/blend.rs +++ b/crates/svg-filters/src/types/nodes/primitives/blend.rs @@ -1,3 +1,9 @@ +use std::{borrow::Cow, fmt::Display}; + +use quick_xml::{events::attributes::Attribute, name::QName}; + +use super::WriteElement; + /// [feBlend](https://www.w3.org/TR/SVG11/filters.html#feBlendElement) #[derive(Debug)] pub struct Blend { @@ -18,6 +24,23 @@ impl Default for Blend { } } +impl WriteElement for Blend { + fn attrs(&self) -> Vec { + if let BlendMode::Normal = self.mode { + Vec::new() + } else { + vec![Attribute { + key: QName(b"mode"), + value: Cow::from(self.mode.to_string().into_bytes()), + }] + } + } + + fn tag_name(&self) -> &'static str { + "feBlend" + } +} + /// as according to https://drafts.fxtf.org/compositing-1/#blending #[derive(Debug)] pub enum BlendMode { @@ -39,3 +62,26 @@ pub enum BlendMode { Color, Luminosity, } + +impl Display for BlendMode { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(match self { + BlendMode::Normal => "normal", + BlendMode::Multiply => "multiply", + BlendMode::Screen => "screen", + BlendMode::Overlay => "overlay", + BlendMode::Darken => "darken", + BlendMode::Lighten => "lighten", + BlendMode::ColorDodge => "color-dodge", + BlendMode::ColorBurn => "color-burn", + BlendMode::HardLight => "hard-light", + BlendMode::SoftLight => "soft-light", + BlendMode::Difference => "difference", + BlendMode::Exclusion => "exclusion", + BlendMode::Hue => "hue", + BlendMode::Saturation => "saturation", + BlendMode::Color => "color", + BlendMode::Luminosity => "luminosity", + }) + } +} diff --git a/crates/svg-filters/src/types/nodes/primitives/color_matrix.rs b/crates/svg-filters/src/types/nodes/primitives/color_matrix.rs index ef32b60..4469500 100644 --- a/crates/svg-filters/src/types/nodes/primitives/color_matrix.rs +++ b/crates/svg-filters/src/types/nodes/primitives/color_matrix.rs @@ -1,6 +1,6 @@ use std::borrow::Cow; -use quick_xml::{events::attributes::Attribute, name::QName, se::to_string}; +use quick_xml::{events::attributes::Attribute, name::QName}; use super::WriteElement; @@ -23,7 +23,7 @@ impl WriteElement for ColorMatrix { key: QName(b"values"), value: Cow::from( v.iter() - .map(|v| v.to_string()) + .map(std::string::ToString::to_string) .reduce(|mut acc, e| { acc.push(' '); acc.push_str(&e); @@ -33,9 +33,11 @@ impl WriteElement for ColorMatrix { .into_bytes(), ), }], - ColorMatrixType::Saturate(v) => todo!(), - ColorMatrixType::HueRotate(v) => todo!(), - ColorMatrixType::LuminanceToAlpha => todo!(), + ColorMatrixType::Saturate(v) | ColorMatrixType::HueRotate(v) => vec![Attribute { + key: QName(b"values"), + value: Cow::from(v.to_string().into_bytes()), + }], + ColorMatrixType::LuminanceToAlpha => Vec::new(), } } diff --git a/crates/svg-filters/src/types/nodes/primitives/flood.rs b/crates/svg-filters/src/types/nodes/primitives/flood.rs index 94f6eee..3a90590 100644 --- a/crates/svg-filters/src/types/nodes/primitives/flood.rs +++ b/crates/svg-filters/src/types/nodes/primitives/flood.rs @@ -1,4 +1,9 @@ +use std::borrow::Cow; + use csscolorparser::Color; +use quick_xml::{events::attributes::Attribute, name::QName}; + +use super::WriteElement; /// [feFlood](https://www.w3.org/TR/SVG11/filters.html#feFloodElement) #[derive(Debug)] @@ -6,3 +11,22 @@ pub struct Flood { flood_color: Color, flood_opacity: f32, } + +impl WriteElement for Flood { + fn attrs(&self) -> Vec { + vec![ + Attribute { + key: QName(b"flood-color"), + value: Cow::from(self.flood_color.to_hex_string().into_bytes()), + }, + Attribute { + key: QName(b"flood-opacity"), + value: Cow::from(self.flood_opacity.to_string().into_bytes()), + }, + ] + } + + fn tag_name(&self) -> &'static str { + "feFlood" + } +} diff --git a/crates/svg-filters/src/types/nodes/primitives/morphology.rs b/crates/svg-filters/src/types/nodes/primitives/morphology.rs index 6b33825..dc11775 100644 --- a/crates/svg-filters/src/types/nodes/primitives/morphology.rs +++ b/crates/svg-filters/src/types/nodes/primitives/morphology.rs @@ -1,3 +1,9 @@ +use std::{borrow::Cow, fmt::Display}; + +use quick_xml::{events::attributes::Attribute, name::QName}; + +use super::WriteElement; + /// [feMorphology](https://www.w3.org/TR/SVG11/filters.html#feMorphologyElement) #[derive(Debug)] pub struct Morphology { @@ -5,8 +11,36 @@ pub struct Morphology { radius: (f32, f32), } +impl WriteElement for Morphology { + fn attrs(&self) -> Vec { + vec![ + Attribute { + key: QName(b"operator"), + value: Cow::from(self.operator.to_string().into_bytes()), + }, + Attribute { + key: QName(b"radius"), + value: Cow::from(format!("{} {}", self.radius.0, self.radius.1).into_bytes()), + }, + ] + } + + fn tag_name(&self) -> &'static str { + "feMorphology" + } +} + #[derive(Debug)] enum Operator { Erode, Dilate, } + +impl Display for Operator { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(match self { + Operator::Erode => "erode", + Operator::Dilate => "dilate", + }) + } +} diff --git a/crates/svg-filters/src/types/nodes/primitives/tile.rs b/crates/svg-filters/src/types/nodes/primitives/tile.rs index 7a957c5..d83b65f 100644 --- a/crates/svg-filters/src/types/nodes/primitives/tile.rs +++ b/crates/svg-filters/src/types/nodes/primitives/tile.rs @@ -1,3 +1,15 @@ +use super::WriteElement; + /// [feTile](https://www.w3.org/TR/SVG11/filters.html#feTileElement) #[derive(Debug)] pub struct Tile; + +impl WriteElement for Tile { + fn attrs(&self) -> Vec { + Vec::new() + } + + fn tag_name(&self) -> &'static str { + "feTile" + } +} diff --git a/crates/svg-filters/src/types/nodes/primitives/turbulence.rs b/crates/svg-filters/src/types/nodes/primitives/turbulence.rs index ec815cb..8bdcab0 100644 --- a/crates/svg-filters/src/types/nodes/primitives/turbulence.rs +++ b/crates/svg-filters/src/types/nodes/primitives/turbulence.rs @@ -1,21 +1,75 @@ +use std::borrow::Cow; + +use quick_xml::{ + events::attributes::{Attr, Attribute}, + name::QName, +}; + +use super::WriteElement; + /// [feTurbulence](https://www.w3.org/TR/SVG11/filters.html#feTurbulenceElement) #[derive(Debug)] pub struct Turbulence { base_frequency: (f32, f32), - num_octaves: (u16), + num_octaves: u16, seed: u32, - stich_tiles: StitchTiles, + stitch_tiles: StitchTiles, // attr name: type noise_type: NoiseType, } -#[derive(Debug)] -enum StitchTiles { +impl WriteElement for Turbulence { + fn attrs(&self) -> Vec { + let mut r = Vec::from([Attribute { + key: QName(b"baseFrequency"), + value: Cow::from( + format!("{} {}", self.base_frequency.0, self.base_frequency.1).into_bytes(), + ), + }]); + + if self.num_octaves != 1 { + r.push(Attribute { + key: QName(b"numOctaves"), + value: Cow::from(format!("{}", self.num_octaves).into_bytes()), + }); + } + + if self.seed != 0 { + r.push(Attribute { + key: QName(b"seed"), + value: Cow::from(self.seed.to_string().into_bytes()), + }); + } + + if self.stitch_tiles != StitchTiles::NoStitch { + r.push(Attribute { + key: QName(b"stitchTiles"), + value: Cow::from(b"stitch".to_vec()), + }); + } + + if self.noise_type != NoiseType::Turbulence { + r.push(Attribute { + key: QName(b"type"), + value: Cow::from(b"fractalNoise".to_vec()), + }); + } + + r + } + + fn tag_name(&self) -> &'static str { + "feTurbulence" + } +} + +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub enum StitchTiles { Stitch, NoStitch, } -#[derive(Debug)] -enum NoiseType { +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub enum NoiseType { Turbulence, FractalNoise, } diff --git a/crates/svg-filters/src/types/nodes/standard_input.rs b/crates/svg-filters/src/types/nodes/standard_input.rs index add10d0..3004e19 100644 --- a/crates/svg-filters/src/types/nodes/standard_input.rs +++ b/crates/svg-filters/src/types/nodes/standard_input.rs @@ -1,6 +1,6 @@ /// [svg filter effect standard input](https://www.w3.org/TR/SVG11/filters.html#FilterPrimitiveInAttribute) /// technically not a node, but for implementation simplicity... yeah -#[derive(Debug)] +#[derive(Debug, Clone, Copy)] pub enum StandardInput { SourceGraphic, SourceAlpha,