Almacenamiento y acceso a una imagen numérica
Por Francisco Bernal Rosso.
 




La imagen

Una imagen es una proyección plana de una escena. Una escena es la disposición de cuerpos en el espacio. Como proyección plana la imagen nos interesa exclusivamente en tanto puede ser contemplada con los ojos. Por tanto solo nos interesan las propiedades visuales de la imagen, osea los brillos.
Una imagen es digital cuando la proyección plana de la escena está referida mediante números. Una imagen es analógica cuando la proyección plana de la escena está referida mediante algún parámetro físico medible.
Así una imagen formada por brillos sobre un papel es analógica ya que los brillos correpsonden a una magnitud física visual.
La imagen en un televisor es una imagen ya que es una proyección de una escena y es analógica porque está formada por brillos. El registro en una cinta de vídeo podemos entenderla como una imagen ya que supone la proyección, susceptible de ser restaurada en un plano, de una escena y es analógica debido a que lo que almacenamos es un campo magnético que está relacionado con los brillos de la escena.

Una imagen digital, por el contrario está formada exclusivamente por números. La densidad, o sea el grado de oscurecimiento de un punto de un papel no se puede considerar un número debido a que es necesario realizar la medición.

Un sistema es analógico cuando procesa magnitudes físicas. Es digital cuando procesa números.
Aunque la realidad última sea que los números se transforman en algún tipo de magnitud física para poder ser usados automáticamente siempre existe algún tipo de codificación que nos independiza del medio físico real empleado. Por ejemplo, podemos emplear niveles de tensión eléctrica para representar las cifras binarias de un número en un procesado digital. Pero lo que nos interesa es el valor como signo, no como magnitud física.
Un objetivo transforma la luz en brillos (dicho así un poco a lo bruto) mientras que digitalmente convertimos la escena en números y trabajamos con ellos.

En este contexto vamos a llamar imagen digital, abreviadamente solo imagen, a una o varias matrices rectangulares formadas por números.

La sensación visual está formada por varios aspectos. Cada uno de estos aspectos lo vamos a llamar componente. Podemos distinguir tres aspectos visuales que son: el matiz, relacionado con la frecuencia central de la luz y que identificamos con el color; la saturación, relacionada con la mezcla o composición espectral total de la luz y que identificamos como la viveza de un color; y la luminosidad o brillo, relacionada con la cantidad de energía total de la luz, que relacionamos con la claridad u oscuridad del color.

Estas son las tres componentes fisiológicas, pero técnicamente podemos establecer otras. Las principales, debido a su tradición en las artes, son aquellas que establecen una serie de colores “primarios” a partir de los cuales se obtienen los demás. Los dos sistemas mas usuales en transmisión y proceso de datos son el sistema RGB y el CMYK.
El sistema RGB consiste en mezclar colores a partir de un rojo un verde y un azul determinado. El sistema CMYK consiste en la obtención de los colores mediante la mezcla de un celeste (cian, azul verdoso) un púrpura (tambien llamado magenta) un amarillo y negro.

Otros sistemas de determinación de color son los sistemas de tres componentes XYZ o el más importante de todos: el sistema lab, sistema de tres componentes que situa el matiz y la saturación mediante los parámetros a y b y la luminosidad mediante el parámetro l.

Técnicamente el sistema tricomponente lab es el mas importante y empleado, aunque quizás el RGB y el CMYK sean los más populares. De hecho, gran parte de los sistemas existentes emplean internamente la codificación lab y solo externamente muestran la imagen en RGB o CMYK.

Cada componente de una imagen está formada por una matriz rectangular de números que indican el valor local (geométricamente hablando) de dicha componente. De ahí que se haya definido una imagen como una o mas matrices de números.

Por lo general una matriz está formada por un cierto número de columnas y de filas. Lo normal es que, en tratamiento de la imagen, se numeren las columnas de izquierda a derecha y las filas de arriba a abajo. Según unos sistemas la primera columna es la 1, mientras que según otros es la 0. Nosotros, que vamos a emplear el lenguaje C vamos a numerar siempre a partir de 0.

