Creating New Video Effects

This chapter discusses how to write your own video effects. If you are only interested in building applications that use effects, you can skip this chapter.

QuickTime video effects are implemented as Component Manager components, the standard mechanism for extending QuickTime. To implement your own effect, you create a new effect component. An effect component is a specialized type of image decompressor component.

This chapter walks you through the implementation of a sample effect component. The sample effect is built on a framework of code that you can reuse when you implement your own effect component.

What Effects Components Do

The basic task of every effect component is very simple. The component is passed zero or more source frames and must produce a single destination frame. The destination frame is the source frame or frames after processing by the effect-rendering algorithm.

The component must provide a set of services that QuickTime can call. These services allow QuickTime (or any other client software that uses your component) to perform actions such as these:

Your effect component must be able to service such requests. To do so, it implements a set of standard interface functions that are called through a component dispatch function. Details of these functions are given in the section The Effect Component Interface.

The main task of the effect component is to implement the specific algorithm that transforms source frames into a destination frame. You need to supply versions of your algorithm for each bit depth and pixel format that your component supports. Choosing which bit depths and pixel formats to support, and implementing algorithms for each combination of these, are a significant part of building your effect component.

In addition, your effect component must provide a parameter description that describes the parameters that the effect takes. The parameter description can be used by the software that is calling your component to construct a user interface that allows users to change the value of the parameters sent to your component. This is described in detail in the section Supplying Parameter Description Information.

The Effect Component Interface

Effect components, as with all other types of QuickTime components, must implement a defined set of functions. To ease the component development process, the Generic Effect component is provided for you. This component implements many of the “housekeeping” functions that all components must perform. In most cases, these default implementations are appropriate for your effect, and you simply delegate these functions to the generic effect component. In the rare instances when you need to provide your own implementations of one of these basic functions, you can override the generic version and provide your own implementation.

By delegating many of the functions to the generic effect, you not only decrease the number of functions you must implement, you also produce a smaller effect component, because common code is stored only once, in the generic effect.

The framework code provided in the dimmer effect sample (The Dimmer Effect), shows how to delegate interface functions to the generic effect component.

Your component must provide implementations for these functions:

Term

Definition

Open

Opens a connection between the client software and your component.

Close

Closes the connection between the client and your component.

Version

Returns the version number of your component.

EffectSetup

Called once before a sequence of frames are rendered. This gives your effect the chance to set up variables that will alter their value during the execution of a sequence of frames.

EffectBegin

Called once before a frame is rendered. Your component can safely perform operations that move memory when this function is called.

EffectRenderFrame

Called to render a frame. Because this function can be called asynchronously, it is not safe to perform operations that may move memory during this call.

EffectCancel

Cancels the rendering of a frame. If your component supports asynchronous operation, this function can be called while a frame is being rendered.

GetParameterListHandle

Returns a parameter description atom container, as described in the section "Supplying Parameter Description Information" [link s Creating New Video Effects].

GetCodecInfo

Returns information to the codec manager about the capabilities of your component.

EffectGetSpeed

Returns the approximate number of frames per second that your effect is capable of transforming.

These functions can be categorized into four groups. The Open and Close functions deal with maintaining a connection between your component and client software. In most cases, you can implementation these functions using the sample code provided by Apple without modification.

The Version, GetParameterListHandle, GetCodecInfo and EffectGetSpeed functions return information about your component. The most important of these functions is GetParameterListHandle, which returns a description of the parameters that your effect can take. See Supplying Parameter Description Information for more details of this what this function should do.

The EffectSetup function is called immediately before your component is required to render a sequence of frames. On entry, the function contains a description of the sequence that is about to be rendered. Most importantly, it describes the bit depth and pixel format of the sources that your component has to deal with. Your Setup function can then verify that your component can handle these formats. If it cannot, EffectSetup should return the “closest” bit depth and pixel format combination that it can handle, and QuickTime will generate versions of the sources and destination in the requested format. This ensures that your effect component is given source and destination buffers in a format that it understands. See The EffectRenderFrame Function for more details.

The most significant function group contains the EffectBegin, EffectRenderFrame, and EffectCancel functions. These functions contain the implementation of your effect algorithm. In most cases, you can implement the EffectCancel function simply by using the sample code provided by Apple. The implementation of the EffectBegin and RenderFrame functions is covered in Implementing the EffectBegin and EffectRenderFrame Functions.

Full details of the interface functions your component must supply are given in Component-Defined Functions.

Supplying Parameter Description Information

Your effect component must supply information that describes the parameters that your effect takes. This information is used to create an appropriate user interface for setting the parameters to your effect. The parameter description lists your effect’s parameters and their data types and indicates the appropriate selection interface for each parameter, such as a slider or a pull-down list, as well as information such as the minimum, maximum, and default values for each parameter. Each parameter is described using a specific format, which is shown in The Parameter Description Format.

Your effect component returns its parameter description information through the GetParameterListHandle function. The easiest way to provide this information back to the client software is to add an 'atms' resource to your component. The 'atms' resource contains the parameter descriptions in the required format. You can then retrieve the resource by calling the GetComponentResource function, returning it to the client through your implementation of GetParameterListHandle, as shown in Listing 4-1.

Listing 4-1  Implementing the GetParameterListHandle function using GetComponentResource

pascal ComponentResult GetParameterListHandle(EffectGlobals *glob,
                                                Handle *theHandle) 
{
    OSErr   err = noErr;
    err = GetComponentResource((Component) glob->self,
                        OSTypeConst('atms'),
                        kEffectatmsRes,
                        theHandle);
    return err;
}

By implementing the GetParameterListHandle function in this way, you can simplify the process of packaging the necessary information in the proper format.

Implementing the EffectBegin and EffectRenderFrame Functions

The core of implementing an effect component is implementing the EffectBegin and EffectRenderFrame functions. Together, these functions handle the rendering of a single frame of the effect.

The EffectBegin function is called immediately before each frame is to be rendered. It is guaranteed that this function is never called from an interrupt, so it is safe to perform actions that could move memory within this function. In general, the EffectBegin function should set up the internal state of your component so it has all the information it needs to render a single frame.

The EffectRenderFrame function is called to actually render the frame. This can be called at interrupt time, so it is not safe to move or allocate memory in this function. You should also take care not to call functions that would do so. Your EffectRenderFrame function should actually render a single frame of your effect.

The EffectBegin function

The main tasks that the EffectBegin function should perform are:

  • Ensure that the effect component has valid references to the current sources. If the component does not have a reference to the sources, or the sources have changed since the last call to EffectBegin, they must be updated.

  • Ensure that the component has a valid reference to the current destination. If the component does not have a reference to the destination, or the destination has changed since the last call to EffectBegin, it must be updated.

  • Ensure that the component has the current parameter values. If the source or destination has changed, or the component does not currently have values for the effect parameters, these parameter values are read.

  • If any of the parameter values are tweened, tweening is performed to determine the actual value for those parameters.

Checking Source and Destination References

The following code checks to see if the destination has changed since the last call to the EffectBegin function:

if (p->conditionFlags & (codecConditionNewClut+
                    codecConditionFirstFrame+codecConditionNewDepth+
    codecConditionNewDestination+codecConditionNewTransform))

If this evaluates to true, the destination has changed. This expression checks a series of flags that are passed to the EffectBegin function in the conditionsFlags field of the decompressParams parameter. When the destination is changed, QuickTime sets these flags to alert the effect component to update its internal state.

The most important information that you need to store about the new destination is its base address and its rowBytes value. These values allow you to draw onto the destination surface.

Listing 4-2 shows an example function that stores information in the effect component’s global data structure about the destination PixMap passed to the function.

Listing 4-2  Storing information about a new destination frame

