Вы находитесь на странице: 1из 37

INFORMÁTICA GRÁFICA

Tema 10: Gráficos Planos en OpenGL

Gonzalo Cascón Barbero


Elena Jorge Rico
Índice

Índice _______________________________________________________________ 2
Tabla de figuras _______________________________________________________ 3
1 Introducción________________________________________________________ 4
2 Mapas de bits _______________________________________________________ 5
Concepto _________________________________________________________________ 5
Ejemplo de bitmap con OpenGL _____________________________________________ 5
Aplicaciones: Generación de caracteres y fuentes _______________________________ 9
Generación de caracteres __________________________________________________________9
Generación de fuentes ___________________________________________________________11
3 Imágenes __________________________________________________________ 14
Lectura de píxeles de framebuffer a memoria _________________________________ 14
Escritura de píxeles de memoria a framebuffer ________________________________ 17
Copia de píxeles dentro del framebuffer ______________________________________ 18
5 El Pipeline de renderizado de OpenGL _________________________________ 22
6 Lectura y Dibujo de rectángulos de píxeles _____________________________ 25
Proceso de dibujo de rectángulos de píxeles ___________________________________ 25
Proceso de lectura de rectángulos de píxeles ___________________________________ 26
7 Formatos de imagen _______________________________________________ 28
El formato BMP __________________________________________________________ 28
8 Manejo de ficheros BMP en OpenGL __________________________________ 31
Tabla de figuras

Ilustración 1.-Bitmap de ejemplo. ________________________________________________________6


Ilustración 2.-Bitmap como matriz de 0s y 1s _______________________________________________6
Ilustración 3.-Estructura GLubyte _______________________________________________________7
Ilustración 4.-Código para dibujar un bitmap ______________________________________________7
Ilustración 5.- Resultado de la ejecución del código del bitmap_________________________________9
Ilustración 6.- Carácter A como bitmap ___________________________________________________9
Ilustración 7.- Carácter A como estructura GLubyte ________________________________________10
Ilustración 8.- Dos bitmaps del carácter A superpuestos _____________________________________10
Ilustración 9.- Dos bitmaps del carácter A superpuestos en código _____________________________11
Ilustración 10.- Creación de una fuente mediante DLs_______________________________________12
Ilustración 11.- Método para imprimir una cadena con la fuente creada ________________________12
Ilustración 12.- Ejemplo de uso de la fuente creada _________________________________________13
Ilustración 13.- Resultado de ejecutar el código del ejemplo de la fuente ________________________13
Ilustración 14.- Tipos de datos para glReadPixels() o glDrawPixels() __________________________15
Ilustración 15.- Formatos de Pixels para glReadPixels() o glDrawPixels()_______________________16
Ilustración 16.- Fragmento de código para hacer capturas de pantalla__________________________17
Ilustración 17.- Fragmento de código para dibujar un tablero de ajedrez ________________________18
Ilustración 18.- Código de un programa para copiar píxeles y hacer zoom_______________________21
Ilustración 19 Pipeline de renderizado de OpenGL _________________________________________23
Ilustración 20 Dibujo de píxeles con glDrawPixeles() _______________________________________25
Ilustración 21 Lectura de píxeles con glReadPixeles() _______________________________________27
1 Introducción
La pantalla de un ordenador sólo es capaz de visualizar gráficos en dos
dimensiones (2D). Para conseguir que los objetos que en ella aparecen tengan
volumen es necesario el uso de técnicas que nos hagan creer que éstos tienen
3D. Estas técnicas serán el uso de focos de luz, sombreado de colores,
sombras, etc.

Los gráficos planos son útiles en OpenGL para combinarlos con los de
3D para la aplicación de texturas a los objetos. Pero no solo esto, también se
utilizan para la visualización y creación de mapas de bits, mapas de píxeles y
fuentes de texto. En ocasiones, la creación de estas imágenes desde OpenGL
supone una tarea demasiado compleja, por eso también existen librerías que
nos permiten importar imágenes ya creadas para incorporarlas a la escena.
2 Mapas de bits

Concepto

Los mapas de bits, también conocidos como bitmaps, son imágenes


bicolor usadas para dibujar de forma rápida caracteres o símbolos (iconos,
cursores, fuentes de texto, etc.). Emplean un único bit de información por cada
píxel, constituyendo así un array rectangular de 0’s y 1’s. En la posición del
array en que aparezca un 0 se estará indicando transparencia, esto es, no se
modifica el contenido del píxel que le corresponda. En la posición del array en
que aparezca un 1 se estará indicando que habrá que dibujar usando el color y
atributos de iluminación del material actual.

A los mapas de píxeles también se les suele llamar mapas de bits. Los
dos pueden ser vistos de una manera abstracta como un array rectangular de
píxeles. Sin embargo los mapas de píxeles suelen tener más de dos colores y
se emplean como imágenes de fondo o texturas. En este documento
consideraremos los mapas de píxeles como imágenes y hablaremos de ellos
en el apartado 3.

Ejemplo de bitmap con OpenGL

En este apartado vamos a ver como se crearía un bitmap directamente


desde una aplicación con OpenGL.
Imaginemos que queremos dibujar un “smiley” de 16x16 bits como el
que se muestra en la siguiente figura.
Ilustración 1.-Bitmap de ejemplo.

Como hemos dicho el bitmap es una matriz de 0’s y 1’s donde los bits
que están a 0 indican ausencia de color y los bits que estan a 1 indica que se
representen los píxeles correspondientes. Por lo tanto tendríamos una matriz
como la que se muestra a continuación:

B y t e 1 B y t e 2
0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7
1 0 0 0 0 0 0 1 1 1 1 0 0 0 0 0 0 0x03, 0xc0
2 0 0 0 0 1 1 1 1 1 1 1 1 0 0 0 0 0x0f, 0xf0
3 0 0 0 1 1 1 1 1 1 1 1 1 1 0 0 0 0x1f, 0xf8
4 0 0 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0x3f, 0xfc
5 0 1 1 1 0 0 1 1 1 1 0 0 1 1 1 0 0x73,0xce
6 0 1 1 1 0 0 1 1 1 1 0 0 1 1 1 0 0x73, 0xcc
7 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0xff, 0xff
8 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0xff, 0xff
9 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0xff, 0xff
10 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0xff, 0xff
11 0 1 1 0 1 1 1 1 1 1 1 1 0 1 1 0 0x6f, 0xf6
12 0 1 1 1 0 1 1 1 1 1 1 0 1 1 1 0 0x77, 0xee
13 0 0 1 1 1 0 0 1 1 0 0 1 1 1 0 0 0x39,0x9c
14 0 0 0 1 1 1 1 0 0 1 1 1 1 0 0 0 0x1e,0x78
16 0 0 0 0 1 1 1 1 1 1 1 1 0 0 0 0 0x0f,0xf0
16 0 0 0 0 0 0 1 1 1 1 0 0 0 0 0 0 0x03,0xc0
Ilustración 2.-Bitmap como matriz de 0s y 1s
El primer paso para crear el bitmap será definir una estructura de tipo
GLubyte. El mapa será una matriz bytes sin signo. En OpenGL los mapas de
bits se definen invertidos, esto es, se almacenan desde la parte inferior hasta la
superior. Es decir, el bit más significativo del primer byte del array GLubyte
corresponde a la parte esquina inferior izquierda del bitmap. Veamos un
ejemplo de cómo se definiría este bitmap.

