QTReadWriteJPEG.c

//////////
//
//  File:       QTReadWriteJPEG.c
//
//  Contains:   Sample code for compressing and decompressing JPEG images.
//
//  Written by: Michael Marinkovich and Guillermo Ortiz
//  Revised by: Tim Monroe
//              Based heavily on the existing "JPEG Sample" code.
//
//  Copyright:  © 1996-1998 by Apple Computer, Inc., all rights reserved.
//
//  Change History (most recent first):
//
//     <4>      02/03/99    rtm     reworked prompt and filename handling to remove "\p" sequences
//     <3>      04/27/98    rtm     revised to uncouple this file from its Macintosh framework,
//                                  to make the coding style conform to other code samples, and
//                                  to make it run on Windows
//     <2>      02/??/97    mwm     fixed some memory leaks; expanded JPEG parsing
//     <1>      04/03/96    mwm     initial coding
//     
//  This sample code illustrates how to compress and decompress JPEG images using QuickTime. 
//  We use the FCompressImage function, but you could also use the CompressImage function.
//  Although this sample demonstrates only JPEG compression/decompression, you could use this
//  as a framework for other types of compression (except for the decoding of the JPEG header).
//
//  In this sample code, we allow the user to open a JPEG image file; then we draw it into
//  a window on the screen. Your application, of course, will probably want to do more
//  interesting things with the image. We also allow the user to save an image using JPEG
//  compression.
//
//  NOTES:
//
//  *** (1) ***
//  You may incorporate this sample code into your applications without restriction, though
//  the sample code has been provided "AS IS" and the responsibility for its operation is 100% yours.
//  However, what you are not permitted to do is to redistribute the source as "DSC Sample Code"
//  after having made changes. If you're going to redistribute the source, we require that you
//  make it clear in the source that the code was descended from Apple Sample Code, but that
//  you've made changes.
//
//////////
 
//////////
//
// header files
//
//////////
 
#include "QTReadWriteJPEG.h"
#include "QTUtilities.h"
 
 
//////////
//
// global variables
//
//////////
 
GWorldPtr                       gGWorld = NULL;         // the GWorld we load the image data into
WindowPtr                       gImageWindow = NULL;    // the window we display the image in
 
 
//////////
//
// QTJPEG_PromptUserForJPEGFileAndDisplay
// Let the user select a JPEG image file, then display it in a window.
//
//////////
 
void QTJPEG_PromptUserForJPEGFileAndDisplay (void)
{
    SFTypeList                  myTypeList;
    StandardFileReply           myReply;
    PixMapHandle                myPixMap;   
    Rect                        myRect;
    OSErr                       myErr = noErr;
    
    // have the user select a JPEG image file
    myTypeList[0] = kQTFileTypeJPEG;
 
    StandardGetFilePreview(NULL, 1, myTypeList, &myReply);
    if (!myReply.sfGood)
        goto bail;
    
    // read the file data into an offscreen graphics world
    myErr = QTJPEG_ReadJPEG(myReply.sfFile, &gGWorld);
    if (myErr != noErr)
        goto bail;
 
    myRect = gGWorld->portRect;
    myPixMap = GetGWorldPixMap(gGWorld);
    if (LockPixels(myPixMap)) {
 
        // create a window to display the image in; then draw into that window      
        MacOffsetRect(&myRect, 50, 50);
        gImageWindow = NewCWindow(NULL, &myRect, myReply.sfFile.name, true, movableDBoxProc, (WindowPtr)-1L, true, 0);
        if (gImageWindow == NULL)
            goto bail;
            
        MacOffsetRect(&myRect, -50, -50);
        
        // copy the image from the offscreen port into the window
        CopyBits(   (BitMapPtr)(*myPixMap),
                    (BitMapPtr)(&(gImageWindow->portBits)),
                    &myRect, 
                    &gImageWindow->portRect,
                    srcCopy, 
                    NULL);
    }
 
bail:
    UnlockPixels(myPixMap);
}
 
 
//////////
//
// QTJPEG_ReadJPEG
// Open a JPEG file with supplied FSSpec; the graphics world containing the image is returned through theGWorld.
//
//////////
 
