http://mathling.com/svg/effects library module
http://mathling.com/svg/effects
Module with functions providing some filter-building support.
Note: this provides hooks to reference Inkscape filters by name
too, but you'll need to install your own version of Inkscape and
point at the filter file (/usr/share/inkscape/filters/filters.svg)
for that to work. By default we expect to find it at ../EFFECTS/inkscape_effects.svg
Copyright© Mary Holstege 2021-2023
CC-BY (https://creativecommons.org/licenses/by/4.0/)
Status: Active
Imports
http://mathling.com/core/randomimport module namespace rand="http://mathling.com/core/random" at "../core/random.xqy"http://mathling.com/core/utilities
import module namespace util="http://mathling.com/core/utilities" at "../core/utilities.xqy"http://mathling.com/svg/gradients
import module namespace gradient="http://mathling.com/svg/gradients" at "../svg/gradients.xqy"http://mathling.com/type/distribution
import module namespace dist="http://mathling.com/type/distribution" at "../types/distributions.xqy"http://mathling.com/geometric/rectangle
import module namespace box="http://mathling.com/geometric/rectangle" at "../geo/rectangle.xqy"http://mathling.com/core/sequences
import module namespace seq="http://mathling.com/core/sequences" at "../core/sequences.xqy"http://mathling.com/geometric/point
import module namespace point="http://mathling.com/geometric/point" at "../geo/point.xqy"
Variables
Variable: $BUILTIN-EFFECTS as xs:string*
Variable: $NAMED-EFFECTS as xs:string*
Variable: $INKSCAPE-ALIASES as map(xs:string,xs:string)
Variable: $KNOWN-ALIASES as xs:string*
Variable: $PARAMETERIZED-PREFIXES as xs:string*
Variable: $PREFIX-REGEX as xs:string
Variable: $PARAMETERIZED-SUFFIXES as xs:string*
Variable: $SUFFIX-REGEX as xs:string
Variable: $MODIFIER-SUFFIXES as xs:string*
Variable: $MODIFIER-REGEX as xs:string
Variable: $GRAIN-MATRIX as xs:double*
Variable: $WATERGRAIN-MATRIX as xs:double*
Functions
Function: fetch-effect
declare function fetch-effect($effect as xs:string) as element()?
declare function fetch-effect($effect as xs:string) as element()?
fetch-effect()
Get the filter definition of a known named filter.
Empty otherwise.
Params
- effect as xs:string
Returns
- element()?
declare function this:fetch-effect($effect as xs:string) as element()? { (: SLEAZY HACK ALERT :) if ($effect = $this:BUILTIN-EFFECTS) then ( (: Note: may depend on Inkscape effects :) switch ($effect) case "sandstone" return this:perturb(this:effect("rough_paper"), 1.0, 0.25)=>this:rename($effect) case "sandpaper" return this:perturb(this:effect("linen_canvas"), 1.0, 2.0)=>this:rename($effect) default return () ) else if ($effect = $this:NAMED-EFFECTS) then ( (: Is is a known effect in the effects stylesheet? :) (: trace((), "Named effect "||$effect), :) let $def := try { doc("../stylesheets/effects.xsl")//xsl:param[@name=$effect]/svg:filter } catch * { () } return ( if (exists($def)) then $def else ( try { doc("../EFFECTS/"||$effect||".svg")//svg:defs/svg:filter } catch * { () } ) ) ) else if ($effect = $this:INKSCAPE-ALIASES=>map:keys()) then ( (: Is it a known Inkscape effect, as named in our mapping? :) trace((), "Inkscape alias effect "||$effect), try { let $def := doc("../EFFECTS/inkscape_effects.svg")//svg:filter[@id=$this:INKSCAPE-ALIASES($effect)] return ( if (exists($def)) then ( <svg:filter id="{$effect}">{ $def/@* except ($def/@id|$def/@inkscape:*), $def/node() }</svg:filter> ) else () ) } catch * { () } ) else ( (: Is it a known Inkscape effect, as named by Inkscape? :) trace((), "Inkscape effect "||$effect), try { let $def := doc("../EFFECTS/inkscape_effects.svg")//svg:filter[@id=$effect] return ( if (exists($def)) then ( <svg:filter>{ $def/@* except $def/@inkscape:*, $def/node() }</svg:filter> ) else () ) } catch * {()} ) }
Function: get-effect-definition
declare function get-effect-definition($effect as xs:string) as element()?
declare function get-effect-definition($effect as xs:string) as element()?
get-effect-definition()
Get the effect definition of the name, allowing for variants.
Params
- effect as xs:string
Returns
- element()?
declare function this:get-effect-definition($effect as xs:string) as element()? { let $effect-pieces := tokenize($effect, "·") (: middle dot :) let $effects := for $effect in $effect-pieces return ( if (($effect = $this:NAMED-EFFECTS) or ($effect = $this:KNOWN-ALIASES)) then ( this:fetch-effect($effect) ) else if (matches($effect, $this:MODIFIER-REGEX)) then ( let $base-name := replace($effect, $this:MODIFIER-REGEX, '') let $modifier := substring-after($effect, $base-name||"-") let $base := this:get-effect-definition($base-name) return switch($modifier) case "overlay" return this:overlay-source($base)=>this:rename($effect) case "blend-normal" return this:blend-source($base, "normal")=>this:rename($effect) case "blend-multiply" return this:blend-source($base, "multiply")=>this:rename($effect) case "blend-screen" return this:blend-source($base, "screen")=>this:rename($effect) case "blend-darken" return this:blend-source($base, "darken")=>this:rename($effect) case "blend-lighten" return this:blend-source($base, "lighten")=>this:rename($effect) case "compose-over" return this:compose-source($base, "over")=>this:rename($effect) case "compose-in" return this:compose-source($base, "in")=>this:rename($effect) case "compose-out" return this:compose-source($base, "out")=>this:rename($effect) case "compose-atop" return this:compose-source($base, "atop")=>this:rename($effect) case "compose-xor" return this:compose-source($base, "xor")=>this:rename($effect) default return (util:log("Modifier not found "||$modifier||" to "||$base)) ) else if (matches($effect, $this:PREFIX-REGEX)) then ( let $parameters := replace($effect, $this:PREFIX-REGEX, '') let $operation := substring-before($effect, "-"||$parameters) return switch($operation) case "shadow" return this:shadow($effect, xs:integer($parameters)) case "fuzz" return this:fuzz($effect, xs:integer($parameters)) case "speckled" return this:speckled($effect, 0.25, number($parameters)) case "wavy" return this:wavy($effect, (0.04, 0.2), xs:integer($parameters)) default return (util:log("Prefix not found "||$operation||" with "||$parameters)) ) else if (matches($effect, $this:SUFFIX-REGEX)) then ( let $parameters := replace($effect, $this:SUFFIX-REGEX, '') let $operation := substring-after($effect, $parameters||"-") return switch ($operation) case "softglow" return this:glow($effect, $parameters, 0.4, 1, 3) case "hardglow" return this:glow($effect, $parameters, 1, 5, 11) case "glow" return this:glow($effect, $parameters, 1, 2, 5) case "roughpaper" return this:texture($effect, $parameters, 0.04) case "stone" return this:texture($effect, $parameters, 0.02) case "grain" return this:grain($effect, $parameters, $this:GRAIN-MATRIX) case "watergrain" return this:grain($effect, $parameters, $this:WATERGRAIN-MATRIX) case "glint" return this:glint($effect, $parameters, point:point(200, 200, 50)) case "filigree" return this:filigree($effect, $parameters) default return (util:log("Suffix not found "||$operation||"+"||$parameters)) ) else ( util:log("Unknown effect") ) ) return ( if (count($effects) le 1) then $effects else if (count($effects) = count($effect-pieces)) then ( this:merge($effect, $effects) ) else ( util:log("Unprocessed effect piece: no effect generated") ) ) }
Function: effect
declare function effect($effect as xs:string) as element()?
declare function effect($effect as xs:string) as element()?
Params
- effect as xs:string
Returns
- element()?
declare function this:effect($effect as xs:string) as element()? { this:get-effect-definition($effect) }
Function: ref
declare function ref($effect as xs:string) as xs:string
declare function ref($effect as xs:string) as xs:string
Params
- effect as xs:string
Returns
- xs:string
declare function this:ref($effect as xs:string) as xs:string { if ($effect=("none","")) then "none" else if (starts-with($effect, "url(")) then $effect else "url(#"||$effect||")" }
Function: shadow
declare function shadow($name as xs:string,
$height as xs:integer) as element(svg:filter)
declare function shadow($name as xs:string, $height as xs:integer) as element(svg:filter)
Params
- name as xs:string
- height as xs:integer
Returns
- element(svg:filter)
declare function this:shadow( $name as xs:string, $height as xs:integer ) as element(svg:filter) { <svg:filter id="{$name}" x="-0.25" y="-0.25" width="1.5" height="1.5"> <svg:feGaussianBlur in="SourceAlpha" stdDeviation="2.5" result="blur"/> <svg:feColorMatrix result="bluralpha" type="matrix" values= "1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 0.4 0 "/> <svg:feOffset in="bluralpha" dx="3" dy="{$height}" result="offsetBlur"/> <svg:feMerge result="{rand:id('filter')}"> <svg:feMergeNode in="offsetBlur"/> <svg:feMergeNode in="SourceGraphic"/> </svg:feMerge> </svg:filter> }
Function: fuzz
declare function fuzz($name as xs:string,
$width as xs:integer) as element(svg:filter)
declare function fuzz($name as xs:string, $width as xs:integer) as element(svg:filter)
Params
- name as xs:string
- width as xs:integer
Returns
- element(svg:filter)
declare function this:fuzz( $name as xs:string, $width as xs:integer ) as element(svg:filter) { <svg:filter id="{$name}" filterUnits="userSpaceOnUse" x="0%" y="0%" width="100%" height="100%"> <svg:feGaussianBlur stdDeviation="{$width}" result="{rand:id('filter')}"/> </svg:filter> }
Function: glow
declare function glow($name as xs:string,
$colour as xs:string,
$strength as xs:double, (: [0,1] :)
$size as xs:integer,
$spread as xs:integer) as element(svg:filter)
declare function glow($name as xs:string, $colour as xs:string, $strength as xs:double, (: [0,1] :) $size as xs:integer, $spread as xs:integer) as element(svg:filter)
Params
- name as xs:string
- colour as xs:string
- strength as xs:double
- size as xs:integer
- spread as xs:integer
Returns
- element(svg:filter)
declare function this:glow( $name as xs:string, $colour as xs:string, $strength as xs:double, (: [0,1] :) $size as xs:integer, $spread as xs:integer ) as element(svg:filter) { <svg:filter id="{$name}" x="{-100*$size}%" y="{-100*$size}%" width="{2*100*$size}%" height="{2*100*$size}%"> <svg:feFlood result="flood" flood-color="{gradient:colour($colour)}" flood-opacity="{$strength}"></svg:feFlood> <svg:feComposite in="flood" result="mask" in2="SourceGraphic" operator="in"></svg:feComposite> <svg:feMorphology in="mask" result="dilated" operator="dilate" radius="{$size}"></svg:feMorphology> <svg:feGaussianBlur in="dilated" result="blurred" stdDeviation="{$spread}"></svg:feGaussianBlur> <svg:feMerge result="{rand:id('filter')}"> <svg:feMergeNode in="blurred"></svg:feMergeNode> <svg:feMergeNode in="SourceGraphic"></svg:feMergeNode> </svg:feMerge> </svg:filter> }
Function: texture
declare function texture($name as xs:string,
$colour as xs:string,
$roughness as xs:double) as element(svg:filter)
declare function texture($name as xs:string, $colour as xs:string, $roughness as xs:double) as element(svg:filter)
Params
- name as xs:string
- colour as xs:string
- roughness as xs:double
Returns
- element(svg:filter)
declare function this:texture( $name as xs:string, $colour as xs:string, $roughness as xs:double ) as element(svg:filter) { <svg:filter id="{$name}" x="0%" y="0%" width="100%" height="100%"> <svg:feTurbulence type="fractalNoise" baseFrequency="{$roughness}" numOctaves="5" result="noise"/> <svg:feDiffuseLighting in="noise" lighting-color="{gradient:colour($colour)}" surfaceScale="2" result="{rand:id('filter')}"> <svg:feDistantLight azimuth="45" elevation="35"/> </svg:feDiffuseLighting> </svg:filter> }
Function: grain
declare function grain($name as xs:string,
$colour as xs:string,
$matrix as xs:double*) as element(svg:filter)
declare function grain($name as xs:string, $colour as xs:string, $matrix as xs:double*) as element(svg:filter)
Params
- name as xs:string
- colour as xs:string
- matrix as xs:double*
Returns
- element(svg:filter)
declare function this:grain( $name as xs:string, $colour as xs:string, $matrix as xs:double* ) as element(svg:filter) { <svg:filter id="{$name}" > <svg:feFlood flood-color="{gradient:colour($colour)}" result="one"/> <svg:feTurbulence baseFrequency=".004,.25" numOctaves="1" seed="403" result="turb"/> <svg:feColorMatrix type="matrix" result="turb1" values="{string-join($matrix!string(.),' ')}"/> <svg:feMerge result="{rand:id('filter')}"> <svg:feMergeNode in="turb"/> <svg:feMergeNode in="one"/> <svg:feMergeNode in="turb1"/> </svg:feMerge> </svg:filter> }
Function: filigree
declare function filigree($name as xs:string,
$colour as xs:string) as element(svg:filter)
declare function filigree($name as xs:string, $colour as xs:string) as element(svg:filter)
Params
- name as xs:string
- colour as xs:string
Returns
- element(svg:filter)
declare function this:filigree( $name as xs:string, $colour as xs:string ) as element(svg:filter) { <svg:filter id="{$name}" filterUnits="userSpaceOnUse" x="0" y="0" width="110%" height="110%" style="color-interpolation-filters:sRGB;"> <svg:feFlood flood-color="{$colour}" result="result9"/> <svg:feGaussianBlur stdDeviation="7" in="SourceGraphic" result="result8"/> <svg:feTurbulence type="turbulence" numOctaves="1" baseFrequency="0.09" result="result7"/> <svg:feColorMatrix result="result5" values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 4 0 "/> <svg:feComposite in="result8" in2="result8" operator="in" result="result6"/> <svg:feDisplacementMap in="result5" in2="result6" xChannelSelector="A" yChannelSelector="A" scale="100" result="result4"/> <svg:feComposite in="result8" in2="result4" operator="in" result="result2"/> <svg:feComposite operator="in" in="SourceGraphic" in2="result2" result="fbSourceGraphic"/> <svg:feMerge result="result10"> <svg:feMergeNode in="result9"/> <svg:feMergeNode in="fbSourceGraphic"/> </svg:feMerge> <svg:feComposite in2="SourceGraphic" operator="in" result="result11"/> </svg:filter> }
Function: glint
declare function glint($name as xs:string,
$colour as xs:string,
$light as map(xs:string,item()*)) as element(svg:filter)
declare function glint($name as xs:string, $colour as xs:string, $light as map(xs:string,item()*)) as element(svg:filter)
Params
- name as xs:string
- colour as xs:string
- light as map(xs:string,item()*)
Returns
- element(svg:filter)
declare function this:glint( $name as xs:string, $colour as xs:string, $light as map(xs:string,item()*) ) as element(svg:filter) { <svg:filter id="{$name}" filterUnits="userSpaceOnUse" x="0" y="0" width="100%" height="100%"> <svg:feSpecularLighting in="SourceGraphic" surfaceScale="5" specularConstant="0.5" specularExponent="90" lighting-color="{gradient:colour($colour)}" result="specOut"> <svg:fePointLight x="{point:x($light)}" y="{point:y($light)}" z="{point:z($light)}"/> </svg:feSpecularLighting> <svg:feComposite in="specOut" in2="SourceAlpha" operator="in" result="specOut"/> <svg:feComposite result="{rand:id('filter')}" in="SourceGraphic" in2="specOut" operator="arithmetic" k1="0" k2="1" k3="1" k4="0"/> </svg:filter> }
Function: speckled
declare function speckled($name as xs:string,
$frequency as xs:double,
$scale as xs:double) as element(svg:filter)
declare function speckled($name as xs:string, $frequency as xs:double, $scale as xs:double) as element(svg:filter)
Params
- name as xs:string
- frequency as xs:double
- scale as xs:double
Returns
- element(svg:filter)
declare function this:speckled( $name as xs:string, $frequency as xs:double, $scale as xs:double ) as element(svg:filter) { <svg:filter id="{$name}" x="0%" y="0%" width="100%" height="100%"> <svg:feTurbulence type="turbulence" baseFrequency="{$frequency}" numOctaves="3" result="turbulence"/> <svg:feDisplacementMap result="{rand:id('filter')}" in2="turbulence" in="SourceGraphic" scale="{$scale}" xChannelSelector="R" yChannelSelector="G"/> </svg:filter> }
Function: wavy
declare function wavy($name as xs:string,
$frequencies as xs:double*, (: x,y or x=y one value :)
$scale as xs:integer) as element(svg:filter)
declare function wavy($name as xs:string, $frequencies as xs:double*, (: x,y or x=y one value :) $scale as xs:integer) as element(svg:filter)
Params
- name as xs:string
- frequencies as xs:double*
- scale as xs:integer
Returns
- element(svg:filter)
declare function this:wavy( $name as xs:string, $frequencies as xs:double*, (: x,y or x=y one value :) $scale as xs:integer ) as element(svg:filter) { <svg:filter id="{$name}"> <svg:feTurbulence baseFrequency="{string-join($frequencies!string(.),',')}" numOctaves="2" result="turb"/> <svg:feDisplacementMap result="{rand:id('filter')}" in="SourceGraphic" in2="turb" scale="{$scale}" xChannelSelector="R" yChannelSelector="B"/> </svg:filter> }
Function: effect-ref
declare function effect-ref($name as xs:string,
$base-effect-name as xs:string) as element(svg:filter)
declare function effect-ref($name as xs:string, $base-effect-name as xs:string) as element(svg:filter)
Params
- name as xs:string
- base-effect-name as xs:string
Returns
- element(svg:filter)
declare function this:effect-ref( $name as xs:string, $base-effect-name as xs:string ) as element(svg:filter) { let $def := this:effect($base-effect-name) return ( <svg:filter id="{$name}" xlink:href="#{$base-effect-name}"/> ) }
Function: merge
declare function merge($name as xs:string,
$effects as element(svg:filter)*) as element(svg:filter)
declare function merge($name as xs:string, $effects as element(svg:filter)*) as element(svg:filter)
merge()
Merge several filters into one. Take the min (x%, y%) and max (height%, width%)
as the new extent.
Params
- name as xs:string: name for new filter @parm $effects: set of filters to combine
- effects as element(svg:filter)*
Returns
- element(svg:filter): new filter
declare function this:merge( $name as xs:string, $effects as element(svg:filter)* ) as element(svg:filter) { let $min-x := min( for $x in $effects/@x[ends-with(.,"%")] return number(substring-before($x,"%")) ) let $min-y := min( for $y in $effects/@x[ends-with(.,"%")] return number(substring-before($y,"%")) ) let $max-width := max( for $width in $effects/@width[ends-with(.,"%")] return number(substring-before($width,"%")) ) let $max-height := max( for $height in $effects/@height[ends-with(.,"%")] return number(substring-before($height,"%")) ) let $bodies := for $effect in $effects return ( <body>{ if (exists($effect/*[last()]/@result)) then ( $effect/node() ) else ( $effect/*[position() < last()], let $last := $effect/*[last()] return ( $effect/node()[position() < last()], element {node-name($last)} { $last/@*, attribute result {rand:id("step")}, $last/node() } ) ) }</body> ) return ( <svg:filter id="{$name}">{ if (exists($min-x)) then attribute x {$min-x||"%"} else (), if (exists($min-y)) then attribute y {$min-y||"%"} else (), if (exists($max-width)) then attribute width {$max-width||"%"} else (), if (exists($max-height)) then attribute height {$max-height||"%"} else (), $bodies/*, <svg:feMerge result="{rand:id('filter')}">{ for $effect in $bodies return ( <svg:feMergeNode in="{$effect/*[last()]/@result}"/> ) }</svg:feMerge> }</svg:filter> ) }
Function: overlay-source
declare function overlay-source($base as element(svg:filter)) as element(svg:filter)
declare function overlay-source($base as element(svg:filter)) as element(svg:filter)
this:overlay-source()
Construct a filter that overlays the source graphic. Useful for filters
like 'dust' that completely replace the source graphic.
Params
- base as element(svg:filter): base effect to modify
Returns
- element(svg:filter)
declare function this:overlay-source( $base as element(svg:filter) ) as element(svg:filter) { <svg:filter>{ $base/@*, $base/node(), <svg:feMerge> <svg:feMergeNode in="SourceGraphic"/> <svg:feMergeNode/> </svg:feMerge> }</svg:filter> }
Function: blend
declare function blend($effect1 as element(svg:filter),
$effect2 as element(svg:filter),
$mode as xs:string) as element(svg:filter)
declare function blend($effect1 as element(svg:filter), $effect2 as element(svg:filter), $mode as xs:string) as element(svg:filter)
this:blend()
Construct a filter that blends the outputs of the two effects.
Take the min (x%, y%) and max (height%, width%) as the new extent.
Params
- effect1 as element(svg:filter): first effect
- effect2 as element(svg:filter): second effect
- mode as xs:string: blend mode (normal|multiply|screen|darken|lighten)
Returns
- element(svg:filter): new filter
declare function this:blend( $effect1 as element(svg:filter), $effect2 as element(svg:filter), $mode as xs:string ) as element(svg:filter) { let $effects := ($effect1, $effect2) let $min-x := min( for $x in $effects/@x[ends-with(.,"%")] return number(substring-before($x,"%")) ) let $min-y := min( for $y in $effects/@x[ends-with(.,"%")] return number(substring-before($y,"%")) ) let $max-width := max( for $width in $effects/@width[ends-with(.,"%")] return number(substring-before($width,"%")) ) let $max-height := max( for $height in $effects/@height[ends-with(.,"%")] return number(substring-before($height,"%")) ) let $bodies := for $effect in $effects return ( <body>{ if (exists($effect/*[last()]/@result)) then ( $effect/node() ) else ( $effect/*[position() < last()], let $last := $effect/*[last()] return ( $effect/node()[position() < last()], element {node-name($last)} { $last/@*, attribute result {rand:id("step")}, $last/node() } ) ) }</body> ) return ( <svg:filter>{ $effect1/@id, if (exists($min-x)) then attribute x {$min-x||"%"} else (), if (exists($min-y)) then attribute y {$min-y||"%"} else (), if (exists($max-width)) then attribute width {$max-width||"%"} else (), if (exists($max-height)) then attribute height {$max-height||"%"} else (), $bodies/*, <svg:feBlend result="{rand:id('filter')}" mode="{$mode}" in="{$bodies[1]/*[last()]/@result}" in2="{$bodies[2]/*[last()]/@result}"/> }</svg:filter> ) }
Function: blend-source
declare function blend-source($base as element(svg:filter),
$mode as xs:string) as element(svg:filter)
declare function blend-source($base as element(svg:filter), $mode as xs:string) as element(svg:filter)
this:blend-source()
Construct a filter that blends source graphic with output of base effect.
Params
- base as element(svg:filter): base effect to modify
- mode as xs:string: blend mode (normal|multiply|screen|darken|lighten)
Returns
- element(svg:filter): new filter
declare function this:blend-source( $base as element(svg:filter), $mode as xs:string ) as element(svg:filter) { <svg:filter>{ $base/@*, $base/node(), <svg:feBlend mode="{$mode}" in="SourceGraphic"/> }</svg:filter> }
Function: compose-source
declare function compose-source($effect1 as element(svg:filter),
$effect2 as element(svg:filter),
$mode as xs:string) as element(svg:filter)
declare function compose-source($effect1 as element(svg:filter), $effect2 as element(svg:filter), $mode as xs:string) as element(svg:filter)
this:compose-source()
Construct a filter that composes the outputs of the two effects
Params
- effect1 as element(svg:filter): first effect2: second effect
- effect2 as element(svg:filter)
- mode as xs:string: composition mode (over|in|out|atop|xor)
Returns
- element(svg:filter): new filter
declare function this:compose-source( $effect1 as element(svg:filter), $effect2 as element(svg:filter), $mode as xs:string ) as element(svg:filter) { let $effects := ($effect1, $effect2) let $min-x := min( for $x in $effects/@x[ends-with(.,"%")] return number(substring-before($x,"%")) ) let $min-y := min( for $y in $effects/@x[ends-with(.,"%")] return number(substring-before($y,"%")) ) let $max-width := max( for $width in $effects/@width[ends-with(.,"%")] return number(substring-before($width,"%")) ) let $max-height := max( for $height in $effects/@height[ends-with(.,"%")] return number(substring-before($height,"%")) ) let $bodies := for $effect in $effects return ( <body>{ if (exists($effect/*[last()]/@result)) then ( $effect/node() ) else ( $effect/*[position() < last()], let $last := $effect/*[last()] return ( $effect/node()[position() < last()], element {node-name($last)} { $last/@*, attribute result {rand:id("step")}, $last/node() } ) ) }</body> ) return ( <svg:filter>{ $effect1/@id, if (exists($min-x)) then attribute x {$min-x||"%"} else (), if (exists($min-y)) then attribute y {$min-y||"%"} else (), if (exists($max-width)) then attribute width {$max-width||"%"} else (), if (exists($max-height)) then attribute height {$max-height||"%"} else (), $bodies/*, <svg:feComposite result="{rand:id('filter')}" operator="{$mode}" in="{$bodies[1]/*[last()]/@result}" in2="{$bodies[2]/*[last()]/@result}"/> }</svg:filter> ) }
Function: compose-source
declare function compose-source($base as element(svg:filter),
$mode as xs:string) as element(svg:filter)
declare function compose-source($base as element(svg:filter), $mode as xs:string) as element(svg:filter)
Params
- base as element(svg:filter)
- mode as xs:string
Returns
- element(svg:filter)
declare function this:compose-source( $base as element(svg:filter), $mode as xs:string ) as element(svg:filter) { <svg:filter>{ $base/@*, $base/node(), <svg:feComposite operator="{$mode}" in="SourceGraphic"/> }</svg:filter> }
Function: rename
declare function rename($base as element(svg:filter),
$name as xs:string) as element(svg:filter)
declare function rename($base as element(svg:filter), $name as xs:string) as element(svg:filter)
rename()
Rename the filter
Params
- base as element(svg:filter): base effect to modify
- name as xs:string: new name of effect
Returns
- element(svg:filter)
declare function this:rename( $base as element(svg:filter), $name as xs:string ) as element(svg:filter) { <svg:filter id="{$name}">{ $base/(@* except @id), $base/node() }</svg:filter> }
Function: extent
declare function extent($base as element(svg:filter),
$extent as map(xs:string,item()*),
$percent as xs:boolean) as element(svg:filter)
declare function extent($base as element(svg:filter), $extent as map(xs:string,item()*), $percent as xs:boolean) as element(svg:filter)
extent()
Create a new effect with different extent (x/y, width/height) as a base effect
Params
- base as element(svg:filter): base effect to modify
- extent as map(xs:string,item()*): a box defining the extent min-point => x/y width/height => width/height Example: [-10, -10, 120, 120] => x="-10%" y="-10%" width="130%" height="130%"
- percent as xs:boolean: interpret extent as percentages (default=true())
Returns
- element(svg:filter): new filter
declare function this:extent( $base as element(svg:filter), $extent as map(xs:string,item()*), $percent as xs:boolean ) as element(svg:filter) { let $pc := if ($percent) then "%" else "" return <svg:filter x="{point:x(box:min-point($extent))}{$pc}" y="{point:y(box:min-point($extent))}{$pc}" width="{box:width($extent)}{$pc}" height="{box:height($extent)}{$pc}" >{ $base/(@* except (@x|@y|@width|@height)), $base/node() }</svg:filter> }
Function: extent
declare function extent($base as element(svg:filter),
$extent as map(xs:string,item()*)) as element(svg:filter)
declare function extent($base as element(svg:filter), $extent as map(xs:string,item()*)) as element(svg:filter)
Params
- base as element(svg:filter)
- extent as map(xs:string,item()*)
Returns
- element(svg:filter)
declare function this:extent( $base as element(svg:filter), $extent as map(xs:string,item()*) ) as element(svg:filter) { this:extent($base, $extent, true()) }
Function: colour
declare function colour($base as element(svg:filter),
$colours as xs:string*) as element(svg:filter)
declare function colour($base as element(svg:filter), $colours as xs:string*) as element(svg:filter)
colour()
Create a new effect with colours replaced with a new colour. All the
lighting-color or flood-color attributes on svg:feFlood, svg:feSpecularLighting,
and svg:feDiffuseLighting elements will be changed to the new colour. If
more than one colour is given, the colours will be selected in rotation.
Params
- base as element(svg:filter): base effect to modify
- colours as xs:string*: new colours
Returns
- element(svg:filter): new filter
declare function this:colour( $base as element(svg:filter), $colours as xs:string* ) as element(svg:filter) { <svg:filter>{ $base/@*, this:replace-colour($base/node(), $colours!gradient:colour(.), ()) }</svg:filter> }
Function: colour
declare function colour($base as element(svg:filter),
$colours as xs:string*,
$opacity as xs:double?) as element(svg:filter)
declare function colour($base as element(svg:filter), $colours as xs:string*, $opacity as xs:double?) as element(svg:filter)
Params
- base as element(svg:filter)
- colours as xs:string*
- opacity as xs:double?
Returns
- element(svg:filter)
declare function this:colour( $base as element(svg:filter), $colours as xs:string*, $opacity as xs:double? ) as element(svg:filter) { <svg:filter>{ $base/@*, this:replace-colour($base/node(), $colours!gradient:colour(.), $opacity) }</svg:filter> }
Function: point-light
declare function point-light($base as element(svg:filter),
$point as map(xs:string,item()*)) as element(svg:filter)
declare function point-light($base as element(svg:filter), $point as map(xs:string,item()*)) as element(svg:filter)
point-light()
Create a new effect with the lightsource replaced with a point light source.
svg:feDistantLight, svg:fePointLight, svg:feSpotLight in
svg:feDiffuseLighting or svg:feSpecularLighting elements will be replaced
with the new point light source.
Params
- base as element(svg:filter): base effect to modify
- point as map(xs:string,item()*): new point light source (should have a z value)
Returns
- element(svg:filter): new filter
declare function this:point-light( $base as element(svg:filter), $point as map(xs:string,item()*) ) as element(svg:filter) { <svg:filter>{ $base/@*, this:replace-light($base/node(), $point) }</svg:filter> }
Function: distant-light
declare function distant-light($base as element(svg:filter),
$elevation as xs:double,
$azimuth as xs:double) as element(svg:filter)
declare function distant-light($base as element(svg:filter), $elevation as xs:double, $azimuth as xs:double) as element(svg:filter)
distant-light()
Create a new effect with the lightsource replaced with a distant light source.
svg:feDistantLight, svg:fePointLight, svg:feSpotLight in
svg:feDiffuseLighting or svg:feSpecularLighting elements will be replaced
with the new light source.
Params
- base as element(svg:filter): base effect to modify
- elevation as xs:double: new elevation of the light source
- azimuth as xs:double: new azimuth of the light source
Returns
- element(svg:filter): new filter
declare function this:distant-light( $base as element(svg:filter), $elevation as xs:double, $azimuth as xs:double ) as element(svg:filter) { <svg:filter>{ $base/@*, this:replace-light($base/node(), $elevation, $azimuth) }</svg:filter> }
Function: perturb
declare function perturb($base as element(svg:filter),
$octaves as xs:double,
$frequency as xs:double) as element(svg:filter)
declare function perturb($base as element(svg:filter), $octaves as xs:double, $frequency as xs:double) as element(svg:filter)
perturb()
Create a new effect with turbulence adjusted by new values.
Existing baseFrequency and numOctave attributes on existing svg:feTurbulence
elements will be multiplied by the modifiers.
Params
- base as element(svg:filter): base effect to modify
- octaves as xs:double: octave multiplier
- frequency as xs:double: frequency multiplier
Returns
- element(svg:filter)
declare function this:perturb( $base as element(svg:filter), $octaves as xs:double, $frequency as xs:double ) as element(svg:filter) { <svg:filter>{ $base/@*, this:replace-turbulence($base/node(), $octaves, $frequency) }</svg:filter> }
Function: blur
declare function blur($base as element(svg:filter),
$blurring as xs:double) as element(svg:filter)
declare function blur($base as element(svg:filter), $blurring as xs:double) as element(svg:filter)
blur()
Create a new effect with Gaussian blur adjusted by new values.
Existing stdDevision values on existing svg:feGaussianBlur
elements will be multiplied by the modifiers.
Params
- base as element(svg:filter): base effect to modify
- blurring as xs:double: std deviation multiplier
Returns
- element(svg:filter)
declare function this:blur( $base as element(svg:filter), $blurring as xs:double ) as element(svg:filter) { <svg:filter>{ $base/@*, this:replace-blur($base/node(), $blurring) }</svg:filter> }
Function: stretch
declare function stretch($base as element(svg:filter),
$scale-x as xs:double,
$scale-y as xs:double) as element(svg:filter)
declare function stretch($base as element(svg:filter), $scale-x as xs:double, $scale-y as xs:double) as element(svg:filter)
stretch()
Create a new effect with offset adjusted by new values.
Existing dx and dy attributes on existing svg:feOffset
elements will be multiplied by the modifiers.
Params
- base as element(svg:filter): base effect to modify
- scale-x as xs:double: dx multiplier
- scale-y as xs:double: dy multiplier
Returns
- element(svg:filter)
declare function this:stretch( $base as element(svg:filter), $scale-x as xs:double, $scale-y as xs:double ) as element(svg:filter) { <svg:filter>{ $base/@*, this:replace-offset($base/node(), $scale-x, $scale-y) }</svg:filter> }
Function: displace
declare function displace($base as element(svg:filter),
$rescale as xs:double) as element(svg:filter)
declare function displace($base as element(svg:filter), $rescale as xs:double) as element(svg:filter)
displace()
Create a new effect with displacement scale offset adjusted by new value.
Params
- base as element(svg:filter): base effect to modify
- rescale as xs:double: scale multiplier
Returns
- element(svg:filter)
declare function this:displace( $base as element(svg:filter), $rescale as xs:double ) as element(svg:filter) { <svg:filter>{ $base/@*, this:replace-scale($base/node(), $rescale) }</svg:filter> }
Original Source Code
xquery version "3.1"; (:~ : Module with functions providing some filter-building support. : Note: this provides hooks to reference Inkscape filters by name : too, but you'll need to install your own version of Inkscape and : point at the filter file (/usr/share/inkscape/filters/filters.svg) : for that to work. By default we expect to find it at ../EFFECTS/inkscape_effects.svg : : Copyright© Mary Holstege 2021-2023 : CC-BY (https://creativecommons.org/licenses/by/4.0/) : @since December 2021 : @custom:Status Active :) module namespace this="http://mathling.com/svg/effects"; declare namespace svg="http://www.w3.org/2000/svg"; declare namespace xsl="http://www.w3.org/1999/XSL/Transform"; declare namespace xhtml="http://www.w3.org/1999/xhtml"; declare namespace xlink="http://www.w3.org/1999/xlink"; declare namespace inkscape="http://www.inkscape.org/namespaces/inkscape"; declare namespace map="http://www.w3.org/2005/xpath-functions/map"; import module namespace util="http://mathling.com/core/utilities" at "../core/utilities.xqy"; import module namespace seq="http://mathling.com/core/sequences" at "../core/sequences.xqy"; import module namespace rand="http://mathling.com/core/random" at "../core/random.xqy"; import module namespace dist="http://mathling.com/type/distribution" at "../types/distributions.xqy"; import module namespace point="http://mathling.com/geometric/point" at "../geo/point.xqy"; import module namespace box="http://mathling.com/geometric/rectangle" at "../geo/rectangle.xqy"; import module namespace gradient="http://mathling.com/svg/gradients" at "../svg/gradients.xqy"; declare copy-namespaces no-preserve, inherit; declare variable $this:BUILTIN-EFFECTS as xs:string* := ( "sandstone", "sandpaper" ); declare variable $this:NAMED-EFFECTS as xs:string* := ( "wavy", "little-wavy", "warped", "fuzz", "blurEnhance", "blur3D", "downBlur3D", "blurShift", "blurShiftSource", "watercolour", "stained-glass", "splotchify", "dust", "speckled", "flame", "incised", "extruded", "selfGlow", "softbloom", "topography", $this:BUILTIN-EFFECTS ); (: "frost", "organic", "pixel_smear", "bark", "scotland", "dragee", "garden_of_delights", "enamel_jewelry", "thick_acrylic", "metal_casting", "wood3D", "embossed_leather", "bloom" :) declare variable $this:INKSCAPE-ALIASES as map(xs:string,xs:string) := map { (: Bevels :) "smart_jelly": "f001", (: Same as Matte jelly but with more controls :) "metal_casting": "f002", (: Smooth drop-like bevel with metallic finish :) "jigsaw_piece": "f005", (: Low, sharp bevel :) "bloom": "f009", (: Soft, cushion-like bevel with matte highlights :) "ridged_border": "f010", (: Ridged border with inner bevel :) "button": "f030", (: Soft bevel, slightly depressed middle :) "neon": "f038", (: Neon light effect :) "molten_metal": "f039", (: Melting parts of object together, with a glossy bevel and a glow :) "pressed_steel": "f040", (: Pressed metal with a rolled edge :) "matte_bevel": "f041", (: Soft, pastel-colored, blurry bevel :) "glowing_metal": "f044", (: Glowing metal texture :) "translucent": "f046", (: Illuminated translucent plastic or glass effect :) "raised_border": "f058", (: Strongly raised border around a flat surface :) "fat_oil": "f060", (: Fat oil with some adjustable turbulence :) "electronic_microscopy": "f077", (: Bevel, crude light, discoloration and glow like in electronic microscopy :) "stained_glass": "f086", (: Illuminated stained glass effect :) "dark_glass": "f087", (: Illuminated glass effect with light coming from beneath :) "bright_metal": "f161", (: Bright metallic effect for any color :) "deep_colors_plastic": "f162", (: Transparent plastic with deep colors :) "melted_jelly_matte": "f163", (: Matte bevel with blurred edges :) "melted_jelly": "f164", (: Glossy bevel with blurred edges :) "combined_lighting": "f165", (: Basic specular bevel to use for building textures :) (: Blurs :) "apparition": "f003", (: Edges are partly feathered out :) "evanescent": "f093", (: Blur the contents of objects, preserving the outline and adding progressive transparency at edges :) "blur_double": "f193", (: Overlays two copies with different blur amounts and modifiable blend and composite :) (: Overlays :) "rubber_stamp": "f006", (: Random whiteouts inside :) "speckle": "f012", (: Fill object with sparse translucent specks :) "oil_slick": "f013", (: Rainbow-colored semitransparent oily splotches :) "frost": "f014", (: Flake-like white splotches :) "zebra": "f016", (: Irregular vertical dark stripes (loses object's own color) :) "clouds": "f017", (: Airy, fluffy, sparse white clouds :) "barbed_wire": "f027", (: Gray bevelled wires with drop shadows :) "swiss_cheese": "f028", (: Random inner-bevel holes :) "blue_cheese": "f029", (: Marble-like bluish speckles :) "tartan": "f078", (: Checkered tartan pattern :) "shaken_liquid": "f083", (: Colorizable filling with flow inside like transparency :) "people": "f095", (: Colorized blotches, like a crowd of people :) "scotland": "f096", (: Colorized mountain tops out of the fog :) "garden_of_delights": "f097", (: Phantasmagorical turbulent wisps, like Hieronymus Bosch's Garden of Delights :) "wavy_tartan": "f113", (: Tartan pattern with a wavy displacement and bevel around the edges :) "tiger_fur": "f117", (: Tiger fur pattern with folds and bevel around the edges :) "dots_transparency": "f134", (: Gives a pointillist HSL sensitive transparency :) "canvas_transparency": "f135", (: Gives a canvas like HSL sensitive transparency. :) "smear_transparency": "f136", (: Paint objects with a transparent turbulence which turns around color edges :) "carnaval": "f140", (: White splotches evocating carnaval masks :) "rough_transparency": "f143", (: Adds a turbulent transparency which displaces pixels at the same time :) "growing_cells": "f169", (: Random rounded living cells like fill :) "silhouette_marbled": "f189", (: Basic noise transparency texture :) "alpha_monochrome_cracked": "f201", (: Basic noise fill texture; adjust color in Flood :) "alpha_turbulent": "f202", (: Basic noise fill texture; adjust color in Flood :) "colorize_turbulent": "f203", (: Basic noise fill texture; adjust color in Flood :) "cross_noise_b": "f204", (: Adds a small scale crossy graininess :) "cross_noise": "f205", (: Adds a small scale screen like graininess :) "duotone_turbulent": "f206", (: Basic noise fill texture; adjust color in Flood :) "light_eraser_cracked": "f207", (: Basic noise fill texture; adjust color in Flood :) "poster_turbulent": "f208", (: Basic noise fill texture; adjust color in Flood :) "tartan_smart": "f209", (: Highly configurable checkered tartan pattern :) "liquid": "f216", (: Colorizable filling with liquid transparency :) (: Protrusions :) "ink_bleed": "f007", (: Inky splotches underneath the object :) "fire": "f008", (: Edges of object are on fire :) "dripping": "f032", (: Random paint streaks downwards :) "chewing_gum": "f127", (: Creates colorizable blotches which smoothly flow over the edges of the lines at their crossings :) (: Distort :) "ripple": "f011", (: Horizontal rippling of edges :) "pixel_smear": "f034", (: Van Gogh painting effect for bitmaps :) "torn_edges": "f090", (: Displace the outside of shapes and pictures without altering their content :) "roughen_inside": "f092", (: Roughen all inside shapes :) "chalk_and_sponge": "f094", (: Low turbulence gives sponge look and high turbulence chalk :) "torn_edges2": "f104", (: Displace the outside of shapes and pictures without altering their content :) "rough_and_dilate": "f131", (: Create a turbulent contour around :) "lapping": "f151", (: Something like a water noise :) "swirl": "f185", (: Paint objects with a transparent turbulence which wraps around color edges :) (: Materials :) "leopard_fur": "f015", (: Leopard spots (loses object's own color) :) "iridescent_beeswax": "f047", (: Waxy texture which keeps its iridescence through color fill change :) "eroded_metal": "f048", (: Eroded metal texture with ridges, grooves, holes and bumps :) "cracked_lava": "f049", (: A volcanic texture, a little like leather :) "lizard_skin": "f051", (: Stylized reptile skin texture :) "metallized_paint": "f056", (: Metallized effect with a soft lighting, slightly translucent at the edges :) "peel_off": "f066", (: Peeling painting on a wall :) "gold_splatter": "f067", (: Splattered cast metal, with golden highlights :) "gold_paste": "f068", (: Fat pasted cast metal, with golden highlights :) "enamel_jewelry": "f070", (: Slightly cracked enameled texture :) "flex_metal": "f112", (: Bright, polished uneven metal casting, colorizable :) "marble_3d": "f114", (: 3D warped marble texture :) "wood_3d": "f115", (: 3D warped, fibered wood texture :) "mother_of_pearl_3d": "f116", (: 3D warped, iridescent pearly shell texture :) (: Image Effects :) "sharpen": "f018", (: Sharpen edges and boundaries within the object, force=0.15 :) "sharpen_more": "f019", (: Sharpen edges and boundaries within the object, force=0.3 :) "age": "f025", (: Imitate aged photograph :) "soft_focus_lens": "f085", (: Glowing image content without blurring it :) "film_grain": "f122", (: Adds a small scale graininess :) (: Image Paint and Draw :) "oil_painting": "f020", (: Simulate oil painting style :) "pencil": "f021", (: Detect color edges and retrace them in grayscale :) "blueprint": "f022", (: Detect color edges and retrace them in blue :) "old_postcard": "f132", (: Slightly posterize and draw edges like on old printed postcards :) "alpha_engraving": "f145", (: Gives a transparent engraving effect with rough line and filling :) "alpha_draw_liquid": "f146", (: Gives a transparent fluid drawing effect with rough line and filling :) "liquid_drawing": "f147", (: Gives a fluid and wavy expressionist drawing effect to images :) "marbled_ink": "f148", (: Marbled transparency effect which conforms to image detected edges :) "alpha_engraving_b": "f150", (: Gives a controllable roughness engraving effect to bitmaps and materials :) "image_drawing_basic": "f194", (: Enhance and redraw color edges in 1 bit black and white :) "poster_draw": "f195", (: Enhance and redraw edges around posterized areas :) "cross_noise_poster": "f197", (: Overlay with a small scale screen like noise :) "cross_noise_poster_b": "f198", (: Adds a small scale screen like noise locally :) "poster_color_fun": "f199", (: Poster Color Fun :) "poster_rough": "f200", (: Adds roughness to one of the two channels of the Poster paint filter :) "light_contour": "f210", (: Uses vertical specular light to draw lines :) "chromolitho_alternate": "filter81", (: Old chromolithographic effect :) "litho": "filter169", (: Create a two colors lithographic effect :) (: Textures :) "organic": "f026", (: Bulging, knotty, slick 3D surface :) "jam_spread": "f033", (: Glossy clumpy jam spread :) "cracked_glass": "f035", (: Under a cracked glass :) "bark": "f050", (: Bark texture, vertical; use with deep colors :) "stone_wall": "f052", (: Stone wall texture to use with not too saturated colors :) "silk_carpet": "f053", (: Silk carpet texture, horizontal stripes :) "crumpled_plastic": "f069", (: Crumpled matte plastic, with melted edge :) "rough_paper": "f071", (: Aquarelle paper effect which can be used for pictures as for objects :) "rough_and_glossy": "f072", (: Crumpled glossy paper effect which can be used for pictures as for objects :) "blotting_paper": "f101", (: Inkblot on blotting paper :) "wax_print": "f102", (: Wax print on tissue texture :) "watercolor": "f107", (: Cloudy watercolor effect :) "felt": "f108", (: Felt like texture with color turbulence and slightly darker at the edges :) "ink_paint": "f109", (: Ink paint on paper with some turbulent color shift :) "tinted_rainbow": "f110", (: Smooth rainbow colors melted along the edges and colorizable :) "melted_rainbow": "f111", (: Smooth rainbow colors slightly melted along the edges :) "warped_rainbow": "f130", (: Smooth rainbow colors warped along the edges and colorizable :) "burst": "f138", (: Burst balloon texture crumpled and with holes :) "gouache": "f144", (: Partly opaque water color effect with bleed :) "riddled": "f155", (: Riddle the surface and add bump to images :) (: Shadows and Glows :) "inset": "f031", (: Shadowy outer bevel :) "in_and_out": "f073", (: Inner colorized shadow, outer black shadow :) "cutout_glow": "f098", (: In and out glow with a possible offset and colorizable flood :) "dark_and_glow": "f128", (: Darkens the edge with an inner blur and adds a flexible glow :) "emergence": "filter127", (: Cut out, add inner shadow and colorize some parts of an image :) (: Bumps :) "bubbly_bumps": "f036", (: Flexible bubbles effect with some displacement :) "hsl_bumps_alpha": "f088", (: Same as HSL Bumps but with transparent highlights :) "bubbly_bumps_alpha": "f089", (: Same as Bubbly Bumps but with transparent highlights :) "dark_emboss": "f099", (: Emboss effect : 3D relief where white is replaced by black :) "bubbly_bumps_matte": "f100", (: Same as Bubbly Bumps but with a diffuse light instead of a specular one :) "plaster_color": "f123", (: Colored plaster emboss effect :) "velvet_bumps": "f124", (: Gives Smooth Bumps velvet like :) "thick_paint": "f137", (: Thick painting effect with turbulence :) "embossed_leather": "f139", (: Combine a HSL edges detection bump with a leathery or woody and colorizable texture :) "plastify": "f141", (: HSL edges detection bump with a wavy reflective surface effect and variable crumple :) "plaster": "f142", (: Combine a HSL edges detection bump with a matte and crumpled surface effect :) "thick_acrylic": "f149", (: Thick acrylic paint texture with high texture depth :) "wrinkled_varnish": "f156", (: Thick glossy and translucent paint texture with high depth :) "canvas_bumps": "f157", (: Canvas texture with an HSL sensitive height map :) "canvas_bumps_matte": "f158", (: Same as Canvas Bumps but with a diffuse light instead of a specular one :) "canvas_bumps_alpha": "f159", (: Same as Canvas Bumps but with transparent highlights :) "tinfoil": "f166", (: Metallic foil effect combining two lighting types and variable crumple :) "relief_print": "f168", (: Bumps effect with a bevel, color flood and complex lighting :) "basic_diffuse_bump": "f173", (: Matte emboss effect :) "basic_specular_bump": "f174", (: Specular emboss effect :) "basic_two_lights_bump": "f175", (: Two types of lighting emboss effect :) "linen_canvas": "f176", (: Painting canvas emboss effect :) "plasticine": "f177", (: Matte modeling paste emboss effect :) "rough_canvas_painting": "f178", (: Painting canvas emboss effect :) "paper_bump": "f179", (: Paper like emboss effect :) "jelly_bump": "f180", (: Convert pictures to thick jelly :) "bump_engraving": "filter53", (: Carving emboss effect :) "convoluted_bump": "filter109", (: Convoluted emboss effect :) (: Ridges :) "glowing_bubble": "f037", (: Bubble effect with refraction and glow :) "thin_membrane": "f042", (: Thin like a soap membrane :) "matte_ridge": "f043", (: Soft pastel ridge :) "refractive_gel_a": "f054", (: Gel effect with light refraction :) "refractive_gel_b": "f055", (: Gel effect with strong refraction :) "dragee": "f057", (: Gel Ridge with a pearlescent look :) "metallized_ridge": "f059", (: Gel Ridge metallized at its top :) (: Scatter :) "leaves": "f045", (: Leaves on the ground in Fall, or living foliage :) "cubes": "f065", (: Scattered cubes; adjust the Morphology primitive to vary size :) "air_spray": "f074", (: Convert to small scattered particles with some thickness :) "pointillism": "f188", (: Gives a turbulent pointillist HSL sensitive transparency :) (: Morphology :) "black_hole": "f063", (: Creates a black light inside and outside :) "warm_inside": "f075", (: Blurred colorized contour, filled inside :) "cool_outside": "f076", (: Blurred colorized contour, empty inside :) "contouring_table": "filter581", (: Blurred multiple contours for objects :) "posterized_blur": "filter609", (: Converts blurred contour to posterized steps :) "contouring_discrete": "filter672", (: Sharp multiple contour for objects :) (: Color :) "black_light": "f119", (: Light areas turn to black :) "soft_colors": "f167", (: Adds a colorizable edges glow inside objects and pictures :) "fluorescence": "f170", (: Oversaturate colors which can be fluorescent in real world :) "blend_opposites": "f181", (: Blend an image with its hue opposite :) "hue_to_white": "f182", (: Fades hue progressively to white :) "paint_channels": "filter291", (: Colorize separately the three color channels :) "trichrome": "filter499", (: Like Duochrome but with three colors :) "simulate_cmy": "filter106", (: Render Cyan, Magenta and Yellow channels with a colorizable background :) (: Non-Realistic 3D Shaders :) "comics_cream": "f125", (: Comics shader with creamy waves transparency :) "aluminium": "f217", (: Aluminium effect with sharp brushed reflections :) "comics": "f218", (: Comics cartoon drawing effect :) "comics_draft": "f219", (: Draft painted cartoon shading with a glassy look :) "comics_fading": "f220", (: Cartoon paint style with some fading at the edges :) "brushed_metal": "f221", (: Satiny metal surface effect :) "opaline": "f222", (: Contouring version of smooth shader :) "chrome": "f223", (: Bright chrome effect :) "deep_chrome": "f224", (: Dark chrome effect :) "emboss_shader": "f225", (: Combination of satiny and emboss effect :) "sharp_metal": "f226", (: Chrome effect with darkened edges :) "brush_draw": "f227", (: Draft painted cartoon shading with a glassy look :) "chrome_emboss": "f228", (: Embossed chrome effect :) "contour_emboss": "f229", (: Satiny and embossed contour effect :) "sharp_deco": "f230", (: Unrealistic reflections with sharp edges :) "deep_metal": "f231", (: Deep and dark metal shading :) "aluminium_emboss": "f232", (: Satiny aluminium effect with embossing :) "refractive_glass": "f233", (: Double reflection through glass with some refraction :) "frosted_glass": "f234", (: Satiny glass effect :) (: Fill and Transparency :) "fast_crop": "f000", (: Does not filter but adds a filter region :) "monochrome_transparency": "f152", (: Convert to a colorizable transparent positive or negative :) "saturation_map": "f154", (: Creates an approximative semi-transparent and colorizable image of the saturation levels :) "fill_background": "f190", (: Adds a colorizable opaque background :) "flatten_transparency": "f191", (: Adds a white opaque background :) "posterized_light_eraser": "filter451", (: Create a semi transparent posterized image :) (: Pixel Tools :) "pixellize": "f171" (: Reduce or remove antialiasing around shapes :) }; declare variable $this:KNOWN-ALIASES as xs:string* := ( $this:INKSCAPE-ALIASES=>map:keys(), util:map-entries($this:INKSCAPE-ALIASES) ); declare variable $this:PARAMETERIZED-PREFIXES as xs:string* := ( "shadow-", "fuzz-", "speckled-", "wavy-" ); declare variable $this:PREFIX-REGEX as xs:string := "^("||string-join($this:PARAMETERIZED-PREFIXES, "|")||")" ; declare variable $this:PARAMETERIZED-SUFFIXES as xs:string* := ( "-softglow", "-hardglow", "-glow", "-roughpaper", "-stone", "-grain", "-watergrain", "-glint", "-filigree" ); declare variable $this:SUFFIX-REGEX as xs:string := "("||string-join($this:PARAMETERIZED-SUFFIXES, "|")||")$" ; declare variable $this:MODIFIER-SUFFIXES as xs:string* := ( "-overlay", "-blend-normal", "-blend-multiply", "-blend-screen", "-blend-darken", "-blend-lighten", "-compose-over", "-compose-in", "-compose-out", "-compose-atop", "-compose-xor" ); declare variable $this:MODIFIER-REGEX as xs:string := "("||string-join($this:MODIFIER-SUFFIXES, "|")||")$" ; declare variable $this:GRAIN-MATRIX as xs:double* := ( 0.4E0, 0E0, 0E0, -1E0, 0.4E0, 0E0, -1E0, 0E0, -1E0, 0.3E0, 0E0, 0E0, -1E0, -1E0, 0.2E0, -0.4E0, -0.2E0, 0.4E0, 1E0, 0.8E0 ); declare variable $this:WATERGRAIN-MATRIX as xs:double* := ( 1E0, 0E0, 0E0, -1E0, 0E0, 0E0, -1E0, 0E0, -1E0, 0.5E0, 0E0, 0E0, -1E0, -1E0, 0.5E0, -0.5E0, -0.5E0, 1E0, 0E0, 0.5E0 ); (:~ : fetch-effect() : Get the filter definition of a known named filter. : Empty otherwise. :) declare function this:fetch-effect($effect as xs:string) as element()? { (: SLEAZY HACK ALERT :) if ($effect = $this:BUILTIN-EFFECTS) then ( (: Note: may depend on Inkscape effects :) switch ($effect) case "sandstone" return this:perturb(this:effect("rough_paper"), 1.0, 0.25)=>this:rename($effect) case "sandpaper" return this:perturb(this:effect("linen_canvas"), 1.0, 2.0)=>this:rename($effect) default return () ) else if ($effect = $this:NAMED-EFFECTS) then ( (: Is is a known effect in the effects stylesheet? :) (: trace((), "Named effect "||$effect), :) let $def := try { doc("../stylesheets/effects.xsl")//xsl:param[@name=$effect]/svg:filter } catch * { () } return ( if (exists($def)) then $def else ( try { doc("../EFFECTS/"||$effect||".svg")//svg:defs/svg:filter } catch * { () } ) ) ) else if ($effect = $this:INKSCAPE-ALIASES=>map:keys()) then ( (: Is it a known Inkscape effect, as named in our mapping? :) trace((), "Inkscape alias effect "||$effect), try { let $def := doc("../EFFECTS/inkscape_effects.svg")//svg:filter[@id=$this:INKSCAPE-ALIASES($effect)] return ( if (exists($def)) then ( <svg:filter id="{$effect}">{ $def/@* except ($def/@id|$def/@inkscape:*), $def/node() }</svg:filter> ) else () ) } catch * { () } ) else ( (: Is it a known Inkscape effect, as named by Inkscape? :) trace((), "Inkscape effect "||$effect), try { let $def := doc("../EFFECTS/inkscape_effects.svg")//svg:filter[@id=$effect] return ( if (exists($def)) then ( <svg:filter>{ $def/@* except $def/@inkscape:*, $def/node() }</svg:filter> ) else () ) } catch * {()} ) }; (:~ : get-effect-definition() : Get the effect definition of the name, allowing for variants. :) declare function this:get-effect-definition($effect as xs:string) as element()? { let $effect-pieces := tokenize($effect, "·") (: middle dot :) let $effects := for $effect in $effect-pieces return ( if (($effect = $this:NAMED-EFFECTS) or ($effect = $this:KNOWN-ALIASES)) then ( this:fetch-effect($effect) ) else if (matches($effect, $this:MODIFIER-REGEX)) then ( let $base-name := replace($effect, $this:MODIFIER-REGEX, '') let $modifier := substring-after($effect, $base-name||"-") let $base := this:get-effect-definition($base-name) return switch($modifier) case "overlay" return this:overlay-source($base)=>this:rename($effect) case "blend-normal" return this:blend-source($base, "normal")=>this:rename($effect) case "blend-multiply" return this:blend-source($base, "multiply")=>this:rename($effect) case "blend-screen" return this:blend-source($base, "screen")=>this:rename($effect) case "blend-darken" return this:blend-source($base, "darken")=>this:rename($effect) case "blend-lighten" return this:blend-source($base, "lighten")=>this:rename($effect) case "compose-over" return this:compose-source($base, "over")=>this:rename($effect) case "compose-in" return this:compose-source($base, "in")=>this:rename($effect) case "compose-out" return this:compose-source($base, "out")=>this:rename($effect) case "compose-atop" return this:compose-source($base, "atop")=>this:rename($effect) case "compose-xor" return this:compose-source($base, "xor")=>this:rename($effect) default return (util:log("Modifier not found "||$modifier||" to "||$base)) ) else if (matches($effect, $this:PREFIX-REGEX)) then ( let $parameters := replace($effect, $this:PREFIX-REGEX, '') let $operation := substring-before($effect, "-"||$parameters) return switch($operation) case "shadow" return this:shadow($effect, xs:integer($parameters)) case "fuzz" return this:fuzz($effect, xs:integer($parameters)) case "speckled" return this:speckled($effect, 0.25, number($parameters)) case "wavy" return this:wavy($effect, (0.04, 0.2), xs:integer($parameters)) default return (util:log("Prefix not found "||$operation||" with "||$parameters)) ) else if (matches($effect, $this:SUFFIX-REGEX)) then ( let $parameters := replace($effect, $this:SUFFIX-REGEX, '') let $operation := substring-after($effect, $parameters||"-") return switch ($operation) case "softglow" return this:glow($effect, $parameters, 0.4, 1, 3) case "hardglow" return this:glow($effect, $parameters, 1, 5, 11) case "glow" return this:glow($effect, $parameters, 1, 2, 5) case "roughpaper" return this:texture($effect, $parameters, 0.04) case "stone" return this:texture($effect, $parameters, 0.02) case "grain" return this:grain($effect, $parameters, $this:GRAIN-MATRIX) case "watergrain" return this:grain($effect, $parameters, $this:WATERGRAIN-MATRIX) case "glint" return this:glint($effect, $parameters, point:point(200, 200, 50)) case "filigree" return this:filigree($effect, $parameters) default return (util:log("Suffix not found "||$operation||"+"||$parameters)) ) else ( util:log("Unknown effect") ) ) return ( if (count($effects) le 1) then $effects else if (count($effects) = count($effect-pieces)) then ( this:merge($effect, $effects) ) else ( util:log("Unprocessed effect piece: no effect generated") ) ) }; declare function this:effect($effect as xs:string) as element()? { this:get-effect-definition($effect) }; declare function this:ref($effect as xs:string) as xs:string { if ($effect=("none","")) then "none" else if (starts-with($effect, "url(")) then $effect else "url(#"||$effect||")" }; (:====================================================================== : Basic effect constructions :======================================================================:) declare function this:shadow( $name as xs:string, $height as xs:integer ) as element(svg:filter) { <svg:filter id="{$name}" x="-0.25" y="-0.25" width="1.5" height="1.5"> <svg:feGaussianBlur in="SourceAlpha" stdDeviation="2.5" result="blur"/> <svg:feColorMatrix result="bluralpha" type="matrix" values= "1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 0.4 0 "/> <svg:feOffset in="bluralpha" dx="3" dy="{$height}" result="offsetBlur"/> <svg:feMerge result="{rand:id('filter')}"> <svg:feMergeNode in="offsetBlur"/> <svg:feMergeNode in="SourceGraphic"/> </svg:feMerge> </svg:filter> }; declare function this:fuzz( $name as xs:string, $width as xs:integer ) as element(svg:filter) { <svg:filter id="{$name}" filterUnits="userSpaceOnUse" x="0%" y="0%" width="100%" height="100%"> <svg:feGaussianBlur stdDeviation="{$width}" result="{rand:id('filter')}"/> </svg:filter> }; declare function this:glow( $name as xs:string, $colour as xs:string, $strength as xs:double, (: [0,1] :) $size as xs:integer, $spread as xs:integer ) as element(svg:filter) { <svg:filter id="{$name}" x="{-100*$size}%" y="{-100*$size}%" width="{2*100*$size}%" height="{2*100*$size}%"> <svg:feFlood result="flood" flood-color="{gradient:colour($colour)}" flood-opacity="{$strength}"></svg:feFlood> <svg:feComposite in="flood" result="mask" in2="SourceGraphic" operator="in"></svg:feComposite> <svg:feMorphology in="mask" result="dilated" operator="dilate" radius="{$size}"></svg:feMorphology> <svg:feGaussianBlur in="dilated" result="blurred" stdDeviation="{$spread}"></svg:feGaussianBlur> <svg:feMerge result="{rand:id('filter')}"> <svg:feMergeNode in="blurred"></svg:feMergeNode> <svg:feMergeNode in="SourceGraphic"></svg:feMergeNode> </svg:feMerge> </svg:filter> }; declare function this:texture( $name as xs:string, $colour as xs:string, $roughness as xs:double ) as element(svg:filter) { <svg:filter id="{$name}" x="0%" y="0%" width="100%" height="100%"> <svg:feTurbulence type="fractalNoise" baseFrequency="{$roughness}" numOctaves="5" result="noise"/> <svg:feDiffuseLighting in="noise" lighting-color="{gradient:colour($colour)}" surfaceScale="2" result="{rand:id('filter')}"> <svg:feDistantLight azimuth="45" elevation="35"/> </svg:feDiffuseLighting> </svg:filter> }; declare function this:grain( $name as xs:string, $colour as xs:string, $matrix as xs:double* ) as element(svg:filter) { <svg:filter id="{$name}" > <svg:feFlood flood-color="{gradient:colour($colour)}" result="one"/> <svg:feTurbulence baseFrequency=".004,.25" numOctaves="1" seed="403" result="turb"/> <svg:feColorMatrix type="matrix" result="turb1" values="{string-join($matrix!string(.),' ')}"/> <svg:feMerge result="{rand:id('filter')}"> <svg:feMergeNode in="turb"/> <svg:feMergeNode in="one"/> <svg:feMergeNode in="turb1"/> </svg:feMerge> </svg:filter> }; declare function this:filigree( $name as xs:string, $colour as xs:string ) as element(svg:filter) { <svg:filter id="{$name}" filterUnits="userSpaceOnUse" x="0" y="0" width="110%" height="110%" style="color-interpolation-filters:sRGB;"> <svg:feFlood flood-color="{$colour}" result="result9"/> <svg:feGaussianBlur stdDeviation="7" in="SourceGraphic" result="result8"/> <svg:feTurbulence type="turbulence" numOctaves="1" baseFrequency="0.09" result="result7"/> <svg:feColorMatrix result="result5" values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 4 0 "/> <svg:feComposite in="result8" in2="result8" operator="in" result="result6"/> <svg:feDisplacementMap in="result5" in2="result6" xChannelSelector="A" yChannelSelector="A" scale="100" result="result4"/> <svg:feComposite in="result8" in2="result4" operator="in" result="result2"/> <svg:feComposite operator="in" in="SourceGraphic" in2="result2" result="fbSourceGraphic"/> <svg:feMerge result="result10"> <svg:feMergeNode in="result9"/> <svg:feMergeNode in="fbSourceGraphic"/> </svg:feMerge> <svg:feComposite in2="SourceGraphic" operator="in" result="result11"/> </svg:filter> }; declare function this:glint( $name as xs:string, $colour as xs:string, $light as map(xs:string,item()*) ) as element(svg:filter) { <svg:filter id="{$name}" filterUnits="userSpaceOnUse" x="0" y="0" width="100%" height="100%"> <svg:feSpecularLighting in="SourceGraphic" surfaceScale="5" specularConstant="0.5" specularExponent="90" lighting-color="{gradient:colour($colour)}" result="specOut"> <svg:fePointLight x="{point:x($light)}" y="{point:y($light)}" z="{point:z($light)}"/> </svg:feSpecularLighting> <svg:feComposite in="specOut" in2="SourceAlpha" operator="in" result="specOut"/> <svg:feComposite result="{rand:id('filter')}" in="SourceGraphic" in2="specOut" operator="arithmetic" k1="0" k2="1" k3="1" k4="0"/> </svg:filter> }; declare function this:speckled( $name as xs:string, $frequency as xs:double, $scale as xs:double ) as element(svg:filter) { <svg:filter id="{$name}" x="0%" y="0%" width="100%" height="100%"> <svg:feTurbulence type="turbulence" baseFrequency="{$frequency}" numOctaves="3" result="turbulence"/> <svg:feDisplacementMap result="{rand:id('filter')}" in2="turbulence" in="SourceGraphic" scale="{$scale}" xChannelSelector="R" yChannelSelector="G"/> </svg:filter> }; declare function this:wavy( $name as xs:string, $frequencies as xs:double*, (: x,y or x=y one value :) $scale as xs:integer ) as element(svg:filter) { <svg:filter id="{$name}"> <svg:feTurbulence baseFrequency="{string-join($frequencies!string(.),',')}" numOctaves="2" result="turb"/> <svg:feDisplacementMap result="{rand:id('filter')}" in="SourceGraphic" in2="turb" scale="{$scale}" xChannelSelector="R" yChannelSelector="B"/> </svg:filter> }; (:====================================================================== : Effect modifications :======================================================================:) declare function this:effect-ref( $name as xs:string, $base-effect-name as xs:string ) as element(svg:filter) { let $def := this:effect($base-effect-name) return ( <svg:filter id="{$name}" xlink:href="#{$base-effect-name}"/> ) }; (:~ : merge() : Merge several filters into one. Take the min (x%, y%) and max (height%, width%) : as the new extent. : @param $name: name for new filter : @parm $effects: set of filters to combine : @return new filter :) declare function this:merge( $name as xs:string, $effects as element(svg:filter)* ) as element(svg:filter) { let $min-x := min( for $x in $effects/@x[ends-with(.,"%")] return number(substring-before($x,"%")) ) let $min-y := min( for $y in $effects/@x[ends-with(.,"%")] return number(substring-before($y,"%")) ) let $max-width := max( for $width in $effects/@width[ends-with(.,"%")] return number(substring-before($width,"%")) ) let $max-height := max( for $height in $effects/@height[ends-with(.,"%")] return number(substring-before($height,"%")) ) let $bodies := for $effect in $effects return ( <body>{ if (exists($effect/*[last()]/@result)) then ( $effect/node() ) else ( $effect/*[position() < last()], let $last := $effect/*[last()] return ( $effect/node()[position() < last()], element {node-name($last)} { $last/@*, attribute result {rand:id("step")}, $last/node() } ) ) }</body> ) return ( <svg:filter id="{$name}">{ if (exists($min-x)) then attribute x {$min-x||"%"} else (), if (exists($min-y)) then attribute y {$min-y||"%"} else (), if (exists($max-width)) then attribute width {$max-width||"%"} else (), if (exists($max-height)) then attribute height {$max-height||"%"} else (), $bodies/*, <svg:feMerge result="{rand:id('filter')}">{ for $effect in $bodies return ( <svg:feMergeNode in="{$effect/*[last()]/@result}"/> ) }</svg:feMerge> }</svg:filter> ) }; (:~ : this:overlay-source() : Construct a filter that overlays the source graphic. Useful for filters : like 'dust' that completely replace the source graphic. : : @param $base: base effect to modify :) declare function this:overlay-source( $base as element(svg:filter) ) as element(svg:filter) { <svg:filter>{ $base/@*, $base/node(), <svg:feMerge> <svg:feMergeNode in="SourceGraphic"/> <svg:feMergeNode/> </svg:feMerge> }</svg:filter> }; (:~ : this:blend() : Construct a filter that blends the outputs of the two effects. : Take the min (x%, y%) and max (height%, width%) as the new extent. : : @param $effect1: first effect : @param $effect2: second effect : @param $mode: blend mode (normal|multiply|screen|darken|lighten) : @return new filter :) declare function this:blend( $effect1 as element(svg:filter), $effect2 as element(svg:filter), $mode as xs:string ) as element(svg:filter) { let $effects := ($effect1, $effect2) let $min-x := min( for $x in $effects/@x[ends-with(.,"%")] return number(substring-before($x,"%")) ) let $min-y := min( for $y in $effects/@x[ends-with(.,"%")] return number(substring-before($y,"%")) ) let $max-width := max( for $width in $effects/@width[ends-with(.,"%")] return number(substring-before($width,"%")) ) let $max-height := max( for $height in $effects/@height[ends-with(.,"%")] return number(substring-before($height,"%")) ) let $bodies := for $effect in $effects return ( <body>{ if (exists($effect/*[last()]/@result)) then ( $effect/node() ) else ( $effect/*[position() < last()], let $last := $effect/*[last()] return ( $effect/node()[position() < last()], element {node-name($last)} { $last/@*, attribute result {rand:id("step")}, $last/node() } ) ) }</body> ) return ( <svg:filter>{ $effect1/@id, if (exists($min-x)) then attribute x {$min-x||"%"} else (), if (exists($min-y)) then attribute y {$min-y||"%"} else (), if (exists($max-width)) then attribute width {$max-width||"%"} else (), if (exists($max-height)) then attribute height {$max-height||"%"} else (), $bodies/*, <svg:feBlend result="{rand:id('filter')}" mode="{$mode}" in="{$bodies[1]/*[last()]/@result}" in2="{$bodies[2]/*[last()]/@result}"/> }</svg:filter> ) }; (:~ : this:blend-source() : Construct a filter that blends source graphic with output of base effect. : : @param $base: base effect to modify : @param $mode: blend mode (normal|multiply|screen|darken|lighten) : @return new filter :) declare function this:blend-source( $base as element(svg:filter), $mode as xs:string ) as element(svg:filter) { <svg:filter>{ $base/@*, $base/node(), <svg:feBlend mode="{$mode}" in="SourceGraphic"/> }</svg:filter> }; (:~ : this:compose-source() : Construct a filter that composes the outputs of the two effects : : @param $effect1: first effect : @param $effect12: second effect : @param $mode: composition mode (over|in|out|atop|xor) : @return new filter :) declare function this:compose-source( $effect1 as element(svg:filter), $effect2 as element(svg:filter), $mode as xs:string ) as element(svg:filter) { let $effects := ($effect1, $effect2) let $min-x := min( for $x in $effects/@x[ends-with(.,"%")] return number(substring-before($x,"%")) ) let $min-y := min( for $y in $effects/@x[ends-with(.,"%")] return number(substring-before($y,"%")) ) let $max-width := max( for $width in $effects/@width[ends-with(.,"%")] return number(substring-before($width,"%")) ) let $max-height := max( for $height in $effects/@height[ends-with(.,"%")] return number(substring-before($height,"%")) ) let $bodies := for $effect in $effects return ( <body>{ if (exists($effect/*[last()]/@result)) then ( $effect/node() ) else ( $effect/*[position() < last()], let $last := $effect/*[last()] return ( $effect/node()[position() < last()], element {node-name($last)} { $last/@*, attribute result {rand:id("step")}, $last/node() } ) ) }</body> ) return ( <svg:filter>{ $effect1/@id, if (exists($min-x)) then attribute x {$min-x||"%"} else (), if (exists($min-y)) then attribute y {$min-y||"%"} else (), if (exists($max-width)) then attribute width {$max-width||"%"} else (), if (exists($max-height)) then attribute height {$max-height||"%"} else (), $bodies/*, <svg:feComposite result="{rand:id('filter')}" operator="{$mode}" in="{$bodies[1]/*[last()]/@result}" in2="{$bodies[2]/*[last()]/@result}"/> }</svg:filter> ) }; declare function this:compose-source( $base as element(svg:filter), $mode as xs:string ) as element(svg:filter) { <svg:filter>{ $base/@*, $base/node(), <svg:feComposite operator="{$mode}" in="SourceGraphic"/> }</svg:filter> }; (:~ : rename() : Rename the filter : : @param $base: base effect to modify : @param $name: new name of effect :) declare function this:rename( $base as element(svg:filter), $name as xs:string ) as element(svg:filter) { <svg:filter id="{$name}">{ $base/(@* except @id), $base/node() }</svg:filter> }; (:~ : extent() : Create a new effect with different extent (x/y, width/height) as a base effect : : @param $base: base effect to modify : @param $extent: a box defining the extent : min-point => x/y : width/height => width/height : Example: [-10, -10, 120, 120] => x="-10%" y="-10%" width="130%" height="130%" : @param $percent: interpret extent as percentages (default=true()) : @return new filter :) declare function this:extent( $base as element(svg:filter), $extent as map(xs:string,item()*), $percent as xs:boolean ) as element(svg:filter) { let $pc := if ($percent) then "%" else "" return <svg:filter x="{point:x(box:min-point($extent))}{$pc}" y="{point:y(box:min-point($extent))}{$pc}" width="{box:width($extent)}{$pc}" height="{box:height($extent)}{$pc}" >{ $base/(@* except (@x|@y|@width|@height)), $base/node() }</svg:filter> }; declare function this:extent( $base as element(svg:filter), $extent as map(xs:string,item()*) ) as element(svg:filter) { this:extent($base, $extent, true()) }; (:~ : colour() : Create a new effect with colours replaced with a new colour. All the : lighting-color or flood-color attributes on svg:feFlood, svg:feSpecularLighting, : and svg:feDiffuseLighting elements will be changed to the new colour. If : more than one colour is given, the colours will be selected in rotation. : : @param $base: base effect to modify : @param $colours: new colours : @return new filter :) declare function this:colour( $base as element(svg:filter), $colours as xs:string* ) as element(svg:filter) { <svg:filter>{ $base/@*, this:replace-colour($base/node(), $colours!gradient:colour(.), ()) }</svg:filter> }; declare function this:colour( $base as element(svg:filter), $colours as xs:string*, $opacity as xs:double? ) as element(svg:filter) { <svg:filter>{ $base/@*, this:replace-colour($base/node(), $colours!gradient:colour(.), $opacity) }</svg:filter> }; declare %private function this:replace-colour( $nodes as node()*, $colours as xs:string*, $opacity as xs:double? ) as node()* { for $node in $nodes return typeswitch ($node) case element(svg:feFlood) return element {node-name($node)} { $node/(@* except (@flood-color|@flood-opacity)), attribute flood-color {head($colours)}, if (empty($opacity)) then $node/@flood-opacity else attribute flood-opacity {$opacity} , this:replace-colour($node/*, seq:rotate($colours,1), $opacity) } case element(svg:feDiffuseLighting) return element {node-name($node)} { $node/(@* except @lighting-color), attribute lighting-color {head($colours)}, this:replace-colour($node/*, seq:rotate($colours,1), $opacity) } case element(svg:feSpecularLighting) return element {node-name($node)} { $node/(@* except @lighting-color), attribute lighting-color {head($colours)}, this:replace-colour($node/*, seq:rotate($colours,1), $opacity) } case element() return element {node-name($node)} { $node/@*, this:replace-colour($node/*, $colours, $opacity) } default return $node }; (:~ : point-light() : Create a new effect with the lightsource replaced with a point light source. : svg:feDistantLight, svg:fePointLight, svg:feSpotLight in : svg:feDiffuseLighting or svg:feSpecularLighting elements will be replaced : with the new point light source. : : @param $base: base effect to modify : @param $point: new point light source (should have a z value) : @return new filter :) declare function this:point-light( $base as element(svg:filter), $point as map(xs:string,item()*) ) as element(svg:filter) { <svg:filter>{ $base/@*, this:replace-light($base/node(), $point) }</svg:filter> }; declare %private function this:replace-light( $nodes as node()*, $point as map(xs:string,item()*) ) as node()* { for $node in $nodes return typeswitch ($node) case element(svg:fePointLight) return <svg:fePointLight x="{point:x($point)}" y="{point:y($point)}" z="{point:z($point)}"/> case element(svg:feDistantLight) return <svg:fePointLight x="{point:x($point)}" y="{point:y($point)}" z="{point:z($point)}"/> case element(svg:feSpotLight) return <svg:fePointLight x="{point:x($point)}" y="{point:y($point)}" z="{point:z($point)}"/> case element() return element {node-name($node)} { $node/@*, this:replace-light($node/*, $point) } default return $node }; (:~ : distant-light() : Create a new effect with the lightsource replaced with a distant light source. : svg:feDistantLight, svg:fePointLight, svg:feSpotLight in : svg:feDiffuseLighting or svg:feSpecularLighting elements will be replaced : with the new light source. : : @param $base: base effect to modify : @param $elevation: new elevation of the light source : @param $azimuth: new azimuth of the light source : @return new filter :) declare function this:distant-light( $base as element(svg:filter), $elevation as xs:double, $azimuth as xs:double ) as element(svg:filter) { <svg:filter>{ $base/@*, this:replace-light($base/node(), $elevation, $azimuth) }</svg:filter> }; declare %private function this:replace-light( $nodes as node()*, $elevation as xs:double, $azimuth as xs:double ) as node()* { for $node in $nodes return typeswitch ($node) case element(svg:fePointLight) return <svg:feDistantLight elevation="{$elevation}" azimuth="{$azimuth}"/> case element(svg:feDistantLight) return <svg:feDistantLight elevation="{$elevation}" azimuth="{$azimuth}"/> case element(svg:feSpotLight) return <svg:feDistantLight elevation="{$elevation}" azimuth="{$azimuth}"/> case element() return element {node-name($node)} { $node/@*, this:replace-light($node/*, $elevation, $azimuth) } default return $node }; (:~ : perturb() : Create a new effect with turbulence adjusted by new values. : Existing baseFrequency and numOctave attributes on existing svg:feTurbulence : elements will be multiplied by the modifiers. : : @param $base: base effect to modify : @param $octaves: octave multiplier : @param $frequency: frequency multiplier :) declare function this:perturb( $base as element(svg:filter), $octaves as xs:double, $frequency as xs:double ) as element(svg:filter) { <svg:filter>{ $base/@*, this:replace-turbulence($base/node(), $octaves, $frequency) }</svg:filter> }; declare %private function this:replace-turbulence( $nodes as node()*, $octaves as xs:double, $frequency as xs:double ) as node()* { for $node in $nodes return typeswitch ($node) case element(svg:feTurbulence) return element {node-name($node)} { $node/(@* except (@numOctaves|@baseFrequency)), attribute numOctaves {util:round($node/@numOctaves * $octaves)}, attribute baseFrequency { string-join( for $n in tokenize($node/@baseFrequency,"[\p{Z},]+")[. ne ""] return string(xs:double($n)*$frequency) ," ") }, this:replace-turbulence($node/*, $octaves, $frequency) } case element() return element {node-name($node)} { $node/@*, this:replace-turbulence($node/*, $octaves, $frequency) } default return $node }; (:~ : blur() : Create a new effect with Gaussian blur adjusted by new values. : Existing stdDevision values on existing svg:feGaussianBlur : elements will be multiplied by the modifiers. : : @param $base: base effect to modify : @param $blurring: std deviation multiplier :) declare function this:blur( $base as element(svg:filter), $blurring as xs:double ) as element(svg:filter) { <svg:filter>{ $base/@*, this:replace-blur($base/node(), $blurring) }</svg:filter> }; declare %private function this:replace-blur( $nodes as node()*, $blurring as xs:double ) as node()* { for $node in $nodes return typeswitch ($node) case element(svg:feGaussianBlur) return element {node-name($node)} { $node/(@* except (@stdDeviation)), attribute stdDeviation { string-join( for $n in tokenize($node/@stdDeviation,"[\p{Z},]+")[. ne ""] return string(xs:double($n)*$blurring) ," ") }, this:replace-blur($node/*, $blurring) } case element() return element {node-name($node)} { $node/@*, this:replace-blur($node/*, $blurring) } default return $node }; (:~ : stretch() : Create a new effect with offset adjusted by new values. : Existing dx and dy attributes on existing svg:feOffset : elements will be multiplied by the modifiers. : : @param $base: base effect to modify : @param $scale-x: dx multiplier : @param $scale-y: dy multiplier :) declare function this:stretch( $base as element(svg:filter), $scale-x as xs:double, $scale-y as xs:double ) as element(svg:filter) { <svg:filter>{ $base/@*, this:replace-offset($base/node(), $scale-x, $scale-y) }</svg:filter> }; declare %private function this:replace-offset( $nodes as node()*, $scale-x as xs:double, $scale-y as xs:double ) as node()* { for $node in $nodes return typeswitch ($node) case element(svg:feOffset) return element {node-name($node)} { $node/(@* except (@dx|@dy)), attribute dx {util:decimal($node/@dx * $scale-x, 1)}, attribute dy {util:decimal($node/@dy * $scale-y, 1)}, this:replace-offset($node/*, $scale-x, $scale-y) } case element() return element {node-name($node)} { $node/@*, this:replace-offset($node/*, $scale-x, $scale-y) } default return $node }; (:~ : displace() : Create a new effect with displacement scale offset adjusted by new value. : : @param $base: base effect to modify : @param $rescale: scale multiplier :) declare function this:displace( $base as element(svg:filter), $rescale as xs:double ) as element(svg:filter) { <svg:filter>{ $base/@*, this:replace-scale($base/node(), $rescale) }</svg:filter> }; declare %private function this:replace-scale( $nodes as node()*, $rescale as xs:double ) as node()* { for $node in $nodes return typeswitch ($node) case element(svg:feDisplacementMap) return element {node-name($node)} { $node/(@* except (@scale)), attribute scale {util:decimal($node/@scale * $rescale, 1)}, this:replace-scale($node/*, $rescale) } case element() return element {node-name($node)} { $node/@*, this:replace-scale($node/*, $rescale) } default return $node };