home *** CD-ROM | disk | FTP | other *** search
/ Celestin Apprentice 4 / Apprentice-Release4.iso / Source Code / C++ / Snippets / QuickCamObjects / CQuickCam.cp < prev    next >
Encoding:
Text File  |  1995-12-05  |  14.7 KB  |  507 lines  |  [TEXT/CWIE]

  1. //    © Paul B. Beeken, Work In Progress, 1994-5
  2. //    Knowledge Software Consulting.
  3. //
  4. //    Please, please, please, in the unlikely event you should use this stuff
  5. //    for some commercial application I would appreciate you contacting me.  If
  6. //    its for your own use, use away. Send email: knowsoft@ios.com
  7. //    My personal philosophy closely adheres to that of GNU software.  I offer this
  8. //    in the hope that others will improve and expand upon it.  Post your additions,
  9. //    leave this notice in place and add your own comments.
  10. //
  11. //    As always: this file is presented as is with no warrantees expressed or implied.
  12. //    Swim at your own risk, etc. etc.
  13. //
  14. //    CQuickCam
  15. //
  16. //    A c++ object library for the Connectix QuickCam using the
  17. //    standardized vdig functions outlined in the Ch. 8 of QTComponents.
  18. //    Yeah, you could do much of this seamlessly with Ch. 6 but with alot
  19. //    of loss of control and performance.  Some considerable experimentation
  20. //    has gone into this object and many related threads along this line.
  21. //    I think this provides the best trade off of flexability and control
  22. //
  23. //    Notes in general:  There are sometimes many ways to handle the 
  24. //        output of a vdig.  It can preview directly to the screen (if it is
  25. //        a cGrafPort) or to a pixmap.  Multiple buffering is an option and
  26. //        can be a very effective method given the serial data stream nature of 
  27. //        this kind of digitizer.  It can allow the greatest flexability
  28. //        and smoothness of updating.  It may not be the best solution for a
  29. //        particular application.
  30. //
  31. //    QuickCam in particular:
  32. //        The B&W camera is a 4 bit machine whose vdig doesn't do DMA.
  33. //        This object always buffers the data and the instatiator is responsible
  34. //        for updating.  Previewing is done by copying the GWorld pixmap to a 
  35. //        rectangle in a given cGrafport.  There are a couple of ways to accumulate
  36. //        data to the pixMap.  One is to use a buffers and the other is to write
  37. //        to a pixmap.  The former allows async grabs while the later does not.
  38. //        The routines outlined here allow either.  By specifying 0 for buffers,
  39. //        the vdig writes directly to the pixmap without any overhead associated
  40. //        with buffers.  Any value from 1 to 3 will allow you to write to different
  41. //        rectangles in the same pixmap but  will allow async grabs.  This may
  42. //        allow for some performance imporvements.
  43. //        The QuickCam vdig doesn't seem to like more than 3 buffers.  Why would you
  44. //        need more?  Actually this makes sense.  One would be the writethrubuffer
  45. //        the other two are the alternating capture buffers.
  46. //
  47. //    Current version 0.8        © Paul B. Beeken, Knowledge Software Consulting.
  48. //
  49. //    11/08/95    Finished basic object after creating various types of LPane
  50. //            derivatives.  This object borrows the from the best of them.  Its
  51. //            creation also eliminates the dependance on MW PowerPlant.
  52. //    12/04/95    Completed spot metering and fixed a memory leak.
  53. //
  54. //    Wishlist:    Sequential capture to moov.
  55. //                Include methods for capturing from a subset rect in vdig bounds.
  56. //                Motion triggering.
  57.  
  58. /*
  59.         Digitizer Information:
  60.         • vdig type: 0  Basic
  61.         • inputCapabilities: 
  62.             digiInDoesNTSC            digiInDoesBW
  63.         • outputCapabilities: 
  64.             digiOutDoes1            digiOutDoes2
  65.             digiOutDoes4            digiOutDoes8
  66.             digiOutDoes16            digiOutDoes32
  67.             digiOutDoesQuarter        digiOutDoesSixteenth
  68.             digiOutDoesAsyncGrabs
  69.         • min frame dimentions: 
  70.             hor: 1  ver: 1
  71.         • max frame dimentions: 
  72.             hor: 240    ver: 320
  73.         • blend levels: 
  74.             blev: 0
  75.         • preferred device: 
  76.             none
  77. */
  78.  
  79. #include    "CQuickCam.h"
  80. #include    <limits.h>
  81.  
  82. #pragma mark    • Creation and Destruction operators
  83. //    Creation routine.
  84. CQuickCam::CQuickCam( short inSrc, short nbuffers  )
  85. {
  86.     SpotMeter( UINT_MAX/2 ); // set the middle as the default
  87.     
  88.     if ( nbuffers>3 ) nbuffers = 3;  // QuickCam doesn't support more than 3.
  89.     // If you have a QuickCam, you've got QT.  If you want to
  90.     //    test for it do so before creating an instance of me.
  91.  
  92.     // Instantiate a component
  93.     ComponentDescription    theDesc;
  94.     
  95.     // Find and open a sequence grabber
  96.     theDesc.componentType            = videoDigitizerComponentType;
  97.     theDesc.componentSubType        = 'CtxV';    // BW QuickCam
  98.     theDesc.componentManufacturer    = 'Ctx6';    // Connectix
  99.     theDesc.componentFlags            = nil;
  100.     theDesc.componentFlagsMask        = nil;
  101.  
  102.     try {
  103.             ComponentResult            rc            = noErr;
  104.             Component                sgCompID    = nil;
  105.         
  106.             // Find a vdig component, I'll take the first one.
  107.         sgCompID = FindNextComponent(nil, &theDesc);
  108.         if ( sgCompID == nil ) throw cantFindHandler;
  109.         
  110.             // Open the component, This may fail for two reasons.
  111.             //    1. a problem with the vdig or
  112.             //    B. the vdig is already instantiated and being used elsewhere.
  113.         vdig = OpenComponent(sgCompID);
  114.         if ( vdig == nil ) throw cantOpenHandler;
  115.  
  116.             // Fill in my info record.
  117.         rc = VDGetDigitizerInfo( vdig, &vdigInfo );
  118.         if ( rc!=noErr ) throw rc;
  119.             
  120.         }
  121.  
  122.     catch(  ComponentResult rc ) {
  123.         CloseComponent(vdig);
  124.         vdig = nil;
  125.         throw rc;    // for a superior handler.
  126.         }
  127.  
  128.     // start by assuming the maximum frame.
  129.     ::SetRect( &videoFrame, 0, 0, vdigInfo.maxDestWidth, vdigInfo.maxDestHeight );
  130.  
  131.     // Initialize all phases of the digitizer    
  132.     VDSetFrameRate( vdig, 0 );
  133.     VDSetInputStandard( vdig, ntscIn );
  134.     VDSetInput( vdig, inSrc );
  135.     
  136.     VDSetDigitizerRect( vdig, &videoFrame );
  137.  
  138.     // And an identity matrix
  139.     SetIdentityMatrix( &videoMatrix );
  140.  
  141.     // Set up the buffers...
  142.     SetUpBuffers( nbuffers );
  143.  
  144. }
  145.  
  146. //    Destruction routine.
  147. CQuickCam::~CQuickCam( void )
  148. {
  149.     // Clean up
  150.     if (vdig != nil) {
  151.         ClearUpBuffers();
  152.         CloseComponent( vdig );
  153.         vdig = nil;
  154.         }
  155. }
  156.  
  157. #pragma mark    -
  158. #pragma mark    • Public Methods
  159. void
  160. CQuickCam::Brightness( unsigned short b )
  161. {
  162.     if ( !vdig ) throw cantFindHandler;
  163.     VDSetBrightness( vdig, &b );
  164. }
  165.  
  166. unsigned short
  167. CQuickCam::Brightness( void )
  168. {
  169.         unsigned short    b = 0;
  170.  
  171.     if ( !vdig ) throw cantFindHandler;
  172.     VDGetBrightness( vdig, &b );
  173.         
  174.     return b;
  175. }
  176.  
  177. void
  178. CQuickCam::SpotMeter( unsigned short b )
  179. {    // set the target spot meter
  180.     bright = float(b) / float(UINT_MAX);
  181. }
  182.  
  183. void
  184. CQuickCam::SpotMeter( const Rect& r )
  185. {
  186.     // Given a rectangle r = to or inside the video rect
  187.     //  This function will automatically set the brightness.
  188.     //    N.B. a single call to this function won't do the trick.
  189.     //        the "servo" needs a couple of passes to stabilize.
  190.     //        Moreover, this function is not optimized to run fast
  191.     //        so it isn't intended to run all the time.  You might
  192.     //        connect it to some user interface like a button for
  193.     //        momentary adjustment for example.
  194.     unsigned short    b;
  195.     long            avgPixel = 0L;
  196.  
  197.     if ( bufferIndex < 0 )    // no buffers.
  198.         b = 0;
  199.     else {                    // buffers.
  200.         // Set b to the previous buffer index
  201.         b = (bufferIndex==0 ? bufferCount : bufferIndex) - 1 ;
  202.     
  203.         // wait for the buffer to be filled.
  204.         if ( vdig )
  205.             while( !VDDone( vdig, b ) );
  206.         }
  207.  
  208.     // No buffer, no metering.
  209.     if ( gWorld ) {
  210.         Rect    bufferFrame = videoFrame;
  211.         Rect    mapFrame    = r;
  212.         // Move it to the buffer's position.
  213.         ::OffsetRect( &bufferFrame, 0, b * vdigInfo.maxDestHeight );
  214.         MapRect(&mapFrame,&videoFrame,&bufferFrame); 
  215.  
  216.         PixMapHandle    pm = GetGWorldPixMap(gWorld);
  217.  
  218.         LockPixels( pm );    // Lock 'em
  219.         {    // guarantee _base_ never gets used outside Locked PixMap
  220.             Ptr        base        = GetPixBaseAddr( pm );
  221.             short    rowBytes    = (**pm).rowBytes & 0x4FFF;
  222.                         
  223.             for( int h=r.left; h < r.right; h++ )
  224.                 for ( int v=r.top; v < r.bottom; v++ ) {
  225.                     avgPixel +=  0xF & *(base+h+(v*rowBytes));
  226.                     avgPixel +=  0xF & (*(base+h+(v*rowBytes)))>>4;
  227.                     }
  228.             }
  229.         UnlockPixels( pm );    // Unlock 'em
  230.         
  231.         // Servo 101, develop a position error signal.
  232.         //    0.    avgPixel is maximum for a dark screen, minimum for a white screen.
  233.         //    1.  This is the avg brightness in range from 0 ≤ <avgPix> ≤ 1, 1 is white.
  234.         float    avgPix = 1.0 - float(avgPixel)/(30.0*float(r.bottom-r.top)*float(r.right-r.left));
  235.         //                 1 -> see 0 above        30 -> 2 pixels / byte * 15 maxSig / pixel.
  236.         //    2. develop target error signal
  237.         float    error = bright - avgPix;
  238.         //    now if we are spot on (pun intended) then error is 0 and we do nothing.
  239.         //        if it is < 0 then we decrease the vdig brightness.
  240.         //        if it is > 0 then we increase the vdig brightness.
  241.         //        -1.0 ≤ error ≤ 1.0
  242.         // Develop a factor which allows for a complete metering
  243.         error *= 0.9;     // dampenning factor (arb. number)
  244.         if ( error*error > 0.05 ) { // tolerance threshold for control. (arb. number)
  245.             unsigned short brightFactor = Brightness();  // current value
  246.             if ( error > 0 )
  247.                 brightFactor += (UINT_MAX-brightFactor)*error;    // error is positive
  248.             else
  249.                 brightFactor += brightFactor * error;            // error is negative or zero
  250.             // reset brightness.
  251.             Brightness( brightFactor );
  252.             }
  253.         // There are a lot of floating calculations in here.  That may seem scary and ripe for
  254.         // optimization, but these calculations aren't the speed bottleneck for this function.
  255.         }    
  256. }
  257.  
  258. void
  259. CQuickCam::InputSource( short src )
  260. {
  261.     if ( !vdig ) throw cantFindHandler;
  262.     VDSetInput( vdig, src );
  263. }
  264.  
  265. short
  266. CQuickCam::InputSource( void )
  267. {
  268.         short    b = 0;
  269.  
  270.     if ( !vdig ) throw cantFindHandler;
  271.     VDGetInput( vdig, &b );
  272.         
  273.     return b;
  274. }
  275.  
  276. void
  277. CQuickCam::SetDefaults( void )
  278. {
  279.     unsigned short    def[7];
  280.     if ( !vdig ) throw cantFindHandler;
  281.     
  282.     VDGetVideoDefaults( vdig, &def[0], &def[1], &def[2], 
  283.                         &def[3], &def[4], &def[5], &def[6] );
  284.     Brightness( def[2] );  // The only value we have any control over.
  285. }
  286.  
  287.  
  288. void
  289. CQuickCam::UpdateVideo( void )
  290. {
  291.     if ( !vdig ) throw cantFindHandler;
  292.  
  293.     if ( bufferIndex < 0 )
  294.         VDGrabOneFrame( vdig );
  295.     else {
  296.         VDGrabOneFrameAsync( vdig, bufferIndex++ );
  297.         bufferIndex = bufferIndex % bufferCount;
  298.         }
  299.  
  300.     // Update this information.
  301.     VDGetCurrentFlags( vdig, &vdigInfo.inputCurrentFlags, &vdigInfo.outputCurrentFlags );
  302. }
  303.  
  304. Rect
  305. CQuickCam::OptVideoRect( const Rect& r )
  306. {
  307.  
  308.     ComponentResult result;
  309.     short    height    =    r.bottom - r.top;
  310.     short    width    =    r.right  - r.left;
  311.         
  312.     if ( !vdig ) throw cantFindHandler;
  313.  
  314.     // Adjust the videoRectangle to reflect a smaller pane.
  315.     // Smaller videoRectangle updates faster?   No no no!
  316.     //    scaling the video with buffering will not accelerate
  317.     //    the updating.  We need to figure out the scaling.
  318.     long    scale = 8;
  319.     if ( vdigInfo.maxDestHeight <= 4*height &&
  320.          vdigInfo.maxDestWidth <= 4*width )    scale = 4;
  321.  
  322.     if ( vdigInfo.maxDestHeight <= 2*height &&
  323.          vdigInfo.maxDestWidth <= 2*width )    scale = 2;
  324.  
  325.     if ( vdigInfo.maxDestHeight <= height &&
  326.              vdigInfo.maxDestWidth <= width )    scale = 1;
  327.  
  328.     Rect    origSize = videoFrame;
  329.     ::SetRect( &videoFrame, 0, 0, vdigInfo.maxDestWidth/scale, vdigInfo.maxDestHeight/scale );
  330.  
  331.     // And an identity matrix
  332.     SetIdentityMatrix( &videoMatrix );
  333.     // Scale the matrix appropriately
  334.     ScaleMatrix( &videoMatrix, FixRatio(1,scale), FixRatio(1,scale), 0, 0);
  335.  
  336.         short n = bufferCount;
  337.     ClearUpBuffers();        // Clear the old buffer
  338.     SetUpBuffers( n );        // Set the new buffers
  339.  
  340.     Rect rr = videoFrame;    // for adjustment and return.
  341.     ::OffsetRect( &rr, r.left, r.top );
  342.     return rr;
  343.  
  344. }
  345.  
  346.  
  347. #pragma mark    -
  348. #pragma mark    • Action methods    
  349.  
  350. PicHandle
  351. CQuickCam::GrabPict( void )
  352. {
  353.     OpenCPicParams    pp;
  354.     
  355.     pp.srcRect = videoFrame;
  356.     pp.hRes =  0x00480000;
  357.     pp.vRes =  0x00480000;
  358.     pp.version = -2;
  359.     pp.reserved1 = 0;
  360.     pp.reserved2 = 0;
  361.     
  362.         GWorldPtr        savePort;
  363.         GDHandle        saveGD;
  364.         
  365.     GetGWorld( &savePort, &saveGD );
  366.     SetGWorld( gWorld, nil );
  367.     
  368.     PicHandle p = OpenCPicture( &pp );
  369.  
  370.     UpdateVideo();
  371.     DrawVideo( gWorld, videoFrame );
  372.  
  373.     ClosePicture();
  374.  
  375.     SetGWorld( savePort, saveGD );
  376.  
  377.     
  378.     return p;
  379.     
  380. }
  381. void
  382. CQuickCam::DrawVideo( CGrafPtr gw, const Rect& r )
  383. {
  384.     short b;
  385.  
  386.     if ( bufferIndex < 0 )    // no buffers.
  387.         b = 0;
  388.     else {                    // buffers.
  389.         // Set b to the previous buffer index
  390.         b = (bufferIndex==0 ? bufferCount : bufferIndex) - 1 ;
  391.     
  392.         // wait for the buffer to be filled.
  393.         if ( vdig )
  394.             while( !VDDone( vdig, b ) );
  395.         }
  396.  
  397.     // if we have a display gWorld
  398.     // To handle multiple buffers I need to devlop a scheme for addressing
  399.     //    the buffers through their respective rectangles.  Given an index
  400.     //    I need to map the rectangle to the pixMap.  I've chosen to map them
  401.     //    vertically.
  402.     if ( gWorld ) {
  403.         Rect    bufferFrame = videoFrame;
  404.         
  405.         // Move it to the buffer's position.
  406.         ::OffsetRect( &bufferFrame, 0, b * vdigInfo.maxDestHeight );
  407.         
  408.         LockPixels( GetGWorldPixMap(gWorld) );
  409.         CopyBits(    &((GrafPtr)gWorld)->portBits,    &((GrafPtr)gw)->portBits,
  410.                     &bufferFrame,                     &r,
  411.                     srcCopy, nil );
  412.         UnlockPixels( GetGWorldPixMap(gWorld) );
  413.         }
  414. }
  415.  
  416. DigitizerInfo
  417. CQuickCam::GetInfoRecord( void )
  418. {
  419.     return vdigInfo;
  420. }
  421.  
  422. #pragma mark    -
  423. #pragma mark    • Protected methods    
  424.  
  425. void
  426. CQuickCam::SetUpBuffers( short n )
  427. {
  428.     // The videoFrame contains the rectangle required for capture.
  429.         VideoDigitizerError rc;
  430.     //    I propose the following scheme for buffering:
  431.     //        Allocate one GWorld with a huge pixMap  Width x n Height
  432.     //        rectangle.  Define the pixMaps in the buffer array to be
  433.     //        the same pm and different location to start points along
  434.     //        the way.  • I assume that videoFrame is set to reflect the
  435.     //        area to be digitized.
  436.  
  437.     // BW QuickCam captures 16 levels of gray choose an appropos clut.
  438.         short    depth = 4;
  439.         Rect    bufferFrame;
  440.  
  441.     gWorld = nil;
  442.     
  443.     // use the current video frame.
  444.     bufferCount = n;
  445.     bufferFrame = videoFrame;
  446.     if ( n > 0 ) // if we want a buffer, set aside a big enough area.
  447.         bufferFrame.bottom *= n;
  448.  
  449.     // We want a GWorld in any case.
  450.     CTabHandle    ct    = GetCTable(32+depth);
  451.     NewGWorld( &gWorld, depth, &bufferFrame, ct, nil, 0 );
  452.     DisposeHandle( (Handle) ct );
  453.     if ( gWorld == nil ) throw dsMemFullErr;
  454.  
  455.     if ( n > 0 ) { // Yes we want to buffer data.
  456.         VdigBufferRecListHandle
  457.         vdigBuffers    =    (VdigBufferRecListHandle) NewHandle( sizeof(VdigBufferRecList) + (n-1)*sizeof(VdigBufferRec) );
  458.         if ( !vdigBuffers ) throw dsMemFullErr;
  459.     
  460.         HLock( (Handle) vdigBuffers );
  461.         (**vdigBuffers).count = bufferCount;
  462.         (**vdigBuffers).matrix = &videoMatrix;
  463.         (**vdigBuffers).mask = nil;
  464.         short height = videoFrame.bottom - videoFrame.top;
  465.         for( int j = 0; j < n; j++ ) {
  466.             ((**vdigBuffers).list[j]).dest        = GetGWorldPixMap(gWorld);
  467.             ::SetPt( &((**vdigBuffers).list[j]).location, 0, j * height ); 
  468.             ((**vdigBuffers).list[j]).reserved    = 0L;
  469.             }
  470.         
  471.         // If we lock it we gotta unlock it.
  472.         HUnlock( (Handle) vdigBuffers );
  473.         
  474.         // Set the buffers    
  475.         rc = VDSetupBuffers( vdig, vdigBuffers );
  476.         DisposeHandle( (Handle) vdigBuffers );
  477.         if ( rc ) throw rc;
  478.     
  479.         bufferIndex = 0;
  480.         }
  481.  
  482.     else { // no we do not want to buffer so we preflight and PlayThru the pixMap.
  483.  
  484.         rc = VDPreflightDestination( vdig, &videoFrame, GetGWorldPixMap(gWorld), &videoFrame, nil );
  485.             if ( rc!=noErr && rc!=qtParamErr ) throw rc;
  486.         rc = VDSetPlayThruDestination( vdig, GetGWorldPixMap(gWorld), &videoFrame, nil, nil );
  487.             if ( rc!=noErr ) throw rc;
  488.         bufferIndex = -1;
  489.  
  490.         }
  491. }
  492.  
  493. void
  494. CQuickCam::ClearUpBuffers( void )
  495. {
  496.     // i don't throw in destructors.  Too many potential problems.
  497.     if ( bufferCount>0 )
  498.         if ( vdig ) VDReleaseAsyncBuffers( vdig );
  499.  
  500.     if ( gWorld ) {
  501.         // gets rid of the clut automatically
  502.         DisposeGWorld( gWorld );
  503.         gWorld = nil;
  504.         }        
  505. }
  506.  
  507.