OSErr QTJPEG_ReadJPEG (FSSpec theFile, CGrafPtr *theGWorld)
{
    ImageDescriptionHandle      myDesc;
    Handle                      myData;
    GWorldPtr                   myGWorld = NULL;
    GWorldPtr                   oldWorld;
    GDHandle                    oldGD;
    PixMapHandle                myPixMap;
    ICMDataProcRecord           myLoadProc;
    Rect                        myRect;
    DLDataRec                   myDataRec;
    long                        mySize;
    short                       myRefNum;
    Ptr                         myDataPtr;
    OSErr                       myErr = paramErr;
    
    // save the current graphics port
    GetGWorld(&oldWorld, &oldGD);
    
    if (theGWorld != NULL) {
            
        myErr = FSpOpenDF(&theFile, fsRdWrShPerm, &myRefNum);
        if (myErr == noErr) {
        
            myDesc = (ImageDescriptionHandle)NewHandle(sizeof(ImageDescription));
            if (myDesc != NULL) {
                HLock((Handle)myDesc);
                myErr = QTJPEG_ReadJPEGHeader(myRefNum, myDesc, &myRect);
                if (myErr == noErr) {
                
                    myData = NewHandleClear(kBufferSize);
                    myErr = MemError();
                    
                    if ((myData != NULL) && (myErr == noErr)) {
                        myErr = QTJPEG_NewJPEGWorld(&myGWorld, (*myDesc)->depth, myRect);
                        if ((myGWorld != NULL) && (myErr == noErr)){
                            myErr = SetFPos(myRefNum, fsFromStart, 0);
                            if (myErr == noErr) {
                            
                                myPixMap = GetGWorldPixMap(myGWorld);
                                LockPixels(myPixMap);
                                SetGWorld(myGWorld, NULL);
                                HLock(myData);
                                
                                mySize = kBufferSize;
                                
                                // make sure the file size is greater than the buffer size
                                (void)GetEOF(myRefNum, &mySize);
                                if (kBufferSize > mySize)
                                    mySize = mySize;
                                    
                                myErr = FSRead(myRefNum, &mySize, *myData);
                                if (myErr == noErr) {
                                    mySize = (*myDesc)->dataSize - kBufferSize;
                                    if (mySize < 0) 
                                        mySize = 0;
                                        
                                    myDataRec.fRefNum = myRefNum;
                                    myDataRec.fFileLength = mySize;
                                    myDataRec.fOrigPtr = *myData;
                                    myDataRec.fEndPtr = *myData + kBufferSize;
                                    myLoadProc.dataProc = NewICMDataProc(QTJPEG_DataLoadingProc);
                                    myLoadProc.dataRefCon = (long)&myDataRec;
                                    
                                    myDataPtr = StripAddress(*myData);
                                    
                                    myErr = FDecompressImage(   myDataPtr,
                                                                myDesc,
                                                                myPixMap,
                                                                &myRect,
                                                                NULL,
                                                                srcCopy,
                                                                NULL,
                                                                NULL,
                                                                NULL,
                                                                codecHighQuality,
                                                                anyCodec,
                                                                kBufferSize,
                                                                &myLoadProc,
                                                                NULL);
                                                          
                                    DisposeRoutineDescriptor(myLoadProc.dataProc);
                                    HUnlock(myData);
                                    UnlockPixels(myPixMap);
                                    
                                    // restore the previous graphics world 
                                    SetGWorld(oldWorld, oldGD);
                                    
                                    if (myErr == noErr)
                                        *theGWorld = myGWorld;
                                }
                            }
                            
                            if (myErr != noErr)
                                DisposeGWorld(myGWorld);
                        }
                        
                        DisposeHandle(myData);
                    }
                }
                
                HUnlock((Handle)myDesc);                
                DisposeHandle((Handle)myDesc);
            }
            
            FSClose(myRefNum);              // close the file
        }
    }
            
    return(myErr);
}
 
 
//////////
//
// QTJPEG_ReadJPEGHeader
// Read the JPEG header and fill out the specified ImageDescription with needed info.
//
//////////
 