static GLubyte smiley[] = /* Cara sonriente de 16x16 */


{
0x03, 0xc0, /* **** */
0x0f, 0xf0, /* ******** */
0x1e, 0x78, /* **** **** */
0x39, 0x9c, /* *** ** *** */
0x77, 0xee, /* *** ****** *** */
0x6f, 0xf6, /* ** ******** ** */
0xff, 0xff, /* **************** */
0xff, 0xff, /* **************** */
0xff, 0xff, /* **************** */
0xff, 0xff, /* **************** */
0x73, 0xce, /* *** **** *** */
0x73, 0xce, /* *** **** *** */
0x3f, 0xfc, /* ************ */
0x1f, 0xf8, /* ********** */
0x0f, 0xf0, /* ******** */
0x03, 0xc0, /* **** */
};

Ilustración 3.-Estructura GLubyte


Como vemos es una matriz de 32 bytes sin signo, del tipo Glubyte.
Una vez que tenemos la matriz GLubyte creada ya podemos dibujar
nuestro bitmap.

void display(void){
glClear(GL_COLOR_BUFFER_BIT);
//Color de fondo blanco
glClearColor(1.0,1.0,1.0,1.0);

//Smiley verde
glColor3f (0.0, 1.0, 0.0); //Establecemos color de dibujo
glRasterPos2i (50, 100); //Fijamos posición y color de dibujo
glBitmap(16,16,0,0,0,0,smiley); //Dibujamos el bitmap

//Smiley rojo
glColor3f (1.0, 0.0, 0.0);
glRasterPos2i (100, 100);
glBitmap(16,16,0,0,0,0,smiley);

//Smiley azul
glColor3f (0.0, 0.0, 1.0);
glRasterPos2i (150, 100);
glBitmap(16,16,0,0,0,0,smiley);
glFlush();
}

Ilustración 4.-Código para dibujar un bitmap


La llamada glClearColor nos permite establecer el color del fondo. Los
píxeles que tengan el valor 0 en nuestro bitmap se pintarán de este color. Su
prototipo es el siguiente:
Void glClearColor(GLclampf red, GLclampf green, GLclampf blue, GLclampf
alpha) Como se puede apreciar sus parámetros son los valores RGBA.

La llamada glColor3f nos permite establecer el color de dibujo. Los


píxeles que tengan el valor 1 en nuestro bitmap se pintarán de este color. Su
prototipo es el siguiente:
Void glColor{3/4}{b/d/f/i/s/ub/ui/us}(GL… red, GL… green, GL… blue, GL…
alpha)
Este método cambia el valor de la variable GL_CURRENT_RASTER_COLOR
que es la que establece el color actual de dibujo.
Dependiendo de la función los parámetros serán de tipo GLbyte (b), GLdouble
(d), GLfloat (f), GLint (i)…

La llamada glRasterPos2i nos permite establecer la posición en pantalla


donde se va a dibujar. Además de lo anterior, también sirve para fijar el color
establecido previamente por glColor. Por lo tanto deberemos hacer siempre
antes la llamada a glColor y después a glRasterPos. El prototipo de esta
función es el siguiente:
Void glRasterPos{2/3/4}{s/i/f/d}(GL… x, GL… y, GL… z, GL… w)
La posición viene dada por las coordenadas anchura, altura y profundidad
(x,y,z) del punto dentro de la ventana de dibujo, la cuarta coordenada es una
coordenada asociada al color y a las características de las texturas y no es una
coordenada del espacio. La función glRasterPos cambia el valor de la variable
GL_CURRENT_RASTER_POSITION_VALID estableciéndola a False si la
posición nueva esta fuera del área de dibujo y a True en caso contrario.

Por último, una vez que hemos establecido los colores de fondo, de
dibujo y la posición sobre la que dibujar, solo queda dibujar el mapa de bits.
Esto se hace con la función Void glBitmap (Glsizei ancho, Glsizei alto, Glfloat
xorig, Glfloat yorig, Glfloat xmov, Glfloat ymov, const Glubyte *bitmap). Los dos
primeros parámetros indican la anchura y altura del bitmap (en nuestro ejemplo
16 x 16). Los dos siguientes la coordenadas (en píxeles) de la posición de inicio
de dibujo del bitmap. Los parámetros quinto y sexto el desplazamiento (en
píxeles) desde la posición de inicio del bitmap actual hasta la posición de inicio
del siguiente bitmap (teniendo en cuenta siempre que la posición de inicio de
un bitmap es la esquina inferior izquierda). Si la variable
GL_CURRENT_RASTER_POSITION_VALID tiene el valor False se ignorará la
llamada a la función glBitmap.

Veamos cual sería el resultado de la ejecución del código mostrado más


arriba.
Ilustración 5.- Resultado de la ejecución del código del bitmap

Aplicaciones: Generación de caracteres y fuentes


Generación de caracteres

En el punto anterior hemos visto como se puede crear un bitmap sencillo


y mostrarlo por pantalla. Para dibujar bitmaps de mayor tamaño y complejidad
evidentemente no se utiliza este método porque la tarea sería tediosa. Por ello
OpenGL contiene bibliotecas de funciones que permiten importar imágenes
BMP ya creadas. La creación de bitmaps es una tarea que ha quedado
relegada a la creación de caracteres.
Visto el punto anterior sería fácil crear un carácter con un bitmap,
veamos un ejemplo.

0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7
0x0C, 0x00
0x1E, 0x00
0x3F, 0x00
0x73, 0x80
0xE1, 0xC0
0xC0, 0xC0
0xC0, 0xC0
0xC0, 0xC0
0xFF, 0xC0
0xFF, 0xC0
0xC0, 0xC0
0xC0, 0xC0
0xC0, 0xC0
0xC0, 0xC0
0x00, 0x00
0x00, 0x00
Ilustración 6.- Carácter A como bitmap
Esta imagen representa un bitmap de 16x16 que contiene un carácter A
de 10 píxeles de ancho y 14 píxeles de alto. Para crear un bitmap con OpenGL
que represente este carácter hay que tener en cuenta que se empieza a dibujar
por la esquina inferior izquierda, por tanto hay que invertir el orden en el que
introducimos los datos en el array de tipo GLubyte. Podemos ver lo que
acabamos de decir en el siguiente recuadro:

static GLubyte LetraA[] =


{
0x00, 0x00,
0x00, 0x00,
0xC0, 0xC0,
0xC0, 0xC0,
0xC0, 0xC0,
0xC0, 0xC0,
0xFF, 0xC0,
0xFF, 0xC0,
0xC0, 0xC0,
0xC0, 0xC0,
0xC0, 0xC0,
0xE1, 0xC0,
0x73, 0x80,
0x3F, 0x00,
0x1E, 0x00,
0x0C, 0x00
};
Ilustración 7.- Carácter A como estructura GLubyte
Debemos tener en cuenta que todo bitmap debe tener un ancho que sea
múltiplo de 8 porque los elementos de la matriz de tipo GLubyte son conjuntos
de 8 bits. Esto no quiere decir que nuestro carácter tenga que tener
forzosamente un ancho que sea múltiplo de 8 bits; podemos asignarle el ancho
que queramos y dejar en blanco el espacio que sobre en la parte derecha.
Después, a la hora de utilizar los caracteres, podemos superponer sus bitmaps
dejando el espacio que nos interese entre caracteres.

Ilustración 8.- Dos bitmaps del carácter A superpuestos


