The Machine Perception Toolbox

[Introduction]- [News]- [Download]- [Screenshots]- [Manual (pdf)]- [Forums]- [API Reference]- [Repository ]

 

Main Page | Namespace List | Class Hierarchy | Alphabetical List | Class List | Directories | File List | Namespace Members | Class Members | File Members | Related Pages

MyQuickDrawView.mm

Go to the documentation of this file.
00001 
00002 //
00003 //      File:           MyQuickDrawView.m
00004 //
00005 //      Contains:       Implementation file for the MyQuickDrawView class
00006 //
00007 //      Original Written by:    Apple Developer Technical Support
00008 //      Modified by:    Ian Fasel -- added Face Detector (MPISearch) support
00009 //
00010 //      Copyright:      ©  2003 Machine Perception Laboratory 
00011 //                      University of California San Diego.
00012 //
00013 //      Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
00014 //
00015 //    1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
00016 //    2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
00017 //    3. The name of the author may not be used to endorse or promote products derived from this software without specific prior written permission.
00018 //
00019 // THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
00020 // 
00022 
00023 #import <QuickTime/QuickTime.h>
00024 #import <Carbon/Carbon.h>
00025 
00026 #import "MyQuickDrawView.h"
00027 #import "MyObject.h"
00028 
00029 static MyQuickDrawView          *myQDViewObject;        // our MyQuickDrawView object
00030 static TimeScale                gTimeScale;                     // time scale for our grabbed video
00031 static TimeValue                gLastTime;                      // time value when a frame was last given to us
00032 static float                    gLastFps=26;
00033 
00034 @implementation MyQuickDrawView
00035 
00036 #define BailErr(x) {err = x; if(err != noErr) goto bail;}
00037 #define BailIfNull(x) {gSeqGrab = x; if(gSeqGrab == nil) goto bail;}
00038 
00039 
00040 
00042 //
00043 // setupDecomp
00044 //
00045 // Code to setup our decompresion sequences. We make
00046 // two, one to decompress to a gworld, and the other
00047 // to decompress to the window 
00048 //
00050 
00051 -(ComponentResult)setupDecomp
00052 {
00053         ComponentResult                 err = noErr;
00054     Rect                                        sourceRect = { 0, 0 }, bounds;
00055     MatrixRecord                        scaleMatrix;    
00056     ImageDescriptionHandle      imageDesc = (ImageDescriptionHandle)NewHandle(0);
00057     PixMapHandle                        hPixMap;
00058     
00059     // Create Face Finder
00060     gFaceFinder = [[FindFacesInGWorld alloc] init];
00061     
00062     /* Set up getting grabbed data into the GWorld */
00063     
00064     // retrieve a channelŐs current sample description, the channel returns a sample description that is
00065     // appropriate to the type of data being captured
00066     err = SGGetChannelSampleDescription(gSGChanVideo,(Handle)imageDesc);
00067     BailErr(err);
00068 
00069     /***** IMPORTANT NOTE *****
00070     
00071         Previous versions of this sample code made an incorrect decompression
00072         request.  Intending to draw the DV frame at quarter-size into a quarter-size
00073         offscreen GWorld, it made the call
00074 
00075         err = DecompressSequenceBegin(..., &rect, nil, ...);
00076 
00077         passing a quarter-size rectangle as the source rectangle.  The correct
00078         interpretation of this request is to draw the top-left corner of the DV
00079         frame cropped at normal size.  Unfortunately, a DV-specific bug in QuickTime
00080         5 caused it to misinterpret this request and scale the frame to fit.
00081 
00082         This bug will be fixed in QuickTime 6.  If your code behaves as intended
00083         because of the bug, you should fix your code to pass a matrix scaling the
00084         frame to fit the offscreen gworld:
00085 
00086         RectMatrix( & scaleMatrix, &dvFrameRect, &gworldBounds );
00087         err = DecompressSequenceBegin(..., nil, &scaleMatrix, ...);
00088     
00089         This approach will work in all versions of QuickTime.
00090                 
00091     **************************/
00092     
00093     // make a scaling matrix for the sequence
00094     sourceRect.right = (**imageDesc).width;
00095     sourceRect.bottom = (**imageDesc).height;
00096     RectMatrix(&scaleMatrix, &sourceRect, &gBoundsRect);
00097             
00098     // begin the process of decompressing a sequence of frames
00099     // this is a set-up call and is only called once for the sequence - the ICM will interrogate different codecs
00100     // and construct a suitable decompression chain, as this is a time consuming process we don't want to do this
00101     // once per frame (eg. by using DecompressImage)
00102     // for more information see Ice Floe #8 http://developer.apple.com/quicktime/icefloe/dispatch008.html
00103     // the destination is specified as the GWorld
00104     err = DecompressSequenceBegin(&gDecomSeq,   // pointer to field to receive unique ID for sequence
00105                                     imageDesc,          // handle to image description structure
00106                                     gPGWorld,           // port for the DESTINATION image
00107                                     NULL,                       // graphics device handle, if port is set, set to NULL
00108                                     NULL,                       // source rectangle defining the portion of the image to decompress
00109                                     &scaleMatrix,       // transformation matrix
00110                                     srcCopy,            // transfer mode specifier
00111                                     NULL,                       // clipping region in dest. coordinate system to use as a mask
00112                                     0,                                          // flags
00113                                     codecNormalQuality,         // accuracy in decompression
00114                                     bestSpeedCodec);            // compressor identifier or special identifiers ie. bestSpeedCodec
00115     BailErr(err);
00116     
00117     DisposeHandle((Handle)imageDesc);
00118     imageDesc = NULL;
00119     
00120     /* Set up getting grabbed data into the Window */
00121     hPixMap = GetGWorldPixMap(gPGWorld);
00122     GetPixBounds(hPixMap,&bounds);
00123     gDrawSeq = 0;
00124     
00125     // returns an image description for the GWorlds PixMap
00126     // on entry the imageDesc is NULL, on return it is correctly filled out
00127     // you are responsible for disposing it
00128     err = MakeImageDescriptionForPixMap(hPixMap, &imageDesc);
00129     BailErr(err);
00130     
00131     gImageSize = (GetPixRowBytes(hPixMap) * (*imageDesc)->height); // ((**hPixMap).rowBytes & 0x3fff) * (*desc)->height;
00132     
00133     // begin the process of decompressing a sequence of frames - see above notes on this call.
00134     // destination is specified as the QuickDraw port for our NSView
00135     err = DecompressSequenceBegin(&gDrawSeq,
00136                                     imageDesc,
00137                                     [self qdPort],      // Use the QuickDraw port for our NSView as destination!
00138                                     NULL,
00139                                     &bounds,
00140                                     NULL,
00141                                     ditherCopy,
00142                                     NULL,
00143                                     0,
00144                                     codecNormalQuality,
00145                                     anyCodec);
00146     BailErr(err);
00147 
00148 bail:
00149 
00150     if (imageDesc)
00151         DisposeHandle((Handle)imageDesc);
00152     
00153     return (err);
00154 }
00155 
00156 
00158 //
00159 // decompToWindow
00160 //
00161 // Decompress an image to our window (the QuickDraw port for
00162 // our NSView)
00163 //
00165 
00166 -(ComponentResult)decompToWindow
00167 {
00168         ComponentResult err = noErr;
00169     CodecFlags          ignore;
00170 
00171     err = DecompressSequenceFrameS(gDrawSeq,                                                    // sequence ID returned by DecompressSequenceBegin
00172                                 GetPixBaseAddr(GetGWorldPixMap(gPGWorld)),      // pointer to compressed image data
00173                                 gImageSize,                                                                     // size of the buffer
00174                                 0,                                                                                      // in flags
00175                                 &ignore,                                                                        // out flags
00176                                 NULL);                                                                          // async completion proc
00177     return err;
00178 }
00179 
00181 //
00182 // doDecomp
00183 //
00184 // Setup and run our decompression sequence, plus display
00185 // frames-per-second data to our window
00186 //
00188 
00189 -(void)doDecomp:(NSRect)rect
00190 {    
00191     if(gPGWorld) 
00192     {
00193         if (gDecomSeq == 0) 
00194         {
00195             [self setupDecomp];
00196         }
00197         else
00198         {
00199             [self decompToWindow];
00200         }
00201     }
00202 }
00203 
00205 //
00206 // drawRect
00207 //
00208 // Overridden by subclasses of NSView to draw the receiver's 
00209 // image within aRect. It's here we decompress our frames
00210 // to the window for display
00211 //
00213 
00214 -(void)drawRect:(NSRect)rect
00215 {
00216     [self doDecomp:rect];
00217 }
00218 
00219 
00221 //
00222 // sgIdleTimer
00223 //
00224 // A timer whose purpose is to call the SGIdle function
00225 // to provide processing time for our sequence grabber
00226 // component. 
00227 //
00229 
00230 -(void) sgIdleTimer:(id)sender
00231 {
00232     OSErr err;
00233 
00234     err = SGIdle(gSeqGrab);
00235     /* put up an error dialog to display any errors */
00236     if (err != noErr)
00237     {
00238         NSString *errorStr = [[NSString alloc] initWithFormat:@"%d" , err];
00239         int choice;
00240 
00241         // some error specific to SGIdle occurred - any errors returned from the
00242         // data proc will also show up here and we don't want to write over them
00243         
00244         // in QT 4 you would always encounter a cDepthErr error after a user drags
00245         // the window, this failure condition has been greatly relaxed in QT 5
00246         // it may still occur but should only apply to vDigs that really control
00247         // the screen
00248         
00249         // you don't always know where these errors originate from, some may come
00250         // from the VDig...
00251                         
00252         /* now display error dialog and quit */
00253         choice = NSRunAlertPanel(@"Error", errorStr, @"OK", nil, nil);
00254         [errorStr release];
00255                         
00256         // ...to fix this we simply call SGStop and SGStartRecord again
00257         // calling stop allows the SG to release and re-prepare for grabbing
00258         // hopefully fixing any problems, this is obviously a very relaxed
00259         // approach
00260 
00261         SGStop(gSeqGrab);
00262         SGStartRecord(gSeqGrab);
00263     }
00264 
00265 }
00266 
00267 
00269 //
00270 // doSeqGrab
00271 //
00272 // Initialize the Sequence Grabber, create a new
00273 // sequence grabber channel, create an offscreen
00274 // GWorld for use with our decompression sequence,
00275 // then begin recording. We also setup a timer to
00276 // idle the sequence grabber
00277 //
00279 
00280 -(OSErr) doSeqGrab:(NSRect)grabRect
00281 {
00282     // Setup Face Detector to find faces
00283     [gFaceFinder InitializeStreamWithWidth:grabRect.size.width Height:grabRect.size.height];
00284 
00285     OSErr       err = noErr;
00286     
00287     gTimeScale  = 0;
00288     gLastTime   = 0;
00289 
00290     /* initialize the movie toolbox */
00291     err = EnterMovies();
00292     BailErr(err);
00293     
00294     // open the sequence grabber component and initialize it
00295     gSeqGrab = OpenDefaultComponent(SeqGrabComponentType, 0);
00296     BailIfNull(gSeqGrab);
00297     
00298     err = SGInitialize(gSeqGrab);
00299     BailErr(err);
00300     
00301         // specify the destination data reference for a record operation
00302         // tell it we're not making a movie
00303         // if the flag seqGrabDontMakeMovie is used, the sequence grabber still calls
00304         // your data function, but does not write any data to the movie file
00305         // writeType will always be set to seqGrabWriteAppend
00306     err = SGSetDataRef(gSeqGrab, 0, 0, seqGrabDontMakeMovie);
00307     BailErr(err);
00308 
00309     // create a new sequence grabber video channel 
00310     err = SGNewChannel(gSeqGrab, VideoMediaType, &gSGChanVideo);
00311     BailErr(err); 
00312 
00313     gBoundsRect.top     = (short int)grabRect.origin.y;
00314     gBoundsRect.left    = (short int)grabRect.origin.x;
00315     gBoundsRect.bottom  = (short int)grabRect.size.height;
00316     gBoundsRect.right   = (short int)grabRect.size.width;
00317     err = SGSetChannelBounds(gSeqGrab, &gBoundsRect); 
00318     
00319     // create the GWorld
00320     err = QTNewGWorld(&gPGWorld,        // returned GWorld
00321                                   k32ARGBPixelFormat,           // pixel format
00322                                   &gBoundsRect,                         // bounding rectangle
00323                                   0,                                            // color table
00324                                   NULL,                                         // graphic device handle
00325                                   0);                                           // flags
00326     BailErr(err);
00327    
00328     // lock the pixmap and make sure it's locked because
00329     // we can't decompress into an unlocked PixMap
00330     if(!LockPixels(GetPortPixMap(gPGWorld)))
00331     {
00332         BailErr(-1);
00333     }
00334 
00335     err = SGSetGWorld(gSeqGrab, gPGWorld, GetMainDevice());
00336     BailErr(err);
00337 
00338     // set the bounds for the channel
00339     err = SGSetChannelBounds(gSGChanVideo, &gBoundsRect);
00340     BailErr(err);
00341     
00342     // set the usage for our new video channel to avoid playthrough
00343     // note: we do not set seqGrabPlayDuringRecord because if you set this flag
00344     // the data from the channel may be played during the record operation,
00345     // if the destination buffer is onscreen. However, playing the
00346     // data may affect the quality of the recorded sequence by causing frames 
00347     // to be dropped...something we definitely want to avoid
00348     err = SGSetChannelUsage(gSGChanVideo, seqGrabRecord);
00349     BailErr(err);
00350     
00351     // specify a data function for use by the sequence grabber
00352     // whenever any channel assigned to the sequence grabber writes data,
00353     // this data function is called and may then write the data to another destination
00354     err = SGSetDataProc(gSeqGrab,NewSGDataUPP(&mySGDataProc),NULL);
00355     BailErr(err);
00356 
00357     /* lights...camera... */
00358     err = SGPrepare(gSeqGrab,false,true);
00359     BailErr(err);
00360     
00361     // start recording!!
00362     err = SGStartRecord(gSeqGrab);
00363     BailErr(err);
00364 
00365     /* setup a timer to idle the sequence grabber */
00366     gMyTimer = // interval, 0.1 seconds// call this method[[NSTimer scheduledTimerWithTimeInterval:0.005f               
00367                         target:self
00368                         selector:@selector(sgIdleTimer:)                
00369                         userInfo:nil
00370                         repeats:YES] retain];                                   // repeat until we cancel it
00371     
00372 bail:
00373 
00374         return err;
00375 }
00376 
00378 //
00379 // gworld
00380 //
00381 // Accessor method for the gPGWorld class variable
00382 //
00384 
00385 -(GWorldPtr)gworld
00386 {
00387     return gPGWorld;
00388 }
00389 
00391 //
00392 // FindFaces 
00393 //
00394 // Find faces using the built-in face finder
00395 //
00397 
00398 -(void)FindFaces
00399 {
00400     [gFaceFinder FindFaces:gPGWorld];
00401 }
00402 
00403 
00405 //
00406 // decomSeq
00407 //
00408 // Accessor method for the gDecomSeq class variable
00409 //
00411 
00412 -(ImageSequence)decomSeq
00413 {
00414     return gDecomSeq;
00415 }
00416 
00418 //
00419 // drawSeq
00420 //
00421 // Accessor method for the gDrawSeq class variable
00422 //
00424 
00425 -(ImageSequence)drawSeq
00426 {
00427     return gDrawSeq;
00428 }
00429 
00431 //
00432 // sgChanVideo
00433 //
00434 // Accessor method for the gSGChanVideo class variable
00435 //
00437 
00438 -(SGChannel)sgChanVideo
00439 {
00440     return gSGChanVideo;
00441 }
00442 
00444 //
00445 // boundsRect
00446 //
00447 // Accessor method for the boundsRect class variable
00448 //
00450 
00451 -(Rect)boundsRect
00452 {
00453     return gBoundsRect;
00454 }
00455 
00457 //
00458 // endGrab
00459 //
00460 // Perform clean-up when we are finished recording
00461 //
00463 
00464 -(void)endGrab
00465 {
00466     ComponentResult result;
00467     OSErr                       err;
00468 
00469     // kill our sequence grabber idle timer first
00470     [gMyTimer invalidate];
00471     [gMyTimer release];
00472     
00473     // stop recording
00474     SGStop(gSeqGrab);
00475 
00476     // end our decompression sequences
00477     err = CDSequenceEnd(gDecomSeq);
00478     err = CDSequenceEnd(gDrawSeq);
00479 
00480     // finally, close our sequence grabber component
00481     result = CloseComponent(gSeqGrab);
00482     
00483     // get rid of our gworld
00484     DisposeGWorld(gPGWorld);
00485 }
00486 
00487 @end
00488 
00489 
00490 /* ---------------------------------------------------------------------- */
00491 /* sequence grabber data procedure - this is where the work is done       */
00492 /* ---------------------------------------------------------------------- */
00493 /* mySGDataProc - the sequence grabber calls the data function whenever
00494    any of the grabberŐs channels write digitized data to the destination movie file.
00495    
00496    NOTE: We really mean any, if you have an audio and video channel then the DataProc will
00497                  be called for either channel whenever data has been captured. Be sure to check which
00498                  channel is being passed in. In this example we never create an audio channel so we know
00499                  we're always dealing with video.
00500    
00501    This data function decompresses captured video data into an offscreen GWorld,
00502    then transfers the frame to an onscreen window.
00503    
00504    For more information refer to Inside Macintosh: QuickTime Components, page 5-120
00505    c - the channel component that is writing the digitized data.
00506    p - a pointer to the digitized data.
00507    len - the number of bytes of digitized data.
00508    offset - a pointer to a field that may specify where you are to write the digitized data,
00509                         and that is to receive a value indicating where you wrote the data.
00510    chRefCon - per channel reference constant specified using SGSetChannelRefCon.
00511    time - the starting time of the data, in the channelŐs time scale.
00512    writeType - the type of write operation being performed.
00513                 seqGrabWriteAppend - Append new data.
00514                 seqGrabWriteReserve - Do not write data. Instead, reserve space for the amount of data
00515                                     specified in the len parameter.
00516                 seqGrabWriteFill - Write data into the location specified by offset. Used to fill the space
00517                                     previously reserved with seqGrabWriteReserve. The Sequence Grabber may
00518                                     call the DataProc several times to fill a single reserved location.
00519    refCon - the reference constant you specified when you assigned your data function to the sequence grabber.
00520 */
00521 
00522 
00523 pascal OSErr mySGDataProc(SGChannel c, 
00524                             Ptr p,
00525                             long len,
00526                             long *offset,
00527                             long chRefCon,
00528                             TimeValue time,
00529                             short writeType, 
00530                             long refCon)
00531 {
00532 #pragma unused(offset,chRefCon,time,writeType)
00533     
00534     CodecFlags          ignore;
00535     ComponentResult     err = noErr;
00536     CGrafPtr            theSavedPort;
00537     GDHandle            theSavedDevice;
00538     char                status[64];
00539     Str255              theString;
00540     Rect                bounds;
00541     float               fps;
00542     
00543     /* grab the time scale for use with our fps calculations - but this 
00544         needs to be done only once */
00545     if (gTimeScale == 0)
00546     {        
00547         err = SGGetChannelTimeScale([myQDViewObject sgChanVideo], &gTimeScale);
00548         BailErr(err);
00549     }
00550     
00551     if([myQDViewObject gworld]) 
00552     {
00553         // decompress a frame into the GWorld - can queue a frame for async decompression when passed in a completion proc
00554         // once the image is in the GWorld it can be manipulated at will
00555         err = DecompressSequenceFrameS([myQDViewObject decomSeq],       // sequence ID returned by DecompressSequenceBegin
00556                                                                         p,                                              // pointer to compressed image data
00557                                                                         len,                                    // size of the buffer
00558                                                                         0,                                              // in flags
00559                                                                         &ignore,                                // out flags
00560                                                                         NULL);                                  // async completion proc
00561         BailErr(err);
00562 
00563         // ******  IMAGE IS NOW IN THE GWORLD ****** //
00564 
00565         }
00566     
00567     /* compute and display frames-per-second */
00568     [myQDViewObject FindFaces];
00569     GetGWorld(&theSavedPort, &theSavedDevice);
00570     SetGWorld([myQDViewObject gworld], NULL);
00571     TextSize(12);
00572     TextMode(srcCopy);
00573     bounds = [myQDViewObject boundsRect];
00574     MoveTo(bounds.left, bounds.bottom-3);
00575     fps = (float)((float)gTimeScale / (float)(time - gLastTime));
00576     fps = 0.03f*fps + 0.97f*gLastFps;
00577     //sprintf(status, "fps:%5.1f", fps);
00578     sprintf(status, "fps:%2.0f", fps);
00579     CopyCStringToPascal(status, theString);
00580     DrawString(theString);
00581     SetGWorld(theSavedPort, theSavedDevice);
00582     
00583     /* remember current time, so next time this routine is called
00584         we can compute the frames-per-second */
00585     gLastTime = time;
00586     gLastFps = fps;
00587     /* calling the display method will invoke this NSView's lockFocus, drawRect and unlockFocus methods as necessary.
00588         Our drawRect method (above) is used to decompress one of a sequence of frames. This method draws the image
00589         back to the window from the GWorld and could be used as a "preview" */
00590     [myQDViewObject display];
00591 bail:
00592     
00593         return err;
00594 }
00595 
00597 //
00598 // saveQDViewObjectForCallback
00599 //
00600 // This routine stores a reference to our MyQuickDrawView object. We'll
00601 // need this so we can call into methods in this class from outside the
00602 // implementation of the class methods (specifically, from our SGDataProc
00603 // C routine above).
00604 //
00606 
00607 void saveQDViewObjectForCallback(void *theObject)
00608 {
00609    myQDViewObject = (MyQuickDrawView *)theObject;
00610 }
00611 
00612 

Generated on Mon Nov 8 17:07:46 2004 for MPT by  doxygen 1.3.9.1