/**
 *	openframework library v2.0b
 */




#include "ofxOpenCV.h"




/**
 * Setup Cv variables
 */
ofxOpenCV::ofxOpenCV()
{

	bnew_frame = false; 
	cam = NULL;
	buffer = NULL;
	memory = NULL;
	source = NULL;
	haarcascade = NULL;
	
	width  = 0;
	height = 0;
	
	running = false;
	flipmode = OFX_OPENCV_FLIP_NONE;
	
	interpolation_mode = CV_INTER_LINEAR;
	
	
	#ifdef OF_MAIN
	glGenTextures(1, &tex );
	glBindTexture( GL_TEXTURE_2D, tex );
	glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
	glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
	
	printf( "\n\nYOU ARE RUNNING THE OPENFRAMEWORKS OPENCV LIBRARY PRE-RELEASED VERSION %s\n", OFX_OPENCV_VERSION );
	printf( "BUILDED WITH THE C++ LIBRARY VERSION %s\n", CV_VERSION );
	printf( "CHECK FOR UPDATE @ http://ubaa.net/shared/processing/opencv\n\n" );
	#endif
	
	//cout << "-----> OPENCV create" << endl;
}



/**
 * Forget Cv resources and free memory
 */
ofxOpenCV::~ofxOpenCV()
{
	
	// deallocate cv images and/or camera capture
	deallocate();
	//cout << "-----> OPENCV close" << endl;
	
}



/**
  * Allocate required resources with the given size.
  * This method allows you to create your own (blank) image buffer to work on. Except for creating 
  * an empty buffer, this method should not be directly used.
  * 
  * note: capture(), movie() and loadImage() methods will automatically re-allocate current 
  * resources.
  *
  * @param width	the width image dimension
  * @param height	the height image dimension
  * @param mode	(optionnal), set up with the specify color mode, without using the convert() function
  */
void ofxOpenCV::allocate( int w, int h, int mode )
{
	
	// deallocate buffer first
	if ( width && height ) deallocate( OFX_OPENCV_BUFFER );
	
	
	width  = w;
	height = h;
	
	
	// creates images
	buffer = cvCreateImage( cvSize(width,height), IPL_DEPTH_8U, mode==OFX_OPENCV_GRAY ? 1 : 3 );
	source = cvCreateImage( cvSize(width,height), IPL_DEPTH_8U, 3 );
	cvZero( buffer );
	cvZero( source );
	
	// set current color space
	colormode = mode;
}


void ofxOpenCV::deallocate( int type )
{
	
	
	// release capture
	if ( cam && ( type==OFX_OPENCV_CAPTURE || type==-1 ) ) {
		stop();
		cvReleaseCapture( &cam );
		cam = NULL;
	}
	
	// release cascade classifier
	// TODO: ne pas virer le cascade sur un changement de film par ex.
	if ( haarcascade && type==-1 ) cvReleaseHaarClassifierCascade( &haarcascade );
	
	// images : because running methods could use it, 
	// this should be released at the end
	if ( type==OFX_OPENCV_BUFFER || type==-1 ) {
		if ( buffer ) cvReleaseImage( &buffer );
		if ( source ) cvReleaseImage( &source );
		if ( memory ) cvReleaseImage( &memory );
	}
	
}




unsigned char* ofxOpenCV::pixels( int kind )// bool roi
{
	
	
	int size = width*height;
	unsigned char * out = new unsigned char[ size*3 ];
	
	// nothing to do
	if ( kind==OFX_OPENCV_SOURCE && !source ) return out;
	if ( kind==OFX_OPENCV_BUFFER && !buffer ) return out;
	if ( kind==OFX_OPENCV_MEMORY && !memory ) return out;
	
	
	IplImage * src;
	switch( kind ) {
		case OFX_OPENCV_MEMORY	: src = memory; break;
		case OFX_OPENCV_SOURCE	: src = source; break;
		default					: src = buffer; break;
	}
	
	// TODO: add roi
	for( int i=0; i<size; i++ ) {
		
		int x = i%width;
		int y = i/width;
		int p = ( x*src->nChannels ) + ( y*src->widthStep );
		
		out[ i+0 ] = src->imageData[ p ];
		out[ i+1 ] = src->nChannels==1 ? out[ i ] : src->imageData[ p+1 ];
		out[ i+2 ] = src->nChannels==1 ? out[ i ] : src->imageData[ p+2 ];
		
	}
	
	return out;
	
}


/**
 * Store the specified image in memory and flip to the specified axis if necessary.
 *
 * @param kind	the image source to be stored : SOURCE or BUFFER
 * @param mode	the axis to flip the image : FLIP_HORIZONTAL, FLIP_VERTICAL, FLIP_BOTH or 
 *				every other value for no flip
 */
void ofxOpenCV::remember( int kind, int mode )
{
	
	// -> exit: without source… yep, without buffer image too
	if ( !source ) return;
	
	// -> exit: if kind not match SOURCE or BUFFER but display message to take off frustrations
	if ( kind<OFX_OPENCV_SOURCE || kind>OFX_OPENCV_BUFFER ) {
		std::cout << "Error while using remember() : unknow source" << std::endl;
		return;
	}
	
	
	
	IplImage * src = kind==OFX_OPENCV_SOURCE ? source : buffer;
	
	
	// create the memory buffer (if needed)
	// with the target image channels -> should be convert for grayscale manipulation
	if ( !memory ) {
		memory = cvCreateImage( cvSize(width,height), IPL_DEPTH_8U, src->nChannels );
		cvZero( memory );
	}
	
	// clear region of interest (if need)
	if ( buffer->roi ) cvResetImageROI( memory );
	
	// store the current buffer
	cvCopy( src, memory, NULL );
	
	// flip the image if require
	if ( mode>=-1 && mode<=1 ) cvFlip( memory, NULL, mode );
	
	// restore the region of interest (if need)
	if ( buffer->roi ) cvSetImageROI( memory, cvGetImageROI(buffer) );
	
	
}


