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

#include "stdafx.h"
#include "resource.h"
#include "CImagen.h"
#include "math.h"
#include "funciones.h"

IMPLEMENT_SERIAL(CImagen, CObject, 0)

CImagen::CImagen()
{
	m_dwLongDib = 0L;
	m_nBitsColor = 0;
	m_lpDib = NULL;
	m_colores_paleta = 0;
}

CImagen::~CImagen()
{
	if(m_lpDib)
		GlobalFreePtr(m_lpDib);
}

int CImagen::GetBitsColor()
{
	return m_nBitsColor;
}

LPBITMAPINFO CImagen::GetlpBmI()
{
	return m_lpBmI;
}

DWORD CImagen::GetLong()
{
	return m_dwLongDib;
}

bool CImagen::CrearImagenMonocromo(int width, int height)
{
	ASSERT(m_dwLongDib == 0L); // Comprobamos que el DIB está vacio
	
	m_nBitsColor = 1;

	// Calculo la longitud inicial del fichero de la imagen
	m_dwLongDib = sizeof(BITMAPFILEHEADER) +
				  sizeof(BITMAPINFOHEADER) +
				  (sizeof(RGBQUAD) * 2); // 2 es el tamaño de la paleta
										 // puesto q la imagen es monocromo
	
	if (!AsignarMem()) return false;

	m_lpBmIH->biBitCount = 1;
	m_lpBmIH->biClrImportant = 0;
	m_lpBmIH->biClrUsed = 0;
	m_lpBmIH->biCompression = BI_RGB; // sin compresión
	m_lpBmIH->biHeight = height;
	m_lpBmIH->biWidth = width;
	m_lpBmIH->biPlanes = 1;
	m_lpBmIH->biSize = sizeof(BITMAPINFOHEADER);
	m_lpBmIH->biSizeImage = 0;
	m_lpBmIH->biXPelsPerMeter = 0;
	m_lpBmIH->biYPelsPerMeter = 0;

	// Añadimos a la paleta de colores el color negro
	m_lpBmI->bmiHeader = *m_lpBmIH;
	m_lpBmI->bmiColors[0].rgbBlue = 0;
	m_lpBmI->bmiColors[0].rgbGreen = 0;
	m_lpBmI->bmiColors[0].rgbRed = 0;
	m_lpBmI->bmiColors[0].rgbReserved = 0;

	// Añadimos a la paleta de colores el color blanco
	m_lpBmI->bmiColors[1].rgbBlue = 255;
	m_lpBmI->bmiColors[1].rgbGreen = 255;
	m_lpBmI->bmiColors[1].rgbRed = 255;
	m_lpBmI->bmiColors[1].rgbReserved = 0;

	// Calculo la longitud total de la imagen en disco
	m_dwLongDib += ((((m_lpBmIH->biWidth * m_lpBmIH->biBitCount)
				 + 31) & ~31) >> 3) * m_lpBmIH->biHeight;
	
	if (!AsignarMem(true)) return false;

	m_lpBits = (LPSTR) m_lpBmIH + 
			   sizeof(BITMAPINFOHEADER) +
			   (sizeof(RGBQUAD) * 2);   // 2 es el tamaño de la paleta
										// puesto q la imagen es monocromo

	m_lpBmFH->bfType = 0x4d42; // 'BM'
	m_lpBmFH->bfSize = m_dwLongDib;
	m_lpBmFH->bfReserved1 = 0;
	m_lpBmFH->bfReserved2 = 0;
	m_lpBmFH->bfOffBits = (char *) m_lpBits - m_lpDib;

	return true;
}

