MyScreenSaverView.m

/*
    File:       MyScreenSaverView.c
    
    Description: A simple Screen Saver module which uses the Sequence Grabber 
                 DataProc mung technique first demonstrated in samples like MungGrab by km.
                 This sample is for 10.3 only and uses the vImage library to perform some
                 very basic pixel munging producing an effect familiar to anyone who remembers
                 the 1960's (or an Austin Powers movie). A good enhancement would be to have some
                 Hendrix or maybe early Floyd playing in the background. Enjoy!
 
    Author:     era
 
    Copyright:  © Copyright 2003 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> 10/28/03 initial release
 
*/
 
#import "MyScreenSaverView.h"
#include <Accelerate/Accelerate.h>
 
#define Log(X, Y) { fprintf(stderr," %s self=%#x\n", X, (unsigned int)Y); }
#define LogErr(X, Y) { if (X != noErr) { fprintf(stderr,"error: %ld", (long)X); Log(Y, self); }}
#define BailErr(X, Y) { if (X != noErr) { LogErr(X, Y); goto bail; } }
 
// globals
static UInt8 OwnsHardware = 0;
static SeqGrabComponent SeqGrab = 0; // the sequence grabber component
 
static Component *PanelListPtr = NULL;
static UInt8 NumberOfPanels = 0;
 
static UInt8 Index = 0;
static Pixel_8 RedTable[256], GreenTable[256], BlueTable[256];
 
// MungPixels uses vImage to transform the source before it is drawn to the screen
// http://developer.apple.com/documentation/Performance/Conceptual/vImage/index.html
static vImage_Error MungPixels(const GWorldPtr inGWorld) 
{
    vImage_Buffer srcVImageBuffer, dstVImageBuffer; 
    PixMapHandle hPixMap = GetGWorldPixMap(inGWorld); 
    void         *pPixels= GetPixBaseAddr(hPixMap); 
    long         rowBytes = GetPixRowBytes(hPixMap); 
    
    Rect bounds;
    vImage_Error vIError;
    
    GetPortBounds(inGWorld, &bounds);
     
    srcVImageBuffer.data = pPixels + (bounds.top * rowBytes) + (bounds.left * GetPixDepth(hPixMap) / 8); // pointer to the top left pixel of the buffer 
    srcVImageBuffer.height = bounds.bottom - bounds.top;                                                 // the height (in pixels) of the buffer 
    srcVImageBuffer.width = bounds.right - bounds.left;                                                  // the width (in pixels) of the buffer 
    srcVImageBuffer.rowBytes = rowBytes;                                                                 // the number of bytes in a pixel row 
    dstVImageBuffer = srcVImageBuffer;                                                                   // src and dest buffer are the same
 
    // transforms an ARGB8888 image by replacing all pixels of a given color with pixels
    // of a new color using separate look-up tables to replace each of the four components
   vIError = vImageTableLookUp_ARGB8888(&srcVImageBuffer, &dstVImageBuffer, NULL, RedTable, GreenTable, BlueTable, kvImageLeaveAlphaUnchanged);
    
    // mess with the tables for next time
    RedTable[Index++] ^= 0x80;
    GreenTable[Index++] ^= 0xA0;
    BlueTable[Index++] ^= 0x50;
    
    return vIError;
}
 
