#include "image_classes.h"

#include <math.h>



// Function to compute the hough transform. Result 
// is stored in the accumulator[][][] array. This 
// array is not destroyed until the destructor is 
// called

void HoughCircle::ComputeTransform()
{
	int r, a, b, i;
	int x, y;
	unsigned char *edge_image;
	EdgeDetector ef;

	// Allocate a 3D array for the accumulator
	accumulator = (int ***) malloc(max_r * sizeof(int **));
	for (r = 0; r < max_r; r++)
	{
		accumulator[r] = (int **) malloc(max_a * sizeof(int *));
		for (a = 0; a < max_a; a++)
			accumulator[r][a] = (int *)calloc(max_b, sizeof(int));
	}


	// Do edge detection using 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);


	// Find edge pixels and draw cones in the hough space 
	// corresponding to the edge pixels
	for (i = 0; i < numpixels; i++)
	{
		if (edge_image[i] > 0)	// This is an edge pixel
		{
			// populate the accumulator array
			x = i%width;
			y = i/width;

			for (a = 0; a < max_a; a++)
				for (b = 0; b < max_b; b++)
				{
					r = (int) sqrt((x-a)*(x-a) + (y-b)*(y-b));
					if ((r > 0) && (r < max_r))
						++accumulator[r][a][b];
				}
		}
	}

}



// This function returns a slice of the hough transform.

unsigned char *HoughCircle::GetHoughImage(int slice, int *w, int *h)
{
	unsigned char *out_image;
	int a, b;

	if (accumulator == NULL)
		ComputeTransform();

	out_image = (unsigned char *) malloc(numpixels);
	*w = max_a;
	*h = max_b;

	if ((slice > max_r) || (slice < 0))		// illegal value test
		slice = max_r;

	for (a = 0; a < max_a; a++)
		for (b = 0; b < max_b; b++)
			out_image[b*max_a + a] = accumulator[slice][a][b];

	return out_image;
}



// This function does the actual circle detection by 
// scanning the accumulator array for a local maxima

void HoughCircle::DetectCircles()
{
	int a, b, r;
	int da, db, dr;
	int maxima, max_value=0;

	if (accumulator == NULL)
		ComputeTransform();
	


	// Search hough space for the maximum value
	for (r = 3; r < 12; r++)
		for (a = 2; a < max_a-2; a++)
			for (b = 2; b < max_b-2; b++)
				if (accumulator[r][a][b] > max_value)
					max_value = accumulator[r][a][b];


	// Look for a local maxima in the accumulator array
	for (r = 4; r < 12; r++)
		for (a = 4; a < max_a-4; a++)
			for (b = 4; b < max_b-4; b++)
			{
				if (accumulator[r][a][b] > 0.6*max_value)  // value is among highest 40%
				{
					maxima = TRUE;

					// Check a 9x9x9 window (cube!)
					for (dr = r-4; dr < r+5; dr++)
						for (da = a-4; da < a+5; da++)
							for (db = b-4; db < b+5; db++)	
								if (accumulator[dr][da][db] > accumulator[r][a][b])
									maxima = FALSE;
				
					if (maxima)
					{
						// draw a 2 pixel thick circle
						DrawCircle(edge_image, a, b, r);
						DrawCircle(edge_image, a, b, r + 1);
					}
				}
			}
}




// Function to draw a circle with the given center and radius 

void HoughCircle::DrawCircle(unsigned char *image, int xc, int yc, int r)
{
	int x = 0;
	int y = r;
	int d = 1 - r;

	while (y > x)
	{
		if (d<0)
			d += 2*x + 3;
		else
		{
			d += 2*(x-y) + 5;
			y--;
		}
		x++;
		
		// Check to see if the circle lies completely 
		// within the image.
		if (((xc-r) < 0) || ((xc+r) > width) ||
			((yc-r) < 0) || ((yc+r) > height))
			return;

		image[(y+yc)*width + (x+xc)] = 255;
		image[(x+yc)*width + (y+xc)] = 255;
		image[(-x+yc)*width + (y+xc)] = 255;
		image[(-y+yc)*width + (x+xc)] = 255;
		image[(-y+yc)*width + (-x+xc)] = 255;
		image[(-x+yc)*width + (-y+xc)] = 255;
		image[(x+yc)*width + (-y+xc)] = 255;
		image[(y+yc)*width + (-x+xc)] = 255;
	}
}



// Function sets the image and its dimensions

void HoughCircle::SetImage(unsigned char *image, int w, int h)
{
	width = w;
	height = h;
	numpixels = width * height;
	max_r = width/2;
	max_a = width;
	max_b = 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 
// containing just the detected circles

unsigned char *HoughCircle::GetEdgeImage()
{
	unsigned char *out_image;

	out_image = (unsigned char *)malloc(numpixels);
	memcpy(out_image, edge_image, numpixels);

	return out_image;
}




// This function superimposes the detected circles 
// onto the original image

unsigned char *HoughCircle::GetSuperimposedImage()
{
	int i;
	unsigned char *out_image;

	out_image = (unsigned char *) malloc(numpixels);
	memcpy(out_image, in_image, numpixels);

	for (i = 0; i < numpixels; i++)
		if (edge_image[i] > 0)
			out_image[i] = 255;

	return out_image;
}



// The constructor;

HoughCircle::HoughCircle()
{
	in_image = NULL;
	edge_image = NULL;
	accumulator = NULL;
	edge_threshold = 25;
}



// The destructor. Frees up the memory allocated for 
// the accumulator array.

HoughCircle::~HoughCircle()
{
	int r, a;
	
	if (in_image != NULL)
		free (in_image);

	if (edge_image != NULL)
		free (edge_image);

	if (accumulator != NULL)
	{
		for (r = 0; r < max_r; r++)
		{
			for (a = 0; a < max_a; a++)
				free (accumulator[r][a]);

			free(accumulator[r]);
		}
	}
}