La siguiente matriz tiene 6 columnas y 5 filas.

Profundidad de color
Por lo general los números son enteros. Cada componente de color se codifica mediante un número, habitualmente en base 2 y de ocho o más cifras.
Actualmente se codifican las imágenes de forma que cada número tenga uno o dos bytes. Un byte, como se sabe, es un número entero en base dos de 8 cifras. Por lo que con un solo byte podemos contar desde 0 hasta 255. Aunque hay imágenes que se codifican con dos bytes solo se emplea parte del segundo. Así podemos tener una imagen con 10 bits por canal, que significa que emplea 10 cifras en base dos. Con 10 cifras podemos contar desde 0 hasta 1023. Con 12 cifras se puede contar desde 0 hasta 4095.
Por lo general se va a decir el número de bits por componente (8, 10, 12, 14) o bien el número de bits totales de la imagen. Osea el número de bits multiplicado por el número de componentes. A esto se llama profundidad de color. Por ejemplo, una imagen lab de 12 bits por componente suponen multiplicar tres por doce, es decir treinta y seis bits (treinta y seis bits de color). Para saber el número de colores disponibles solo tenemos que elevar dos al número de bits totales. El porqué de estos números las implicaciones prácticas será motivo de un capítulo aparte.
Así, un sistema de 8 bits por componente puede dar 256 distintos valor por cada una de las componentes que al combinarse con las demás se obtienen dos elevado a veinticuatro, que es algo mas de dieciseis millones de colores.

Canales
A las componentes se les llama canales cuando trabajamos con ellas. Habitualmente diferenciamos dos tipos de canales: los canales principales que se corresponden con las componentes de imagen y los canales alfa.

Un canal alfa es una matriz rectangular de números (osea una imagen) que tiene algún uso dentro del sistema de proceso de datos y que no es una componente de la imagen.

Así en un modo RGB hay tres canales principales que recogen la información sobre el color primario rojo, el primario verde y el primario azul del modo. Un canal alfa puede ser una máscara de transparencia que indica que parte de la imagen son opacas y que partes son transparentes.

Por lo general los canales alfa son matrices de solo 8 bits (256 tonos) y se emplean como filtros a través de los cuales se altera la imagen real, osea las componentes de color.

Una imagen en C
En lenguaje C una imagen es, como hemos dicho una matriz de números. Si queremos establecer una imagen de 800x600 puntos podemos definir el la matriz de la siguiente forma:

Imagen como matriz

unsigned char imagen[800][600];

unsigned char es el tipo de dato. En este caso hemos escogido un char, o sea un carácter pero sin signo (unsigned) de manera que tenemos un número de un solo byte (los char siempre tienen un solo byte) y que se puede emplear en el rango 0 a 255.
Si quisieramos una imagen de 16bits tendríamos que establecer el tipo exacto según el sistema operativo. Por ejemplo, en un ordenador compatible PC-IBM de 16 bits (versiones de MS-DOS inferiores a la 5 o procesadores anteriores al 386) podríamos poner:

unsigned int imagen[800][600];

ya que en estos ordenadores los enteros de tipo int tienen 16 bits. Sin embargo en los 486 y posteriores los int tienen 32 bits. Aunque esto realmente depende del compilador de C que empleemos. Por ejemplo, si empleamos un compilador antiguo como el Turbo-C versión 2 en un ordenador moderno (un pentium) los int serán de 16 bits. De manera que este tipo de dato es una parte del programa que hay que determinar en el momento y circunstancias concretas en que se crea (para qué sistema operativo, para qué plataforma y para qué compilador).

Imagen como serie lineal
Realmente la matriz no es suficiente. Por ejemplo las matrices así definidas son estáticas y no podemos emplear cualquier resolución de imagen sino solo las que establezcamos. Por ello rara vez se emplean matrices definidas directamente, lo que se emplean son matrices dinámicas.
Una matriz dinámica es simplemente una matriz cuyo tamaño no se especifica en el texto del programa sino que resulta salir durante la ejecución y según las circunstancias propias del trabajo concreto que se haga. Por ejemplo, si vamos a recoger una imagen de un escaner es el escaner el que nos va a imponer el tamaño de la matriz.

