Common Files/SpriteUtilities.c

//////////
//
//  File:       SpriteUtilities.c
//
//  Contains:   Utilities for adding sprite tracks to QuickTime movies.
//
//  Written by: Sean Allen
//  Revised by: Chris Flick and Tim Monroe
//
//  Copyright:  © 1997-1998 by Apple Computer, Inc., all rights reserved.
//
//  Change History (most recent first):
//
//     <3>      03/27/98    rtm     added error checking to AddPICTImageToKeyFrameSample to prevent crashes
//                                  if PICT resources not found
//     <2>      03/27/98    cf      further fixes for Windows compiles
//     <1>      03/26/98    rtm     made fixes for Windows compiles
//     
//
//////////
 
#ifndef _SPRITEUTILITIES_
#include "SpriteUtilities.h"
#endif
 
#ifndef _IMAGECOMPRESSIONUTILITIES_
#include "ImageCompressionUtilities.h"
#endif
 
#ifndef __ENDIANUTILITIES__
#include "EndianUtilities.h"
#endif
 
#ifndef __RESOURCES__
#include <Resources.h>
#endif
 
#ifndef __QUICKTIMECOMPONENTS__
#include <QuickTimeComponents.h>
#endif
 
#ifndef __MEDIAHANDLERS__
#include <MediaHandlers.h>
#endif
 
// exception handling macros
 
#define     FailIf(a, e)        {if (a)     { err = e; goto bail; }}
#define     FailOSErr(a)        {if (err = a)   goto bail;}
#define     FailMemErr(a)       {a; if (err = MemError()) goto bail;}
 
OSErr GetImageDescription( QTAtomContainer keySample, QTAtom imagesContainerAtom, short imageIndex, ImageDescriptionHandle imageDesc );
OSErr SetImageGroupID( QTAtomContainer keySample, QTAtom imagesContainerAtom, short imageIndex, long groupID );
OSErr GetImageGroupID( QTAtomContainer keySample, QTAtom imagesContainerAtom, short imageIndex, long *groupID );
 
