From a42ec014e550a4c3570e920c533ac5844e4fa499 Mon Sep 17 00:00:00 2001 From: Schrottkatze Date: Sat, 16 Mar 2024 00:35:23 +0100 Subject: [PATCH] svg-filters: get svg generation working!!!! --- crates/svg-filters/src/lib.rs | 3 +- crates/svg-filters/src/main.rs | 35 ++-- crates/svg-filters/src/types.rs | 169 ++++++++++++------ .../svg-filters/src/types/nodes/primitives.rs | 16 +- .../src/types/nodes/primitives/composite.rs | 53 ++++++ .../types/nodes/primitives/gaussian_blur.rs | 21 +++ .../src/types/nodes/primitives/offset.rs | 25 +++ 7 files changed, 237 insertions(+), 85 deletions(-) diff --git a/crates/svg-filters/src/lib.rs b/crates/svg-filters/src/lib.rs index 5081a19..a87a611 100644 --- a/crates/svg-filters/src/lib.rs +++ b/crates/svg-filters/src/lib.rs @@ -1,5 +1,6 @@ -pub mod types; +#![feature(lint_reasons)] +pub mod types; pub use types::nodes::Node; pub use types::Edge; pub use types::Filter; diff --git a/crates/svg-filters/src/main.rs b/crates/svg-filters/src/main.rs index 5531784..fb6a782 100644 --- a/crates/svg-filters/src/main.rs +++ b/crates/svg-filters/src/main.rs @@ -4,17 +4,6 @@ use svg_filters::{ }; fn main() { - let mut supersimple = Filter::new(); - - let cm = supersimple.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., - ])))); - - supersimple - .graph - .extend_with_edges(&[(supersimple.source_graphic(), cm)]); - dbg!(supersimple.to_svg()); - let mut filter = Filter::new(); // @@ -51,18 +40,18 @@ fn main() { let composite_final = filter.add_node(Node::composite_arithmetic(0., 1., 1., 0.)); filter.graph.extend_with_edges(&[ - (filter.source_graphic(), chan_r, Edge::new()), - (filter.source_graphic(), chan_b, Edge::new()), - (filter.source_graphic(), chan_g, Edge::new()), - (chan_r, offset_r, Edge::new()), - (offset_r, blur_r, Edge::new()), - (chan_b, offset_b, Edge::new()), - (offset_b, blur_b, Edge::new()), - (blur_r, composite_rb, Edge::in_idx(0)), - (blur_b, composite_rb, Edge::in_idx(1)), - (composite_rb, composite_final, Edge::in_idx(0)), - (chan_g, composite_final, Edge::in_idx(1)), + (filter.source_graphic(), chan_r, Edge::unnamed()), + (filter.source_graphic(), chan_b, Edge::unnamed()), + (filter.source_graphic(), chan_g, Edge::unnamed()), + (chan_r, offset_r, Edge::new("ro")), + (offset_r, blur_r, Edge::new("rob")), + (chan_b, offset_b, Edge::new("bo")), + (offset_b, blur_b, Edge::new("bob")), + (blur_r, composite_rb, Edge::new("robc").with_idx(0)), + (blur_b, composite_rb, Edge::new("bobc").with_idx(1)), + (composite_rb, composite_final, Edge::new("cf").with_idx(0)), + (chan_g, composite_final, Edge::new("gf").with_idx(1)), ]); - println!("Result: {}", filter.to_svg()) + println!("{}", filter.to_svg()) } diff --git a/crates/svg-filters/src/types.rs b/crates/svg-filters/src/types.rs index df679ba..325a869 100644 --- a/crates/svg-filters/src/types.rs +++ b/crates/svg-filters/src/types.rs @@ -1,4 +1,11 @@ -use std::{borrow::Cow, collections::HashMap, fmt::Debug, io::BufWriter, primitive}; +use core::panic; +use std::{ + borrow::Cow, + collections::{HashMap, HashSet}, + fmt::Debug, + io::BufWriter, + primitive, +}; use petgraph::{ adj::EdgeIndex, @@ -9,7 +16,7 @@ use petgraph::{ prelude::NodeIndex, visit::NodeIndexable, }; -use quick_xml::{events::attributes::Attribute, name::QName, ElementWriter}; +use quick_xml::{events::attributes::Attribute, name::QName, ElementWriter, Error}; pub mod length; pub mod nodes; @@ -53,47 +60,53 @@ impl Filter<'_> { pub fn to_svg(&self) -> String { let mut result = Vec::new(); - let mut writer = quick_xml::Writer::new(&mut result); + // let mut doc_writer = quick_xml::Writer::new(&mut result); + let mut doc_writer = quick_xml::Writer::new_with_indent(&mut result, b' ', 2); - let mut dfs_space = DfsSpace::new(&self.graph); - let sorted = toposort(&self.graph, Some(&mut dfs_space)).expect("No cycles! Bad user!"); + doc_writer + .create_element("filter") + .with_attribute(("id", "chromabb_gen")) + .write_inner_content(|writer| { + let mut dfs_space = DfsSpace::new(&self.graph); + let sorted = + toposort(&self.graph, Some(&mut dfs_space)).expect("No cycles! Bad user!"); - sorted - .into_iter() - .filter_map(|node_idx| { - let node = self - .graph - .node_weight(node_idx) - .expect("toposorting will not return invalid indices"); - - if let Node::Primitive { - primitive, - common_attrs, - } = node - { - Some((node_idx, primitive, common_attrs)) - } else { - None - } - }) - .fold( - &mut writer, - |writer, (node_idx, primitive, common_attrs)| { - let el_writer = primitive.element_writer(writer); - create_input_elements( - &self.graph, - el_writer, - node_idx, - self.graph + let v = sorted + .into_iter() + .filter_map(|node_idx| { + let node = self + .graph .node_weight(node_idx) - .expect("cannot get invalid node_idx from toposort") - .input_count(), - ) - .write_empty() - .expect("should write successfully") - }, - ); + .expect("toposorting will not return invalid indices"); + if let Node::Primitive { + primitive, + common_attrs, + } = node + { + Some((node_idx, primitive, common_attrs)) + } else { + None + } + }) + .try_fold(writer, |acc, (node_idx, primitive, common_attrs)| { + let mut el_writer = primitive.element_writer(&mut *acc); + el_writer = create_input_attr( + &self.graph, + el_writer, + node_idx, + self.graph + .node_weight(node_idx) + .expect("cannot get invalid node_idx from toposort") + .input_count(), + ); + create_output_attr(&self.graph, el_writer, node_idx).write_empty() + }) + .map(|_| ()); + + Ok::<(), Error>(()) + }) + .expect("shouldnt fail to write or something"); String::from_utf8_lossy(&result).to_string() } } @@ -112,25 +125,31 @@ pub struct Edge<'a> { pub in_idx: Option, } -impl Edge<'_> { - pub fn new() -> Self { +impl<'a> Edge<'a> { + pub fn new(name: &'a str) -> Self { Self { - edge_type: EdgeType::Unnamed, + edge_type: EdgeType::Named(name), in_idx: None, } } - pub fn in_idx(idx: u8) -> Self { + #[must_use] + pub fn with_idx(mut self, idx: u8) -> Self { + self.in_idx = Some(idx); + self + } + + pub fn unnamed() -> Self { Self { edge_type: EdgeType::Unnamed, - in_idx: Some(idx), + in_idx: None, } } } impl Default for Edge<'_> { fn default() -> Self { - Self::new() + Self::unnamed() } } @@ -140,12 +159,13 @@ pub enum EdgeType<'a> { /// For standard inputs such as SourceGraphic etc., which we'll just be representing as nodes for simplicity Unnamed, } -fn create_input_elements<'a>( - g: &'a DiGraph>, - mut el_writer: ElementWriter<'a, &'a mut Vec>, + +fn create_input_attr<'w, 'b>( + g: &'_ DiGraph>, + mut el_writer: ElementWriter<'w, &'b mut Vec>, node_idx: NodeIndex, input_count: u8, -) -> ElementWriter<'a, &'a mut Vec> { +) -> ElementWriter<'w, &'b mut Vec> { let inputs = g .neighbors_directed(node_idx, petgraph::Direction::Incoming) .collect::>(); @@ -176,12 +196,55 @@ fn create_input_elements<'a>( let v = match incoming_node { Node::StdInput(std_in) => format!("{std_in:?}"), - Node::Primitive { - primitive, - common_attrs, - } => todo!(), + Node::Primitive { .. } => { + if let EdgeType::Named(name) = edge_type { + (*name).to_owned() + } else { + panic!( + "unnamed edges should not be used for connections between primitives" + ) + } + } }; el_writer.with_attribute((in_attr_name.as_str(), v.as_str())) }) } + +#[allow(clippy::unwrap_used, reason = "all unwraps are for finding on options")] +fn create_output_attr<'w, 'b>( + g: &'_ DiGraph>, + mut el_writer: ElementWriter<'w, &'b mut Vec>, + node_idx: NodeIndex, +) -> ElementWriter<'w, &'b mut Vec> { + let output = g + .neighbors_directed(node_idx, petgraph::Direction::Outgoing) + .map(|neighbor_idx| { + let edge_idx = g.find_edge(node_idx, neighbor_idx).unwrap(); + + let Edge { + edge_type: EdgeType::Named(name), + .. + } = g.edge_weight(edge_idx).unwrap() + else { + panic!("Unnamed edge used for connection between primitives"); + }; + *name + }) + .collect::>(); + + if output.is_empty() { + el_writer + } else if output.len() == 1 { + el_writer.with_attribute(Attribute { + key: QName(b"result"), + value: Cow::from( + (*output.into_iter().collect::>().first().unwrap()) + .to_string() + .into_bytes(), + ), + }) + } else { + panic!("Can't have more then one named output: {output:?}") + } +} diff --git a/crates/svg-filters/src/types/nodes/primitives.rs b/crates/svg-filters/src/types/nodes/primitives.rs index 012fd9a..513f1a8 100644 --- a/crates/svg-filters/src/types/nodes/primitives.rs +++ b/crates/svg-filters/src/types/nodes/primitives.rs @@ -62,18 +62,18 @@ impl WriteElement for FePrimitive { fn attrs(&self) -> std::vec::Vec> { match self { FePrimitive::Blend(_) => todo!(), - FePrimitive::ColorMatrix(cm) => cm.attrs(), + FePrimitive::ColorMatrix(el) => el.attrs(), FePrimitive::ComponentTransfer(_) => todo!(), - FePrimitive::Composite(_) => todo!(), + FePrimitive::Composite(el) => el.attrs(), FePrimitive::ConvolveMatrix(_) => todo!(), FePrimitive::DiffuseLighting(_) => todo!(), FePrimitive::DisplacementMap(_) => todo!(), FePrimitive::Flood(_) => todo!(), - FePrimitive::GaussianBlur(_) => todo!(), + FePrimitive::GaussianBlur(el) => el.attrs(), FePrimitive::Image(_) => todo!(), FePrimitive::Merge(_) => todo!(), FePrimitive::Morphology(_) => todo!(), - FePrimitive::Offset(_) => todo!(), + FePrimitive::Offset(el) => el.attrs(), FePrimitive::SpecularLighting(_) => todo!(), FePrimitive::Tile(_) => todo!(), FePrimitive::Turbulence(_) => todo!(), @@ -83,18 +83,18 @@ impl WriteElement for FePrimitive { fn tag_name(&self) -> &'static str { match self { FePrimitive::Blend(_) => todo!(), - FePrimitive::ColorMatrix(cm) => cm.tag_name(), + FePrimitive::ColorMatrix(el) => el.tag_name(), FePrimitive::ComponentTransfer(_) => todo!(), - FePrimitive::Composite(_) => todo!(), + FePrimitive::Composite(el) => el.tag_name(), FePrimitive::ConvolveMatrix(_) => todo!(), FePrimitive::DiffuseLighting(_) => todo!(), FePrimitive::DisplacementMap(_) => todo!(), FePrimitive::Flood(_) => todo!(), - FePrimitive::GaussianBlur(_) => todo!(), + FePrimitive::GaussianBlur(el) => el.tag_name(), FePrimitive::Image(_) => todo!(), FePrimitive::Merge(_) => todo!(), FePrimitive::Morphology(_) => todo!(), - FePrimitive::Offset(_) => todo!(), + FePrimitive::Offset(el) => el.tag_name(), FePrimitive::SpecularLighting(_) => todo!(), FePrimitive::Tile(_) => todo!(), FePrimitive::Turbulence(_) => todo!(), diff --git a/crates/svg-filters/src/types/nodes/primitives/composite.rs b/crates/svg-filters/src/types/nodes/primitives/composite.rs index c86de26..e27664d 100644 --- a/crates/svg-filters/src/types/nodes/primitives/composite.rs +++ b/crates/svg-filters/src/types/nodes/primitives/composite.rs @@ -1,3 +1,9 @@ +use std::borrow::Cow; + +use quick_xml::{events::attributes::Attribute, name::QName}; + +use super::WriteElement; + /// [feComposite](https://www.w3.org/TR/SVG11/filters.html#feCompositeElement) #[derive(Debug)] pub struct Composite { @@ -25,3 +31,50 @@ pub enum CompositeOperator { Xor, Arithmetic { k1: f32, k2: f32, k3: f32, k4: f32 }, } + +impl WriteElement for Composite { + fn attrs(&self) -> Vec { + let (op_name, vals) = match self.operator { + CompositeOperator::Over => ("over", None), + CompositeOperator::In => ("in", None), + CompositeOperator::Out => ("out", None), + CompositeOperator::Atop => ("atop", None), + CompositeOperator::Xor => ("xor", None), + CompositeOperator::Arithmetic { k1, k2, k3, k4 } => { + ("arithmetic", Some([k1, k2, k3, k4])) + } + }; + + let mut r = vec![Attribute { + key: QName(b"operator"), + value: Cow::from(op_name.as_bytes()), + }]; + + if let Some([k1, k2, k3, k4]) = vals { + r.append(&mut vec![ + Attribute { + key: QName(b"k1"), + value: Cow::from(k1.to_string().into_bytes()), + }, + Attribute { + key: QName(b"k2"), + value: Cow::from(k2.to_string().into_bytes()), + }, + Attribute { + key: QName(b"k3"), + value: Cow::from(k3.to_string().into_bytes()), + }, + Attribute { + key: QName(b"k4"), + value: Cow::from(k4.to_string().into_bytes()), + }, + ]); + } + + r + } + + fn tag_name(&self) -> &'static str { + "feComposite" + } +} diff --git a/crates/svg-filters/src/types/nodes/primitives/gaussian_blur.rs b/crates/svg-filters/src/types/nodes/primitives/gaussian_blur.rs index e6ca9c1..a0c42bd 100644 --- a/crates/svg-filters/src/types/nodes/primitives/gaussian_blur.rs +++ b/crates/svg-filters/src/types/nodes/primitives/gaussian_blur.rs @@ -1,3 +1,9 @@ +use std::borrow::Cow; + +use quick_xml::{events::attributes::Attribute, name::QName}; + +use super::WriteElement; + /// [feGaussianBlur](https://www.w3.org/TR/SVG11/filters.html#feGaussianBlurElement) #[derive(Debug)] pub struct GaussianBlur { @@ -17,3 +23,18 @@ impl GaussianBlur { } } } + +impl WriteElement for GaussianBlur { + fn attrs(&self) -> Vec { + vec![Attribute { + key: QName(b"stdDeviation"), + value: Cow::from( + format!("{} {}", self.std_deviation.0, self.std_deviation.1).into_bytes(), + ), + }] + } + + fn tag_name(&self) -> &'static str { + "feGaussianBlur" + } +} diff --git a/crates/svg-filters/src/types/nodes/primitives/offset.rs b/crates/svg-filters/src/types/nodes/primitives/offset.rs index 96b47ef..4cad35b 100644 --- a/crates/svg-filters/src/types/nodes/primitives/offset.rs +++ b/crates/svg-filters/src/types/nodes/primitives/offset.rs @@ -1,3 +1,9 @@ +use std::borrow::Cow; + +use quick_xml::{events::attributes::Attribute, name::QName}; + +use super::WriteElement; + /// [feOffset](https://www.w3.org/TR/SVG11/filters.html#feOffsetElement) #[derive(Debug)] pub struct Offset { @@ -10,3 +16,22 @@ impl Offset { Self { dx, dy } } } + +impl WriteElement for Offset { + fn attrs(&self) -> Vec { + vec![ + Attribute { + key: QName(b"dx"), + value: Cow::from(self.dx.to_string().into_bytes()), + }, + Attribute { + key: QName(b"dy"), + value: Cow::from(self.dy.to_string().into_bytes()), + }, + ] + } + + fn tag_name(&self) -> &'static str { + "feOffset" + } +}