Coverage for lib/lottie/utils/transform.py: 90%

131 statements  

« prev     ^ index     » next       coverage.py v7.2.2, created at 2023-03-20 16:17 +0100

1import math 

2from ..nvector import NVector 

3 

4 

5def _sign(x): 

6 if x < 0: 

7 return -1 

8 return 1 

9 

10 

11class TransformMatrix: 

12 scalar = float 

13 

14 def __init__(self): 

15 """ Creates an Identity matrix """ 

16 self.to_identity() 

17 

18 def clone(self): 

19 m = TransformMatrix() 

20 m._mat = list(self._mat) 

21 return m 

22 

23 return self 

24 

25 def __getitem__(self, key): 

26 row, col = key 

27 return self._mat[row*4+col] 

28 

29 def __setitem__(self, key, value): 

30 row, col = key 

31 self._mat[row*4+col] = self.scalar(value) 

32 

33 @property 

34 def a(self): 

35 return self[0, 0] 

36 

37 @a.setter 

38 def a(self, v): 

39 self[0, 0] = self.scalar(v) 

40 

41 @property 

42 def b(self): 

43 return self[0, 1] 

44 

45 @b.setter 

46 def b(self, v): 

47 self[0, 1] = self.scalar(v) 

48 

49 @property 

50 def c(self): 

51 return self[1, 0] 

52 

53 @c.setter 

54 def c(self, v): 

55 self[1, 0] = self.scalar(v) 

56 

57 @property 

58 def d(self): 

59 return self[1, 1] 

60 

61 @d.setter 

62 def d(self, v): 

63 self[1, 1] = self.scalar(v) 

64 

65 @property 

66 def tx(self): 

67 return self[3, 0] 

68 

69 @tx.setter 

70 def tx(self, v): 

71 self[3, 0] = self.scalar(v) 

72 

73 @property 

74 def ty(self): 

75 return self[3, 1] 

76 

77 @ty.setter 

78 def ty(self, v): 

79 self[3, 1] = self.scalar(v) 

80 

81 def __str__(self): 

82 return str(self._mat) 

83 

84 def scale(self, x, y=None): 

85 if y is None: 

86 y = x 

87 

88 m = TransformMatrix() 

89 m.a = x 

90 m.d = y 

91 self *= m 

92 return self 

93 

94 def translate(self, x, y=None): 

95 if y is None: 95 ↛ 96line 95 didn't jump to line 96, because the condition on line 95 was never true

96 x, y = x 

97 m = TransformMatrix() 

98 m.tx = x 

99 m.ty = y 

100 self *= m 

101 return self 

102 

103 def skew(self, x_rad, y_rad): 

104 m = TransformMatrix() 

105 m.c = math.tan(x_rad) 

106 m.b = math.tan(y_rad) 

107 self *= m 

108 return self 

109 

110 def skew_from_axis(self, skew, axis): 

111 self.rotate(axis) 

112 m = TransformMatrix() 

113 m.c = math.tan(skew) 

114 self *= m 

115 self.rotate(-axis) 

116 return self 

117 

118 def row(self, i): 

119 return NVector(self[i, 0], self[i, 1], self[i, 2], self[i, 3]) 

120 

121 def column(self, i): 

122 return NVector(self[0, i], self[1, i], self[2, i], self[3, i]) 

123 

124 def to_identity(self): 

125 self._mat = [ 

126 1., 0., 0., 0., 

127 0., 1., 0., 0., 

128 0., 0., 1., 0., 

129 0., 0., 0., 1., 

130 ] 

131 

132 def apply(self, vector): 

133 vector3 = NVector(vector.x, vector.y, 0, 1) 

134 return NVector( 

135 self.column(0).dot(vector3), 

136 self.column(1).dot(vector3), 

137 ) 

138 

139 @classmethod 

140 def rotation(cls, radians): 

141 m = cls() 

142 m.a = math.cos(radians) 

143 m.b = -math.sin(radians) 

144 m.c = math.sin(radians) 

145 m.d = math.cos(radians) 

146 

147 return m 

148 

149 def __mul__(self, other): 

150 m = TransformMatrix() 

151 for row in range(4): 

152 for col in range(4): 

153 m[row, col] = self.row(row).dot(other.column(col)) 

154 return m 

155 

156 def __imul__(self, other): 

157 m = self * other 

158 self._mat = m._mat 

159 return self 

160 

161 def rotate(self, radians): 

162 self *= TransformMatrix.rotation(radians) 

163 return self 

164 

165 def extract_transform(self): 

166 a = self.a 

167 b = self.b 

168 c = self.c 

169 d = self.d 

170 tx = self.tx 

171 ty = self.ty 

172 

173 dest_trans = { 

174 "translation": NVector(tx, ty), 

175 "angle": 0, 

176 "scale": NVector(1, 1), 

177 "skew_axis": 0, 

178 "skew_angle": 0, 

179 } 

180 

181 delta = a * d - b * c 

182 if a != 0 or b != 0: 182 ↛ 189line 182 didn't jump to line 189, because the condition on line 182 was never false

183 r = math.hypot(a, b) 

184 dest_trans["angle"] = - _sign(b) * math.acos(a/r) 

185 sx = r 

186 sy = delta / r 

187 dest_trans["skew_axis"] = 0 

188 else: 

189 r = math.hypot(c, d) 

190 dest_trans["angle"] = math.pi / 2 + _sign(d) * math.acos(c / r) 

191 sx = delta / r 

192 sy = r 

193 dest_trans["skew_axis"] = math.pi / 2 

194 

195 dest_trans["scale"] = NVector(sx, sy) 

196 

197 skew = math.atan2((a * c + b * d), r * r) 

198 dest_trans["skew_angle"] = skew 

199 

200 return dest_trans 

201 

202 def to_css_2d(self): 

203 return "matrix(%s, %s, %s, %s, %s, %s)" % ( 

204 self.a, self.b, self.c, self.d, self.tx, self.ty 

205 )