En el ejemplo vemos como superponer 2 bitmaps de la letra A dejando
un espacio entre caracteres de 2 píxeles. Esto traducido a código sería lo
mismo que esto otro:

glBitmap (16, 16, 0.0, 0.0, 12.0, 0.0, LetraA);


glBitmap (16, 16, 0.0, 0.0, 0.0, 0.0, LetraA);
Ilustración 9.- Dos bitmaps del carácter A superpuestos en código
En el quinto parámetro de la función glBitmap indicamos el
desplazamiento desde el inicio del bitmap que vamos a dibujar hasta el inicio
del siguiente (en el eje x). Por tanto si ponemos un desplazamiento de 12
píxeles, como nuestro carácter tiene un ancho de 10 estamos dejando una
separación de 2 píxeles entre caracteres.

Generación de fuentes

Una fuente consiste en un conjunto de caracteres, donde cada carácter


tiene un número identificativo (usualmente el código ASCII) y un método de
dibujo.
Para un conjunto de caracteres ASCII estándar, la letra mayúscula A es
el número 65, B es el 66, y así sucesivamente. La cadena “DAB” se podría
representar por tres números 68, 65, 66.
Para la definición de fuentes completas resulta de gran utilidad el uso de
las Listas de Pantalla o Display List (de ahora en adelante DL). Mediante el uso
de las DL podemos definir un procedimiento de dibujo para cada letra, de modo
que cuando queramos escribir una frase únicamente con saber el código de
cada carácter podamos acceder al procedimiento que lo dibuja.
Podemos definir un DL como un procedimiento que se define una sola
vez con un nombre o identificador único y que incluye una secuencia de
comandos. Este procedimiento puede ser usado las veces que se quiera.
Cuando es creado sus comandos son precompilados y, si es posible, llevados
a memoria, aumentando la velocidad de ejecución de nuestro programa. Si la
secuencia que incluye es utilizada muchas veces nuestra aplicación aumentará
el rendimiento considerablemente.
La función GLuint glGenLists(GLsizei range) genera el numero de DL
vacías indicado por su parámetro (siempre un número positivo), devolviendo un
entero que representa al índice de la primera DL dentro del conjunto de DL. Así
si range es 5 se crea un conjunto de 5 DL y se recibe un entero con valor, por
ejemplo, 3. Las listas se identificarán del 3 al 7, ambos inclusive. Con este
identificador se crearán cada uno de los DL. Este identificador debe conocerse
fuera de la función donde se obtiene, por tanto es obligatorio que se guarde en
una variable global. Un DL solo puede ser creado dentro de la función de
iniciación.
La función void glNewList(GLuint list, GLenum mode) crea un Nuevo
DL. El parámetro list contiene el identificador del DL que se va a crear, y el
parámetro mode puede tomar dos posibles valores: GL_COMPILE y
GL_COMPILE_AND_EXECUTE. Si toma el primer valor los comandos dentro
del DL son únicamente compilados. Si toma el segundo valor los comandos
son compilados y ejecutados al crear el DL. A continuación de esta función se
escribe la secuencia de comandos que debe ejecutar el DL (incluso se puede
llamar a un DL dentro de otro DL). Al finalizar dicha consecución de ordenes se
debe llamar a la función void glEndList(void void).
La función void glCallList(GLuint list) ejecuta el DL identificado por list.
Por último la función void glDeleteLists(GLuint list, GLsizei range) borra el
número de listas especificado por range comenzando por el que tiene el
identificador dado por list.

Veamos un ejemplo de cómo se crearía una fuente haciendo uso de las


Display List:

GLuint desplazamiento;
void fuenteDeEjemplo(void){
GLuint i, j;
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
desplazamiento = glGenLists (128);
/*El desplazamiento es el valor ASCII de la letra*/
for (i = 0,j = 'A'; i < 26; i++,j++) {
glNewList(desplazamiento + j, GL_COMPILE);
glBitmap(8, 13, 0.0, 2.0, 10.0, 0.0, alfabeto[i]);
glEndList();
}
glNewList(desplazamiento + ' ', GL_COMPILE);
glBitmap(8, 13, 0.0, 2.0, 10.0, 0.0, espacio);
glEndList();
}

Ilustración 10.- Creación de una fuente mediante DLs


La variable “alfabeto[]” es un array de tipo GLubyte que cotiene todos los
caracteres del alfabeto. Cada carácter es de 8 pixels de ancho por 13 de alto.
Creamos un DL para cada una de las letras del alfabeto con un desplazamiento
igual a su código ASCII sobre la base. Como se puede ver, lo que hacemos
dentro de la DL es definir el procedimiento para dibujar cada una de las letras
llamando a la función glBitmpap. Dejamos una separación horizontal entre
caracteres de 2 píxeles y una separación vertical también de 2 píxeles.

Una vez que tenemos definida la fuente ya sólo necesitamos un método


para imprimir cadenas:

void imprimirCadena(char *s){


glPushAttrib (GL_LIST_BIT);
glListBase(desplazamiento);
glCallLists(strlen(s), GL_UNSIGNED_BYTE, (GLubyte *) s);
glPopAttrib ();
}

Ilustración 11.- Método para imprimir una cadena con la fuente creada
Mediante la llamada glCallList podemos invocar las DL que hemos
definido anteriormente. El procedimiento obtiene el código ASCCII de cada
carácter de la cadena y se lo suma a la variable “desplazamiento”, que contiene
el índice de la primera DL del conjunto de DLs que habíamos definido en el
ejemplo anterior. De esta forma podemos acceder a la DL de cada una de las
letras del alfabeto.

Por último veamos como se utilizaría esta fuente:

void display(void){
GLfloat black[3] = { 0.0, 0.0, 0.0 };
GLfloat red[3] = { 1.0, 0.0, 0.0 };
glClear(GL_COLOR_BUFFER_BIT);
glColor3fv(red);
glRasterPos2i(20, 60);
imprimirCadena("ESTO ES UN EJEMPLO DE FUENTE");
glColor3fv(black);
glRasterPos2i(20, 40);
imprimirCadena("ABCDEFGHIJKLMNOPQRSTUVWXYZ");
glFlush ();
}

Ilustración 12.- Ejemplo de uso de la fuente creada


El resultado de la ejecución de este código sería el siguiente:

Ilustración 13.- Resultado de ejecutar el código del ejemplo de la fuente


3 Imágenes
Una imagen es un archivo de características similares a las de un
archivo bitmap. Una imagen no se limita a un bit por píxel, sino que es capaz
de almacenar mayor información por píxel. Así por ejemplo, una imagen puede
contener un color entero (R, G, B, A) en un único píxel.

Tienen diversas fuentes. Una imagen puede proceder de:


• Fotografías digitalizadas con escáner.
• Imágenes generadas en pantalla por un programa gráfico utilizando
hardware gráfico y recuperada píxel a píxel.
• Un programa software que generó la imagen en memoria píxel a píxel.

Las imágenes tipo fotos se generan a partir de buffers de color. Sin


embargo, se pueden leer o escribir áreas rectangulares de datos de píxeles de
o hacia buffers de profundidad (depth buffers) o buffers de paletas (stencil
buffers). Además, para simplificar su presentación en pantalla, se pueden
utilizar las imágenes para hacer mapas de texturas, en cuyo caso se colocan
en polígonos que son renderizados en la pantalla de la forma habitual.