OSErr SetSpriteData( QTAtomContainer sprite, Point *location, short *visible, short *layer, short *imageIndex, ModifierTrackGraphicsModeRecord *graphicsMode, StringPtr spriteName, QTAtomContainer actionAtoms )
{
    OSErr   err = noErr;
    QTAtom  propertyAtom;
    
    if ( location ) {
        MatrixRecord    matrix;
        
        SetIdentityMatrix( &matrix );
        matrix.matrix[2][0] = ((long)location->h << 16);
        matrix.matrix[2][1] = ((long)location->v << 16);
        
        EndianUtils_MatrixRecord_NtoB( &matrix );
 
        if ( (propertyAtom = QTFindChildByIndex( sprite, 0, kSpritePropertyMatrix, 1, nil )) == 0 )
            FailOSErr( QTInsertChild( sprite, 0, kSpritePropertyMatrix, 1, 0, sizeof(MatrixRecord), &matrix, nil ) )
        else
            FailOSErr( QTSetAtomData( sprite, propertyAtom, sizeof(MatrixRecord), &matrix ) );
    }
    if ( visible ) {
        short tempVisible = *visible;
        
        tempVisible = EndianS16_NtoB(tempVisible);
        
        if ( (propertyAtom = QTFindChildByIndex( sprite, 0, kSpritePropertyVisible, 1, nil )) == 0 )
            FailOSErr( QTInsertChild( sprite, 0, kSpritePropertyVisible, 1, 0, sizeof(short), &tempVisible, nil ) )
        else
            FailOSErr( QTSetAtomData( sprite, propertyAtom, sizeof(short), &tempVisible ) );
    }
    if ( layer ) {
        short tempLayer = *layer;
        
        tempLayer = EndianS16_NtoB(tempLayer);
 
        if ( (propertyAtom = QTFindChildByIndex( sprite, 0, kSpritePropertyLayer, 1, nil )) == 0 )
            FailOSErr( QTInsertChild( sprite, 0, kSpritePropertyLayer, 1, 0, sizeof(short), &tempLayer, nil ) )
        else
            FailOSErr( QTSetAtomData( sprite, propertyAtom, sizeof(short), &tempLayer ) );
    }
    if ( imageIndex ) {
        short tempImageIndex = *imageIndex;
 
        tempImageIndex = EndianS16_NtoB(tempImageIndex);
        
        if ( (propertyAtom = QTFindChildByIndex( sprite, 0, kSpritePropertyImageIndex, 1, nil )) == 0 )
            FailOSErr( QTInsertChild( sprite, 0, kSpritePropertyImageIndex, 1, 0, sizeof(short), &tempImageIndex, nil ) )
        else
            FailOSErr( QTSetAtomData( sprite, propertyAtom, sizeof(short), &tempImageIndex ) );
    }
    if ( graphicsMode ) {
        ModifierTrackGraphicsModeRecord tempGraphicsMode;
        
        tempGraphicsMode.graphicsMode   = EndianU32_NtoB(graphicsMode->graphicsMode);
        tempGraphicsMode.opColor.red    = EndianU16_NtoB(graphicsMode->opColor.red);
        tempGraphicsMode.opColor.green  = EndianU16_NtoB(graphicsMode->opColor.green);
        tempGraphicsMode.opColor.blue   = EndianU16_NtoB(graphicsMode->opColor.blue);
 
        if ( (propertyAtom = QTFindChildByIndex( sprite, 0, kSpritePropertyGraphicsMode, 1, nil )) == 0 )
            FailOSErr( QTInsertChild( sprite, 0, kSpritePropertyGraphicsMode, 1, 0, sizeof(tempGraphicsMode), &tempGraphicsMode, nil ) )
        else
            FailOSErr( QTSetAtomData( sprite, propertyAtom, sizeof(tempGraphicsMode), &tempGraphicsMode ) );
    }
    if( spriteName ) {
        QTAtom spriteNameAtom;
        
        if ( (spriteNameAtom = QTFindChildByIndex( sprite, 0, kSpriteNameAtomType, 1, nil )) == 0 )
            FailOSErr( QTInsertChild( sprite, 0, kSpriteNameAtomType, 1, 0, spriteName[0]+1, spriteName, nil ) )
        else
            FailOSErr( QTSetAtomData( sprite, spriteNameAtom, spriteName[0]+1, spriteName ) );
    }
    
    if ( actionAtoms ) {
        FailOSErr( QTInsertChildren( sprite, kParentAtomIsContainer, actionAtoms ) );
    }
    
bail:
    if ( err && sprite )
        QTRemoveChildren( sprite, 0 );
 
    return err;
}
 
OSErr AddSpriteToSample( QTAtomContainer theSample, QTAtomContainer theSprite, QTAtomID spriteID )
{
    OSErr   err = noErr;
    QTAtom  newSpriteAtom;
    
    FailIf ( QTFindChildByID( theSample, 0, kSpriteAtomType, spriteID, nil ), paramErr );
    
    FailOSErr( QTInsertChild( theSample, 0, kSpriteAtomType, spriteID, 0, 0, nil, &newSpriteAtom ) );       // index of zero means append
    FailOSErr( QTInsertChildren( theSample, newSpriteAtom, theSprite ) );
bail:
    return err;
}
 