OSErr QTJPEG_ReadJPEGHeader (short theRefNum, ImageDescriptionHandle theDesc, Rect *theRect)
{
    long                    mySize;
    short                   mySkip;
    UInt8                   myMarker;
    Boolean                 isJFIF = false;
    Boolean                 readingExtension = false;
    OSErr                   myErr = noErr;
    
    // set file position to beginning of file
    myErr = SetFPos(theRefNum, fsFromStart , 0);
    if (myErr != noErr)
        return(myErr);
        
    // get file length, so we don't overflow
    myErr = GetEOF(theRefNum, &mySize);
    if (myErr != noErr)
        return(myErr);
    
    (*theDesc)->dataSize = mySize;
 
    // loop forever
    while (true) {
        myMarker = QTJPEG_FindNextMarker(theRefNum);
        
        switch (myMarker) {
            case kSOIMarker:
                isJFIF = true;
                break;
                
            case kAPPOMarker + 0:
            case kAPPOMarker + 1:
            case kAPPOMarker + 2:
            case kAPPOMarker + 3:
            case kAPPOMarker + 4:
            case kAPPOMarker + 5:
            case kAPPOMarker + 6:
            case kAPPOMarker + 7:
            case kAPPOMarker + 8:
            case kAPPOMarker + 9:
            case kAPPOMarker + 10:
            case kAPPOMarker + 11:
            case kAPPOMarker + 12:
            case kAPPOMarker + 13:
            case kAPPOMarker + 14:
            case kAPPOMarker + 15:
                myErr = QTJPEG_HandleAPPOMarker(myMarker, theRefNum, theDesc, &readingExtension);
                if (myErr != noErr)
                    return(myErr);
                break;
                
            case kCommentMarker:
                QTJPEG_SkipLength(theRefNum);
                break;
        
            case kSOFMarker + 0:        // start of frame header marker
            case kSOFMarker + 1:
            case kSOFMarker + 2:
            case kSOFMarker + 3:
            case kSOFMarker + 5:
            case kSOFMarker + 6:
            case kSOFMarker + 7:
            case kSOFMarker + 9:
            case kSOFMarker + 10:
            case kSOFMarker + 11:
            case kSOFMarker + 13:
            case kSOFMarker + 14:
            case kSOFMarker + 15:
                myErr = QTJPEG_HandleSOFMarker(theRefNum, theDesc, readingExtension);
                if (myErr != noErr)
                    return(myErr);
                    
                if (!readingExtension) {
                    MacSetRect(theRect, 0, 0, (*theDesc)->width, (*theDesc)->height);
                    return(noErr);
                }
                break;
                
            case kDACMarker:
                mySkip = QTJPEG_ReadWord(theRefNum) - 2;
                mySkip *= QTJPEG_ReadWord(theRefNum);
                myErr = SetFPos(theRefNum, fsFromMark, mySkip);
                break;
                
            case kSOSMarker:
                QTJPEG_HandleSOSMarker(theRefNum);
                break;
                
            case kDHTMarker:
            case kDQTMarker:
            case kRSTOMarker:
                QTJPEG_SkipLength(theRefNum);
                break;
                
            case kEOIMarker:        // we reached the end of image
                // we are reading an extension so keep going
                if (readingExtension)
                    readingExtension = false;
                break;
        }
    }
    
    return(myErr);
}
 
 
//////////
//
// QTJPEG_FindNextMarker
// Find the next marker in the specified file.
//
//////////
 
