#include "image_classes.h"
#include <math.h>



// This function computes the hough transform and populates 
// the accumulator array

void HoughLine::ComputeTransform()
{
	int max_radius;
	int t, r, x, y, i;
	unsigned char *edge_image;	
	EdgeDetector ef;

	// max_radius is set to the diagonal length of the image
	// and max_theta is set to 360 degrees
	max_radius = (int) sqrt(width*width + height*height);

	h_width = 360;
	h_height = (int)max_radius;

	// Allocate memory for a 2D accumulator array
	accumulator = (unsigned char **)malloc(h_height * sizeof(unsigned char *));
	for (i = 0; i < h_height; i++) 
		accumulator[i] = (unsigned char *) calloc(h_width, sizeof(unsigned char));

	
	// Do edge detection using the Sobel operator
	// Calls the EdgeDetector class
	ef.SetImage(in_image, width, height);
	if (use_direction)
		edge_image = ef.FindEdgesUsingGradientDirection(edge_threshold, edge_direction);
	else
		edge_image = ef.FindEdgesUsingGradient(edge_threshold);


	// Loop through all pixels and for each edge pixel,
	// plot the corresponding curve in the Hough space.
	for (i = 0; i < numpixels; i++)
	{
		if (edge_image[i] > 0)	// This is an edge pixel
		{
			x = i%width;
			y = i/width;

			// Draw the curve in the Hough space
			for (t = 0; t < 360; t++)
			{
				r = (int)((x - width/2)*COS[t] + (y - height/2)*SIN[t]);
				if ((r > -max_radius/2) && (r < max_radius/2) && (r != 0))
					++accumulator[r+max_radius/2][t];
			}
		}
	}
	free(edge_image); // we do not need the edge image any more
}



// This function detects lines in a given image
// by looking for maximas in the accumulator array

void HoughLine::DetectLines()
{
	int max_accum = 0;
	int r, t, dr, dt;
	int maxima, max_value = 0;


	if (accumulator == NULL)
		ComputeTransform();



	// Find the maximum accumulator value. We use this 
	// later to consider only the top 30% of the values 
	// when looking for local maximas

	for (r = 0; r < h_height; r++)
		for (t = 0; t < h_width; t++)
			if (accumulator[r][t] > max_value)
				max_value = accumulator[r][t];



	// Scan the accumulator array for local maximas
	// If one is found, draw the corresponding line in the 
	// original image

	for (r = 4; r < h_height - 5; r++)
		for (t = 4; t < h_width - 5; t++)
		{
			if (accumulator[r][t] > 0.7 * max_value)
			{
				maxima = TRUE;

				// Check a 9x9 window
				for (dr = r-4; dr < r+5; dr++)
					for (dt = t-5; dt < t+5; dt++)
						if (accumulator[dr][dt] > accumulator[r][t])
							maxima = FALSE;


				if (maxima == TRUE)
				{
					DrawLine((float)(r - h_height/2), (float)((t-180)*pi/180.0));
				}
			}
		}
}



//////////////////////////////////////////////////////////
// This function draws a line in an image for a			//
// given theta and radius. Modified to accomodate my	//
// image conventions									//
//														//
// Author: John Gauch									//
//         University of Kansas							//
//////////////////////////////////////////////////////////
void HoughLine::DrawLine(float radius, float theta)
{
	float M, B;
	int x, y;
	int start, end;

	// Handle lines with X changing faster than y */
	if (((theta >= pi/4) && (theta <= 3*pi/4)) ||
		((-theta >= pi/4) && (-theta <= 3*pi/4)))
	{
		M = (float) cos(theta) / (float) sin(theta);
		B = radius / (float) sin(theta) + (width/2)*M + height/2;
		start = end = -1;
		for (x = 0; x < width; x++)
		{
			y = (int) (0.5 + B - x * M);
			if ((y >= 0) && (y < height))
				if (start < 0)
					start = x;
				else
				{
					end = x;
					for (x = start; x < end; x++)
					{
						y = (int) (0.5 + B - x * M);
						edge_image[y*width + x] = 255;
					}
					start = end = -1;
				}		
		}
	}
	else
	{
		M = (float) sin(theta) / (float) cos(theta);
		B = radius / (float) cos(theta) + (height/2) * M + width/2;
		start = end = -1;

		for (y = 0; y < height; y++)
		{
			x = (int) (0.5 + B - y * M);
			if ((x >= 0) && (x < width))
				if (start < 0)
					start = y;
				else
				{
					end = y;
					for (y = start; y < end; y++)
					{
						x = (int) (0.5 + B - y * M);
						edge_image[y*width + x] = 255;
					}
					start = end = -1;
				}
		}
	}
}



// Set the image and its dimensions

void HoughLine::SetImage(unsigned char *image, int w, int h)
{
	width = w;
	height = h;
	numpixels = width * height;
	in_image = (unsigned char *) malloc(numpixels);
	memcpy(in_image, image, numpixels);
	edge_image = (unsigned char *) calloc(numpixels, 1);
}




// This function returns the binary edge image

unsigned char *HoughLine::GetEdgeImage()
{
	unsigned char *out_image;

	out_image = (unsigned char *)malloc(numpixels);
	memcpy(out_image, edge_image, numpixels);

	return out_image;
}




// This function returns edge lines superimposed on
// the original image

unsigned char *HoughLine::GetSuperimposedImage()
{
	int i;
	unsigned char *out_image;

	out_image = (unsigned char *)malloc(numpixels);
	memcpy(out_image, in_image, numpixels);

	// Mark all edge pixels
	for (i = 0; i < numpixels; i++)
		if (edge_image[i] > 0)
			out_image[i] = 255;

	return out_image;
}




// This function converts the 2D accumulator array 
// to a 1D image array so that it can be displayed on
// the screen

unsigned char * HoughLine::GetHoughImage(int *hough_width, int *hough_height)
{
	unsigned char *hough_image;
	int row, col;

	if (accumulator == NULL)
		ComputeTransform();

	// allocate memory for the image
	hough_image = (unsigned char *)malloc(h_width * h_height);
	*hough_width = h_width;
	*hough_height = h_height;

	// transfer values from the accumulator to the pixel array
	for (row = 0; row < h_height; row++)
		for (col = 0; col < h_width; col++)
			hough_image[row*h_width + col] = accumulator[row][col];

	return hough_image;
}



// The constructor. Initializes the SIN and COS tables
HoughLine::HoughLine()
{
	double theta;
	
	pi = 3.141592654;
	in_image = NULL;
	edge_image = NULL;
	accumulator = NULL;
	edge_threshold = 12;
	edge_direction = 10;
	use_direction = 0;
	
	// Allocate memory for SIN and COS lookup tables
	SIN = (float *) malloc(360 * sizeof(float));
	COS = (float *) malloc(360 * sizeof(float));


	// Fill in the SIN and COS lookup tables
	for (int t = 0; t < 360; t++)
	{
		theta = (t - 180) * pi / 180;
		SIN[t] = (float) sin(theta);
		COS[t] = (float) cos(theta);
	}
}


// The destructor. Frees all allocated memory
HoughLine::~HoughLine()
{
	int row;

	if (in_image != NULL)
		free (in_image);

	if (edge_image != NULL)
		free (edge_image);

	if (accumulator != NULL)
		for (row = 0; row < h_height; row++)
			free (accumulator[row]);

	free(accumulator);
	free (SIN);
	free (COS);
}