void ofxOpenCV::restore( int type ) 
{
	
	if ( width==0 || height==0 ) return;
	
	CvRect roi;
	bool hasROI = false;
	
	
	// remember buffer ROI and 
	// release region to process with the entire image
	if ( buffer->roi ) {
		roi = cvGetImageROI( buffer );
		cvResetImageROI( buffer );
		hasROI = true;
	}
	
	
	// re-allocate  buffer if needed
	if ( type!=colormode ) {
		cvReleaseImage( &buffer );
		buffer = cvCreateImage( cvSize(width,height), IPL_DEPTH_8U, type==OFX_OPENCV_GRAY ? 1: 3 );
	}
	
	// copy/convert original data
	switch( type ) {
		case OFX_OPENCV_RGB  : cvCopy(source,buffer,NULL); break;
		case OFX_OPENCV_GRAY : cvCvtColor(source,buffer,CV_BGR2GRAY); break;
		case OFX_OPENCV_HSB  : cvCvtColor(source,buffer,CV_BGR2HSV); break;
	}
	
	
	// flip ?
	if ( flipmode!=OFX_OPENCV_FLIP_NONE ) cvFlip( buffer, NULL, flipmode );
	
	
	// re-affect buffer ROI if exists
	if ( hasROI ) cvSetImageROI( buffer, roi );
	
	// set the actual buffer color space
	colormode = type;
	
}


/**
 * Converts the actual image from one color space to another.
 *
 * @param type	the target color space
 */
void ofxOpenCV::convert( int type )
{
	
	// nothing to do
	if ( colormode==type || !buffer ) return;
	
	CvRect roi;
	bool hasROI = false;
	
	
	// remember buffer ROI and 
	// release region to process with the entire image
	if ( buffer->roi ) {
		roi = cvGetImageROI( buffer );
		cvResetImageROI( buffer );
		hasROI = true;
	}
	
	
	// because buffer depth could change :
	// make a temporary copy of the actual buffer
	// before de-allocate it
	IplImage * copy = cvCloneImage(buffer);
	cvReleaseImage( &buffer );
	
	
	switch( type ) {
			
		// convert to gray
		case OFX_OPENCV_GRAY : 
			buffer = cvCreateImage( cvSize(width,height), IPL_DEPTH_8U, 1 );
			if ( colormode==OFX_OPENCV_HSB ) cvCvtColor( copy, copy, CV_HSV2BGR );
			cvCvtColor( copy, buffer, CV_BGR2GRAY );
			break;
			
		// convert to RGB
		case OFX_OPENCV_RGB : 
			buffer = cvCreateImage( cvSize(width,height), IPL_DEPTH_8U, 3 );
			cvCvtColor( copy, buffer, copy->nChannels==1 ? CV_GRAY2BGR : CV_HSV2BGR );
			break;
			
		// convert to HSB
		case OFX_OPENCV_HSB :
			buffer = cvCreateImage( cvSize(width,height), IPL_DEPTH_8U, 3 );
			if ( copy->nChannels==1 ) cvCvtColor( copy, copy, CV_GRAY2BGR );
			cvCvtColor( copy, buffer, CV_BGR2HSV );
	}
	
	
	
	
	// free memory
	cvReleaseImage( &copy );
	
	// re-affect buffer ROI if exists
	if ( hasROI ) cvSetImageROI( buffer, roi );
	
	// set the actual buffer color space
	colormode = type;
}



/**
 * Calculates the absolute difference between the image in memory and the actual buffer data.
 */
void ofxOpenCV::absDiff()
{
	
	IplImage * tmp;
	
	
	// -> exit
	if ( !memory ) return;
	
	
	// working with the same color space
	if ( memory->nChannels==buffer->nChannels ) {
		// FIXME: think about absdiff between RGB and HSV 
		cvAbsDiff( memory, buffer, buffer );
		return;
	}
	// or convert to gray first 
	else {
		tmp = cvCreateImage( cvSize(width,height), IPL_DEPTH_8U, 1 );
		// !!! set tmp image ROI to match image size
		if ( buffer->roi ) cvSetImageROI( tmp, cvGetImageROI(buffer) );
		if ( colormode==OFX_OPENCV_RGB ) cvCvtColor( memory, tmp, CV_GRAY2RGB ); // TODO: test
		else if ( colormode==OFX_OPENCV_GRAY ) cvCvtColor( memory, tmp, CV_BGR2GRAY );
		else {
			cvCvtColor( memory, tmp, CV_HSV2BGR  );
			cvCvtColor( tmp, tmp, CV_BGR2GRAY );
		}
		cvAbsDiff( tmp, buffer, buffer );
		cvReleaseImage( &tmp );
	}
	
}

void ofxOpenCV::threshold( float value, float max, int type ) 
{
	if ( buffer ) cvThreshold( buffer, buffer, value, max, type );
}


