
#include "ofxCvImage.h"
#include "ofxCvGrayscaleImage.h"
#include "ofxCvColorImage.h"
#include "ofxCvFloatImage.h"



//--------------------------------------------------------------------------------
ofxCvImage::ofxCvImage() {
    width			= 0;
    height			= 0;
    roiX            = 0;
    roiY            = 0;
    bUseTexture		= true;
    bTextureDirty   = true;
	bAllocated		= false;
	pixels			= NULL;
    bPixelsDirty    = true;
    pixelsWidth     = 0;
    pixelsHeight    = 0;
    bUseRoiOffsetWhenDrawing = false;
}

//--------------------------------------------------------------------------------
ofxCvImage::~ofxCvImage() {
    clear();
}

//--------------------------------------------------------------------------------
void ofxCvImage::allocate( int w, int h ) {
	if (bAllocated == true){
		ofLog(OF_LOG_WARNING, "in allocate, reallocating a ofxCvImage");
		clear();
	}
    
	cvImage = cvCreateImage( cvSize(w,h), ipldepth, iplchannels );
	cvImageTemp	= cvCreateImage( cvSize(w,h), ipldepth, iplchannels );

	width = w;
	height = h;
    roiStack.push_back( ofRectangle(0,0,w,h) );   // set ROI to full image
	bAllocated = true;

    if( bUseTexture ) {
        tex.allocate( width, height, glchannels );
        bTextureDirty = true;
    }
}

//--------------------------------------------------------------------------------
void ofxCvImage::clear() {

	if (bAllocated == true){
		if (width > 0 && height > 0){
			cvReleaseImage( &cvImage );
			cvReleaseImage( &cvImageTemp );
		}
        if( pixels != NULL ) {
            delete pixels;
            pixels = NULL;
            bPixelsDirty = true;
            pixelsWidth = 0;
            pixelsHeight = 0;            
        }
		width = 0;
		height = 0;
        roiX = 0;    
        roiY = 0;
        roiStack.clear(); 

		if( bUseTexture ) {
			tex.clear();
            bTextureDirty = true;
		}
		
		bAllocated = false;
	}
}

//--------------------------------------------------------------------------------
float ofxCvImage::getWidth(){
	return width;
}

//--------------------------------------------------------------------------------
float ofxCvImage::getHeight(){
	return height;
}

//--------------------------------------------------------------------------------
void ofxCvImage::setUseTexture( bool bUse ) {
	bUseTexture = bUse;
}

//--------------------------------------------------------------------------------
ofTexture& ofxCvImage::getTextureReference() {
	return tex;
}

//--------------------------------------------------------------------------------
void ofxCvImage::swapTemp() {
	IplImage*  temp;
	temp = cvImage;
	cvImage	= cvImageTemp;
	cvImageTemp	= temp;
}

//--------------------------------------------------------------------------------
void ofxCvImage::flagImageChanged() {
    bTextureDirty = true;
    bPixelsDirty = true;
}

//--------------------------------------------------------------------------------
void ofxCvImage::setUseRoiOffsetWhenDrawing( bool bUse ) {
    bUseRoiOffsetWhenDrawing = bUse;
}

//--------------------------------------------------------------------------------
void ofxCvImage::rangeMap( IplImage* img, float min1, float max1, float min2, float max2 ) {
    // map from min1-max1 to min2-max2
    float scale = (max2-min2)/(max1-min1);
    cvConvertScale( img, img, scale, -(min1*scale)+min2 );
}

//--------------------------------------------------------------------------------
bool ofxCvImage::pushSetBothToTheirIntersectionROI( ofxCvImage& img1, ofxCvImage& img2 ) {
    // calculates intersection ROI
    // pushes the intersection ROI on both the images' roiStack
    // use popROI() to restore previous ROI
    ofRectangle iRoi = getIntersectionROI( img1.getROI(), img2.getROI() );
    if( iRoi.width > 0 && iRoi.height > 0 ) { 
        img1.pushROI();
        img1.setROI( iRoi );
        img2.pushROI();
        img2.setROI( iRoi );            
        return true;
    } else {
        return false;
    }
}


// ROI - region of interest

//--------------------------------------------------------------------------------
void ofxCvImage::pushROI() {
    roiStack.push_back( ofRectangle() );
    setROI(0,0,cvImage->width,cvImage->height);
    
}