// SGDataProc callback
static pascal OSErr mySGDataProc(SGChannel c, Ptr p, long len, long *offset, long chRefCon, TimeValue time, short writeType, long refCon)
{
#pragma unused(offset,chRefCon,time,writeType)
    
    CodecFlags ignore;
    OSErr err = paramErr;
    
    MyScreenSaverView *myViewObject = (MyScreenSaverView *)refCon;
    if (NULL == myViewObject) { Log("myViewObject NULL!", myViewObject); return err; }
    
    err = DecompressSequenceFrameS([myViewObject decoSeq],  // sequence ID returned by DecompressSequenceBegin
                                    p,                      // pointer to compressed image data
                                    len,                    // size of the buffer
                                    0,                      // in flags
                                    &ignore,                // out flags
                                    NULL);                  // sync ie. no async completion proc
                                    
    if (err) { Log("decoSeq", myViewObject); return err; }
    
    err = MungPixels([myViewObject offscreen]);
    if (err) { Log("MungPixels", myViewObject); return err; }
    
    [myViewObject lockFocus];
    
    err = DecompressSequenceFrameS([myViewObject drawSeq],  // sequence ID returned by DecompressSequenceBegin
                                   [myViewObject baseAddr], // pointer to data
                                   [myViewObject length],   // size of the buffer
                                   0,                       // in flags
                                   &ignore,                 // out flags
                                   NULL);                   // sync ie. no async completion proc
    
    if (err) Log("drawSeq", myViewObject);
    
    [myViewObject unlockFocus];
    
    return err;
}
 
@implementation MyScreenSaverView
 
- (id)initWithFrame:(NSRect)frame isPreview:(BOOL)isPreview
{
    NSRect bounds;
        
    // init da super
    self = [super initWithFrame:frame isPreview:isPreview];
    
    Log("\pInitializing ScreenSaver", self);
    
    if (self) {
        // grab the screensaver defaults
        mBundleID = [[NSBundle bundleForClass:[self class]] bundleIdentifier];
        ScreenSaverDefaults *defaults = [ScreenSaverDefaults defaultsForModuleWithName:mBundleID];
        
        // try to load the version key, used to see if we have any saved settings
        mVersion = [defaults floatForKey:@"version"];
        if (!mVersion) {
            // no previous settings so define our defaults
            mVersion = 1;
            mUseHighQuality = NO;
            mMirrorCheckbox = NO;
            
            // write out the defaults
            [defaults setInteger:mVersion forKey:@"version"];
            [defaults setInteger:mUseHighQuality forKey:@"useHighQuality"];
            [defaults setInteger:mMirror forKey:@"mirror"];
            
            // synchronize
            [defaults synchronize];
        }
 
        // set defaults...
        mUseHighQuality = [defaults integerForKey:@"useHighQuality"];
        mMirror = [defaults integerForKey:@"mirror"];
        
        //...first time though mUserData may be 0 which is fine
        [self newUserData:&mUserData fromDefaults:defaults];
        
        mIsPreview = isPreview;
        
        // create our quickdraw view for the sequence grabber
        mQDView = [[NSQuickDrawView alloc] initWithFrame:NSZeroRect];
        if (!mQDView) {
            [self autorelease];
            BailErr(paramErr, "[NSQuickDrawView alloc]");
        }
        
        // make sure the subview resizes
        [self setAutoresizesSubviews:YES];
        [self addSubview:mQDView];
        [mQDView release];
        
        bounds = [self bounds];
        
        SetRect(&mWindowBounds, (SInt16)NSMinX(bounds), (SInt16)NSMinY(bounds),
                                (SInt16)NSMaxX(bounds), (SInt16)NSMaxY(bounds));
        
        // set up the sequence grabber for all instances
        if (!OwnsHardware++) {
            ComponentDescription cd = { SeqGrabPanelType, VideoMediaType, 0, 0, 0 };
            Component            c = 0;
            Component            *cPtr = NULL;
            ComponentResult      err;
            int                  index;
            
            Log("\pOpened Sequence Grabber", self);
 
            // initialize the movie toolbox
            err = EnterMovies();
            BailErr(err, "EnterMovies");
            
            // open the sequence grabber component and initialize it
            err = OpenADefaultComponent(SeqGrabComponentType, 0, &SeqGrab);
            BailErr(err, "OpenADefaultComponent");
            
            err = SGInitialize(SeqGrab);
            BailErr(err, "SGInitialize");
            
            // tell the SG we're not making a movie
            err = SGSetDataRef(SeqGrab, 0, 0, seqGrabDontMakeMovie | seqGrabDataProcIsInterruptSafe);
            BailErr(err, "SGSetDataRef");
            
            // 'safe' GWorld - we don't actually draw anything here
            err = SGSetGWorld(SeqGrab, NULL, NULL);
            BailErr(err, "SGSetGWorld");
 
            // set up the settings panel list removing the "Compression" panel
            NumberOfPanels = CountComponents(&cd);
            if (NumberOfPanels == 0) BailErr(paramErr, "CountComponents");
            
            PanelListPtr = (Component *)NewPtr(sizeof(Component) * (NumberOfPanels + 1));
            if (err = MemError() || NULL == PanelListPtr) BailErr(err, "PanelListPtr");
            
            cPtr = PanelListPtr;
            NumberOfPanels = 0;
            do {
                ComponentDescription compInfo;
                c = FindNextComponent(c, &cd);
                if (c) {
                    Handle hName = NewHandle(0);
                    if (err = MemError() || NULL == hName) BailErr(err, "NewHandle");
                    
                    GetComponentInfo(c, &compInfo, hName, NULL, NULL);
                    if (PLstrcmp(*hName, "\pCompression") != 0) {
                        *cPtr++ = c;
                        NumberOfPanels++;
                    }
                    DisposeHandle(hName);
                }
            } while (c);
             
            // init the color tables for munging
            for (index = 0; index < 256; index++) {         
                RedTable[index] = index;
                GreenTable[index] = index;
                BlueTable[index] = index;
            }
            
            // if we can't get a channel at this point we're probaby already in use
            // by some other process so we're not going to be able to work anyway
            err = SGNewChannel(SeqGrab, VideoMediaType, &mSGChanVideo);
            BailErr(err, "SGNewChannel");
        }
 
        // set the animation time interval, animateOneFrame is called 60 times a second
        [self setAnimationTimeInterval:1/60.0];
        
        return self;
    }
    
bail:
 
    return nil;
}
 
