We end with a program, TransitionEffect.java that demonstrates how to use the QuickTime effects architecture and apply it to a character in an animation scene.
The code in this section shows how you can build a transition effect and apply it to a character in a realistic animation (Fig. 9.9):
Transition Effect Example
It shows the usage of effects and effects presenters. The kCrossFadeTransitionType effect is applied to the source and the destination images, which that makes them fade as per the number of frames set for the transition.
The QTEffectPresent is used to embed the QTTransition effect and present it to the Compositor, which draws it to the canvas. Note that the QTTransition effect cannot be directly added to the Compositor; instead, it is given to the QTEffectPresent, which is added to the Compositor. If a filter were applied, it would have the same limitations as a transition when added to a Compositor. It must be added using the QTEffectPresent.
A ripple effect is applied to the water image (in front of the water image taking up the same location), using the CompositableEffect class. Zero sourced effects, such as the ripple effect, can be added directly to a Compositor.
class Fader implements StdQTConstants { Fader() throws Exception { File file = QTFactory.findAbsolutePath ("pics/Ship.pct"); QTFile f = new QTFile (file.getAbsolutePath()); QDGraphics g = new QDGraphics (new QDRect (78, 29)); g.setBackColor (QDColor.black); g.eraseRect(null); ImagePresenter srcImage = ImagePresenter.fromGWorld(g); Compositable destImage = new GraphicsImporterDrawer (f); ef = new QTTransition (); ef.setRedrawing(true); ef.setSourceImage (srcImage); ef.setDestinationImage (destImage); ef.setDisplayBounds (new QDRect(78, 29)); ef.setEffect (createFadeEffect (kEffectBlendMode, kCrossFadeTransitionType)); ef.setFrames(60); ef.setCurrentFrame(0); } private QTTransition ef; public QTEffectPresenter makePresenter() throws QTException { QTEffectPresenter efPresenter = new QTEffectPresenter (ef); return efPresenter; } public QTTransition getTransition () { return ef; AtomContainer createFadeEffect (int effectType, int effectNumber) throws QTException { AtomContainer effectSample = new AtomContainer(); effectSample.insertChild (new Atom(kParentAtomIsContainer), kEffectWhatAtom, 1, 0, EndianOrder.flipNativeToBigEndian32(kCrossFadeTransitionType)); effectSample.insertChild (new Atom(kParentAtomIsContainer), effectType, 1, 0, EndianOrder.flipNativeToBigEndian32(effectNumber)); return effectSample; } }
We then create the QTEffectPresent for the transition and add it as a member of the Compositor:
Fader fader = new Fader(); QTEffectPresenter efp = fader.makePresenter(); efp.setGraphicsMode (new GraphicsMode (blend, QDColor.gray)); efp.setLocation(80, 80); comp.addMember (efp, 1); comp.addController(new TransitionControl (20, 1, fader.getTransition()));
The controller object implements the TicklishController and subclasses the PeriodicAction class that has a doAction() method, which gets invoked on every tickle call.
class TransitionControl extends PeriodicAction implements TicklishController { TransitionControl (int scale, int period, QTTransition t) { super (scale , period); this.t = t; }
The doAction() call is overridden to set the current frame and redraw the TransitionEffect. The source and the destination images of the transition effect are swapped when the number of set frames is reached. The transition's controller then rests for a few seconds before it is awakened again and reapplied. The incoming time values to the doAction method (called by PeriodicAction.tickle()) are used to calculate the rest and transition, ensuring that if the rate of playback changes, the transition controller will react to these changes. When the transition is quiescent, we set the redrawing state of the QTEffectPresent to false. This ensures that when the Compositor invalidates this presenter it will not invalidate the sprite, as we are not currently drawing into the QTEffectPresent. When the transition is being applied, that is when the current frame is set, then the isRedrawing method will return true. The Invalidator for the QTEffectPresenter will then redraw the effect and invalidate its sprite presenter. Thus, this controller is also able to control the redrawing of both itself and its sprite through the use of the redrawing state and ensure that the Compositor only renders the sprite that presents this QTEffectPresent when it actually changes its pixel data -- that is, the image data of the effect's presenter.
protected void doAction (float er, int tm) throws QTException { if (waiting) { if ((er > 0 && ((startWaitTime + waitForMsecs) <= tm)) || (er < 0 && ((startWaitTime - waitForMsecs) >= tm))) { waiting = false; t.setRedrawing(true); } else return; } int curr_frm = t.getCurrentFrame(); curr_frm++; t.setCurrentFrame(curr_frm); if (curr_frm > t.getFrames()) { curr_frm = 0; t.setRedrawing(false); t.setCurrentFrame(curr_frm); t.swapImages(); waiting = true; startWaitTime = tm; }