/********************************************************************************
*         CS 791E Programming Assignment # 2 (2.1)
*              Canny Algorithm Application
*
*  
*  Name:   snake.c
*  Usage:  1). make
*          2). snake <input-image-file-name> <output-image-FILEBASE-name>
*                    <snake-window-size>   <gradient-flag>
*  Autor:  Beifang Yi
*  Date:   3/16/03
*
*
********************************************************************************/

#ifdef _CH_
#pragma package <opencv>
#endif

#ifndef _EiC
#include "cv.h"
#include "highgui.h"
#endif

#include <stdlib.h>
#include <string.h>
#include <iostream.h>
#include <fstream.h>
#include <unistd.h>

//parameters for snake
#define CN 100
#define SNAKE_LINE_COLOR 128

int numPoints = 0;
int origNumPoints = 0;
int curPointNum = -1;
CvPoint sPoints[CN];
CvPoint origSPoints[CN];
float alpha[CN];
float beta[CN];
float gammaa[CN];
int alphaX100 = 80;
int betaX100 = 80;
int gammaX100 = 60;
char baseFileName[20];

static int animateImageCount = 0;

//for calculating external energy: 0---use image's intensity;
//                                 1/other---pixel's gradient.
int gradientFlag = 1;

//the neighborhood size for snaking
int maskSize = 5;
CvSize sWin = {maskSize, maskSize};

//for recording the VERY DETAILED SNAKING PROCESS---
bool isDetailedSnaking = false;

//callback for snaking when parameters changed
CvTermCriteria criteria = {CV_TERMCRIT_ITER|CV_TERMCRIT_EPS, 5, 1 };

//for get initial snake points
bool notGetPoint = true;
void getPointsViaMouse( int event, int x, int y, int flags);

//image structures
IplImage *sourceI = 0;
IplImage *tempI = 0;
IplImage *destI = 0;
char destIF[80];

//a callback function for canny edge detection
void snake_image(int h);

void origSPointsToSPoints(void);
void sPointsToOrigSPoints(void);


