New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Seeing SVG Filter #9
Comments
I love SVG, especially the fascinating filter effects. But it's hard to learn and make some of your own effects. Usually, you learn by looking into the code made by others with the documentation. But there are 25 filter effect SVG elements, and each of them has its own attributes, not to mention the combination and composition of them. Check out the amazing effect made by Dirk Weber, even with the comments, I have no clue about how it works.<filter>
<!-- COLOR -->
<feFlood flood-color="#73DCFF" flood-opacity="0.75" result="COLOR-blu" />
<feFlood flood-color="#9673FF" flood-opacity="0.4" result="COLOR-red" />
<!-- COLOR END -->
<!-- Texture -->
<feTurbulence baseFrequency=".05" type="fractalNoise" numOctaves="3" seed="0" result="Texture_10" />
<feColorMatrix type="matrix"
values="0 0 0 0 0,
0 0 0 0 0,
0 0 0 0 0,
0 0 0 -2.1 1.1" in="Texture_10" result="Texture_20" />
<feColorMatrix result="Texture_30" type="matrix"
values="0 0 0 0 0,
0 0 0 0 0,
0 0 0 0 0,
0 0 0 -1.7 1.8" in="Texture_10" />
<!-- Texture -->
<!-- FILL -->
<feOffset dx="-3" dy="4" in="SourceAlpha" result="FILL_10"/>
<feDisplacementMap scale="17" in="FILL_10" in2="Texture_10" result="FILL_20" />
<feComposite operator="in" in="Texture_30" in2 = "FILL_20" result="FILL_40"/>
<feComposite operator="in" in="COLOR-blu" in2="FILL_40" result="FILL_50" />
<!-- FILL END-->
<!-- OUTLINE -->
<feMorphology operator="dilate" radius="3" in="SourceAlpha" result="OUTLINE_10" />
<feComposite operator="out" in="OUTLINE_10" in2 = "SourceAlpha" result="OUTLINE_20" />
<feDisplacementMap scale="7" in="OUTLINE_20" in2="Texture_10" result="OUTLINE_30" />
<feComposite operator="arithmetic" k2="-1" k3="1" in="Texture_20" in2="OUTLINE_30" result="OUTLINE_40" />
<!-- OUTLINE END-->
<!-- BEVEL OUTLINE -->
<feConvolveMatrix order="8,8" divisor="1" kernelMatrix="1 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 1 " in="SourceAlpha" result="BEVEL_10" />
<feMorphology operator="dilate" radius="2" in="BEVEL_10" result="BEVEL_20" />
<feComposite operator="out" in="BEVEL_20" in2="BEVEL_10" result="BEVEL_30"/>
<feDisplacementMap scale="7" in="BEVEL_30" in2="Texture_10" result="BEVEL_40" />
<feComposite operator="arithmetic" k2="-1" k3="1" in="Texture_20" in2="BEVEL_40" result="BEVEL_50" />
<feOffset dx="-7" dy="-7" in="BEVEL_50" result="BEVEL_60"/>
<feComposite operator="out" in="BEVEL_60" in2 = "OUTLINE_10" result="BEVEL_70" />
<!-- BEVEL OUTLINE END -->
<!-- BEVEL FILL -->
<feOffset dx="-9" dy="-9" in="BEVEL_10" result="BEVEL-FILL_10"/>
<feComposite operator="out" in="BEVEL-FILL_10" in2 = "OUTLINE_10" result="BEVEL-FILL_20" />
<feDisplacementMap scale="17" in="BEVEL-FILL_20" in2="Texture_10" result="BEVEL-FILL_30" />
<feComposite operator="in" in="COLOR-red" in2="BEVEL-FILL_30" result="BEVEL-FILL_50" />
<!-- BEVEL FILL END-->
<feMerge result="merge2">
<feMergeNode in="BEVEL-FILL_50" />
<feMergeNode in="BEVEL_70" />
<feMergeNode in="FILL_50" />
<feMergeNode in="OUTLINE_40" />
</feMerge>
</filter> So I decide to make a small tool to help me understand the structure of the filter and how each of them works. The idea
The Data StructureFirst, we need to find a proper data structure to represent the filter and the connections between them. A filter is composed of one or several SVG filter effect elements, the atomic part, which is kinda like a function in computer language. It takes inputs from the output of other effect elements, modifies the graphic, and passes the output to the next one. The output of the last effect element will be the result of this filter. It's like the pedal board in front of a guitar player. The input signal is from the guitar, then each pedal modifies the signal to the next one. They can be arranged in different ways. Then the last pedal sends the output signal to the AMP. So is it a tree? Wait, there are some elements doesn't work like a pure function. They can produce output by themselves, like Let's group the filter elements by the way they take inputs:
We need to implement a interface parseFilter {
(filter: SVGFilterElement): Graph;
}
interface Graph {
nodes: { id: string };
links: { source: string, target: string };
} Based on what we know about the Filter Effect Group, we can loop through the children of <filter> to find inputs for each item. Here is the implementation: const parseFilter = (filter) => {
const [nodes, links] = [...filter.children].reduce(
([nodeMemo, linkMemo], child, i) => {
child.id = `${child.tagName}${ID_JOIN}${i}`;
const linkSet = getLinks(child, filter)
.filter((link) => link)
.reduce((memo, link) => new Set([...memo, link]), new Set());
return [
[...nodeMemo, child.id],
new Set([...linkMemo, ...linkSet])
];
},
[[...FE.SOURCE].map(([feName]) => feName), new Set()]
);
// [[source, target]]
const linksTuple = [...links].map((link) => link.split(LINK_JOIN));
const nodesInFilter = new Set(linksTuple.flatMap((item) => item));
return {
nodes: nodes
.filter((id) => nodesInFilter.has(id))
.map((id) => ({ id })),
links: linksTuple.map(([source, target]) => ({ source, target }))
};
}; FEconst source = new Map([
['SourceGraphic', ''],
['SourceAlpha', ''],
['BackgroundImage', ''],
['BackgroundAlpha', ''],
['FillPaint', ''],
['StrokePaint', '']
]);
const noInput = new Map([
['feImage', 'orange'],
['feTurbulence', 'drakorange'],
['feFlood', 'orangered']
]);
const withInput2 = new Map([
['feBlend', 'blueviolet'],
['feComposite', 'royalblue'],
['feDisplacementMap', 'darkslateblue']
]);
const withInput = new Map([
...withInput2,
['feColorMatrix', 'blue'],
['feComponentTransfer', 'steelblue'],
['feConvolveMatrix', 'slateblue'],
['feDiffuseLighting', 'skyblue'],
['feSpecularLighting', 'lightblue'],
['feDropShadow', 'powderblue'],
['feGaussianBlur', 'midnightblue'],
['feMergeNode', 'drakblue'],
['feMorphology', 'dodgerblue'],
['feOffset', 'cadetblue'],
['feTile', 'cornflowerblue']
]);
const withInputs = new Map([['feMerge', 'yellowgreen']]);
export const FE = {
SOURCE: source,
NO_INPUT: noInput,
WITH_INPUT: withInput,
WITH_INPUT2: withInput2,
WITH_INPUTS: withInputs,
ALL: new Map([...source, ...noInput, ...withInput, ...withInputs])
}; getLink()const getLinks = (node, container) => {
if (FE.NO_INPUT.has(node.tagName)) {
return [null];
}
if (FE.WITH_INPUTS.has(node.tagName)) {
return [...node.children].map((child) => {
const inStr = child.getAttribute('in');
const source = container.querySelector(`[result="${inStr}"]`);
return `${source?.id || inStr}${LINK_JOIN}${node.id}`;
});
}
if (FE.WITH_INPUT.has(node.tagName)) {
const ins = [
getInputs(node, 'in'),
FE.WITH_INPUT2.has(node.tagName) ? getInputs(node, 'in2') : null
].filter((item) => item !== undefined && item !== null);
return ins.map((inStr) => {
const source = container.querySelector(`[result="${inStr}"]`);
return `${source?.id || inStr}${LINK_JOIN}${node.id}`;
});
}
throw new Error(`no links found ${node.id}`);
};
const getInputs = (node, attr = 'in') => {
const val = node.getAttribute(attr);
if (val) {
return val;
}
if (node.isSameNode(node.parentNode.firstElementChild)) {
return 'SourceGraphic';
}
const children = [...node.parentNode.children];
const index = children.findIndex((item) => item.isSameNode(node));
return children[index - 1].id;
}; VisualizationThe Sankey graph is a perfect way to demonstrate this data structure. I choose d3-sankey because it doesn't render the graph directly, just offers the data to render. So I can take control of the details of the graph. After giving different colors to each type of filter element and adjusting the connection line between them, this is what I got: Meet seeing-svg-filterAfter adding some other features, seeing-svg-filter is born. It can:
Now I have the perfect tool to learn more about SVG filters. Maybe I'll try to implement some of them to understand better. And maybe move this tool further, to make another tool to easily generate a new filter effect.
Further readingFootnotes |
The text was updated successfully, but these errors were encountered: