GreyscaleEffect.c

/*
    File:       GreyscaleEffect.c
    
    Description: Sample QuickTime effect component 
 
    Author:     era
 
    Copyright:  © Copyright 2002 Apple Computer, Inc. All rights reserved.
    
    Disclaimer: IMPORTANT:  This Apple software is supplied to you by Apple Computer, Inc.
                ("Apple") in consideration of your agreement to the following terms, and your
                use, installation, modification or redistribution of this Apple software
                constitutes acceptance of these terms.  If you do not agree with these terms,
                please do not use, install, modify or redistribute this Apple software.
 
                In consideration of your agreement to abide by the following terms, and subject
                to these terms, Apple grants you a personal, non-exclusive license, under AppleÕs
                copyrights in this original Apple software (the "Apple Software"), to use,
                reproduce, modify and redistribute the Apple Software, with or without
                modifications, in source and/or binary forms; provided that if you redistribute
                the Apple Software in its entirety and without modifications, you must retain
                this notice and the following text and disclaimers in all such redistributions of
                the Apple Software.  Neither the name, trademarks, service marks or logos of
                Apple Computer, Inc. may be used to endorse or promote products derived from the
                Apple Software without specific prior written permission from Apple.  Except as
                expressly stated in this notice, no other rights or licenses, express or implied,
                are granted by Apple herein, including but not limited to any patent rights that
                may be infringed by your derivative works or by other works in which the Apple
                Software may be incorporated.
 
                The Apple Software is provided by Apple on an "AS IS" basis.  APPLE MAKES NO
                WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED
                WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR
                PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND OPERATION ALONE OR IN
                COMBINATION WITH YOUR PRODUCTS.
 
                IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR
                CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
                GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
                ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, MODIFICATION AND/OR DISTRIBUTION
                OF THE APPLE SOFTWARE, HOWEVER CAUSED AND WHETHER UNDER THEORY OF CONTRACT, TORT
                (INCLUDING NEGLIGENCE), STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN
                ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
                
    Change History (most recent first):
       <1>      05/18/02    era     first file
*/
 
/*
    Greyscale Filter Effect - a sample QuickTime video effect.
    
    This effect uses a single source as input, and renders that source in
    greyscale. It also allows for a brightness value which can range from
    -100...0...100. A value of 0 signifies no change in brightness.
    
    This is a very basic sample and a good place to start if you've never looked at
    QuickTime effect code before.
    
    This sample makes use of some code from the Dimmer2 Effect Framework. While Dimmer2 is
    a recommended companion to this sample, it is more complex, using multiple sources,
    multiple pixel formats, non-Macintosh support, tweens etc.
    
    For developers who have asked for a quick and dirty single source filter sample, this one's
    for you...enjoy.
*/
 
// --------------------------------------------------------------------------------------
// INCLUDES
// --------------------------------------------------------------------------------------
 
#if (__APPLE_CC__ || __MACH__)
    #include <Carbon/Carbon.h>
    #include <QuickTime/QuickTime.h>
#elif TARGET_API_MAC_CARBON
    #include <Carbon.h>
    #include <QuickTime.h>
#else
    #include <ConditionalMacros.h>
    #include <ImageCodec.h>
    #include <QuickDraw.h>
#endif
 
#include "EffectDefinitions.h"
 
// --------------------------------------------------------------------------------------
// INTERNAL TYPEDEFS
// --------------------------------------------------------------------------------------
 
// Structure used to store information about the source
typedef struct {                        
    CDSequenceDataSourcePtr src;
    void                    *srcBaseAddr;
    long                    srcRowBytes;
} SourceRecord;
 
// This is the structure used to store information for drawing a single frame of the effect
typedef struct {                            
    SourceRecord source;                // inputs
    void         *dstBaseAddr;          // output base address
    long         dstRowBytes;           // output row bytes
    long         height;                // output height
    long         width;                 // output width
    OSType       dstPixelFormat;        // output pixel format
    long         brightValue;           // brightness value, from -100 to 100
 
} BlitGlobals;
 
// global data per instance. This holds data for the entire effect as it runs its course.
typedef struct {
    ComponentInstance   self;       // us
    ComponentInstance   target;     // top of the calling chain
    ComponentInstance   delegate;   // if we can't handle an effect, this one can
    
    BlitGlobals         blitter;    // information for drawing the data
    OSType              **wantedDestinationPixelTypeH;
    
    // parameter/source/dest seed tracking
    long                initialized;
    long                frameNumber;            
    long                virtualDuration;
    long                majorSourceChangeSeed;
} EffectGlobals;
 
