summaryrefslogtreecommitdiffstats
path: root/share/extensions/inkex/elements/_filters.py
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--share/extensions/inkex/elements/_filters.py367
1 files changed, 367 insertions, 0 deletions
diff --git a/share/extensions/inkex/elements/_filters.py b/share/extensions/inkex/elements/_filters.py
new file mode 100644
index 0000000..ce86507
--- /dev/null
+++ b/share/extensions/inkex/elements/_filters.py
@@ -0,0 +1,367 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (c) 2020 Martin Owens <doctormo@gmail.com>
+# Sergei Izmailov <sergei.a.izmailov@gmail.com>
+# Thomas Holder <thomas.holder@schrodinger.com>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# pylint: disable=arguments-differ
+"""
+Element interface for patterns, filters, gradients and path effects.
+"""
+from __future__ import annotations
+from typing import List, Tuple, TYPE_CHECKING, Optional
+
+from lxml import etree
+
+from ..transforms import Transform
+from ..utils import parse_percent
+
+from ..styles import Style
+
+from ._utils import addNS
+from ._base import BaseElement
+
+
+if TYPE_CHECKING:
+ from ._svg import SvgDocumentElement
+
+
+class Filter(BaseElement):
+ """A filter (usually in defs)"""
+
+ tag_name = "filter"
+
+ def add_primitive(self, fe_type, **args):
+ """Create a filter primitive with the given arguments"""
+ elem = etree.SubElement(self, addNS(fe_type, "svg"))
+ elem.update(**args)
+ return elem
+
+ class Primitive(BaseElement):
+ """Any filter primitive"""
+
+ class Blend(Primitive):
+ """Blend Filter element"""
+
+ tag_name = "feBlend"
+
+ class ColorMatrix(Primitive):
+ """ColorMatrix Filter element"""
+
+ tag_name = "feColorMatrix"
+
+ class ComponentTransfer(Primitive):
+ """ComponentTransfer Filter element"""
+
+ tag_name = "feComponentTransfer"
+
+ class Composite(Primitive):
+ """Composite Filter element"""
+
+ tag_name = "feComposite"
+
+ class ConvolveMatrix(Primitive):
+ """ConvolveMatrix Filter element"""
+
+ tag_name = "feConvolveMatrix"
+
+ class DiffuseLighting(Primitive):
+ """DiffuseLightning Filter element"""
+
+ tag_name = "feDiffuseLighting"
+
+ class DisplacementMap(Primitive):
+ """Flood Filter element"""
+
+ tag_name = "feDisplacementMap"
+
+ class Flood(Primitive):
+ """DiffuseLightning Filter element"""
+
+ tag_name = "feFlood"
+
+ class GaussianBlur(Primitive):
+ """GaussianBlur Filter element"""
+
+ tag_name = "feGaussianBlur"
+
+ class Image(Primitive):
+ """Image Filter element"""
+
+ tag_name = "feImage"
+
+ class Merge(Primitive):
+ """Merge Filter element"""
+
+ tag_name = "feMerge"
+
+ class Morphology(Primitive):
+ """Morphology Filter element"""
+
+ tag_name = "feMorphology"
+
+ class Offset(Primitive):
+ """Offset Filter element"""
+
+ tag_name = "feOffset"
+
+ class SpecularLighting(Primitive):
+ """SpecularLighting Filter element"""
+
+ tag_name = "feSpecularLighting"
+
+ class Tile(Primitive):
+ """Tile Filter element"""
+
+ tag_name = "feTile"
+
+ class Turbulence(Primitive):
+ """Turbulence Filter element"""
+
+ tag_name = "feTurbulence"
+
+
+class Stop(BaseElement):
+ """Gradient stop
+
+ .. versionadded:: 1.1"""
+
+ tag_name = "stop"
+
+ @property
+ def offset(self) -> float:
+ """The offset of the gradient stop"""
+ return self.get("offset")
+
+ @offset.setter
+ def offset(self, number):
+ self.set("offset", number)
+
+ def interpolate(self, other, fraction):
+ """Interpolate gradient stops"""
+ from ..tween import StopInterpolator
+
+ return StopInterpolator(self, other).interpolate(fraction)
+
+
+class Pattern(BaseElement):
+ """Pattern element which is used in the def to control repeating fills"""
+
+ tag_name = "pattern"
+ WRAPPED_ATTRS = BaseElement.WRAPPED_ATTRS + (("patternTransform", Transform),)
+
+
+class Gradient(BaseElement):
+ """A gradient instruction usually in the defs."""
+
+ WRAPPED_ATTRS = BaseElement.WRAPPED_ATTRS + (("gradientTransform", Transform),)
+ """Additional to the :attr:`~inkex.elements._base.BaseElement.WRAPPED_ATTRS` of
+ :class:`~inkex.elements._base.BaseElement`, ``gradientTransform`` is wrapped."""
+
+ orientation_attributes = () # type: Tuple[str, ...]
+ """
+ .. versionadded:: 1.1
+ """
+
+ @property
+ def stops(self):
+ """Return an ordered list of own or linked stop nodes
+
+ .. versionadded:: 1.1"""
+ gradcolor = (
+ self.href
+ if isinstance(self.href, (LinearGradient, RadialGradient))
+ else self
+ )
+ return sorted(
+ [child for child in gradcolor if isinstance(child, Stop)],
+ key=lambda x: parse_percent(x.offset),
+ )
+
+ @property
+ def stop_offsets(self):
+ # type: () -> List[float]
+ """Return a list of own or linked stop offsets
+
+ .. versionadded:: 1.1"""
+ return [child.offset for child in self.stops]
+
+ @property
+ def stop_styles(self): # type: () -> List[Style]
+ """Return a list of own or linked offset styles
+
+ .. versionadded:: 1.1"""
+ return [child.style for child in self.stops]
+
+ def remove_orientation(self):
+ """Remove all orientation attributes from this element
+
+ .. versionadded:: 1.1"""
+ for attr in self.orientation_attributes:
+ self.pop(attr)
+
+ def interpolate(
+ self,
+ other: LinearGradient,
+ fraction: float,
+ svg: Optional[SvgDocumentElement] = None,
+ ):
+ """Interpolate with another gradient.
+
+ .. versionadded:: 1.1"""
+ from ..tween import GradientInterpolator
+
+ return GradientInterpolator(self, other, svg).interpolate(fraction)
+
+ def stops_and_orientation(self):
+ """Return a copy of all the stops in this gradient
+
+ .. versionadded:: 1.1"""
+ stops = self.copy()
+ stops.remove_orientation()
+ orientation = self.copy()
+ orientation.remove_all(Stop)
+ return stops, orientation
+
+
+class LinearGradient(Gradient):
+ """LinearGradient element"""
+
+ tag_name = "linearGradient"
+ orientation_attributes = ("x1", "y1", "x2", "y2")
+ """
+ .. versionadded:: 1.1
+ """
+
+ def apply_transform(self): # type: () -> None
+ """Apply transform to orientation points and set it to identity.
+ .. versionadded:: 1.1
+ """
+ trans = self.pop("gradientTransform")
+ pt1 = (
+ self.to_dimensionless(self.get("x1")),
+ self.to_dimensionless(self.get("y1")),
+ )
+ pt2 = (
+ self.to_dimensionless(self.get("x2")),
+ self.to_dimensionless(self.get("y2")),
+ )
+ p1t = trans.apply_to_point(pt1)
+ p2t = trans.apply_to_point(pt2)
+ self.update(
+ x1=self.to_dimensionless(p1t[0]),
+ y1=self.to_dimensionless(p1t[1]),
+ x2=self.to_dimensionless(p2t[0]),
+ y2=self.to_dimensionless(p2t[1]),
+ )
+
+
+class RadialGradient(Gradient):
+ """RadialGradient element"""
+
+ tag_name = "radialGradient"
+ orientation_attributes = ("cx", "cy", "fx", "fy", "r")
+ """
+ .. versionadded:: 1.1
+ """
+
+ def apply_transform(self): # type: () -> None
+ """Apply transform to orientation points and set it to identity.
+
+ .. versionadded:: 1.1
+ """
+ trans = self.pop("gradientTransform")
+ pt1 = (
+ self.to_dimensionless(self.get("cx")),
+ self.to_dimensionless(self.get("cy")),
+ )
+ pt2 = (
+ self.to_dimensionless(self.get("fx")),
+ self.to_dimensionless(self.get("fy")),
+ )
+ p1t = trans.apply_to_point(pt1)
+ p2t = trans.apply_to_point(pt2)
+ self.update(
+ cx=self.to_dimensionless(p1t[0]),
+ cy=self.to_dimensionless(p1t[1]),
+ fx=self.to_dimensionless(p2t[0]),
+ fy=self.to_dimensionless(p2t[1]),
+ )
+
+
+class PathEffect(BaseElement):
+ """Inkscape LPE element"""
+
+ tag_name = "inkscape:path-effect"
+
+
+class MeshGradient(Gradient):
+ """Usable MeshGradient XML base class
+
+ .. versionadded:: 1.1"""
+
+ tag_name = "meshgradient"
+
+ @classmethod
+ def new_mesh(cls, pos=None, rows=1, cols=1, autocollect=True):
+ """Return skeleton of 1x1 meshgradient definition."""
+ # initial point
+ if pos is None or len(pos) != 2:
+ pos = [0.0, 0.0]
+ # create nested elements for rows x cols mesh
+ meshgradient = cls()
+ for _ in range(rows):
+ meshrow: BaseElement = meshgradient.add(MeshRow())
+ for _ in range(cols):
+ meshrow.append(MeshPatch())
+ # set meshgradient attributes
+ meshgradient.set("gradientUnits", "userSpaceOnUse")
+ meshgradient.set("x", pos[0])
+ meshgradient.set("y", pos[1])
+ if autocollect:
+ meshgradient.set("inkscape:collect", "always")
+ return meshgradient
+
+
+class MeshRow(BaseElement):
+ """Each row of a mesh gradient
+
+ .. versionadded:: 1.1"""
+
+ tag_name = "meshrow"
+
+
+class MeshPatch(BaseElement):
+ """Each column or 'patch' in a mesh gradient
+
+ .. versionadded:: 1.1"""
+
+ tag_name = "meshpatch"
+
+ def stops(self, edges, colors):
+ """Add or edit meshpatch stops with path and stop-color."""
+ # iterate stops based on number of edges (path data)
+ for i, edge in enumerate(edges):
+ if i < len(self):
+ stop = self[i]
+ else:
+ stop = self.add(Stop())
+
+ # set edge path data
+ stop.set("path", str(edge))
+ # set stop color
+ stop.style["stop-color"] = str(colors[i % 2])