Lectura de píxeles de framebuffer a memoria

Para leer un array rectangular de píxeles de un framebuffer y guardarlo


en memoria utilizamos la siguiente función:

void glReadPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum


format, GLenum type, GLvoid *pixels);

Lee datos del rectángulo del framebuffer cuya esquina inferior izquierda
está en (x, y) y cuyas dimensiones son width y height y los guarda en el array
apuntado por pixels. format indica la clase de elementos de datos del píxel que
son leídos (un índice o un valor de componente R, G, B, o A), y type indica el
tipo de dato de cada elemento.
Ilustración 14.- Tipos de datos para glReadPixels() o glDrawPixels()
Ilustración 15.- Formatos de Pixels para glReadPixels() o glDrawPixels()

Si se está usando glReadPixels() para obtener RGBA o información de


índice de color, se necesitaría aclarar a qué buffer se está intentando acceder.
Por ejemplo, si se tiene una ventana de doble buffer, se necesita especificar si
se están leyendo datos del buffer superior o del buffer inferior. Para controlar el
buffer de lectura actual, se llama a glReadBuffer().

En la siguiente figura podemos ver un ejemplo de una función que


realiza una captura de la pantalla de la aplicación haciendo uso de las
funciones explicadas anteriormente.
En primer lugar obtenemos las coordenadas de la ventana actual y sus
dimensiones. Después obtenemos el buffer de la pantalla con glReadBuffer().
Por último con glReadPixels podemos obtener el rectángulo de píxeles que nos
interesa, a partir de las coordenadas que habíamos obtenido previamente de la
ventana de la aplicación.
Con la información obtenida podríamos implementar una función como
por ejemplo WriteTga que nos permitiera guardar esos datos que tenemos en
memoria en un archivo TGA.
bool SaveScreenGrab(const char* filename) {

//Obtenemos los parametros de la ventana


unsigned sw = glutGet(GLUT_WINDOW_WIDTH);
unsigned sh = glutGet(GLUT_WINDOW_HEIGHT);
unsigned bpp = glutGet(GLUT_WINDOW_RGBA) ? 4 : 3;
GLenum format = (bpp==4) ? GL_RGBA : GL_RGB;

// Reservamos memoria para almacenar los datos de la imagen


unsigned char* pdata = new unsigned char[sw*sh*bpp];

//Leemos del buffer de la pantalla


glReadBuffer(GL_FRONT);

//Leemos los pixeles que nos interesan


glReadPixels(0,0,sw,sh,format,GL_UNSIGNED_BYTE,pdata);

//Guardamos los datos como un archivo tga


bool ret = WriteTga(filename,sw,sh,bpp,pdata);

//Liberamos memoria
delete [] pdata;

return ret;
}
Ilustración 16.- Fragmento de código para hacer capturas de pantalla

Escritura de píxeles de memoria a framebuffer

Para dibujar un rectangulo de píxeles desde memoria a un framebuffer


utilizamos la siguiente función:

void glDrawPixels(GLsizei width, GLsizei height, GLenum format, GLenum


type, const GLvoid *pixels);

Dibuja un rectángulo de datos de píxel de dimensiones width y height. El


rectángulo de píxeles se dibuja con la esquina inferior izquierda en la posición
de raster actual. format y type tienen el mismo significado que con
glReadPixels() (ver tablas anteriores). El array apuntado por píxeles contiene
los datos de píxel que se quiere dibujar. Si la posición del raster actual es
inválida, no se dibuja nada, y la posición del raster permanece inválida.

El siguiente ejemplo, utiliza glDrawPixels() para dibujar un rectángulo


de píxeles en la esquina inferior izquierda de una ventana. makeCheckImage()
crea un array RGB de 64×64 de una imagen de tablero de ajedrez en blanco y
negro. glRasterPos2i(0, 0) coloca la esquina inferior izquierda de la imagen.

#define checkImageWidth 64
#define checkImageHeight 64
GLubyte checkImage[checkImageHeight][checkImageWidth][3];
void makeCheckImage(void){
int i, j, c;
for (i = 0; i < checkImageHeight; i++){
for (j = 0; j < checkImageWidth; j++){
c = ((((i&0x8)==0)^((j&0x8))==0))*255;
checkImage[i][j][0] = (GLubyte) c;
checkImage[i][j][1] = (GLubyte) c;
checkImage[i][j][2] = (GLubyte) c;
}
}
}

void init(void){
glClearColor (0.0, 0.0, 0.0, 0.0);
glShadeModel(GL_FLAT);
makeCheckImage();
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
}

void display(void){
glClear(GL_COLOR_BUFFER_BIT);
glRasterPos2i(0, 0);
glDrawPixels(checkImageWidth, checkImageHeight, GL_RGB,
GL_UNSIGNED_BYTE, checkImage);
glFlush();
}

Ilustración 17.- Fragmento de código para dibujar un tablero de ajedrez

Existe una función llamada glDrawBuffer(), que se utiliza para controlar


el buffer de escritura actual. Hay ocasiones en que cuando se utiliza
glDrawPixels() para escribir RGBA o información de índices de color, se puede
tener que controlar este buffer.

Copia de píxeles dentro del framebuffer

Para copiar un array rectangular de píxeles de una parte del framebuffer


a otra tenemos la siguiente función:
void glCopyPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum
buffer);

Copia datos de píxeles del rectángulo del framebuffer cuya esquina


inferior izquierda es (x, y) y cuyas dimensiones son width y height. Los datos
son copiados a una nueva posición cuya esquina inferior izquierda está dada
por la posición actual del raster. buffer es GL_COLOR, GL_STENCIL, o
GL_DEPTH, especificando el framebuffer que se utiliza. glCopyPixels() se
comporta de manera similar a glReadPixels() seguido de glDrawPixels(), con
la siguiente interpretación del buffer al parámetro format:

• Si buffer es GL_DEPTH o GL_STENCIL, entonces se utiliza


GL_DEPTH_COMPONENT o GL_STENCIL_INDEX , respectivamente
como parámetros en format.
• Si buffer es GL_COLOR, format es GL_RGBA o GL_COLOR_INDEX,
dependiendo si el sistema está en modo RGBA o índice de color.

Hay que señalar que no es necesario un parámetro format o data en


glCopyPixels(), ya que el dato nunca es copiado en la memoria del
procesador. El buffer de lectura (fuente) y el buffer de destino de
glCopyPixels() son especificados con glReadBuffer() y glDrawBuffer()
respectivamente.

Para las tres funciones, las conversiones exactas de los datos que se
envían o se reciben del framebuffer dependen de los modos que estén activos
en ese momento.

A continuación mostramos el código de un programa que copia un


rectángulo de píxeles de una zona de la pantalla a otra y le aplica un zoom.
Consiste en un ejemplo simple en el cual se hace una copia en el lugar donde
esté señalando el cursor al picar con el botón izquierdo del ratón. Para hacer el
zoom se utiliza la función void glPixelZoom(GLfloat xfactor, GLfloat yfactor)
cuyos parámetros especifican los factores de zoom en los ejes x e y
respectivamente.
Con las teclas ‘+’ y ‘-’ podemos configurar el factor de aumento para el
zoom y con la tecla ‘R’ reseteamos el factor de zoom a 1.

#include "glut.h"
#include <stdlib.h>
#include <stdio.h>

#define checkImageWidth 64
#define checkImageHeight 64
GLubyte checkImage[checkImageHeight][checkImageWidth][3];