int main( int argc, char** argv )
{
  int i;

  if (argc < 5 ) {
    cout << " To run the program, please type: "<<endl;
    cout << " ./snake <input-image-file-name> <output-image-FILEBASE-name>"<<endl;
    cout << "                <snake-window-size>  <gradient-flag>"<<endl;
    exit(0);
  }
    
  // check and load source image
  if( (sourceI = cvLoadImage(argv[1], 0)) == 0 ) {
    cout << " Can not load image. " << endl;
    return -1;
  }

  //The instruction for using this "snaking" program
  cout<<endl;
  cout<<"-----------Welcome to Beifang's 'Snaking' program!!--------"<<endl;
  cout<<"This program interacts with you via key and mouse and terminal."<<endl;
  cout<<"----------------------------------------------------------"<<endl;
  cout<<"       FIRST: CLICK ON THE IMAGE/INTERFACE and then....             "<<endl;
  cout<<"type 'P' or 'p'-----to click the snake INITIAL points."<<endl;
  cout<<"type 'Q' or 'q'-----to be OUT of getting initial points."<<endl;
  cout<<"type 'W' or 'w'-----to save into file those initial points."<<endl;
  cout<<"type 'R' or 'r'-----to read from a file the initial points."<<endl;
  cout<<"type 'S' or 's'-----to save the image with the snake trace."<<endl;
  cout<<"type 'A' or 'a'-----to save the image for use of animated GIFs."<<endl;
  cout<<"type 'V' or 'v'-----to go to A DETAILED Snaking Progress"<<endl;
  cout<<"type 'U' or 'u'-----to be out of the above 'V'/'v' Process."<<endl;
  cout<<"type 'D' or 'd'-----to recording the above 'V'/'v' Process."<<endl;
  cout<<"type  SPACEBAR -----to quit this program.  "<<endl;
  cout<<endl<<"---Click/Move alpha/beta/gamma sliderbar to do snaking----"<<endl<<endl;
  cout<<"-----------------------------------------------------------"<<endl;
  cout<<"   ALWAYS MAKE the image/interface the current environment."<<endl;
  cout<<"Images saved in file format: "<<endl;
  cout<<"            s_alphaX100_betaX100_gammaX100_maskSize.jpg"<<endl;
  cout<<"            a_nnn.jpg"<<endl;
  cout<<"            d_nnn.jpg"<<endl<<endl;
  

  //get the output base-file-name
  strcpy(baseFileName, argv[2]);

  //get mask size
  sWin.width = sWin.height = atoi(argv[3]);
  maskSize = sWin.width;

  //get external energy function flag
  gradientFlag = atoi(argv[4]);

  //get specifics about the input image
  double minVal, maxVal;
  CvPoint *minLoc, *maxLoc;
  minLoc = new CvPoint; maxLoc = new CvPoint;
  cvMinMaxLoc( sourceI, &minVal, &maxVal, minLoc, maxLoc);

  // Create the temporary and destination images
  destI = cvCloneImage( sourceI );
  tempI = cvCloneImage( sourceI );

  // Create windows for displaying  the effect
  cvNamedWindow("Source", CV_WINDOW_AUTOSIZE);
  cvNamedWindow("SnakedImage", CV_WINDOW_AUTOSIZE);

  // Show the source image.
  cvShowImage("Source", sourceI);
  cvShowImage( "SnakedImage", destI );

  // Create toolbars: for low/high threshold adjustment
  cvCreateTrackbar( "alphaX100", "SnakedImage", &alphaX100, 
		    200, snake_image );
  cvCreateTrackbar( "betaX100", "SnakedImage", &betaX100, 
		    200, snake_image );
  cvCreateTrackbar( "gammaX100", "SnakedImage", &gammaX100, 
		    200, snake_image );

  //set mouse callback function to get initial points
  cvSetMouseCallback( "SnakedImage", getPointsViaMouse);

  //canny the image for the first time by using the default parameters
  // snake_image(0);

  // Wait for a key stroke, and save the processed (edge) images
  int key;  char tempC[10];
  key = cvWaitKey(0);
  while (key != 32) {
    //save the snaked image
    if (key == 83 || key == 115) {      //key == 'S' or 's': save image
      strcpy(destIF, "s_");
      strcat(destIF, argv[2]);
      sprintf(tempC, "_%d", alphaX100);
      strcat(destIF, tempC);
      sprintf(tempC, "_%d", betaX100);
      strcat(destIF, tempC);
      sprintf(tempC, "_%d", gammaX100);
      strcat(destIF, tempC); 
      sprintf(tempC, "_%d", maskSize);
      strcat(destIF, tempC);
      strcat(destIF, ".jpg");
      cvSaveImage(destIF, destI);
    }
    //get the initial snake points
    else if (key == 80 || key == 112) { // key = 'P'/'p': get initial points
      notGetPoint = false;
      cout<<"----Now click the initial point on the image "<<endl;
      cout<<"               (type 'Q' or 'q' when finished). "<<endl;
    } 
    //finished getting the initial points
    else if (key == 81 || key == 113) {  // key = 'Q' or 'q':
      cout<<"----Now finished getting the initial boundary"<<endl;
      cout<<"       Please click/move the slide bar to SNAKE. "<<endl;
      notGetPoint = true;
    }
    //keep those initial points into a file
    else if (key == 87 || key == 119) { // key = 'W'/'w': write the points
      notGetPoint = true;
      char tempFileName[80];
      cout<<endl<<"----Please give file name for keeping the initial points: ";
      cin>>tempFileName; cout<<endl;
      ofstream fout(tempFileName);
      fout<<numPoints<<endl;
      for (i = 0; i < numPoints; i++)
	fout<<sPoints[i].x<<endl<<sPoints[i].y<<endl;
      fout.close();
      cout<<"  Points stored in file. Now move/click slidebar(s) to SNAKE the image."<<endl;
      //show the initial boundary
      sPointsToOrigSPoints();
      cvReleaseImage(&destI);
      destI = cvCloneImage(sourceI);
      for (i = 0; i < numPoints - 1; i ++)
	cvLine( destI, sPoints[i], sPoints[i+1], SNAKE_LINE_COLOR);
      cvLine( destI, sPoints[numPoints - 1], sPoints[0], SNAKE_LINE_COLOR);
      // Show image. HighGUI use.
      cvShowImage( "SnakedImage", destI );
    } 
    //read from a file the initial points
    else if (key == 82 || key == 114) { // key = 'R'/'r': write the points
      char tempFileName[80];
      notGetPoint = true;
      cout<<endl<<"----Please give the snake-initial-point file name: ";
      cin>>tempFileName; cout<<endl<<endl;
      ifstream fin(tempFileName);
      fin>>numPoints;
      for (i = 0; i < numPoints; i++)
	fin>>sPoints[i].x>>sPoints[i].y;
      fin.close();
      sPointsToOrigSPoints();
      
      //copy the intial points and draw the initial image and boundary
      cvReleaseImage(&destI);
      destI = cvCloneImage(sourceI);
      for (i = 0; i < numPoints - 1; i ++)
	cvLine( destI, sPoints[i], sPoints[i+1], SNAKE_LINE_COLOR);
      cvLine( destI, sPoints[numPoints - 1], sPoints[0], SNAKE_LINE_COLOR);
      // Show image. HighGUI use.
      cvShowImage( "SnakedImage", destI );
    } 
    //save the snaked image for animated gifs
    else if (key == 65 || key == 97) {      //key == 'A' or 'a': save image
      static int animateFlag = 0;
      strcpy(destIF, "a_");
      strcat(destIF, argv[2]);
      sprintf(tempC, "_%03d", animateFlag);
      strcat(destIF, tempC);
      strcat(destIF, ".jpg");
      cvSaveImage(destIF, destI);
      animateFlag++;
    }
    //for recording the VERY PROCESS OF SNAKING---YES
    else if (key == 86 || key == 118) {    //key = 'V' or 'v'
      isDetailedSnaking = true;

      //record the initial boundary
      origSPointsToSPoints();
      cvReleaseImage(&destI);
      destI = cvCloneImage(sourceI);
      for (i = 0; i < numPoints - 1; i ++)
	cvLine( destI, sPoints[i], sPoints[i+1], SNAKE_LINE_COLOR);
      cvLine( destI, sPoints[numPoints - 1], sPoints[0], SNAKE_LINE_COLOR);
      cvShowImage( "SnakedImage", destI );
      strcpy(destIF, "d_");
      strcat(destIF, baseFileName);
      sprintf(tempC, "_%03d", animateImageCount);
      strcat(destIF, tempC);
      strcat(destIF, ".jpg");
      cvSaveImage(destIF, destI);
      animateImageCount++;
      
      snake_image(0);
    }
    //for recording the VERY PROCESS OF SNAKING---NO!!!!
    else if (key == 85 || key == 117) {    //key = 'U' or 'u'
      isDetailedSnaking = false;
    }
    
    key = cvWaitKey(0);
  }

  //release the images
  cvReleaseImage(&tempI);
  cvReleaseImage(&destI);
  cvDestroyWindow("Source");
  cvDestroyWindow("SnakedImage");

  return 0;
}