- (void)dealloc
{
    OSErr err;
    
    if (--OwnsHardware == 0) {
        // when completely finished with all instances
        // make sure to dispose of everything correctly
 
        err = CloseComponent(SeqGrab);
        DisposePtr((Ptr)PanelListPtr);
        ExitMovies();
        
        Log("\pClosed Sequence Grabber", self);
    }
    
    Log("\pDeallocating ScreenSaver", self);
        
    [super dealloc];
}
 
- (BOOL)isFlipped
{
    return YES;
}
 
- (void)startAnimation
{
    ImageDescriptionHandle desc = NULL; // an image description
    ComponentResult err;
    
    [super startAnimation];
    
    Log("\pStart Animation", self);
    
    if ([mQDView qdPort] == NULL) {
        // the first time lockFocus is called on the NSQuickDrawView
        // it creates a valid qdPort - we need this to draw into and
        // before this is done, qdPort is NULL
        [mQDView lockFocus];
        [mQDView unlockFocus];
        Log("\pCreated qdport", self);
 
    }
    
    if (mSGChanVideo == 0) {
        // create a new sequence grabber video channel
        err = SGNewChannel(SeqGrab, VideoMediaType, &mSGChanVideo);
        BailErr(err, "SGNewChannel");
    }
    
    // set to record, we've already told SG we're not making a movie
    err = SGSetChannelUsage(mSGChanVideo, seqGrabRecord | seqGrabLowLatencyCapture);
    BailErr(err, "SGSetChannelUsage");
    
    // this will return the video digitizer's active source rectangle
    err = SGGetSrcVideoBounds(mSGChanVideo, &mSourceBounds);
    BailErr(err, "SGGetSrcVideoBounds");
    
    // set the channel bounds to either the full active source rectangle
    // or to whatever the default size is, ignore error first time
    SGSetChannelBounds(mSGChanVideo, &mSourceBounds);
    
    // if we have some user channel settings, set them now
    if (mUserData) SGSetChannelSettings(SeqGrab, mSGChanVideo, mUserData, 0);
 
    // get the channels image description
    desc = (ImageDescriptionHandle)NewHandle(0);
    if (err = MemError() || NULL == desc) BailErr(err, "NewHandle");
 
    err = SGPrepare(SeqGrab, false, true);
    BailErr(err, "SGPrepare");
    
    err = SGGetChannelSampleDescription(mSGChanVideo, (Handle)desc);
    BailErr(err, "SGGetChannelSampleDescription");
    
    // grab the native capture size from the Image Description
    // and really set the channel bounds to this size
    mSourceBounds.bottom = (*desc)->height;
    mSourceBounds.right = (*desc)->width;
    
    err = SGSetChannelBounds(mSGChanVideo, &mSourceBounds);
    BailErr(err, "SGSetChannelBounds");
    
    // create a 32-bit offscreen to decompress into
    err = QTNewGWorld(&mOffscreen, k32ARGBPixelFormat, &mSourceBounds, NULL, NULL, pixelsLocked);
    BailErr(err, "QTNewGWorld");
    
    // to make the SG happy
    err = SGSetGWorld(SeqGrab, mOffscreen, NULL);
    BailErr(err, "SGSetGWorld");
    
    // save the baseAddr for later
    mBaseAddr = GetPixBaseAddr(GetPortPixMap(mOffscreen));
    
    // set up the SGDataProc callback
    mSGDataUPP = NewSGDataUPP(&mySGDataProc);
    if (mSGDataUPP == NULL) BailErr(paramErr, "NewSGDataUPP");
    
    err = SGSetDataProc(SeqGrab, mSGDataUPP, (long)self);
    BailErr(err, "SGSetDataProc");
    
    // start recording
    err = SGStartRecord(SeqGrab);
    LogErr(err, "SGStartRecord");
    
    if (mDecoSeq == 0) {
        
        // set up decompression sequence - DataProc to offscreen
        CodecFlags cFlags = (mUseHighQuality && !mIsPreview) ? codecHighQuality : codecNormalQuality;
        
        err = DecompressSequenceBeginS(&mDecoSeq,                   // pointer to field to receive unique ID for sequence
                                       desc,                        // handle to image description structure
                                       NULL,                        // points to the compressed image data
                                       0,                           // size of the data buffer
                                       mOffscreen,                  // port for the DESTINATION image
                                       NULL,                        // graphics device handle, if port is set, set to NULL
                                       NULL,                        // decompress the entire source image - no source extraction
                                       NULL,                        // transformation matrix
                                       srcCopy,                     // transfer mode specifier
                                       (RgnHandle)NULL,             // clipping region in dest. coordinate system to use as a mask
                                       0,                           // flags
                                       cFlags,                      // accuracy in decompression
                                       bestSpeedCodec);             // compressor identifier or special identifiers ie. bestSpeedCodec
        
        LogErr(err, "DecompressSequenceBeginS decoSeq");
    }
    
    if (mDrawSeq == 0) {
        
        // set up draw sequence - offscreen to NSQuickDrawView
        MatrixRecord scaleMatrix;
        Rect         tempBounds = mWindowBounds;
        
        // always scale and mirror if user wants to
        if (mMirror) {
            tempBounds.left = mWindowBounds.right;
            tempBounds.right = mWindowBounds.left;
        }
        
        RectMatrix(&scaleMatrix, &mSourceBounds, &tempBounds);
        
        // dispose of the channel image description and get the offscreen pixmap image description
        DisposeHandle((Handle)desc); desc = NULL;
        err = MakeImageDescriptionForPixMap(GetPortPixMap(mOffscreen), &desc);
        BailErr(err, "MakeImageDescriptionForPixMap");
 
        // remember the data length
        mLen = ((*desc)->height * (*desc)->width) * 4;
        
        err = DecompressSequenceBeginS(&mDrawSeq,                   // pointer to field to receive unique ID for sequence
                                       desc,                        // handle to image description structure
                                       mBaseAddr,                   // points to the image data
                                       mLen,                        // size of the data buffer
                                       [mQDView qdPort],            // port for the DESTINATION image
                                       NULL,                        // graphics device handle, if port is set, set to NULL
                                       NULL,                        // decompress the entire source image - no source extraction
                                       &scaleMatrix,                // transformation matrix
                                       srcCopy,                     // transfer mode specifier
                                       (RgnHandle)NULL,             // clipping region in dest. coordinate system to use as a mask
                                       0,                           // flags
                                       codecNormalQuality,          // accuracy in decompression
                                       bestSpeedCodec);             // compressor identifier or special identifiers ie. bestSpeedCodec
        
        BailErr(err, "DecompressSequenceBeginS drawSeq");
        
        // tell the sequence to flush
        SetDSequenceFlags(mDrawSeq, codecDSequenceFlushInsteadOfDirtying, codecDSequenceFlushInsteadOfDirtying);
    }
    
bail:
 
    // dispose of the Image Description we have
    if (desc) DisposeHandle((Handle)desc);
}
 
