source/QTReadWriteJPEG.c

//////////
//
//  File:       QTReadWriteJPEG.c
//
//  Contains:   Sample code for compressing and decompressing JPEG images.
//
//  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
//     
//
//////////
 
/*
    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.
*/
 
//////////
//
// 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
 
 
//////////
//
// QTUtils_ConvertCToPascalString
// Convert a C string into a Pascal string.
//
// The caller is responsible for disposing of the pointer returned by this function (by calling free).
//
//////////
 
StringPtr QTUtils_ConvertCToPascalString (char *theString)
{
    StringPtr   myString = (unsigned char*)(malloc(strlen(theString) + 1));
    short       myIndex = 0;
 
    while (theString[myIndex] != '\0') {
        myString[myIndex + 1] = theString[myIndex];
        myIndex++;
    }
    
    myString[0] = (unsigned char)myIndex;
    
    return(myString);
}
 
 
 
//////////
//
// 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);
}
 
 
 
#if 0
OSErr QTJPEG_ReadJPEGPixels (FSSpec theFile, PixMapHandle *thePixels, Rect *theRect)
{
    ImageDescriptionHandle      myDesc;
    Handle                      myData;
    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)) {
                        *thePixels = NewHandle(4* (myRect.bottom-myRect.top) * (myRect.right-myRect.left));
                        myErr = MemErr();
                        if ((*thePixels != NULL) && (myErr == noErr)){
                            myErr = SetFPos(myRefNum, fsFromStart, 0);
                            if (myErr == noErr) {
                                                            
                                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);
}
#endif
 
 
 
 
 
 
 
//////////
//
// 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 = kBufferSize;
                                    
                                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);
}