// CALLBACK: when either the threshold changes,
//           this function will be called.
//           FOR Canny edge detection based on the changed parameters
void snake_image(int h)
{
  int i, j;
  int key;  char tempC[10];
  
  //check initial points
  if (numPoints == 0) {
    cout<<endl<<" No Initial Snake Points. ! "<<endl;
    cout<<" Please type 'R' or 'r' to read in the points"<<endl;
    return;
  }
  
  //get new alpha, beta, and gamma
  alpha[0] = ((double) alphaX100 ) / 100.0;
  beta[0] = ((double) betaX100 ) / 100.0;
  gammaa[0] = ((double) gammaX100 ) / 100.0;

  //copy the intial points and draw the initial image and boundary
  origSPointsToSPoints();
  cvReleaseImage(&destI);
  destI = cvCloneImage(sourceI);
  for (i = 0; i < numPoints - 1; i ++)
    cvLine( destI, sPoints[i], sPoints[i+1], SNAKE_LINE_COLOR);
  cvLine( destI, sPoints[numPoints - 1], sPoints[0], SNAKE_LINE_COLOR);
  // Show image. HighGUI use.
  cvShowImage( "SnakedImage", destI );
    
  for (j = 0; j < 220; j++) {
    //copy the source image
    cvReleaseImage(&destI);
    destI = cvCloneImage(sourceI);
    cvSnakeImage( tempI, sPoints, numPoints, &alpha[0],
		  &beta[0], &gammaa[0], CV_VALUE, sWin, criteria, gradientFlag);
    for (i = 0; i < numPoints - 1; i ++)
      cvLine( destI, sPoints[i], sPoints[i+1], SNAKE_LINE_COLOR);
    cvLine( destI, sPoints[numPoints - 1], sPoints[0], SNAKE_LINE_COLOR);
    
    // Show image. HighGUI use.
    cvShowImage( "SnakedImage", destI );
    //sleep(1);
    
    //xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
    // !!!!!!!!---Following are only for RECORDING 
    //------------VERY SPECIFIC (SLOW) PROCESS 
    //------------OF THE "SNAKING"----!!!!!!!!
    // Wait for a key stroke, and save the processed (edge) images
    if (isDetailedSnaking) {
      key = cvWaitKey(0);
      if (key == 68 || key == 100) {      //key == 'D' or 'd': save image
	strcpy(destIF, "d_");
	strcat(destIF, baseFileName);
	sprintf(tempC, "_%03d", animateImageCount);
	strcat(destIF, tempC);
	strcat(destIF, ".jpg");
	cvSaveImage(destIF, destI);
	animateImageCount++;
      }
      else if (key == 85 || key == 117) {    //key = 'U' or 'u'
	isDetailedSnaking = false;
      }
    }
    //xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

  }
}


