// CContorno.cpp. Implementación de la clase CContorno

#include "stdafx.h"
#include <stdlib.h>
#include "resource.h"
#include "CContorno.h"
#include "windowsx.h"
#include <math.h>
#include "funciones.h"

// ** Variables globales ** //
CPoint inic; // Representa el inicio de un camino que puede llegar a ser contorno.
bool exito;  // Permite acabar la recursividad al poner exito = true.

CContorno::CContorno()
{
	dimensx = 0;
	dimensy = 0;	
	num_puntos_forma = 0;
	min_contorno  = 0;
	contorno = NULL;
}

CContorno::CContorno(unsigned char *s, int dimx, int dimy, int numv)
{
	int i, j, diametro;
	
	num_vecinos = numv;
	dimensx = dimx;
	dimensy = dimy;
	contorno = NULL;

	// -- Cálculo del la longitud mínima que debe tener el contorno de esta imagen --
	// Calculo el área del objeto
	for (j=0;j<dimensy;j++)
	{
		for (i=0;i<dimensx;i++)
		{
			if (ExistePunto(s,i,j))
				num_puntos_forma++;
		}
	}

	// En función del número de puntos del objeto calculamos cuál debería ser
	// la longitud mínima del contorno. Esto nos sirve de cota, puesto que si
	// el contorno encontrado no tiene al menos esta longitud, tendremos la
	// seguridad de que es un contorno inválido.
	if (num_puntos_forma < 5)
		min_contorno = 1;
	else
	{
		// Calculo el diámetro de un cuadrado girado 45 grados con ese nº de ptos
		diametro = (int) floor(sqrt(2.0 * (double) num_puntos_forma));
		// Calculo su perímetro y establezco dicho perímetro como cota inferior
		min_contorno = 4 * ((diametro - 1) / 2);
	}		
}

CContorno::~CContorno()
{	
	dimensx = 0;
	dimensy = 0;
	num_puntos_forma = 0;
	min_contorno = 0;
}

int CContorno::ObtenerContornos(unsigned char *s)
{
	CPoint inicio;
	CCamino camino;
	
	inicio = CPoint(-1,-1);
	
	do
	{
		// Si entramos aqui es porque hemos obtenido un contorno no válido
		// y hemos de eliminar el punto inicial para no tomarlo otra vez.
		if (inicio != CPoint(-1,-1))
			s[(inicio.y * dimensx) + inicio.x] = 0;

		// Destruimos el camino que llevábamos porque no era bueno
		camino.CCamino::~CCamino();
			
		// Obtenemos el punto inicial del futuro (posible) contorno
		inicio = CalcularInicio(s);
		inic = inicio;
		
		if (num_puntos_forma != 0)
		{
			// Creamos un nuevo camino con punto_inicial = 'inicio'
			camino.CCamino::CCamino(num_puntos_forma);
			camino.AnadirPunto(inicio);

			exito = false;
			// Llamamos a la función recursiva que calcula el contorno a partir de
			// un punto de inicio. El parámetro tiporecta = 1 por ser el inicio.
			CalcularContorno(s,inicio,camino,1);
		}

	}while ((contorno.longitud < min_contorno) && (inicio != CPoint(-1,-1)));

	// Quitamos el punto final del contorno que es igual al primero
	contorno.longitud--;

	if (contorno.longitud <= 0)
		return 1; // Contorno vacio
	else
		return 0; // No errores
}

CPoint CContorno::CalcularInicio(unsigned char *s)
{
	CPoint pto_nulo;
	int i,j;

	pto_nulo = (-1,-1);

	// Buscamos el primer punto del objeto (blanco) que haya en la imagen.
	// De izquierda a derecha y de abajo a arriba.
	for (j=0;j<dimensy;j++)
	{
		for (i=0;i<dimensx;i++)
		{
			if (ExistePunto(s,i,j))
				return(CPoint(i,j));
		}
	}
	
	return pto_nulo;
}

