Ejercicio obligatorio 2

Fecha de entrega: Lunes 6 de octubre

Nota

Nunca es buena idea empezar por un ejercicio integrador antes de tener practicados los temas que el trabajo integra.

Se sugiere antes de desarrollar este trabajo resolver, al menos, los siguientes ejercicios de la guía de Memoria dinámica:

  • Ejercicio 12 (combinar vectores).

  • Ejercicio 14.a (matriz identidad).

  • Ejercicio 16 con el 1.b y 1.c de la guía de estructuras (dirección y persona).

Introducción

Colores

En el EJ1 vimos que en una imagen digital se tienen valores sobre posiciones discretas, llamados píxeles. En esa ocasión los píxeles eran monocromáticos, es decir, solo admitían dos valores, ahora bien, los píxeles pueden representar colores y hay muchos formatos para representarlos.

Los formatos varían en la manera en la que se codifican esos colores y en cuánta capacidad se asigna a esa codificación. Por ejemplo, un formato común podría ser descomponer el color en sus componentes RGB (rojo, verde y azul) y utilizar 8 bits para cada una de ellas, tendríamos en ese caso color RGB de 24 bits. Así como existe este formato hay muchos otros: YUV, CMYK, HSV, YIQ, HSL e incluso muchos formatos previos a las imágenes digitales como PAL, NTSC, etc.

Cada formato está pensado para diferentes medios, maximizando o minimizando características de interés y, en principio, el mismo color debería poder representarse en cada uno de elos.

Formato DEF

Proponemos en este trabajo el formato DEF de 16 bits. Nuestro tipo estará definido como:

typedef uint16_t color_t;

Dentro de este formato tendremos tres componentes: D, E y F. La componente D utilizará 5 bits, la componente E utilizará 6 bits y la componente F utilizará 5 bits, según el siguiente esquema:

MSB 15                                                                       0 LSB
+----+----+----+----+----+----+----+----++----+----+----+----+----+----+----+----+
| d4 | d3 | d2 | d1 | d0 | e5 | e4 | e3 || e2 | e1 | e0 | f4 | f3 | f2 | f1 | f0 |
+----+----+----+----+----+----+----+----++----+----+----+----+----+----+----+----+

En rangos es inmediato observar que D y F estarán entre 0 y 31 mientras que E estará entre 0 y 63.

Formato PPM

El formato Portable Pixel Map (PPM) es el formato que ofrece el paquete Netpbm para la representación de imágenes a color en un archivo sencillo.

El formato es similar al PGM ya analizado con la diferencia de que ahora por cada pixel en vez de tener un único valor de grises tendremos tres valores, uno para cada componente RGB del color. Si bien se pueden cambiar la profundidad de color del RGB seteando un máximo, por lo general se utiliza 255 como máximo por lo que este formato suele representar RGB de 24 bits.

Por ejemplo el siguiente archivo

P3
# Este es un ejemplo de PPM
4 4
# Asumimos que el máximo es siempre 255
255
#   Rojo                                     Verde
255   0   0   170  85   0    85 170   0     0 255   0
170   0  85   141  85  85   113 170  85    85 255  85
 85   0 170   113  85 170   141 170 170   170 255 170
  0   0 255    85  85 255   170 170 255   255 255 255
#   Azul                                     Blanco

genera la siguiente imagen:

../_images/20252_ej2_ppm.png

Resumiendo: El encabezado es P3, hay 3 valores numéricos por cada pixel.

Una imagen puede contener líneas con comentarios, las mismas comienzan con el caracter numeral y deben ser ignoradas.

Convertir de RGB en DEF y viceversa

Siendo que las imágenes PPM se codifican en RGB de 24 bits y nosotros queremos almacenarlas en DEF de 16 bits, entonces tenemos que definir cómo va a ser esa conversión.

Siendo R, G, B las componentes de 8 bits cada una (entre 0 y 255) calcularemos los valores de D, E, F como:

../_images/20252_ej2_rgb_def.png

Todas las operaciones son con aritmética entera, así que respetar las cuentas como están expresadas ahí, o sea \(\frac{A x}{y} \neq A\frac xy\), etc.

Eso sí, todavía falta una cuenta, el valor de E tiene que estar entre 0 y 63, hay que hacer un módulo 64 o, lo que es lo mismo, truncar el resultado a 6 bits.

El camino de vuelta se hace según estas relaciones:

../_images/20252_ej2_def_rgb.png

Acá todas las operaciones son de aritmética entera salvo \(\frac E{11}\mod 2\), que necesita una división flotante y computar un módulo 2 flotante. En C la implementación de esa operación es fmodf(E / 11.0, 2), el resultado es flotante y eso tiene que propagarse a todo el término que multiplica a C, luego X será entera, como el resto.