static GLdouble zoomFactor = 1.0;


static GLint height;
static bool hayCopia=false;
static int xCoord=-1,yCoord=-1;
void makeCheckImage(void)
{
int i, j, c;

for (i = 0; i < checkImageHeight; i++) {


for (j = 0; j < checkImageWidth; j++) {
c = ((((i&0x8)==0)^((j&0x8))==0))*255;
checkImage[i][j][0] = (GLubyte) c;
checkImage[i][j][1] = (GLubyte) c;
checkImage[i][j][2] = (GLubyte) c;
}
}
}

void copia (int x,int y)


{
GLint screeny;
if(hayCopia)
{
screeny = height - (GLint) y;
glRasterPos2i (x, y);
glPixelZoom (zoomFactor, zoomFactor);
glCopyPixels (0, 0, checkImageWidth, checkImageHeight,
GL_COLOR);
glPixelZoom (1.0, 1.0);
glFlush ();
}
}

void init(void)
{
glClearColor (0.0, 0.0, 0.0, 0.0);
glShadeModel(GL_FLAT);
makeCheckImage();
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
}

void display(void)
{
glClear(GL_COLOR_BUFFER_BIT);
glRasterPos2i(0, 0);
glDrawPixels(checkImageWidth, checkImageHeight, GL_RGB,
GL_UNSIGNED_BYTE, checkImage);
copia (xCoord,yCoord);
glFlush();
}

void reshape(int w, int h)


{
glViewport(0, 0, (GLsizei) w, (GLsizei) h);
height = (GLint) h;
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluOrtho2D(0.0, (GLdouble) w, 0.0, (GLdouble) h);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}
void mouse(int button, int state, int x, int y)
{

switch (button) {
case GLUT_LEFT_BUTTON:
hayCopia=true;
xCoord=x;
yCoord=y;
break;
default:
break;
}
glutPostRedisplay();
}

void keyboard(unsigned char key, int x, int y)


{
switch (key) {
case 'r':
case 'R':
zoomFactor = 1.0;
glutPostRedisplay();
printf ("ZoomFactor reseteado a 1.0\n");
break;
case '+':
zoomFactor += 0.5;

printf ("ZoomFactor es ahora %4.1f\n", zoomFactor);


break;
case '-':
zoomFactor -= 0.5;
if (zoomFactor <= 0.5)
zoomFactor = 0.5;
printf ("ZoomFactor es ahora %4.1f\n", zoomFactor);
break;
case 27:
exit(0);
break;
default:
break;
}
}

int main(int argc, char** argv)


{
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB);
glutInitWindowSize(400, 400);
glutInitWindowPosition(100, 100);
glutCreateWindow(argv[0]);
init();
glutDisplayFunc(display);
glutReshapeFunc(reshape);
glutKeyboardFunc(keyboard);
glutMouseFunc(mouse);
glutMainLoop();
return 0;
}

Ilustración 18.- Código de un programa para copiar píxeles y hacer zoom


5 El Pipeline de renderizado de OpenGL
La mayor parte de las implementaciones de OpenGL siguen un mismo
orden en sus operaciones, una serie de plataformas de proceso, que en su
conjunto crean lo que se suele llamar el “OpenGL Rendering Pipeline”.

La palabra renderización define un proceso de cálculo complejo


desarrollado por un ordenador destinado a generar una imagen 2D a partir de
una escena 3D. Así podría decirse que en el proceso de renderización, la
computadora "interpreta" la escena 3D y la plasma en una imagen 2D

Este proceso se desarrolla con el fin de imitar un espacio 3D formado


por estructuras poligonales, comportamiento de luces, texturas, materiales,
animación, simulando ambientes y estructuras físicas verosímiles, etc

Cuando se trabaja en un programa de diseño 3D por computadora, no


es posible visualizar en tiempo real el acabado final deseado de una escena 3D
compleja ya que esto requiere una potencia de cálculo demasiado elevada. Por
lo que se opta por crear el entorno 3D con una forma de visualización más
simple y técnica y luego generar el lento proceso de renderización para
conseguir los resultados finales deseados.

El siguiente diagrama describe el funcionamiento del pipeline:


Ilustración 19 Pipeline de renderizado de OpenGL

En este diagrama se puede apreciar el orden de operaciones que sigue


el pipeline para renderizar. Por un lado tenemos el “vertex data”, que describe
los objetos de nuestra escena, y por el otro, el “píxel data”, que describe las
propiedades de la escena que se aplican sobre la imagen tal y como se
representa en el buffer. Ambas se pueden guardar en una “display list”, que es
un conjunto de operaciones que se guardan para ser ejecutadas en cualquier
momento.

Sobre el “vertex data” se pueden aplicar “evaluators. Los “evaluators” de


OpenGL nos permiten especificar una superficie solo usando sus puntos de
control, podemos crear superficies compuestas por puntos, modelos de
alambre (wireframe) o dibujar modelos sombreados (shaded) ya que los
vectores normales a la superficie son calculados en forma automática, y hasta
superficies con la aplicación de Texturas.

Luego se aplicaran las “per-vertex operations”, que convierten los


vértices en primitivas. Aquí es donde se aplican las transformaciones
geométricas como rotaciones, translaciones, etc., por cada vértice. En la
sección de “primittive assembly”, se hace clipping de lo que queda fuera del
plano de proyección, entre otros. El clipping consiste en recortar (ocultar) todo
aquello que “esta” pero “no se ve”. Es decir, todo aquello que la cámara no
puede ver, porque no entra en su ángulo de visión, se elimina.

Por la parte de “píxel data”, tenemos las “píxel operations”. Aquí los
píxeles son desempaquetados desde algún array del sistema (como el
framebuffer) y tratados (escalados, etc.). Luego, si estamos tratando con
texturas, se preparan en la sección “texture assembly”.

Ambos caminos convergen en la “Rasterization”, donde son convertidos


en fragmentos. Cada fragmento será un píxel del framebuffer. Aquí es donde
se tiene en cuenta el modelo de sombreado, la anchura de las líneas, o el
antialiassing.

En la última etapa, las “per-fragmet operations”, es donde se preparan


los texels (elementos de texturas) para ser aplicados a cada píxel, la fog
(niebla), el z-buffering, el blending, etc. Todas estas operaciones desembocan
en el framebuffer, donde obtenemos el render final.
6 Lectura y Dibujo de rectángulos de píxeles

Existen dos procesos parecidos, pero distintos a la vez en el tratamiento de


imágenes. La transferencia de datos del framebuffer a memoria y viceversa no
es idéntica y provoca diferentes operaciones.

Proceso de dibujo de rectángulos de píxeles

En esta figura se describe el proceso de dibujo de píxeles en el frambuffer.

Ilustración 20 Dibujo de píxeles con glDrawPixeles()


Los pasos que se siguen en este proceso son los siguientes:

1. Si los píxeles no son índices (o sea, el formato no es


GL_COLOR_INDEX o GL_STENCIL_INDEX), el primer paso es
convertir las componentes a formato de punto flotante si es necesario.
2. Si el formato es GL_LUMINANCE o GL_LUMINANCE_ALPHA, el
elemento de luminancia es convertido a RGB. En caso de no indicarse el
componente alpha, por defecto sería 1.0.
3. Cada componente (rojo, verde, azul, alpha o profundidad) se multiplica
por la escala apropiada y se suma la distorsión que se especifique, esto
es, la componente verde se multiplica por el valor indicado en
GL_GREEN_SCALE y se suma al valor correspondiente a
GL_GREEN_BIAS.
4. Si GL_MAP_COLOR es TRUE, cada uno de los componentes es fijado
dentro de un rango de 0.0 a 1.0, multiplicado por un entero uno menos
del tamaño de la tabla, modulado y localizado en la tabla.
5. Una vez que las componentes están dentro de un rango 0.0 – 1.0, se
convierten a coma fija con tantos bits a la izquierda del punto decimal
como haya en el componente correspondiente dentro del framebuffer.
6. Si trabajamos con valores de índices (de paleta o color), primero se
convierten los de punto flotante a punto fijo con algunos bits sin
especificar a la derecha del punto decimal y los que ya estaban en punto
fijo, se ponen a cero los bits a la derecha del punto decimal. El valor que
resulta se desplaza hacia la izquierda si GL_INDEX_SHIFT > 0 y a la
derecha si GL_INDEX_SHIFT < 0. Posteriormente se suma
GL_INDEX_OFFSET.
7. Si se usa el modo RGBA, el índice se convierte a RGBA utilizando los
componentes de color especificados por GL_PIXEL_MAP_I_TO_R,
GL_PIXEL_MAP_I_TO_G, GL_PIXEL_MAP_I_TO_B, y
GL_PIXEL_MAP_I_TO_A. En otro caso, si GL_MAP_COLOR es
GL_TRUE, el índice de color se localiza a través de la tabla
GL_PIXEL_MAP_I_TO_I. (si GL_MAP_COLOR es GL_FALSE, el índice
no cambia). Si la imagen está hecha de índices de paleta en lugar de
índices de color, y si GL_MAP_STENCIL es GL_TRUE, el índice es
localizado en la tabla correspondiente a GL_PIXEL_MAP_S_TO_S. Si
GL_MAP_STENCIL es FALSE, el índice de paleta no cambia
8. Si los índices no han sido convertidos a RGBA, son enmascarados al
número de bits de cualquier índice de color o buffer de paleta.

Proceso de lectura de rectángulos de píxeles

El proceso de lectura es semejante al de escritura (dibujo) y las transformaciones que se


realizan son parecidas. El proceso de lectura queda especificado por la siguiente figura:
Ilustración 21 Lectura de píxeles con glReadPixeles()

1. Si el formato no es GL_COLOR_INDEX o GL_STENCIL_INDEX (esto


es, los píxeles que se van a leer no son índices), los componentes se
traducen a valores de rango 0.0 – 1.0 (de forma opuesta a la escritura).
2. En caso de que sean índices, se desplazan.
3. Se aplican las escalas y distorsiones a cada componente. Se ajustan
otra vez los valores a 0.0 – 1.0 en caso de que GL_MAP_COLOR sea
TRUE. En caso de que se desee usar luminancia en vez de RGB, se
suman las tres componentes RGB.
4. Si se trata de índices, se enmascaran al número de bits del tipo de
almacenamiento (8,16 o 32 bits) y empaquetados en memoria.
5. En caso de usarse el modo RGBA, se mapea el resultado con un mapa
“índice a RGBA”.
6. Se empaqueta el resultado en memoria con las opciones que tuviese
glPixelStore * ().

Los valores de escala y distorsión son los mismo que los usados cuando se realizó la
escritura, así que sise están haciendo las dos operaciones a la vez, hay que vigilar que
estos datos son apropiados, puesto que de lo contrario podrían ocurrir errores. Si se usan
mapas para lectura y para escritura (dibujo), hay que reestablecer esos mapas.
7 Formatos de imagen
Las imágenes pueden ser guardadas en archivos usando diferentes
formatos, ejemplos de ellos son el BMP, JPG y GIF, que quizá sean los más
extendidos, o si no los más conocidos. Todos ellos emplean una compresión de
los datos, aunque cada uno define su algoritmo haciéndolo mejor o peor para
determinados usos. Aún así todos ellos contienen una estructura similar.
Existirá una cabecera con información acerca del tipo de imagen, tamaño,
número de colores, compresión utilizada, etc.; seguida de la información, es
decir de los datos de la imagen, comprimida.

La calidad de la imagen la da el número de colores utilizados para


dibujarla. Normalmente el número de colores es de 2, 16, 256 o 16 millones, lo
que requiere el uso de 1, 4, 16 o 24 bits por píxel.

Como ya sabemos el formato usado para representar el color es el RGB.


Según esto un color es la mezcla de la intensidad de tres colores, rojo, verde y
azul. En una representación binaria, un color vendría dado por 3 bytes: el
primer byte daría la intensidad del color rojo, el segundo la del color verde y el
tercero la del color azul. Pues bien, se plantea un problema. En el caso de una
imagen de 16 millones de colores se tienen 24 bits por píxel, un byte para cada
color del RGB, especifica el color real. Pero en el caso de imágenes que
utilicen 4 o 16 bits por píxel hay que decir que intensidad de rojo, verde o azul
se utilizará para representar el color real. Hay que crear una tabla que asocia
cada color (serie de 4 o 16 bits) a las correspondientes cantidades de rojo,
verde y azul. Esta tabla recibe el nombre de paleta de colores que será distinta
a cada imagen, por tanto debe aparecer junto a la cabecera del archivo.

Por tanto, señalar a modo de resumen, que un fichero gráfico contendrá,


independientemente del formato utilizado, una cabecera, la paleta de colores y
los datos de los píxeles (información de la imagen).

El formato BMP

EL formato BMP (BitMaP) es el formato mas extendido en los sistemas


Windows y OS/2. Es quizá el formato de ficheros de imágenes más simple que
existe y aunque admite compresión, en la práctica casi nunca se usa.

Los archivos de mapas de bits se componen de direcciones asociadas a


códigos de color, uno para cada cuadro en una matriz de pixeles tal como se
esquematizaría un dibujo de "colorea los cuadros" para niños pequeños.
Normalmente, se caracterizan por ser muy poco eficientes en su uso de
espacio en disco, pero pueden mostrar un buen nivel de calidad.

Un fichero de este tipo consta de una cabecera de archivo con las letras
'BM' (0x42 0x4D), una cabecera de información, la paleta (en caso de no
tratarse de una imagen de 16 millones de colores) y la información o datos.
Esta estructura queda resumida de la siguiente forma (utilizando os tipos
declarados en Windows):

BITMAPFILEHEADER.
BITMAPINFOHEADER.
RGBQUAD.
BYTE.

A continuación se pasará a explicar cada una de estas partes utilizando las


estructuras y variables definidas en Windows.

La cabecera de archivo. Es una estructura con los siguientes campos:

typedef struct tagBITMAPFILEHEADER {


UINT bfType;
DWORD bfSize;
UINT bfReserved1;
UINT bfReserved2;
DWORD bfOffBits;
} BITMAPFILEHEADER;

El campo bfType ocupa dos bytes y se utiliza para especificar el tipo de


formato gráfico utilizado por el fichero, en el caso de BMP será BM. bfSize
ocupa cuatro bytes y contiene el tamaño, en bytes, del archivo. Los siguientes
parámetros están reservados, y no nos interesan para nuestro propósito. El
último parámetro también ocupa cuatro bytes e indica el desplazamiento, en
bytes, desde el inicio del archivo hasta el comienzo de la información gráfica.

La cabecera de información. Se sitúa dentro de otra estructura que contiene