static long BlitterSetDest(BlitGlobals*glob,    // input: our globals
    PixMap  *dstPixMap,     // input: pixels we will draw into
    Rect    *dstRect)       // input: area of pixels we will draw into
{
    OSErr   result = noErr;
    long    offsetH,offsetV;
    char    *baseAddr;
    // Calculate the based address according to the format of the
    // destination PixMap
    offsetH = (dstRect->left - dstPixMap->bounds.left);
    if (dstPixMap->pixelSize == 16)
    {
        offsetH <<= 1;          // 1 pixel = 2 bytes
    }
    else
    {
        if (dstPixMap->pixelSize == 32)
        {
            offsetH <<= 2;      // 1 pixel = 4 bytes
        }
        else
        {
            result = -1;        // this is a data format we can't handle
        }
    }
    offsetV = (dstRect->top - dstPixMap->bounds.top)
                * dstPixMap->rowBytes;
    baseAddr = dstPixMap->baseAddr + offsetH + offsetV;
    glob->dstBaseAddr = baseAddr;
    glob->dstRowBytes = dstPixMap->rowBytes;
    return result;
} // BlitterSetDest

The process for checking for new sources is broadly similar. The CodecDecompressParams data structure passed into the EffectBegin function has a field called majorSourceChangeSeed. This contains a seed number generated from the characteristics of the set of sources for the effect. If the sources change, the majorSourceChangeSeed value will also change, so the effect can store the current value in its global data structure and compare it to the current value. If they are different, the effect knows its sources have changed.

When the effect detects that one or more of its sources have changed, it must iterate through all its sources and reload information about them.

Listing 4-3 shows example code that performs these operations. Listing 4-4 shows the BlitterSetSource function that is called by this example code. The BlitterSetSource function is analogous to the BlitterSetDest function shown in Listing 4-2.

Listing 4-3  Checking for source changes

// Check to see if one or more sources have changed
if (p->majorSourceChangeSeed != glob->majorSourceChangeSeed)
{
    // grab start of input chain for this effect
    source = effect->source;
    // we can play with up to kMaxSources sources, so go get them
    while (source != nil && numSources < kMaxSources)
    {
        // now give that source to our blitter
        err = BlitterSetSource(glob, numSources, source);
        if (err != noErr)
            goto bail;
        source = source->next;
        ++numSources;
    }
}

Listing 4-4  Storing information about a new source frame

static long BlitterSetSource(BlitGlobals*glob,      // input: our globals
    long sourceNumber,                  // input: source index to set
    CDSequenceDataSourcePtr source)     // input: source value
{
    OSErr   err = noErr;
    if (sourceNumber >= kMaxSources)
    {
        // too many sources for us to handle
        return noErr;
    }
    else
    {
        // a source we can handle, save it away
        err = RequestImageFormat(source, glob->width, glob->height,
                                    glob->dstPixelFormat); 
        if (err == noErr)
        {
            glob->sources[sourceNumber].src = source;
        }
        else
        {
            glob->sources[sourceNumber].src = nil;
        }
    }
    return (err);
} // BlitterSetSource

Reading Parameter Values

Listing 4-5 shows how to read the value of a non-tweened parameter. The QTFindChildByID function is used to retrieve the atom containing the parameter value. The parameter value is then copied from the atom using the function QTCopyAtomDataToPtr. If the value is successfully copied, it is endian-flipped to ensure it is in native-endian format (parameter values are always stored in big-endian format). If the copy failed, a default value is provided.

The value retrieved from the parameter is stored in the component’s global data structure (called, in this example, global -> blitter ). This allows the value to be used by other functions, notably the component’s EffectRenderFrame function.

Listing 4-5  Reading a parameter value

{
    Ptr             data = p->data;
    QTAtom          atom;
    QTAtomID        atomID = 1;
    long            actSize;
    // Find the 'sden' atom
    atom = QTFindChildByID((QTAtomContainer) &data,
                        kParentAtomIsContainer,
                        OSTypeConst('sden'), // The name of the parameter
                        atomID,              // The ID of the parameter 
                        nil);
    // Copy the parameter value from the atom
    if (QTCopyAtomDataToPtr((QTAtomContainer) &data,
                        atom,
                        false,
                        sizeof(long),
                        &((glob->blitter).scratchDensity),
                        &actSize)!=noErr)
    {
        // If the copy failed, use a default value for this parameter
        ((glob->blitter).scratchDensity) = 1;
    }
    else
    {
        // Otherwise, the copy succeeded, so endian flip and store the
        // parameter value
        ((glob->blitter).scratchDensity) = EndianS32_BtoN(((glob->blitter).scratchDensity));
    }
}

If the parameter value can contain a tweened value, you can use code similar to that shown in Listing 4-6 to retrieve the parameter value. The functions InitializeTweenGlobals and CreateTweenRecord are utility functions that Apple provides as part of the dimmer effect sample framework (see The Sample Effect Component).

Listing 4-6  Reading a tweened parameter value

{
    Ptr                 data = p->data;
    OSErr               err;
    long                index = 1;
    err = InitializeTweenGlobals(&glob->tweenGlobals, p);
    if (err!=noErr)
        goto bail;
    // Make our tweener, return if we already have it
    err = CreateTweenRecord(&glob->tweenGlobals,
                    &glob->percentage,
                    OSTypeConst('pcnt'),    // The name of the parameter
                    1,                      // The ID of the parameter 
                    sizeof(Fixed),
                    kTweenTypeFixed,
                    (void*) 0,
                    (void*) fixed1,
                    effect->frameTime.virtualDuration);
    if (err!=noErr)
        goto bail;
    glob->initialized = true;
}

Tweening Parameter Values

If you have specified that one or more of your parameter’s values can be tweened, you need to implement code to perform the tweening in the EffectBegin function.

Listing 4-7 shows an example of tweening a parameter value. The current frame time is retrieved and subtracted from the effect’s virtualStartTime. This calculates how far through the execution of the current effect sequence we are, expressed as a percentage.

With this information, the code then calls QTDoTween to interpolate the parameter value, leaving the resulting value in glob -> comp1Tween.tweenData.

Listing 4-7  Tweening parameter values

wide    percentage;
// Find out how far through the effect we are
percentage = effect->frameTime.value;
CompSub(&effect->frameTime.virtualStartTime, &percentage);
// Tween our parameters and get the current value for this frame, prepare
// to render it when the EffectRenderFrame happens
{
    Fixed   thePercentage;
    if (glob->percentage.tween)
        QTDoTween(glob->percentage.tween, percentage.lo,
                    glob->percentage.tweenData, nil, nil, nil);
    thePercentage = **(Fixed**) (glob->percentage.tweenData);
    // If we are before the half-way point of this transition, we should
    // be fading the first source to black
    if (thePercentage < fixed1/2)
    {
        (glob->blitter).direction = 1;
        (glob->blitter).dimValue = FixedToInt(FixMul(IntToFixed(512), thePercentage));
    }
    // Otherwise, we are fading up onto the new source
    else
    {
        (glob->blitter).direction = 0;
        (glob->blitter).dimValue = FixedToInt(FixMul(IntToFixed(512),
                                                thePercentage)) - 255; 
    }
}

The EffectRenderFrame Function

The EffectRenderFrame function is called to actually render a single frame of your effect. This is where you transform the sources of your effect into the destination frame, using the algorithm that implements your effect.

This is also where you have to handle multiple bit depth and pixel format combinations.

Internally, QuickTime stores bitmaps in a wide variety of formats. The system can handle images in a number of bit depths and with many different pixel formats. Effect components must have some ability to handle source and destination frames that are at any of the bit depths and in any of the pixel formats that QuickTime supports.

Obviously, providing a separate implementation of your effect algorithm for every combination of bit depth and pixel format could be an enormous task. Fortunately, QuickTime provides mechanisms for you to limit the number of formats you have to explicitly support.

When your effect component’s EffectSetup function is called, it is passed information about the bit depth and pixel formats that the source frames are in. Your component should examine the formats and react in one of two ways:

  • If the format is one of those which your effect does support, the EffectSetup function does nothing.

  • If the format is not supported by your effect, EffectSetup returns the nearest format that is supported.

