~ubuntu-branches/ubuntu/karmic/tovid/karmic

« back to all changes in this revision

Viewing changes to libtovid/render/effect.py

  • Committer: Bazaar Package Importer
  • Author(s): Matvey Kozhev
  • Date: 2008-01-24 22:04:40 UTC
  • Revision ID: james.westby@ubuntu.com-20080124220440-x7cheljduf1rdgnq
Tags: upstream-0.31
ImportĀ upstreamĀ versionĀ 0.31

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#! /usr/bin/env python
 
2
# effect.py
 
3
 
 
4
"""This module defines classes for creating and drawing effects on a series
 
5
of drawings (as in a Flipbook).
 
6
 
 
7
Effect classes are arranged in a (currently) simple hierarchy:
 
8
 
 
9
    Effect (base class)
 
10
    |-- Movement
 
11
    |   \-- Translate
 
12
    |-- Fade
 
13
    |   \-- FadeInOut
 
14
    |-- Colorfade
 
15
    |-- Spectrum
 
16
    |-- Scale
 
17
    |-- Whirl
 
18
    |-- PhotoZoom
 
19
    \-- KeyFunction
 
20
 
 
21
"""
 
22
 
 
23
__all__ = [\
 
24
    'Effect',
 
25
    'Movement',
 
26
    'Translate',
 
27
    'Fade',
 
28
    'FadeInOut'
 
29
    'Colorfade',
 
30
    'Spectrum',
 
31
    'Scale',
 
32
    'Whirl',
 
33
    'PhotoZoom',
 
34
    'KeyFunction'
 
35
    ]
 
36
 
 
37
from libtovid.render.drawing import Drawing
 
38
from libtovid.render.animation import Keyframe, Tween
 
39
from random import randint
 
40
 
 
41
class Effect:
 
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."""
 
46
        self.start = start
 
47
        self.end = end
 
48
        # List of Keyframes
 
49
        self.keyframes = [Keyframe(self.start, 0), Keyframe(self.end, 0)]
 
50
        self.tween = Tween(self.keyframes)
 
51
 
 
52
        # Parents
 
53
        self._parent_flipbook = None
 
54
        self._parent_layer = None
 
55
 
 
56
    def _init_parent_flipbook(self, fb):
 
57
        self._parent_flipbook = fb
 
58
 
 
59
    def _init_parent_layer(self, layer):
 
60
        self._parent_layer = layer
 
61
 
 
62
    def pre_draw(self, drawing, frame):
 
63
        """Set up effect elements that must be applied before drawing a Layer.
 
64
 
 
65
            drawing: The Drawing to apply effects to
 
66
            frame:   The frame for which to render the effect
 
67
 
 
68
        Extend this function in derived classes.
 
69
        """
 
70
        drawing.save()
 
71
 
 
72
    def post_draw(self, drawing, frame):
 
73
        """Finalize effect elements after a Layer is drawn.
 
74
 
 
75
            drawing: The Drawing to apply effects to
 
76
            frame:   The frame for which to render the effect
 
77
 
 
78
        Extend this function in derived classes.
 
79
        """
 
80
        drawing.restore()
 
81
 
 
82
 
 
83
# ============================================================================
 
84
# New Effect template
 
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.
 
91
    """
 
92
    
 
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.
 