- (void)stopAnimation
{
    ComponentResult err;
    
    [super stopAnimation];
    
    Log("\pStop Animation", self);
    
    // stop the sequence grabber
    err = SGStop(SeqGrab);
    LogErr(err, "SGStop");
    
    // if we have a valid channel toss it
    if (mSGChanVideo) {
        err = SGDisposeChannel(SeqGrab, mSGChanVideo);
        mSGChanVideo = 0;
        LogErr(err, "SGDisposeChannel");
    }
    
    // remove the dataproc
    err = SGSetDataProc(SeqGrab, NULL, NULL);
    LogErr(err, "SGSetDataProc");
    DisposeSGDataUPP(mSGDataUPP);
    
    // release the sequence grabber
    err = SGRelease(SeqGrab);
    LogErr(err, "SGRelease");
    
    // close down the sequences
    err = CDSequenceEnd(mDecoSeq);
    mDecoSeq = 0;
    LogErr(err, "CDSequenceEnd");
    
    err = CDSequenceEnd(mDrawSeq);
    mDrawSeq = 0;
    LogErr(err, "CDSequenceEnd");
    
    // change to 'safe' gworld before we toss the offscreen
    err = SGSetGWorld(SeqGrab, NULL, NULL);
    LogErr(err, "SGSetGWorld");
 
    if (mOffscreen) DisposeGWorld(mOffscreen);
}
 