Una matriz dinámica se define mediante un puntero. Así:

unsigned char *imagen;

Esto por supuesto no es suficiente, hay que añadir el tamaño para poder trabajar y el espacio. Recordemos que un puntero no es mas que la dirección de comienzo de la matriz. Osea donde se situa el punto superior izquierda.

Reserva de espacio para una imagen
Para reservar el espacio total debemos hacer lo siguiente:

imagen=(unsigned char *) malloc(sizeof(unsigned char)*f*c);

Donde sizeof es una macro que informa del tamaño de cada punto, f es el número de filas y c el de columnas.

Una forma alternativa es:

imagen=(unsigned char *) alloc(sizeof(unsigned char),f*f);

La diferencia es que en malloc decimos el número de bytes que queremos reservar en memoria y alloc decimos el número de unidades de tamaño tal.

Acceso a un punto de la imagen

Supongamos la matriz expuesta anteriormente. Al ser una matriz de números reales podemos considerarla como una imagen digital.

 

2
0
3
4
8
7
3
2
1
8
9
12
21
2
34
5
5
2
98
87
76
65
45
3
43
4
0
1
2
3

Vamos a escribirla como imagen matricial y como imagen lineal.
Hay que recordar que estrictemente hablando no hay diferencia entre una imagen matricial y una lineal. Simplemente que en la lineal somos nosotros los responsables de delimitar la longitud de la línea.

unsigned char imm[6][5]; /* Imagen con matriz. */
unsigned char *pim;  /* Imagen con puntero. */

En principio no nos preocupa como se ha cargado la imagen, lo cierto esque imm es una matriz de 6x5 que contiene la imagen anterior y que pim es el puntero a la imagen, o sea la dirección del primer punto de la imagen. El que está arriba a la izquierda.

Una imagen se lee siempre de izquierda a derecha y de arriba a abajo.
Si imm[6][5] es como hemos definido la matriz entonces imm es la dirección de comienzo de la imagen, osea, la dirección de memoria a partir de la que se almacena la imagen. En el ejemplo anterior imm y pim valen lo mismo: la dirección de memoria del punto de arriba a la izquierda (el número 2).

Una matriz almacena los números primero por filas y luego por columnas. Osea que el orden que guardan en memneoria es: c0c0, c1,f0, c2f0. Donde c es columna y f fila. De manera que se guardan consecutívamente las filas contando de arriba hacia abajo. Esto es, primero se guarda toda la fila 0, edspués toda la fila 1. De esta manera después del punto [5][0], que es le número que está arriba a la izquierda (7) se guarda el número primero de la segunda fila, un 3.
Es muy importante recordar que se cuenta siempre desde la posición 0. De manera que la primera posición es la 0, la segunda la1 y así las demás.

Si quisiéramos llegar al número de la tercera columna, fila cuarta (que es un 76) deberíamos hacer:

imm[2][3]

Primero la columna, después la fila.

Con la imagen lineal sería algo mas complicado. Hay que tomar la dirección de memoria del punto. Sabemos, el tamaño de la imagen y la dirección de comienzo. La imagen tiene F filas y C columnas (5 y 6 respectívamente, no confundir el orden en que se dan los valores, hay quefijarse en si son filas o columnas). Luego la dirección de un punto cualquiera será la del comienzo de la imagen (pim) mas el número de puntos que hay hasta el que queremos.
Queremos la tercera columna fila cuarta. Luego hay que saltar tres filas completas de  seis columnas.