OSErr AddSpriteSampleToMedia( Media theMedia, QTAtomContainer sample, TimeValue duration, Boolean isKeyFrame,
                        TimeValue *sampleTime )
{
    OSErr                   err = noErr;
    SampleDescriptionHandle sampleDesc = nil;
    
    FailMemErr( sampleDesc = (SampleDescriptionHandle) NewHandleClear( sizeof(SpriteDescription ) ) );
    
    FailOSErr( AddMediaSample(  theMedia, (Handle) sample, 0, GetHandleSize( sample ),
                                duration, sampleDesc, 1,
                                (short)(isKeyFrame ? 0 : mediaSampleNotSync), sampleTime ) );
bail:
    if ( sampleDesc )   DisposeHandle( (Handle) sampleDesc );
    return err;
}
 
 
OSErr AddCompressedSpriteSampleToMedia( Media theMedia, QTAtomContainer sample, TimeValue duration, Boolean isKeyFrame,
                        OSType dataCompressorType,
                        TimeValue *sampleTime )
{
    OSErr                   err = noErr;
    SpriteDescriptionHandle sampleDesc = nil;
    Handle                  compressedSample = nil;
    ComponentInstance       dataCompressorComponentInstance = nil;
    
    err = OpenADefaultComponent(DataCompressorComponentType, dataCompressorType, &dataCompressorComponentInstance);
    if( err ) goto bail;
 
    FailMemErr( sampleDesc = (SpriteDescriptionHandle) NewHandleClear( sizeof(SpriteDescription) ) );
    
    if( dataCompressorComponentInstance != nil ) {
        UInt32          compressBufferSize, actualCompressedSize, decompressSlop = 0;
        UInt32          uncompressedSize;
        SignedByte      saveState = HGetState( sample );
        
        err = (OSErr)DataCodecGetCompressBufferSize( dataCompressorComponentInstance, GetHandleSize( sample ), &compressBufferSize );
        if(err) goto bail;
        
        compressedSample = NewHandle( sizeof(UInt32) + compressBufferSize );
        err = MemError();
        if(err) goto bail;
        
        HLockHi( sample );
        HLockHi( compressedSample );
        err = (OSErr)DataCodecCompress( dataCompressorComponentInstance, *sample, 
                                GetHandleSize(sample), 
                                *compressedSample + sizeof(UInt32),         // room for size at beginning
                                compressBufferSize, 
                                &actualCompressedSize,
                                &decompressSlop );
        
        HSetState( sample, saveState );
        HUnlock( compressedSample );
        
        if(err) goto bail;
        
        SetHandleSize( compressedSample, sizeof(UInt32) + actualCompressedSize );
        err = MemError();
        if(err) goto bail;
 
        (**sampleDesc).decompressorType = EndianU32_NtoB(dataCompressorType);
    
        uncompressedSize = GetHandleSize(sample);
        (*(UInt32*) *compressedSample) = EndianU32_NtoB(uncompressedSize);      // add uncompressed size at beginning
        
        FailOSErr( AddMediaSample(  theMedia, (Handle) compressedSample, 0, GetHandleSize( compressedSample ),
                                    duration, (SampleDescriptionHandle) sampleDesc, 1,
                                    (short)(isKeyFrame ? 0 : mediaSampleNotSync), sampleTime ) );
    }
    else
    {
        FailOSErr( AddMediaSample(  theMedia, (Handle) sample, 0, GetHandleSize( sample ),
                                    duration, (SampleDescriptionHandle) sampleDesc, 1,
                                    (short)(isKeyFrame ? 0 : mediaSampleNotSync), sampleTime ) );
    }
    
bail:
    if ( compressedSample )                 DisposeHandle( compressedSample );
    if ( sampleDesc )                       DisposeHandle( (Handle) sampleDesc );
    if ( dataCompressorComponentInstance )  CloseComponent( dataCompressorComponentInstance );
    
    return err;
}
 
 
OSErr AddPICTImageToKeyFrameSample( QTAtomContainer keySample, short pictID, RGBColor *keyColor, QTAtomID id, FixedPoint *registrationPoint, StringPtr imageName )
{
    OSErr                   err = noErr;
    PicHandle               picture;
    Handle                  compressedPicture = NULL;
    ImageDescriptionHandle  idh = NULL;
    
    // get picture from resource
    picture = (PicHandle) GetPicture( pictID );
    if (picture == NULL)
        err = resNotFound;
 
    if(err) goto bail;
    
    DetachResource( (Handle)picture );
    
    // convert it to image data compressed by the animation compressor
    err = RecompressPictureWithTransparency( picture, keyColor, nil, &idh, &compressedPicture );
    if(err) goto bail;
 
    // add it to the keySample
    HLock( compressedPicture );
    err = AddCompressedImageToKeyFrameSample( keySample, idh, GetHandleSize( compressedPicture ), *compressedPicture, id, registrationPoint, imageName );
    
bail:
    if ( picture )              KillPicture( picture );
    if ( compressedPicture )    DisposeHandle( compressedPicture ); 
    if ( idh )                  DisposeHandle( (Handle)idh );   
    return err;
}
 
 
 
 
OSErr AddCompressedImageToKeyFrameSample( QTAtomContainer keySample, 
                ImageDescriptionHandle idh, long dataSize, Ptr compressedDataPtr, 
                QTAtomID imageID, FixedPoint *registrationPoint, StringPtr imageName )
{
    OSErr       err = noErr;
    Handle      imageData;
    QTAtom      defaultsAtom, imagesContainerAtom, imageAtom;
    ImageDescriptionHandle  bigEndianImageDescription = nil;
 
#if TARGET_RT_LITTLE_ENDIAN
    bigEndianImageDescription = (ImageDescriptionHandle) NewHandle(GetHandleSize((Handle)idh));
    BlockMoveData( *idh, *bigEndianImageDescription, GetHandleSize((Handle)idh));
    EndianUtils_ImageDescription_NtoB(bigEndianImageDescription);
#else
    bigEndianImageDescription = idh;            // already is big endian
#endif
 
    // append compressed picture data to imageDescription to obtain sprite image data
    FailMemErr( imageData = NewHandle(0) );
    FailMemErr( HandAndHand( (Handle)bigEndianImageDescription, imageData ) );  // imageData <= imageData + bigEndianImageDescription
    FailMemErr( PtrAndHand( compressedDataPtr, imageData, dataSize ) );
    
    if ( (defaultsAtom = QTFindChildByIndex( keySample, 0, kSpriteSharedDataAtomType, 1, nil )) == 0 )
        FailOSErr( QTInsertChild( keySample, 0, kSpriteSharedDataAtomType, 1, 0, 0, nil, &defaultsAtom ) );
        
    if ( (imagesContainerAtom = QTFindChildByIndex( keySample, defaultsAtom, kSpriteImagesContainerAtomType, 1, nil )) == 0 )
        FailOSErr( QTInsertChild( keySample, defaultsAtom, kSpriteImagesContainerAtomType, 1, 0, 0, nil, &imagesContainerAtom ) );
 
    FailOSErr( QTInsertChild( keySample, imagesContainerAtom, kSpriteImageAtomType, imageID, 0, 0, nil, &imageAtom ) );
 
    HLock( imageData );
    FailOSErr( QTInsertChild( keySample, imageAtom, kSpriteImageDataAtomType, 1, 0, GetHandleSize(imageData), *imageData, nil ) );
    HUnlock( imageData );
    
    if ( registrationPoint ) {
        FixedPoint tempRegistrationPoint;
        
        tempRegistrationPoint.x = EndianS32_NtoB(registrationPoint->x);
        tempRegistrationPoint.y = EndianS32_NtoB(registrationPoint->y);
        
        FailOSErr( QTInsertChild( keySample, imageAtom, kSpriteImageRegistrationAtomType, 1, 0, sizeof(tempRegistrationPoint), &tempRegistrationPoint, nil ) );
    }
    else {
        FixedPoint regPoint = { 0, 0 };
        
        // Flipping {0,0} doesn't change anything so we don't flip
        
        FailOSErr( QTInsertChild( keySample, imageAtom, kSpriteImageRegistrationAtomType, 1, 0, sizeof(regPoint), &regPoint, nil ) );
    }
    
    if( imageName ) {
        FailOSErr( QTInsertChild( keySample, imageAtom, kSpriteImageNameAtomType, 1, 0, imageName[0], &imageName[1], nil ) );
    }
 
bail:
#if TARGET_RT_LITTLE_ENDIAN
    if(bigEndianImageDescription)
        DisposeHandle((Handle)bigEndianImageDescription);
#else
    // bigEndianImageDescription is still idh, so don't dispose of it
#endif
 
    if ( imageData )    DisposeHandle( imageData ); 
    return err;
}
 