/**
 * Apply adaptive-level threshold to the current image.
 *
 * <p>This method applies adaptive-level thresholding to single-channel array.</p>
 * <p>About method:</p>
 * <ul>
 * 		<li>CV_ADAPTIVE_THRESH_MEAN_C - it is the mean of a "block_size X block_size" pixel neighborhood, minus param1</li>
 * 		<li>CV_ADAPTIVE_THRESH_GAUSSIAN_C - it is the weighted sum (gaussian) of a "block_size X block_size" pixel neighborhood, minus param1</li>
 * </ul>
 * <p>About types of thresholding:</p>
 * <ul>
 *		<li>THRESH_BINARY		: dst(x,y) = src(x,y) > value ? max : 0</li>
 *		<li>THRESH_BINARY_INV	: dst(x,y) = src(x,y) > value ? 0 : max</li>
 * </ul>
 * <p>blockSize – The size of a pixel neighborhood that is used to calculate a threshold value for the pixel: 3, 5, 7, etc</p>
 *
 * @param max		the maximum value to use with THRESH_BINARY and THRESH_BINARY_INV thresholding types.
 * @param method	the adaptive method type MEAN or GAUSSIAN
 * @param type		Types of thresholding
 * @param blockSize the block size to perform on
 * @param param1	the constant to subtract from the mean
 *
 * @see #adaptiveThreshold(float)
 */
void ofxOpenCV::adaptiveThreshold( float max, int method, int type, int block_size, float mean_constant ) 
{
	if ( colormode!=OFX_OPENCV_GRAY )  printf( "adaptive threshold is apply only on grayscale image\n" );// FIXME: convert(OFX_OPENCV_GRAY);
	else cvAdaptiveThreshold( buffer, buffer, max, method, type, block_size, mean_constant );               
}



///////////////////////////////// BLOBs DETECTION //////////////////////////////////////////////////



/**
 * Detects and returne all blobs found in the current buffered image as hypermedia.video.Blob 
 * object list.
 *
 * @param env	pointer to native method interface
 * @param obj	the Java object
 */
int ofxOpenCV::blobs( vector<ofxOpenCvBlob> &list, int minArea, int maxArea, int maxBlobs, bool findHoles, bool approx )
{
	
	// no blobs required !
	if ( !buffer || maxBlobs<=0 ) return 0;
	
	CvRect roi = cvGetImageROI( buffer );
	CvPoint point;
	CvSeqReader reader;
	
	
	// memory storage for analysis
	CvMemStorage * storage = cvCreateMemStorage();
	// to calculate gravity center, area, etc…
	CvMoments * moments = (CvMoments*) malloc( sizeof(CvMoments) );
	// the  contour list
	vector<CvSeq*> contours;
	// contour index that will walk through starting at 0 address
	CvSeq * index = 0;
	// the number of coontours found
	int count = 0;
	
	
	// make a copy
	IplImage * copy = cvCloneImage( buffer );
	
	
	//int colorspace = colormode; // <-- used to restore original image see below 
	// !!! require -> convert buffer to gray
	if ( buffer->nChannels!=1 ) convert( OFX_OPENCV_GRAY );
	
	
	// find all contours
	cvFindContours(
		buffer,
		storage,
		&index,
		sizeof(CvContour),
		findHoles ? CV_RETR_LIST : CV_RETR_EXTERNAL,
		approx ? CV_CHAIN_APPROX_SIMPLE : CV_CHAIN_APPROX_NONE,
		cvPoint( roi.x, roi.y )
	);
	
	
	
	// list valid blob's contour : included in the miminum and maximum area defined by the user
	// to remove noises or biggest blob
	while( index!=NULL ) {
		float area = fabs( cvContourArea(index,CV_WHOLE_SEQ) );
		if ( area>=minArea && area<=maxArea ) contours.push_back( index );
		index = index->h_next;
	}
	
	// reorder contours from the largest to the smallest 
	//if ( count > 0 ) qsort( contours, count, sizeof(CvSeq*), qsort_blobarea_comparator );
	// reorder contours from the largest to the smallest 
	if ( contours.size()>1 ) sort( contours.begin(), contours.end(), blobarea_comparator );
	
	// limit the number of blob elements in list
	// should be limited after Sorting elements in array to retrieve all biggest blobs
	count = MIN( (int)contours.size(), maxBlobs );
	
	
	// convert the CV formatted contour data into our Java Blob object
	for( int i=0; i<count; i++ ) {
		
		list.push_back( ofxOpenCvBlob() );
		
		// calculates all moments up to third order of a polygon or rasterized shape
		cvMoments( contours[i], moments );
		
		
		// retrieve CV blob's properties (area, arc length, bounding rectangle, gravity center)
		CvRect rect	 = cvBoundingRect( contours[i] );
		float area	 = cvContourArea(  contours[i] );
		
		list[i].area				= fabs( area );
		list[i].isHole				= area < 0 ? true : false;
		list[i].length				= cvArcLength( contours[i]) ;
		list[i].rectangle.x			= rect.x;
		list[i].rectangle.y			= rect.y;
		list[i].rectangle.width		= rect.width;
		list[i].rectangle.height	= rect.height;
		list[i].centroid.x			= ( moments->m10/moments->m00 ) + roi.x;
		list[i].centroid.y			= ( moments->m01/moments->m00 ) + roi.y;
		list[i].center.x			= rect.x+(rect.width/2);
		list[i].center.y			= rect.y+(rect.height/2);
		
		list[i].pwidth  = width;
		list[i].pheight = height;
		
		// initializes process of sequential reading from sequence
		cvStartReadSeq( contours[i], &reader, 0 );
		
		// list contour points
		for( int j=0; j<contours[i]->total; j++ ) {
			CV_READ_SEQ_ELEM( point, reader );
			list[i].points.push_back( ofPoint(point.x, point.y) );
		}
		
	}
	
	
	/*// restore original image here ?
	// why the restore function do the same think 
	if ( copy->nChannels!=1 ) {
		cvReleaseImage( &buffer );
		buffer = cvCreateImage( cvSize(width,height), IPL_DEPTH_8U, 3 );
		colormode = colorspace;
	}
	buffer = cvCloneImage( copy );*/
	
	
	// release memory
	cvReleaseMemStorage( &storage );
	cvReleaseImage( &copy );
	free( moments );
	
	
	// return the result
	return list.size();
}