In the second case, where you do not directly support the format, QuickTime automatically creates buffers in the format returned by EffectSetup. The source frames are written into the buffer before EffectRenderFrame is called, so that source data is always available in a supported format. The destination frame is also buffered, and QuickTime automatically transforms the image into the required format for you.

This way, you only need to support a limited number of image formats, and QuickTime will ensure that EffectRenderFrame isn’t called with data in any other format.

Handling Multiple Formats

Although you can write separate versions of your effect algorithm for each combination of bit depth and pixel format, Apple recommends that you implement your effect algorithm once for each bit depth. You should then use the Apple-supplied blit macros to automatically generate versions of these implementations for each supported pixel format. This significantly reduces the number of separate implementations you have to maintain, and allows easy support of multiple pixel formats.

The blit macros are contained in the file BltMacros.h, which is included with the sample effect framework code.

To use the blit macros in your effect component, you must store each bit depth implementation of your effect algorithm in a separate file. These files are then included into the effect component’s main source code file multiple times, once per pixel format supported. Each inclusion is surrounded by #define statements that define the pixel format version to be generated.

Each file uses a #include statement to include BltMacros.h, and all operations that read pixels from a source buffer or write pixels to the destination buffer are performed using appropriate macros.

The macros are automatically converted to the correct operations for the pixel format when the file is included into the main source code. This generates a version of the algorithm for each pixel format.

Finally, code is put into place in the effect component’s EffectRenderFrame function, which calls the appropriate generated algorithm according to the current bit depth and pixel format of the source buffers.

Implementing a Bit-depth Specific Version of Your Algorithm

Listing 4-8 shows an example implementation of an effect algorithm. The code uses the blit macros to read pixels from the source frame and write them to the destination frame. This example shows a filter that changes a single source. It also shows how to read and alter a single pixel at a time; other effects may handle multiple pixels at a time for efficiency.

The sample shows the following operations for each pixel of the source frame:

  • Retrieving the next pixel from the source, using the Get16 (which reads a 16-bit pixel from a memory address) and cnv16SPFto16RG (which converts a 16-bit pixel in the current pixel format to the standardized 16-bit ARGB format) macros to handle pixel format conversion;

  • Decomposing the pixel into alpha, red, green and blue components;

  • Reassembling the alpha, red, green and blue components into a standardized ARGB pixel value;

  • Writing the pixel value to the destination buffer, using the cnv16RGto16DPF (which converts the 16-bit standardized format pixel back into the current buffer’s 16-bit pixel format) and Set16 (which writes a 16-bit pixel to a memory address) macros to handle pixel format conversion.

The actual effect implementation, which would alter the alpha, red, green and blue values of each pixel according to the effect specification, is not shown in this example code.

Listing 4-8  A sample effect algorithm for 16-bit frames

#include <BltMacros.h>
void EffectFilter16(BlitGlobals *glob);
void EffectFilter16(BlitGlobals *glob)
{
    long    height = glob->height;      // Local copy of the height of
                                        // the buffers 
    UInt16  *srcA = glob->sources[0].srcBaseAddr;   // Local pointer to
                                                // the first source image 
    UInt16  *dst = glob->dstBaseAddr;   // Local pointer to the
                                        // destination 
    long    srcABump;
    long    dstBump;
    
    // Work out the source and destination "bumps". The rowBytes value
    // gives you the number of bytes in each scanline of an image. This
    // is not necessarily the same as the number of pixels in a scanline
    // multiplied by the number of bytes each pixel occupies. When
    // we copy pixels from source to destination, via our effect
    // algorithm, we need to account for this discrepancy. The following
    // lines lines pre-calculate the differences.
    srcABump = glob->sources[0].srcRowBytes - (glob->width * 2);
    dstBump = glob->dstRowBytes - (glob->width * 2);
    // Now, for every scanline in the source image we are dealing with...
    while (height--)
    {
        long    width = glob->width;
        // ...iterate through every pixel in that scanline
        while (width--)
        {
            UInt16      thePixelValue;
            // Retrieve the next pixel value
            thePixelValue = Get16(srcA);
            srcA++;
            // Call to blit macros to ensure the pixel format is
            // converted appropriately
            cnv16SPFto16RG(thePixelValue);
            // Get the alpha, red, green and blue values of the pixel
            alpha = 0x8000 & thePixelValue;
            red  = (thePixelValue & 0x7C00) >> 10;
            green = (thePixelValue & 0x03E0) >> 5;
            blue = (thePixelValue & 0x001F) >> 0;
            // IMPLEMENT YOUR EFFECT ALGORITHM HERE ON EACH PIXEL
            // Re-assemble the A, R, G and B values into a 16-bit
            // destination pixel
            thePixelValue = alpha | (red << 10) | (green << 5)
                                  | (blue << 0));  
            // Set the destination pixel,first passing it through the
            // appropriate blit macro to
            // ensure the correct pixel format conversion is performed
            cnv16RGto16DPF(thePixelValue);
            Set16(dst, thePixelValue);
            dst++;
        }
        // Bump the source and destination pointers we are using, to
        // avoid problems when moving from one scanline to the next
        srcA = (void *) (((Ptr) srcA) + srcABump);
        dst = (void *) (((Ptr) dst) + dstBump);
    }
}

Including the Bit-depth Implementations in Your Effect Code

Once you have produced separate implementations of your effect algorithm for each bit depth you support, you need to include these in your main effect source code file. Each bit depth implementation is included once for every pixel format you support.

Listing 4-9 shows statements to include the 16-bit implementation of the effect into the main effect source code file. The implementation is included three times, for the following pixel formats:

  • Big-endian 555 RGB

  • Little-endian 555 RGB

  • Little-endian 565 RGB

The result of the code in Listing 4-9 is that your effect source code contains three separate versions of the effect algorithm for handling 16-bit sources. These are named EffectFilter16BE555, EffectFilter16LE555, and EffectFilter16LE565, respectively.

Listing 4-9  Including the 16-bit implementation into the main effect source code

// 16-bit, Big Endian 555 pixel format
#define EffectFilter16 EffectFilter16BE555
#define srcIs16BE555 1
#define dstIs16BE555 1
#include "EffectFilter16.c"
#undef EffectFilter16
#undef srcIs16BE555
#undef dstIs16BE555
// 16-bit, Little Endian, 555 pixel format
#define EffectFilter16 EffectFilter16LE555
#define srcIs16LE555 1
#define dstIs16LE555 1
#include "EffectFilter16.c"
#undef EffectFilter16
#undef srcIs16LE555
#undef dstIs16LE555
// 16-bit, Little Endian, 565 pixel format
#define EffectFilter16 EffectFilter16LE565
#define srcIs16LE565 1
#define dstIs16LE565 1
#include "EffectFilter16.c"
#undef EffectFilter16
#undef srcIs16LE565
#undef dstIs16LE565

Calling the Effect Implementations from EffectRenderFrame

Finally, you must provide code inside your EffectRenderFrame function to call the appropriate implementation of your effect algorithm, depending on the pixel format and bit depth of the source frames you are dealing with. Listing 4-10 shows how to do this for the 16-bit pixel formats.

Listing 4-10  Calling pixel format specific versions of the 16-bit effect implementation

switch (glob->dstPixelFormat)
{
    case k16BE555PixelFormat:
            EffectFilter16BE555(glob);
            break;
    case k16LE565PixelFormat:
            EffectFilter16LE565(glob);
            break;
    case k16LE555PixelFormat:
            EffectFilter16LE555(glob);
            break;
}

The code to handle the 32-bit pixel formats is an easy extension of the code shown in this section, and can be found in the sample effect component included in the QuickTime SDK and described in detail in the next section.

The Sample Effect Component

This section introduces you to the sample effect component supplied as part of the QuickTime SDK. It takes you through the parts of the code that you will need to change in order to implement your own effect component.

The Dimmer Effect

