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:

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:

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:

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:

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:

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

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:
Compilar correctamente con los flags:
-Wall -Werror -std=c99 -pedantic
y pasar Valgrind correctamente
La entrega se realiza a través del sistema de entregas.
El ejercicio es de entrega individual.