// --------------------------------------------------------------------------------------
// DISPATCHER
// --------------------------------------------------------------------------------------
/************************************************************************************ 
 *  This is the main dispatcher for our codec. All calls from the codec manager
 *  will come through here, with a unique selector and corresponding parameter block.
 *
 *  This routine must be first in the code segment of the codec component.
 *
 *  We use the normal dispatcher rather than the codec dispatcher as we need to
 *  implement the extra effects routines on top of the codec ones.
 */
/************************************************************************************/
// Begin Dispatch Stuff
 
// Used by Component Dispatch Helper to call our routines
#define CALLCOMPONENT_BASENAME()        GreyscaleFilter
#define CALLCOMPONENT_GLOBALS()         EffectGlobals * storage
 
// Used by Type's .k.h to create prototypes for our routines
#define IMAGECODEC_BASENAME()           CALLCOMPONENT_BASENAME()
#define IMAGECODEC_GLOBALS()            CALLCOMPONENT_GLOBALS()
 
// Used by SubType's .k.h to create prototypes for our routines
#define IMAGECODECEFFECT_BASENAME()     CALLCOMPONENT_BASENAME()
#define IMAGECODECEFFECT_GLOBALS()      CALLCOMPONENT_GLOBALS()
 
// Other defines for Component Dispatch Helper
#define COMPONENT_DISPATCH_FILE         "EffectDispatch.h"  // describes what to dispatch
#define GET_DELEGATE_COMPONENT()        (storage->delegate) // how to find delegate component
 
#define COMPONENT_UPP_SELECT_ROOT()     ImageCodec          // root for Type's UPP_PREFIX and SELECT_PREFIX     
 
#include "Components.k.h"               // StdComponent's .k.h
#include "ImageCodec.k.h"               // Type's .k.h
#include "ComponentDispatchHelper.c"    // make our dispatcher and cando
 
// End Dispatch Stuff
/************************************************************************************/
 
// --------------------------------------------------------------------------------------
// EFFECT CODE - This is a "basic" sample so we only support one pixel format
//               k32ARGBPixelFormat 32 bit argb (Mac). See Dimmer2 Effect for a more
//               complete cross platform implementation.
// --------------------------------------------------------------------------------------
 
// --------------------------------------------------------------------------------------
// INTERNAL ROUTINES
// --------------------------------------------------------------------------------------
 
#define Get32(x)   (*(long*)(x))
#define Set32(x,y) (*(long*)(x)) = ((long)(y))
 
// There are two versions of the EffectFilter32 function (the actual blitter code), they
// do the exact same thing but the second one was happily optimized by geowar for fun.
// Note, this code is intended as an example, yours will hopefully be cooler, faster, more bionic
#define OPTIMIZED 1
 
#if !OPTIMIZED
 
// Code that draws our actual effect.
static void EffectFilter32(BlitGlobals *glob)
{
    long height  = glob->height;
    long *src    = glob->source.srcBaseAddr;
    long *dst    = glob->dstBaseAddr;
    long srcBump = glob->source.srcRowBytes - (glob->width * 4);
    long dstBump = glob->dstRowBytes - (glob->width * 4);
    
    while (height--)
    {
        long width = glob->width;
        
        while (width--)
        {
            UInt32 thePixelValue;
            UInt8  theGrayValue;
            SInt16 adjust;
            
            thePixelValue = Get32(src);
            src++;
            
            // Grey scale filter, each pixel has a value ranging from 0 (black) to 255 (white)
            // The transformation is based on the equation:
            //                  
            //      grey value = 0.299*r + 0.587*g + 0.114*b    (see [Russ95], pp. 39)
            //
            // r, g, b indicate the red, green, blue color values of a pixel in the original image
        
            theGrayValue = (0.299 * ((thePixelValue >> 16) & 0xff)) +
                           (0.587 * ((thePixelValue >> 8) & 0xff)) +
                           (0.114 * ((thePixelValue >> 0) & 0xff));
            
            // figrue out the brightness adjustment        
            if (glob->brightValue < 0 )
                adjust = glob->brightValue * theGrayValue / 100;
            else
                adjust = glob->brightValue * (255 - theGrayValue) / 100;
    
            theGrayValue += adjust;
 
            thePixelValue = (0xff000000) | (theGrayValue << 16) | (theGrayValue << 8) | (theGrayValue << 0); // alpha (opaque)
            
            Set32(dst,thePixelValue);
            dst++;
        }
        
        src = (void *)(((Ptr)src) + srcBump);
        dst = (void *)(((Ptr)dst) + dstBump);
    }
}
 