además la paleta de colores.

typedef struct tagBITMAPINFO {


BITMAPINFOHEADER bmiHeader;
RGBQUAD bmiColors[ 1 ];
} BITMAPINFO;

typedef struct tagBITMAPINFOHEADER {


DWORD biSize;
DWORD biWidth;
DWORD biHeight;
WORD biPlanes;
WORD biBitCount;
DWORD biCompression;
DWORD biSizeImage;
DWORD biXpelsPerMeter;
DWORD biYPelsPerMeter;
DWORD biClrUsed;
DWORD biClrImportant;
} BITMAPINFOHEADER;
El primer parámetro (4 bytes) da el tamaño (siempre en bytes) de la
cabecera de información, que será 40. Los dos siguientes (4 bytes ambos) dan
el tamaño (ancho y alto) en píxeles de la imagen. El atributo biPlanes, que
ocupa 2 bytes, informa del número de planos de la imagen, generalmente uno.
El siguiente es biBitCount, de 2 bytes de tamaño, que da el número de bits por
píxel, esto es, 1, 4, 8 o 24. Los seis parámetros que restan ocupan todos 4
bytes y sus funciones son: dar la compresión utilizada (se explicará mas
adelante), pudiendo tomar los valores BI_RGB si no hay compresión, BI_RLE8
si la compresión es RLE para 8 bits por píxel y BI_RLE si es para 4 bits por
píxel); dar el tamaño de la imagen; dar los píxeles por metro horizontal y
vertical; dar el número de colores usados (con biClrUsed); y cuales son los
importantes (si biClrImportant vale 0 todos son importantes).

La paleta de color. Definida como un array de estructuras RGBQUAD y


contiene los colores del mapa de bits. Si se tiene una imagen de 1 bit por píxel,
significa que se tiene una imagen monocroma, la paleta tiene dos entradas
(array de tamaño 2). Si es de 4 bits por píxel la paleta de colores es de 16
entradas. Si es de 8 bits por píxel la paleta es de 256 entradas.

La estructura RGBQUAD tiene la siguiente forma:

typedef struct tagRGBQUAD {


BYTE rgbBlue;
BYTE rgbGreen;
BYTE rgbRed;
BYTE rgbReserved;
} RGBQUAD;

Determina los valores de intensidad de los colores rojo verde y azul para
un color dado (índice del array).

Los datos. A continuación de la estructura BITMAPINFO aparece ya la


información gráfica del fichero. Un array de bytes que representan los píxeles
del mapa de bits. Recordar que un bitmap se comienza definiendo por la
esquina inferior izquierda, y añadir que el valor del color también esta el revés,
es decir, se comienza dando un valor para el azul, luego para el verde y por
último para el rojo.

La imagen de un fichero BMP se almacena en formato DIB (Device


Independent Bitmap -Mapa de bits independiente de dispositivo-), que permite
al sistema Windows visualizar el mapa de bits en cualquier tipo de dispositivo
de visualización puesto que el fichero especifica el color de un píxel de forma
independiente del método usado para representar el color (1, 4, 8, 24 bits por
píxel).

Como ya se dijo antes, el formato BMP soporta compresión pero casi


nunca se usa, es esto lo que hace que los ficheros sean tan grandes. El
algoritmo de compresión utilizada es el RLE (run-length encoded), que permite
comprimir los ficheros que utilicen una representación de 4 y 8 bits por píxel.
8 Manejo de ficheros BMP en OpenGL
Ahora que ya conocemos la estructura de un fichero BMP, podemos
utilizarlo para incorporarlo a nuestra aplicación OpenGL bajo Windows,
principalmente para la incorporación de texturas.

Para ayudar a su comprensión se acompaña de código fuente extraído del


ejemplo FicheroBMP que visualiza, guarda e imprime un archivo de imagen
BMP. A partir de este ejemplo se procederá a detallar las operaciones de
manejo de ficheros de tipo Bitmap.

Lectura de un Fichero. Debe abrirse el fichero para lectura binaria, leer


las cabeceras, para obtener el tamaño de la información y así reservar espacio
suficiente en memoria.

/* Carga un fichero desde desde el disco a memoria */

void *CargarBMP (char *archivo, BITMAPINFO **info){


FILE *f;
void *bits;
long tamanoBMP, tamanoInfo;
BITMAPFILEHEADER cabecera;

if ((f = fopen(archivo, "rb")) == NULL)


return (NULL);

/*Leer la cabecera del archivo y comprobar si se trata


en verdad de un archibo BMP*/
if (fread(&cabecera, sizeof(BITMAPFILEHEADER), 1, f) < 1){
fclose(f);
return (NULL);
};
if (cabecera.bfType != 'MB'){
/*Si no es BMP aboratar*/
fclose(f);
return (NULL);
};
/*Obtener el tamaño de la cabecera de informacion
restando el tamaño de la cabecera del archivo al
tamaño de las dos cabeceras (desplazamiento del
inicio del archivo al inicio de la informacion)*/
tamanoInfo = cabecera.bfOffBits - sizeof(BITMAPFILEHEADER);
if ((*info = (BITMAPINFO *)malloc(tamanoInfo)) == NULL){
fclose(f);
return (NULL);
};

/*Obtener la cabecera de informacion*/


if (fread(*info, 1, tamanoInfo, f) < tamanoInfo){
free(*info);
fclose(f);
return (NULL);
};

/*Obtener el tamano en bytes de la informacion del archivo


y reservar espacio en memoria para guardarlo*/
if ((tamanoBMP = (*info)->bmiHeader.biSizeImage) == 0)
tamanoBMP = ((*info)->bmiHeader.biWidth * (*info)-
>bmiHeader.biBitCount + 7) / 8 * abs((*info)->bmiHeader.biHeight);
if ((bits = malloc(tamanoBMP)) == NULL){
free(*info);
fclose(f);
return (NULL);
};

/*Cargar la informacion*/
if (fread(bits, 1, tamanoBMP, f) < tamanoBMP){
free(*info);
free(bits);
fclose(f);
return (NULL);
};
fclose(f);
return (bits);
}

Guardar una imagen en disco. Para guardar una imagen que esta en
pantalla, previamente hay que leerla, pasarla a formato BMP y posteriormente
guardarla en disco.
/* Guarda una imagen BMP leida previamente a disco */