bool CImagen::LeerBmp(CFile *pFich)
{
	ASSERT(m_dwLongDib == 0L); // Comprobamos que el DIB está vacio

	// Calculo la longitud total del fichero
	m_dwLongDib = pFich->GetLength();

	// Asignar memoria para el DIB, incluida la cabecera
	if (!AsignarMem())
		return false;

	// Leemos la información del fichero. Si se produce algún error
	// al abrir el fichero, devolvemos false y lo gestionamos fuera
	DWORD dwBytesLeidos = pFich->Read(m_lpDib, m_dwLongDib);
	if (dwBytesLeidos != m_dwLongDib)
		return false;
	if (m_lpBmFH->bfType != 0x4d42) // 'BM'
		return false;
  
	// Dirección de comienzo del array de bits del mapa de bits
	m_lpBits = (LPSTR)m_lpBmFH + m_lpBmFH->bfOffBits;

	// El número de bits por píxel tiene que ser 1,4,8,16,24 ó 32
	ASSERT((m_lpBmIH->biBitCount ==  1) ||
           (m_lpBmIH->biBitCount ==  4) ||
           (m_lpBmIH->biBitCount ==  8) || 
           (m_lpBmIH->biBitCount == 16) || 
           (m_lpBmIH->biBitCount == 24) || 
           (m_lpBmIH->biBitCount == 32));
  
	m_nBitsColor = m_lpBmIH->biBitCount;

	// Si tiene paleta de colores, calculamos su tamaño. Si no, ponemos 0.
	// Sólo admitimos imágenes de 2, 16 ó 256 colores (1,4,8 bits/color respec.)
	if ((m_nBitsColor == 1) || (m_nBitsColor == 4) || (m_nBitsColor == 8))
	{
		if (m_lpBmIH->biClrUsed == 0) // Si biClrUsed = 0 usamos todos los colores
			m_colores_paleta = (int) pow(2.0,(double) m_nBitsColor);
		else // Si no, usamos solo los colores indicados por biClrUsed
			m_colores_paleta = m_lpBmIH->biClrUsed;
	}
	else
		m_colores_paleta = 0;

	return true;
}

bool CImagen::EscribirBmp(CFile *pFich)
{
	TRY
	{
		pFich->Write(m_lpDib, m_dwLongDib);
	}
	CATCH (CException, pExcepcion)
	{
		return false;
	}
	END_CATCH

	return true;
}

bool CImagen::VisualizarDib(CDC *pDC, CPoint origen, int scrx, int scry, int destx, int desty)
{
	if (!m_lpDib)
		return false; // No hay nada que visualizar

	// Visualizamos en la pantalla el trozo de la imagen indicado por
	// scrx y scry en la cuadrícula indicada en destx y desty 
	if (!SetDIBitsToDevice(
		    pDC->GetSafeHdc(),        // handle al DC
			origen.x, origen.y,       // origen del destino
			destx,					  // ancho destino
			desty, 					  // alto destino
			scrx, scry,               // origen de la fuente
			0,                        // línea 0
			(WORD)m_lpBmIH->biHeight, // número de líneas
			m_lpBits,                 // dirección del array de bits
			m_lpBmI,                  // dirección de BITMAPINFO
			DIB_RGB_COLORS))          // colores RGB
	{
		return false;
	}
	
	return true;
}

bool CImagen::AjustarDib(CDC *pDC, CPoint origen, CSize Tam)
{
	if (!m_lpDib)
		return false; // No hay nada que visualizar
	
	if (!StretchDIBits(
		    pDC->GetSafeHdc(),        // handle al DC
			origen.x, origen.y,       // origen del destino
			Tam.cx, Tam.cy,           // ancho y alto del destino
			0, 0,                     // origen de la fuente
			(WORD)m_lpBmIH->biWidth,  // ancho fuente
			(WORD)m_lpBmIH->biHeight, // alto fuente
			m_lpBits,                 // dirección del array de bits
			m_lpBmI,                  // dirección de BITMAPINFO
			DIB_RGB_COLORS, SRCCOPY)) // colores RGB
	{
		return false;
	}
	
	return true;
}