En este caso, no por diseño, si no por redondeos varios los valores finales de R, G, B pueden dar superiores a 255... si se pasaran de ese valor, hay que forzarlos a 255 (esto no es un truncado de bits, estamos diciendo que \(R' = \min\{R, 255\}\)).

De más está decir que la conversión RGB -> DEF -> RGB no va a ser exacta, dado que estamos truncando información al codificar 24 bits en 16.

Imágenes

Queremos modelar imágenes, para ello definiremos la estructura:

typedef struct {
    color_t **pixeles;
    size_t ancho, alto;
} imagen_t;

que permite almacenar en memoria una imagen según el siguiente esquema:

../_images/20252_ej2.png

donde color_t es nuestro tipo de color en formato DEF.

Trabajo

Color

Implementar una función color_t color_desde_def(uint8_t d, uint8_t e, uint8_t f); que cree un color_t desde sus componentes d, e, f. Asumir que las componentes vienen en rango.

Implementar una función uint8_t color_d(color_t c); que devuelva la componente d del color c.

Implementar una función uint8_t color_e(color_t c); que devuelva la componente e del color c.

Implementar una función uint8_t color_f(color_t c); que devuelva la componente f del color c.

Conversión de colores

Implementar una función color_t color_desde_rgb(uint8_t r, uint8_t g, uint8_t b); que cree un color_t en base a las componentes de un RGB de 24 bits.

Implementar una función void color_a_rgb(color_t c, uint8_t *r, uint8_t *g, uint8_t *b); que convierta un color c a sus componentes equivalentes r, g y b de 24 bits y las devuelva.

Nota

En las cuentas provistas hay un montón de multiplicaciones y divisiones por potencias de dos, hay operaciones de truncamiento, etc.

Estas operaciones deben realizarse con operaciones de bits.

Recomendación: Implementar inicialmente las operaciones con operadores aritméticos, reimplementar con bits una vez funcionen.

Imagen

Implementar una función imagen_t *imagen_crear(size_t ancho, size_t alto); que cree una imagen de ancho x alto, sin inicializar el valor de sus píxeles. La función debe devolver NULL en caso de falla.

Implementar una función void imagen_destruir(imagen_t *i); que libere la memoria asociada a la imagen i previamente creada.

Implementar una función imagen_t *imagen_clonar(const imagen_t *i); que devuelva una copia de i en memoria nueva o NULL en caso de falla.

Lectura y escritura

Implementar una función void imagen_escribir_ppm(const imagen_t *i); que imprima por stdout la imagen i en formato PPM en RGB de 24 bits.

Implementar una función imagen_t *imagen_leer_ppm(); que lea de stdin una imagen en formato PPM RGB de 24 bits y la cargue en memoria. Debe validarse que la primera línea de la entrada sea P3\n. Deben ignorarse las líneas que correspondan a comentarios (las que comiencen con #).

Nota

Para simplificar asumir que luego del encabezado del formato y sus dimensiones los valores numéricos pueden venir pura y exclusivamente de a un valor numérico por línea (a diferencia del ejemplo dado en la introducción).

Aplicación

Se provee el siguiente main():

int main() {
    imagen_t *imagen = imagen_leer_ppm();
    assert(imagen != NULL);

    imagen_t *copia = imagen_clonar(imagen);
    assert(copia != NULL);

    imagen_destruir(imagen);

    imagen_escribir_ppm(copia);
    imagen_destruir(copia);

    assert(imagen_crear(1UL << 60, 10000) == NULL);
    assert(imagen_crear(10000, 1UL << 60) == NULL);

    return 0;
}

El programa al ser ejecutado debe leer una imagen RGB 24 de stdin, almacenarla en DEF 16, y luego imprimirla por stdout en RGB 24.

Se proveen ejemplos de esta conversión, que como ya se anticipó, no será exacta dada la compresión del formato dado.

Se provee además la salida de estas imágenes en formato PPM con los valores DEF, es decir, se proveen los tres archivos RGB -> DEF -> RGB. Es decir, se dan miles de ejemplos de conversión ida y vuelta entre los dos formatos, para verificar la consistencia de las cuentas.

La imagen de entrada se ve:

../_images/20252_ej2_original.png

mientras que la salida RGB -> DEF -> RGB se ve

../_images/20252_ej2_salida.png

Si se ejecutara el programa como:

$ ./20252_ej2 < 20252_ej2_original.ppm > 20252_ej2_salida.ppm

debería generarse una imagen exactamente igual a la de salida provista.

Se adjuntan ambas imágenes y la intermedia en formato PPM en este adjunto: archivos_20252_ej2.tar.gz

Entrega

Deberá entregarse el código fuente del programa desarrollado.

El programa debe:

  1. Compilar correctamente con los flags:

    -Wall -Werror -std=c99 -pedantic
    
  2. y pasar Valgrind correctamente

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

El ejercicio es de entrega individual.