100
        """
 
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.
 
104
 
 
105
        # Next, define any keyframes your effect needs to use. This
 
106
        # effect just varies something from start_val to end_val:
 
107
        self.keyframes = [\
 
108
            Keyframe(start, start_val),
 
109
            Keyframe(end, end_val)
 
110
            ]
 
111
 
 
112
        # Call this afterwards, to calculate the values at all frames
 
113
        self.tween = Tween(self.keyframes)
 
114
 
 
115
    def draw(self, drawing, frame):
 
116
        """Undocumented"""
 
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.
 
121
        
 
122
        # First, it's good to make sure we really have a Drawing class
 
123
        assert isinstance(drawing, Drawing)
 
124
        
 
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])
 
128
 
 
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)
 
136
        
 
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])
 
140
 
 
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)
 
148
 
 
149
    # That's it! Your effect is ready to use.
 
150
    # See libtovid/flipbook.py for examples on how to use effects
 
151
 
 
152
# ============================================================================
 
153
# End of new effect template
 
154
# ============================================================================
 
155
 
 
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)
 
161
        self.keyframes = [\
 
162
            Keyframe(start, (x0, y0)),
 
163
            Keyframe(end, (x1, y1))
 
164
            ]
 
165
        self.tween = Tween(self.keyframes)
 
166
 
 
167
    def pre_draw(self, drawing, frame):
 
168
        drawing.save()
 
169
        drawing.translate(*self.tween[frame])
 
170
 
 
171
    def post_draw(self, drawing, frame):
 
172
        drawing.restore()
 
173
 
 
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))
 
178
        
 
179
 
 
180
class Fade (Effect):
 
181
    """A generic fade effect, varying the opacity of a layer.
 
182
    """
 
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.
 
186
 
 
187
        fade_length -- num of frames to fade-in from start, and num of
 
188
                       frames to fade-out before end. Everything in-between
 
189
                       is at full opacity.
 
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
 
194
 
 
195
        """
 
196
        # A fill-opacity curve, something like:
 
197
        #         ______        100%
 
198
        #        /      \
 
199
        # start./        \.end  0%
 
200
        #
 
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
 
205
        else:
 
206
            raise ValueError, "List of Keyframe objects required"
 
207
 
 
208
        self.tween = Tween(self.keyframes, method)
 
209
        
 
210
 
 
211
    def pre_draw(self, drawing, frame):
 
212
        """Called before drawing on a layer"""
 
213
        drawing.push_group()
 
214
 
 
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])
 
220
 
 
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.
 
225
 
 
226
        fade_length -- num of frames to fade-in from start, and num of
 
227
                       frames to fade-out before end. Everything in-between
 
228
                       is at full opacity.
 
229
        """
 
230
        # A fill-opacity curve, something like:
 
231
        #         ______        100%
 
232
        #        /      \
 
233
        # start./        \.end  0%
 
234
        #
 
235
        self.keyframes = [\
 
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
 
240
                ]
 
241
 
 
242
        self.tween = Tween(self.keyframes)
 
243
 
 
244
    
 
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)
 
250
        self.keyframes = [\
 
251
            Keyframe(start, (r0, g0, b0)),
 
252
            Keyframe(end, (r1, g1, b1))
 
253
            ]
 
254
        self.tween = Tween(self.keyframes)
 
255
 
 
256
    def pre_draw(self, drawing, frame):
 
257
        """Set source color"""
 
258
        drawing.set_source(self.tween[frame])
 
259
 
 
260
    def post_draw(self, drawing, frame):
 
261
        pass
 
262
 
 
263
 
 
264
 
 
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
 
270
        self.keyframes = [\
 
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))
 
278
            ]
 
279
        self.tween = Tween(self.keyframes)
 
280
 
 
281
    def pre_draw(self, drawing, frame):
 
282
        drawing.set_source(self.tween[frame])
 
283
 
 
284
    def post_draw(self, drawing, frame):
 
285
        pass
 
286
 
 
287
 
 
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)
 
292
        self.keyframes =[\
 
293
            Keyframe(start, (w0, h0)),
 
294
            Keyframe(end, (w1, h1))
 
295
            ]
 
296
        self.tween = Tween(self.keyframes)
 
297
 
 
298
 
 
299
    def pre_draw(self, drawing, frame):
 
300
        drawing.save()
 
301
        drawing.scale(self.tween[frame])
 
302
 
 
303
    def post_draw(self, drawing, frame):
 
304
        drawing.restore()
 
305
 
 
306
 
 
307
class Whirl(Effect):
 
308
    """Rotates an object a number of times.
 