UInt8 QTJPEG_FindNextMarker (long theRefNum)
{
    UInt8           myMarker;
    
    myMarker = QTJPEG_ReadByte(theRefNum);
    
    while (myMarker == kStartMarker)
        myMarker = QTJPEG_ReadByte(theRefNum);
 
    while (myMarker == 0x00) {
        myMarker = QTJPEG_ReadByte(theRefNum);
 
        while (myMarker != kStartMarker)
            myMarker = QTJPEG_ReadByte(theRefNum);
            
        myMarker = QTJPEG_ReadByte(theRefNum);
    }
        
    return(myMarker);
}
 
 
//////////
//
// QTJPEG_HandleAPPOMarker
// Handle an APPO marker in the specified file.
//
//////////
 
OSErr QTJPEG_HandleAPPOMarker (UInt8 theMarker, long theRefNum, ImageDescriptionHandle theDesc, Boolean *readingExtension)
{
    Fixed           xRes, yRes;
    long            myLength;
    short           myUnits;
    short           myVersion;
    UInt8           myExtension;
    UInt8           myType[5];
    OSErr           myErr = noErr;
 
    // read skip bytes - header length - skip count
    myLength = QTJPEG_ReadWord(theRefNum) - 2;
    
    if ((theMarker == kAPPOMarker) && (myLength >= 14)) {
        myType[0] = QTJPEG_ReadByte(theRefNum);
        myType[1] = QTJPEG_ReadByte(theRefNum);
        myType[2] = QTJPEG_ReadByte(theRefNum);
        myType[3] = QTJPEG_ReadByte(theRefNum);
        myType[4] = QTJPEG_ReadByte(theRefNum);
        
        // check to see if we really have the JFIF header
        if ((myType[0] == 'J') &&
            (myType[1] == 'F') &&
            (myType[2] == 'I') &&
            (myType[3] == 'F')) {
            
            myVersion = QTJPEG_ReadWord(theRefNum);
 
            if (myVersion < 0x100)  
                return(paramErr);   // don't know this
            else
                (*theDesc)->version = myVersion;
                
            myUnits = QTJPEG_ReadByte(theRefNum);
            xRes = QTJPEG_ReadWord(theRefNum);
            yRes = QTJPEG_ReadWord(theRefNum);
 
            switch (myUnits) {
                case 0:         // no res, just aspect ratio
                    xRes = FixMul(72L << 16, xRes << 16);
                    yRes = FixMul(72L << 16, yRes << 16);
                    break;
                    
                case 1:         // dots per inch
                    xRes = xRes << 16;
                    yRes = yRes << 16;
                    break;
                    
                case 2:         // dots per centimeter (we convert to dpi )
                    xRes = FixMul(0x28a3d, xRes << 16);
                    yRes = FixMul(0x28a3d, xRes << 16);     // yRes?? RTM
                    break;  
                    
                default:
                    break;
            }
            
            (*theDesc)->hRes = xRes;
            (*theDesc)->vRes = yRes;
 
            myLength -= 12;
            myErr = SetFPos(theRefNum, fsFromMark, myLength);
            
        } else {
            if ((myType[0] == 'J') &&
                (myType[1] == 'F') &&
                (myType[2] == 'X') &&
                (myType[3] == 'X')) { 
                
                *readingExtension = true;       // next markers are extensions; ignore them
 
                myExtension = QTJPEG_ReadByte(theRefNum);
                switch (myExtension) {
                    case 0x10:
                    case 0x11:
                    case 0x13:
                        break;
                    default:
                        return(paramErr);
                }
            }
        }
    } else
        myErr = SetFPos(theRefNum, fsFromMark, myLength);
 
    return(myErr);
}
 
 
//////////
//
// QTJPEG_HandleSOFMarker
// Handle an SOF marker in the specified file.
//
//////////
 