bool CImagen::GetBmpPixel(int i, int j, RGBQUAD *color, int *pos)
{
	int lugar, tmp, mask;
	
	switch(m_nBitsColor)
	{
	case 1: // Monocromo
		if ((m_lpBmIH->biWidth % 32) == 0)
			tmp = 0;
		else
			tmp = 32 - (m_lpBmIH->biWidth % 32);

		lugar = (j * (m_lpBmIH->biWidth + tmp)) + i;
		*pos = (BYTE) m_lpBits[lugar / 8];
		mask = (int) pow(2, 7 - (lugar % 8));
		
		if ((*pos & mask) == 0)
		{
			*pos = 0;
			*color = m_lpBmI->bmiColors[*pos];
		}
		else
		{
			*pos = 1;
			*color = m_lpBmI->bmiColors[*pos];
		}

		break;
	case 4: // 16 colores
		if ((m_lpBmIH->biWidth % 8) == 0)
			tmp = 0;
		else
			tmp = 8 - (m_lpBmIH->biWidth % 8);

		lugar = (j * (m_lpBmIH->biWidth + tmp)) + i;
		*pos = (BYTE) m_lpBits[lugar / 2];
		if (lugar % 2 == 0)
			*pos = *pos >> 4;
		else
			*pos = *pos & 15;

		*color = m_lpBmI->bmiColors[*pos];
		
		break;
	case 8: // 256 colores
		if ((m_lpBmIH->biWidth % 4) == 0)
			tmp = 0;
		else
			tmp = 4 - (m_lpBmIH->biWidth % 4);

		lugar = (j * (m_lpBmIH->biWidth + tmp)) + i;
		*pos = (BYTE) m_lpBits[lugar];
		*color = m_lpBmI->bmiColors[*pos];

		break;
	}

	return true;
}

bool CImagen::SetBmpPixel(int i, int j, RGBQUAD color)
{
	BYTE b;
	int lugar, mask, maskinv, tmp, valor_inferior, valor_superior, vsupcolor, vinfcolor;	

	switch(m_nBitsColor)
	{
	case 1: // Monocromo
		tmp = 32 - (m_lpBmIH->biWidth % 32);
		if (tmp == 32)
			tmp = 0;

		lugar = (j * (m_lpBmIH->biWidth + tmp)) + i;
		b = m_lpBits[lugar / 8];
		mask = (int) pow(2, 7 - (lugar % 8));
		maskinv = mask ^ 255;

		if (color.rgbBlue == 0)
			m_lpBits[lugar / 8] = m_lpBits[lugar / 8] & maskinv;
		else
			m_lpBits[lugar / 8] = m_lpBits[lugar / 8] | mask;
		
		break;
	case 4: // 16 colores
		tmp = 8 - (m_lpBmIH->biWidth % 8);
		if (tmp == 8)
			tmp = 0;
		
		lugar = (j * (m_lpBmIH->biWidth + tmp)) + i;
		b = m_lpBits[lugar / 2];

		if (lugar % 2 == 0) // es par
		{
			valor_inferior = b & 15;		 // 0000XXXX
			vsupcolor = color.rgbBlue & 240; // CCCC0000
			m_lpBits[lugar / 2] = vsupcolor | valor_inferior;
		}
		else				// es impar
		{
			valor_superior = b & 240;		 // XXXX0000
			vinfcolor = color.rgbBlue & 15;	 // 0000CCCC
			m_lpBits[lugar / 2] = valor_superior | vinfcolor;
		}

		break;
	case 8: // 256 colores (Cada color ocupa un byte entero)	
		if ((m_lpBmIH->biWidth % 4) == 0)
			tmp = 0;
		else
			tmp = 4 - (m_lpBmIH->biWidth % 4);

		lugar = (j * (m_lpBmIH->biWidth + tmp)) + i;
		m_lpBits[lugar] = color.rgbBlue;
		
		break;
	}
	
	return true;
}

bool CImagen::EsGamaGrises256()
{
	RGBQUAD color;
	int i, j, pos;

	if (m_nBitsColor != 8)
		return false; // No es una imagen de 256 colores
	
	for (j=0;j<m_lpBmIH->biHeight;j++)
	{
		for (i=0;i<m_lpBmIH->biWidth;i++)
		{			
			GetBmpPixel(i,j,&color,&pos);
			if ((color.rgbBlue != color.rgbGreen) ||
				(color.rgbGreen != color.rgbRed))
				return false; // No es una imagen escala de grises
		}
	}

	return true;
}

