Game Development Community

Help with callback function in code converted to a class

by Paul Griffiths · in Torque Game Engine · 05/29/2006 (4:24 am) · 21 replies

I'm adding a web-cam video capture library to Torque codevis.com/vidcapture/index.html and i have images saved to files working.

But I'm having a problem with a callback function.
I took a code example and converted it to a class but a callback function is creating an error.

The error is:

c:\Torque\SDK\engine\TMessenger\VideoCapture.h(395): error C2664: 'CVVidCapture::StartImageCap' : cannot convert parameter 2 from 'bool (CVRES,CVImage *,void *)' to 'CVVidCapture::CVVIDCAP_CALLBACK'

The original example code was:

//-------------------------------------------------------------------------
// capCallback is the main image capture callback.
//
// We play with the image a bit inside to test out
// CVImage's functions.
//
bool capCallback( CVRES             status,
                  CVImage*          imagePtr,
                  void*             userParam)
{
   static int xOffset  = 0;
   static int yOffset  = 0;

    ...  
    return true;
}


and i put this inside a class, thus it's now:


//-------------------------------------------------------------------------
// capCallback is the main image capture callback.
//
// We play with the image a bit inside to test out
// CVImage's functions.
//
bool GuiVideoCameraCapture::capCallback( CVRES             status,
                  CVImage*          imagePtr,
                  void*             userParam)
{
   static int xOffset  = 0;
   static int yOffset  = 0;

    ...  
    return true;
}

It's called in a line of code:
started = CVSUCCESS(vidCap->StartImageCap(CVImage::CVIMAGE_GREY, capCallback,0));

I'm quite new to c++ so I'm stumped! Any ideas?
Page «Previous 1 2
#1
05/29/2006 (7:14 am)
Here is the function:

virtual CVRES StartImageCap( CVImage::CVIMAGE_TYPE imageType,
CVVIDCAP_CALLBACK callback,
void* userParam) = 0;


Any ideas? Has virtual got anything to do with it?
#2
05/29/2006 (7:49 am)
It looks like your StartImageCap is asking for a callback function that's a C function pointer as apposed to a C++ method pointer (I'm not entirely certain about the name of function pointers to methods in C++).

Simply put, the function/method you're passing into StartImageCap needs to be a standard C function and not associated with any namespace.

Unfortunately it's difficult to work around a modular solution to this problem. You might want to consider using a friend function as a wrap-around for it, and that's the best I can think of.

There's a better explaination to why this is a problem found on the C++ Faq site here.

- Eric
#3
05/29/2006 (9:14 am)
Use a static function.
#4
05/29/2006 (9:34 am)
Quote:
Use a static function.

Isn't guarenteed to work.

It will generally complain about how it can't convert from bool (GuiVideoCameraCapture::capCallback*)(CVRES,CVImage *,void *) to CVVidCapture::CVVIDCAP_CALLBACK (or whatever the exact syntax is). The static method will still be associated with the class namespace and AFAIK you cannot cast a static method pointer into a c function pointer - I believe it's mentioned somewhere on the link I showed above.

- Eric
#5
05/29/2006 (10:29 am)
I tried making a wrapper and also tried making a static function , but no such luck which is a shame. I still got image capture but not video capture.
#6
05/29/2006 (10:36 am)
Are you still getting compile errors?
What exactly is going wrong?
#7
05/29/2006 (11:13 am)
Quote:
It will generally complain about how it can't convert from bool (GuiVideoCameraCapture::capCallback*)(CVRES,CVImage *,void *) to CVVidCapture::CVVIDCAP_CALLBACK (or whatever the exact syntax is). The static method will still be associated with the class namespace and AFAIK you cannot cast a static method pointer into a c function pointer - I believe it's mentioned somewhere on the link I showed above.

Unless you're a C++ Standard evangelist, casting a static member function to a C function pointer will work on almost all modern C++ compilers, and you can pass the pointer to the object in the userParam.

However, a more correct way to do what you want to do is to just use a C function and pass the 'this' pointer of your object to the C function, and then call a member function of your class from within the C function. i.e.:

bool capCallback( CVRES status, CVImage* imagePtr, void* userParam)
{  
   GuiVideoCameraCapture* camCap = (GuiVideoCameraCapture*)userParam;
   return camCap->capCallback( status, imagePtr );
}

.....

started = CVSUCCESS(vidCap->StartImageCap(CVImage::CVIMAGE_GREY, capCallback, [b]this[/b] ));

And just make your class method a regular (non-static) method. i.e:

bool GuiVideoCameraCapture::capCallback( CVRES status, CVImage* imagePtr )
{   
   static int xOffset  = 0;
   static int yOffset  = 0;   
   return true;
}

Note that you don't really need the userParam argument in your member function at this point.
#8
05/29/2006 (11:19 am)
Unless of course, you need to pass something else in your userParam. In that case, you can make a small structure, i.e.:

struct UserParamStruct
{
   GuiVideoCameraCapture* mObject;
   void* mUserParam;
};

and pass an instance of that structure as the userParam.
#9
05/29/2006 (11:25 am)
Quote:
[quote]
Use a static function.
Isn't guarenteed to work.

It will generally complain about how it can't convert from bool (GuiVideoCameraCapture::capCallback*)(CVRES,CVImage *,void *) to CVVidCapture::CVVIDCAP_CALLBACK (or whatever the exact syntax is). The static method will still be associated with the class namespace and AFAIK you cannot cast a static method pointer into a c function pointer - I believe it's mentioned somewhere on the link I showed above.
[/quote]
You are referring to www.parashift.com/c++-faq-lite/pointers-to-members.html#faq-33.2. It's true that it's not guaranteed to work by the C++ standard, nonetheless it does work on all compilers that I know of and that includes all compilers supported by Torque. On those compilers a static member function behaves exactly the same as a regular "free" function. BTW, there is no "association" between a static method and the class namespace. In fact there's is no concept in C++ as a "class namespace". There's a "class scope" concept in C++, but it's quite different. Don't make confusion between C++ and TorqueScript ;)

What Paul needs is simply:

1) declare a non-static method GuiVideoCameraCapture::capCallback like the one above
bool GuiVideoCameraCapture::capCallback(CVRES status, CVImage* imagePtr)
{
   static int xOffset  = 0;
   static int yOffset  = 0;

    ...  
    return true;
}
notice that the userParam parameter has been removed.

2) declare a static method GuiVideoCameraCapture::capCallbackStatic like this:
bool GuiVideoCameraCapture::capCallbackStatic(CVRES status, CVImage* imagePtr, void* userParam)
{
    return static_cast<GuiVideoCameraCapture*>(userParam)->capCallback(status, imagePtr);
}

3) assuming that you invoke StartImageCap from inside a method of class GuiVideoCameraCapture, the invocation will be:
started = CVSUCCESS(vidCap->StartImageCap(CVImage::CVIMAGE_GREY, capCallbackStatic, this));
Notice the "this" instead of the "0" as the last parameter.

If you still get errors, please post the exact definition of CVVidCapture::CVVIDCAP_CALLBACK, as it is might declare a calling convention (such as __stdcall), in which case the calling convention must be added to the declaration of capCallbackStatic.
#10
05/29/2006 (1:33 pm)
Alberto, i tried your idea and still have errors:

c:\Torque\SDK\engine\TMessenger\VideoCapture.h(410): error C2664: 'CVVidCapture::StartImageCap' : cannot convert parameter 2 from 'bool (CVRES,CVImage *,void *)' to 'CVVidCapture::CVVIDCAP_CALLBACK'


I'm guessing the definition is:

typedef bool (*CVVIDCAP_CALLBACK)( CVRES status,
CVImage* imagePtr,
void* userParam );
#11
05/29/2006 (1:37 pm)
Heres a cut down version of what i have done:

#if (defined(STOP_BEFORE_EXIT) || defined(LOOP_TEST))
#include <conio.h>
#endif

#ifdef STOP_BEFORE_EXIT
// Clear any existing keystrokes, then wait for a final one on exit.
#define EXIT_MACRO printf("Press any key to exit...\n"); \
	while (kbhit()) {getch();} \
	getch();