- (void)animateOneFrame
{
    ComponentResult err;
    
    // sequence grabber do some work
    err = SGIdle(SeqGrab);
    LogErr(err, "SGIdle");
 
    if (err) [self stopAnimation];
}
 
- (BOOL)hasConfigureSheet
{
    return YES;
}
 
// Display the configuration sheet for the user to choose their settings
- (NSWindow*)configureSheet
{
    // if we haven't loaded our configure sheet, load the nib named MyScreenSaver.nib
    if (!mConfigureSheet)
        [NSBundle loadNibNamed:@"MyScreenSaver" owner:self];
 
    // set the UI state
    [mUseHQCheckbox setState:mUseHighQuality];
    [mMirrorCheckbox setState:mMirror];
    
    return mConfigureSheet;
}
 
// Called when the user clicked the SAVE button
- (IBAction) closeSheetSave:(id) sender
{
    // get the defaults
    ScreenSaverDefaults *defaults = [ScreenSaverDefaults defaultsForModuleWithName:mBundleID];
 
    // save the UI state
    mUseHighQuality = [mUseHQCheckbox state];
    mMirror = [mMirrorCheckbox state];
    
    // write the defaults
    [defaults setInteger:mUseHighQuality forKey:@"useHighQuality"];
    [defaults setInteger:mMirror forKey:@"mirror"];
    
    [self saveUserData:mUserData toDefaults:defaults];
    
    // synchronize
    [defaults synchronize];
 
    // end the sheet
    [NSApp endSheet:mConfigureSheet];
}
 