OSErr AssignImageGroupIDsToKeyFrame( QTAtomContainer keySample )
{
    OSErr                   err = noErr;
    QTAtom                  defaultsAtom, imagesContainerAtom;
    ImageDescriptionHandle  firstImageDesc = nil, secondImageDesc = nil;
    short                   firstIndex, secondIndex, numImages;
    CodecType               firstImageType, secondImageType;
    long                    groupID = 0, testID;
    
    defaultsAtom = QTFindChildByIndex( keySample, 0, kSpriteSharedDataAtomType, 1, nil );
    if ( ! defaultsAtom )   goto bail;
    
    imagesContainerAtom = QTFindChildByIndex( keySample, defaultsAtom, kSpriteImagesContainerAtomType, 1, nil );
    if ( ! imagesContainerAtom )    goto bail;
    
    firstImageDesc = (ImageDescriptionHandle)NewHandle(0);
    if ( firstImageDesc == nil ) { err = memFullErr; goto bail; }
 
    secondImageDesc = (ImageDescriptionHandle)NewHandle(0);
    if ( secondImageDesc == nil ) { err = memFullErr; goto bail; }
 
    numImages = QTCountChildrenOfType( keySample, imagesContainerAtom, kSpriteImageAtomType );
    for ( firstIndex = 1; firstIndex <= numImages; firstIndex++ ) {
        FailOSErr( SetImageGroupID( keySample, imagesContainerAtom, firstIndex, 0 ) );
    }
    for ( firstIndex = 1; firstIndex <= (numImages - 1); firstIndex++ ) {
        FailOSErr( GetImageGroupID( keySample, imagesContainerAtom, firstIndex, &testID ) );
        if ( testID == 0 ) {
            groupID++;
            FailOSErr( SetImageGroupID( keySample, imagesContainerAtom, firstIndex, groupID ) );
            
            FailOSErr( GetImageDescription( keySample, imagesContainerAtom, firstIndex, firstImageDesc ) );
            firstImageType = (**firstImageDesc).cType;
            
            for ( secondIndex = (firstIndex + 1); secondIndex <= numImages; secondIndex++ ) {
                FailOSErr( GetImageGroupID( keySample, imagesContainerAtom, secondIndex, &testID ) );
                if ( testID == 0 ) {
                    FailOSErr( GetImageDescription( keySample, imagesContainerAtom, secondIndex, secondImageDesc ) );
                    secondImageType = (**secondImageDesc).cType;
                
                    if ( firstImageType == secondImageType ) {
                        ImageSequence   seqID;
                        Boolean         equivalent;
                        
                        FailOSErr( DecompressSequenceBegin( &seqID, firstImageDesc, nil, nil, nil, nil, 
                                    ditherCopy, (RgnHandle)nil,  0, codecNormalQuality, anyCodec ) );
                                     
                        CDSequenceEquivalentImageDescription( seqID, secondImageDesc, &equivalent );
 
                        CDSequenceEnd( seqID );
                        
                        if ( equivalent ) {
                            FailOSErr( SetImageGroupID( keySample, imagesContainerAtom, secondIndex, groupID ) );
                        }
                    }
                }
            }
        }
    }
    // assign an ID to the last image
    FailOSErr( GetImageGroupID( keySample, imagesContainerAtom, numImages, &testID ) );
    if ( testID == 0 ) {
        groupID++;
        FailOSErr( SetImageGroupID( keySample, imagesContainerAtom, numImages, groupID ) );
    }
    
bail:
    if ( firstImageDesc )   DisposeHandle( (Handle)firstImageDesc );
    if ( secondImageDesc )  DisposeHandle( (Handle)secondImageDesc );
    return err;
}
 
 
 