Vamos a hacerlo detenidamente. Empezemos por la primer fila. pim es la dirección de comienzo de la imagen y *pim es el valor de arriba a la izquierda, el 2. el siguiente valor (un 0) es el primer valor después del principio. De manera que su dirección es la pim+1. Su valor es *(pim+1). ¡OJO! No es lo mismo *(pim+1) (valor de la direccion siguiente a la de comienzo, 0) que *pim+1 (valor de la dirección de comienzo, 2, mas uno, osea 3).
Bien. El último número de la primera fila es un 7. Su posición dentro de la imagen es la sexta de la primera fila. Luego su posición de memoria será la del principio (pim, que como sabemos se cuenta como 0 no como 1) mas 5. Hay que tener mucho cuidado ya que nos interesa la dirección de número, no la siguiente. pim+5 es el principio del número mientras que pim+6 es se lo salta. No hay que olvidar que los números tienen un tamaño.
Bien, si pim+5 es la posición sexta de la primera fila entonces *(pim+5) es el número que se guarda en este sitio, un 7.
Por tanto el primer número de la segunda fila tiene por dirección la pim+6 y el número en si mismo (osea su valor) es *(pim+6).
En nuestro ejemplo tenemos 6 columnas. El primer número de la fila tercera es el de posición C*f, osea, la cantidad de columnas multiplicada por la fila. En este caso pim+12 es lo mismo que pim+6*f donde f es la tercera fila, osea un número 2 ya que contamos desde el 0 y no desde el 1.
De forma general para llegar al comienzo de una fila se multiplica la longitud de la fila (osea la cantidad de puntos que tiene, no la cantidad menos 1)  por el número de fila.
De manera que el principio de la fila 125 de una imagen de 1300x2000 (columnas y filas) es, suponiendo que hemos llamado pim al puntero de la imagen:

pim+125*1300 y el valor de ese punto es el *(pim+125*1300).

Una vez determinado el principio de la fila solo hay que sumar el número de la columna deseada. Si queremos tercera columna fila cuarta será: para llegar a la fila 3 (la cuarta): pim+3*6. Y a esto hay que sumarle hasta llegar a la tercera columna, osea un dos.

Tenemos por tanto:

pim+3*6+2.

Y el valor es

*(pim+3*6+2).

De forma general; Tenemos una imagen de F filas por C columnas. Si queremos el punto que está en la fila f columna c y el puntero al comienzo se llama pim:

pim+f*C+c

y su valor es

*(pim+f*C+c).

Como ya se ha dicho normalmente se emplean punteros e imágenes lineales y no matrices. De manear que no se va a volver a hablar de las matrices.

Acceso rápido

Acceder a un punto de imagen concreto, como hemos visto, requiere realizar una multiplicación y dos sumas. Multiplicar es un proceso lento, muy lento. Además en el procesado habitual de una imagen no habrá que limitarse a localizar un punto sino que habremos de trabajar con la totalidad de ellos, eso suponen muchas multiplicaciones. Operaciones como la convolución exigen esfuerzos de computación aún mayores debido a la gran cantidad de multiplicaciones necesarias. Por todo esto hay una moda entre los matemáticos que consiste en diseñar “algoritmos rápidos” de cálculo. Estos algoritmos consiguen acelerar los cálculos reduciendo el número de multiplicaciones, lo que se consigue normalmente reordenando los números a operar y añadiendo sumas. Pero las sumas son mucho mas rápidas de hacer que las multiplicaciones.
Un ejemplo lo tenemos en el producto de dos binomios, pensemos por ejemplo en el producto de una suma por una diferencia:

(a+b)*(a-b)

Realizando este binomio tenemos:

a*a-a*b+b*a+b*b

Tres multiplicaciones y tres sumas.

Pero como recordaremos de nuestras clases de matemáticas de 7º de básica suma por diferencia es igual a diferencia de cuadrados. O sea:

(a*a+b*b)

Dos multiplicaciones y una suma.

A la hora de localizar un punto podemos  reducir el tiempo de búsqueda eliminando la multiplicación. Para ello se emplea una tabla de inicio.
Una tabla de inicio es una matriz lineal (osea una lista de números) que contiene la dirección de comienzo de cada linea de la imagen.

de esta manera si queremos encontrar el punto de la 5ª columna 3ª fila leerímaos la dirección de comienzo de la 3ª fila y sumaríamos 5.
Esto exige un aumento del gasto de memoria pero se vé ampliamente compensado con la reducción de tiempo que supone evitar la multiplicación.