The sample effect described in this section is a dimmer effect. This simple effect fades the first source to black, then fades up to show the second source. The source code for this effect is provided as a CodeWarrior Pro project as part of the QuickTime SDK.

The Standard Effect Framework

Much of the code required to implement an effect component is the same for all components. Apple has provided a framework of code that you can adapt to create your own effect components. In most cases, you only have to change limited portions of the framework code to create your new component.

Structure of the Framework

The effect framework is one approach to writing effects components. It has been designed to provide most of the basic code required to implement an effect component, leaving the implementation of the effect algorithm to you. It should be possible to write most effects using the framework, though you may need to adapt the framework code for more complex effect components.

The framework implements the required component functions for an effect. The main body of the framework is the Effect.c file, which contains the source code for the framework and an implementation of the dimmer effect. This file is made up of four main parts:

  • The global data structures used by the framework. You will need to update some of the data structures to reflect the capabilities of the effect you are implementing.

  • The dispatcher is the entry point to your component. Because all effects components have the same set of component functions, you should not need to alter the dispatcher.

  • The internal functions are the set of functions that actually execute your effect. This is where most of your own code will be added.

  • The component functions are the standard functions called by the dispatcher. These functions call the internal functions to actually execute the effect. For most effects, you won’t need to change much code in this section.

Naming Conventions

All the function and data structure names in the framework are arbitrary. The names have been chosen to reflect their purpose, but you are free to change the names, as long as they remain internally consistent.

If you choose to change the names of the component functions, you will have to change the CALLCOMPONENT_BASENAME #define in Effect.c. This defines the root of the name used for component functions. For example, if CALLCOMPONENT_BASENAME is set to SlideEffect, then the Open component function must be called SlideEffectOpen, the Close component function must be called SlideEffectClose, and so forth.

Apple recommends that you do not change the names used in the framework.

Writing an Effect Component Using the Framework

The effect component framework is provided for you to simplify the development of QuickTime video effects components.

The QuickTime SDK includes the folder DimmerEffect, which contains the framework, complete with associated resources and makefiles. See the ReadMe file in the DimmerEffect folder for full installation and use instructions.

To adapt the dimmer framework to create your own component, search through the source code file Effect.c for the comments CHANGE. These comments mark the sections of the source code you will need to change to write your own effect.

The following sections take you through the specific changes you need to make to the framework. All the changes except the last, which implements the actual effect algorithm, are made to the Effect.c file.

Synchronous vs. Asynchronous Processing

The first change you may need to make is to the following #define :

#define kMaxAsyncFrames 0

This value defines the number of frames that can be queued for asynchronous rendering by this effect. If your effect declares that is can handle more than 0 asynchronous frames, frames may be queued for rendering. If you wish to render synchronously, set kMaxAsyncFrames to 0; otherwise set it to the number of frames that can be held in the queue.

Defining the Number of Sources

Most effects require one or more sources to operate on, though some effects (such as Apple’s fire effect) operate without any sources. The dimmer effect uses two sources: the first is the source to fade to black, the second is the source to fade up on. Most effects transition between two sources. Sometimes, effects control the transition between two scenes by blending in one or more other sources, in which case the effect may require three or more sources. You may also want to implement a filter effect that has only a single source and produces a transformed version of that source.

You set the value of kMaxSources to the maximum number of sources required by the effect. Effects that can take more than one source should be prepared to handle the case when fewer than the maximum number of sources are actually provided. For example, if your effect expects two sources to transition between and a third source to use as a mask, your code must handle the case where only the two transition sources are provided. In this case you should use a default mask instead of a third source.

Adding to the Global Data Structures

The framework defines two global data structures: BlitGlobals and EffectGlobals. The BlitGlobals structure holds information related to drawing a single frame of the effect, while the EffectGlobals holds data for the entire effect as it is executed. These data structures are global to an instance of the effect component. That is, if you have multiple instances of the component opened, each instance gets its own copy of both data structures.

You can add fields to the BlitGlobals data structure to hold information specific to your effect. A set of standard fields are already defined, which hold information used by the framework. You can add your own effect-specific fields between the CHANGE and END CHANGE comments.

The example defines two fields, dimValue and direction. These hold the current dim value for the effect and a flag indicating whether it is fading down or up, respectively. Because the dimmer fades the first scene down to black then fades up on the second scene, it also needs to store the dim value between individual frames. This value is stored in the dimValue field of BlitGlobals.

You can also add fields to the EffectGlobals structure. Generally, you will read the values for the parameters to your effect in these fields so that they can be referenced while the effect executes.

Preflighting the Blitter

The internal function BlitterPreflight is called from EffectSetup before the first frame of the effect is rendered. This function’s main task is to validate the bit depth that the effect is being requested to support.

The bit depth that the effect is being asked to operate at is passed in the depth parameter to BlitterPreflight. The function should return in the same parameter the bit depth at which it wants to operate.

For example, the dimmer effect can operate on 16-bit or 32-bit sources. If either of these values is passed in, it simply returns depth unaltered. If any other bit depth is requested, it sets depth to 16, the default bit depth for this effect.

Your effect should validate the bit depth passed in a similar way. Apple recommends that your effect support at least 16- and 32-bit depths.

When you set the depth parameter to a different value than it was on entry to BlitterPreflight, QuickTime creates an offscreen buffer for the sources and destination of the effect. All data is passed through these offscreen buffers, to ensure that your effect only sees data in a format it can handle.

Setting the Destination

The BlitterSetDest function is called from EffectBegin and is passed the effect’s destination, in the form of a PixMap. The BlitterSetDest function should calculate the base address and rowBytes values for the destination and store these in the BlitGlobals data structure for future reference.

You need to make changes to this function only if your effect supports destinations in bit depths other than 16-bit and 32-bit.

The BlitterRenderFrame function

This function calls the functions that implement your effect algorithm. The function names to be called are those generated by the blit macros.

The example code supports the three most common pixel formats in 16-bit and 32-bit. If your effect needs to support other bit depths or pixel formats, you need to update the switch statement in this function so that the appropriate drawing functions are called.

The EffectsFrameClose function

This function is called when the client software has finished using your component. At this time, your component should dispose of any memory it allocated. In particular, you should call DisposeTweenRecord for each tween record you allocated and then call DisposeTweenGlobals.

Reading the Effect Parameters

The parameters of the effect are read in the EffectsFrameEffectBegin function. Your effect should read its parameter values in the section between the CHANGE and END CHANGE comments, reading either non-tweened or (more frequently) tweened values. Example code for both these cases is given in Reading Parameter Values.

Once you have read in the parameter values, you need to tween those parameters that contain tween records. This code should be placed between the second pair of CHANGE and END CHANGE comments. Again, example code to do this is supplied, see Tweening Parameter Values.

Implementing your Effect

The last stage in adapting the framework is to implement your effect algorithm. You need to provide one implementation per bit depth that your effect explicitly supports, and each implementation must be placed in a separate file. These files are named EffectFilter16.c, EffectFilter32.c, and so forth.

The dimmer effect code provides an example of the pixel manipulations that an effect will typically perform, and shows how to use the blit macros to support multiple pixel formats at a given bit depth.

Clearly, the details of these routines are entirely dependent on the effect being implemented.

Adding an 'atms' Resource to your Component

The 'atms' resource for your effect contains two sets of information. The first set contains the effect information that is used to construct the standard parameters dialog box. This includes items such as the name of your effect and optional copyright information.

The second set contains the parameter information, which is a description of each parameter that your effect takes. If your effect does not take parameters, there is no information in this set.

The structure of an 'atms' resource is as follows:

resource 'atms' (kEffectatmsRes) {
   7,
 {
    // The resource body goes here
 };
};

The header for this resource contains two items: the resource ID, and the number of root level atoms the resource contains.

The first line contains the ID of the 'atms' resource. In this example, the identifier that is used (kEffectatmsRes ) is also used in the call to GetComponentResource in Listing 4-1. This ensures that the right 'atms' resource is read by QuickTime.

