svg-filters: get svg generation working!!!!

This commit is contained in:
Schrottkatze 2024-03-16 00:35:23 +01:00
parent 01b1880089
commit a42ec014e5
Signed by: schrottkatze
SSH key fingerprint: SHA256:hXb3t1vINBFCiDCmhRABHX5ocdbLiKyCdKI4HK2Rbbc
7 changed files with 237 additions and 85 deletions

View file

@ -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;

View file

@ -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();
// <filter id="chromabb" >
@ -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())
}

View file

@ -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<u8>,
}
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<Node, Edge<'_>>,
mut el_writer: ElementWriter<'a, &'a mut Vec<u8>>,
fn create_input_attr<'w, 'b>(
g: &'_ DiGraph<Node, Edge<'_>>,
mut el_writer: ElementWriter<'w, &'b mut Vec<u8>>,
node_idx: NodeIndex,
input_count: u8,
) -> ElementWriter<'a, &'a mut Vec<u8>> {
) -> ElementWriter<'w, &'b mut Vec<u8>> {
let inputs = g
.neighbors_directed(node_idx, petgraph::Direction::Incoming)
.collect::<Vec<NodeIndex>>();
@ -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<Node, Edge<'_>>,
mut el_writer: ElementWriter<'w, &'b mut Vec<u8>>,
node_idx: NodeIndex,
) -> ElementWriter<'w, &'b mut Vec<u8>> {
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::<HashSet<&str>>();
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::<Vec<&str>>().first().unwrap())
.to_string()
.into_bytes(),
),
})
} else {
panic!("Can't have more then one named output: {output:?}")
}
}

View file

@ -62,18 +62,18 @@ impl WriteElement for FePrimitive {
fn attrs(&self) -> std::vec::Vec<quick_xml::events::attributes::Attribute<'_>> {
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!(),

View file

@ -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<quick_xml::events::attributes::Attribute> {
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"
}
}

View file

@ -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<quick_xml::events::attributes::Attribute> {
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"
}
}

View file

@ -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<quick_xml::events::attributes::Attribute> {
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"
}
}