int GuardarBMP (char *archivo, BITMAPINFO *info, void *bits) {


FILE *f;
long tamanoArchivo, tamanoInfo, tamanoBMP;
BITMAPFILEHEADER cabecera;

/*Abrir el archivo en escritura binaria*/


if ((f = fopen(archivo, "wb")) == NULL)
return (-1);

/*Obtener el tamaño de los datos de igual


forma que antes*/
if (info->bmiHeader.biSizeImage == 0) /* Figure out the bitmap size
*/
tamanoBMP = (info->bmiHeader.biWidth * info-
>bmiHeader.biBitCount + 7) / 8 * abs(info->bmiHeader.biHeight);
else
tamanoBMP = info->bmiHeader.biSizeImage;

/*Obtener al tamaño de la cabecera de


informacion*/
tamanoInfo = sizeof(BITMAPINFOHEADER);

/*Editar campos de la cabecera en funcion


de la compresión usada*/
switch (info->bmiHeader.biCompression){
case BI_BITFIELDS :
tamanoInfo += 12; /* Añadir 3 mascaras RGB de 4
bytes (double)*/
if (info->bmiHeader.biClrUsed == 0)
break;
case BI_RGB :
if (info->bmiHeader.biBitCount > 8 && info-
>bmiHeader.biClrUsed == 0)
break;
case BI_RLE8 :
case BI_RLE4 :
if (info->bmiHeader.biClrUsed == 0)
tamanoInfo += (1 << info-
>bmiHeader.biBitCount) * 4;
/* << operador de rotacion de bits a la izq.*/
else
tamanoInfo += info->bmiHeader.biClrUsed * 4;
break;
}
/*Calcular el tamaño del archivo*/
tamanoArchivo = sizeof(BITMAPFILEHEADER) +
tamanoInfo + tamanoBMP;

/*Completar los campos de las cabeceras*/


cabecera.bfType = 'MB';
cabecera.bfSize = tamanoArchivo;
cabecera.bfReserved1 = 0;
cabecera.bfReserved2 = 0;
cabecera.bfOffBits = sizeof(BITMAPFILEHEADER) +
tamanoInfo;

/*Escribir en el fichero las cabecera y los datos*/


if (fwrite(&cabecera, 1, sizeof(BITMAPFILEHEADER), f) <
sizeof(BITMAPFILEHEADER)){
fclose(f);
return (-1);
};
if (fwrite(info, 1, tamanoInfo, f) < tamanoInfo){
fclose(f);
return (-1);
};
if (fwrite(bits, 1, tamanoBMP, f) < tamanoBMP){
fclose(f);
return (-1);
};
fclose(f);
return (0);
}

/* Lee una imagen de pantalla y la pasa a


formato BMP */

void *LeerBMP(BITMAPINFO **info){


long i, j, tamanoBMP, ancho;
GLint viewport[4];
void *bits;
GLubyte *rgb, temp;

/*Obtener el viewport de pantalla acutual*/


glGetIntegerv(GL_VIEWPORT, viewport);

/*Reservar espacio para la cabecera del archivo*/


if ((*info = (BITMAPINFO
*)malloc(sizeof(BITMAPINFOHEADER))) == NULL){
return (NULL);
};
/*El parametro tercero de viewport es la
anchura de la imagen y el cuarto la
altura*/
ancho = viewport[2] * 3;
ancho = (ancho + 3) & ~3;
/* ~ Operador de complemento a 1*/
tamanoBMP = ancho * viewport[3];

/*Reservar espacio para el tamaño del fichero*/


if ((bits = calloc(tamanoBMP, 1)) == NULL){
free(*info);
return (NULL);
};

/*Leer los pixeles del buffer de pantalla*/


glFinish();
glPixelStorei(GL_PACK_ALIGNMENT, 4);
glPixelStorei(GL_PACK_ROW_LENGTH, 0);
glPixelStorei(GL_PACK_SKIP_ROWS, 0);
glPixelStorei(GL_PACK_SKIP_PIXELS, 0);
glReadPixels(0, 0, viewport[2], viewport[3], GL_RGB,
GL_UNSIGNED_BYTE, bits);

/*Cambiar el orden en el que viene dado el color


RGB*/
for (i = 0; i < viewport[3]; i ++)
for (j = 0, rgb = ((GLubyte *)bits) + i * ancho; j <
viewport[2]; j ++, rgb += 3){
temp = rgb[0];
rgb[0] = rgb[2];
rgb[2] = temp;
};

/*Dar valores a la cabecera*/


(*info)->bmiHeader.biSize =
sizeof(BITMAPINFOHEADER);
(*info)->bmiHeader.biWidth = viewport[2];
(*info)->bmiHeader.biHeight = viewport[3];
(*info)->bmiHeader.biPlanes = 1;
(*info)->bmiHeader.biBitCount = 24;
(*info)->bmiHeader.biCompression = BI_RGB;
(*info)->bmiHeader.biSizeImage = tamanoBMP;
(*info)->bmiHeader.biXPelsPerMeter = 2952;
(*info)->bmiHeader.biYPelsPerMeter = 2952;
(*info)->bmiHeader.biClrUsed = 0;
(*info)->bmiHeader.biClrImportant = 0;

return (bits);
}
Imprimir una imagen BMP. Para imprimir una imagen por impresora
también hay que leerla de pantalla previamente. Después de esto hay que
mostrar el cuadro de dialogo de impresoras para que el usuario pueda elegir el
color, el número de copias y demás opciones. Cuando el usuario acepte, se
colocará el trabajo en la cola de impresión.

/* Imprime por dispositivo una imagen leida de pantalla*/

int ImprimirBMP (HWND padre, BITMAPINFO *info, void *bits){


PRINTDLG pd;
long xtam, ytam, xdesplazamiento, ydesplazamiento;
RECT area;
DOCINFO di;
HDC hdc;
HBITMAP bitmap;
HBRUSH brocha;
HCURSOR ocupado, cursorViejo;

if (info == NULL || bits == NULL)


return (0);

/*Iniciar el cuadro de dialogo de usuario para


impresoras*/
memset(&pd, 0, sizeof(pd));
pd.lStructSize = sizeof(pd);
pd.hwndOwner = padre;
pd.Flags = PD_RETURNDC;
pd.hInstance = NULL;

if (!PrintDlg(&pd))
return (0); /*Si el usuario aborta*/

/*Si se va a imprimir, cambiar el cursor mientras se


hacen las gestiones*/
ocupado = LoadCursor(NULL, IDC_WAIT);
cursorViejo = SetCursor(ocupado);
SetMapMode(pd.hDC, MM_TEXT);
di.cbSize = sizeof(DOCINFO);
di.lpszDocName = "Imagen BMP OpenGL";
di.lpszOutput = NULL;
StartDoc(pd.hDC, &di);
StartPage(pd.hDC);
/*Poner el fondo a blanco*/
area.top = 0;
area.left = 0;
area.right = GetDeviceCaps(pd.hDC, HORZRES);
area.bottom = GetDeviceCaps(pd.hDC, VERTRES);
brocha = CreateSolidBrush(0x00ffffff);
FillRect(pd.hDC, &area, brocha);

/*Escalar el Bitmap para que entre en los


margenes de la pagina*/
hdc = CreateCompatibleDC(pd.hDC);
bitmap = CreateDIBitmap(hdc, &(info->bmiHeader), CBM_INIT,
bits, info, DIB_RGB_COLORS);
SelectObject(hdc, bitmap);

xtam = area.right;
ytam = xtam * info->bmiHeader.biHeight / info-
>bmiHeader.biWidth;
if (ytam > area.bottom){
ytam = area.bottom;
xtam = ytam * info->bmiHeader.biWidth / info-
>bmiHeader.biHeight;
};

/*Margenes procesados tomando la mitad de la diferencia


entre anchuras y alturas*/
xdesplazamiento = (area.right - xtam) / 2;
ydesplazamiento = (area.bottom - ytam) / 2;

StretchBlt(pd.hDC, xdesplazamiento, ydesplazamiento, xtam,


ytam, hdc, 0, 0, info->bmiHeader.biWidth, info->bmiHeader.biHeight,
SRCCOPY);

/*Terminar el proceso*/
EndPage(pd.hDC);
EndDoc(pd.hDC);
DeleteDC(pd.hDC);
DeleteObject(bitmap);
DeleteObject(brocha);
DeleteObject(ocupado);
DeleteDC(hdc);
SetCursor(cursorViejo);
return (1);
}

Вам также может понравиться