OSErr QTJPEG_HandleSOFMarker (long theRefNum, ImageDescriptionHandle theDesc, Boolean readingExtension)
{
    short           myWidth = 0;
    short           myHeight = 0;
    short           myComponents;
    short           myLength;
    StringPtr       myTitle = QTUtils_ConvertCToPascalString(kWindowTitle);
    OSErr           myErr = noErr;
 
    if (!readingExtension) {
        myLength = QTJPEG_ReadWord(theRefNum);
        QTJPEG_ReadByte(theRefNum);
        myHeight = QTJPEG_ReadWord(theRefNum);
        myWidth = QTJPEG_ReadWord(theRefNum);
 
        // make sure we do have something to display
        if ((myWidth != 0) && (myHeight != 0)) {
        
            // now set up the image description
            (*theDesc)->idSize          = sizeof(ImageDescription);
            (*theDesc)->cType           = FOUR_CHAR_CODE('jpeg');
            (*theDesc)->dataRefIndex    = 0;
            (*theDesc)->revisionLevel   = 0;
            (*theDesc)->vendor          = 0;
            (*theDesc)->temporalQuality = 0;
            (*theDesc)->spatialQuality  = codecNormalQuality;
            (*theDesc)->width           = myWidth;
            (*theDesc)->height          = myHeight;
            (*theDesc)->frameCount      = 1;
            BlockMove(myTitle, (*theDesc)->name, 13);
            (*theDesc)->clutID          = -1;
            
            myComponents = QTJPEG_ReadByte(theRefNum);
            
            switch (myComponents) {
                case 1:     
                    (*theDesc)->depth = 40;
                    break;
 
                case 3:
                    (*theDesc)->depth = 32;
                    break;
 
                case 4:
                    (*theDesc)->depth = 32;
                    break;
                                        
                default:
                    myErr = paramErr;
                    return(myErr);
                    break;
            }
            
            myErr = SetFPos(theRefNum, fsFromMark, myLength - 8);
            return(noErr);
        }
        
    } else {
        myLength = QTJPEG_ReadWord(theRefNum) - 2;
        myErr = SetFPos(theRefNum, fsFromMark, myLength);
        if (myErr != noErr)
            return(myErr);
    }
    
    free(myTitle);
 
    return(myErr);
}
 
 
//////////
//
// QTJPEG_HandleSOSMarker
// Handle an SOS marker in the specified file.
//
//////////
 
void QTJPEG_HandleSOSMarker (long theRefNum)
{
    short       myComponents;
    short       myWord;
    
    QTJPEG_ReadWord(theRefNum);
    myComponents = QTJPEG_ReadByte(theRefNum);
    
    for (myWord = 0; myWord < myComponents; myWord++)
        QTJPEG_ReadWord(theRefNum);
}
 
 
//////////
//
// QTJPEG_ReadByte
// Read the next byte from the specified file.
//
//////////
 
UInt8 QTJPEG_ReadByte (short theRefNum)
{
    UInt8       myData;
    long        myBytesNeeded = sizeof(char);
 
    (void)FSRead(theRefNum, &myBytesNeeded, &myData);
    return(myData);
}
 
 
//////////
//
// QTJPEG_ReadWord
// Read the next word from the specified file.
//
//////////
 
UInt16 QTJPEG_ReadWord (short theRefNum)
{
    UInt16      myData;
    long        myBytesNeeded = sizeof(UInt16);
 
    (void)FSRead(theRefNum, &myBytesNeeded, &myData);
    
    // in JPEG files, the data is stored in a big-endian order
    myData = EndianU16_BtoN(myData);
    return(myData);
}
 
 
//////////
//
// QTJPEG_SkipLength
// Skip over the length word.
//
//////////
 
void QTJPEG_SkipLength (long theRefNum)
{
    UInt16      mySkip;
    
    mySkip = QTJPEG_ReadWord(theRefNum) - 2;
    SetFPos(theRefNum, fsFromMark, mySkip);
}
 
 
//////////
//
// QTJPEG_NewJPEGWorld
// Return, through the theWorld parameter, a new offscreen graphics world suitable
// for drawing a JPEG image of the specified bitdepth into.
//
//////////
 