void CContorno::CalcularContorno(unsigned char *s, CPoint actual, CCamino camino, int tiporecta)
{
	CPoint vecino;
	int dir;

	dir = 0;

	do
	{
		dir++;

		// Calculamos el siguiente vecino en función de las direcciones ya probadas
		// y del punto actual del contorno en el que nos encontramos
		SiguienteVecino(s,actual,&camino,&vecino,&dir,tiporecta);

		if (EsValido(s,vecino,&camino))
		{
			camino.AnadirPunto(vecino);
			if (!FinContorno(&camino))
			{
				tiporecta = TipoRecta(actual,vecino);
				CalcularContorno(s,vecino,camino,tiporecta);
				if (!exito)
					camino.BorrarPuntoPos(0);					
			}
			else // Hemos encontrado un posible contorno
			{
				if (camino.longitud >= min_contorno)
				{
					contorno.CCamino::CCamino(camino.longitud);
					contorno = camino;
					exito = true;
				}
				else // Contorno es demasiado pequeño
				{
					// Eliminamos todos los puntos del contorno (para no usarlos otra vez)
					EliminarPuntos(s,&camino);
					// Aplicamos el filtro anti-ruido para evitar que el contorno borrado falle
					AntiRuido(s,&camino);
					// Salimos para hallar un nuevo punto de inicio y comenzar el proceso
					exito = true;
				}
			}
		}
	}while ((dir <= 8) && (!exito));
}

void CContorno::SiguienteVecino(unsigned char *s, CPoint actual, CCamino *camino, CPoint *vecino, int *dir, int tiporecta)
{
	CPoint punto;
	bool salir;

	salir = false;

	while ((*dir <= 8) && (!salir))
	{
		// Obtenemos el siguiente vecino a probar como sucesor en el contorno
		punto = CalcularVecino(*dir,actual,camino,tiporecta);
		
		// Comprobamos si es un vecino posible:
		// * No se ha salido de los límites de la imagen.
		// * Es un punto del objeto (blanco).
		// * No esté rodeado por sus 4 vecinos (arriba, abajo, izquierda y derecha) por puntos blancos
		// * Que tengan 'actual' y 'punto' un vecino común (con la relación 8-vecinos).
		if ((punto.x >= 0) && (punto.x < dimensx) && (punto.y >= 0) && (punto.y < dimensy) &&
			(ExistePunto(s,punto.x,punto.y)) &&
		    (Salida4Vecinos(s,punto)) &&
			(SalidaComun(s,actual,punto)))
		{
			*vecino = punto;
			salir = true;	
		}
		else
			(*dir)++;
	}
	if (*dir > 8)
		*vecino = CPoint(-1,-1);
}

CPoint CContorno::CalcularVecino(int dir, CPoint actual, CCamino *camino, int tiporecta)
{
	CPoint vecino;
	int dir_ini, direc;

//////////////////////////////////
// ** Códigos de direcciones ** //
//		     4  3  2            //
//   		  \ | /             //
//	         5- X -1            //
//		      / | \             //
//           6  7  8            //
//////////////////////////////////

	// Si es el primer punto del contorno comenzamos con la dirección 7, puesto
	// que buscamos puntos de izquierda a derecha y de abajo a arriba
	if (tiporecta == 1)
		dir_ini = 7;
	else
		dir_ini = (2 * (tiporecta - 2)) + 1;

	direc = ((dir_ini + dir - 2) % 8) + 1;

	switch(direc)
	{
	case 1:
		vecino.x = actual.x + 1;
		vecino.y = actual.y;
		break;
	case 2:
		vecino.x = actual.x + 1;
		vecino.y = actual.y + 1;
		break;
	case 3:
		vecino.x = actual.x;
		vecino.y = actual.y + 1;
		break;
	case 4:
		vecino.x = actual.x - 1;
		vecino.y = actual.y + 1;
		break;
	case 5:
		vecino.x = actual.x - 1;
		vecino.y = actual.y;
		break;
	case 6:
		vecino.x = actual.x - 1;
		vecino.y = actual.y - 1;
		break;
	case 7:
		vecino.x = actual.x;
		vecino.y = actual.y - 1;
		break;
	case 8:
		vecino.x = actual.x + 1;
		vecino.y = actual.y - 1;
		break;
	}

	return vecino;
}

bool CContorno::Salida4Vecinos(unsigned char *s, CPoint punto)
{
	CPoint salida;

	salida.x = punto.x + 1;
	salida.y = punto.y;
	if ((salida.x < 0) || (salida.x >= dimensx) || (salida.y < 0) || (salida.y >= dimensy))
		return true;
	else if (!ExistePunto(s,salida.x,salida.y))
		return true;

	salida.x = punto.x - 1;
	salida.y = punto.y;
	if ((salida.x < 0) || (salida.x >= dimensx) || (salida.y < 0) || (salida.y >= dimensy))
	    return true;
	else if	(!ExistePunto(s,salida.x,salida.y))
		return true;

	salida.x = punto.x;
	salida.y = punto.y + 1;
	if ((salida.x < 0) || (salida.x >= dimensx) || (salida.y < 0) || (salida.y >= dimensy))
		return true;
	else if (!ExistePunto(s,salida.x,salida.y))
		return true;

	salida.x = punto.x;
	salida.y = punto.y - 1;
	if ((salida.x < 0) || (salida.x >= dimensx) || (salida.y < 0) || (salida.y >= dimensy))
	    return true;
	else if (!ExistePunto(s,salida.x,salida.y))
		return true;

	return false;
}