/**
 * Flip the actual buffer around vertical, horizontal or both axises.
 *
 * @param mode	which axis : FLIP_HORIZONTAL, FLIP_VERTICAL or FLIP_BOTH
 */
void ofxOpenCV::flip( int mode ) 
{

	flipmode = mode;
	if ( running || !buffer ) return;
	
	CvRect roi;
	bool hasROI = false;
	
	
	// remember buffer ROI and 
	// release region to process with the entire image
	if ( buffer->roi ) {
		roi = cvGetImageROI( buffer );
		cvResetImageROI( buffer );
		hasROI = true;
	}
	
	// flip the image
	cvFlip( buffer, NULL, mode );
	
	// re-affect buffer ROI if exists
	if ( hasROI ) cvSetImageROI( buffer, roi );
	
}


//////////////////////////////// DATA MANIPULATION /////////////////////////////////////////////////


void ofxOpenCV::interpolation( int method )
{
	interpolation_mode = method;
}

/**
 * Copies into the buffer a region of a source image at the specified position by resizing the result
 * if required. no interpolation.
 *
 * @param pixels	image's data to be copied
 * @param step		full row length, explicitly the image width for non-deformation
 * @param srcx		the source X coordinate
 * @param srcy		the source Y coordinate
 * @param srcw		the source width (image or region)
 * @param srch		the source height (image or region)
 * @param destx		the destination x coordinate
 * @param desty		the destination y coordinate
 * @param destw		the destination width
 * @param desth		the destination height
 */
bool ofxOpenCV::copy( string file, float x, float y )
{
	return copy( file, 0, 0, width, height, x, y, width, height );
}
bool ofxOpenCV::copy( string file, float srcx, float srcy, float srcw, float srch, float dstx, float dsty, float dstw, float dsth )
{
	
	// -> exit : without buffer or outside the scope
	if ( !buffer || dstx>width-1 || dsty>height-1 ) {
		ofLog( OF_LOG_VERBOSE, "[ofxOpenCV] buffer does not exists or to small" );
		return false;
	}
	
	
	bool done = false;
	IplImage * src  = cvLoadImage( file.c_str(), buffer->nChannels/3 );
	if ( src ) {
		
		if ( srcw==-1 ) srcw = src->width;
		if ( srch==-1 ) srch = src->height;
		if ( dstw==-1 ) dstw = src->width;
		if ( dsth==-1 ) dsth = src->height;
		
		done = copy( src, srcx, srcy, srcw, srch, dstx, dsty, dstw, dsth );
	}
	else ofLog( OF_LOG_WARNING, "[ofxOpenCV] the image file '%s' cannot be found or loaded", file.c_str() );
	
	// free memory
	cvReleaseImage( &src );
	
	return done;
}


bool ofxOpenCV::copy( unsigned char* pixels, int step, float x, float y )
{ 
	return copy( pixels, step, 0, 0, width, height, x, y, width, height );
}

bool ofxOpenCV::copy
( unsigned char * pixels, int step, float srcx, float srcy, float srcw, float srch, float dstx, float dsty, float dstw, float dsth )
{
	
	// -> exit: without buffer, outside the scope, or without source's region
	if ( !buffer || dstx>width-1 || dsty>height-1 || srcw<=0 || srch<=0 ) return false;
	
	
	// scaled image
	IplImage * src  = cvCreateImage( cvSize(srcw,srch), IPL_DEPTH_8U, buffer->nChannels );
	
	for( int i=0; i<srch; i++ ) {
		memcpy(
			src->imageData + ( i*src->widthStep ),
			pixels + ( i*(int)srcw*src->nChannels ),
			srcw*src->nChannels
		);
	}
	
	// scaled image
	/*IplImage * src  = cvCreateImage( cvSize(step,srcsize/step), IPL_DEPTH_8U, buffer->nChannels );
	
	
	// add data
	for( int i=0; i<size; i++ ) {
		
		int x = i%step;
		int y = i/step;
		
		// retrieve scaled source and destination pixel's index
		int srci  = x+(y*step);
		int dsti = ( x*src->nChannels )+( y*src->widthStep );
		
		// get RGB values
		unsigned char b = data[ srci ] & 0xff;
		unsigned char g = data[ srci ] >> 8 & 0xff;
		unsigned char r = data[ srci ] >> 16 & 0xff;
		
		
		// set RGB pixel's value
		if ( src->nChannels==3 ) {
			src->imageData[ dsti ]		= b;
			src->imageData[ dsti+1 ]	= g;
			src->imageData[ dsti+2 ]	= r;
		}
		// or convert to gray
		else src->imageData[ dsti ] = (b*0.114) + (g*0.587) + (r*0.299);
		
	}*/
	
	// copy
	bool done = copy( src, srcx, srcy, srcw, srch, dstx, dsty, dstw, dsth );
	
	// free memory
	cvReleaseImage( &src );
	return done;
}


