Lecc 9

  • Uploaded by: maria
  • 0
  • 0
  • December 2019
  • PDF

This document was uploaded by user and they confirmed that they have the permission to share it. If you are author or own the copyright of this book, please report to us by using this DMCA report form. Report DMCA


Overview

Download & View Lecc 9 as PDF for free.

More details

  • Words: 7,974
  • Pages: 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:

0 1 0 2 0 3 0 4 0 5 0 6 0 7 1 8 1 9 1 10 1 11 0 12 0 13 0 14 0 16 0 16 0

B 1 0 0 0 0 1 1 1 1 1 1 1 1 0 0 0 0

y 2 0 0 0 1 1 1 1 1 1 1 1 1 1 0 0 0

t 3 0 0 1 1 1 1 1 1 1 1 0 1 1 1 0 0

e 4 0 1 1 1 0 0 1 1 1 1 1 0 1 1 1 0

1 5 0 1 1 1 0 0 1 1 1 1 1 1 0 1 1 0

6 1 1 1 1 1 1 1 1 1 1 1 1 0 1 1 1

7 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1 1

0 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1 1

1 1 1 1 1 1 1 1 1 1 1 1 1 0 1 1 1

B 2 0 1 1 1 0 0 1 1 1 1 1 1 0 1 1 0

y 3 0 1 1 1 0 0 1 1 1 1 1 0 1 1 1 0

t 4 0 0 1 1 1 1 1 1 1 1 0 1 1 1 0 0

e 5 0 0 0 1 1 1 1 1 1 1 1 1 1 0 0 0

2 6 0 0 0 0 1 1 1 1 1 1 1 1 0 0 0 0

7 0 0 0 0 0 0 1 1 1 1 0 0 0 0 0 0

0x03, 0xc0 0x0f, 0xf0 0x1f, 0xf8 0x3f, 0xfc 0x73,0xce 0x73, 0xcc 0xff, 0xff 0xff, 0xff 0xff, 0xff 0xff, 0xff 0x6f, 0xf6 0x77, 0xee 0x39,0x9c 0x1e,0x78 0x0f,0xf0 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. ejemplo de cómo se definiría este bitmap.

Veamos un

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 static static static

GLdouble zoomFactor = 1.0; GLint height; bool hayCopia=false; 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); }

Related Documents

Lecc 9
December 2019 15
Lecc 11
December 2019 18
Lecc 6
December 2019 16
Lecc 12
December 2019 17
Lecc 3
December 2019 7
Lecc 13
December 2019 9

More Documents from "maria"