diff options
Diffstat (limited to 'share/extensions/perspective.py')
-rwxr-xr-x | share/extensions/perspective.py | 136 |
1 files changed, 136 insertions, 0 deletions
diff --git a/share/extensions/perspective.py b/share/extensions/perspective.py new file mode 100755 index 0000000..88c3632 --- /dev/null +++ b/share/extensions/perspective.py @@ -0,0 +1,136 @@ +#!/usr/bin/env python +# coding=utf-8 +# +# Copyright (C) 2005 Aaron Spike, aaron@ekips.org +# +# 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. +""" +Perspective approach & math by Dmitry Platonov, shadowjack@mail.ru, 2006 +""" + +import inkex +from inkex.localization import inkex_gettext as _ + +X, Y = range(2) + +try: + import numpy as np + import numpy.linalg as lin + FLOAT = np.float64 +except ImportError: + np = None + + +class Perspective(inkex.EffectExtension): + """Apply a perspective to a path/group of paths""" + def effect(self): + if np is None: + raise inkex.AbortExtension( + _("Failed to import the numpy or numpy.linalg modules." + " These modules are required by this extension. Please install them." + " On a Debian-like system this can be done with the command, " + "sudo apt-get install python-numpy.")) + if len(self.svg.selection) != 2: + raise inkex.AbortExtension(_("This extension requires two selected objects.")) + + obj, envelope = self.svg.selection.values() + + if isinstance(obj, (inkex.PathElement, inkex.Group)): + if isinstance(envelope, inkex.PathElement): + path = envelope.path.transform(envelope.composed_transform()).to_superpath() + + if len(path) < 1 or len(path[0]) < 4: + raise inkex.AbortExtension( + _("This extension requires that the second path be four nodes long.")) + + dip = np.zeros((4, 2), dtype=FLOAT) + for i in range(4): + dip[i][0] = path[0][i][1][0] + dip[i][1] = path[0][i][1][1] + + # Get bounding box plus any extra composed transform of parents. + bbox = obj.bounding_box(obj.getparent().composed_transform()) + + sip = np.array([ + [bbox.left, bbox.bottom], + [bbox.left, bbox.top], + [bbox.right, bbox.top], + [bbox.right, bbox.bottom]], dtype=FLOAT) + else: + if isinstance(envelope, inkex.Group): + raise inkex.AbortExtension(_("The second selected object is a group, not a" + " path.\nTry using Object->Ungroup.")) + raise inkex.AbortExtension(_("The second selected object is not a path.\nTry using" + " the procedure Path->Object to Path.")) + else: + raise inkex.AbortExtension(_("The first selected object is neither a path nor a group.\nTry using" + " the procedure Path->Object to Path.")) + + solmatrix = np.zeros((8, 8), dtype=FLOAT) + free_term = np.zeros(8, dtype=FLOAT) + for i in (0, 1, 2, 3): + solmatrix[i][0] = sip[i][0] + solmatrix[i][1] = sip[i][1] + solmatrix[i][2] = 1 + solmatrix[i][6] = -dip[i][0] * sip[i][0] + solmatrix[i][7] = -dip[i][0] * sip[i][1] + solmatrix[i + 4][3] = sip[i][0] + solmatrix[i + 4][4] = sip[i][1] + solmatrix[i + 4][5] = 1 + solmatrix[i + 4][6] = -dip[i][1] * sip[i][0] + solmatrix[i + 4][7] = -dip[i][1] * sip[i][1] + free_term[i] = dip[i][0] + free_term[i + 4] = dip[i][1] + + res = lin.solve(solmatrix, free_term) + projmatrix = np.array([ + [res[0], res[1], res[2]], + [res[3], res[4], res[5]], + [res[6], res[7], 1.0]], dtype=FLOAT) + + self.process_object(obj, projmatrix) + + def process_object(self, obj, matrix): + if isinstance(obj, inkex.PathElement): + self.process_path(obj, matrix) + elif isinstance(obj, inkex.Group): + self.process_group(obj, matrix) + + def process_group(self, group, matrix): + """Go through all groups to process all paths inside them""" + for node in group: + self.process_object(node, matrix) + + def process_path(self, element, matrix): + """Apply the transformation to the selected path""" + point = element.path.to_absolute().transform(element.composed_transform()).to_superpath() + for subs in point: + for csp in subs: + csp[0] = self.project_point(csp[0], matrix) + csp[1] = self.project_point(csp[1], matrix) + csp[2] = self.project_point(csp[2], matrix) + element.path = inkex.Path(point).transform(-element.composed_transform()) + + @staticmethod + def project_point(point, matrix): + """Apply the matrix to the given point""" + return [(point[X] * matrix[0][0] + point[Y] * matrix[0][1] + matrix[0][2]) / + (point[X] * matrix[2][0] + point[Y] * matrix[2][1] + matrix[2][2]), + (point[X] * matrix[1][0] + point[Y] * matrix[1][1] + matrix[1][2]) / + (point[X] * matrix[2][0] + point[Y] * matrix[2][1] + matrix[2][2])] + + +if __name__ == '__main__': + Perspective().run() |