The second line contains the number of root atoms in the resource. Each 'atms' resource contains a number of atoms. The number in the second line must contain a count of the number of first-level atoms in the resource.

The body of the 'atms' resource consists of a number of atom declarations. Each declaration has a header that contains

Each header is followed by the atom’s data, which is one or more typed values, such as a string or a long, or a set of child atoms.

Listing 4-11 shows an example atom that contains a single typed value as its data. Note that the value is a type followed by the data itself. The number of children of the atom is declared as noChildren because the atom contains a typed value.

Listing 4-11  An example 'atms' atom declaration

kParameterTitleName, kParameterTitleID, noChildren,
{
    string { "Dimmer2 Effect Parameters" };
};

The Standard Information in an 'atms' Resource

The standard information stored in an 'atms' resource is made up of three required atoms and five optional atoms.

The three required atoms are

Term

Definition

kParameterTitleName

a string used as the title of the standard parameters dialog box. An example of a kParameterTitleName atom declaration is shown in Listing 4-11.

kParameterWhatName

an OSType containing the name of this effect component.

kParameterSourceCountName

a long integer containing the maximum number of sources that the effect can take.

The five optional atoms are

Term

Definition

kParameterAlternateCodecName

an OSType containing the unique identifier of another effect component that should be used to replace this effect if this effect cannot be used.

kParameterInfoLongName

a string containing the long version of the name of the effect.

kParameterInfoCopyright

a string containing a copyright statement for the effect.

kParameterInfoDescription

a string containing a brief description of what the effect does.

kParameterInfoWindowTitle

a string containing the title of the window that displays the information contained in the optional atoms.

The Parameter Information in an 'atms' Resource

For each parameter of the effect, your 'atms' resource must contain a set of atoms in the 'atms' resource that describes that parameter. This description includes the name of the parameter, the type and range of values it can take, and hints on appropriate user interface element for setting this parameter.

A complete description of the information you need to provide for each parameter can be found in The Parameter Description Format.

For a basic parameter, there are five atoms that you should supply:

Term

Definition

kParameterAtomTypeAndID

contains the type and ID of the parameter (an OSType and a long integer, respectively), the atom flags for the parameter, and a string containing the name of the parameter.

kParameterDataType

a long integer containing the type of the parameter, such as kParameterTypeDataFixed.

kParameterDataRange

a set of typed values describing the range of values the parameter can take. The number and type of values you supply depend on the value of the kParameterDataType atom.

kParameterDataBehavior

two long integer values containing the behavior type, such as kParameterItemControl for a slider, and any flags, such as kAtomNotInterpolated for a parameter that cannot be tweened.

kParameterDataDefaultItem

the default value of the parameter. Again, the type of this value will depend on the type of the kParameterDataType atom. This atom must be a correctly-formatted parameter atom that can be passed back to your component by client software without modification.

An example of a basic parameter description is shown in Listing 4-12.

Listing 4-12  An example set of parameter description atoms

kParameterAtomTypeAndID, 101, noChildren,
{
    OSType { "sden" };      // atomType--the name of this parameter
    long { "1" };           // atomID--this is atom number 1
    kAtomNotInterpolated; // atomFlags--this parameter cannot be tweened
    string { "Scratch Density" }; // atomName--the name of the parameter
            // as it will appear in the standard parameters dialog box
};
kParameterDataType, 101, noChildren,
{
    kParameterTypeDataLong;     // dataType--this parameter contains a
                                // long value 
};
kParameterDataRange, 101, noChildren,
{
    long { "0" };       // minimumValue
    long { "25" };      // maximumValue
    long { "1" };       // scaleFactor--no scaling is applied to this
                        // parameter
    long { "0" };       // precision--0 indicates that this parameter is
                        // not a floating-point value
};
kParameterDataBehavior, 101, noChildren,
{
    kParameterItemControl;  // behaviorType--this parameters should be
                            // represented by a slider
    long { "0" };           // behaviorFlags - no flags
};
kParameterDataDefaultItem, 101, noChildren,
{
    long { "5" };           // the default value of the parameter
};

The Parameter Description Format

The parameter description data structure is a QTAtomContainer structure that, when filled out by the ImageCodecGetParameterList call, contains a set of QTAtoms for each parameter of the effect. These atoms define the base type of the parameter, the legal range of values that can be stored in it, and hints for displaying a user interface to set values for the parameter.

The atoms in a parameter description are described in the following sections. The order in which the atoms are stored in the QTAtomContainer structure is important. Applications should present parameters to the user in the same order that they are contained in the parameter description.

Each of the atom types in a parameter description has a name; you will find constants for these in ImageCodec.h. You should use these constants when retrieving atoms from the data structure. The data stored in the atoms of the parameter description is structured, and the struct definitions are given in the atom descriptions below.

Many of the atoms must be present to create a valid parameter description. Some are optional, as noted.

Parameter Atom Type and ID

This atom contains information about the type and ID of the parameter. The data is contained in the following structure:

typedef struct
{
    QTAtomType  atomType;
    QTAtomID    atomID;
    long        atomFlags;
    Str255      atomName;
} ParameterAtomTypeAndID;

Term

Definition

atomType

This field contains either a unique identifier for the parameter or the value kNoAtom. The unique identifier is a four character OSType that you use to retrieve the parameter's value. If this field contains kNoAtom, the "parameter" being described is actually a group description; groups are described in "Special Description Types" [link s Creating New Video Effects].

atomID

This field contains the ID of this parameter.

atomFlags

This field can contain one of the predefined values: kAtomNoFlags, kAtomNotInterpolated, or kAtomInterpolateIsOptional. If it contains kAtomNotInterpolated, the user interface allows users to enter only a single value for this parameter, and this value remains constant while the effect is playing. If it contains kAtomNoFlags, the user interface allows users to enter a set of values for the parameter. This set of values are stored in a tween atom, and the value of the parameter is interpolated between these values during effect playback. If it contains kAtomInterpolateIsOptional, the user interface defaults to allowing a single value for the parameter. If the user interface supports an "advanced" mode of operation, then a tween value can be entered for this parameter when the user interface is in this mode. An example of an advanced mode is the standard parameters dialog box: if you hold down the option key while selecting an effect, any parameters that have the kAtomInterpolateIsOptional flag set will allow a tween value to be entered.

atomName

The name of this parameter. This string value is used as the name of the control displayed in the standard parameter dialog box to enter a value for this parameter. This atom is required.

Special Description Types

If the parameter atom type and ID atom of a parameter description contains the constant kNoAtom, this indicates that the value being described is not a parameter to the effect but is a group. Besides groups, two further special cases are covered in the following sections: enumeration lists and source counts.

Groups

It is sometimes useful to treat a set of parameters as a group. For example, you might want to label a group of parameters that jointly control something, align a group of controls, or enclose a set of parameters in a box. The grouping mechanism allows you to specify a set of parameters and the attributes that are applied to the group.

If the parameter data type and ID atom of a description contains child atoms, rather than data, it defines a group. A group is a set of related atoms, where the relationship amongst them can be based on attributes such as:

  • layout; for example, the group is a set of text labels that should be aligned.

  • spatial; for example, the items in the group should be placed side by side to optimize dialog box layout.

  • naming; the items in the group are related controls that should be displayed under a single heading in the dialog box.

  • usage; a pair of long integers may together specify a coordinate. In this case, they can be grouped together and the group’s parameter data usage atom set to kParameterUsagePoint.

Groups can be nested within one another as needed. Groups can optionally have a name, which allows your application to place grouped parameters within a panel or tabbed group under that name.

Listing 4-13 shows an example of a group, which in this case contains a single parameter description.

Listing 4-13  An example group atom from an 'atms' resource definition.

