home *** CD-ROM | disk | FTP | other *** search
Text File | 1995-12-05 | 14.7 KB | 507 lines | [TEXT/CWIE] |
- // © Paul B. Beeken, Work In Progress, 1994-5
- // Knowledge Software Consulting.
- //
- // Please, please, please, in the unlikely event you should use this stuff
- // for some commercial application I would appreciate you contacting me. If
- // its for your own use, use away. Send email: knowsoft@ios.com
- // My personal philosophy closely adheres to that of GNU software. I offer this
- // in the hope that others will improve and expand upon it. Post your additions,
- // leave this notice in place and add your own comments.
- //
- // As always: this file is presented as is with no warrantees expressed or implied.
- // Swim at your own risk, etc. etc.
- //
- // CQuickCam
- //
- // A c++ object library for the Connectix QuickCam using the
- // standardized vdig functions outlined in the Ch. 8 of QTComponents.
- // Yeah, you could do much of this seamlessly with Ch. 6 but with alot
- // of loss of control and performance. Some considerable experimentation
- // has gone into this object and many related threads along this line.
- // I think this provides the best trade off of flexability and control
- //
- // Notes in general: There are sometimes many ways to handle the
- // output of a vdig. It can preview directly to the screen (if it is
- // a cGrafPort) or to a pixmap. Multiple buffering is an option and
- // can be a very effective method given the serial data stream nature of
- // this kind of digitizer. It can allow the greatest flexability
- // and smoothness of updating. It may not be the best solution for a
- // particular application.
- //
- // QuickCam in particular:
- // The B&W camera is a 4 bit machine whose vdig doesn't do DMA.
- // This object always buffers the data and the instatiator is responsible
- // for updating. Previewing is done by copying the GWorld pixmap to a
- // rectangle in a given cGrafport. There are a couple of ways to accumulate
- // data to the pixMap. One is to use a buffers and the other is to write
- // to a pixmap. The former allows async grabs while the later does not.
- // The routines outlined here allow either. By specifying 0 for buffers,
- // the vdig writes directly to the pixmap without any overhead associated
- // with buffers. Any value from 1 to 3 will allow you to write to different
- // rectangles in the same pixmap but will allow async grabs. This may
- // allow for some performance imporvements.
- // The QuickCam vdig doesn't seem to like more than 3 buffers. Why would you
- // need more? Actually this makes sense. One would be the writethrubuffer
- // the other two are the alternating capture buffers.
- //
- // Current version 0.8 © Paul B. Beeken, Knowledge Software Consulting.
- //
- // 11/08/95 Finished basic object after creating various types of LPane
- // derivatives. This object borrows the from the best of them. Its
- // creation also eliminates the dependance on MW PowerPlant.
- // 12/04/95 Completed spot metering and fixed a memory leak.
- //
- // Wishlist: Sequential capture to moov.
- // Include methods for capturing from a subset rect in vdig bounds.
- // Motion triggering.
-
- /*
- Digitizer Information:
- • vdig type: 0 Basic
- • inputCapabilities:
- digiInDoesNTSC digiInDoesBW
- • outputCapabilities:
- digiOutDoes1 digiOutDoes2
- digiOutDoes4 digiOutDoes8
- digiOutDoes16 digiOutDoes32
- digiOutDoesQuarter digiOutDoesSixteenth
- digiOutDoesAsyncGrabs
- • min frame dimentions:
- hor: 1 ver: 1
- • max frame dimentions:
- hor: 240 ver: 320
- • blend levels:
- blev: 0
- • preferred device:
- none
- */
-
- #include "CQuickCam.h"
- #include <limits.h>
-
- #pragma mark • Creation and Destruction operators
- // Creation routine.
- CQuickCam::CQuickCam( short inSrc, short nbuffers )
- {
- SpotMeter( UINT_MAX/2 ); // set the middle as the default
-
- if ( nbuffers>3 ) nbuffers = 3; // QuickCam doesn't support more than 3.
- // If you have a QuickCam, you've got QT. If you want to
- // test for it do so before creating an instance of me.
-
- // Instantiate a component
- ComponentDescription theDesc;
-
- // Find and open a sequence grabber
- theDesc.componentType = videoDigitizerComponentType;
- theDesc.componentSubType = 'CtxV'; // BW QuickCam
- theDesc.componentManufacturer = 'Ctx6'; // Connectix
- theDesc.componentFlags = nil;
- theDesc.componentFlagsMask = nil;
-
- try {
- ComponentResult rc = noErr;
- Component sgCompID = nil;
-
- // Find a vdig component, I'll take the first one.
- sgCompID = FindNextComponent(nil, &theDesc);
- if ( sgCompID == nil ) throw cantFindHandler;
-
- // Open the component, This may fail for two reasons.
- // 1. a problem with the vdig or
- // B. the vdig is already instantiated and being used elsewhere.
- vdig = OpenComponent(sgCompID);
- if ( vdig == nil ) throw cantOpenHandler;
-
- // Fill in my info record.
- rc = VDGetDigitizerInfo( vdig, &vdigInfo );
- if ( rc!=noErr ) throw rc;
-
- }
-
- catch( ComponentResult rc ) {
- CloseComponent(vdig);
- vdig = nil;
- throw rc; // for a superior handler.
- }
-
- // start by assuming the maximum frame.
- ::SetRect( &videoFrame, 0, 0, vdigInfo.maxDestWidth, vdigInfo.maxDestHeight );
-
- // Initialize all phases of the digitizer
- VDSetFrameRate( vdig, 0 );
- VDSetInputStandard( vdig, ntscIn );
- VDSetInput( vdig, inSrc );
-
- VDSetDigitizerRect( vdig, &videoFrame );
-
- // And an identity matrix
- SetIdentityMatrix( &videoMatrix );
-
- // Set up the buffers...
- SetUpBuffers( nbuffers );
-
- }
-
- // Destruction routine.
- CQuickCam::~CQuickCam( void )
- {
- // Clean up
- if (vdig != nil) {
- ClearUpBuffers();
- CloseComponent( vdig );
- vdig = nil;
- }
- }
-
- #pragma mark -
- #pragma mark • Public Methods
- void
- CQuickCam::Brightness( unsigned short b )
- {
- if ( !vdig ) throw cantFindHandler;
- VDSetBrightness( vdig, &b );
- }
-
- unsigned short
- CQuickCam::Brightness( void )
- {
- unsigned short b = 0;
-
- if ( !vdig ) throw cantFindHandler;
- VDGetBrightness( vdig, &b );
-
- return b;
- }
-
- void
- CQuickCam::SpotMeter( unsigned short b )
- { // set the target spot meter
- bright = float(b) / float(UINT_MAX);
- }
-
- void
- CQuickCam::SpotMeter( const Rect& r )
- {
- // Given a rectangle r = to or inside the video rect
- // This function will automatically set the brightness.
- // N.B. a single call to this function won't do the trick.
- // the "servo" needs a couple of passes to stabilize.
- // Moreover, this function is not optimized to run fast
- // so it isn't intended to run all the time. You might
- // connect it to some user interface like a button for
- // momentary adjustment for example.
- unsigned short b;
- long avgPixel = 0L;
-
- if ( bufferIndex < 0 ) // no buffers.
- b = 0;
- else { // buffers.
- // Set b to the previous buffer index
- b = (bufferIndex==0 ? bufferCount : bufferIndex) - 1 ;
-
- // wait for the buffer to be filled.
- if ( vdig )
- while( !VDDone( vdig, b ) );
- }
-
- // No buffer, no metering.
- if ( gWorld ) {
- Rect bufferFrame = videoFrame;
- Rect mapFrame = r;
- // Move it to the buffer's position.
- ::OffsetRect( &bufferFrame, 0, b * vdigInfo.maxDestHeight );
- MapRect(&mapFrame,&videoFrame,&bufferFrame);
-
- PixMapHandle pm = GetGWorldPixMap(gWorld);
-
- LockPixels( pm ); // Lock 'em
- { // guarantee _base_ never gets used outside Locked PixMap
- Ptr base = GetPixBaseAddr( pm );
- short rowBytes = (**pm).rowBytes & 0x4FFF;
-
- for( int h=r.left; h < r.right; h++ )
- for ( int v=r.top; v < r.bottom; v++ ) {
- avgPixel += 0xF & *(base+h+(v*rowBytes));
- avgPixel += 0xF & (*(base+h+(v*rowBytes)))>>4;
- }
- }
- UnlockPixels( pm ); // Unlock 'em
-
- // Servo 101, develop a position error signal.
- // 0. avgPixel is maximum for a dark screen, minimum for a white screen.
- // 1. This is the avg brightness in range from 0 ≤ <avgPix> ≤ 1, 1 is white.
- float avgPix = 1.0 - float(avgPixel)/(30.0*float(r.bottom-r.top)*float(r.right-r.left));
- // 1 -> see 0 above 30 -> 2 pixels / byte * 15 maxSig / pixel.
- // 2. develop target error signal
- float error = bright - avgPix;
- // now if we are spot on (pun intended) then error is 0 and we do nothing.
- // if it is < 0 then we decrease the vdig brightness.
- // if it is > 0 then we increase the vdig brightness.
- // -1.0 ≤ error ≤ 1.0
- // Develop a factor which allows for a complete metering
- error *= 0.9; // dampenning factor (arb. number)
- if ( error*error > 0.05 ) { // tolerance threshold for control. (arb. number)
- unsigned short brightFactor = Brightness(); // current value
- if ( error > 0 )
- brightFactor += (UINT_MAX-brightFactor)*error; // error is positive
- else
- brightFactor += brightFactor * error; // error is negative or zero
- // reset brightness.
- Brightness( brightFactor );
- }
- // There are a lot of floating calculations in here. That may seem scary and ripe for
- // optimization, but these calculations aren't the speed bottleneck for this function.
- }
- }
-
- void
- CQuickCam::InputSource( short src )
- {
- if ( !vdig ) throw cantFindHandler;
- VDSetInput( vdig, src );
- }
-
- short
- CQuickCam::InputSource( void )
- {
- short b = 0;
-
- if ( !vdig ) throw cantFindHandler;
- VDGetInput( vdig, &b );
-
- return b;
- }
-
- void
- CQuickCam::SetDefaults( void )
- {
- unsigned short def[7];
- if ( !vdig ) throw cantFindHandler;
-
- VDGetVideoDefaults( vdig, &def[0], &def[1], &def[2],
- &def[3], &def[4], &def[5], &def[6] );
- Brightness( def[2] ); // The only value we have any control over.
- }
-
-
- void
- CQuickCam::UpdateVideo( void )
- {
- if ( !vdig ) throw cantFindHandler;
-
- if ( bufferIndex < 0 )
- VDGrabOneFrame( vdig );
- else {
- VDGrabOneFrameAsync( vdig, bufferIndex++ );
- bufferIndex = bufferIndex % bufferCount;
- }
-
- // Update this information.
- VDGetCurrentFlags( vdig, &vdigInfo.inputCurrentFlags, &vdigInfo.outputCurrentFlags );
- }
-
- Rect
- CQuickCam::OptVideoRect( const Rect& r )
- {
-
- ComponentResult result;
- short height = r.bottom - r.top;
- short width = r.right - r.left;
-
- if ( !vdig ) throw cantFindHandler;
-
- // Adjust the videoRectangle to reflect a smaller pane.
- // Smaller videoRectangle updates faster? No no no!
- // scaling the video with buffering will not accelerate
- // the updating. We need to figure out the scaling.
- long scale = 8;
- if ( vdigInfo.maxDestHeight <= 4*height &&
- vdigInfo.maxDestWidth <= 4*width ) scale = 4;
-
- if ( vdigInfo.maxDestHeight <= 2*height &&
- vdigInfo.maxDestWidth <= 2*width ) scale = 2;
-
- if ( vdigInfo.maxDestHeight <= height &&
- vdigInfo.maxDestWidth <= width ) scale = 1;
-
- Rect origSize = videoFrame;
- ::SetRect( &videoFrame, 0, 0, vdigInfo.maxDestWidth/scale, vdigInfo.maxDestHeight/scale );
-
- // And an identity matrix
- SetIdentityMatrix( &videoMatrix );
- // Scale the matrix appropriately
- ScaleMatrix( &videoMatrix, FixRatio(1,scale), FixRatio(1,scale), 0, 0);
-
- short n = bufferCount;
- ClearUpBuffers(); // Clear the old buffer
- SetUpBuffers( n ); // Set the new buffers
-
- Rect rr = videoFrame; // for adjustment and return.
- ::OffsetRect( &rr, r.left, r.top );
- return rr;
-
- }
-
-
- #pragma mark -
- #pragma mark • Action methods
-
- PicHandle
- CQuickCam::GrabPict( void )
- {
- OpenCPicParams pp;
-
- pp.srcRect = videoFrame;
- pp.hRes = 0x00480000;
- pp.vRes = 0x00480000;
- pp.version = -2;
- pp.reserved1 = 0;
- pp.reserved2 = 0;
-
- GWorldPtr savePort;
- GDHandle saveGD;
-
- GetGWorld( &savePort, &saveGD );
- SetGWorld( gWorld, nil );
-
- PicHandle p = OpenCPicture( &pp );
-
- UpdateVideo();
- DrawVideo( gWorld, videoFrame );
-
- ClosePicture();
-
- SetGWorld( savePort, saveGD );
-
-
- return p;
-
- }
- void
- CQuickCam::DrawVideo( CGrafPtr gw, const Rect& r )
- {
- short b;
-
- if ( bufferIndex < 0 ) // no buffers.
- b = 0;
- else { // buffers.
- // Set b to the previous buffer index
- b = (bufferIndex==0 ? bufferCount : bufferIndex) - 1 ;
-
- // wait for the buffer to be filled.
- if ( vdig )
- while( !VDDone( vdig, b ) );
- }
-
- // if we have a display gWorld
- // To handle multiple buffers I need to devlop a scheme for addressing
- // the buffers through their respective rectangles. Given an index
- // I need to map the rectangle to the pixMap. I've chosen to map them
- // vertically.
- if ( gWorld ) {
- Rect bufferFrame = videoFrame;
-
- // Move it to the buffer's position.
- ::OffsetRect( &bufferFrame, 0, b * vdigInfo.maxDestHeight );
-
- LockPixels( GetGWorldPixMap(gWorld) );
- CopyBits( &((GrafPtr)gWorld)->portBits, &((GrafPtr)gw)->portBits,
- &bufferFrame, &r,
- srcCopy, nil );
- UnlockPixels( GetGWorldPixMap(gWorld) );
- }
- }
-
- DigitizerInfo
- CQuickCam::GetInfoRecord( void )
- {
- return vdigInfo;
- }
-
- #pragma mark -
- #pragma mark • Protected methods
-
- void
- CQuickCam::SetUpBuffers( short n )
- {
- // The videoFrame contains the rectangle required for capture.
- VideoDigitizerError rc;
- // I propose the following scheme for buffering:
- // Allocate one GWorld with a huge pixMap Width x n Height
- // rectangle. Define the pixMaps in the buffer array to be
- // the same pm and different location to start points along
- // the way. • I assume that videoFrame is set to reflect the
- // area to be digitized.
-
- // BW QuickCam captures 16 levels of gray choose an appropos clut.
- short depth = 4;
- Rect bufferFrame;
-
- gWorld = nil;
-
- // use the current video frame.
- bufferCount = n;
- bufferFrame = videoFrame;
- if ( n > 0 ) // if we want a buffer, set aside a big enough area.
- bufferFrame.bottom *= n;
-
- // We want a GWorld in any case.
- CTabHandle ct = GetCTable(32+depth);
- NewGWorld( &gWorld, depth, &bufferFrame, ct, nil, 0 );
- DisposeHandle( (Handle) ct );
- if ( gWorld == nil ) throw dsMemFullErr;
-
- if ( n > 0 ) { // Yes we want to buffer data.
- VdigBufferRecListHandle
- vdigBuffers = (VdigBufferRecListHandle) NewHandle( sizeof(VdigBufferRecList) + (n-1)*sizeof(VdigBufferRec) );
- if ( !vdigBuffers ) throw dsMemFullErr;
-
- HLock( (Handle) vdigBuffers );
- (**vdigBuffers).count = bufferCount;
- (**vdigBuffers).matrix = &videoMatrix;
- (**vdigBuffers).mask = nil;
- short height = videoFrame.bottom - videoFrame.top;
- for( int j = 0; j < n; j++ ) {
- ((**vdigBuffers).list[j]).dest = GetGWorldPixMap(gWorld);
- ::SetPt( &((**vdigBuffers).list[j]).location, 0, j * height );
- ((**vdigBuffers).list[j]).reserved = 0L;
- }
-
- // If we lock it we gotta unlock it.
- HUnlock( (Handle) vdigBuffers );
-
- // Set the buffers
- rc = VDSetupBuffers( vdig, vdigBuffers );
- DisposeHandle( (Handle) vdigBuffers );
- if ( rc ) throw rc;
-
- bufferIndex = 0;
- }
-
- else { // no we do not want to buffer so we preflight and PlayThru the pixMap.
-
- rc = VDPreflightDestination( vdig, &videoFrame, GetGWorldPixMap(gWorld), &videoFrame, nil );
- if ( rc!=noErr && rc!=qtParamErr ) throw rc;
- rc = VDSetPlayThruDestination( vdig, GetGWorldPixMap(gWorld), &videoFrame, nil, nil );
- if ( rc!=noErr ) throw rc;
- bufferIndex = -1;
-
- }
- }
-
- void
- CQuickCam::ClearUpBuffers( void )
- {
- // i don't throw in destructors. Too many potential problems.
- if ( bufferCount>0 )
- if ( vdig ) VDReleaseAsyncBuffers( vdig );
-
- if ( gWorld ) {
- // gets rid of the clut automatically
- DisposeGWorld( gWorld );
- gWorld = nil;
- }
- }
-
-