309
    """
 
310
    def __init__(self, keyframes, center=(0,0), method='linear', units='deg'):
 
311
        """Create a Whirl effect
 
312
        
 
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
 
316
        """
 
317
        if units != 'deg' and units != 'rad':
 
318
            raise ValueError, "units must be 'rad' (radians) or 'deg' (degrees)"
 
319
        self.units = units
 
320
        
 
321
        if not isinstance(center, tuple):
 
322
            raise ValueError, "center must be a two-value tuple"
 
323
        self.center = center
 
324
 
 
325
        self.keyframes = keyframes
 
326
        self.tween = Tween(self.keyframes, method)
 
327
 
 
328
    def pre_draw(self, drawing, frame):
 
329
        drawing.save()
 
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])
 
336
 
 
337
    def post_draw(self, drawing, frame):
 
338
        drawing.translate(- self.center[0], - self.center[1])
 
339
        drawing.restore()
 
340
 
 
341
 
 
342
 
 
343
class PhotoZoom(Effect):
 
344
    """Zoom in and create dynamism by moving a picture.
 
345
 
 
346
    Normally applies to an Image layer, but can be used with others.
 
347
    """
 
348
    def __init__(self, keyframes, subject=(0,0), direction=None, movement=50, method='linear'):
 
349
        """Create a PhotoZoom effect
 
350
 
 
351
        keyframes -- 0.0 for beginning of effect, and 1.0 to reach the end,
 
352
                     intermediate values show the intermediate state of the
 
353
                     effect.
 
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'
 
361
        """
 
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'
 
366
        else:
 
367
            self.direction = direction
 
368
 
 
369
        print "Zoom in direction: %s" % self.direction
 
370
        
 
371
        self.subject = subject
 
372
 
 
373
        self.movement = movement
 
374
 
 
375
        self.keyframes = keyframes
 
376
        self.tween = Tween(self.keyframes, method)
 
377
 
 
378
    def pre_draw(self, drawing, frame):
 
379
        drawing.save()
 
380
 
 
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),
 
385
                                    int(0.66 * fb.w)),
 
386
                            randint(int(0.33 * fb.h),
 
387
                                    int(0.66 * fb.h)))
 
388
 
 
389
        # Max moving = 25% * pixels estimation * movement factor
 
390
        zoomfactor = 0.25 * (self.movement / 100.)
 
391
 
 
392
        inter = self.tween[frame]
 
393
        
 
394
        if (self.direction == 'in'):
 
395
            gozoom = 1.0 + (1.0 - inter) * zoomfactor
 
396
        else:
 
397
            gozoom = 1.0 + inter * zoomfactor
 
398
 
 
399
 
 
400
        #print "Values: zoomfactor %s inter %s gozoom %s fb %s" % (zoomfactor, inter, gozoom, fb)
 
401
 
 
402
        drawing.scale_centered(self.subject[0], self.subject[1],
 
403
                               gozoom, gozoom)
 
404
 
 
405
 
 
406
    def post_draw(self, drawing, frame):
 
407
        drawing.restore()
 
408
 
 
409
 
 
410
 
 
411
 
 
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:
 
417
 
 
418
            KeyFunction(Drawing.stroke_width,
 
419
                        [Keyframe(1, 1), Keyframe(30, 12)])
 
420
 
 
421
        This says to vary the stroke width from 1 (at frame 1) to 12 (at
 
422
        frame 30).
 
423
 
 
424
        The 'method' argument defines an interpolation method to use between
 
425
        keyframes, and may be either 'linear' or 'cosine'. 
 
426
        """
 
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)
 
434
 
 
435
    def pre_draw(self, drawing, frame):
 
436
        drawing.save()
 
437
        self.draw_function(drawing, self.tween[frame])
 
438
 
 
439
    def post_draw(self, drawing, frame):
 
440
        drawing.restore()