void getPointsViaMouse( int event, int x, int y, int flags )
{
  int i;

  if (notGetPoint) return;

  switch( event ) {
  case CV_EVENT_LBUTTONDOWN:
    curPointNum++;
    numPoints++;
    sPoints[curPointNum].x = x;
    sPoints[curPointNum].y = y;
    sPointsToOrigSPoints();
    //cout<<"---got one more point: "<<curPointNum<<"  at (x-y)  "<<x<<"  "<<y<<endl;
    //cout<<"---WHEN FINISHING INPUT POINTS type: 'W' or 'w' to store them.---"<<endl;

    //copy the intial points and draw the initial image and temp boundary
    sPointsToOrigSPoints();
    cvReleaseImage(&destI);
    destI = cvCloneImage(sourceI);
    for (i = 0; i < numPoints - 1; i ++)
      cvLine( destI, sPoints[i], sPoints[i+1], SNAKE_LINE_COLOR);
    cvLine( destI, sPoints[numPoints - 1], sPoints[0], SNAKE_LINE_COLOR);
    // Show image. HighGUI use.
    cvShowImage( "SnakedImage", destI );
    break;
  }
}

void sPointsToOrigSPoints()
{
  origNumPoints = numPoints;
  for (int i = 0; i < numPoints; i++) {
    origSPoints[i].x = sPoints[i].x;
    origSPoints[i].y = sPoints[i].y;
  }

}

void origSPointsToSPoints()
{
  numPoints = origNumPoints;
  for (int i = 0; i < numPoints; i++) {
    sPoints[i].x = origSPoints[i].x;
    sPoints[i].y = origSPoints[i].y;
  }

}

