[transform] Improve DecomposedTransform
And add tests. See thread starting at: https://github.com/fonttools/fonttools/pull/2958#issuecomment-1416859441
This commit is contained in:
parent
08d03a82b2
commit
4355d006ad
@ -407,6 +407,10 @@ def Scale(x, y=None):
|
||||
return Transform(x, 0, 0, y, 0, 0)
|
||||
|
||||
|
||||
def _sign(v):
|
||||
return +1 if v >= 0 else -1
|
||||
|
||||
|
||||
@dataclass
|
||||
class DecomposedTransform:
|
||||
"""The DecomposedTransform class implements a transformation with separate
|
||||
@ -428,6 +432,12 @@ class DecomposedTransform:
|
||||
# Adapted from an answer on
|
||||
# https://math.stackexchange.com/questions/13150/extracting-rotation-scale-values-from-2d-transformation-matrix
|
||||
a, b, c, d, x, y = transform
|
||||
|
||||
sx = _sign(a)
|
||||
if sx < 0:
|
||||
a *= sx
|
||||
b *= sx
|
||||
|
||||
delta = a * d - b * c
|
||||
|
||||
rotation = 0
|
||||
@ -437,12 +447,14 @@ class DecomposedTransform:
|
||||
# Apply the QR-like decomposition.
|
||||
if a != 0 or b != 0:
|
||||
r = math.sqrt(a * a + b * b)
|
||||
rotation = math.acos(a / r) if b > 0 else -math.acos(a / r)
|
||||
rotation = math.acos(a / r) if b >= 0 else -math.acos(a / r)
|
||||
scaleX, scaleY = (r, delta / r)
|
||||
skewX, skewY = (math.atan((a * c + b * d) / (r * r)), 0)
|
||||
elif c != 0 or d != 0:
|
||||
s = math.sqrt(c * c + d * d)
|
||||
rotation = math.pi / 2 - (math.acos(-c / s) if d > 0 else -math.acos(c / s))
|
||||
rotation = math.pi / 2 - (
|
||||
math.acos(-c / s) if d >= 0 else -math.acos(c / s)
|
||||
)
|
||||
scaleX, scaleY = (delta / s, s)
|
||||
skewX, skewY = (0, math.atan((a * c + b * d) / (s * s)))
|
||||
else:
|
||||
@ -453,9 +465,9 @@ class DecomposedTransform:
|
||||
x,
|
||||
y,
|
||||
math.degrees(rotation),
|
||||
scaleX,
|
||||
scaleX * sx,
|
||||
scaleY,
|
||||
math.degrees(skewX),
|
||||
math.degrees(skewX) * sx,
|
||||
math.degrees(skewY),
|
||||
0,
|
||||
0,
|
||||
|
@ -123,6 +123,19 @@ class TransformTest(object):
|
||||
assert d.translateX == 5
|
||||
assert d.translateY == 7
|
||||
|
||||
def test_decompose(self):
|
||||
t = Transform(-1, 0, 0, 1, 0, 0)
|
||||
d = t.toDecomposed()
|
||||
assert d.scaleX == -1
|
||||
assert d.scaleY == 1
|
||||
assert d.rotation == 0
|
||||
|
||||
t = Transform(1, 0, 0, -1, 0, 0)
|
||||
d = t.toDecomposed()
|
||||
assert d.scaleX == 1
|
||||
assert d.scaleY == -1
|
||||
assert d.rotation == 0
|
||||
|
||||
|
||||
class DecomposedTransformTest(object):
|
||||
def test_identity(self):
|
||||
@ -141,3 +154,45 @@ class DecomposedTransformTest(object):
|
||||
def test_toTransform(self):
|
||||
t = DecomposedTransform(scaleX=2, scaleY=3)
|
||||
assert t.toTransform() == (2, 0, 0, 3, 0, 0)
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"decomposed",
|
||||
[
|
||||
DecomposedTransform(scaleX=1, scaleY=0),
|
||||
DecomposedTransform(scaleX=0, scaleY=1),
|
||||
DecomposedTransform(scaleX=1, scaleY=0, rotation=30),
|
||||
DecomposedTransform(scaleX=0, scaleY=1, rotation=30),
|
||||
DecomposedTransform(scaleX=1, scaleY=1),
|
||||
DecomposedTransform(scaleX=-1, scaleY=1),
|
||||
DecomposedTransform(scaleX=1, scaleY=-1),
|
||||
DecomposedTransform(scaleX=-1, scaleY=-1),
|
||||
DecomposedTransform(rotation=90),
|
||||
DecomposedTransform(rotation=-90),
|
||||
DecomposedTransform(skewX=45),
|
||||
DecomposedTransform(skewY=45),
|
||||
DecomposedTransform(scaleX=-1, skewX=45),
|
||||
DecomposedTransform(scaleX=-1, skewY=45),
|
||||
DecomposedTransform(scaleY=-1, skewX=45),
|
||||
DecomposedTransform(scaleY=-1, skewY=45),
|
||||
DecomposedTransform(scaleX=-1, skewX=45, rotation=30),
|
||||
DecomposedTransform(scaleX=-1, skewY=45, rotation=30),
|
||||
DecomposedTransform(scaleY=-1, skewX=45, rotation=30),
|
||||
DecomposedTransform(scaleY=-1, skewY=45, rotation=30),
|
||||
DecomposedTransform(scaleX=-1, skewX=45, rotation=-30),
|
||||
DecomposedTransform(scaleX=-1, skewY=45, rotation=-30),
|
||||
DecomposedTransform(scaleY=-1, skewX=45, rotation=-30),
|
||||
DecomposedTransform(scaleY=-1, skewY=45, rotation=-30),
|
||||
DecomposedTransform(scaleX=-2, skewX=45, rotation=30),
|
||||
DecomposedTransform(scaleX=-2, skewY=45, rotation=30),
|
||||
DecomposedTransform(scaleY=-2, skewX=45, rotation=30),
|
||||
DecomposedTransform(scaleY=-2, skewY=45, rotation=30),
|
||||
DecomposedTransform(scaleX=-2, skewX=45, rotation=-30),
|
||||
DecomposedTransform(scaleX=-2, skewY=45, rotation=-30),
|
||||
DecomposedTransform(scaleY=-2, skewX=45, rotation=-30),
|
||||
DecomposedTransform(scaleY=-2, skewY=45, rotation=-30),
|
||||
],
|
||||
)
|
||||
def test_roundtrip(lst, decomposed):
|
||||
assert decomposed.toTransform().toDecomposed().toTransform() == pytest.approx(
|
||||
tuple(decomposed.toTransform())
|
||||
), decomposed
|
||||
|
Loading…
x
Reference in New Issue
Block a user