#else
 
// Optimized code that draws our actual effect.
static void EffectFilter32(BlitGlobals *glob)
{
    long height  = glob->height;
    long *src    = glob->source.srcBaseAddr;
    long *dst    = glob->dstBaseAddr;
    long srcBump = glob->source.srcRowBytes - (glob->width * 4);
    long dstBump = glob->dstRowBytes - (glob->width * 4);
 
    // pre-compute the adjustment factors and convert the number to a power of two so 
    // the shift operator can be used instead of a divide when applying the adustment
    SInt32 thePixelValue;
    UInt8  theGrayValue;
    SInt32 posAdj = 0,
           negAdj = 0;
    long   theBrightValue = 128 * glob->brightValue / 100;
 
    // do we have a pos or neg adjustment to make?
    (theBrightValue < 0) ? (negAdj = theBrightValue) : (posAdj = theBrightValue);
 
    while (height--)
    {
        long width = glob->width;
        
        while (width--)
        {            
            thePixelValue = Get32(src);
            src++;
            
            // Grey scale filter, each pixel has a value ranging from 0 (black) to 255 (white)
            // The transformation is based on the equation:
            //                     
            //        grey value = 0.299*r + 0.587*g + 0.114*b    (see [Russ95], pp. 39)
            //
            // r, g, b indicate the red, green, blue color values of a pixel in the original image
 
            // Compare this code to the original implemtation above
            
            // 1. conversion between ints and floats is slow - avoid by using int fractions
            //    (299*r / 1000) + (587*g / 1000)+ (114*b/1000)
            // 2. combine all the divides
            //    (299*r + 587*g + 114*b) / 1000
            // 3. convert weighting factors to power of two so a shift can be used instead of a divide
            //    299 / 1000 == 306 / 1024 == 306 >> 10
            //    587 / 1000 == 601 / 1024 == 601 >> 10
            //    114 / 1000 == 114 / 1024 == 114 >> 10
            // Zippy!
            theGrayValue = ((306 * ((thePixelValue >> 16) & 0xff)) +
                            (601 * ((thePixelValue >>  8) & 0xff)) +
                            (117 * ((thePixelValue >>  0) & 0xff))) >> 10; 
 
            // do the brightness adjustment
            // the branch statement is eliminated and replaced with the positive and
            // negative adjustment variables, one of which should be zero
            // pre-converted power of two values so a shift can be used instead of a divide
            theGrayValue += ((theGrayValue * negAdj) + ((255 - theGrayValue) * posAdj)) >> 7;
            
            // alpha (opaque)
            thePixelValue = (0xff000000) | (theGrayValue << 16) | (theGrayValue << 8) | (theGrayValue << 0);
            
            Set32(dst,thePixelValue);
            dst++;
        }
        src = (void *)(((Ptr) src) + srcBump);
        dst = (void *)(((Ptr) dst) + dstBump);
    }
}
 
#endif // OPTIMIZED
 
// BlitterRenderFrame
// --------------------------------------------------------------------------------------
static long BlitterRenderFrame(BlitGlobals *blitGlob) // input: our globals
{
    
    // convert data into base/size
    if (blitGlob->source.src) {
        blitGlob->source.srcBaseAddr = blitGlob->source.src->dataPtr;
        blitGlob->source.srcRowBytes = blitGlob->source.src->dataSize / blitGlob->height;
    }
    
    // do the actual render
    switch (blitGlob->dstPixelFormat) {
        case k32ARGBPixelFormat:
            EffectFilter32(blitGlob);
            break;
    }
    
    return noErr;
}
 