bool ofxOpenCV::copy( IplImage * src, float x, float y )
{
	return copy( src, 0, 0, width, height, x, y, width, height );
}
bool ofxOpenCV::copy( IplImage * src, float srcx, float srcy, float srcw, float srch, float dstx, float dsty, float dstw, float dsth )
{	
	
	int mindestw = max( (int)dstw, 1 );
	int mindesth = max( (int)dsth, 1 );
	
	dstw = dstx>=0 ? min( width, mindestw )  : ( min(width,mindestw)+dstx );
	dsth = dsty>=0 ? min( height, mindesth ) : ( min(height,mindesth)+dsty );
	
	
	// -> exit: without buffer, outside the scope, or without source's region
	if ( !buffer || dstx>width-1 || dsty>height-1 || dstw<=0 || dsth<=0 ) return false;
	if ( srcw<=0 || srch<=0 ) return false;
	
	
	
	// load the image file...
	IplImage * dest = cvCreateImage( cvSize(mindestw,mindesth), IPL_DEPTH_8U, buffer->nChannels );
	
	
	
	CvRect roi;
	bool hasROI = false;
	
	
	
	CvRect srcr, destr, bufr;
	
	srcr = cvRect(
		max( (int) srcx, 0 ),
		max( (int) srcy, 0 ),
		min( (int) srcw, (int) src->width ),
		min( (int) srch, (int) src->height )
	);
	
	// copy/scale source region
	cvSetImageROI( src, srcr );
	cvResize( src, dest, interpolation_mode );
	
	// copy destination to the actual buffer
	
	
	// remember user ROI
	if ( buffer->roi ) {
		roi = cvGetImageROI( buffer );
		hasROI = true;
	}
	
	// re-define destination dimensions to fit at most to the buffer dimensions
	bufr  = cvRect(
	   max( (int) dstx, 0 ),
	   max( (int) dsty, 0 ),
	   dstw,
	   dsth
	);
	
	destr = cvRect(
	   dstx>=0 ? 0 : -dstx,
	   dsty>=0 ? 0 : -dsty,
	   dstx>=0 ? buffer->width  - bufr.x : dstw,
	   dsty>=0 ? buffer->height - bufr.y : dsth
	);
	
	cvSetImageROI( dest, destr );
	cvSetImageROI( buffer, bufr );
	cvCopy( dest, buffer );
	
	// re-affect user ROI or clear the created one
	if ( hasROI ) cvSetImageROI( buffer, roi );
	else cvResetImageROI( buffer );
	
	
	// free memory
	cvReleaseImage( &dest );
	
	return true;
}










// -------------------------------------------------------------------------------------------------
	#pragma mark -
	#pragma mark capture and movie file
// -------------------------------------------------------------------------------------------------
	
bool ofxOpenCV::capture( int w, int h, int index, float framerate )
{
	bool done = false;
	
	// deallocate previous capture if need
	// and initialise video stream from camera
	deallocate( OFX_OPENCV_CAPTURE );
	cam = cvCreateCameraCapture( index );
	fps = OFX_OPENCV_FPS;
	
	// allocate memory on success
	// or print error message
	if ( cam ) {
	
		allocate( w, h );
		if ( framerate==-1 && property(CV_CAP_PROP_FPS)!=0 ) fps = (int)( 1000.0f*abs(1.f/property(CV_CAP_PROP_FPS)) );
		if ( framerate!=-1 ) fps = framerate==0 ? 0 : (int)( 1000.f*abs(1.f/framerate) );
		
		start();
		done = true;
	}
	else ofLog( OF_LOG_ERROR, "[ofxOpenCV] error while starting capture : device %i", index );
	
	
	// functions not yet implemented
	// cvSetCaptureProperty( cam, CV_CAP_PROP_FRAME_WIDTH, (double) width );
	// cvSetCaptureProperty( cam, CV_CAP_PROP_FRAME_HEIGHT, (double) height );
	// cvSetCaptureProperty( cam, CV_CAP_PROP_FPS, (double)framerate );
	
	return done;
}


/**
 * Initializes OpenCV for reading the video stream from the specified file.
 *
 * @param env	pointer to native method interface
 * @param obj	the Java object
 */
bool ofxOpenCV::movie( string file, int w, int h, float framerate )
{	
	bool done = false;
	
	#ifdef OF_MAIN
	file = ofToDataPath(file);
	#endif
	
	// deallocate previous capture if need
	// load the movie with the absolute path...
	deallocate( OFX_OPENCV_CAPTURE );
	cam = cvCreateFileCapture( file.c_str() );
	fps = OFX_OPENCV_FPS;
	
	// retrieve movie size and allocate memory for this capture
	// or print error message
	if ( cam ) {
	
		allocate(
			w==-1 ? cvGetCaptureProperty(cam,CV_CAP_PROP_FRAME_WIDTH) : w,
			h==-1 ? cvGetCaptureProperty(cam,CV_CAP_PROP_FRAME_HEIGHT): h
		);
		
		// can found movie size ?
		if ( width==0 && height==0 ) {
			ofLog( OF_LOG_WARNING, "[ofxOpenCV] the movie file '%s' is successfully loaded", file.c_str() );
			ofLog( OF_LOG_WARNING, "[ofxOpenCV] but can not retrieve movie dimensions" );
			ofLog( OF_LOG_WARNING, "[ofxOpenCV] please specify movie size by hand" );
		}
		
		
		if ( framerate==-1 && property(CV_CAP_PROP_FPS)!=0 ) fps = (int)( 1000.0f*abs(1.f/property(CV_CAP_PROP_FPS)) );
		if ( framerate!=-1 ) fps = framerate==0 ? 0 : (int)( 1000.f*abs(1.f/framerate) );
		
		start();
		done = true;
		
	}
	else ofLog( OF_LOG_WARNING, "[ofxOpenCV] the movie file '%s' cannot be found or loaded", file.c_str() );
	return done;
}

