Skip to content

Commit 7d8db39

Browse files
committed
Implement all simple operations for Eigen transforms
1 parent 9892ac9 commit 7d8db39

File tree

3 files changed

+85
-78
lines changed

3 files changed

+85
-78
lines changed

lib/matplotlib/_eigen.pyi

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
from __future__ import annotations
2+
13
from collections.abc import Sequence
24

35
import numpy as np
@@ -7,3 +9,10 @@ class Affine2d:
79
def get_matrix(self) -> np.ndarray: ...
810
def is_diagonal(self) -> bool: ...
911
def __call__(self, i: int, j: int) -> float: ...
12+
def __matmul__(self, other: Affine2d) -> Affine2d: ...
13+
def remove_translate(self) -> None: ...
14+
def reset(self) -> None: ...
15+
def rotate(self, theta: float) -> None: ...
16+
def scale(self, sx: float, sy: float) -> None: ...
17+
def skew(self, xShear: float, yShear: float) -> None: ...
18+
def translate(self, tx: float, ty: float) -> None: ...

lib/matplotlib/transforms.py

Lines changed: 37 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -1971,7 +1971,7 @@ def set_matrix(self, mtx):
19711971
19721972
.
19731973
"""
1974-
self._mtx = mtx
1974+
self._mtx = _eigen.Affine2d(mtx)
19751975
self.invalidate()
19761976

19771977
def set(self, other):
@@ -1980,15 +1980,14 @@ def set(self, other):
19801980
`Affine2DBase` object.
19811981
"""
19821982
_api.check_isinstance(Affine2DBase, other=other)
1983-
self._mtx = other.get_matrix()
1983+
self._mtx = _eigen.Affine2d(other.get_matrix())
19841984
self.invalidate()
19851985

19861986
def clear(self):
19871987
"""
19881988
Reset the underlying matrix to the identity transform.
19891989
"""
1990-
# A bit faster than np.identity(3).
1991-
self._mtx = IdentityTransform._mtx.copy()
1990+
self._mtx.reset()
19921991
self.invalidate()
19931992
return self
19941993

@@ -2000,18 +1999,7 @@ def rotate(self, theta):
20001999
calls to :meth:`rotate`, :meth:`rotate_deg`, :meth:`translate`
20012000
and :meth:`scale`.
20022001
"""
2003-
a = math.cos(theta)
2004-
b = math.sin(theta)
2005-
mtx = self._mtx
2006-
# Operating and assigning one scalar at a time is much faster.
2007-
(xx, xy, x0), (yx, yy, y0), _ = mtx.tolist()
2008-
# mtx = [[a -b 0], [b a 0], [0 0 1]] * mtx
2009-
mtx[0, 0] = a * xx - b * yx
2010-
mtx[0, 1] = a * xy - b * yy
2011-
mtx[0, 2] = a * x0 - b * y0
2012-
mtx[1, 0] = b * xx + a * yx
2013-
mtx[1, 1] = b * xy + a * yy
2014-
mtx[1, 2] = b * x0 + a * y0
2002+
self._mtx.rotate(theta)
20152003
self.invalidate()
20162004
return self
20172005

@@ -2055,8 +2043,7 @@ def translate(self, tx, ty):
20552043
calls to :meth:`rotate`, :meth:`rotate_deg`, :meth:`translate`
20562044
and :meth:`scale`.
20572045
"""
2058-
self._mtx[0, 2] += tx
2059-
self._mtx[1, 2] += ty
2046+
self._mtx.translate(tx, ty)
20602047
self.invalidate()
20612048
return self
20622049

@@ -2073,13 +2060,7 @@ def scale(self, sx, sy=None):
20732060
"""
20742061
if sy is None:
20752062
sy = sx
2076-
# explicit element-wise scaling is fastest
2077-
self._mtx[0, 0] *= sx
2078-
self._mtx[0, 1] *= sx
2079-
self._mtx[0, 2] *= sx
2080-
self._mtx[1, 0] *= sy
2081-
self._mtx[1, 1] *= sy
2082-
self._mtx[1, 2] *= sy
2063+
self._mtx.scale(sx, sy)
20832064
self.invalidate()
20842065
return self
20852066

@@ -2094,18 +2075,7 @@ def skew(self, xShear, yShear):
20942075
calls to :meth:`rotate`, :meth:`rotate_deg`, :meth:`translate`
20952076
and :meth:`scale`.
20962077
"""
2097-
rx = math.tan(xShear)
2098-
ry = math.tan(yShear)
2099-
mtx = self._mtx
2100-
# Operating and assigning one scalar at a time is much faster.
2101-
(xx, xy, x0), (yx, yy, y0), _ = mtx.tolist()
2102-
# mtx = [[1 rx 0], [ry 1 0], [0 0 1]] * mtx
2103-
mtx[0, 0] += rx * yx
2104-
mtx[0, 1] += rx * yy
2105-
mtx[0, 2] += rx * y0
2106-
mtx[1, 0] += ry * xx
2107-
mtx[1, 1] += ry * xy
2108-
mtx[1, 2] += ry * x0
2078+
self._mtx.skew(xShear, yShear)
21092079
self.invalidate()
21102080
return self
21112081