// RequestImageFormat
//      If the data is already in the requested height and depth, return.
//  Otherwise, calls decompression to get it into the format we can handle
// --------------------------------------------------------------------------------------
static OSErr RequestImageFormat(EffectGlobals  *glob,       // input: globals for rendering
                                EffectSourcePtr source,     // input: source to potentially convert
                                short           width,      // input: desired width
                                short           height,     // input: desired height
                                OSType          pixelFormat)// input: desired pixel format (depth & format)
{
    CDSequenceDataSourcePtr sourceData = source->source.image;
    ImageDescriptionHandle  curDesc = (ImageDescriptionHandle)sourceData->dataDescription;
    ImageDescriptionHandle  newDesc = NULL;
    ImageDescriptionPtr     dp;
    OSErr                   err = noErr;
 
    dp = *curDesc;
    if ((source->effectType == kEffectRawSource) && (((dp->cType == kRawCodecType) && (dp->depth == (short)pixelFormat)) || 
        (dp->cType == pixelFormat)) && (dp->width == width) && (dp->height == height))
    {
        /* already got what we need */
        return noErr;
    }
 
    // otherwise, call the ICM to convert to desired data format
    newDesc = (ImageDescriptionHandle)NewHandleClear(sizeof(ImageDescription));
    err = MemError();
 
    if (noErr == err) {
        short pixelSize = QTGetPixelSize(pixelFormat);
        dp = *newDesc;
        dp->cType = pixelFormat;
        dp->depth = pixelSize;
        dp->width  = width;
        dp->height = height;
        dp->clutID = -1;
        
        /* the source is a stacked effect - or one in a format we can't handle. */
        /* pass it off to the Generic Effect to convert */
        /* it to a normal source */
        err = ImageCodecEffectConvertEffectSourceToFormat(glob->target, source, newDesc);
 
        if (newDesc) {
            DisposeHandle((Handle)newDesc);
        }
    }
 
    return err;
}
 
#pragma mark-
// --------------------------------------------------------------------------------------
// COMPONENT ENTRY POINTS - Standard Component Calls
// --------------------------------------------------------------------------------------
 
// The number of supported pixel formats
#define kNumPixelFormatsSupported 1
 
/* -- This Effect Component uses the Generic Effect Component --
    The Generic Effect Component is an Apple-supplied component
    that makes it easier for developers to create new Effects.
    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.
*/
 
// Component Open Request - Required
// This is called once per instance of our component.  Allocate our storage at
// this point.  If we have any shared storage, we would check here to make sure
// it exists, else create it.
// input/output: our globals
// input: reference to ourself
pascal ComponentResult GreyscaleFilterOpen(EffectGlobals        *glob,
                                           ComponentInstance    self)
{
    ComponentResult result;
    
    result = noErr;
        
    // first, allocate our local storage
    if ((glob = (EffectGlobals *)NewPtrClear(sizeof(EffectGlobals))) == NULL)
    {
        result = MemError();
        goto bail;
    }
    
    SetComponentInstanceStorage(self, (Handle) glob);
    
    // we are ourselves, and the current top of chain is us
    glob->self = self;
    glob->target = self;
    glob->wantedDestinationPixelTypeH = (OSType **)NewHandleClear(sizeof(OSType) * (kNumPixelFormatsSupported + 1));
    
    // open the generic effect, this will handle effects we can't handle ourselves
    result = OpenADefaultComponent(decompressorComponentType, kEffectGenericType, &glob->delegate);
    if (result) goto bail;
        
    // set up the target for the components below us
    ComponentSetTarget(glob->delegate, self);
 
bail:
    return result;
}
 
// Component Close Request - Required
// Called each time an instance of our component is going away.  Toss anything we allocated.
// input: our globals
// input: reference to ourself
pascal ComponentResult GreyscaleFilterClose(EffectGlobals     *glob,
                                            ComponentInstance self)
{
#pragma unused (self)
 
    if (glob)
    {
        CloseComponent(glob->delegate);
        DisposeHandle((Handle)glob->wantedDestinationPixelTypeH);
        DisposePtr((Ptr)glob);
    }
    
    return noErr;
}
 
// Component Target Request
//      Allows another component to "target" you i.e., you call another component whenever
// you would call yourself (as a result of your component being used by another component).
// input: our globals
// input: reference to new top of chain
pascal ComponentResult GreyscaleFilterTarget(EffectGlobals     *glob,
                                             ComponentInstance target)
{
    // remember who is top of chain
    glob->target = target;
    
    // and tell folks below us, too.
    ComponentSetTarget(glob->delegate, target);
    
    return noErr;
}
 
// Component Version Request - Required
//      Called to obtain the version of our component.
//  input: our globals
pascal ComponentResult GreyscaleFilterVersion(EffectGlobals *glob)
{
#pragma unused (glob)
    
    return kEffectVersion;
}
 