//--------------------------------------------------------------------------------
void ofxCvImage::popROI() {
    if(roiStack.size() > 1) {
        roiStack.pop_back();
    } else {
        ofLog(OF_LOG_WARNING, "in popROI, not popping since there is only one element left.");
    }
    setROI( roiStack.back() );
}

//--------------------------------------------------------------------------------
void ofxCvImage::setROI( int x, int y, int w, int h ) {
    
    x = (int)ofClamp(x, 0, (int)cvImage->width-1);
    y = (int)ofClamp(y, 0, (int)cvImage->height-1);
    w = (int)ofClamp(w, 0, (int)cvImage->width - x);
    h = (int)ofClamp(h, 0, (int)cvImage->height - y);
    
    cvSetImageROI( cvImage, cvRect(x,y, w,h) );
    cvSetImageROI( cvImageTemp, cvRect(x,y, w,h) );
    width = w;
    height = h;
    roiX = x;
    roiY = y;
    roiStack.back().x = x;
    roiStack.back().y = y;
    roiStack.back().width = w;
    roiStack.back().height = h;    
    flagImageChanged();
}

//--------------------------------------------------------------------------------
void ofxCvImage::setROI( const ofRectangle& rect ) {
    setROI( (int)rect.x, (int)rect.y, (int)rect.width, (int)rect.height );
}

//--------------------------------------------------------------------------------
ofRectangle ofxCvImage::getROI() {
    return roiStack.back();
}

//--------------------------------------------------------------------------------
void ofxCvImage::resetROI() {
    cvResetImageROI( cvImage );
    cvResetImageROI( cvImageTemp );
    width = cvImage->width;
    height = cvImage->height;
    roiX = 0;
    roiY = 0;
    setROI( 0,0, cvImage->width, cvImage->height );
}

//--------------------------------------------------------------------------------
ofRectangle ofxCvImage::getIntersectionROI( const ofRectangle& r1, const ofRectangle& r2 ) {
    int r1x1 = (int)r1.x;
    int r1y1 = (int)r1.y;
    int r1x2 = (int)(r1.x+r1.width);
    int r1y2 = (int)(r1.y+r1.height);

    int r2x1 = (int)r2.x;
    int r2y1 = (int)r2.y;
    int r2x2 = (int)(r2.x+r2.width);
    int r2y2 = (int)(r2.y+r2.height);

    int r3x1 = 0;
    int r3y1 = 0;
    int r3x2 = 0;
    int r3y2 = 0;
    
    bool bIntersect =  ( ( ofInRange(r2x1, r1x1,r1x2) || ofInRange(r1x1, r2x1,r2x2) ) &&
                         ( ofInRange(r2y1, r1y1,r1y2) || ofInRange(r1y1, r2y1,r2y2) ) );

    if( bIntersect ){
        r3x1 = MAX( r1x1, r2x1 );
        r3y1 = MAX( r1y1, r2y1 );

        r3x2 = MIN( r1x2, r2x2 );
        r3y2 = MIN( r1y2, r2y2 );
    
        return ofRectangle( r3x1,r3y1, r3x2-r3x1,r3y2-r3y1 );
        
    } else {
        return ofRectangle( 0,0, 0,0 );
    }
}




// Set Pixel Data

//--------------------------------------------------------------------------------
void  ofxCvImage::operator = ( const IplImage* mom ) {
	if( mom->width == width && mom->height == height &&
	    mom->nChannels == cvImage->nChannels && 
        mom->depth == cvImage->depth )
    {
		cvCopy( mom, cvImage );
        flagImageChanged();
	} else {
        ofLog(OF_LOG_ERROR, "in =, images need to match in size, channels, and depth");
	}
}

//--------------------------------------------------------------------------------
void ofxCvImage::operator -= ( float value ) {
	cvSubS( cvImage, cvScalar(value), cvImageTemp );
	swapTemp();
    flagImageChanged();
}

//--------------------------------------------------------------------------------
void ofxCvImage::operator += ( float value ) {
	cvAddS( cvImage, cvScalar(value), cvImageTemp );
	swapTemp();
    flagImageChanged();
}


//--------------------------------------------------------------------------------
void ofxCvImage::operator -= ( ofxCvImage& mom ) {
	if( mom.getCvImage()->nChannels == cvImage->nChannels && 
        mom.getCvImage()->depth == cvImage->depth )
    {
        if( pushSetBothToTheirIntersectionROI(*this,mom) ) {
            cvSub( cvImage, mom.getCvImage(), cvImageTemp );
            swapTemp();
            popROI();       //restore prevoius ROI
            mom.popROI();   //restore prevoius ROI              
            flagImageChanged();
        } else {
            ofLog(OF_LOG_ERROR, "in -=, ROI mismatch");
        }
	} else {
        ofLog(OF_LOG_ERROR, "in -=, images need to have matching type");
	}
}

