[ contenidos | #winprog]

Bitmaps, Dispositivos de Contexto y BitBlt

Ejemplo: bmp_one

[images/bmp_one.gif]

GDI

Una cosa buena que tiene MS Windows, a diferencia de DOS, es que para mostrar gr�ficos no necesitamos conocer nada acerca del hardware de video que estamos usando. Por el contrario, Windows provee una API llamada Graphics Device Interface (Interface del Dispositivo Gr�fico) o GDI, en forma abreviada. LA GDI usa un conjunto de objetos gr�ficos gen�ricos que pueden ser usados para dbujar en la pantalla, en la memoria, o en la impresora.

Dispositivo de Contextos

La GDI gira entorno a un objeto llamado Dispositivo de Contexto (DC), representado por el tipo de dato HDC (Handle al Dispositivo de Contexto). Un HDC es b�sicamente un handle a algo en lo que podemos dibujar, puede representar la pantalla entera, una ventana completa, el �rea cliente de una ventana, un bitmap almacenado en memoria o puede ser una impresora. La parte buena es que no necesitamos saber a cu�l de todas estas cosas hace referencia, se usa b�sicamente de la misma manera, lo cual es muy �til para escribir funciones de dibujo que podemos usar en cualquiera de estos dispositivos sin necesidad de cambiarla para cada uno de estos.

Un HDC, al igual que la mayor�a de los objetos GDI, es opaco. Esto significa que no podemos acceder a sus datos de forma directa... pero podemos pas�rselo a varias funciones GDI que operar�n en sus datos, ya sea para dibujar algo, para obtener informaci�n a cerca de �ste, o para cambiar de alguna manera el objeto.

Por ejemplo, si queremos dibujar en una ventana, primero debemos obtener un HDC que represente la ventana, para esto utilizamos GetDC( ). Luego podemos usar cualquiera de las funciones GDI que toman un HDC, como por ejemplo BitBlt( ) para dibujar im�genes, TextOut( ) para dibujar texto, LineTo( ) para lineas, etc...

Bitmaps

Los bitmaps puden ser cargados en su mayor�a como los �conos de los primeros ejemplos. Hay una funci�n LoadBitmap( ) para simplemente cargar un recurso bitmap y una funci�n LoadImage( ) para cargar bitmaps desde cualquier archivo *.bmp.

Una de las sutilezas de la GDI es que no podemos dibujar directamente sobre objetos bitmap (HBITMAP). Recuerda que las operacioens de dibujo son abstractas y dicha abstracci�n la realiza el Dispositivo de Contextos, por lo tanto para usar cualquiera de estas funciones de dibujo sobre un bitmap necesitamos crear un Memory DC y luego seleccionar el HBITMAP dentro de �ste usando SelectObject( ). El efecto es que el "dispositvo" al cual el HDC hace referencia es el bitmap en memoria y cuando operamos sobre el HDC los resutados de las funcioens gr�ficas son aplicados al bitmap. Como vimos, esto es una forma muy conveniente de hacer las cosas porque podemos escribir c�digo que dibuja sobre un HDC y luego usarlo sobre un Window DC o sobre un Memory DC sin tener que realizar cambios o chequeos.

Tenemos la opci�n de manipular nosotros mismos los datos del bitmap en memoria. Podemos hacer esto con el Dispositivo de Bitmaps Independientes (DIB) y podemos a�n combinar operaciones GDI y operaciones manuales en el DIB. Sin embargo, por el momento, esto est� mas all� del alcance de este tutorial b�sico y por ahora s�lo cubriremos las operaciones GDI mas simples.

Liberaci�n de Objetos GDI

Una vez que hemos finalizado de usar un HDC es muy importante liberarlo (c�mo lo hacemos depende de c�mo lo obtuvimos, hablaremos de esto dentro de un momento). Los objetos GDI est�n limitados en n�mero. En versiones de Windows anteriores a Windows 95, no s�lo estaban incre�blemente limitados sin� que tambi�n compart�an ampliamente los recursos del sistema, por lo tanto, si un programa usaba demasiados, ninguno de los dem�s programas pod�a ser capaz de dibujar nada! Afortunadamente, en Windows 2000 o XP, esto ya no sucede m�s y podemos obtener una gran cantidad de recursos sin que suceda algo malo... pero a�n as� es f�cil olvidarse de liberar objetos GDI y r�pidamente podemos obtener un agotamiento de los recursos en sistemas como Windows 9x. Te�ricamente no deber�amos poder agotar los recursos GDI en sistemas basados en tecnolog�a NT (NT/2K/XP) pero puede llegar a suceder en casos extremos, o si acertamos en el bug apropiado...

Si nuestro programa se ejecuta bien algunos minutos y luego comienza a dibujar extra�amente o de forma incompleta, es una buena se�al de que est�s reteniendo objetos GDI. Los HDCs no son los �nicos objetos con los que necesitamos ser cuidadosos con respecto a su liberaci�n, en general es bueno mantener cosas como bitmaps y fuentes hasta el final de la ejecuci�n del programa, debido a que es mucho m�s eficiente que recargarlos cada vez que los necesitamos.

Actualizaci�n Importante: Windows tiene asignados (retenidos) ciertos objetos por default en los HDC, pero hay unos pocos objetos que nunca utiliza para esto y puedes buscar cu�les son en MSDN. Debido a esto, ten�a la incertidumbre si HBITMAP era uno de ellos, ya que parece no haber una documentaci�n definitiva sobre �ste y los ejemplos (a�n los de Microsoft) suelen ignorar el bitmap por default. De la escritura del tutorial original hace varios a�os, me han confirmado que hay un bitmap que necesita ser liberado. Esta informaci�n es cortes�a de Shaun Ivory, un ingeniero de software de MS y un amigo mio de #winprog.

Aparentemente hay un bug en un protector de pantalla escrito por MS y esto se debe a que el bitmap por default no fu� reemplazado o destru�do y eventualmente provoca un desborde de los recursos GDI. Debes estar atentos!, es un error f�cil de cometer.

Como Mostrar Bitmaps

Ok, volvamos al tema. Las operaciones m�s simple de dibujo sobre una ventana se realizan procesando el mensaje WM_PAINT. Cuando nuestra ventana es mostrada por primera vez, o es restaurada o minimizada, o es descubierta luego de haber estado tapada por otra ventana, Windows le env�a a la ventana el mensaje WM_PAINT para hacerle saber que necesita redibujar su contenido. Cuando dibujamos algo sobre la pantalla NO ES permanente, simplemente permanece hasta que algo se dibuja encima y luego, cuando se destapa, s�lo es necesario redibujarlo.
HBITMAP g_hbmBall = NULL;
    case WM_CREATE:
        g_hbmBall = LoadBitmap(GetModuleHandle(NULL), MAKEINTRESOURCE(IDB_BALL));
        if(g_hbmBall == NULL)
            MessageBox(hwnd, "Could not load IDB_BALL!", "Error", MB_OK | MB_ICONEXCLAMATION);
    break;
El primer paso, por supuesto, es cargar el bitmap. Esto es simple cuando tenemos un recurso bitmap ya que no hay diferencias significantes de cuando cargamos otro tipo de recursos.
    case WM_PAINT:
    {
        BITMAP bm;
        PAINTSTRUCT ps;

        HDC hdc = BeginPaint(hwnd, &ps);

        HDC hdcMem = CreateCompatibleDC(hdc);
        HBITMAP hbmOld = SelectObject(hdcMem, g_hbmBall);

        GetObject(g_hbmBall, sizeof(bm), &bm);

        BitBlt(hdc, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY);

        SelectObject(hdcMem, hbmOld);
        DeleteDC(hdcMem);

        EndPaint(hwnd, &ps);
    }
    break;

Obtener el Window DC

Para comenzar, declaramos algunas variables. Observa que la primera es un BITMAP y no un HBITMAP. BITMAP es una estructura que almacena informaci�n acerca de un HBITMAP el cual es el verdadero objeto GDI. Necesitamos una forma de obtener el alto y ancho del HBITMAP, para esto usamos GetObject( ), el cual, contrariamente a su nombre, no obtiene un objeto pero si obtiene informaci�n acerca de uno ya existente. "GetObjectInfo" podr�a haber sido un nombre m�s apropiado. GetObject( ) funciona con varios tipos de objetos GDI, que puede distinguir usando el segundo par�metro, el tama�o de la estructura.

PAINTSTRUCT es una estructura que contiene informaci�n acerca de la ventana que esta siendo pintada, que es la que contin�a con el mensaje paint. Para las tareas m�s simples, podemos ignorar la informaci�n que esta estructura contiene, pero �sta es necesaria en la llamada a BeginPaint( ). BeginPaint( ) est� dise�ada para procesar especif�camente el mensaje WM_PAINT. Cuando no procesamos el mensaje WM_PAINT podemos usar GetDC( ), el cual veremos en los ejemplos de la animaci�n dentro de un momento... pero en el mensaje WM_PAINT es importante usar BeginPaint( ) y EndPaint( ).

BeginPaint( ) retorna un HDC que representa el HWND que le pasamos, el de la ventana que est� procesando el WM_PAINT. Cualquier operaci�n de dibujo que realizemos sobre este HDC ser� inmediatamente mostrada en la pantalla.

Preparando un Memory DC para el Bitmap

Como mencion� anteriormente, para poder dibujar sobre bitmaps o con bitmaps, necesitamos crear un DC en memoria... la forma m�s f�cil de hacerlo aqu� es usando CreateCompatibleDC( ) con el que ya tenemos. Con esto obtenemos un Memory DC que es compatible con el color y las propiedades de dibujo del HDC de la ventana.

Ahora llamamos a SelectObject( ) para seleccionar el bitmap dentro del DC, siendo cuidadosos de almacenar el bitmap por default para que podamos reemplazarlo posteriormente y no retener objetos GDI.

Dibujo

Una vez que hemos llenado los valores ancho y alto dentro de la estructura BITMAP podemos llamar a BitBlt( ) para copiar la imagen desde nuestro Memory DC hasta el Window DC y luego mostrarlo en pantalla. Como siempre puedes buscar cada par�metro en MSDN, pero ac� est�n resumidos: El destino, la posici�n, el tama�o, el origen y la posici�n origen y finalmente el Raster Operation (c�digo ROP), el cual especifica como hacer la copia. En ese caso queremos una copia exacta del recurso, nada m�s.

BitBlt probablemente sea la funci�n mas feliz de toda la API Win32 y es la dieta pricipal de todos aquellos que est�n aprendiendo a escribir juegos o alguna otra aplicaci�n gr�fica en Windows. Esta fu� probablemente la primer API de la cual he memorizado todos los par�metros.

Limpieza

Hasta este punto el bitmap deber�a estar en pantalla y es necesario que lo borremos nosotros mismos. Lo primero que hay que hacer es restaurar el Memory DC al estado en el que estaba cuando lo obtuvimos, para esto, reemplazamos nuestro bitmap con el bitmap por default que hab�amos guardado. Luego podemos borrarlo por completo usando DeleteDC( ).

Finalmente liberamos el Window DC que obtuvimos con BeginPaint( ) usando EndPaint( ).

Destruir un HDC a veces es un poco confuso debido a que hay al menos 3 formas de hacerlo que dependen de como lo obtuvimos. Aqu� hay una lista de los m�todos m�s comunes para obtener un HDC y luego liberarlo.

Por �ltimo, al final de nuestro programa, liberamos todos los recursos que tenemos asignados. T�cnicamente hablando, esto no es absolutamente necesario debido a que las plataformas modernas de Windows son muy buenas liberando todos los recursos cuando nuestro programa termina, aunque siempre es conveniente hacerlo uno mismo ya que hay viejas versiones de Windows que no hacen esto de manera efectiva.

    case WM_DESTROY:
        DeleteObject(g_hbmBall);
        PostQuitMessage(0);
    break;

Copyright © 1998-2003, Brook Miles (theForger). All rights reserved.

Versi�n en Espa�ol: Federico Pizarro - 2003