#pragma mark-
// --------------------------------------------------------------------------------------
// COMPONENT ENTRY POINTS - Image Codec Calls
// --------------------------------------------------------------------------------------
 
// ImageCodecGetCodecInfo
//      Your component receives the ImageCodecGetCodecInfo request whenever an application calls the
//  Image Compression Manager's GetCodecInfo function. Your component should return a formatted compressor
//  information structure defining its capabilities.
//  The info is stored as a resource in our component.
//
//      input: our globals
//      output: our codec info
// ----------------------------------------------------------------------------------------
pascal ComponentResult GreyscaleFilterGetCodecInfo(EffectGlobals *glob,
                                                   CodecInfo     *info)
{
    OSErr err = noErr;
 
    if (info == NULL)
    {
        err = paramErr;
    }
    else
    {
        CodecInfo **tempCodecInfo;
 
        err = GetComponentResource((Component) glob->self,
                                   codecInfoResourceType,
                                   kEffect_cdci_ResID,
                                   (Handle *)&tempCodecInfo);
        if (err == noErr)
        {
            *info = **tempCodecInfo;
            DisposeHandle((Handle)tempCodecInfo);
        }
    }
 
    return err;
}
 
// ImageCodecGetParameterListHandle
//      Returns a parameter description atom container, as described in the QuickTime Effect Documentaion
//  section "Supplying Parameter Description Information".
//  http://developer.apple.com/techpubs/quicktime/qtdevdocs/REF/refEffects.28.htm
//  This function can use the GetComponentResource function to retrieve an 'atms' resource that stores this
//  information if you have provided one in your component.
//
//      input: our globals
//      output: the parameter description for this effect
// ----------------------------------------------------------------------------------------
pascal ComponentResult GreyscaleFilterGetParameterListHandle(EffectGlobals *glob,
                                                             Handle        *theHandle)
{
    OSErr err = noErr;
    
    err = GetComponentResource((Component)glob->self,
                               kEffectAtomsResType,
                               kEffect_atms_ResID,
                               theHandle);
    return err;
}
 
#pragma mark-
// --------------------------------------------------------------------------------------
// COMPONENT ENTRY POINTS - Effect Codec Calls
// --------------------------------------------------------------------------------------
 
// ImageCodecEffectSetup
//      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.
// 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.
// 
//      input: our globals
//      input: information about the thing being decompressed
//
//  Return in p->capabilities anything in particular your effect requires, such as 
//  limitations on bitdepth.
// --------------------------------------------------------------------------------------
pascal long GreyscaleFilterEffectSetup(EffectGlobals         *glob,
                                       CodecDecompressParams *p)
{
    CodecCapabilities   *capabilities = p->capabilities;
    OSType              *formats = *glob->wantedDestinationPixelTypeH;
    long                wantedPixelSize = capabilities->wantedPixelSize;
    OSType              dstPixelFormat;
    OSErr               err = noErr;
    
    dstPixelFormat = GETPIXMAPPIXELFORMAT(&p->dstPixMap);
 
    switch (dstPixelFormat) {
    case k32ARGBPixelFormat:    // we know how to do these pixel formats
        *formats++ = dstPixelFormat;
        break;
    default:                    // we really need this!
        *formats++ = k32ARGBPixelFormat;
        break;
    }
    
    // end of the format list
    *formats++ = 0;
    
    /* set up our blitter */
    glob->blitter.width  = (*p->imageDescription)->width;
    glob->blitter.height = (*p->imageDescription)->height;
        
    capabilities->wantedPixelSize = 0;
 
    p->wantedDestinationPixelTypes = glob->wantedDestinationPixelTypeH;
 
    return err;
}
 