//--------------------------------------------------------------------------------
void ofxCvImage::operator += ( ofxCvImage& mom ) {
	if( mom.getCvImage()->nChannels == cvImage->nChannels && 
        mom.getCvImage()->depth == cvImage->depth )
    {
        if( pushSetBothToTheirIntersectionROI(*this,mom) ) {
            cvAdd( cvImage, mom.getCvImage(), cvImageTemp );
            swapTemp();
            popROI();       //restore prevoius ROI
            mom.popROI();   //restore prevoius ROI              
            flagImageChanged();
        } else {
            ofLog(OF_LOG_ERROR, "in +=, ROI mismatch");
        }
	} else {
        ofLog(OF_LOG_ERROR, "in +=, images need to have matching type");
	}
}

//--------------------------------------------------------------------------------
void ofxCvImage::operator *= ( ofxCvImage& mom ) {
	if( mom.getCvImage()->nChannels == cvImage->nChannels && 
        mom.getCvImage()->depth == cvImage->depth )
    {
        if( pushSetBothToTheirIntersectionROI(*this,mom) ) {
            float scalef = 1.0f / 255.0f;
            cvMul( cvImage, mom.getCvImage(), cvImageTemp, scalef );
            swapTemp();
            popROI();       //restore prevoius ROI
            mom.popROI();   //restore prevoius ROI              
            flagImageChanged();
        } else {
            ofLog(OF_LOG_ERROR, "in *=, ROI mismatch");
        }
	} else {
        ofLog(OF_LOG_ERROR, "in *=, images need to have matching type");
	}
}

//--------------------------------------------------------------------------------
void ofxCvImage::operator &= ( ofxCvImage& mom ) {
	if( mom.getCvImage()->nChannels == cvImage->nChannels && 
        mom.getCvImage()->depth == cvImage->depth )
    {
        if( pushSetBothToTheirIntersectionROI(*this,mom) ) {
            cvAnd( cvImage, mom.getCvImage(), cvImageTemp );
            swapTemp();
            popROI();       //restore prevoius ROI
            mom.popROI();   //restore prevoius ROI              
            flagImageChanged();
        } else {
            ofLog(OF_LOG_ERROR, "in &=, ROI mismatch");
        }
	} else {
        ofLog(OF_LOG_ERROR, "in &=, images need to have matching type");
	}
}



// Draw Image

//--------------------------------------------------------------------------------
void ofxCvImage::draw( float x, float y ) {
    draw( x,y, width,height );
}

//--------------------------------------------------------------------------------
void ofxCvImage::draw( float x, float y, float w, float h ) {    
    if( bUseTexture ) {
        if( bTextureDirty ) {
            if(tex.getWidth() != width || tex.getHeight() != height) {
                //ROI was changed
                // reallocating texture - this could be faster with ROI support
                tex.clear();
                tex.allocate( width, height, glchannels );
            }
            tex.loadData( getPixels(), width, height, glchannels );
            bTextureDirty = false;
        }
        
        if( bUseRoiOffsetWhenDrawing ){
            x += roiX;
            y += roiY;
        }        

        tex.draw(x,y, w,h);

    } else {
        // this is slower than the typical draw method based on textures
        // but useful when dealing with threads GL textures often don't work
        ofLog(OF_LOG_NOTICE, "in draw, using slow texture-less drawing");
        ofLog(OF_LOG_NOTICE, "texture-less drawing - be aware, unlike texture drawing, \
                          this always draws window aligned, rotation not supported");
        
        if( x == 0) {
            x += 0.01;
            ofLog(OF_LOG_NOTICE, "BUG: can't draw at x==0 in texture-less mode.");
        }
        
        if(bAnchorIsPct){
			x -= anchor.x * w;
			y -= anchor.y * h;
		}else{
			x -= anchor.x;
			y -= anchor.y;
		}
        
        if( bUseRoiOffsetWhenDrawing ){
            x += roiX;
            y += roiY;
        }
                
        glRasterPos2f( x, y+h );

        IplImage* tempImg;
        tempImg = cvCreateImage( cvSize((int)w, (int)h), ipldepth, iplchannels );        
        cvResize( cvImage, tempImg, CV_INTER_NN );
        cvFlip( tempImg, tempImg, 0 );
        glDrawPixels( tempImg->width, tempImg->height ,
                      glchannels, gldepth, tempImg->imageData );
        cvReleaseImage( &tempImg );
       
    }
}