kParameterAtomTypeAndID, 100, noChildren,
{
    OSType { "none" };      // Use 'none' as this is not a real parameter
    long { "0" };
    kAtomNoFlags;
    string { "" };
};
kParameterDataBehavior, 100, noChildren,
{
    kParameterItemGroupDivider; // Use a divider to separate this group
    kGroupNoFlags;
};
kParameterDataType, 100, 1*5,   // 1 parameter * 5 atoms to describe each
                                //parameter 
{
};
kParameterAtomTypeAndID, 3, noChildren,
{
    OSType { "pMul" };
    long { "1" };
    kAtomNotInterpolated;
    string { "Pre-multiply color" };
};
kParameterDataType, 3, noChildren,
{
    kParameterTypeDataRGBValue;
};
kParameterDataRange, 3, noChildren,
{
    short { "0" };
    short { "0" };
    short { "0" };
    short { "65535" };
    short { "65535" };
    short { "65535" };
};
kParameterDataBehavior, 3, noChildren,
{
    kParameterItemColorPicker;
    long { "0" };
};
kParameterDataDefaultItem, 3, noChildren,
{
    short { "65535" };
    short { "65535" };
    short { "65535" };
};

Enumeration Lists

When an enumerated type is required for a parameter value, a new enumeration list is placed directly into the root atom container. Enumeration lists are arrays of name-and-value pairings in the following format:

typedef struct
{
    long    value;
    Str255  name;
} EnumValuePair;
 
typedef struct
{
    long            enumCount;  // number of enumeration items to follow
    EnumValuePair   values[1];  // values and names for them
} EnumListRecord;

The type of an enumeration list atom is kParameterEnumList ('enum' ). Listing 4-14 shows an enumeration list that contains three elements.

Listing 4-14  An example enumeration list from an 'atms' resource definition

kParameterEnumList, 1, noChildren,
{
    long { "3" };           // No of elements in the enum
    long {"1"}; string { "Straight Alpha" };
    long {"2"}; string { "Pre-multiply Alpha" };
    long {"3"}; string { "Reverse Alpha" };
};

Source Count

The source count atom (kParameterSourceCountName, 'srcs' ) contains a single long integer value that defines the maximum number of sources that this effect can accept. The atom is always placed in the root atom container of the parameter description.

The source count atom is required.

Parameter Data Type

This atom defines the type of the data for this parameter. It contains data in the following structure:

typedef struct
{
    OSType  dataType;
}

Term

Definition

dataType

This field contains the type of the value that is stored in this parameter. This can be one of the following values:

Term

Definition

kParameterTypeDataText

editable text item

kParameterTypeDataLong

integer value

kParameterTypeDataEnum

enumerated lookup value

kParameterTypeDataFixed

fixed point value

kParameterTypeDataDouble

IEEE 64 bit floating point value

kParameterTypeDataBitField

bit field (Boolean) value

kParameterTypeDataRGBValue

RGBColor data

kParameterTypeDataImage

reference to an image

This atom is required.

Parameter Alternate Data Type

This atom defines a preferred data type for the parameter. If the system your application is running on does not support this preferred data type, the data type specified in the parameter data type atom will be used instead.

Use the alternate data type atom if you would prefer to use a data type that is not supported on all platforms, and use the parameter data type atom to specify a fall-back data type for systems that do not support your preferred data type.

For example, if the parameter alternate data type is kParameterTypeDataColorValue, the parameter holds a value of type CMColor on systems that have the ColorSync extension. On systems that do not have ColorSync, whatever is specified in the parameter data type (such as an RGBValue ) is used instead.

This atom’s data is stored in a ParameterAlternateDataType data structure, which in turn relies on the ParameterAlternateDataEntry data structure.

typedef struct
{
    OSType      dataType;       // The type of the data
    QTAtomType  alternateAtom;  // The atom to use for alternate data
} ParameterAlternateDataEntry;
typedef struct
{
    long                            alternateCount; 
    ParameterAlternateDataEntry     alternates[];
} ParameterAlternateDataType;

Term

Definition

dataType

This field in the ParameterAlternateDataEntry structure can take one of the following values:

Term

Definition

kParameterTypeDataColorValue

CM color data

kParameterTypeDataCubic

Cubic Beziers

kParameterTypeDataNURB

Nurbs

The parameter alternate data type atom is optional.

Parameter Data Range

The Parameter Data Range atom defines the legal range of values that the parameter can take. It also defines a scaling constant that defines how the legal range of values can be translated into a range that is more suitable for display in a user interface. For example, a value with a range of 0-255 might be scaled as 0-100 for user input.

The atom’s data is structured as a RangeRecord, defined below. The exact format of this data depends on the data type of the parameter being described.

// 'text'
typedef struct
{
    long    maxChars;   // Maximum length of the string
    long    maxLines;   // Number of editing lines (typically 1)
} StringRangeRecord;
// 'long'
typedef struct
{
    long            minValue;           // Minimum value the long can be 
    long            maxValue;           // Maximum value the long can be 
    long            scaleValue;         // Scaling constant
    long            precisionDigits;    // number of digits of precision
                                        // when editing via typing 
} LongRangeRecord;
// 'enum'
typedef struct
{
    long            enumID;         // The ID of the 'enum' atom in the
                                    // root container to search 
} EnumRangeRecord;
// 'fixd'
typedef struct
{
    Fixed           minValue;           // Minimum value the Fixed can be 
    Fixed           maxValue;           // Maximum value the Fixed can be 
    Fixed           scaleValue;         // Scaling constant
    long            precisionDigits;    // number of digits of precision
                                        // when editing via typing 
} FixedRangeRecord;
// 'doub'
typedef struct
{
    QTFloatDouble   minValue;           // Minimum value of parameter
    QTFloatDouble   maxValue;           // Maximum value of parameter
    QTFloatDouble   scaleValue;         // Scaling constant
    long            precisionDigits;    // number of digits of precision
                                        // when editing via typing 
} DoubleRangeRecord;
// 'bool'
typedef struct
{
    long            maskValue;  // value to mask on/off to set/clear the
                                // boolean 
} BooleanRangeRecord;
 
// 'rgb '
typedef struct
{
    RGBColor    minColor;       // Minimum value the RGBColor can be
    RGBColor    maxColor;       // Maximum value the RGBColor can be
} RGBRangeRecord;
// The RangeRecord data structure is the union of all of the above
typedef struct
{
    union
    {
        LongRangeRecord         longRange;
        EnumRangeRecord         enumRange;
        FixedRangeRecord        fixedRange;
        DoubleRangeRecord       doubleRange;
        StringRangeRecord       stringRange;
        BooleanRangeRecord      booleanRange;
        RGBRangeRecord          rgbRange;
    } u;
} RangeRecord;

The minValue and maxValue fields of the DoubleRangeRecord data structure can take, in addition to an actual QTFloatDouble value, the following predefined values:

  • kNoMinimumDouble; ignore the minimum value

  • kNoMaximumDouble; ignore the maximum value

  • kNoScaleDouble; don’t perform any scaling of value

The minValue and MaxValue fields of the LongRangeRecord data structure can take, in addition to an actual long integer value, the following predefined values:

  • kNoMinimumLongFixed; ignore minimum value

  • kNoMaximumLongFixed; ignore maximum value

  • kNoScaleLongFixed; don’t perform any scaling of value

  • kNoPrecision; allow as many digits as format

The Parameter Data Range atom is required, except for group descriptions.

Parameter Data Behavior

The Parameter Data Behavior atom contains user interface hints that suggest to the client application how a parameter should be displayed.

typedef struct
{
    QTAtomID    groupID;
    long        controlValue;
} ControlBehaviors;
typedef struct
{
    OSType  behaviorType;
    long    behaviorFlags;
    union
    {
        ControlBehaviorscontrols;
    } u;
} ParameterDataBehavior;

Term

Definition

behaviorType

This field contains a value that specifies a user interface for editing the parameter's value. This field should contain one of the following pre-defined values:

Term

Definition

kParameterItemEditText

the parameter should be edited using an edit text field.

kParameterItemEditLong

the parameter should be edited using an edit text field that only accepts numerical entries.

kParameterItemEditFixed