/**
 * Loads an image from the specified file.
 *
 * @param env	pointer to native method interface
 * @param obj	the Java object
 * @param file	the name of file to be loaded
 */
bool ofxOpenCV::image( string file, int w, int h )
{
	bool done = false;
	
	
	#ifdef OF_MAIN
	file = ofToDataPath(file);
	#endif
	
	// cvReleaseImage(&image) before image = cvLoadImage() to avoid memory leaks
	// load the image file...
	IplImage * src = cvLoadImage( file.c_str() );
	
	
	// set buffer's memory and copy/scale the new data, 
	// or display a message on error
	if ( src ) {
		
		// allocate
		allocate(
			w==-1 ? src->width : w, 
			h==-1 ? src->height : h
		);
		
		// copy/scale original image
		if ( src->width!=w || src->height!=h ) cvResize( src, source, interpolation_mode );
		else cvCopy( src, source );
		
		// copy to the buffer
		restore( OFX_OPENCV_RGB );
		done = true;
	}
	else ofLog( OF_LOG_WARNING, "[ofxOpenCV] the image file '%s' cannot be found or loaded", file.c_str() );
	
	// free memory
	cvReleaseImage( &src );
	
	return done;
	
}


void ofxOpenCV::read()
{

	// exit to prevent errors if no capture exists
	// or if OpenCV dimensions are not set
	if ( !cam || !width ) return;
	
	// while capturing this method is call internaly
	if ( running && !bnew_frame ) return;

	
	// grab frame from the video stream
	// WARNING : The returned image should not be released or modified
	IplImage * frame = cvQueryFrame( cam );
	if ( !frame ) return;
	
	
	// scale image if needed
	if ( frame->width!=width || frame->height!=height ) { cvResize( frame, source, interpolation_mode ); }
	else cvCopy( frame, source );
	
	// automatically flip Windows bitmap : origin = bottom-left
	if ( frame->origin==1 ) cvFlip( source, NULL, OFX_OPENCV_FLIP_VERTICAL );
	
	// copy the original image to the buffer
	restore( colormode );
	
	
	// frame is always new when playing with no framerate
	bnew_frame = fps==0 ? true : false;
}

bool ofxOpenCV::available()
{
	return bnew_frame;
}


/**
 * Jump to a specific movie frame.
 */
void ofxOpenCV::jump( float value, int type )
{
	if ( cam ) {
		cvSetCaptureProperty( cam, type, (double)value );
		bnew_frame = true;
	}
}

/**
 * Go the the next movie frame.
 */
void ofxOpenCV::next()
{
	if ( cam ) {
	
		double frame = cvGetCaptureProperty( cam, CV_CAP_PROP_POS_FRAMES );
		double max = cvGetCaptureProperty( cam, CV_CAP_PROP_FRAME_COUNT );
		// FIXME: with cycle
		//if ( frame>max ) frame = frame - ( max*(int)(frame/max) );
		if ( ++frame<=max ) {
			cvSetCaptureProperty( cam, CV_CAP_PROP_POS_FRAMES, frame-1 );
			printf("-> %f \n", cvGetCaptureProperty( cam, CV_CAP_PROP_POS_FRAMES ));
			bnew_frame = true;
		}
	}
}

void ofxOpenCV::previous()
{
	if ( cam ) {
	
		double frame = cvGetCaptureProperty( cam, CV_CAP_PROP_POS_FRAMES );
		// FIXME: with cycle
		// prepare the previous frame, movie will be read so jump to -2
		cvSetCaptureProperty( cam, CV_CAP_PROP_POS_FRAMES, --frame<=0 ? -1 : frame-1 );
		bnew_frame = true;
	}
}


/**
 * Retrieve the specified information from the current capture
 *
 * @param env		pointer to native method interface
 * @param obj		the Java object
 * @param property	the required information type
 */
float ofxOpenCV::property( int which )
{
	if ( !cam ) return -1.;
	//#if defined(__APPLE__) && !defined(__x86_64__)
		/*if ( cam==CvCapture_QT_Cam ) {
			ofLog( OF_LOG_WARNING, "capture properties currently unimplemented for QuickTime camera interface" );
			return 0;
		}*/
	//#else
		return max( 0., cvGetCaptureProperty(cam,which) );
	//#endif
}







///////////////////////////// REGION OF INTEREST ///////////////////////////////////////////////////

/**
 * Sets buffer region of interest to the given rectangle.
 *
 * @param env	pointer to native method interface
 * @param obj	the Java object
 *
 * @param x		the region x coordinate
 * @param y		the region y coordinate
 * @param w		the region width
 * @param h		the region height
 */