bool CContorno::SalidaComun(unsigned char *s, CPoint primero, CPoint segundo)
{
	CPoint hueco;

	// Trato de encontrar un punto de los 8 vecinos posibles de 'primero' que esté
	// también a distancia 1 de 'segundo'.

	hueco.x = primero.x;
	hueco.y = primero.y - 1;
	if (Distancia(hueco,segundo) == 1)
	{
		if ((hueco.x < 0) || (hueco.x >= dimensx) || (hueco.y < 0) || (hueco.y >= dimensy))
			return true;
		else if	(!ExistePunto(s,hueco.x,hueco.y))
			return true;
	}

	hueco.x = primero.x;
	hueco.y = primero.y + 1;
	if (Distancia(hueco,segundo) == 1)
	{
		if ((hueco.x < 0) || (hueco.x >= dimensx) || (hueco.y < 0) || (hueco.y >= dimensy))
			return true;
		else if (!ExistePunto(s,hueco.x,hueco.y))
			return true;
	}

	hueco.x = primero.x - 1;
	hueco.y = primero.y - 1;
	if (Distancia(hueco,segundo) == 1)
	{
		if ((hueco.x < 0) || (hueco.x >= dimensx) || (hueco.y < 0) || (hueco.y >= dimensy))
			return true;
		else if (!ExistePunto(s,hueco.x,hueco.y))
			return true;
	}

	hueco.x = primero.x - 1;
	hueco.y = primero.y;
	if (Distancia(hueco,segundo) == 1)
	{
		if ((hueco.x < 0) || (hueco.x >= dimensx) || (hueco.y < 0) || (hueco.y >= dimensy))
			return true;
		else if (!ExistePunto(s,hueco.x,hueco.y))
			return true;
	}

	hueco.x = primero.x - 1;
	hueco.y = primero.y + 1;
	if (Distancia(hueco,segundo) == 1)
	{
		if ((hueco.x < 0) || (hueco.x >= dimensx) || (hueco.y < 0) || (hueco.y >= dimensy))
			return true;
		else if (!ExistePunto(s,hueco.x,hueco.y))
			return true;
	}

	hueco.x = primero.x + 1;
	hueco.y = primero.y - 1;
	if (Distancia(hueco,segundo) == 1)
	{
		if ((hueco.x < 0) || (hueco.x >= dimensx) || (hueco.y < 0) || (hueco.y >= dimensy))
			return true;
		else if (!ExistePunto(s,hueco.x,hueco.y))
			return true;
	}

	hueco.x = primero.x + 1;
	hueco.y = primero.y;
	if (Distancia(hueco,segundo) == 1)
	{
		if ((hueco.x < 0) || (hueco.x >= dimensx) || (hueco.y < 0) || (hueco.y >= dimensy))
			return true;
		else if (!ExistePunto(s,hueco.x,hueco.y))
			return true;
	}

	hueco.x = primero.x + 1;
	hueco.y = primero.y + 1;
	if (Distancia(hueco,segundo) == 1)
	{
		if ((hueco.x < 0) || (hueco.x >= dimensx) || (hueco.y < 0) || (hueco.y >= dimensy))
			return true;
		else if (!ExistePunto(s,hueco.x,hueco.y))
			return true;
	}

	return false;
}

bool CContorno::EsValido(unsigned char *s, CPoint vecino, CCamino *camino)
{
	// Comprobamos los siguientes puntos:
	// * Si es el punto (-1,-1) -> No es válido (hemos probado todas las direcciones)
	// * Si el punto es el inicio del contorno actual y su long. es menor que 3 -> No es válido
	// * Si el punto ya se hallaba en el camino -> No es válido (si lo fuera, bucle infinito)
	if ((vecino.x == -1) && (vecino.y == -1))
		return false;
	if (vecino == inic) //&& (camino->longitud >= 3))
		return true;
	if (camino->EstaEnRama(vecino))
		return false;

	return true;
}

bool CContorno::FinContorno(CCamino *camino)
{
	if (camino->ptos[0] == camino->ptos[camino->longitud - 1])
		return true;

	return false;
}

bool CContorno::ExistePunto(unsigned char *s, int i, int j)
{
	if (s[j*dimensx+i] == 255)
		return true;

	return false;
}