the parameter should be edited using an edit text field that accepts floating-point numerical entries.

kParameterItemPopUp

the parameter should be edited using a pop-up menu. This data behavior should only be used with parameters whose data type is kParameterTypeDataEnum; the pop-up menu is populated from the enumeration values.

kParameterItemRadioCluster

the parameter should be edited using a group of radio buttons. This data behavior should only be used with parameters whose data type is kParameterTypeDataEnum ; the radio buttons are created from the enumeration values

kParameterItemCheckBox

the parameter should be edited using a checkbox This data behavior should only be used with parameters whose data type is kParameterTypeDataBitField.

kParameterItemControl

the parameter should be edited using a standard control appropriate to the data type of the parameter. For parameters that accept a scalar value, such as a Fixed or a Long, the control used is a slider.

kParameterItemLine

a horizontal line is drawn in above the control that manipulates this parameter's value.

kParameterItemRectangle

a rectangle is drawn around the control that manipulates this parameter's value.

kParameterItemColorPicker

the parameter should be edited using a color swatch and picker.

kParameterItemGroupDivider

start of a new group of items.

kParameterItemStaticText

the parameter's name is displayed as a static text field.

kParameterItemDragImage

the parameter should be edited as an image that accepts drag and drop entry of new images.

kParameterItemDragPath

the parameter should be edited as a path display that allows the user to drag out a new path.

behaviorFlags

This field can take one or more of the following values:

Flag

Definition

kGraphicsNoFlags

no options for graphics.

kGraphicsFlagsGray

any lines or rectangles that are drawn have a grayscale appearance. If this option is not set, lines and rectangles are drawn in black.

kGroupNoFlags

no options for the group.

kGroupAlignText

the controls in the group are aligned.

kGroupSurroundBox

the controls in the group are surrounded with a box.

kGroupMatrix

display the controls in the group in a matrix, if such an arrangement is possible.

kGroupNoName

do not display the name of the group.

The behaviorFlags values allow you to optionally show or hide a group depending on the value entered into a parameter. This allows you to express simple conditionals within a standard parameters dialog box. For example, you may want a pop-up menu with a set of fixed options, and an 'Others...' option; if the user chooses 'others', a text edit field is enabled to allow users to enter their own value.

To do this, you can use the kDisableWhenLessThan flag to specify that the group containing the text control is disabled when the user chooses any value in the pop-up menu that is less than the last, 'Others...' option.

The following flags are available to control selective disabling of groups. For each of these flags, the ID of the group to be disabled is stored in the groupID field of the controls data structure. The value that is used in the comparison operation is stored in the controlValue field of the controls data structure.

Flag

Definition

kDisableWhenNotEqual

When the value chosen for this parameter is not equal to controlValue, disable the group groupID.

kDisableWhenEqual

When the value chosen for this parameter is equal to controlValue, disable the group groupID.

kDisableWhenLessThan

When the value chosen for this parameter is less than the controlValue, disable the group groupID.

kDisableWhenGreaterThan

When the value chosen for this parameter is greater than the controlValue, disable the group groupID.

The parameter data behavior atom is required.

Parameter Data Usage

The parameter data usage atom defines the intended use of the data in the parameter. This information can be used by your application to provide a more appropriate user interface for a parameter or group of parameters. For example, if your application knows that a set of four long integer values actually represent a rectangle, it can present a graphical display of the rectangle, rather than simply displaying four numeric input fields.

The data in this atom is stored in the following data structure:

typedef struct
{
    OSType  usageType;
} ParameterDataUsage;

Term

Definition

usageType

This field defines the actual use that a parameter or group of parameters. It can take one of the following values:

Term

Definition

kParameterUsagePixels

The parameters in the group contain a set of pixels.

kParameterUsageRectangle

The parameters in the group contain the top-left and bottom-right coordinates of a rectangle.

kParameterUsagePoint

The parameters in the group contain the coordinates of a point.

kParameterUsage3DPoint

The parameters in the group contain the X,Y,Z coordinates of a 3D point.

kParameterUsage3by3Matrix

The parameters in the group contain a 3x3 matrix of values.

kParameterUsageDegree

The parameter contains degrees.

kParameterUsageRadians

The parameter contains radians.

kParameterUsagePercent

The parameter contains a percentage.

kParameterUsageSeconds

The parameter contains seconds.

kParameterUsageMilliseconds

The parameter contains milliseconds.

kParameterUsageMicroseconds

The parameter contains microseconds.

The parameter data usage atom is optional.

Parameter Data Default Item

The parameter data default item atom contains the default value for the parameter. This value is stored in a QuickTime atom and can be copied directly into the parameter or into an effect description; an application does not need to understand the contents or format of the atom in order to do this.

The parameter data default item atom is required, except for group descriptions.

Tweening Parameters

An important property of effect parameters is that many can be tweened, and some must be tweened. Tweening is QuickTime’s general purpose interpolation mechanism (see QuickTime Media Types and Media Handlers Guide). This allows the value of the parameter to change as the effect executes.

For example, the slide effect built into QuickTime (see Slide) has an angle parameter. This controls the angle from which the second source will slide over the first during the execution of the effect. If this parameter contains a single value, the second source will slide over the first in a straight line from the selected angle. However, if the parameter contains two values, the angle will be interpolated between these values during the execution of the effect. This allows you to specify a curved slide effect.

In fact, any valid tween record can be specified as the parameter value, not just records containing pairs of values. The QuickTime tweening mechanism supports tween records that contain more than two values and that specify the interpolation algorithm used to produce intermediate values. However, the standard parameters dialog box allows only a pair of values to be entered, and the appropriate default interpolator is used. The standard parameter dialog box presents the user with a pair of values for parameters that must be tweened. Parameters that are optionally tweened, such as the angle for the slide effect, are set to a single value by default. In order to set an optionally-tweened parameter to a tweened value, the user must hold down the Option key when selecting the effect in the dialog box.

An application can provide its own user interface for entering multiple tween values for a parameter and choosing an appropriate tweener to perform interpolation, if required.

For more details on specifying which parameter values can contain tween values, see Parameter Atom Type and ID. For more details on supporting tweened parameters in your effect component, see Tweening Parameter Values.

Refer to The Parameter Description Format for a complete description of the possible parameter descriptions you can place in your 'atms' resource.

Slide

kSlideTransitionType ('slid')

In a slide effect, source B slides onto the screen to cover source A. The angle from which source B enters the frame is stored in a parameter, with 0 degrees being the top of the screen.

The slide effect takes a maximum of two sources and has two parameters.

Use the descriptions below to help you understand what the parameters do. To learn how to use parameter atoms, see Adding Video Effects to a QuickTime Movie.

Name

Code

QTAtom Type

Description

Percentage

'pcnt'

kParameterTypeDataFixed Always a tween

This parameter contains a tween. As the effect progresses, QuickTime renders the frame of the effect indicated by the tween's current value, as a percentage of the whole effect. For example, if the tween goes from 0 to 100, the effect renders completely; if the tween goes from 25 to 75, rendering begins 25% into the effect and terminates 75% through the effect.

Slide angle

'angl'

kParameterTypeDataFixed Can be a tween

The angle from which source B will enter the frame. This value is expressed in degrees, with 0 degrees defined as the top of the screen.

Parameter Descriptions

Each effect component supplies a parameter description data structure that describes in detail the set of parameters that the effect has.

This section describes the parameter description format in detail. You need this information if you are writing an effect component. If you are writing an effect component, you should provide a parameter description as part of an 'atms' resource (see Supplying Parameter Description Information for more details).

You may also need this information if you are writing an application that presents its own user interface for setting effect parameters. In this case, you will need to parse parameter descriptions to generate appropriate controls to set parameter values. Most applications can simply use the QTCreateStandardParameterDialog function, and do not need to parse effect parameter descriptions.

Any software that uses an effect can request its parameter description. Typically, the parameter description is then passed to QTCreateStandardParameterDialog or especially, ImageCodecCreateStandardParameterDialog. These functions use the parameter description to display a user interface that allows users to choose the values of the parameters.

