Introducción
1. Escala de grises
La escala de grises utiliza tonalidades de negro para representar objetos, empleando el negro como color base y distintos niveles de saturación para mostrar la imagen. Cada objeto en escala de grises posee un valor de brillo que va desde el 0% (blanco) hasta el 100% (negro). Las imágenes generadas con escáneres en blanco y negro o escala de grises suelen mostrarse en este formato.
El valor de cada píxel se cuantifica y almacena en un byte (8 bits). Si se discretizan los valores continuos de negro-gris-blanco en 256 niveles, el rango de valores será de 0 a 255, representando desde la oscuridad máxima (negro) hasta la luminosidad máxima (blanco). Una fotografía en blanco y negro contiene todos los tonos intermedios; cada píxel es uno de los 256 posibles valores de gris.
2. Imagen en escala de grises
Una imagen representada mediante escala de grises se denomina imagen en escala de grises. En este formato, los tres canales RGB cumplen la relación: R = G = B.
3. Formato de archivo BMP para imágenes en escala de grises
La estructura detallada del archivo BMP se resume en la siguiente tabla:
| Sección | Desplazamiento | Nombre del campo | Tamaño | Descripción |
|---|---|---|---|---|
| Cabecera del archivo de imagen | 0000h | Identificador | 2 bytes | Contenido para reconocer el tipo de mapa de bits: 'BM' para Windows 3.1x, 95, NT, ... |
| 0002h | Tamaño del archivo | 1 dword | Tamaño total del archivo en bytes | |
| 0006h | Reservado | 1 dword | Reservado, se establece a 0 | |
| 000Ah | Desplazamiento de datos de mapa de bits | 1 dword | Desplazamiento desde el inicio del archivo hasta el inicio de los datos de la imagen | |
| 000Eh | Tamaño de la cabecera de mapa de bits | 1 dword | Longitud de la cabecera de información del mapa de bits, que describe colores, método de compresión, etc. Valores comunes: 28h (Windows), 0Ch (OS/2 1.x), F0h (OS/2 2.x) | |
| 0012h | Ancho | 1 dword | Ancho de la imagen en píxeles | |
| 0016h | Alto | 1 dword | Alto de la imagen en píxeles | |
| 001Ah | Planos | 1 word | Número de planos de color del mapa de bits | |
| Cabecera de información de la imagen | 001Ch | Bits por píxel | 1 word | Número de bits por píxel: 1 (monocromo), 4 (16 colores), 8 (256 colores), 16 (color alto), 24 (color verdadero), 32 (color verdadero con canal alfa) |
| 001Eh | Compresión | 1 dword | Método de compresión: 0 (BI_RGB, sin compresión), 1 (BI_RLE8), 2 (BI_RLE4), 3 (BI_BITFIELDS) | |
| 0022h | Tamaño de datos de mapa de bits | 1 dword | Tamaño de los datos de la imagen en bytes. Debe ser múltiplo de 4 | |
| 0026h | Resolución horizontal | 1 dword | Resolución horizontal en píxeles por metro | |
| 002Ah | Resolución vertical | 1 dword | Resolución vertical en píxeles por metro | |
| 002Eh | Número de colores | 1 dword | Número de colores usados en la imagen. Ej.: 100h (256) para imágenes de 8 bits por píxel | |
| 0032h | Colores importantes | 1 dword | Número de colores considerados importantes. Igual al número de colores indica que todos son igualmente importantes | |
| Datos de la paleta | 0036h | Paleta | N * 4 bytes | Especificación de la paleta. Cada entrada usa 4 bytes para describir los valores RGB: 1 byte para azul, 1 byte para verde, 1 byte para rojo y 1 byte de relleno (0) |
| Datos de la imagen | 0436h | Datos del mapa de bits | x bytes | Tamaño dependiente del método de compresión. Contiene todos los bytes de datos de la imagen, que son índices hacia la paleta de colores |
Ejemplo: gray.bmp
1) Información de la imagen gray.bmp
2) Datos del archivo gray.bmp
0000h: Identificador es "BM"
0002h: Tamaño del archivo es 0x13036 (77878 bytes)
0006h: Reservado
000Ah: Desplazamiento a los datos de mapa de bits es 0x436
000Eh: Longitud de la cabecera de información es 0x28
0012h: Ancho de la imagen es 0x140 (320 píxeles)
0016h: Alto de la imagen es 0xF0 (240 píxeles)
001Ah: Número de planos es 1
001Ch: Bits por píxel es 8
001Eh: Sin compresión
0022H: Tamaño de los datos de la imagen es 0x12C00 (76800 bytes)
0026H: Resolución horizontal es 7200
002Ah: Resolución vertical es 7200
002Eh: Número de colores - campo no asignado
0032h: Colores importantes - campo no asignado
0036h-0435h: Datos de la paleta
0436h: Zona de datos de la imagen
3) Interpretación
La zona de datos de la imagen almacena la información válida. Un byte de almacenamiento corresponde a un píxel de la imagen, y su valor numérico representa el nivel de gris de dicho píxel. Para una imagen de 320x240 píxeles, la zona de datos ocupa 76800 bytes. Sumando la cabecera de 1078 bytes, el tamaño total del archivo es 77878 bytes.
Durante la visualización, se leen los datos de la zona de datos de la imagen. Una vez obtenido el valor de gris de un píxel, se utiliza como índice para buscar en la paleta el color RGB correspondiente y luego se muestra en la pantalla.
Nota: Windows especifica que el número de bytes por fila de escaneo debe ser múltiplo de 4, rellenando con ceros si es necesario. Por ejemplo, una imagen de 318 píxeles de ancho realmente ocupa 320 bytes por fila.
4) Orden de almacenamiento de la imagen en archivos BMP
Los archivos BMP almacenan la imagen desde la esquina inferior izquierda hasta la superior derecha, es decir, "de abajo hacia arriba y de izquierda a derecha".
Al mostrar la imagen en pantalla se debe respetar este orden: de abajo hacia arriba y de izquierda a derecha. Si se muestra "de arriba hacia abajo y de izquierda a derecha", la imagen aparecerá invertida respecto al eje Y.
Conversión de formato de 8 bits en escala de grises a RGB565 de 16 bits
En una imagen en escala de grises, los canales RGB cumplen R=G=B. Para obtener datos en formato RGB, se sustituye el valor de gris en los tres canales. Considerando que el formato de visualización del LCD es 5:6:5 (rojo:verde:azul) y el valor de gris es de 8 bits, la conversión se realiza mediante las siguientes fórmulas:
- Componente rojo (5 bits): r = valor_gris / 8 (equivalente a valor_gris >> 3)
- Componente verde (6 bits): g = valor_gris / 4 (equivalente a valor_gris >> 2)
- Componente azul (5 bits): b = valor_gris / 8 (equivalente a valor_gris >> 3)
La conversión de un valor de 8 bits a RGB565 de 16 bits se implementa con la siguiente macro:
#define ESCALA_GRISES_A_RGB16(nivel_gris) ((nivel_gris >> 3) | ((nivel_gris & ~3) << 3) | ((nivel_gris & ~7) << 8))
Programa de ejemplo
/**
* @brief Muestra una imagen en escala de grises en el LCD
* @param posX, posY: Coordenadas de inicio en el LCD
* archivo: Nombre del archivo BMP en escala de grises almacenado en la tarjeta SD
* @retval Ninguno
*/
void PresentarImagenGrisLCD(unsigned short posX, unsigned short posY, char *archivo)
{
int fila, columna;
int ancho_img, alto_img, ancho_alineado;
BITMAPFILEHEADER cabecera_archivo;
BITMAPINFOHEADER cabecera_info;
WORD tipo_archivo;
unsigned int bytes_leidos;
f_mount(0, &sistema_archivos[0]);
DEBUG_BMP("Sistema de archivos montado\r\n");
resultado = f_open(&archivo_bmp, (char *)archivo, FA_OPEN_EXISTING | FA_READ);
if (resultado == FR_OK)
{
DEBUG_BMP("Archivo abierto con éxito\r\n");
/* Leer los primeros dos bytes para identificar el tipo de archivo */
f_read(&archivo_bmp, &tipo_archivo, sizeof(WORD), &bytes_leidos);
/* Verificar si es un archivo BMP mediante la firma "BM" */
if (tipo_archivo != 0x4d42)
{
DEBUG_BMP("El archivo no es de tipo BMP\r\n");
return;
}
else
{
DEBUG_BMP("Archivo BMP reconocido correctamente\r\n");
}
/* Leer la cabecera del archivo BMP */
f_read(&archivo_bmp, &cabecera_archivo, sizeof(tagBITMAPFILEHEADER), &bytes_leidos);
MostrarCabeceraArchivo(&cabecera_archivo);
/* Leer la cabecera de información de la imagen */
f_read(&archivo_bmp, &cabecera_info, sizeof(BITMAPINFOHEADER), &bytes_leidos);
MostrarCabeceraInfo(&cabecera_info);
}
else
{
DEBUG_BMP("Error al abrir el archivo\r\n");
return;
}
ancho_img = cabecera_info.biWidth;
alto_img = cabecera_info.biHeight;
/* Calcular el ancho real de la fila, asegurando que sea múltiplo de 4 */
ancho_alineado = CALCULAR_ANCHO_FILA(ancho_img);
if (ancho_alineado > 320)
{
DEBUG_BMP("\nERROR: IMAGEN DEMASIADO GRANDE (MÁX. 320)\n");
return;
}
/* Configurar la dirección de escaneo del LCD: esquina inferior derecha -> esquina superior izquierda */
ConfigurarEscaneoLCD(3);
/* Abrir una ventana en el LCD del tamaño de la imagen */
AbrirVentanaLCD(posX, posY, ancho_img, alto_img);
/* Verificar si la imagen es de 8 bits en escala de grises */
if (cabecera_info.biBitCount == 8)
{
/* Mover el puntero del archivo al inicio de los datos de la imagen */
f_lseek(&archivo_bmp, 0x0436);
for (fila = 0; fila < alto_img; fila++)
{
/* Leer una fila completa de datos de la imagen */
f_read(&archivo_bmp, buffer_fila, ancho_alineado, &bytes_leidos);
for (columna = 0; columna < ancho_img; columna++)
{
unsigned short color_rgb;
color_rgb = buffer_fila[columna];
EscribirDatoLCD(ESCALA_GRISES_A_RGB16(color_rgb));
}
}
}
else
{
DEBUG_BMP("ERROR: LA IMAGEN NO ES DE 8 BITS EN ESCALA DE GRISES");
return;
}
f_close(&archivo_bmp);
}
Referencias: Cabeceras de archivos BMP y visualización de imágenes de 8 bits en escala de grises.