QTStdCompr.c

//////////
//
//  File:       QTStdCompr.c
//
//  Contains:   Sample code for using QuickTime's standard image compression dialog routines.
//
//  Written by: Tim Monroe
//              Based on existing code by Apple Developer Technical Support, which was itself
//              based on the code in Chapter 3 of Inside Macintosh: QuickTime Components.
//
//  Copyright:  © 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/23/98    rtm     added extended procedures support; everything seems to work
//                                  fine on both Mac and Windows
//     <2>      04/22/98    rtm     revised to personal coding style and made cross-platform
//     <1>      12/04/94    khs     first file
//     
//  This sample code illustrates how to use QuickTime's standard image compression dialog routines
//  to get compression settings from the user and to compress an image using those settings. See
//  Chapter 3 of Inside Macintosh: QuickTime Components for complete information on the standard
//  image compression dialog routines.
//
//  In this sample, we prompt the user to open an image file; then we display the standard image
//  compression dialog box and use the settings selected by the user to compress the image. The 
//  Standard Image Compression Dialog Component currently supports three sources for the image:
//
//      (1) a PICT handle
//      (2) a PICT file
//      (3) a pixel map
//
//  The most general of these is the pixel map, so we'll use that throughout this sample. We can
//  create a pixel map by opening an image file and drawing it into an offscreen graphics world.
//  By using the graphics importer routines, we allow ourselves to handle ANY kind of image file for
//  which QuickTime supplies a graphics importer component. So, for free, we get support for PICT
//  files too.
//
//  This sample also shows how to extend the basic user interface by installing a modal-dialog filter
//  function and a hook function to handle the optional custom button in the dialog box. If you don't
//  want this extended behavior, set gUseExtendedProcs to false.
//
//  NOTES:
//
//  *** (1) ***
//  Using the SCCompressImage function to compress a pixmap using some of the available compression
//  types (for instance, BMP) results in a block of compressed data that does not contain the required
//  headers. As a result, saving that data into a file results in an invalid image file. This is a
//  known limitation of QuickTime 3 and may be fixed in the future. Currently the only way to generate
//  these headers is to use a graphics importer to export the file as a BMP (or whatever) file. This
//  is NOT illustrated in this sample code.
//  
//  *** (2) ***
//  You can use the SCSetInfo function with the scSettingsStateType selector to retrieve a handle
//  containing the current compression settings; this might be useful if you were allowing the user
//  to compress a series of images and wanted to preserve the user's settings from one image to the
//  next (instead of reverting to the defaults for every image). Note, however, that the data in
//  that handle is byte-ordered according to the platform the code is running on. As a result, you
//  should not store that data in a file and expect that file to be valid on other platforms. To
//  get a handle of data in a platform-independent format, use the function SCGetSettingsAsAtomContainer
//  (introduced in QuickTime 3); to restore the settings in that handle, use the related function
//  SCSetSettingsAsAtomContainer.
//  
//////////
 
//////////
//
// header files
//     
//////////
 
#include "QTStdCompr.h"
 
 
//////////
//
// global variables
//     
//////////
 
Boolean                         gUseExtendedProcs = true;   // do we use extended procs with our dialog box?
SCExtendedProcs                 gProcStruct;
 
 
// our application's window-updating function
extern void DoUpdateWindow (WindowRef theWindow, Rect *theRefreshArea);
 
 
//////////
//
// QTStdCompr_PromptUserForImageFileAndCompress
// Let the user select an image file and select its compression settings; then compress it.
//
//////////
 