void ofxOpenCV::ROI( float x, float y, float w, float h )
{
	
	// clear the previous images ROI
	cvResetImageROI( buffer );
	if ( memory ) cvResetImageROI( memory );
	
	
	// set the new images ROI,
	// except for entire image to deallocate buffer->roi
	if ( x==0 && y==0 && width==0 && height==0 ) return;
	if ( x!=0 || y!=0 || w!=width || h!=height ) {
		cvSetImageROI( buffer, cvRect( min((int)x,width-1), min((int)y,height-1), max(1,(int)w), max(1,(int)h) ) );
		if ( memory ) cvSetImageROI( memory, cvGetImageROI(buffer) );
	}
}


/**
 * Invert buffer data colors.
 */
void ofxOpenCV::invert()
{
	if ( buffer ) cvXorS( buffer, cvScalarAll(255), buffer, 0 );
}

/*void ofxOpenCV::kinectThreshold( int near, int far, bool adaptive )
{

	
	near = MAX( 0, MIN( near, 255 ));
	far  = MAX( 0, MIN( far,  255 ));
	

	IplImage * copy = cvCloneImage( buffer );
	
	// proceed thresholding
	if ( !adaptive ) {
		cvThreshold( buffer, buffer, near, 255, CV_THRESH_BINARY );
		cvThreshold( copy, copy, far, 255, CV_THRESH_BINARY_INV );
	}
	else {
		cvAdaptiveThreshold( buffer, buffer, near, CV_ADAPTIVE_THRESH_MEAN_C, CV_THRESH_BINARY, 3, 5.0 );
		cvAdaptiveThreshold( copy, copy, far, CV_ADAPTIVE_THRESH_MEAN_C, CV_THRESH_BINARY_INV, 3, 5.0 );
	}
	
	// Calculates per-element bit-wise conjunction of two arrays
	cvAnd( buffer, copy, buffer, NULL );
	
	// release copy image
	cvReleaseImage( &copy );
	
}*/


/**
 * Smooth the image in one of several ways.
 *
 * @param env	pointer to native method interface
 * @param obj	the Java object
 */
void ofxOpenCV::blur( int type, int param1, int param2, float param3, float param4 )
{
	
	// -> exit: without buffer or with no blur values
	if ( !buffer || (!param1&&!param2&&!param3&&!param4) ) return;
	
	
	/** --------------------------------------------------------------------------------------------
	 CV errors : Only normalized box filter can be used for 8u->8u transformation
	 -------------------------------------------------------------------------------------------- */
	if ( type==CV_BLUR_NO_SCALE ) return;
	/*if ( type==CV_BLUR_NO_SCALE && buffer->nChannels!=1 ) {
	 std::cout << "you must convert the image to GRAY before using the BLUR_NO_SCALE operation";
	 std::cout << endl;
	 return;
	 }*/
	
	
	
	// define the maximum value relativily to the actual buffer size
	int maximum = width-height==0 ? 1 : ( (width*height) / (abs(width-height)*2) )+1;
	
	// convert pair values to the upper odd values (if need)
	if ( param1%2==0 ) param1++;
	if ( param2!=0 && param2%2==0 ) param2++;
	
	
	// smooth buffer
	cvSmooth(
			 buffer,
			 buffer,
			 (int) type,
			 min( max(1,(int)param1), maximum),
			 min( max(0,(int)param2), maximum), 
			 (double) param3,
			 (double) param4
	);
}



/**
 * Adjust the buffer brightness/contrast with the given values.
 *
 * @param brightness	the new brightness value (
 * @param contrast		the new contrast value
 */
void ofxOpenCV::brightnessContrast( int brightness, int contrast )
{
	
	// of course -> exit
	if ( !buffer ) return;
	
	
	uchar data[ 256 ];
	CvMat * matrix;
	double delta, a, b;
	
	// create a new matrix header (8-bit unsigned single-channel)
	// to assigns image data
	matrix = cvCreateMatHeader( 1, 256, CV_8UC1 );
    cvSetData( matrix, data, 0 );
	
	
	// set values
	if ( contrast>0 ) {
        delta = (127.*contrast) / 128;
        a = 255. / ( 255.-(delta*2) );
        b = a * (brightness-delta);
    }
    else {
		delta = (-128.*contrast) / 128;
		a = ( 256.-(delta*2) ) / 255.;
		b = ( a*brightness )+delta;
    }
	
	
	// update data
	for( int i=0; i<256; i++ ) {
		int value = cvRound( (a*i)+b );
		data[i]	= (uchar) min( max(0,value), 255 );
	}
	
	// performs look-up table transform
    cvLUT( buffer, buffer, matrix );
	
	
	// deallocate matrix
	cvReleaseMat( &matrix );
}






// -------------------------------------------------------------------------------------------------
	#pragma mark -
	#pragma mark object detection
// -------------------------------------------------------------------------------------------------

/**
 * Load and store file description of a trained cascade classifier for a futur objects detection.
 *
 * @param env	pointer to native method interface
 * @param obj	the Java object
 * @param file	the haar classifier cascade file to be used for object detection
 */
bool ofxOpenCV::cascade( string file )
{
	
	if ( !buffer ) return false;
	
	
	// load object detection description
	haarcascade = (CvHaarClassifierCascade*) cvLoad( file.c_str() );
	
	// report error if file description can not be loaded
	if ( !haarcascade ) {
		ofLog( OF_LOG_ERROR, "[ofxOpenCV] the haar classifier cascade file '%s' can not be found", file.c_str() );
		return false;
	}
	
	return true;
}


