diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 18:24:48 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 18:24:48 +0000 |
commit | cca66b9ec4e494c1d919bff0f71a820d8afab1fa (patch) | |
tree | 146f39ded1c938019e1ed42d30923c2ac9e86789 /share/extensions/perspective.py | |
parent | Initial commit. (diff) | |
download | inkscape-cca66b9ec4e494c1d919bff0f71a820d8afab1fa.tar.xz inkscape-cca66b9ec4e494c1d919bff0f71a820d8afab1fa.zip |
Adding upstream version 1.2.2.upstream/1.2.2upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'share/extensions/perspective.py')
-rwxr-xr-x | share/extensions/perspective.py | 171 |
1 files changed, 171 insertions, 0 deletions
diff --git a/share/extensions/perspective.py b/share/extensions/perspective.py new file mode 100755 index 0000000..940e42e --- /dev/null +++ b/share/extensions/perspective.py @@ -0,0 +1,171 @@ +#!/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 + + 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() |