#else
#define EXIT_MACRO
#endif


#include <stdio.h>
#include "VidCapture.h"

// You only need to include this if you plan on using CodeVis utility
// or tracing functions
#include "CVUtil.h"


class GuiVideoCameraCapture
{


	bool TestIt();
	void CaptureFrame();

	bool enumCallback(   const char*             devname, 
		void*                   userParam);

	bool capCallback (   CVRES                   status,
		CVImage*                image);

	void SlowNegateImage(CVImage* image);

	// Global test device name
	char gDeviceName[256];

	// Global frame count
	int gFrameNum;   // Offset

	// Global device number
	int gDevNum; 
	CVImage* grabImage;


public:
	GuiVideoCameraCapture();
	char imageData;
	unsigned char getImage(){grabImage->GetRawDataPtr();}
	int getImageSize(){grabImage->AbsSize();}


bool GuiVideoCameraCapture::capCallbackStatic(CVRES status, CVImage* imagePtr, void* userParam){    return static_cast<GuiVideoCameraCapture*>(userParam)->capCallback(status, imagePtr);}


};


GuiVideoCameraCapture::GuiVideoCameraCapture(void) {
	// Global frame count
	gFrameNum = 0;   // Offset

	// Global device number
	gDevNum = 0; 
	CaptureFrame();
}


//-------------------------------------------------------------------------
// Callback for enumeration of capture devices.
//
// We're just saving the first one that comes by 
// into gDeviceName[] and using it.
bool GuiVideoCameraCapture::enumCallback(const char* devname, void* userParam)
{      

	// if (gDevNum < 1)     // use the first device
	if (gDevNum <= 1)    // use the second device
	{
		dStrcpy(gDeviceName,devname);
	}

	gDevNum++;

	printf("Device: %s\n",devname);
	return true;
}


//-------------------------------------------------------------------------
// capCallback is the main image capture callback.
//
// We play with the image a bit inside to test out
// CVImage's functions.
//
bool GuiVideoCameraCapture::capCallback( CVRES             status, CVImage*          imagePtr) 
{
	static int xOffset  = 0;
	static int yOffset  = 0;

	// Only try to work with the image pointer if the
	// status is successful!
	if (CVSUCCESS(status))
	{      
		char framePath[260];
		sprintf(framePath,"cap_frame_%d",gFrameNum);

		CVAssert(imagePtr != 0, "This shouldn't happen. Bad image pointer.");

		printf("Captured frame %d.\n", gFrameNum);

		// Create a sub image
		CVImage* subImg = 0;
		int subWidth = imagePtr->Width() / 4;
		int subHeight = imagePtr->Height() / 5;

		if (CVFAILED(CVImage::CreateSub( imagePtr,
			subImg, 
			xOffset, 
			yOffset, 
			subWidth, 
			subHeight)))
		{
			printf("Failed creating sub image!\n");
		}
		else
		{
			// Negate the sub image
			SlowNegateImage(subImg);

			// Create a sub image of the sub image 5 pixels in from each side
			CVImage *subSubImg = 0;
			if (CVFAILED(CVImage::CreateSub( subImg,
				subSubImg,
				5,
				5,
				subWidth-10,
				subHeight-10)))
			{
				printf("Failed creating sub image of sub image\n");
				// Release first sub image
				CVImage::ReleaseImage(subImg);
			}
			else
			{
				// Release first sub image 
				// (this shouldn't delete subImg - subSubImg holds a reference)
				CVImage::ReleaseImage(subImg);

				// Negate subSubImg - this should restore the center of subImg
				// area to its original colors, just leaving a 5 pixel border of
				// negative around it.
				SlowNegateImage(subSubImg);

				// Release subSubImg
				CVImage::ReleaseImage(subSubImg);
			}

			// Setup offsets for next sub image
			xOffset += subWidth; 
			yOffset += subHeight;

			if (imagePtr->Width() - xOffset < subWidth)
			{
				xOffset = 0;
			}
			if (imagePtr->Height() - yOffset < subHeight)
			{
				yOffset = 0;
			}

		}

		// Save the base image
		if (CVFAILED(CVImage::Save(framePath, imagePtr)))
		{
			printf("Failed saving image!\n");
		}

		gFrameNum++;
	}
	else
	{
		// From here, you'd usually want to signal your main thread 
		// that the capture has been terminated. This is the only notice
		// you'll receive - no more callbacks after this.
		//
		// In this example, since we're just sleeping briefly, there's
		// no need to signal the main thread.
		// 
		printf("Capture failure in callback! Did you disconnect camera?\n");
	}

	// Halt on frame 25 just to test halting from within a callback.
	if (gFrameNum == 25)
	{
		printf("Halting prematurely to test callback exit codes.\n");
		printf("Returning false here should abort the capture.\n");
		// Of course, you'll still need to call CVVidCapture::Stop()
		// from the main thread to clean up.
		return false;
	}   
	return true;
}