// Called when th user clicked the CANCEL button
- (IBAction) closeSheetCancel:(id) sender
{
    // nothing to configure
    [NSApp endSheet:mConfigureSheet];
}
 
// Called when th user clicked the Configure button
// NOTE: There is a know bug in QuickTime 6.4 in which the
// Settings Dialog pops up in random locations...sigh...
- (IBAction) sgConfigurationDialog:(id) sender
{
    OSErr err;
    SGChannel tempChannel;
    
    // create a temporary video channel
    err = SGNewChannel(SeqGrab, VideoMediaType, &tempChannel);
    BailErr(err, "SGNewChannel");
    
    err = SGSetChannelUsage(tempChannel, seqGrabRecord | seqGrabLowLatencyCapture);
    BailErr(err, "SGSetChannelUsage");
 
    SGSetChannelBounds(tempChannel, &mSourceBounds);
    
    // set the previous settings, bring up the dialog and if the user didn't cancel
    // save the new channel settings for later
    if (mUserData) SGSetChannelSettings(SeqGrab, tempChannel, mUserData, 0);
     
    err = SGSettingsDialog(SeqGrab, tempChannel, NumberOfPanels, PanelListPtr, 0, NULL, NULL);
    if (noErr == err) {
        // dispose the old settings and get the new channel settings
        if (mUserData) DisposeUserData(mUserData);
        err = SGGetChannelSettings(SeqGrab, tempChannel, &mUserData, 0);
        LogErr(err, "SGGetChannelSettings");
    } else if (userCanceledErr != err) {
        LogErr(err, "SGSettingsDialog");
    }
    
bail:
 
    if (tempChannel) SGDisposeChannel(SeqGrab, tempChannel);
}
 
// Get the Channel Settings as UserData from the preferences
-(OSErr)newUserData:(UserData *)outUserData fromDefaults:(ScreenSaverDefaults *)inDefaults
{
    NSData   *theSettings;
    Handle   theHandle = NULL;
    UserData theUserData = NULL;
    OSErr    err = paramErr;
    
    // read the new setttings from our preferences
    theSettings = [inDefaults objectForKey:@"sgVideoSettings"];
    
    if (theSettings) {
        err = PtrToHand([theSettings bytes], &theHandle, [theSettings length]);
        
        if (theHandle) {
            err = NewUserDataFromHandle(theHandle, &theUserData);
            if (theUserData) {
                *outUserData = theUserData;
            }
            DisposeHandle(theHandle);
        }
    }
 
  return err;
}
 
// Save the Channel Settings from UserData in the preferences
-(OSErr)saveUserData:(UserData)inUserData toDefaults:(ScreenSaverDefaults *)inDefaults;
{
    NSData *theSettings;
    Handle hSettings;
    OSErr  err;
    
    if (NULL == inUserData) return paramErr;
    
    hSettings = NewHandle(0);
    err = MemError();
    
    if (noErr == err) {
        err = PutUserDataIntoHandle(inUserData, hSettings); 
        
        if (noErr == err) {
            HLock(hSettings);
            theSettings = [NSData dataWithBytes:(UInt8 *)*hSettings length:GetHandleSize(hSettings)];
            
            // save the new setttings to our preferences
            if (theSettings) {
                [inDefaults setObject:theSettings forKey:@"sgVideoSettings"];
                [inDefaults synchronize];
            }
        }
        
        DisposeHandle(hSettings);
    }
    
    return err;
}
 
// getters
-(ImageSequence)decoSeq
{
    return mDecoSeq;
}
 
-(ImageSequence)drawSeq
{
    return mDrawSeq;
}
 
-(GWorldPtr)offscreen;
{
    return mOffscreen;
}
 
-(Ptr)baseAddr;
{
    return mBaseAddr;
}
 
-(UInt32)length
{
    return mLen;
}
 
@end