// ¥¥¥¥ ImageDescriptionHandles are in native endianism, right? If so, we need to endian flip them before
//      they're returned from GetImageDescription.
 
OSErr GetImageDescription( QTAtomContainer keySample, QTAtom imagesContainerAtom, short imageIndex, ImageDescriptionHandle imageDesc )
{
    OSErr   err = noErr;
    QTAtom  imageAtom, imageDataAtom;
    UInt8   saveState;
    UInt32  imageDescriptionSize;
 
 
    imageAtom = QTFindChildByIndex( keySample, imagesContainerAtom, kSpriteImageAtomType, imageIndex, nil );
    if ( imageAtom == 0 )   { err = cannotFindAtomErr; goto bail; }
 
    imageDataAtom = QTFindChildByIndex( keySample, imageAtom, kSpriteImageDataAtomType, 1, nil );
    if ( imageDataAtom == 0 )   { err = cannotFindAtomErr; goto bail; }
 
    saveState = HGetState( (Handle)imageDesc );
    HUnlock( (Handle)imageDesc );
 
    // Copy the data (ImageDescription followed by image data) to a handle
 
    FailOSErr( QTCopyAtomDataToHandle( keySample, imageDataAtom, (Handle)imageDesc ) );
 
    imageDescriptionSize = EndianU32_BtoN((**imageDesc).idSize);
 
    // Pull off anything following the image description (& its color table, if any, and
    //  any image description extensions.
    SetHandleSize( (Handle)imageDesc, imageDescriptionSize );
 
 
 
#if TARGET_RT_LITTLE_ENDIAN
    EndianUtils_ImageDescription_BtoN( imageDesc );
#endif
 
    HSetState( (Handle)imageDesc, saveState );
    err = MemError();
    
bail:
    return err;
}
 
 
 
 
OSErr SetImageGroupID( QTAtomContainer keySample, QTAtom imagesContainerAtom, short imageIndex, long groupID )
{
    OSErr       err = noErr;
    QTAtom      imageAtom, imageGroupAtom;
    
    imageAtom = QTFindChildByIndex( keySample, imagesContainerAtom, kSpriteImageAtomType, imageIndex, nil );
    if ( imageAtom == 0 )   { err = cannotFindAtomErr; goto bail; }
 
    imageGroupAtom = QTFindChildByIndex( keySample, imageAtom, kSpriteImageGroupIDAtomType, 1, nil );
    if ( imageGroupAtom == 0 ) {
        FailOSErr( QTInsertChild( keySample, imageAtom, kSpriteImageGroupIDAtomType, 1, 1, 0, nil, &imageGroupAtom ) );
    }
 
    groupID = EndianU32_NtoB(groupID);
    FailOSErr( QTSetAtomData( keySample, imageGroupAtom, sizeof( groupID ), &groupID ) );
 
bail:
    return err;
}
 
OSErr GetImageGroupID( QTAtomContainer keySample, QTAtom imagesContainerAtom, short imageIndex, long *groupID )
{
    OSErr       err = noErr;
    QTAtom      imageAtom, imageGroupAtom;
 
    imageAtom = QTFindChildByIndex( keySample, imagesContainerAtom, kSpriteImageAtomType, imageIndex, nil );
    if ( imageAtom == 0 )   { err = cannotFindAtomErr; goto bail; }
 
    imageGroupAtom = QTFindChildByIndex( keySample, imageAtom, kSpriteImageGroupIDAtomType, 1, nil );
    
    if ( ! imageGroupAtom )
        *groupID = 0;
    else {
        FailOSErr( QTCopyAtomDataToPtr( keySample, imageGroupAtom, false, sizeof(*groupID), (Ptr)groupID, nil ) );
 
        *groupID = EndianU32_BtoN(*groupID);        // return native endian long
    }
    
bail:
    return err;
}