//-------------------------------------------------------------------------
// SlowNegateImage() is a slow routine to negate an image.
//
// Works on any image type where the pixel values are 0-255,
// but it recalculates each pixel location in addition to 
// having 2 subroutine calls per pixel - lots of overhead. 
// Good for testing CVImage::GetPixel() and CVImage::SetPixel() though.
//
void GuiVideoCameraCapture::SlowNegateImage(CVImage* image)
{
	// Verify that our getpixel/setpixel stuff works even
	// on sub images by creating a negative within the
	// sub image, then saving the parent (which should
	// now have a negative rectangle in it)
	for (int x = 0; x < image->Width(); x++)
	{
		for (int y=0; y < image->Height(); y++)
		{
			float r,g,b;
			if (CVFAILED(image->GetPixel(x,y,r,g,b)))
			{
				printf("GetPixel Error!\n");
			}
			else
			{
				// Assumes images are 0-255. If not, this will
				// generate funky results.
				r = 255.0f - r;
				g = 255.0f - g;
				b = 255.0f - b;

				if (CVFAILED(image->SetPixel(x,y,r,g,b)))
				{
					printf("SetPixel Error!\n");
				}
			}
		}
	}
}
#12
05/29/2006 (2:01 pm)
@Paul, try changing this:

started = CVSUCCESS(vidCap->StartImageCap(CVImage::CVIMAGE_GREY, capCallbackStatic, this));

To this:

started = CVSUCCESS(vidCap->StartImageCap(CVImage::CVIMAGE_GREY, (CVVidCapture::CVVIDCAP_CALLBACK)capCallbackStatic, this));
#13
05/29/2006 (2:10 pm)
No, i get:

c:\Torque\SDK\engine\TMessenger\VideoCapture.h(410): error C2440: 'type cast' : cannot convert from 'overloaded-function' to 'CVVidCapture::CVVIDCAP_CALLBACK'
#14
05/29/2006 (2:23 pm)
Ah, okay. You need to make capCallbackStatic a static method. Change this:

bool GuiVideoCameraCapture::capCallbackStatic(CVRES status, CVImage* imagePtr, void* userParam){    return static_cast<GuiVideoCameraCapture*>(userParam)->capCallback(status, imagePtr);}

to this:

static bool capCallbackStatic(CVRES status, CVImage* imagePtr, void* userParam){    return static_cast<GuiVideoCameraCapture*>(userParam)->capCallback(status, imagePtr);}
#15
05/29/2006 (2:27 pm)
Gerald, that compiled ok, thanks all!
#16
05/29/2006 (2:46 pm)
Yes, thanks all that works fine, i now have a continuous stream of webcam captured images to files.
Next i have to render it to a gui. Any ideas?
#17
05/29/2006 (5:48 pm)
Why always 0?


when i create an instance of the class above and try to access some of it's functions, it always returns 0?

I create it by:

gvcc = new GuiVideoCameraCapture();

one of it's functions is

int getImageCount(){return gFrameNum;}

i access it by:

int frameNumber = gvcc->getImageCount()