void CContorno::EliminarPuntos(unsigned char *s, CCamino *camino)
{
	int i;

	for (i=0;i<camino->longitud - 1;i++)
		s[(camino->ptos[i].y * dimensx) + camino->ptos[i].x] = 0;
}

int CContorno::TipoRecta(CPoint actual,CPoint vecino)
{
	int res;
	
	//////////////////////////
	// ** Tipos de recta ** //
	//       2  2  1        //
	//        \ | /         //
	//       3- X -1        //
	//		  / | \         //
	//       3  4  4        //
	//////////////////////////

	if (((vecino.x == actual.x + 1) && (vecino.y == actual.y)) ||
		((vecino.x == actual.x + 1) && (vecino.y == actual.y + 1)))
		res = 1;
	else if (((vecino.x == actual.x) && (vecino.y == actual.y + 1)) ||
			 ((vecino.x == actual.x - 1) && (vecino.y == actual.y + 1)))
		res = 2;
	else if (((vecino.x == actual.x - 1) && (vecino.y == actual.y)) ||
			 ((vecino.x == actual.x - 1) && (vecino.y == actual.y - 1)))
		res = 3;
	else if (((vecino.x == actual.x) && (vecino.y == actual.y - 1)) ||
			 ((vecino.x == actual.x + 1) && (vecino.y == actual.y - 1)))
		res = 4;

	return res;
}

int CContorno::Distancia(CPoint a, CPoint b)
{
	return (Maximo( abs(a.x - b.x) , abs(a.y - b.y) ));
}

void CContorno::AntiRuido(unsigned char *s, CCamino *camino)
{
	bool no_fin;
	int i, j, color_ar, color_ab, color_iz, color_de;
	int color_arde, color_ariz, color_abiz, color_abde;

	do
	{
		no_fin = false;
		for (j=0;j<dimensy;j++)
		{
			for (i=0;i<dimensx;i++)
			{
				if (s[j*dimensx+i] == 255)
				{
					if (NumVecinos(i,j,s) <= num_vecinos)
					{
						// Borra ese pixel
						s[j*dimensx+i] = 0;
						no_fin = true;
					}
					else
					{
						// Aumento de grosor las lineas delgadas para evitar
						// que no se pueda calcular el contorno
						color_de = s[(j*dimensx) + i + 1];
						color_iz = s[(j*dimensx) + i - 1];
						color_ar = s[((j+1)*dimensx) + i];
						color_ab = s[((j-1)*dimensx) + i];
						color_arde = s[((j+1)*dimensx) + i + 1];
						color_ariz = s[((j+1)*dimensx) + i - 1];
						color_abiz = s[((j-1)*dimensx) + i - 1];
						color_abde = s[((j-1)*dimensx) + i + 1];

						if ((color_iz == 255) && (color_de == 255) &&
							(color_ar == 0) && (color_ab == 0))
							s[((j+1)*dimensx) + i] = 255;

						if ((color_ar == 255) && (color_ab == 255) &&
							(color_iz == 0) && (color_de == 0))
							s[(j*dimensx) + i + 1] = 255;

						if ((color_arde == 255) && (color_ar == 0) &&
							(color_de == 0))
							s[((j+1)*dimensx) + i] = 255;

						if ((color_ariz == 255) && (color_ar == 0) &&
							(color_iz == 0))
							s[((j+1)*dimensx) + i] = 255;

						if ((color_abde == 255) && (color_ab == 0) &&
							(color_de == 0))
							s[((j-1)*dimensx) + i] = 255;
							
						if ((color_abiz == 255) && (color_ab == 0) &&
							(color_iz == 0))
							s[((j-1)*dimensx) + i] = 255;
					}
				}
			}	
		}
	}while (no_fin);
}

int CContorno::NumVecinos(int i, int j, unsigned char *s)
{
	int cont;

	cont = 0;

	if (s[(j*dimensx) + i + 1] == 255)
		cont++;
	if (s[((j+1)*dimensx) + i + 1] == 255)
		cont++;
	if (s[((j+1)*dimensx) + i] == 255)
		cont++;
	if (s[((j+1)*dimensx) + i - 1] == 255)
		cont++;
	if (s[(j*dimensx) + i - 1] == 255)
		cont++;
	if (s[((j-1)*dimensx) + i - 1] == 255)
		cont++;
	if (s[((j-1)*dimensx) + i] == 255)
		cont++;
	if (s[((j-1)*dimensx) + i + 1] == 255)
		cont++;

	return cont;
}