void QTStdCompr_PromptUserForImageFileAndCompress (void)
{
    SFTypeList                  myTypeList;
    StandardFileReply           myReply;
    Rect                        myRect;
    GraphicsImportComponent     myImporter = NULL;
    ComponentInstance           myComponent = NULL;
    GWorldPtr                   myImageWorld = NULL;        // the graphics world we draw the image in
    PixMapHandle                myPixMap = NULL;
    ImageDescriptionHandle      myDesc = NULL;
    Handle                      myHandle = NULL;
    OSErr                       myErr = noErr;
 
    //////////
    //
    // have the user select an image file
    //
    //////////
 
    // kQTFileTypeQuickTimeImage means any image file readable by GetGraphicsImporterForFile
    myTypeList[0] = kQTFileTypeQuickTimeImage;
 
    StandardGetFilePreview(NULL, 1, myTypeList, &myReply);
    if (!myReply.sfGood)
        goto bail;
    
    //////////
    //
    // get a graphics importer for the image file and determine the natural size of the image
    //
    //////////
 
    myErr = GetGraphicsImporterForFile(&myReply.sfFile, &myImporter);
    if (myErr != noErr)
        goto bail;
    
    myErr = GraphicsImportGetNaturalBounds(myImporter, &myRect);
    if (myErr != noErr)
        goto bail;
        
    //////////
    //
    // create an offscreen graphics world and draw the image into it
    //
    //////////
    
    myErr = NewGWorld(&myImageWorld, 0, &myRect, NULL, NULL, 0L);
    if (myErr != noErr)
        goto bail;
    
    // get the pixmap of the GWorld; we'll lock the pixmap, just to be safe
    myPixMap = GetGWorldPixMap(myImageWorld);
    if (!LockPixels(myPixMap))
        goto bail;
    
    // set the current port and draw the image
    GraphicsImportSetGWorld(myImporter, (CGrafPtr)myImageWorld, NULL);
    GraphicsImportDraw(myImporter);
    
    //////////
    //
    // display the standard image compression dialog box
    //
    //////////
    
    // open the standard compression dialog component
    myComponent = OpenDefaultComponent(StandardCompressionType, StandardCompressionSubType);
    if (myComponent == NULL)
        goto bail;
        
    // set the picture to be displayed in the dialog box; passing NULL for the rect
    // means use the entire image; passing 0 for the flags means to use the default
    // system method of displaying the test image, which is currently a combination
    // of cropping and scaling; personally, I prefer scaling (your milage may vary)
    SCSetTestImagePixMap(myComponent, myPixMap, NULL, scPreferScaling);
 
    // install the custom procs, if requested
    // we can install two kinds of custom procedures for use in connection with
    // the standard dialog box: (1) a modal-dialog filter function, and (2) a hook
    // function to handle the custom button in the dialog box
    if (gUseExtendedProcs)
        QTStdCompr_InstallExtendedProcs(myComponent, (long)myPixMap);
    
    // request image compression settings from the user; in other words, put up the dialog box
    myErr = SCRequestImageSettings(myComponent);
    if (myErr == scUserCancelled)
        goto bail;
 
    //////////
    //
    // compress the image
    //
    //////////
    
    myErr = SCCompressImage(myComponent, myPixMap, NULL, &myDesc, &myHandle);
    if (myErr != noErr)
        goto bail;
 
    //////////
    //
    // save the compressed image in a new file
    //
    //////////
    
    QTStdCompr_PromptUserForDiskFileAndSaveCompressed(myHandle, myDesc);
    
bail:
    if (gUseExtendedProcs)
        QTStdCompr_RemoveExtendedProcs();
 
    if (myPixMap != NULL)
        if (GetPixelsState(myPixMap) & pixelsLocked)
            UnlockPixels(myPixMap);
    
    if (myImporter != NULL)
        CloseComponent(myImporter);
            
    if (myComponent != NULL)
        CloseComponent(myComponent);
 
    if (myDesc != NULL)
        DisposeHandle((Handle)myDesc);
 
    if (myHandle != NULL)
        DisposeHandle(myHandle);
 
    if (myImageWorld != NULL)
        DisposeGWorld(myImageWorld);
}
 
 
//////////
//
// QTStdCompr_PromptUserForDiskFileAndSaveCompressed
// Let the user select a disk file, then write the compressed image into that file.
//
//////////
 