OSErr QTJPEG_NewJPEGWorld (GWorldPtr *theWorld, short theDepth, Rect theRect)
{
    GWorldPtr       oldPort;
    GDHandle        oldGD;
    PixMapHandle    myPixMap;
    CTabHandle      myCTab = NULL;
    OSErr           myErr = paramErr;
    
    if (theWorld != NULL) {
        // save the current graphics port
        GetGWorld(&oldPort, &oldGD);
    
        // if depth is greater than 32, then the image is grayscale
        if (theDepth > 32) {
            myCTab = GetCTable(theDepth);
            theDepth = theDepth - 32;
        }
        
        // first try to allocate a GWorld in the application's heap
        myErr = NewGWorld(theWorld, theDepth, &theRect, myCTab, NULL, 0L);
        
        // otherwise, try to allocate a GWorld in temporary memory
        if (myErr != noErr)
            myErr = NewGWorld(theWorld, theDepth, &theRect, myCTab, NULL, useTempMem);
    
        if ((myErr == noErr) && (theWorld != NULL))  {
            myPixMap = GetGWorldPixMap(*theWorld);
            if (LockPixels(myPixMap)) {
                SetGWorld(*theWorld, NULL);
                EraseRect(&theRect);
                UnlockPixels(myPixMap);
            }
        }
        
        SetGWorld(oldPort, oldGD);  
    }
    
    return(myErr);
}
 
 
//////////
//
// QTJPEG_SaveJPEG
// Save the specified image as a compressed file.
//
//////////
 
OSErr QTJPEG_SaveJPEG (GWorldPtr theWorld)
{
    StandardFileReply           myReply;
    ImageDescriptionHandle      myDesc;
    Handle                      myData;
    Rect                        myRect;
    PixMapHandle                myPixMap;   
    CTabHandle                  myCTab = NULL;
    ICMFlushProcRecord          myFlushProc;    
    short                       myRefNum;
    short                       myDepth;
    StringPtr                   myImagePrompt = QTUtils_ConvertCToPascalString(kSaveImagePrompt);
    StringPtr                   myImageFileName = QTUtils_ConvertCToPascalString(kSaveImageFileName);
    OSErr                       myErr = paramErr;
    
    if (theWorld == NULL)
        goto bail;
    
    // have the user select the name of the new image file
    StandardPutFile(myImagePrompt, myImageFileName, &myReply);
    if (!myReply.sfGood)
        goto bail;
    
    myDesc = (ImageDescriptionHandle)NewHandle(sizeof(ImageDescription));
    if (myDesc == NULL)
        goto bail;
    
    myRect = theWorld->portRect;
    myPixMap = GetGWorldPixMap(theWorld);
    
    if (LockPixels(myPixMap)) { 
    
        // if less than 16-bit then get the color table of our GWorld
        myDepth = (**myPixMap).pixelSize;
        if (myDepth < 16)
            myCTab = (**myPixMap).pmTable;
        
        myData = NewHandle(kBufferSize);
        myErr = MemError();
        
        if ((myData != NULL) && (myErr == noErr)) {
            CodecType           myCodec = kJPEGCodecType;
 
            HLock(myData);
                                                        
            if (myReply.sfReplacing) 
                myErr = FSpDelete(&myReply.sfFile);
        
            myErr = FSpCreate(&myReply.sfFile, kImageFileCreator, kQTFileTypeJPEG, myReply.sfScript);
            
            if (myErr == noErr)
                myErr = FSpOpenDF(&myReply.sfFile, fsRdWrPerm, &myRefNum);
                
            if (myErr == noErr)
                myErr = SetFPos(myRefNum, fsFromStart, 0);
                
            if (myErr == noErr) {
                ICMFlushProcRecordPtr       myFlushProcPtr = NULL;
 
                myFlushProc.flushProc = NewICMFlushProc(QTJPEG_DataUnloadProc);
                myFlushProc.flushRefCon = myRefNum;
                myFlushProcPtr = &myFlushProc;
 
                // compress the image
                myErr = FCompressImage( myPixMap,
                                        &myRect,
                                        myDepth,
                                        codecNormalQuality,
                                        myCodec,
                                        anyCodec,
                                        myCTab,
                                        codecFlagWasCompressed,
                                        kBufferSize,
                                        myFlushProcPtr,
                                        NULL,
                                        myDesc,
                                        *myData);
                
                if (myErr == noErr)
                    myErr = SetFPos(myRefNum, fsFromStart, (**myDesc).dataSize);
 
                if (myErr == noErr)
                    myErr = SetEOF(myRefNum, (**myDesc).dataSize);
                             
                if (myErr == noErr)     
                    myErr = FSClose(myRefNum);
                
                HUnlock(myData);
                DisposeHandle(myData);
                
                DisposeRoutineDescriptor(myFlushProc.flushProc);
            }
        }
    }
    
    UnlockPixels(myPixMap);
    
bail:
    if (myDesc != NULL)
        DisposeHandle((Handle)myDesc);
    
    free(myImagePrompt);
    free(myImageFileName);
 
    return(myErr);
}           
    
    
//////////
//
// QTJPEG_DataUnloadProc
// A data unloading procedure: write the compressed data to disk.
//
// The theRefCon parameter is assumed to be a file reference number of an open file.
//
//////////
 
