Ejercicio obligatorio 3

Fecha de entrega: Lunes 11 de noviembre

Introducción

El formato de Windows Bitmap (BMP) es un formato binario con muchas limitaciones pero sencillo de implementar y entender y un formato ampliamente difundido.

Si bien el mismo tiene muchos modos de operación, vamos a centrarnos en la versión de 1 bit monocromo, no comprimida, etc. para simplificar sus particularidades.

Un archivo BMP consta de tres secciones:

Sección

Tamaño

Encabezado de archivo

14

Encabezado de imagen

40

Tabla de colores

8

Pixeles

--

Cada una de estas secciones tiene un formato específico.

Si bien la mayor parte de los campos del archivo son de un solo byte, hay números almacenados en más de un byte y en este caso esos números son números signados almacenados según la convención little-endian. Es decir, si un campo numérico contuviera la secuencia {0xAA, 0xBB, 0xCC, 0xDD} el número representado sería el 0xDDCCBBAA.

Encabezado de archivo

El encabezado de archivo consiste en la siguiente secuencia:

Campo

Tipo

Valor

Tipo

char[2]

"BM"

Tamaño

int32_t

El tamaño en bytes del archivo

Reservado

int16_t

0

Reservado

int16_t

0

Offset

int32_t

62

Como se ve, el único valor no definido del formato es el tamaño en bytes del archivo. El parámetro del offset indica en qué posición comienza la tabla de píxeles en el archivo, pero siendo que estamos usando un formato simplificado donde el encabezado de imagen siempre mide 40 bytes y una paleta de 2 colores y 8 bytes entonces este valor queda fijo.

Encabezado de imagen

El encabezado de imagen consiste en la siguiente secuencia:

Campo

Tipo

Valor

Tamaño

int32_t

40

Ancho

int32_t

El ancho de la imagen

Alto

int32_t

El alto de la imagen

Planos

int16_t

1

Bits de color

int16_t

1

Compresión

int32_t

0

Tamaño de imagen

int32_t

0 o número de bytes

Resolución X

int32_t

0 o resolución en X

Resolución Y

int32_t

0 o resolución en Y

Tablas de color

int32_t

2

Colores importantes

int32_t

0 o 2

Como se ve, con todas las simiplificaciones adoptadas, los únicos dos parámetros variables son el ancho y el alto de la imagen. Al leer un archivo es importante validar que tanto los bits de color, como la compresión como las tablas de color estén en los valores indicados; si no, no sabemos cómo procesar ese archivo. Con nuestra simplificación sólo sabemos leer imágenes monocromáticas.

Cabe remarcar que la altura podría llegar a ser un valor negativo.

Tabla de colores

La tabla de colores será una sucesión de:

Rojo

uint8_t

El valor de rojo

Verde

uint8_t

El valor de verde

Azul

uint8_t

El valor de azul

---

uint8_t

Reservado

Como tenemos sólo dos colores tendremos dos veces esa secuencia.

Pixeles

Como la imagen es monocroma los pixeles ocuparán sólo 1 bit. Es decir, en un byte se empaquetarán 8 bits.

Los archivos BMP se definen por scan lines, cada scan line es una fila de la imagen. Ahora bien, cada scan line tiene que ocupar un número múltiplo de 4 bytes, entonces, si una imagen tuviera, por ejemplo 20 pixeles de ancho, alcanzarían 2 bytes y medio, o sea 3 bytes, para representar una fila; bueno, la scan line igual ocupará 4 bytes, porque tiene que siempre ser un múltiplo de 4.

Notar que el largo de la scan line puede computarse mirando cuánto falta para que sea múltiplo de 4 la operación de dividir por 8 el ancho de la imagen.

En la scan line los bits se ordenan "como se leen". El bit más pesado del primer byte será el primer pixel, el bit que le sigue el segundo y el bit más liviano del primer byte será el octavo pixel. Luego el bit más pesado del segundo byte será el noveno pixel y así hasta terminar la línea. Una vez que se agota el ancho todo lo que sirve para completar la scan line son ceros.