It always returns 0, though i know gFrameNum increases because it's used to name files saved to my harddrive which works.

Heres my full code:
#18
05/29/2006 (5:50 pm)
GuiVideoCameraCtrl.h

//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------

#ifndef _GUIVIDEOCAMERACONTROL_H_
#define _GUIVIDEOCAMERACONTROL_H_

#ifndef _GUICONTROL_H_
#include "gui/core/guiControl.h"
#endif
#ifndef _GTEXMANAGER_H_
#include "dgl/gTexManager.h"
#endif
#include "videoCapture.h"
/// Renders a bitmap.
class GuiVideoCameraCtrl : public GuiControl
{
private:
	typedef GuiControl Parent;

protected:
	StringTableEntry mBitmapName;
	TextureHandle mTextureHandle;
	Point2I startPoint;
	bool mWrap;
	GuiVideoCameraCapture* gvcc;
public:
	//creation methods
	DECLARE_CONOBJECT(GuiVideoCameraCtrl);
	GuiVideoCameraCtrl();
	static void initPersistFields();

	//Parental methods
	bool onWake();
	void onSleep();
	void inspectPostApply();

	void setBitmap(const char *name,bool resize = false);
	void setBitmap(const TextureHandle &handle,bool resize = false);

	S32 getWidth() const       { return(mTextureHandle.getWidth()); }
	S32 getHeight() const      { return(mTextureHandle.getHeight()); }

	void onRender(Point2I offset, const RectI &updateRect);
	void setValue(S32 x, S32 y);
};

#endif
#19
05/29/2006 (5:51 pm)
GuiVideoCameraCtrl.cc

//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------

#include "console/console.h"
#include "console/consoleTypes.h"
#include "dgl/dgl.h"

#include "GuiVideoCameraCtrl.h"

IMPLEMENT_CONOBJECT(GuiVideoCameraCtrl);

GuiVideoCameraCtrl::GuiVideoCameraCtrl(void)
{
	mBitmapName = StringTable->insert("");
	startPoint.set(0, 0);
	mWrap = false;
	gvcc = new GuiVideoCameraCapture();

}


void GuiVideoCameraCtrl::initPersistFields()
{
	Parent::initPersistFields();
	addGroup("Misc");		
	addField("bitmap", TypeFilename, Offset(mBitmapName, GuiVideoCameraCtrl));
	addField("wrap",   TypeBool,     Offset(mWrap,       GuiVideoCameraCtrl));
	endGroup("Misc");		
}

ConsoleMethod( GuiVideoCameraCtrl, setValue, void, 4, 4, "(int xAxis, int yAxis)"
			  "Set the offset of the bitmap.")
{
	object->setValue(dAtoi(argv[2]), dAtoi(argv[3]));
}

ConsoleMethod( GuiVideoCameraCtrl, setBitmap, void, 3, 3, "(string filename)"
			  "Set the bitmap displayed in the control. Note that it is limited in size, to 256x256.")
{
	object->setBitmap(argv[2]);
}






bool GuiVideoCameraCtrl::onWake()
{
	if (! Parent::onWake())
		return false;
	setActive(true);
	setBitmap(mBitmapName);
	return true;
}

void GuiVideoCameraCtrl::onSleep()
{
	mTextureHandle = NULL;
	Parent::onSleep();
}

//-------------------------------------
void GuiVideoCameraCtrl::inspectPostApply()
{
	// if the extent is set to (0,0) in the gui editor and appy hit, this control will
	// set it's extent to be exactly the size of the bitmap (if present)
	Parent::inspectPostApply();

	if (!mWrap && (mBounds.extent.x == 0) && (mBounds.extent.y == 0) && mTextureHandle)
	{
		TextureObject *texture = (TextureObject *) mTextureHandle;
		mBounds.extent.x = texture->bitmapWidth;
		mBounds.extent.y = texture->bitmapHeight;
	}
}