bool CImagen::AumentoContraste()
{
	RGBQUAD color;
	int i,j, maximo, minimo, pos;
	double a, b;

	// Inicializamos el histograma (todos los colores a 0)
	InicializarHistograma();

	// Calculo el histograma de la imagen
	CalcularHistograma();

	// Obtengo el color más pequeño (oscuro) que aparece en la imagen
	minimo = MinimoColorHistograma();
	
	// Obtengo el color más grande (claro) que aparece en la imagen
	maximo = MaximoColorHistograma();
	
	if (maximo == minimo)
		return true;

	// Calculo los parámetros a y b de la función f(x) = a + (b * x).
	b = 255.0 / (maximo - minimo);
	a = - (b * minimo);
	
	// Aplico el aumento de contraste a la imagen punto por punto
	for (j=0;j<m_lpBmIH->biHeight;j++)
	{
		for (i=0;i<m_lpBmIH->biWidth;i++)
		{			
			GetBmpPixel(i,j,&color,&pos);
			color.rgbBlue = AproximarColor(a + (b * color.rgbBlue));
			color.rgbGreen = color.rgbBlue;
			color.rgbRed = color.rgbBlue;
			SetBmpPixel(i,j,color);
		}
	}

	return true;
}

void CImagen::InicializarHistograma()
{
	int i;

	// Ponemos todos los valores del histograma a 0
	for (i=0;i<256;i++)
		histograma[i] = 0;
}

void CImagen::CalcularHistograma()
{
	RGBQUAD color;
	int i, j, pos;

	// Calculamos la frecuencia con la que aparecen los colores
	// de la imagen y los guardamos en 'histograma'
	for (j=0;j<m_lpBmIH->biHeight;j++)
	{
		for (i=0;i<m_lpBmIH->biWidth;i++)
		{			
			GetBmpPixel(i,j,&color,&pos);
			histograma[color.rgbBlue]++;
		}
	}
}

int CImagen::MinimoColorHistograma()
{
	bool salir;
	int i, minimo;

	salir = false;
	i = 0;
	while ((i < 256) && (!salir))
	{
		if (histograma[i] != 0)
		{
			minimo = i;
			salir = true;
		}
		else
			i++;
	}

	return minimo;
}

int CImagen::MaximoColorHistograma()
{
	bool salir;
	int i, maximo;

	salir = false;
	i = 255;
	while ((i >= 0) && (!salir))
	{
		if (histograma[i] != 0)
		{
			maximo = i;
			salir = true;
		}
		else
			i--;
	}

	return maximo;
}

bool CImagen::Umbralizar(int margen)
{
	RGBQUAD color, color_negro, color_blanco;
	int i, j, maximo, frecuencia_max, pos;

	// Inicializamos el histograma (todos los colores a 0)
	InicializarHistograma();

	// Calculo el histograma de la imagen contrastada
	CalcularHistograma();
	
	// Calculo el color más abundante del histograma
	ColorMasFrecuenteHistograma(&maximo, &frecuencia_max);
	
	if (frecuencia_max < 0.5 * m_lpBmIH->biHeight * m_lpBmIH->biWidth)
		return false;

	// Creo el color negro para el color más abundante de la imagen, que
	// suponemos que corresponde al fondo de la imagen
	color_negro.rgbBlue = 0;
	color_negro.rgbGreen = 0;
	color_negro.rgbRed = 0;
	color_negro.rgbReserved = 0;

	// Creo el color blanco para el resto de colores de la imagen
	color_blanco.rgbBlue = 255;
	color_blanco.rgbGreen = 255;
	color_blanco.rgbRed = 255;
	color_blanco.rgbReserved = 0;

	// Convierto la imagen a 2 colores, blanco (imagen) y negro (para el fondo)
	for (j=0;j<m_lpBmIH->biHeight;j++)
	{
		for (i=0;i<m_lpBmIH->biWidth;i++)
		{			
			GetBmpPixel(i,j,&color,&pos);
			
			if ((color.rgbBlue >= (maximo - margen)) && (color.rgbBlue <= (maximo + margen)))
				SetBmpPixel(i,j,color_negro);
			else
				SetBmpPixel(i,j,color_blanco);
		}
	}

	return true;
}

void CImagen::ColorMasFrecuenteHistograma(int *maximo, int *frecuencia)
{
	int i; 
	
	*maximo = 0;
	*frecuencia = 0;

	for (i=0;i<256;i++)
	{
		if (histograma[i] > *frecuencia)
		{
			*maximo = i;
			*frecuencia = histograma[i];
		}
	}
}