void QTStdCompr_PromptUserForDiskFileAndSaveCompressed (Handle theHandle, ImageDescriptionHandle theDesc)
{
    StandardFileReply           myReply;
    short                       myRefNum = -1;
    StringPtr                   myMoviePrompt = QTUtils_ConvertCToPascalString(kSaveMoviePrompt);
    StringPtr                   myMovieFileName = QTUtils_ConvertCToPascalString(kSaveMovieFileName);
    OSErr                       myErr = noErr;
 
    // do a little sanity-checking....
    if ((theHandle == NULL) || (theDesc == NULL))
        goto bail;
        
    if ((**theDesc).dataSize > GetHandleSize(theHandle))
        goto bail;
 
    // prompt the user for a file to put the compressed image into; in theory, the name
    // should have a file extension appropriate to the type of compressed data selected by the user;
    // this is left as an exercise for the reader
    StandardPutFile(myMoviePrompt, myMovieFileName, &myReply);
    if (!myReply.sfGood)
        goto bail;
    
    HLock(theHandle);
 
    // create and open the file
    myErr = FSpCreate(&myReply.sfFile, kImageFileCreator, (**theDesc).cType, 0);
    
    if (myErr == noErr)
        myErr = FSpOpenDF(&myReply.sfFile, fsRdWrPerm, &myRefNum);
        
    if (myErr == noErr)
        myErr = SetFPos(myRefNum, fsFromStart, 0);
 
    // now write the data in theHandle into the file
    if (myErr == noErr)
        myErr = FSWrite(myRefNum, &(**theDesc).dataSize, *theHandle);
    
    if (myErr == noErr)
        myErr = SetFPos(myRefNum, fsFromStart, (**theDesc).dataSize);
 
    if (myErr == noErr)
        myErr = SetEOF(myRefNum, (**theDesc).dataSize);
                 
    if (myRefNum != -1)
        myErr = FSClose(myRefNum);
        
bail:
    free(myMoviePrompt);
    free(myMovieFileName);
 
    HUnlock(theHandle);
}
 
 
//////////
//
// QTStdCompr_InstallExtendedProcs
// Install the modal-dialog filter function and the hook function.
//
//////////
 
void QTStdCompr_InstallExtendedProcs (ComponentInstance theComponent, long theRefCon)
{
    StringPtr       myButtonTitle = QTUtils_ConvertCToPascalString(kButtonTitle);
 
    // the modal-dialog filter function can be used to handle any events that
    // the standard image compression dialog handler doesn't know about, such
    // as any update events for windows owned by the application
    gProcStruct.filterProc = NewSCModalFilterProc(QTStdCompr_FilterProc);
    
    // the hook function can be used to handle clicks on the custom button
    gProcStruct.hookProc = NewSCModalHookProc(QTStdCompr_ButtonProc);
 
    // in this example, we pass the pixel map handle as a refcon
    gProcStruct.refcon = theRefCon;
    
    // copy the string for our custom button into the extended procs structure
    BlockMove(myButtonTitle, gProcStruct.customName, 9);
 
    // set the current extended procs
    SCSetInfo(theComponent, scExtendedProcsType, &gProcStruct);
    
    free(myButtonTitle);
}
 
 
//////////
//
// QTStdCompr_RemoveExtendedProcs
// Remove the modal-dialog filter function and the hook function.
//
//////////
 
void QTStdCompr_RemoveExtendedProcs (void)
{
    // clear out the extended procedures
    SCSetInfo((ComponentInstance)gProcStruct.refcon, scExtendedProcsType, NULL);
    
    // dispose of routine descriptors
    DisposeRoutineDescriptor(gProcStruct.filterProc);
    DisposeRoutineDescriptor(gProcStruct.hookProc);
}
 
 
//////////
//
// QTStdCompr_FilterProc
// Filter events for a standard modal dialog box. 
//
//////////
 
PASCAL_RTN Boolean QTStdCompr_FilterProc (DialogPtr theDialog, EventRecord *theEvent, short *theItemHit, long theRefCon)
{
#pragma unused(theDialog, theItemHit, theRefCon)
    Boolean         myEventHandled = false;
    WindowRef       myWindow = NULL;
        
    switch (theEvent->what) {
        case updateEvt:
            // update the specified window, if it's behind the modal dialog
            myWindow = (WindowRef)theEvent->message;
            if ((myWindow != NULL) && (myWindow != theDialog)) {
                DoUpdateWindow(myWindow, &(**(myWindow->visRgn)).rgnBBox);
                myEventHandled = false;     // so sayeth IM
            }
            break;
    }
    
    return(myEventHandled);
}
 
 
//////////
//
// QTStdCompr_ButtonProc
// Handle item selections in the standard image compression dialog box.
//
// The theParams parameter is the component instance of the standard image compression
// dialog component. Also, the theRefCon paramter is handle to our pixel map.
//
//////////
 
PASCAL_RTN short QTStdCompr_ButtonProc (DialogPtr theDialog, short theItemHit, void *theParams, long theRefCon)
{
#pragma unused(theDialog)
    // in this sample code, we'll have the settings revert to their default values
    // when the user clicks on the custom button
    if (theItemHit == scCustomItem)
        SCDefaultPixMapSettings(theParams, (PixMapHandle)theRefCon, false);
 
    // always return the item passed in
    return(theItemHit);
}