4
"""This module defines classes for creating and drawing effects on a series
5
of drawings (as in a Flipbook).
7
Effect classes are arranged in a (currently) simple hierarchy:
37
from libtovid.render.drawing import Drawing
38
from libtovid.render.animation import Keyframe, Tween
39
from random import randint
42
"""A "special effect" created by keyframing a Cairo drawing command
43
along the given frame interval."""
44
def __init__(self, start, end):
45
"""Create an effect lasting from start frame to end frame."""
49
self.keyframes = [Keyframe(self.start, 0), Keyframe(self.end, 0)]
50
self.tween = Tween(self.keyframes)
53
self._parent_flipbook = None
54
self._parent_layer = None
56
def _init_parent_flipbook(self, fb):
57
self._parent_flipbook = fb
59
def _init_parent_layer(self, layer):
60
self._parent_layer = layer
62
def pre_draw(self, drawing, frame):
63
"""Set up effect elements that must be applied before drawing a Layer.
65
drawing: The Drawing to apply effects to
66
frame: The frame for which to render the effect
68
Extend this function in derived classes.
72
def post_draw(self, drawing, frame):
73
"""Finalize effect elements after a Layer is drawn.
75
drawing: The Drawing to apply effects to
76
frame: The frame for which to render the effect
78
Extend this function in derived classes.
83
# ============================================================================
85
# Copy and paste this code to create your own Effect
86
# ============================================================================
87
# The first line defines your effect's name. (Effect) means it inherits from
88
# the base Effect class, and shares some properties with it.
89
class MyEffect (Effect):
90
"""Modify this documentation string to describe what your effect does.
93
# The __init__ function is called whenever a MyEffect is created.
94
# Make sure your __init__ takes start and end arguments; additional
95
# arguments (such as start_val and end_val below) allow someone using
96
# your effect class to customize its behavior in some way. See the
97
# other effects below for examples.
98
def __init__(self, start, end, start_val, end_val):
99
"""Create a MyEffect lasting from start to end frame.
101
# Be sure to call this first-thing:
102
Effect.__init__(self, start, end)
103
# It initializes the base Effect class with start and end frames.
105
# Next, define any keyframes your effect needs to use. This
106
# effect just varies something from start_val to end_val:
108
Keyframe(start, start_val),
109
Keyframe(end, end_val)
112
# Call this afterwards, to calculate the values at all frames
113
self.tween = Tween(self.keyframes)
115
def draw(self, drawing, frame):
117
# Example function that can be called when drawing a layer.
118
# The Layer must know that his particular effect requires the
119
# calling of this function, or to add special parameters, or to
120
# draw between items in the layer.
122
# First, it's good to make sure we really have a Drawing class
123
assert isinstance(drawing, Drawing)
125
# This effect varies the stroke width across a sequence of frames.
126
# Replace 'stroke_width' with your own drawing function(s)
127
drawing.stroke_width(self.tween[frame])
129
# Effect rendering occurs in two phases: pre_draw and post_draw.
130
# These functions are already defined in the Effect base class; extend
131
# them to do your effect rendering.
132
def pre_draw(self, drawing, frame):
133
"""Do preliminary effect rendering."""
134
# Always call the base class pre_draw first:
135
Effect.pre_draw(self, drawing, frame)
137
# This effect varies the stroke width across a sequence of frames.
138
# Replace 'stroke_width' with your own drawing function(s)
139
drawing.stroke_width(self.tween[frame])
141
# Extend post_draw to do any post-rendering cleanup you need to do. Most
142
# of the time, you won't need to extend this, but if you do, be sure to
143
# call the base class post_draw at the end:
144
def post_draw(self, drawing, frame):
145
"""Do post-drawing cleanup."""
146
# Post-drawing cleanup here
147
Effect.post_draw(self, drawing, frame)
149
# That's it! Your effect is ready to use.
150
# See libtovid/flipbook.py for examples on how to use effects
152
# ============================================================================
153
# End of new effect template
154
# ============================================================================
156
class Movement (Effect):
157
"""A movement effect, from one point to another."""
158
def __init__(self, start, end, (x0, y0), (x1, y1)):
159
"""Move from start (x0, y0) to end (x1, y1)."""
160
Effect.__init__(self, start, end)
162
Keyframe(start, (x0, y0)),
163
Keyframe(end, (x1, y1))
165
self.tween = Tween(self.keyframes)
167
def pre_draw(self, drawing, frame):
169
drawing.translate(*self.tween[frame])
171
def post_draw(self, drawing, frame):
174
class Translate (Movement):
175
"""Translates the layer to some relative (x,y) coordinates"""
176
def __init__(self, start, end, (dx, dy)):
177
Movement.__init__(self, start, end, (0,0), (dx, dy))
181
"""A generic fade effect, varying the opacity of a layer.
183
def __init__(self, keyframes, method='linear'):
184
"""Fade in from start, for fade_length frames; hold at full
185
opacity, then fade out for fade_length frames before end.
187
fade_length -- num of frames to fade-in from start, and num of
188
frames to fade-out before end. Everything in-between
190
keyframes -- a set of Keyframe() objects, determining the fading
191
curve. Values of the Keyframe() must be floats ranging
192
from 0.0 to 1.0 (setting opacity).
193
method -- linear, cosine
196
# A fill-opacity curve, something like:
201
if isinstance(keyframes, list):
202
if not isinstance(keyframes[0], Keyframe):
203
raise ValueError, "Must be a list of Keyframe objects"
204
self.keyframes = keyframes
206
raise ValueError, "List of Keyframe objects required"
208
self.tween = Tween(self.keyframes, method)
211
def pre_draw(self, drawing, frame):
212
"""Called before drawing on a layer"""
215
def post_draw(self, drawing, frame):
216
"""Called after drawing on a layer"""
217
assert isinstance(drawing, Drawing)
218
drawing.pop_group_to_source()
219
drawing.paint_with_alpha(self.tween[frame])
221
class FadeInOut(Fade):
222
def __init__(self, start, end, fade_length=30):
223
"""Fade in from start, for fade_length frames; hold at full
224
opacity, then fade out for fade_length frames before end.
226
fade_length -- num of frames to fade-in from start, and num of
227
frames to fade-out before end. Everything in-between
230
# A fill-opacity curve, something like:
236
Keyframe(start, 0.0), # Start fading in
237
Keyframe(start + fade_length, 1.0), # Fade-in done
238
Keyframe(end - fade_length, 1.0), # Start fading out
239
Keyframe(end, 0.0) # Fade-out done
242
self.tween = Tween(self.keyframes)
245
class Colorfade (Effect):
246
"""A color-slide effect between an arbitrary number of RGB colors."""
247
def __init__(self, start, end, (r0, g0, b0), (r1, g1, b1)):
248
"""Fade between the given RGB colors."""
249
Effect.__init__(self, start, end)
251
Keyframe(start, (r0, g0, b0)),
252
Keyframe(end, (r1, g1, b1))
254
self.tween = Tween(self.keyframes)
256
def pre_draw(self, drawing, frame):
257
"""Set source color"""
258
drawing.set_source(self.tween[frame])
260
def post_draw(self, drawing, frame):
265
class Spectrum (Effect):
266
"""A full-spectrum color-fade effect between start and end frames."""
267
def __init__(self, start, end):
268
Effect.__init__(self, start, end)
269
step = (end - start) / 6
271
Keyframe(start, (1.0, 0, 0)),
272
Keyframe(start + step, (1.0, 0, 1.0)),
273
Keyframe(start + step*2, (0, 0, 1.0)),
274
Keyframe(start + step*3, (0, 1.0, 1.0)),
275
Keyframe(start + step*4, (0, 1.0, 0)),
276
Keyframe(start + step*5, (1.0, 1.0, 0)),
277
Keyframe(end, (1.0, 0, 0))
279
self.tween = Tween(self.keyframes)
281
def pre_draw(self, drawing, frame):
282
drawing.set_source(self.tween[frame])
284
def post_draw(self, drawing, frame):
288
class Scale (Effect):
289
"""A Scaling effect, from one size to another."""
290
def __init__(self, start, end, (w0, h0), (w1, h1)):
291
Effect.__init__(self, start, end)
293
Keyframe(start, (w0, h0)),
294
Keyframe(end, (w1, h1))
296
self.tween = Tween(self.keyframes)
299
def pre_draw(self, drawing, frame):
301
drawing.scale(self.tween[frame])
303
def post_draw(self, drawing, frame):
308
"""Rotates an object a number of times.
310
def __init__(self, keyframes, center=(0,0), method='linear', units='deg'):
311
"""Create a Whirl effect
313
method -- 'linear' or 'cosine', for passing from one angle to another
314
Pass from one angle to another
315
units -- 'deg' or 'rad', the unit used in the Keyframes
317
if units != 'deg' and units != 'rad':
318
raise ValueError, "units must be 'rad' (radians) or 'deg' (degrees)"
321
if not isinstance(center, tuple):
322
raise ValueError, "center must be a two-value tuple"
325
self.keyframes = keyframes
326
self.tween = Tween(self.keyframes, method)
328
def pre_draw(self, drawing, frame):
330
# how to center the thing ? so you give a rotation point ?
331
drawing.translate(*self.center)
332
if self.units is 'deg':
333
drawing.rotate_deg(self.tween[frame])
334
elif self.units is 'rad':
335
drawing.rotate_rad(self.tween[frame])
337
def post_draw(self, drawing, frame):
338
drawing.translate(- self.center[0], - self.center[1])
343
class PhotoZoom(Effect):
344
"""Zoom in and create dynamism by moving a picture.
346
Normally applies to an Image layer, but can be used with others.
348
def __init__(self, keyframes, subject=(0,0), direction=None, movement=50, method='linear'):
349
"""Create a PhotoZoom effect
351
keyframes -- 0.0 for beginning of effect, and 1.0 to reach the end,
352
intermediate values show the intermediate state of the
354
subject -- Position of the subject of interest. That is the point where
355
the zoom in/out will focus it's attention.
356
If (0,0), the focus will be chosen randomly in the 2/3 of
357
the center of the screen.
358
direction -- 'in', 'out', None (random)
359
movement -- 0 to 100, percentage of movement to create.
360
method -- 'linear' or 'cosine'
362
if direction != 'in' and direction != 'out' and direction != None:
363
raise ValueError, "'direction' must be 'in', 'out' or None"
364
if (direction == None):
365
self.direction = randint(0,1) and 'in' or 'out'
367
self.direction = direction
369
print "Zoom in direction: %s" % self.direction
371
self.subject = subject
373
self.movement = movement
375
self.keyframes = keyframes
376
self.tween = Tween(self.keyframes, method)
378
def pre_draw(self, drawing, frame):
381
fb = self._parent_flipbook
382
# Use subject, or randomize in the center 1/3 of the image.
383
if (self.subject == (0,0)):
384
self.subject = (randint(int(0.33 * fb.w),
386
randint(int(0.33 * fb.h),
389
# Max moving = 25% * pixels estimation * movement factor
390
zoomfactor = 0.25 * (self.movement / 100.)
392
inter = self.tween[frame]
394
if (self.direction == 'in'):
395
gozoom = 1.0 + (1.0 - inter) * zoomfactor
397
gozoom = 1.0 + inter * zoomfactor
400
#print "Values: zoomfactor %s inter %s gozoom %s fb %s" % (zoomfactor, inter, gozoom, fb)
402
drawing.scale_centered(self.subject[0], self.subject[1],
406
def post_draw(self, drawing, frame):
412
class KeyFunction (Effect):
413
"""A keyframed effect on an arbitrary Drawing function."""
414
def __init__(self, draw_function, keyframes, method='linear'):
415
"""Create an effect using the given Drawing function, with values
416
determined by the given list of Keyframes. For example:
418
KeyFunction(Drawing.stroke_width,
419
[Keyframe(1, 1), Keyframe(30, 12)])
421
This says to vary the stroke width from 1 (at frame 1) to 12 (at
424
The 'method' argument defines an interpolation method to use between
425
keyframes, and may be either 'linear' or 'cosine'.
427
# Call base constructor with start and end frames
428
Effect.__init__(self, keyframes[0].frame, keyframes[-1].frame)
429
# TODO: Make sure a valid function name is given
430
self.draw_function = draw_function
431
self.keyframes = keyframes
432
# Tween keyframes using the given interpolation method
433
self.tween = Tween(self.keyframes, method)
435
def pre_draw(self, drawing, frame):
437
self.draw_function(drawing, self.tween[frame])
439
def post_draw(self, drawing, frame):