Your are free to use the information in an effect’s parameter description in other ways. For example, your application can use the default value atoms to construct an effect description.

Parameter descriptions are stored in a QTAtomContainer structure, and an application retrieves an effect’s description by calling ImageCodecGetParameterList. This function takes a component instance and returns the parameter description for that component.

The code shown in Listing 4-15 opens the component specified in the variable subType. The code sets up the component description, then finds and opens the requested component. It then calls the ImageCodecGetParameterList function to fill out the parameter description for this effect.

Listing 4-15  Opening the image decompressor component

{
    // Set up a component description
    cd.componentType    = 'imdc';       // Effects are image decompressor
                                        // components 
    cd.componentSubType = subType;      // This is the name of the effect
                                        //(e.g. 'smpt') 
    cd.componentManufacturer    = 0;
    cd.componentFlags           = 0;
    cd.componentFlagsMask       = 0;
    // Find the required component. If it can't be found, generate an
    // error
    if ((theComponent = FindNextComponent(theComponent, &cd))==0)
    {
        err = paramErr;
        goto bail;
    }
    // Open the component
    gCompInstance = OpenComponent(theComponent);
    // Get the parameter description for the effect
    ImageCodecGetParameterList(gCompInstance, &parameterDescription);
}

An application can parse the returned parameter description using the standard QuickTime APIs that query QTAtomContainer data structures. This can be useful if you are writing an application that creates its own interface for users to customize effects.

This section describes the general format of the data returned in a parameter description.

Component-Defined Functions

This section defines the effect-specific functions that you may supply in your effect components. This section is only of interest to developers who are creating their own effects components; if you are writing an application that uses QuickTime video effects, you can skip this section.

The functions defined in this section are those called by the Component Manager through your component’s dispatch function (see What Effects Components Do).

These functions include

If you are using the sample effect component, you can use the default implementations of several of these functions in most circumstances.

MyEffectSetup

The Component Manager calls this function when a sequence of frames is about to be rendered.

ComponentResult MyEffectSetup (
                     EffectGlobals *glob,
                     CodecDecompressParams *decompressParams);

Term

Definition

glob

A pointer to the effect's global data structure.

decompressParams

Information about the sequence that is about to be decompressed.

This function is called immediately before a client application such as MoviePlayer calls your component to render a sequence of frames.

Your component should examine the capabilities field of the decompressParams data structure to ensure that it can meet the requirements for executing this sequence. In particular, it should check the bit depth and pixel format requirements of the sequence. If the sequence requires a bit depth and pixel format combination that your component does not support, this function should return the nearest supported combination in the decompressParams -> capabilities field. In this case, QuickTime will redirect all source and destination bitmaps through offscreen graphics worlds that have the bit depth and pixel format characteristics that you specify.

MyEffectBegin

The Component Manager calls this function to request that your component prepare to render a single frame of its effect.

ComponentResult MyEffectBegin (
                     EffectGlobals *glob,
                     CodecDecompressParams *decompressParams,
                     EffectsFrameParamsPtr effect);

Term

Definition

glob

A pointer to the effect's global data structure.

decompressParams

Information about the current sequence of frames.

effect

The parameters describing this frame.

This function is called immediately before your MyEffectRenderFrame function. Your MyEffectBegin function should ensure that the information it holds about the current source and destination buffers and the parameter values for the effect are valid. If any of these have changed since the last call to MyEffectBegin, the new values should be read from the appropriate data structures.

This function is guaranteed to be called synchronously. In particular, this means you can allocate and move memory, and can call functions that allocate or move memory.

MyEffectRenderFrame

The Component Manager calls this function to request that your component render a single frame of its effect.

ComponentResult MyEffectRenderFrame (
                     EffectGlobals *glob,
                     EffectsFrameParamsPtr effect);

Term

Definition

glob

A pointer to the effect's global data structure.

effect

The parameters describing this frame.

This function is called by a client application when your effect component needs to render a single frame of your effect. This function contains the implementation of your effect.

MyEffectCancel

The Component Manager calls this function to stop processing of the current effect.

ComponentResult MyEffectCancel (
                     EffectGlobals *glob,
                     EffectsFrameParamsPtr effect);

Term

Definition

glob

A pointer to the effect's global data structure.

effect

The parameters describing this frame.

This function is called by a client application (which may be QuickTime) to halt the rendering of the current sequence of frames before the last frame has been rendered. If your component is running synchronously, it should simply return noErr ; no further calls to your MyEffectRenderFrame function will be made for this sequence.

If your component is running asynchronously, this function should dequeue all outstanding render frame requests, then return noErr.

MyEffectGetCodecInfo

The Component Manager calls this function to request information about the component.

ComponentResult MyEffectGetCodecInfo (
                     EffectGlobals *glob,
                     CodecInfo *info);

Term

Definition

glob

A pointer to the effect's global data structure.

info

A pointer to the data structure that will contain the codec information.

This function is called by a client application (which may be QuickTime) to request information about your effect component. Your function should fill out the CodecInfo data structure passed to it. You can use the GetComponentResource function to retrieve a 'cdci' resource that stores this information if you have provided one in your component.

noErr

0

The function successfully filled out the info field.

paramErr

-50

Your function should return this value if the info parameter contains nil.

MyEffectGetParameterListHandle

The Component Manager calls this function to request a parameter description for this component.

ComponentResult MyEffectGetParameterListHandle (
                     EffectGlobals *glob,
                     Handle theHandle);

Term

Definition

glob

A pointer to the effect's global data structure.

theHandle

A pointer to a handle that will contain the parameter description of this effect.

This function is called by a client application (which may be QuickTime) to request a parameter description for your effect. This function can use the GetComponentResource function to retrieve an'atms' resource that stores this information if you have provided one in your component.

MyEffectGetSpeed

The Component Manager calls this function to request information about the rendering speed of this effect component.

long MyEffectGetSpeed (
                     EffectGlobals *glob,
                     QTAtomContainer parameters,
                     Fixed *pFPS)

Term

Definition

glob

A pointer to the effect's global data structure.

parameters

The current parameter values for this effect.

pFPS

A pointer to a Fixed value that will contain the rendering speed of this effect on exit.

This function is called by a client application (which may be QuickTime) to request information about the rendering speed of your effect. This function should return a Fixed value in FPS, which represents the rendering speed in frames-per-second of the effect.

If your effect can render in real time, it should return a value of effectIsRealtime. Otherwise, you should return an estimate of the number of frames your effect can render per second. Because rendering speeds are hardware-dependent, effect authors can choose to measure actual rendering speeds in this function. Alternatively, effect authors can choose to return a single value for all hardware configurations, estimating the value for a reference hardware platform.

Apple recommends that the values returned are rounded down to the nearest common frames-per-second value, such as 15, 24 or 30.

MyEffectValidateParameters

If your effect implements this optional function, the Component Manager calls it whenever the user changes a parameter value in the standard parameter dialog box, or attempts to dismiss the dialog.

ComponentResult MyEffectValidateParameters (
                     EffectGlobals *glob QTAtomContainer parameters,
                     QTParameterValidationOptions validationFlags,
                     StringPtr errorString);

Term

Definition

glob

A pointer to the effect's global data structure.

parameters

The current parameter values for this effect.

validationFlags

Flags that indicate whether a parameter value has changed or the user is dismissing the standard parameter dialog box.

errorString

A StringPtr that is contains an error string explaining to the user why the validation has failed.

This optional function is called by a client application (which may be QuickTime) when your effect’s standard parameter dialog box is being displayed. It can be called in two circumstances: if the user changes a parameter value in the dialog box; or if the user dismisses the dialog box by clicking OK.

The purpose of this function is to allow your effect to validate its parameters. The current parameter values are passed to the effect in parameters. If all of these values are valid, this function should return noErr. Otherwise, you should return a paramErr and put an explanatory message in the errorString parameter.