comparison pyink/tween.py @ 1358:cd0c0c7547b4

Simplify tween function
author Thinker K.F. Li <thinker@codemud.net>
date Wed, 16 Feb 2011 15:07:44 +0800
parents a48df5d53ddc
children 5313bbfafa67
comparison
equal deleted inserted replaced
1357:a48df5d53ddc 1358:cd0c0c7547b4
2 # vim: sw=4:ts=8:sts=4 2 # vim: sw=4:ts=8:sts=4
3 import traceback 3 import traceback
4 import math 4 import math
5 5
6 6
7 def parse_style(style): 7 def _shift_matrix(x, y):
8 return (1, 0, 0, 1, x, y)
9
10
11 def _rotate_matrix(a):
12 return (math.cos(a), math.sin(a), -math.sin(a), math.cos(a), 0, 0)
13
14
15 def _scale_matrix(scale_x, scale_y):
16 return (scale_x, 0, 0, scale_y, 0, 0)
17
18
19 _id_matrix = (1, 0, 0, 1, 0, 0)
20
21
22 def _mulA(a, b):
23 return (a[0] * b[0] + a[2] * b[1],
24 a[1] * b[0] + a[3] * b[1],
25 a[0] * b[2] + a[2] * b[3],
26 a[1] * b[2] + a[3] * b[3],
27 a[0] * b[4] + a[2] * b[5] + a[4],
28 a[1] * b[4] + a[3] * b[5] + a[5])
29
30
31 def _parse_style(style):
8 attrs = {} 32 attrs = {}
9 33
10 style_parts = style.split(';') 34 style_parts = style.split(';')
11 for part in style_parts: 35 for part in style_parts:
12 part = part.strip() 36 part = part.strip()
21 attrs[name] = value 45 attrs[name] = value
22 pass 46 pass
23 47
24 return attrs 48 return attrs
25 49
26 def gen_style(attrs): 50
51 def _gen_style(attrs):
27 parts = [name + ':' + value for name, value in attrs.items()] 52 parts = [name + ':' + value for name, value in attrs.items()]
28 style = ';'.join(parts) 53 style = ';'.join(parts)
29 return style 54 return style
55
56
57 def _parse_transform_str(txt):
58 if txt[0:9] == 'translate':
59 fields = txt[10:].split(',')
60 x = float(fields[0])
61 fields = fields[1].split(')')
62 y = float(fields[0])
63 return [1, 0, 0, 1 , x, y]
64 elif txt[0:6] == 'matrix':
65 fields = txt[7:].split(')')
66 fields = fields[0].split(',')
67 return [float(field) for field in fields]
68 pass
69
70
71 ## \brief Parse style attributes about animation.
72 #
73 def _parse_style_ani(node, ani_attrs):
74 try:
75 style = node.getAttribute('style')
76 except: # has no style
77 style_attrs = {}
78 else:
79 style_attrs = _parse_style(style)
80 pass
81
82 if 'opacity' in style_attrs:
83 ani_attrs['opacity'] = float(style_attrs['opacity'])
84 pass
85
86 if 'display' in style_attrs:
87 ani_attrs['display'] = style_attrs['display'] != 'none'
88 pass
89 pass
90
91
92 ## \brief Parse all attributes about animation
93 #
94 def _parse_attr_ani(node, ani_attrs):
95 def _parse_transform_with_center(attr_value):
96 value = _parse_transform_str(attr_value)
97 x, y = node.spitem.getCenter()
98 return (value, (x, y))
99
100 attr_defs = {'x': float, 'y': float,
101 'width': float, 'height': float,
102 'transform': _parse_transform_with_center}
103
104 for attrname, parser in attr_defs.items():
105 try:
106 value = node.getAttribute(attrname)
107 except: # has no this attribute
108 pass
109 else:
110 parsed_value = parser(value)
111 ani_attrs[attrname] = parsed_value
112 pass
113 pass
114 pass
115
116
117 ## \brief Interpolate float values.
118 #
119 def _interp_float(start_value, stop_value, percent):
120 if start_value == None or stop_value == None:
121 if percent == 1:
122 return stop_value
123 return start_value
124
125 return start_value * (1 - percent) + stop_value * percent
126
127
128 ## \brief Interpolate matric.
129 #
130 def _interp_transform(start_value, stop_value, percent):
131 start_matrix = start_value[0]
132 start_center_x, start_center_y = start_value[1]
133 stop_matrix = stop_value[0]
134 stop_center_x, stop_center_y = stop_value[1]
135
136 start_scale_x, start_scale_y, start_ang, start_x, start_y = \
137 _decomposition(start_matrix)
138 stop_scale_x, stop_scale_y, stop_ang, stop_x, stop_y = \
139 _decomposition(stop_matrix)
140
141 interp = lambda x, y: _interp_float(x, y, percent)
142
143 factor_x = interp(start_scale_x, stop_scale_x) / start_scale_x
144 factor_y = interp(start_scale_y, stop_scale_y) / start_scale_y
145 angle = interp(start_ang, stop_ang)
146 shift_x = interp(start_center_x, stop_center_x)
147 shift_y = interp(start_center_y, stop_center_y)
148
149 # Shift center point back to origin
150 matrix = start_matrix
151 shift_matrix = _shift_matrix(-start_center_x, -start_center_y)
152 matrix = _mulA(shift_matrix, matrix)
153 # Remove rotation
154 rotate_matrix = _rotate_matrix(-start_ang)
155 matrix = _mulA(rotate_matrix, matrix)
156
157 # Apply new scaling
158 scale_matrix = _scale_matrix(factor_x, factor_y)
159 matrix = _mulA(scale_matrix, matrix)
160 # Rotate to new angle
161 rotate_matrix = _rotate_matrix(angle)
162 matrix = _mulA(rotate_matrix, matrix)
163 # Shift space to aim center point on new position.
164 shift_matrix = _shift_matrix(shift_x, shift_y)
165 matrix = _mulA(shift_matrix, matrix)
166
167 return matrix
168
169
170 ## \brief Interpolate for value of display style.
171 #
172 def _interp_display(start_value, stop_value, percent):
173 if percent < 1:
174 return start_value
175 return stop_value
176
177
178 _interp_funcs = {
179 'x': _interp_float, 'y': _interp_float,
180 'width': _interp_float, 'height': _interp_float,
181 'opacity': _interp_float, 'display': _interp_display,
182 'transform': _interp_transform}
183
184
185 def _tween_interpolation(attrname, start_value, stop_value, percent):
186 interp = _interp_funcs[attrname]
187 _interp_value = interp(start_value, stop_value, percent)
188 return _interp_value
189
190
191 def _apply_animation_attrs(ani_attrs, node):
192 for attr in ('x', 'y', 'width', 'height', 'opacity', 'display'):
193 if attr in ani_attrs:
194 node.setAttribute(attr, str(ani_attrs[attr]))
195 pass
196 pass
197
198 if 'transform' in ani_attrs:
199 try:
200 style = node.getAttribute('style')
201 except:
202 style = ''
203 pass
204
205 transform = [str(elm) for elm in ani_attrs['transform']]
206 transform_str = 'matrix(' + ','.join(transform) + ')'
207 node.setAttribute('transform', transform_str)
208 pass
209
210 chg_style = []
211 for attrname in 'opacity display'.split():
212 if attrname in ani_attrs:
213 chg_style.append((attrname, str(ani_attrs[attrname])))
214 pass
215 pass
216 if chg_style:
217 try:
218 style = node.getAttribute('style')
219 except:
220 style_attrs = chg_style
221 else:
222 style_attrs = _parse_style(style)
223 style_attrs.update(dict(chg_style))
224 pass
225 style = _gen_style(style_attrs)
226 node.setAttribute('style', style)
227 pass
228 pass
229
230
231 def _decomposition(m):
232 """
233 Decompose the affine matrix into production of
234 translation,rotation,shear and scale. The algorithm is
235 documented at
236 http://lists.w3.org/Archives/Public/www-style/2010Jun/0602.html
237 """
238 if m[0]*m[3] == m[1]*m[2]:
239 print "The affine matrix is singular"
240 return [1,0,0,1,0,0]
241 A=m[0]
242 B=m[2]
243 C=m[1]
244 D=m[3]
245 E=m[4]
246 F=m[5]
247 sx = math.sqrt(A*A+B*B)
248 A = A/sx
249 B = B/sx
250 shear = m[0]*m[1]+m[2]*m[3]
251 C = C - A*shear
252 D = D - B*shear
253 sy = math.sqrt(C*C+D*D)
254 C = C/sy
255 D = D/sy
256 r = A*D-B*C
257 if r == -1:
258 shear = -shear
259 sy = -sy
260 pass
261 R = math.atan2(-B,A)
262 return [sx,sy, R, E, F]
263
264
265 def _normalize_attrs(node1, attrs1, node2, attrs2):
266 if node2.name() == 'svg:use':
267 for name in 'x y width height'.split():
268 if name in attrs1:
269 del attrs1[name]
270 pass
271 if name in attrs2:
272 del attrs2[name]
273 pass
274 pass
275 pass
276
277 names = set(attrs1.keys() + attrs2.keys())
278
279 if 'transform' in names:
280 if 'transform' not in attrs1:
281 center = node1.spitem.getCenter()
282 attrs1['transform'] = (_id_matrix, center)
283 pass
284 if 'transform' not in attrs2:
285 center = node2.spitem.getCenter()
286 attrs2['transform'] = (_id_matrix, center)
287 pass
288
289 root = node1.root()
290 try:
291 root_h = float(root.getAttribute('height'))
292 except:
293 root_h = 600 # 800x600
294 pass
295
296 for attrs in (attrs1, attrs2):
297 transform = attrs['transform']
298 center = (transform[1][0], root_h - transform[1][1])
299 attrs['transform'] = (transform[0], center)
300
301 if 'x' in attrs:
302 del attrs['x']
303 pass
304 if 'y' in attrs:
305 del attrs['y']
306 pass
307 pass
308 pass
309
310 if 'opacity' in names:
311 if 'opacity' not in attrs1:
312 attrs1['opacity'] = 1.0
313 pass
314 if 'opacity' not in attrs2:
315 attrs2['opacity'] = 1.0
316 pass
317
318 if node2.name() == 'svg:use':
319 attrs2['opacity'] = attrs2['opacity'] * attrs1['opacity']
320 pass
321 pass
322
323 if 'display' in names:
324 if 'display' not in attrs1:
325 attrs1['display'] = ''
326 pass
327 if 'display' not in attrs2:
328 attrs2['display'] = ''
329 pass
330 pass
331
332 for name in 'x y width height'.split():
333 if name in names:
334 if name not in attrs1:
335 attrs1[name] = 0
336 pass
337 if name not in attrs2:
338 attrs2[name] = 0
339 pass
340 pass
341 pass
342 pass
30 343
31 class TweenObject(object): 344 class TweenObject(object):
32 TWEEN_TYPE_NORMAL = 0 345 TWEEN_TYPE_NORMAL = 0
33 #TWEEN_TYPE_RELOCATE = 1 346 #TWEEN_TYPE_RELOCATE = 1
34 TWEEN_TYPE_SCALE = 1 347 TWEEN_TYPE_SCALE = 1
114 start_node_id = start_node.getAttribute('id') 427 start_node_id = start_node.getAttribute('id')
115 dup_node = dup_nodes.setdefault(start_node_id, None) 428 dup_node = dup_nodes.setdefault(start_node_id, None)
116 try: 429 try:
117 stop_node = stop_nodes[start_node_id] 430 stop_node = stop_nodes[start_node_id]
118 except KeyError: 431 except KeyError:
119 self.updateTweenObject(duplicate_group, tween_type, 432 stop_node = start_node
120 start_node, start_node, 433 pass
121 percent, dup_node)
122 start_node = start_node.next()
123 continue
124
125 434
126 self.updateTweenObject(duplicate_group, tween_type, 435 self.updateTweenObject(duplicate_group, tween_type,
127 start_node, stop_node, 436 start_node, stop_node,
128 percent, dup_node) 437 percent, dup_node)
129 start_node = start_node.next() 438 start_node = start_node.next()
130 pass 439 pass
131 pass 440 pass
132 441
133 def parseTransform(self,obj):
134 """
135 Return the transform matrix of an object
136 """
137 try:
138 t = obj.getAttribute("transform")
139 if t[0:9] == 'translate':
140 fields = t[10:].split(',')
141 x = float(fields[0])
142 fields = fields[1].split(')')
143 y = float(fields[0])
144 return [1,0,0,1,x,y]
145 elif t[0:6] == 'matrix':
146 fields=t[7:].split(')')
147 fields = fields[0].split(',')
148 return [float(fields[0]),float(fields[1]),float(fields[2]),float(fields[3]),float(fields[4]),float(fields[5])]
149 except:
150 #traceback.print_exc()
151 return [1,0,0,1,0,0]
152
153 def invA(self,m):
154 d = m[0]*m[3]-m[2]*m[1]
155 return [m[3]/d, -m[1]/d, -m[2]/d, m[0]/d, (m[1]*m[5]-m[4]*m[3])/d, (m[4]*m[2]-m[0]*m[5])/d]
156
157 def mulA(self,a,b):
158 return [a[0]*b[0]+a[1]*b[2],
159 a[0]*b[1]+a[1]*b[3],
160 a[2]*b[0]+a[3]*b[2],
161 a[2]*b[1]+a[3]*b[3],
162 a[0]*b[4]+a[1]*b[5]+a[4],
163 a[2]*b[4]+a[3]*b[5]+a[5]]
164
165 def decomposition(self,m):
166 """
167 Decompose the affine matrix into production of translation,rotation,shear and scale.
168 The algorithm is documented at http://lists.w3.org/Archives/Public/www-style/2010Jun/0602.html
169 """
170 if m[0]*m[3] == m[1]*m[2]:
171 print "The affine matrix is singular"
172 return [1,0,0,1,0,0]
173 A=m[0]
174 B=m[2]
175 C=m[1]
176 D=m[3]
177 E=m[4]
178 F=m[5]
179 sx = math.sqrt(A*A+B*B)
180 A = A/sx
181 B = B/sx
182 shear = m[0]*m[1]+m[2]*m[3]
183 C = C - A*shear
184 D = D - B*shear
185 sy = math.sqrt(C*C+D*D)
186 C = C/sy
187 D = D/sy
188 r = A*D-B*C
189 if r == -1:
190 shear = -shear
191 sy = -sy
192 R = math.atan2(B,A)
193 return [sx,sy, R, E,F]
194
195 442
196 def updateTweenObject(self,obj,typ,s,d,p,newobj): 443 def updateTweenObject(self, obj, typ, s, d, p, newobj):
197 """ 444 """
198 Generate tweened object in the @obj by using s and d in the @p percent 445 Generate tweened object in the @obj by using s and d in the @p percent
199 http://lists.w3.org/Archives/Public/www-style/2010Jun/0602.html 446 http://lists.w3.org/Archives/Public/www-style/2010Jun/0602.html
200 """ 447 """
201 if typ == self.TWEEN_TYPE_SCALE: 448 if typ == self.TWEEN_TYPE_SCALE:
202 self.updateTweenObjectScale(obj,s,d,p,newobj)
203 pass
204 elif typ == self.TWEEN_TYPE_NORMAL:
205 if newobj == None: 449 if newobj == None:
206 newobj = s.duplicate(self._doc) 450 newobj = s.duplicate(self._doc)
207 newobj.setAttribute("ref", s.getAttribute("id")) 451 newobj.setAttribute("ref", s.getAttribute("id"))
208 obj.appendChild(newobj) 452 obj.appendChild(newobj)
453 pass
454 self.update_tween_object_scale(s, d, p, newobj)
455 pass
456 elif typ == self.TWEEN_TYPE_NORMAL and newobj == None:
457 newobj = s.duplicate(self._doc)
458 newobj.setAttribute("ref", s.getAttribute("id"))
459 obj.appendChild(newobj)
460 pass
209 pass 461 pass
210 462
211 def _update_tween_style(self, s, d, p, newobj): 463 def _update_tween_style(self, s, d, p, newobj):
212 if d.name() == 'svg:use': 464 if d.name() == 'svg:use':
213 return 465 return
214 try: 466 try:
215 s_style = s.getAttribute('style') 467 s_style = s.getAttribute('style')
216 except: 468 except:
217 s_attrs = {} 469 s_attrs = {}
218 else: 470 else:
219 s_attrs = parse_style(s_style) 471 s_attrs = _parse_style(s_style)
220 pass 472 pass
221 473
222 try: 474 try:
223 d_style = d.getAttribute('style') 475 d_style = d.getAttribute('style')
224 except: 476 except:
225 d_attrs = {} 477 d_attrs = {}
226 else: 478 else:
227 d_attrs = parse_style(d_style) 479 d_attrs = _parse_style(d_style)
228 pass 480 pass
229 481
230 attrs = dict(s_attrs) 482 attrs = dict(s_attrs)
231 483
232 if s_attrs.has_key('opacity'): 484 if s_attrs.has_key('opacity'):
242 pass 494 pass
243 495
244 cur_opacity = start_opacity * (1 - p) + end_opacity * p 496 cur_opacity = start_opacity * (1 - p) + end_opacity * p
245 attrs['opacity'] = '%g' % (cur_opacity) 497 attrs['opacity'] = '%g' % (cur_opacity)
246 498
247 new_style = gen_style(attrs) 499 new_style = _gen_style(attrs)
248 newobj.setAttribute('style', new_style) 500 newobj.setAttribute('style', new_style)
249 pass 501 pass
250 502
251 def updateTweenObjectScale_Group(self, s, d, p, newobj, top): 503 def update_tween_object_scale(self, start, stop, percent, newobj):
252 # Parse the translate or matrix 504 start_attrs = {}
253 # 505 _parse_style_ani(start, start_attrs)
254 # D = B inv(A) 506 _parse_attr_ani(start, start_attrs)
255 try: 507
256 (ox,oy) = s.spitem.getCenter() 508 stop_attrs = {}
257 except: 509 _parse_style_ani(stop, stop_attrs)
258 ox = 0 510 _parse_attr_ani(stop, stop_attrs)
259 oy = 0 511
260 pass 512 _normalize_attrs(start, start_attrs, stop, stop_attrs)
261 513
262 try: 514 tween_attrs = {}
263 (dx,dy) = d.spitem.getCenter() 515 attrs = set(start_attrs.keys() + stop_attrs.keys())
264 except: 516 for attr in attrs:
265 dx = 0 517 start_v = start_attrs[attr]
266 dy = 0 518 stop_v = stop_attrs[attr]
267 pass 519
268 520 if start_v != stop_v:
269 self._update_tween_style(s, d, p, newobj) 521 new_v = _tween_interpolation(attr, start_v, stop_v, percent)
270 522 tween_attrs[attr] = new_v
271 sm = self.parseTransform(s) 523 pass
272 ss = self.decomposition(sm) 524 pass
273 dm = self.parseTransform(d)
274 dd = self.decomposition(dm)
275 sx = (ss[0]*(1-p)+dd[0]*p)/ss[0]
276 sy = (ss[1]*(1-p)+dd[1]*p)/ss[1]
277 a = ss[2]*(1-p)+dd[2]*p-ss[2]
278 tx = ox*(1-p)+dx*p
279 ty = oy*(1-p)+dy*p
280 m = [math.cos(a),math.sin(a),-math.sin(a),math.cos(a),0,0]
281 m = self.mulA([sx,0,0,sy,0,0],m)
282 m = self.mulA(m,[1,0,0,1,-ox,oy-self.height])
283 m = self.mulA([1,0,0,1,tx,self.height-ty],m)
284
285 top.setAttribute("transform","matrix(%g,%g,%g,%g,%g,%g)" % (m[0],m[2],m[1],m[3],m[4],m[5]))
286 pass
287
288 def updateTweenObjectScale_Use(self, s, d, p, newobj, top):
289 # Parse the translate or matrix
290 #
291 # D = B inv(A)
292 try:
293 (ox,oy) = s.spitem.getCenter()
294 except:
295 ox = 0
296 oy = 0
297 pass
298
299 try:
300 (dx,dy) = d.spitem.getCenter()
301 except:
302 dx = 0
303 dy = 0
304 pass
305
306 self._update_tween_style(s, d, p, newobj)
307 525
308 dm = self.parseTransform(d) 526 _apply_animation_attrs(tween_attrs, newobj)
309 dd = self.decomposition(dm)
310 sx = 1-(1-dd[0])*p
311 sy = 1-(1-dd[1])*p
312 a = dd[2]*p
313 tx = ox*(1-p)+dx*p
314 ty = oy*(1-p)+dy*p
315 m = [math.cos(a),math.sin(a),-math.sin(a),math.cos(a),0,0]
316 m = self.mulA([sx,0,0,sy,0,0],m)
317 m = self.mulA(m,[1,0,0,1,-ox,oy-self.height])
318 m = self.mulA([1,0,0,1,tx,self.height-ty],m)
319
320 top.setAttribute("transform","matrix(%g,%g,%g,%g,%g,%g)" % (m[0],m[2],m[1],m[3],m[4],m[5]))
321 pass
322
323 def updateTweenObjectScale_Primitive(self, s, d, p, newobj, top):
324 try:
325 if d.name() == "svg:use":
326 sw = 1
327 sh = 1
328 dw = 1
329 dh = 1
330 else:
331 try:
332 sw = float(s.getAttribute("width"))
333 except:
334 sw = 1
335 try:
336 sh = float(s.getAttribute("height"))
337 except:
338 sh = 1
339
340 try:
341 dw = float(d.getAttribute("width"))
342 except:
343 dw = 1
344 try:
345 dh = float(d.getAttribute("height"))
346 except:
347 dh = 1
348 pass
349
350 self._update_tween_style(s, d, p, newobj)
351
352 try:
353 (ox,oy) = s.spitem.getCenter()
354 except:
355 ox = 0
356 oy = 0
357 try:
358 (dx,dy) = d.spitem.getCenter()
359 except:
360 dx = 0
361 dy = 0
362 try:
363 sm = self.parseTransform(s)
364 ss = self.decomposition(sm)
365 except:
366 ss = [1,1,0,0,0]
367 pass
368 try:
369 dm = self.parseTransform(d)
370 dd = self.decomposition(dm)
371 except:
372 dd = [1,1,0,0,0]
373 dd[0] = dd[0]*dw/sw
374 dd[1] = dd[1]*dh/sh
375 sx = (ss[0]*(1-p)+dd[0]*p)/ss[0]
376 sy = (ss[1]*(1-p)+dd[1]*p)/ss[1]
377 a = ss[2]*(1-p)+dd[2]*p-ss[2]
378 tx = ox*(1-p)+dx*p
379 ty = oy*(1-p)+dy*p
380 m = [math.cos(a),math.sin(a),-math.sin(a),math.cos(a),0,0]
381 m = self.mulA([sx,0,0,sy,0,0],m)
382 m = self.mulA(m,[1,0,0,1,-ox,oy-self.height])
383 m = self.mulA([1,0,0,1,tx,self.height-ty],m)
384
385 top.setAttribute("transform","matrix(%g,%g,%g,%g,%g,%g)" % (m[0],m[2],m[1],m[3],m[4],m[5]))
386 except:
387 traceback.print_exc()
388 pass
389 pass
390
391 def updateTweenObjectScale(self,obj,s,d,p,newobj):
392 """
393 Generate a new group which contains the original group and then
394 add the transform matrix to generate a tween frame between the
395 origin and destination scene group.
396
397 We will parse the transform matrix of the @s and @d and then
398 generate the matrix which is (1-p) of @s and p percent of @d.
399 """
400 if newobj and not newobj.firstChild():
401 # newobj is not with expect structure.
402 #
403 # When a user change tween type of a scene, the structure
404 # of dup group created by old tween type may not satisfy
405 # the requirement of current tween type.
406 newobj.parent().removeChild(newobj)
407 newobj = None
408 pass
409
410 if newobj == None:
411 newobj = s.duplicate(self._doc)
412 top = self._doc.createElement("svg:g")
413 top.setAttribute("ref",s.getAttribute("id"))
414 top.appendChild(newobj)
415 obj.appendChild(top)
416 else:
417 top = newobj
418 newobj = newobj.firstChild()
419 pass
420 if s.name() == 'svg:g':
421 self.updateTweenObjectScale_Group(s,d,p,newobj,top)
422 elif s.name() == 'svg:use':
423 self.updateTweenObjectScale_Use(s,d,p,newobj,top)
424 else:
425 self.updateTweenObjectScale_Primitive(s,d,p,newobj,top)
426 pass 527 pass
427 pass 528 pass
428 529
429 ## \brief Providing capability of showing scenes. 530 ## \brief Providing capability of showing scenes.
430 # 531 #