Supongamos una imagen lineal  dirigida por el puntero *pim. Para definir una lista de punteros a imagen podemos escribir esto:

unsigned char **pte;

Puntero a un puntero de una tabla de entradas. Es un puntero a otro. En total la tabla consta de tantas casillas como filas tengamos. Para localizar un punto debemos sumar a este puntero de la tabla el número de la fila (primera fila es suma 0). Así para encontrar el punto correspondiente a la fila 30 columna 47 tendríamos que escribir:

*(*(pte+30)+47)

De forma general si llamamos pte al puntero de la tabla (ojo que este nombre se lo damos a nuestro gusto, no se trata de parte d ela sintaxis de C) para localizar el punto de fila f columna c escribiríamos:

Para obtener su dirección de memoria:

*(pte+f)+c

Para obtener el valor del punto:

*(*(pte+f)+c)

La reserva de memoria para la tabla sería:

pte=(unsigned char **) malloc(sizeof (unsigned char *)*NF);

Donde NF es el número de filas.

Una primera estructura de imagen

Como se  ha visto emplear una matriz lineal, osea un puntero, mejora las prestaciones respecto de usar una matriz. Sobre todo porque nos permite trabajar con un número de resoluciones mas amplio y de forma dinámica. Pero tiene en su contra que hay que añadir información extra sobre el tamaño de la matriz, ya que de lo contrario no sabríamos donde termian una fila de puntos y donde comienza otra.

Vamos a definir una primera estructura d econtrol para una imagen. Vamos a llamarla canal_8b porque es un canal principal, o sea una componente cuyos puntos se codifican con 8 bits. En principio nos valdría decir lo siguiente:

typedef struct
 {
 int nf;
 int nc;
 unsigned char *pim;
 } canal_8b;

Osea número de filas, número de columnas y puntero al comienzo de la imagen (realmente del canal).
Pero vamos a añdir alguna información extra, por ejemplo, vamos a comenzar por un número entero que nos informe sobre la versión de la estructura. De esta manera podemos hacer un programa que leyendo solo el primer valor de la estructura sea capaz de lanzar los subprogramas adecuados. Así abrimos una puerta a sucesivas mejoras.
Además vamos a emplear una tabla de entrada.
Y cada canal va a tener dos imágenes, no una sola. Aúnque ahora no resulte evidente la necesidad de tener dos imágenes (dos matrices) en cuanto empecemos a ver procesado de imágenes veremos que es indispensable. Por ahora adelantar que de estas dos matrices una, la imagen, guarda la imagen en sí mientras que la otra, la sombra, se emplea para realizar los cálculos sin tener que tocar la primera. Además de esta forma tendremos la posibilidad de deshacer un cálculo si no nos satisface.
Por supuesto añadir una matriz de imagen implica añadir su correspondiente tabla de entradas.

Nuestro canal de 8 bits queda, pues, de la siguiente forma:

typedef struct
 {
 int version;
 int nf;
 int nc;
 unsigned char **pte;
 unsigned char *pic;
 unsigned char **pts;
 undigned char *pis;
 } canal_8b;

Se ha dado el nombre de pic al puntero de la imagen para llamarlos puntero de imagen del canal para diferenciarlo del puntero de imagen de la sombra.

Esta estructura de control, tal y como está definida maneja un canald e imagen de 8 bits. Por ejemplo la componente de un color o una imagen en blanco y negro (o sea en “escala de grises” que quieren que digamos los informáticos).

Vamos a escribir una función que nos crea un canal vacio:

canal_8b *creacanal8b(int f, int c)
{
canal_8b *pc;
int i;

pc=(canal_8b *) malloc(sizeof(canal_8b)); /* Reserva sitio para la estructura de control. */
if (!pc) return(0); /* Si hay un fallo vuelve dando un error. */

pc->pte=(unsigned char **) malloc(sizeof(unsigned char *)*f); /* Reservamos sitio para f punteros. Osea para la tabla de entrada de la imagen del canal. */
if(!pc->pte) return(0); /* Si algo va mal devuelve un error. */

pc->pic=(unsigned char *) malloc(sizeof(unsigned char)*f*c); /* Reservamos sitio para la componente de imagen. */
if(!pc->pic) return(0); /* Si algo va mal devuelve un error. */

pc->pts=(unsigned char **) malloc(sizeof(unsigned char *)*f); /* Reservamos sitio para f punteros. Osea para la tabla de entrada de la sombra del canal. */
if(!pc->pte) return(0); /* Si algo va mal devuelve un error. */

pc->pis=(unsigned char *) malloc(sizeof(unsigned char)*f*c); /* Reservamos sitio para la sombra de la componente. */
if(!pc->pis) return(0); /* Si algo va mal devuelve un error. */

/* Ahora que estamos seguros de que se han creado las estructuras cargamos los valores. */

pc->version=1; /* Versión 1. */
pc->nf=f;  /* Número de filas. */
pc->nc=c;  /* Número de columnas. */

/* Ahora vamos a situar los punteros de las tablas para que enganchen con las imágenes. */

for (i=0;i<f;i++) /* Por cada fila... */
 {
  /* ...ponemos la dirección de comienzo de cada fila en la tabla correspondiente. */
 *(pc->pti+i*f)=pc->pic+i*f;
 *(pc->pts+i*f)=pc->pis+i*f;
 }
return(pc);
}

La función es bien simple aunque manifiestamente mejorable. El tipo devuelto es un puntero a una estructura de canal. Osea que crea el canal y nos dice donde lo ha dejado, en que posición de memoria. Después reserva los sitios para las matrices y las tablas de entrada. Si hay algún error simplemente devuelve un puntero nulo. Obsérvese que, tal y como está escrita no hay forma de saber donde se produjo el error, exñactamente que sitio estaba pidiendo cuando falló. La gestión de este tipo de errores podría hacerse escribindo en un registro o en un indicador que dijera qué ha fallado, pero por ahora nos abstenemos. Simplemente indicamos que ha habido un error y volvemos inmediatamente de la función.
Por cierto, ya se que los muy puristas criticarán que tengo varias salidas (return) en distintos puntos y que lo académicamente correcto es poner un solo return al final. Pero es que yo intento hacer programas que funcionen, no que ganen concursos de belleza. La eficiencia se mide sopesando los recursos empleados y los resultados obtenidos no la adscripción a un estilo de scribir que esté mas o menos de moda.

Bién, tras reservar toda la memoria necesaria, y puesto que ya sabemso que no ha habido ningún error podemos rellenar las casilas de la versión y tamaño de la imagen.
Por último que hay que escribir las tablas de entrada, de manera que ponemos cada casilla de estas apuntando al comienzo de la fila correspondiente de la matriz, imagen por un lado, sombra por otro.

La función de vuelve la dirección de la estructura de control. Solo queda abrir el fichero de la foto, decodificarla y cargar el canal correspondiente a partir de la dirección pc->pic

Algo más adelante modificaremos la estructura de control mostrada creando la versión 2.

Una imagen multicanal

Lo anteriormente descrito es un solo canal. Por sí solo puede emplearse para controlar una película en blanco y negro (en el sentido fotográfico). Una imagen es, por sí, la reunión de varios canales cada uno de los cuales especifica una componente. De manera que podríamos definir ahora una imagen como una matriz de punteros a canales. Por ejemplo así:

canal_8b *imagenRGB[3], *imagenCMYK[4];

O bien meter cada canal en una nueva estructura como esta:

typedef struct
 {
 canal_8b rojo;
 canal_8b verde;
 canal_8b azul;
 } imRGB_8b;

Esta forma de  poner los canales nos evita tener que reservar la memoria como lo hicimos en la función creacanal_8b. Estas estructuras, de todas maneras, permiten trabajar con una cantidad fija de canales, por lo que necesitamos una estructura de imagen por cada modo de color. Además, tal y como se ha definido el canal hay información redundante: en efecto hemos escrito 3 veces el ancho y el alto de la imagen. Una alternativa para crear una iamgen sería dejar un número indeterminado de canales y modificar la definición de estos. Por ejemplo así:

typedef struct
 {
 int version;
 int nfil;
 int ncol;
 int ncan;
 canal_8b *canales;
 } imagen_8b;

Donde versión sería el núemro de versión del tipo de imagen, para poder hacer modificaciones en las versiones posteriores y guardar la compatibilidad. nfil el número de filas, ncol el número de columnas y ncan el número de canales. Ahora hemos creado una matriz dinámica de canales, lo que nos permite tener un número indeterminado de ellos.

Puesto que la información sobre el tamaño está en la estructura de imagen podemos quitarla de la estructura de canal, con lo que el canal de 8 bits, en la versión 2 quedaría así:

typedef struct
 {
 int version; /¡Versión 2!.*/
 unsigned char **pte;
 unsigned char *pic;
 unsigned char **pts;
 undigned char *pis;
 } canal_8b;

y en la función de creación del canal habría que escribir un 2 en vez de un 1 cuando se especifica la versión y eliminar las líneas donde se da valor a la cantidad de filas y de columnas.

En definitiva la función creacanal_8b quedaría así:

canal_8b *creacanal8b(int f, int c) /* Versión 2. */
{
canal_8b *pc;
int  i;

pc=(canal_8b *) malloc(sizeof(canal_8b)); /* Reserva sitio para la estructura de control. */
if (!pc) return(0); /* Si hay un fallo vuelve dando un error. */

pc->pte=(unsigned char **) malloc(sizeof(unsigned char *)*f); /* Reservamos sitio para f punteros. Osea para la tabla de entrada de la imagen del canal. */
if(!pc->pte) return(0); /* Si algo va mal devuelve un error. */

pc->pic=(unsigned char *) malloc(sizeof(unsigned char)*f*c); /* Reservamos sitio para la componente de imagen. */
if(!pc->pic) return(0); /* Si algo va mal devuelve un error. */

pc->pts=(unsigned char **) malloc(sizeof(unsigned char *)*f); /* Reservamos sitio para f punteros. Osea para la tabla de entrada de la sombra del canal. */
if(!pc->pte) return(0); /* Si algo va mal devuelve un error. */

pc->pis=(unsigned char *) malloc(sizeof(unsigned char)*f*c); /* Reservamos sitio para la sombra de la componente. */
if(!pc->pis) return(0); /* Si algo va mal devuelve un error. */

/* Ahora vamos a situar los punteros de las tablas para que enganchen con las imágenes. */

for (i=0;i<f;i++) /* Por cada fila... */
 {
  /* ...ponemos la dirección de comienzo de cada fila en la tabla correspondiente. */
 *(pc->pti+i*f)=pc->pic+i*f;
 *(pc->pts+i*f)=pc->pis+i*f;
 }
return(pc);
}

Por su parte podemos crear la estructura de contro de una imagen de la siguiente manera:

imagen_8b *creaimagen(int nf, int nc, int canales)
{
imagen_8b *im;
int i;

im=(imagen_8b *) malloc(sizeof(imagen_8b));
if (!im) return(0);

for (i=0;i<canales;i++) /* Creamos los canales. */
 {
 /* Cargamos el canal y lo comprobamos en una sola línea. Solo hay un signo de igual porque estamos asignando el canal creado a la tabla de canales. La comparación con 0 (la negación indicada con la admiración) solo se refiere a si el valor almacenado (el devuelto por creacanal8b) es 0. */
 if (!im->canales+i=creacanal8b(nf,nc)) return(0);
 }
/* Si no ha habido fallos llegamos hasta este punto. */
im->version=2;
im->nfil=nf;
im->ncol=nc;
im->ncan=canales;

return(im);
}

La definición de la estructura de imagen aún deberemos cambiarla. En concreto habrá que añadir información sobre los canales alfa. Pero este será tema de un futuro capítulo.



 
Ir al raiz.
 
Ir al índice de temas.
 
Ir a los enlaces.