//--------------------------------------------------------------------------------
void ofxCvImage::setAnchorPercent( float xPct, float yPct ){
    if( bUseTexture ) {
    	tex.setAnchorPercent(xPct,yPct);
    } else {
        anchor.x = xPct;
        anchor.y = yPct;

        bAnchorIsPct = true;
    }
}

//--------------------------------------------------------------------------------
void ofxCvImage::setAnchorPoint( int x, int y ){
    if( bUseTexture ) {
    	tex.setAnchorPoint(x,y);
    }else{
        anchor.x = x;
        anchor.y = y;

        bAnchorIsPct = false;
    }
}

//--------------------------------------------------------------------------------
void ofxCvImage::resetAnchor(){
    if( bUseTexture ) {
    	tex.resetAnchor();
    }else{
        anchor.set(0,0);
        bAnchorIsPct = false;
    }
}



// Image Filter Operations
//--------------------------------------------------------------------------------
void ofxCvImage::dilate() {
	cvDilate( cvImage, cvImageTemp, 0, 1 );
	swapTemp();
    flagImageChanged();
}

//--------------------------------------------------------------------------------
void ofxCvImage::erode() {
	cvErode( cvImage, cvImageTemp, 0, 1 );
	swapTemp();
    flagImageChanged();
}

//--------------------------------------------------------------------------------
void ofxCvImage::blur( int value ) {
    if( value % 2 == 0 ) {
        ofLog(OF_LOG_NOTICE, "in blur, value not odd -> will add 1 to cover your back");
        value++;
    }
	cvSmooth( cvImage, cvImageTemp, CV_BLUR , value);
	swapTemp();
    flagImageChanged();
}

//--------------------------------------------------------------------------------
void ofxCvImage::blurGaussian( int value ) {
    if( value % 2 == 0 ) {
        ofLog(OF_LOG_NOTICE, "in blurGaussian, value not odd -> will add 1 to cover your back");
        value++;
    }
	cvSmooth( cvImage, cvImageTemp, CV_GAUSSIAN ,value );
	swapTemp();
    flagImageChanged();
}

//--------------------------------------------------------------------------------
void ofxCvImage::invert(){
    cvNot(cvImage, cvImage);
    flagImageChanged();
}




// Image Transformation Operations

//--------------------------------------------------------------------------------
void ofxCvImage::mirror( bool bFlipVertically, bool bFlipHorizontally ) {
	int flipMode = 0;

	if( bFlipVertically && !bFlipHorizontally ) flipMode = 0;
	else if( !bFlipVertically && bFlipHorizontally ) flipMode = 1;
	else if( bFlipVertically && bFlipHorizontally ) flipMode = -1;
	else return;

	cvFlip( cvImage, cvImageTemp, flipMode );
	swapTemp();
    flagImageChanged();
}

//--------------------------------------------------------------------------------
void ofxCvImage::translate( float x, float y ) {
    transform( 0, 0,0, 1,1, x,y );
    flagImageChanged();
}

//--------------------------------------------------------------------------------
void ofxCvImage::rotate( float angle, float centerX, float centerY ) {
    transform( angle, centerX, centerY, 1,1, 0,0 );
    flagImageChanged();
}

//--------------------------------------------------------------------------------
void ofxCvImage::scale( float scaleX, float scaleY ) {
    transform( 0, 0,0, scaleX,scaleY, 0,0 );
    flagImageChanged();
}

//--------------------------------------------------------------------------------
void ofxCvImage::transform( float angle, float centerX, float centerY,
                            float scaleX, float scaleY,
                            float moveX, float moveY ){
    float sina = sin(angle * DEG_TO_RAD);
    float cosa = cos(angle * DEG_TO_RAD);
    CvMat*  transmat = cvCreateMat( 2,3, CV_32F );
    cvmSet( transmat, 0,0, scaleX*cosa );
    cvmSet( transmat, 0,1, scaleY*sina );
    cvmSet( transmat, 0,2, -centerX*scaleX*cosa - centerY*scaleY*sina + moveX + centerX );
    cvmSet( transmat, 1,0, -1.0*scaleX*sina );
    cvmSet( transmat, 1,1, scaleY*cosa );
    cvmSet( transmat, 1,2, -centerY*scaleY*cosa + centerX*scaleX*sina + moveY + centerY);

    cvWarpAffine( cvImage, cvImageTemp, transmat );
	swapTemp();
    flagImageChanged();

    cvReleaseMat( &transmat );
}