PASCAL_RTN OSErr QTJPEG_DataUnloadProc (Ptr theData, long theBytesNeeded, long theRefCon)
{
    OSErr       myErr = noErr;
    
    if (theData == NULL) {
        // if data is NULL, set a new position in the file from the current mark, offset by bytesNeeded
        myErr = SetFPos(theRefCon, fsFromMark, theBytesNeeded);
    } else {
        // otherwise, write the specified data to disk
        myErr = FSWrite(theRefCon, &theBytesNeeded, theData);
    }
    
    return(myErr);
}
 
 
//////////
//
// QTJPEG_DataLoadingProc
// A data loading procedure: read the data from disk.
//
// The theRefCon parameter is assumed to be a pointer to our custom data-loading record.
//
//////////
 
PASCAL_RTN OSErr QTJPEG_DataLoadingProc (Ptr *theData, long theBytesNeeded, long theRefCon)
{
    long        myUnusedDataLen;
    long        myNewDataLen;
    DLDataPtr   myDataRec;
    Ptr         myDataPtr;
    OSErr       myErr = noErr;
    
    myDataRec = (DLDataPtr)theRefCon;
    
    if (theData == NULL) {
        myErr = SetFPos(myDataRec->fRefNum, fsFromMark, theBytesNeeded);
    } else {    
        myDataPtr = *theData;
        
        // if QT requests more data than is in the buffer, we will have to load more
        if ((myDataPtr + theBytesNeeded) >= myDataRec->fEndPtr) {
            // move whats left up to the front of the buffer
            myUnusedDataLen = myDataRec->fEndPtr - myDataPtr;
            BlockMove(myDataPtr, myDataRec->fOrigPtr, myUnusedDataLen);
            
            // now fill the buffer with new data,
            // following the data we moved to the front of the buffer
            myNewDataLen = kBufferSize - myUnusedDataLen;
            
            if (myNewDataLen > myDataRec->fFileLength)
                myNewDataLen = myDataRec->fFileLength;
                
            myDataPtr = myDataRec->fOrigPtr + myUnusedDataLen;
            
            myErr = FSRead(myDataRec->fRefNum, &myNewDataLen, myDataPtr);
            
            myDataRec->fFileLength -= myNewDataLen;
 
            *theData = myDataRec->fOrigPtr;
        }
    }
    
    return(myErr);
}