svg-filters & basic parser #15
15 changed files with 532 additions and 227 deletions
|
@ -1,7 +1,9 @@
|
||||||
use std::{
|
use std::{
|
||||||
borrow::Cow,
|
|
||||||
cmp,
|
cmp,
|
||||||
collections::{BTreeSet, HashMap, HashSet},
|
collections::{BTreeSet, HashMap},
|
||||||
|
fmt::Display,
|
||||||
|
io::Read,
|
||||||
|
ops::Not,
|
||||||
};
|
};
|
||||||
|
|
||||||
use indexmap::IndexMap;
|
use indexmap::IndexMap;
|
||||||
|
@ -10,11 +12,11 @@ use petgraph::{
|
||||||
graph::DiGraph,
|
graph::DiGraph,
|
||||||
prelude::{EdgeIndex, NodeIndex},
|
prelude::{EdgeIndex, NodeIndex},
|
||||||
};
|
};
|
||||||
use quick_xml::{events::attributes::Attribute, name::QName, ElementWriter};
|
use quick_xml::ElementWriter;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
types::{
|
types::{
|
||||||
graph::{edge::Edge, FilterGraph},
|
graph::{edge::Edge, FilterGraph, NodeInput},
|
||||||
nodes::{primitives::WriteElement, CommonAttrs},
|
nodes::{primitives::WriteElement, CommonAttrs},
|
||||||
},
|
},
|
||||||
Node,
|
Node,
|
||||||
|
@ -43,7 +45,7 @@ impl SvgDocument {
|
||||||
|
|
||||||
pub fn generate_svg(&self) -> String {
|
pub fn generate_svg(&self) -> String {
|
||||||
let mut result = Vec::new();
|
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
|
doc_writer
|
||||||
.create_element("svg")
|
.create_element("svg")
|
||||||
|
@ -82,121 +84,35 @@ impl SvgDocument {
|
||||||
.map(|(primitive, common_attrs)| (node_idx, primitive, common_attrs))
|
.map(|(primitive, common_attrs)| (node_idx, primitive, common_attrs))
|
||||||
})
|
})
|
||||||
.try_fold(writer, |writer, (node_idx, primitive, common_attrs)| {
|
.try_fold(writer, |writer, (node_idx, primitive, common_attrs)| {
|
||||||
let mut el_writer = primitive.element_writer(writer);
|
primitive.element_writer(
|
||||||
el_writer = input_attrs(node_idx, &graph.dag).write_into(el_writer);
|
writer,
|
||||||
el_writer = output_attrs(node_idx, &graph.dag).write_into(el_writer);
|
*common_attrs,
|
||||||
el_writer = Attrs::from(*common_attrs).write_into(el_writer);
|
graph
|
||||||
el_writer.write_empty()
|
.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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Attrs(IndexMap<String, String>);
|
|
||||||
|
|
||||||
impl Attrs {
|
|
||||||
fn write_into<'w, 'b>(
|
|
||||||
self,
|
|
||||||
el_writer: ElementWriter<'w, &'b mut Vec<u8>>,
|
|
||||||
) -> ElementWriter<'w, &'b mut Vec<u8>> {
|
|
||||||
let attrs = self.0.iter().map(|(k, v)| (k.as_str(), v.as_str()));
|
|
||||||
el_writer.with_attributes(attrs)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<CommonAttrs> 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<Node, Edge>) -> Attrs {
|
|
||||||
let inputs: Vec<NodeIndex> = 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::<Vec<(&Node, EdgeIndex, &Edge)>>();
|
|
||||||
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<Node, Edge>) -> Attrs {
|
|
||||||
let outputs: Vec<NodeIndex> = 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
|
/// convenience method to avoid fuckups during future changes
|
||||||
fn format_edge_idx(idx: EdgeIndex) -> String {
|
fn format_edge_idx(idx: EdgeIndex) -> String {
|
||||||
format!("edge{}", idx.index())
|
format!("edge{}", idx.index())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn format_node_idx(node_idx: NodeIndex) -> String {
|
||||||
|
format!("r{}", node_idx.index())
|
||||||
|
}
|
||||||
|
|
||||||
mod error {
|
mod error {
|
||||||
use std::{error::Error, fmt::Display};
|
use std::{error::Error, fmt::Display};
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,10 @@ use std::hint::black_box;
|
||||||
|
|
||||||
use svg_filters::{
|
use svg_filters::{
|
||||||
codegen::SvgDocument,
|
codegen::SvgDocument,
|
||||||
types::{graph::edge::Edge, nodes::primitives::color_matrix::ColorMatrixType},
|
types::{
|
||||||
|
graph::edge::Edge,
|
||||||
|
nodes::{primitives::color_matrix::ColorMatrixType, standard_input::StandardInput},
|
||||||
|
},
|
||||||
Node,
|
Node,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -25,39 +28,33 @@ fn main() {
|
||||||
let mut doc = SvgDocument::new();
|
let mut doc = SvgDocument::new();
|
||||||
let chromabb = doc.create_filter("chromabb_gen");
|
let chromabb = doc.create_filter("chromabb_gen");
|
||||||
|
|
||||||
let chan_r = chromabb.add_node(Node::color_matrix(ColorMatrixType::Matrix(Box::new([
|
let chan_r = chromabb.color_matrix(
|
||||||
1., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 1., 0.,
|
StandardInput::SourceGraphic,
|
||||||
]))));
|
ColorMatrixType::Matrix(Box::new([
|
||||||
let chan_g = 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.,
|
||||||
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([
|
let offset_r = chromabb.offset(chan_r, 25., 0.);
|
||||||
0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 1., 0., 0., 0., 0., 0., 1., 0.,
|
let blur_r = chromabb.gaussian_blur_xy(offset_r, 5, 0);
|
||||||
]))));
|
|
||||||
|
|
||||||
let offset_r = chromabb.add_node(Node::offset(25., 0.));
|
let chan_b = chromabb.color_matrix(
|
||||||
let offset_b = chromabb.add_node(Node::offset(-25., 0.));
|
StandardInput::SourceGraphic,
|
||||||
let blur_r = chromabb.add_node(Node::gaussian_blur_xy(5, 0));
|
ColorMatrixType::Matrix(Box::new([
|
||||||
let blur_b = chromabb.add_node(Node::gaussian_blur_xy(5, 0));
|
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_rb = chromabb.composite_arithmetic(blur_r, blur_b, 0., 1., 1., 0.);
|
||||||
let composite_final = chromabb.add_node(Node::composite_arithmetic(0., 1., 1., 0.));
|
|
||||||
|
|
||||||
chromabb.dag.extend_with_edges(&[
|
let chan_g = chromabb.color_matrix(
|
||||||
(chromabb.source_graphic(), chan_r),
|
StandardInput::SourceGraphic,
|
||||||
(chromabb.source_graphic(), chan_b),
|
ColorMatrixType::Matrix(Box::new([
|
||||||
(chromabb.source_graphic(), chan_g),
|
0., 0., 0., 0., 0., 0., 1., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 1., 0.,
|
||||||
(chan_r, offset_r),
|
])),
|
||||||
(offset_r, blur_r),
|
);
|
||||||
(chan_b, offset_b),
|
chromabb.composite_arithmetic(composite_rb, chan_g, 0., 1., 1., 0.);
|
||||||
(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)),
|
|
||||||
]);
|
|
||||||
|
|
||||||
black_box(doc.generate_svg());
|
println!("{}", doc.generate_svg());
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,68 +3,4 @@ pub mod nodes;
|
||||||
|
|
||||||
// pub mod old;
|
// pub mod old;
|
||||||
|
|
||||||
pub mod graph {
|
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<Node, Edge>,
|
|
||||||
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),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
202
crates/svg-filters/src/types/graph.rs
Normal file
202
crates/svg-filters/src/types/graph.rs
Normal file
|
@ -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<Node, ()>,
|
||||||
|
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<StandardInput> for NodeInput {
|
||||||
|
fn from(value: StandardInput) -> Self {
|
||||||
|
Self::Standard(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<NodeIndex> 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<NodeInput> {
|
||||||
|
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::<Vec<_>>();
|
||||||
|
|
||||||
|
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<NodeIndex> {
|
||||||
|
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<NodeInput>,
|
||||||
|
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<NodeInput>, 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<NodeInput>,
|
||||||
|
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<NodeInput>,
|
||||||
|
in2: impl Into<NodeInput>,
|
||||||
|
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;
|
19
crates/svg-filters/src/types/graph/edge.rs
Normal file
19
crates/svg-filters/src/types/graph/edge.rs
Normal file
|
@ -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),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,6 +3,12 @@ use std::fmt::Display;
|
||||||
#[derive(Default, Debug, Clone, Copy)]
|
#[derive(Default, Debug, Clone, Copy)]
|
||||||
pub struct Length(f32, Unit);
|
pub struct Length(f32, Unit);
|
||||||
|
|
||||||
|
impl Length {
|
||||||
|
pub fn is_zero(&self) -> bool {
|
||||||
|
self.0 == 0.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Display for Length {
|
impl Display for Length {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
write!(f, "{}{}", self.0, self.1)
|
write!(f, "{}{}", self.0, self.1)
|
||||||
|
|
|
@ -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::{
|
use self::{
|
||||||
primitives::{
|
primitives::{
|
||||||
|
@ -42,6 +42,41 @@ pub(crate) struct CommonAttrs {
|
||||||
pub height: Length,
|
pub height: Length,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<CommonAttrs> for Vec<Attribute<'_>> {
|
||||||
|
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 {
|
impl Node {
|
||||||
pub fn simple(el: FePrimitive) -> Node {
|
pub fn simple(el: FePrimitive) -> Node {
|
||||||
Node::Primitive {
|
Node::Primitive {
|
||||||
|
|
|
@ -1,10 +1,8 @@
|
||||||
|
use std::convert::Into;
|
||||||
|
|
||||||
use quick_xml::{events::attributes::Attribute, ElementWriter, Writer};
|
use quick_xml::{events::attributes::Attribute, ElementWriter, Writer};
|
||||||
|
|
||||||
use crate::types::length::{Coordinate, Length};
|
use super::CommonAttrs;
|
||||||
|
|
||||||
use self::blend::BlendMode;
|
|
||||||
|
|
||||||
use super::Node;
|
|
||||||
|
|
||||||
pub mod blend;
|
pub mod blend;
|
||||||
pub mod color_matrix;
|
pub mod color_matrix;
|
||||||
|
@ -29,10 +27,34 @@ pub trait WriteElement {
|
||||||
fn element_writer<'writer, 'result>(
|
fn element_writer<'writer, 'result>(
|
||||||
&self,
|
&self,
|
||||||
writer: &'writer mut Writer<&'result mut Vec<u8>>,
|
writer: &'writer mut Writer<&'result mut Vec<u8>>,
|
||||||
) -> ElementWriter<'writer, &'result mut Vec<u8>> {
|
common: CommonAttrs,
|
||||||
writer
|
inputs: Vec<String>,
|
||||||
|
output: Option<String>,
|
||||||
|
) -> quick_xml::Result<&'writer mut quick_xml::Writer<&'result mut Vec<u8>>> {
|
||||||
|
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())
|
.create_element(self.tag_name())
|
||||||
|
.with_attributes(Into::<Vec<Attribute<'_>>>::into(common))
|
||||||
.with_attributes(self.attrs())
|
.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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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)
|
/// [feBlend](https://www.w3.org/TR/SVG11/filters.html#feBlendElement)
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Blend {
|
pub struct Blend {
|
||||||
|
@ -18,6 +24,23 @@ impl Default for Blend {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl WriteElement for Blend {
|
||||||
|
fn attrs(&self) -> Vec<quick_xml::events::attributes::Attribute> {
|
||||||
|
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
|
/// as according to https://drafts.fxtf.org/compositing-1/#blending
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum BlendMode {
|
pub enum BlendMode {
|
||||||
|
@ -39,3 +62,26 @@ pub enum BlendMode {
|
||||||
Color,
|
Color,
|
||||||
Luminosity,
|
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",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use std::borrow::Cow;
|
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;
|
use super::WriteElement;
|
||||||
|
|
||||||
|
@ -23,7 +23,7 @@ impl WriteElement for ColorMatrix {
|
||||||
key: QName(b"values"),
|
key: QName(b"values"),
|
||||||
value: Cow::from(
|
value: Cow::from(
|
||||||
v.iter()
|
v.iter()
|
||||||
.map(|v| v.to_string())
|
.map(std::string::ToString::to_string)
|
||||||
.reduce(|mut acc, e| {
|
.reduce(|mut acc, e| {
|
||||||
acc.push(' ');
|
acc.push(' ');
|
||||||
acc.push_str(&e);
|
acc.push_str(&e);
|
||||||
|
@ -33,9 +33,11 @@ impl WriteElement for ColorMatrix {
|
||||||
.into_bytes(),
|
.into_bytes(),
|
||||||
),
|
),
|
||||||
}],
|
}],
|
||||||
ColorMatrixType::Saturate(v) => todo!(),
|
ColorMatrixType::Saturate(v) | ColorMatrixType::HueRotate(v) => vec![Attribute {
|
||||||
ColorMatrixType::HueRotate(v) => todo!(),
|
key: QName(b"values"),
|
||||||
ColorMatrixType::LuminanceToAlpha => todo!(),
|
value: Cow::from(v.to_string().into_bytes()),
|
||||||
|
}],
|
||||||
|
ColorMatrixType::LuminanceToAlpha => Vec::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,9 @@
|
||||||
|
use std::borrow::Cow;
|
||||||
|
|
||||||
use csscolorparser::Color;
|
use csscolorparser::Color;
|
||||||
|
use quick_xml::{events::attributes::Attribute, name::QName};
|
||||||
|
|
||||||
|
use super::WriteElement;
|
||||||
|
|
||||||
/// [feFlood](https://www.w3.org/TR/SVG11/filters.html#feFloodElement)
|
/// [feFlood](https://www.w3.org/TR/SVG11/filters.html#feFloodElement)
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -6,3 +11,22 @@ pub struct Flood {
|
||||||
flood_color: Color,
|
flood_color: Color,
|
||||||
flood_opacity: f32,
|
flood_opacity: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl WriteElement for Flood {
|
||||||
|
fn attrs(&self) -> Vec<quick_xml::events::attributes::Attribute> {
|
||||||
|
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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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)
|
/// [feMorphology](https://www.w3.org/TR/SVG11/filters.html#feMorphologyElement)
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Morphology {
|
pub struct Morphology {
|
||||||
|
@ -5,8 +11,36 @@ pub struct Morphology {
|
||||||
radius: (f32, f32),
|
radius: (f32, f32),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl WriteElement for Morphology {
|
||||||
|
fn attrs(&self) -> Vec<quick_xml::events::attributes::Attribute> {
|
||||||
|
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)]
|
#[derive(Debug)]
|
||||||
enum Operator {
|
enum Operator {
|
||||||
Erode,
|
Erode,
|
||||||
Dilate,
|
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",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,3 +1,15 @@
|
||||||
|
use super::WriteElement;
|
||||||
|
|
||||||
/// [feTile](https://www.w3.org/TR/SVG11/filters.html#feTileElement)
|
/// [feTile](https://www.w3.org/TR/SVG11/filters.html#feTileElement)
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Tile;
|
pub struct Tile;
|
||||||
|
|
||||||
|
impl WriteElement for Tile {
|
||||||
|
fn attrs(&self) -> Vec<quick_xml::events::attributes::Attribute> {
|
||||||
|
Vec::new()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn tag_name(&self) -> &'static str {
|
||||||
|
"feTile"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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)
|
/// [feTurbulence](https://www.w3.org/TR/SVG11/filters.html#feTurbulenceElement)
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Turbulence {
|
pub struct Turbulence {
|
||||||
base_frequency: (f32, f32),
|
base_frequency: (f32, f32),
|
||||||
num_octaves: (u16),
|
num_octaves: u16,
|
||||||
seed: u32,
|
seed: u32,
|
||||||
stich_tiles: StitchTiles,
|
stitch_tiles: StitchTiles,
|
||||||
// attr name: type
|
// attr name: type
|
||||||
noise_type: NoiseType,
|
noise_type: NoiseType,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
impl WriteElement for Turbulence {
|
||||||
enum StitchTiles {
|
fn attrs(&self) -> Vec<quick_xml::events::attributes::Attribute> {
|
||||||
|
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,
|
Stitch,
|
||||||
NoStitch,
|
NoStitch,
|
||||||
}
|
}
|
||||||
#[derive(Debug)]
|
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||||
enum NoiseType {
|
pub enum NoiseType {
|
||||||
Turbulence,
|
Turbulence,
|
||||||
FractalNoise,
|
FractalNoise,
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/// [svg filter effect standard input](https://www.w3.org/TR/SVG11/filters.html#FilterPrimitiveInAttribute)
|
/// [svg filter effect standard input](https://www.w3.org/TR/SVG11/filters.html#FilterPrimitiveInAttribute)
|
||||||
/// technically not a node, but for implementation simplicity... yeah
|
/// technically not a node, but for implementation simplicity... yeah
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub enum StandardInput {
|
pub enum StandardInput {
|
||||||
SourceGraphic,
|
SourceGraphic,
|
||||||
SourceAlpha,
|
SourceAlpha,
|
||||||
|
|
Loading…
Reference in a new issue