|
|
|
Por Francisco Bernal
Rosso.
|
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.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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.
|
|
|
|
|
|