/**
 * Detects object(s) in the current buffer with the actual cascade classifier.
 *
 * @param env	pointer to native method interface
 * @param obj	the Java object
 *
 * @return		a list of all rectangle as java.awt.Rectangle
 */
int ofxOpenCV::detect( vector<ofRectangle> &list, float scale, int min_neighbors, int flags, int minw, int minh )
{
	
	// -> exit : without buffer or cascade
	if ( !buffer || !haarcascade ) return 0;
	
	
	
	
	// allocate memory for calculations
	CvMemStorage * storage = cvCreateMemStorage( 0 );
	
	// there can be more than one face in an image -> create a growable sequence of faces.
	// detect the objects and store them in the sequence
	CvSeq * faces = cvHaarDetectObjects(
		buffer,
		haarcascade,
		storage,
		scale, 
		min_neighbors, 
		flags,
		cvSize( minw, minh ) 
	);
	
	
	
	// convert each face rectangle
	for( int i=0; i<faces->total; i++ ) {
		CvRect * rect = (CvRect*) cvGetSeqElem( faces, i );
		list.push_back( ofRectangle(rect->x, rect->y, rect->width, rect->height) );
	}
	
	
	// clear the memory storage
    cvReleaseMemStorage( &storage );
	
	// return the result
	return list.size();
	
}


////////////////////////////////////////////////////////////////////////////////////////////////////

/**
 * Blob's area quicksort comparator : reorder areas from the largest to the smallest.
 * This code was taken from the openframeworks version 0.04 opencv addon example.
 * http://openframeworks.cc/
 *
 */
bool ofxOpenCV::blobarea_comparator( const CvSeq *contour1, const CvSeq *contour2 )
{
	return fabs(cvContourArea(contour1)) > fabs(cvContourArea(contour2));
}






// -------------------------------------------------------------------------------------------------
	#pragma mark -
	#pragma mark thread
// -------------------------------------------------------------------------------------------------

// or play()
void ofxOpenCV::start()
{
	bnew_frame = fps==0;
	if ( cam && !running && fps>0 ) {
	
		running = true;	// <-- true to run loop while creating thread !!!
		
		pthread_mutex_init( &mutex, NULL );
		running = !pthread_create( &thread, NULL, run, (void*)this );
		if ( !running ) ofLog( OF_LOG_ERROR,"[ofxOpenCV] pthread_create failed" );
	}
}

void ofxOpenCV::pause()
{
	if ( running ) {
		running = false;
		pthread_join( thread, NULL );
	}
}

void ofxOpenCV::stop()
{
	pause();
	jump(0); // no effect on input stream :)
}


void * ofxOpenCV::run( void *ptr )
{
	
	ofxOpenCV *me = (ofxOpenCV*)ptr;
	
	while( me->running ) {
		me->bnew_frame = true;
		usleep( me->fps*1000 );
	}
	
	pthread_mutex_destroy( &me->mutex );
	pthread_exit( NULL );
	me->thread = NULL;
}

bool ofxOpenCV::playing()
{
	return running;
}


/*void ofxOpenCV::lock()
{
	if ( running && !locked ) locked = !pthread_mutex_lock(&mutex);
}

void ofxOpenCV::unlock()
{
	if ( running && locked ) locked = pthread_mutex_unlock(&mutex);
}*/








////////////////////////////////////// DRAW METHODS ////////////////////////////////////////////////



void ofxOpenCV::draw( float x, float y, int kind )
{
	draw( x, y, width, height, kind );
}


/**
 * TODO: add something like draw ROI only
 */
void ofxOpenCV::draw( float x, float y, float width, float height, int kind, int mode )
{

	IplImage * src;
	
	
	// -> exit: image not allocate or deallocate
	if ( kind==OFX_OPENCV_SOURCE && !source ) return;
	if ( kind==OFX_OPENCV_BUFFER && !buffer ) return;
	if ( kind==OFX_OPENCV_MEMORY && !memory ) return;
	
	
	
	// select image
	switch( kind ) {
		case OFX_OPENCV_MEMORY	: src = memory; break;
		case OFX_OPENCV_SOURCE	: src = source; break;
		default					: src = buffer; break;
	}
 	
	
	// draw depth data preview
	//if ( running && kind==OFX_OPENCV_BUFFER ) pthread_mutex_lock(&mutex);
	
	glPushMatrix();
	glTranslatef( x, y, 0 );
	glEnable( GL_TEXTURE_2D );
	glBindTexture( GL_TEXTURE_2D, tex );
	glTexImage2D( GL_TEXTURE_2D, 0, 3, src->width, src->height, 0, src->nChannels==3 ? mode : GL_LUMINANCE, GL_UNSIGNED_BYTE, src->imageData );
	glBegin( GL_TRIANGLE_FAN );
	glTexCoord2f(0,0); glVertex3f( 0, 0, 0 );
	glTexCoord2f(1,0); glVertex3f( width, 0, 0 );
	glTexCoord2f(1,1); glVertex3f( width, height, 0 );
	glTexCoord2f(0,1); glVertex3f( 0, height, 0 );
	glEnd();
	glDisable( GL_TEXTURE_2D );
	glPopMatrix();
	
	//if ( running && kind==OFX_OPENCV_BUFFER ) pthread_mutex_unlock(&mutex);

}