La sección de píxeles consistirá en una secuencia de tantas scan lines como altura tenga la imagen. Si el parámetro de altura se hubiera indicado como un número positivo las scan lines están ordenadas de abajo hacia arriba, en cambio si fuera negativo estarán ordenadas de arriba hacia abajo.

Trabajo

Endianness

Escribir una función bool leer_int16_little_endian(FILE *f, int16_t *v); que lea un entero de 16 bits en formato little-endian del archivo f y lo escriba en v. La función debe devolver true si puede leer correctamente.

Escribir una función bool leer_int32_little_endian(FILE *f, int32_t *v); similar a la anterior pero que lea un entero de 32 bits.

Nota

No puede asumirse que la plataforma es big/little-endian. Las funciones deben operar con los bytes a bajo nivel y ser portables a cualquier arquitectura.

TDA Imagen

Diseñar un TDA imagen_t que represente una imagen monocroma con las siguientes primitivas:

  • imagen_t *imagen_desde_archivo_BMP(FILE *f); que reciba un archivo f que debería ser un BMP monocromo y devuelva la imagen contenida en él. Si el formato fuera inválido o hubiera una falla de memoria devolverá NULL.

  • void imagen_destruir(imagen_t *im); libera la memoria contenida en la imagen.

  • size_t imagen_ancho(const imagen_t *im); getter del ancho de la imagen.

  • size_t imagen_alto(const imagen_t *im); getter del alto de la imagen.

  • bool imagen_get_pixel(const imagen_t *im, size_t fila, size_t columna); getter de un pixel de la imagen.

Aplicación

Desarrollar una aplicación que se ejecute como:

$ ./ej3 imagen.bmp

que muestre por stdout la imagen pasada como parámetro.

Ejemplo

Por ejemplo la siguiente imagen:

../_images/20242_ej3.png

es una versión escalada de este original en BMP:

../_images/20242_ej3.bmp

El contenido de esta imagen es:

$ hd 20242_ej3.bmp
00000000  42 4d 5e 00 00 00 00 00  00 00 3e 00 00 00 28 00  |BM^.......>...(.|
00000010  00 00 1a 00 00 00 08 00  00 00 01 00 01 00 00 00  |................|
00000020  00 00 20 00 00 00 13 0b  00 00 13 0b 00 00 02 00  |.. .............|
00000030  00 00 02 00 00 00 00 00  00 00 ff ff ff 00 dd dd  |................|
00000040  9c 40 dd dd 6b 80 dc 1d  eb 80 de bd eb 80 de bd  |.@..k...........|
00000050  9b 80 de bd db 80 df 79  eb 80 07 7d 0c 40        |.......y...}.@|
0000005e
$

El archivo tiene 26 (0x0000001a) pixels de ancho. Por ejemplo, tomar la primera scanline que aparece en el archivo, que es la de abajo: {0xdd, 0xdd, 0x9c, 0x40}, como las scanlines tienen múltiplos de 4 bytes a esta le sobran 6 bits. Los bits en binario serían 11011101110111011001110001 esa es la secuencia de blancos y negros (blanco: 1, negro: 0) en la última fila de la imagen.

La aplicación al ser ejecutada debería mostrar algo como:

$ ./ej3 20242_ej3.bmp
* * * * *       *           *   * * * *     * * *
    *           *         * *         *   *       *
    *         *   *         *       *     *       *
    *         *   *         *     * *     *       *
    *         *   *         *         *   *       *
    *       * * * * *       *         *   *       *
    *       *       *       *   *     *   *       *
    *       *       *       *     * *       * * *
$

Entrega

Deberá entregarse el código fuente del programa desarrollado o los fuentes y el Makefile en caso de haber modularizado.

El programa debe:

  1. Compilar correctamente con los flags:

    -Wall -Werror -std=c99 -pedantic
    
  2. y generar la salida correcta para imágenes BMP monocromas.

La entrega se realiza a través del sistema de entregas.

El ejercicio es de entrega individual.