// ImageCodecEffectBegin
//      This function is called immediately before your EffectRenderFrame function.
//  The EffectBegin 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 EffectBegin, 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.
//
//      input: our globals
//      input: info about frame being drawn
//      input: info about this effect frame
// --------------------------------------------------------------------------------------
pascal long GreyscaleFilterEffectBegin(EffectGlobals         *glob,
                                       CodecDecompressParams *p,
                                       EffectsFrameParamsPtr effect)
{
    EffectSourcePtr source;
    OSErr           err = noErr;
    long            offsetH, offsetV;
 
    // we don't async
    effect->doAsync = false;
 
    // Destination:
    // has the destination changed? 
    if (p->conditionFlags & (codecConditionNewClut+codecConditionFirstFrame+
                             codecConditionNewDepth+codecConditionNewDestination+
                             codecConditionNewTransform))
    {
        // this will make us re-scan the sources
        glob->majorSourceChangeSeed = 0;
 
        // this will make us re-read the parameter values
        glob->frameNumber = 0;
 
        // set up the blitter globals for the destination
        glob->blitter.dstPixelFormat = GETPIXMAPPIXELFORMAT(&p->dstPixMap);
        
        // adjust the destination baseaddress to be at the beginning of the desired rect
        offsetH = (p->dstRect.left - p->dstPixMap.bounds.left);
        if (p->dstPixMap.pixelSize == 32) {
            offsetH <<= 2;                  /* 1 pixel = 4 bytes */
        } else {
            err = codecConditionErr;        /* a data format we can't handle */
            goto bail;
        }
        
        offsetV = (p->dstRect.top - p->dstPixMap.bounds.top) * QTGetPixMapPtrRowBytes(&p->dstPixMap);
 
        glob->blitter.dstBaseAddr = p->dstPixMap.baseAddr + offsetH + offsetV;
        glob->blitter.dstRowBytes = QTGetPixMapPtrRowBytes(&p->dstPixMap);
    }
 
    // Source:
    // if it's a new source make note of it!
    if (p->majorSourceChangeSeed != glob->majorSourceChangeSeed)
    {
        // grab start of input chain for this effect
        source = effect->source;
 
        // set the blitters source
        // if it's a source we can handle, save it away, if not bail
        err = RequestImageFormat(glob, source, glob->blitter.width, glob->blitter.height, glob->blitter.dstPixelFormat);
        if (noErr == err) {
            glob->blitter.source.src = source->source.image;
        } else {
            glob->blitter.source.src = NULL;
            goto bail;
        }
        
        glob->majorSourceChangeSeed = p->majorSourceChangeSeed;
    }
    
    // If this is a new frame, or the same frame with a new length, get rid of our old parameters   
    if ((effect->frameTime.frameNumber != glob->frameNumber) || (effect->frameTime.virtualDuration != glob->virtualDuration))
    {
        glob->initialized = false;
        glob->frameNumber = effect->frameTime.frameNumber;
        glob->virtualDuration = effect->frameTime.virtualDuration;
    }
 
    // Read in effect parameters
    if (!glob->initialized)
    {
        Ptr             data = p->data;
        QTAtom          atom;
        QTAtomID        atomID = 1;
        long            actSize;
        
        // Find the 'bryt' atom
        atom = QTFindChildByID((QTAtomContainer)&data,
                               kParentAtomIsContainer,
                               kEffectBrightnessCtrlAtom, // The name of the parameter
                               atomID,                    // The ID of the parameter
                               NULL);
 
        // Copy the parameter value from the atom
        if (QTCopyAtomDataToPtr((QTAtomContainer)&data,
                                atom,
                                false,
                                sizeof(long),
                                &((glob->blitter).brightValue),
                                &actSize) != noErr)
        {
            // If the copy failed, use a default value for this parameter
            (glob->blitter).brightValue = 0;
        }
 
        glob->initialized = true;
    }
    
    // this effect only needs to run when the sources actually change
    p->needUpdateOnTimeChange   = false;
    p->needUpdateOnSourceChange = true;
    
bail:
    return err;
}
 
// ImageCodecEffectRenderFrame
//      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. This function contains the implementation
//  of your effect.
//
//      input: our global
//      input: effect frame to be rendered
// --------------------------------------------------------------------------------------
pascal long GreyscaleFilterEffectRenderFrame(EffectGlobals         *glob,
                                             EffectsFrameParamsPtr effect)
{
#pragma unused (effect)
 
    // render the frame
    BlitterRenderFrame(&glob->blitter);
    
    return noErr;
}
 
// ImageCodecEffectGetSpeed
//      Returns the approximate number of frames per second that your effect is capable of transforming.
//  This function should return a Fixed value in pFPS , 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.
//
//      input: our globals
//      input: the current parameter values for this effect
//      output: a Fixed value that will contain the rendering speed of this effect on exit
// ----------------------------------------------------------------------------------------
pascal long GreyscaleFilterEffectGetSpeed(EffectGlobals * glob,
                                          QTAtomContainer parameters,
                                          Fixed *pFPS)
{
#pragma unused (glob, parameters)
 
    if (pFPS) *pFPS = Long2Fix(30);
        
    return noErr;
}