@@ -2128,7 +2098,7 @@ class IdentityTransform(Affine2DBase):
21282098
A special class that does one thing, the identity transform, in a
21292099
fast way.
21302100
"""
2131-
_mtx = np.identity(3)
2101+
_mtx = _eigen.Affine2d()
21322102

21332103
def frozen(self):
21342104
# docstring inherited
@@ -2138,7 +2108,7 @@ def frozen(self):
21382108

21392109
def get_matrix(self):
21402110
# docstring inherited
2141-
return self._mtx
2111+
return self._mtx.get_matrix()
21422112

21432113
def transform(self, values):
21442114
# docstring inherited
@@ -2325,16 +2295,16 @@ def get_matrix(self):
23252295
# docstring inherited
23262296
if self._invalid:
23272297
if self._x == self._y:
2328-
self._mtx = self._x.get_matrix()
2298+
self._mtx = _eigen.Affine2d(self._x.get_matrix())
23292299
else:
23302300
x_mtx = self._x.get_matrix()
23312301
y_mtx = self._y.get_matrix()
23322302
# We already know the transforms are separable, so we can skip
23332303
# setting b and c to zero.
2334-
self._mtx = np.array([x_mtx[0], y_mtx[1], [0.0, 0.0, 1.0]])
2304+
self._mtx = _eigen.Affine2d([x_mtx[0], y_mtx[1], [0.0, 0.0, 1.0]])
23352305
self._inverted = None
23362306
self._invalid = 0
2337-
return self._mtx
2307+
return self._mtx.get_matrix()
23382308

23392309

23402310
def blended_transform_factory(x_transform, y_transform):
@@ -2510,12 +2480,10 @@ def _iter_break_from_left_to_right(self):
25102480
def get_matrix(self):
25112481
# docstring inherited
25122482
if self._invalid:
2513-
self._mtx = np.dot(
2514-
self._b.get_matrix(),
2515-
self._a.get_matrix())
2483+
self._mtx = self._b._mtx @ self._a._mtx
25162484
self._inverted = None
25172485
self._invalid = 0
2518-
return self._mtx
2486+
return self._mtx.get_matrix()
25192487

25202488

25212489
def composite_transform_factory(a, b):
@@ -2578,13 +2546,14 @@ def get_matrix(self):
25782546
if DEBUG and (x_scale == 0 or y_scale == 0):
25792547
raise ValueError(
25802548
"Transforming from or to a singular bounding box")
2581-
self._mtx = np.array([[x_scale, 0.0, -inl*x_scale+outl],
2582-
[ 0.0, y_scale, -inb*y_scale+outb],
2583-
[ 0.0, 0.0, 1.0]],
2584-
float)
2549+
self._mtx = _eigen.Affine2d([
2550+
[x_scale, 0.0, -inl*x_scale+outl],
2551+
[ 0.0, y_scale, -inb*y_scale+outb],
2552+
[ 0.0, 0.0, 1.0],
2553+
])
25852554
self._inverted = None
25862555
self._invalid = 0
2587-
return self._mtx
2556+
return self._mtx.get_matrix()
25882557

25892558

25902559
class BboxTransformTo(Affine2DBase):
@@ -2616,13 +2585,14 @@ def get_matrix(self):
26162585
outl, outb, outw, outh = self._boxout.bounds
26172586
if DEBUG and (outw == 0 or outh == 0):
26182587
raise ValueError("Transforming to a singular bounding box.")
2619-
self._mtx = np.array([[outw, 0.0, outl],
2620-
[ 0.0, outh, outb],
2621-
[ 0.0, 0.0, 1.0]],
2622-
float)
2588+
self._mtx = _eigen.Affine2d([
2589+
[outw, 0.0, outl],
2590+
[ 0.0, outh, outb],
2591+
[ 0.0, 0.0, 1.0],
2592+
])
26232593
self._inverted = None
26242594
self._invalid = 0
2625-
return self._mtx
2595+
return self._mtx.get_matrix()
26262596

26272597

26282598
class BboxTransformFrom(Affine2DBase):
@@ -2651,13 +2621,14 @@ def get_matrix(self):
26512621
raise ValueError("Transforming from a singular bounding box.")
26522622
x_scale = 1.0 / inw
26532623
y_scale = 1.0 / inh
2654-
self._mtx = np.array([[x_scale, 0.0, -inl*x_scale],
2655-
[ 0.0, y_scale, -inb*y_scale],
2656-
[ 0.0, 0.0, 1.0]],
2657-
float)
2624+
self._mtx = _eigen.Affine2d([
2625+
[x_scale, 0.0, -inl*x_scale],
2626+
[ 0.0, y_scale, -inb*y_scale],
2627+
[ 0.0, 0.0, 1.0],
2628+
])
26582629
self._inverted = None
26592630
self._invalid = 0
2660-
return self._mtx
2631+
return self._mtx.get_matrix()
26612632

26622633

26632634
class ScaledTranslation(Affine2DBase):
@@ -2679,10 +2650,10 @@ def get_matrix(self):
26792650
# docstring inherited
26802651
if self._invalid:
26812652
self._mtx = _eigen.Affine2d()
2682-
self._mtx[:2, 2] = self._scale_trans.transform(self._t)
2653+
self._mtx.translate(*self._scale_trans.transform(self._t))
26832654
self._invalid = 0
26842655
self._inverted = None
2685-
return self._mtx
2656+
return self._mtx.get_matrix()
26862657

26872658

26882659
class _ScaledRotation(Affine2DBase):
@@ -2732,9 +2703,9 @@ def __init__(self, transform, **kwargs):
27322703

27332704
def get_matrix(self):
27342705
if self._invalid:
2735-
self._mtx = self._base_transform.get_matrix().copy()
2736-
self._mtx[:2, -1] = 0
2737-
return self._mtx
2706+
self._mtx = _eigen.Affine2d(self._base_transform.get_matrix())
2707+
self._mtx.remove_translate()
2708+
return self._mtx.get_matrix()
27382709

27392710

27402711
class TransformedPath(TransformNode):

src/_eigen.cpp

Lines changed: 39 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,3 @@
1-
// Matrix operations we need:
2-
// .copy()
3-
// check if [0, 0] != [1, 1]
4-
// re-set a, b, c, d, e, f
5-
// convert to NumPy matrix
6-
// reset to identity
7-
// rotate in place
8-
// translate in place
9-
// scale in place
10-
// skew in place
11-
// dot two matrices
12-
131
#include <pybind11/pybind11.h>
142
#include <pybind11/eigen.h>
153
#include <pybind11/numpy.h>
@@ -46,5 +34,44 @@ PYBIND11_MODULE(_eigen, m) {
4634
}
4735
return self(i, j);
4836
}, "i"_a, "j"_a)
37+
38+
.def("__matmul__", [](const Eigen::Affine2d& self, const Eigen::Affine2d& other) {
39+
return self * other;
40+
}, "other"_a,
41+
"Combines two transforms.")
42+
43+
.def("remove_translate", [](Eigen::Affine2d& self) {
44+
self(0, 2) = 0.0;
45+
self(1, 2) = 0.0;
46+
},
47+
"Remove the translation part of a transform.")
48+
49+
.def("reset", [](Eigen::Affine2d& self) {
50+
self.setIdentity();
51+
},
52+
"Reset to the identity transformation.")
53+
54+
.def("rotate", [](Eigen::Affine2d& self, double theta) {
55+
self.prerotate(theta);
56+
}, "theta"_a,
57+
"Add a rotation (in radians) to this transform in place.")
58+
59+
.def("scale", [](Eigen::Affine2d& self, double sx, double sy) {
60+
self.prescale(Eigen::Vector2d(sx, sy));
61+
}, "sx"_a, "sy"_a,
62+
"Add a scale in place.")
63+
64+
.def("skew", [](Eigen::Affine2d& self, double xShear, double yShear) {
65+
// Transform.preshear appears to be buggy in Eigen 3.4, so do this manually.
66+
auto skew = Eigen::Affine2d::Identity();
67+
skew.shear(tan(yShear), tan(xShear));
68+
self = skew * self;
69+
}, "xShear"_a, "yShear"_a,
70+
"Add a skew in place.")
71+
72+
.def("translate", [](Eigen::Affine2d& self, double tx, double ty) {
73+
self.pretranslate(Eigen::Vector2d(tx, ty));
74+
}, "tx"_a, "ty"_a,
75+
"Add a translation in place.")
4976
;
5077
}

0 commit comments

Comments
 (0)