void GuiVideoCameraCtrl::setBitmap(const char *name, bool resize)
{
	mBitmapName = StringTable->insert(name);
	if (*mBitmapName) {
		mTextureHandle = TextureHandle(mBitmapName, BitmapTexture, true);

		// Resize the control to fit the bitmap
		if (resize) {
			TextureObject* texture = (TextureObject *) mTextureHandle;
			mBounds.extent.x = texture->bitmapWidth;
			mBounds.extent.y = texture->bitmapHeight;
			Point2I extent = getParent()->getExtent();
			parentResized(extent,extent);
		}
	}
	else
		mTextureHandle = NULL;
	setUpdate();
}


void GuiVideoCameraCtrl::setBitmap(const TextureHandle &handle, bool resize)
{
	mTextureHandle = handle;

	// Resize the control to fit the bitmap
	if (resize) {
		TextureObject* texture = (TextureObject *) mTextureHandle;
		mBounds.extent.x = texture->bitmapWidth;
		mBounds.extent.y = texture->bitmapHeight;
		Point2I extent = getParent()->getExtent();
		parentResized(extent,extent);
	}
}


void GuiVideoCameraCtrl::onRender(Point2I offset, const RectI &updateRect)
{
	if (mTextureHandle)
	{
		dglClearBitmapModulation();
		if(mWrap)
		{
			TextureObject* texture = (TextureObject *) mTextureHandle;
			RectI srcRegion;
			RectI dstRegion;
			float xdone = ((float)mBounds.extent.x/(float)texture->bitmapWidth)+1;
			float ydone = ((float)mBounds.extent.y/(float)texture->bitmapHeight)+1;

			int xshift = startPoint.x%texture->bitmapWidth;
			int yshift = startPoint.y%texture->bitmapHeight;
			for(int y = 0; y < ydone; ++y)
				for(int x = 0; x < xdone; ++x)
				{
					srcRegion.set(0,0,texture->bitmapWidth,texture->bitmapHeight);
					dstRegion.set( ((texture->bitmapWidth*x)+offset.x)-xshift,
						((texture->bitmapHeight*y)+offset.y)-yshift,
						texture->bitmapWidth,	
						texture->bitmapHeight);
					dglDrawBitmapStretchSR(texture,dstRegion, srcRegion, false);
				}
		}
		else
		{
			RectI rect(offset, mBounds.extent);
			dglDrawBitmapStretch(mTextureHandle, rect);
		}
		int intCapture;
		if (gvcc->grabImage != NULL) {
			intCapture = gvcc->grabImage->Width();
		} else {
			intCapture =0;
		}

		// Currently only displays min/sec
		char buf[256];
		dSprintf(buf,sizeof(buf), "Frame:%d",gvcc->getImageCount());

		// Center the text
		offset.x += (mBounds.extent.x - mProfile->mFont->getStrWidth((const UTF8 *)buf)) / 2;
		offset.y += (mBounds.extent.y - mProfile->mFont->getHeight()) / 2;
		//dglSetBitmapModulation(mTextColor);
		dglDrawText(mProfile->mFont, offset, buf);

	}

	if (mProfile->mBorder || !mTextureHandle)
	{
		RectI rect(offset.x, offset.y, mBounds.extent.x, mBounds.extent.y);
		dglDrawRect(rect, mProfile->mBorderColor);
	}

	renderChildControls(offset, updateRect);
}

void GuiVideoCameraCtrl::setValue(S32 x, S32 y)
{
	if (mTextureHandle)
	{
		TextureObject* texture = (TextureObject *) mTextureHandle;
		x+=texture->bitmapWidth/2;
		y+=texture->bitmapHeight/2;
	}
	while (x < 0)
		x += 256;
	startPoint.x = x % 256;

	while (y < 0)
		y += 256;
	startPoint.y = y % 256;
}

If i try to access the frame count or the image itself it returns 0 or null. I'm used to java.
#20
05/30/2006 (12:58 am)
Quote:It always returns 0, though i know gFrameNum increases because it's used to name files saved to my harddrive which works.
I'm sorry but it's difficult for me to debug such a long piece of code out of its context. Have you tried setting a breakpoint and inspecting the object with a debugger? Are you sure that gFrameNum really increases on that particular instance?
Page «Previous 1 2