bool CImagen::FiltroAntiRuido(int num_vecinos)
{
	RGBQUAD color, color_negro, color_blanco;
	RGBQUAD color_ar, color_ab, color_iz, color_de, color_arde, color_ariz, color_abde, color_abiz;
	int i, j, pos;
	bool no_fin;
	
	// Establezco el color negro
	color_negro.rgbBlue = 0;
	color_negro.rgbGreen = 0;
	color_negro.rgbRed = 0;
	color_negro.rgbReserved = 0;

	// Establezco el color blanco
	color_blanco.rgbBlue = 255;
	color_blanco.rgbGreen = 255;
	color_blanco.rgbRed = 255;
	color_blanco.rgbReserved = 0;
	
	do
	{
		no_fin = false;
		for (j=0;j<m_lpBmIH->biHeight;j++)
		{
			for (i=0;i<m_lpBmIH->biWidth;i++)
			{
				GetBmpPixel(i,j,&color, &pos);
				if (color.rgbBlue == 255)
				{
					if (NumVecinos(i,j) <= num_vecinos)
					{
						// Borra ese pixel
						SetBmpPixel(i,j,color_negro);
						no_fin = true;
					}
					else
					{
						// Aumento de grosor las lineas delgadas para evitar
						// que no se pueda calcular el contorno
						GetBmpPixel(i+1,j,&color_de,&pos);
						GetBmpPixel(i-1,j,&color_iz,&pos);
						GetBmpPixel(i,j+1,&color_ar,&pos);
						GetBmpPixel(i,j-1,&color_ab,&pos);
						GetBmpPixel(i+1,j+1,&color_arde,&pos);
						GetBmpPixel(i-1,j+1,&color_ariz,&pos);
						GetBmpPixel(i-1,j-1,&color_abiz,&pos);
						GetBmpPixel(i+1,j-1,&color_abde,&pos);

						if ((color_iz.rgbBlue == 255) && (color_de.rgbBlue == 255) &&
							(color_ar.rgbBlue == 0) && (color_ab.rgbBlue == 0))
							SetBmpPixel(i,j+1,color_blanco);

						if ((color_ar.rgbBlue == 255) && (color_ab.rgbBlue == 255) &&
							(color_iz.rgbBlue == 0) && (color_de.rgbBlue == 0))
							SetBmpPixel(i+1,j,color_blanco);

						if ((color_arde.rgbBlue == 255) && (color_ar.rgbBlue == 0) &&
							(color_de.rgbBlue == 0))
							SetBmpPixel(i,j+1,color_blanco);

						if ((color_ariz.rgbBlue == 255) && (color_ar.rgbBlue == 0) &&
							(color_iz.rgbBlue == 0))
							SetBmpPixel(i,j+1,color_blanco);

						if ((color_abde.rgbBlue == 255) && (color_ab.rgbBlue == 0) &&
							(color_de.rgbBlue == 0))
							SetBmpPixel(i,j-1,color_blanco);
							
						if ((color_abiz.rgbBlue == 255) && (color_ab.rgbBlue == 0) &&
							(color_iz.rgbBlue == 0))
							SetBmpPixel(i,j-1,color_blanco);
					}
				}
			}	
		}
	}while (no_fin);
	
	return true;
}

int CImagen::NumVecinos(int i, int j)
{
	RGBQUAD color;
	int cont, pos;

	cont = 0;
	
	if (i + 1 < m_lpBmIH->biWidth)
	{
		GetBmpPixel(i + 1,j,&color,&pos);
		if (color.rgbBlue == 255)
			cont++;
	}

	if ((i + 1 < m_lpBmIH->biWidth) && (j + 1 < m_lpBmIH->biHeight))
	{
		GetBmpPixel(i + 1, j + 1,&color,&pos);
		if (color.rgbBlue == 255)
			cont++;
	}

	if (j + 1 < m_lpBmIH->biHeight)
	{
		GetBmpPixel(i, j + 1,&color,&pos);
		if (color.rgbBlue == 255)
			cont++;
	}
	
	if ((i - 1 >= 0) && (j + 1 < m_lpBmIH->biHeight))
	{
		GetBmpPixel(i - 1, j + 1,&color,&pos);
		if (color.rgbBlue == 255)
			cont++;
	}

	if (i - 1 >= 0)
	{
		GetBmpPixel(i - 1, j,&color,&pos);
		if (color.rgbBlue == 255)
			cont++;
	}

	if ((i - 1 >= 0) && (j - 1 >= 0))
	{
		GetBmpPixel(i - 1, j - 1,&color,&pos);
		if (color.rgbBlue == 255)
			cont++;
	}

	if (j - 1 >= 0)
	{
		GetBmpPixel(i, j - 1,&color,&pos);
		if (color.rgbBlue == 255)
			cont++;
	}

	if ((i + 1 < m_lpBmIH->biWidth) && (j - 1 >= 0))
	{
		GetBmpPixel(i + 1, j - 1,&color,&pos);
		if (color.rgbBlue == 255)
			cont++;
	}

	return cont;
}