//--------------------------------------------------------------------------------
void ofxCvImage::undistort( float radialDistX, float radialDistY,
                            float tangentDistX, float tangentDistY,
                            float focalX, float focalY,
                            float centerX, float centerY ){
    float camIntrinsics[] = { focalX, 0, centerX, 0, focalY, centerY, 0, 0, 1 };
    float distortionCoeffs[] = { radialDistX, radialDistY, tangentDistX, tangentDistY };
    cvUnDistortOnce( cvImage, cvImageTemp, camIntrinsics, distortionCoeffs, 1 );
	swapTemp();
    flagImageChanged();
}


//--------------------------------------------------------------------------------
void ofxCvImage::remap( IplImage* mapX, IplImage* mapY ) {
    cvRemap( cvImage, cvImageTemp, mapX, mapY );
	swapTemp();
    flagImageChanged();
}




/**
*    A  +-------------+  B
*      /               \
*     /                 \
*    /                   \
* D +-------------------- +  C
*/

//--------------------------------------------------------------------------------
void ofxCvImage::warpPerspective( const ofPoint& A, const ofPoint& B, const ofPoint& C, const ofPoint& D ) { 
    // compute matrix for perspectival warping (homography) 
    CvPoint2D32f cvsrc[4]; 
    CvPoint2D32f cvdst[4]; 
    CvMat* translate = cvCreateMat( 3,3, CV_32FC1 ); 
    cvSetZero( translate ); 

    cvdst[0].x = 0; 
    cvdst[0].y = 0; 
    cvdst[1].x = width; 
    cvdst[1].y = 0; 
    cvdst[2].x = width; 
    cvdst[2].y = height; 
    cvdst[3].x = 0; 
    cvdst[3].y = height; 

    cvsrc[0].x = A.x; 
    cvsrc[0].y = A.y; 
    cvsrc[1].x = B.x; 
    cvsrc[1].y = B.y; 
    cvsrc[2].x = C.x; 
    cvsrc[2].y = C.y; 
    cvsrc[3].x = D.x; 
    cvsrc[3].y = D.y; 

    cvWarpPerspectiveQMatrix( cvsrc, cvdst, translate );  // calculate homography 
    cvWarpPerspective( cvImage, cvImageTemp, translate ); 
    swapTemp();
    flagImageChanged();
    cvReleaseMat( &translate ); 
} 



//--------------------------------------------------------------------------------
void ofxCvImage::warpIntoMe( ofxCvImage& mom, const ofPoint src[4], const ofPoint dst[4] ){
    if( mom.getCvImage()->nChannels == cvImage->nChannels && 
        mom.getCvImage()->depth == cvImage->depth ) {
    
    	// compute matrix for perspectival warping (homography)
    	CvPoint2D32f cvsrc[4];
    	CvPoint2D32f cvdst[4];
    	CvMat* translate = cvCreateMat( 3, 3, CV_32FC1 );
    	cvSetZero( translate );
    	for (int i = 0; i < 4; i++ ) {
    		cvsrc[i].x = src[i].x;
    		cvsrc[i].y = src[i].y;
    		cvdst[i].x = dst[i].x;
    		cvdst[i].y = dst[i].y;
    	}
    	cvWarpPerspectiveQMatrix( cvsrc, cvdst, translate );  // calculate homography
    	cvWarpPerspective( mom.getCvImage(), cvImage, translate);
        flagImageChanged();
    	cvReleaseMat( &translate );

    } else {
        ofLog(OF_LOG_ERROR, "in warpIntoMe: mom image type has to match");
    }	
}




// Other Image Operations

//--------------------------------------------------------------------------------
int ofxCvImage::countNonZeroInRegion( int x, int y, int w, int h ) {
    //TODO: test this method
    
	if (w == 0 || h == 0) return 0;
    int count = 0;
    
    // intersect the global ROI with the region to check
    ofRectangle iRoi = getIntersectionROI( getROI(), ofRectangle(x,y,w,h) );
    
    pushROI();
    setROI(iRoi);

	count = cvCountNonZero( cvImage );
    
    popROI();
        
	return count;
}