bool CImagen::FormaValida()
{
	int nptos_objeto, perimetro;
	
	perimetro = (m_lpBmIH->biHeight * 2) + (m_lpBmIH->biWidth * 2);
	nptos_objeto = NumPuntosExtremo();
	
	if (nptos_objeto > (0.5 * perimetro))
		return false;
	else
		return true;
}

int CImagen::NumPuntosExtremo()
{
	RGBQUAD color;
	int i, j, cnt = 0, pos;

	for (j=0;j<m_lpBmIH->biHeight;j++)
	{
		for (i=0;i<m_lpBmIH->biWidth;i++)
		{
			GetBmpPixel(i,j,&color,&pos);
			if ((color.rgbBlue == 255) &&
				((j == 0) || (j == m_lpBmIH->biHeight - 1) ||
				 (i == 0) || (i == m_lpBmIH->biWidth - 1)))
			{
				cnt++;
			}
		}
	}

	return cnt;
} 

bool CImagen::AsignarMem(bool bReasignar)
{
	if (bReasignar) // bReasignar por defecto vale false
		m_lpDib = (char *) GlobalReAllocPtr(m_lpDib, m_dwLongDib, GHND);
	else
		m_lpDib = (char *) GlobalAllocPtr(GHND, m_dwLongDib);
        // GHND combina GMEM_MOVEABLE y GMEM_ZEROINIT
	if (!m_lpDib)
	{
		AfxMessageBox("Insuficiente memoria para el DIB");
		m_dwLongDib = 0L;
		m_nBitsColor = 0;
		return false;
	}
	
	m_lpBmFH = (LPBITMAPFILEHEADER) m_lpDib;
	m_lpBmIH = (LPBITMAPINFOHEADER) (m_lpDib + sizeof(BITMAPFILEHEADER));
	m_lpBmI = (LPBITMAPINFO) m_lpBmIH;
	
	return true;
}

void CImagen::CrearPaletaGrises()
{
	RGBQUAD color;
	int i, j, pos, corresp[256];

	// Sólo hacemos el proceso cuando tengamos 16 ó 256 colores en la paleta
	if (m_lpBmIH->biBitCount != 8)
		return;

	// Guardamos las correspondencias entre paletas
	for (i=0;i<256;i++)
	{
		if ((m_lpBmI->bmiColors[i].rgbBlue == m_lpBmI->bmiColors[i].rgbGreen) &&
			(m_lpBmI->bmiColors[i].rgbGreen == m_lpBmI->bmiColors[i].rgbRed))
		{
			corresp[i] = m_lpBmI->bmiColors[i].rgbBlue;
		}
	}

	// Modificamos la paleta por una de grises únicamente
	for (i=0;i<256;i++)
	{
		m_lpBmI->bmiColors[i].rgbBlue = i;
		m_lpBmI->bmiColors[i].rgbGreen = i;
		m_lpBmI->bmiColors[i].rgbRed = i;
		m_lpBmI->bmiColors[i].rgbReserved = 0;
	}

	// Actualizo los pixels de la imagen con el color de la paleta nueva
	for (j=0;j<m_lpBmIH->biHeight;j++)
	{
		for (i=0;i<m_lpBmIH->biWidth;i++)
		{
			GetBmpPixel(i,j,&color,&pos);
			SetBmpPixel(i,j,m_lpBmI->bmiColors[corresp[pos]]);		
		}
	}
}



