Capítulo 23 Objetos básicos del GDI: El Mapa de Bits (Bitmap)

Windows usa mapas de bits para muchas cosas. Si te fijas en la ventana de tu aplicación, (ahora probablemente sea un explorador de Internet), verás unos cuantos. Por ejemplo, en la barra de herramientas. Pero también son mapas de bits las flechas de las barras de desplazamiento, los dibujos de los botones de cerrar, maximizar o minimizar, los iconos, etc.

Simplificando, podemos considerar que un mapa de bits es un rectángulo de pixels que en conjunto forman una imagen.

Dentro del API un mapa de bits es una colección de estructuras: una cabecera que contiene información sobre dimensiones, tamaño del array de bits, etc. Una paleta lógica y un array de bits, que relacionan los pixels con la paleta.

Tipos de mapas de bits

Windows trabaja con dos tipos de mapas de bits: dependientes del dispositivo (DDBs) e independientes del dispositivo (DIBs).

Los DDBs provienen de las primeras versiones de Windows, anteriores a la 3.0. Más adelante, debido a ciertos problemas con los dispositivos, se crearon los DIBs.

Pero por el momento esto no nos preocupa mucho, ya lo veremos en profundidad. Lo que nos interesa ahora es cómo usar mapas de bits en nuestros programas, y en eso nos vamos a centrar.

Crear un mapa de bits

Existen funciones para crear mapas de bits, pero están pensadas para generarlos de una forma más o menos dinámica, más que para usar mapas de bits que contengas imágenes ya existentes.

En el presente capítulo nos limitaremos a usar mapas de bits que ya existan en forma de fichero ".bmp". Nuestros programas pueden trabajar con esos ficheros de dos formas: mapas de bits en ficheros de recursos, o mapas de bits almacenados en ficheros en disco.

Fichero de recursos

Esta es la forma de añadir mapas de bits que la aplicación pueda usar como parte de controles, por ejemplo, mapas de bits en menús o botones, pero esos mapas de bits pueden usarse por la aplicación para cualquier otra cosa. Hay que tener en cuenta que esos mapas de bits se incluyen en el ejecutable, por lo tanto no es muy recomendable usar mapas de bits muy grandes, ya que eso aumentará considerablemente el tamaño del fichero ".exe".

El modo de usar estos recursos es añadir una línea BITMAP en nuestro fichero de recursos:

#include <windows.h>
#define MASCARA 1000
#define CM_RECURSO 100
#define CM_FICHERO 101

Icono ICON "Food.ico"

Bitmap BITMAP "meninas24.bmp"
MASCARA BITMAP "mascara24.bmp"

Menu MENU
BEGIN
  POPUP "Opciones"
  BEGIN
     MENUITEM "&Bitmap de recurso", CM_RECURSO
     MENUITEM "&Bitmap de fichero", CM_FICHERO
  END
END

Esto es una parte del proceso, la otra parte consiste en obtener un manipulador de mapa de bits en nuestra aplicación, para ello se se usa la función LoadBitmap. Esta función requiere dos parámetros, el primero es un manipulador de la instancia que contiene el mapa de bits. Generalmente será la instancia actual, pero los recursos, como veremos en el futuro, también pueden estar contenidos en bibliotecas dinámicas. El segundo parámetro es el identificador del recurso en forma de cadena, o (si hemos usado un entero) el resultado de aplicar la macro MAKEINTRESOURCE a ese entero.

Por supuesto, una vez que no necesitemos el manipulador, debemos eliminarlo usando la función DeleteObject.

    static HINSTANCE hInstance;
    static HBITMAP hBitmapRes;
    static HBITMAP hMascara;
...    
    switch (msg)                  /* manipulador del mensaje */
    {
        case WM_CREATE:
           hInstance = ((LPCREATESTRUCT)lParam)->hInstance;
           hBitmapRes = LoadBitmap(hInstance, "Bitmap");
           hMascara = LoadBitmap(hInstance, MAKEINTRESOURCE(MASCARA));
...
        case WM_DESTROY:
           DeleteObject(hBitmapRes);
           DeleteObject(hMascara);
           PostQuitMessage(0);    /* envía un mensaje WM_QUIT a la cola de mensajes */
           break;

Fichero BMP

Otro modo de obtener mapas de bits para usarlos en nuestra aplicación es directamente a partir de ficheros BMP, sin necesidad de fichero de recursos. Para leer uno de esos ficheros se usa la función LoadImage.

En realidad, la función LoadImage se puede usar en ambos casos, pero considero que la función LoadBitmap es más cómoda para obtener un manipulador de mapa de bits cuando se trata de recursos.

Necesitamos indicar como manipulador de instancia el valor NULL, si se usa un valor de instancia, generalmente se usará para obtener un mapa de bits de recursos. El segundo parámetro es el nombre del fichero, o el nombre del recurso. El tercer parámetro indica el tipo de imagen, que puede ser un mapa de bits, un icono o un cursor. Los dos siguientes indican un tamaño de imagen, pero no se usan cuando se trata de mapas de bits. El último parámetro permite ajustar algunas opciones, pero sobre todo nos interesa el valor LR_LOADFROMFILE.

    static HBITMAP hBitmapRes;
...    
    switch (msg)                  /* manipulador del mensaje */
    {
        case WM_CREATE:
           hInstance = ((LPCREATESTRUCT)lParam)->hInstance;
           hBitmapFil = (HBITMAP)LoadImage(NULL, "Abanicos.bmp", IMAGE_BITMAP,
              0, 0, LR_LOADFROMFILE);
...
        case WM_DESTROY:
           DeleteObject(hBitmapFil);
           PostQuitMessage(0);    /* envía un mensaje WM_QUIT a la cola de mensajes */
           break;

Por supuesto, cuando ya no se necesite, se debe liberar el recurso usando DeleteObject.

Mostrar un mapa de bits

Al contrario que con otros objetos del GDI que hemos manejado hasta ahora, los mapas de bits no se seleccionan directamente en un DC de ventana. Esto se debe a que Windows sólo permite seleccionar los mapas de bits en DC de memoria, y además, sólo en uno al mismo tiempo.

Por lo tanto, para mostrar un mapa de bits tendremos que realizar algunas tareas:

  1. Crear un DC de memoria.
  2. Seleccionar el mapa de bits en ese DC.
  3. Usar una de las funciones disponibles para mostrar el mapa de bits.
  4. Borrar el DC de memoria.

El DC de memoria que necesitamos no es un DC cualquiera, debe tratarse de un DC compatible con el dispositivo en el que queramos mostrar el mapa de bits. Para crear uno de esos DCs se usa la función CreateCompatibleDC.

Seleccionar el mapa de bits es una operación similar a seleccionar pinceles, brochas o paletas. Se usa la función SelectObject, el mapa de bits se maneja mediante un manipulador de mapas de bits, un HBITMAP, que habremos obtenido de uno de los modos que hemos explicado más arriba.

En cuanto a las funciones que podemos usar para mostrar mapas de bits, la más sencilla es BitBlt. Pero hay otras, aunque algunas no están disponibles en todas las versiones de Windows.

Finalmente liberamos el DC usando la función DeleteDC.

Funciones de visualización de mapas de bits

Las funciones que veremos de momento son:

BitBlt

Muestra un mapa de bits, sin escalar ni deformar. Se especifica un rectángulo de destino, si el mapa de bits origen es más pequeño se rellena con el color de fondo, si es más grande, ser recorta.

No es necesario mostrar todo el mapa de bits, podemos mostrar sólo un área rectangular del tamaño que queramos, y a partir de la dirección que prefiramos.

Por ejemplo, es muy frecuente crear un único mapa de bits con muchos gráficos reunidos, formando una lista o un mosaico. La función BitBlt nos permite mostrar sólo uno de esos gráficos, sin preocuparnos por el resto:

void CTrozo(HDC hDC, HWND hWnd, HBITMAP hBitmap)
{
    HDC memDC;

    memDC = CreateCompatibleDC(hDC);
    SelectObject(memDC, hBitmap);
    BitBlt(hDC, 135, 225, 40, 40, memDC, 135, 225, SRCCOPY);
    DeleteDC(memDC);           
}

Esta función muestra en el rectángulo de la ventana que empieza en las coordenadas (135, 225), y que tiene 40x40 pixels, el trozo del mapa de bits que empieza en las coordenadas (135, 225). Como el mapa de bits se muestra en relación de un pixel por cada pixel del mapa de bits, no habrá distorsión, el trozo de mapa de bits mostrado tendrá el mismo tamaño que el rectángulo en el que se muestra.

StretchBlt

Muestra un mapa de bits o parte de él. El mapa se adapta al rectángulo especificado como destino, estirándose en cada dirección de la forma necesaria para ocuparlo por completo. El modo de estiramiento se puede modificar mediante la función SetStretchBltMode.

Al igual que en el caso anterior, es posible mostrar un trozo del mapa de bits, pero con esta función podemos escalar ese trozo para adaptarlo a la superficie rectangular que queramos:

void CAmpliacion(HDC hDC, HWND hWnd, HBITMAP hBitmap)
{
    HDC memDC;
    BITMAP bm;
    RECT re;

    memDC = CreateCompatibleDC(hDC);
    SelectObject(memDC, hBitmap);
    GetObject(hBitmap, sizeof(BITMAP), (LPSTR)&bm);
    GetClientRect(hWnd, &re);
    StretchBlt(hDC, 0, 0, re.right, re.bottom, memDC, 
                    135, 225, 40, 40, SRCCOPY);
    DeleteDC(memDC);           
}

En este caso se muestra el mismo trozo del mapa de bits que con el ejemplo anterior, pero en lugar de usar el mismo tamaño en la pantalla, el trozo se estirará para adaptarse al área indicada, que no es otra que toda el área de cliente.

Para indicar toda el área de cliente hemos obtenido sus coordenadas mediante la función GetClientRect.

Para obtener el tamaño del mapa de bits usamos la función GetObject que nos devuelve una estructura BITMAP asociada al mapa de bits.

PlgBlt (Sólo en Windows NT)

Muestra el mapa de bits o parte de él, deformándolo para adaptarlo al paralelogramo indicado mediante un array de tres puntos.

Nota: un paralelográmo queda definido por tres puntos, ya que el cuarto se puede calcular a partir de los otros tres. Esto es debido a que los paralelogramos son cuadriláteros con sus lados paralelos dos a dos.

La imagen resultante parece haber sido proyectada sobre una pantalla no paralela al observador.

En lo demás, la función es parecida a StretchBlt. Tammbién requiere que se defina el área del mapa de bits que se mostrará, permitiendo mostrar sólo una parte.

Además, se puede especificar una máscara opcional. La máscara es un mapa de bits monocromo, en la que los pixels de valor uno indican que se debe copiar el pixel, y los de valor cero indican que se debe conservar el fondo.

MaskBlt (Sólo en Windows NT)

Esta es la más complicada de las funciones para mostrar mapas de bits. Funciona de modo análogo a BitBlt, en lo que respecta a que se mantiene la relación de pixels uno a uno, sin deformar la imagen. Pero usa un segundo mapa de bits monocromo como máscara. Esa máscara se puede combinar mediante códigos ROP el mapa de bits con la imagen actual de la ventana y con el pincel actual seleccionado en el contexto de dispositivo.

Para esta función se deben especificar dos mapas de bits. El primero es la imagen a mostrar, el segundo es un mapa de bits monocromo que define una máscara.

Además debemos indicar dos códigos ROP ternarios, uno de los cuales se usa para el fondo y el otro para el primer plano. Si un pixel del mapa de bits de la máscara es un uno, se aplicará el código ROP para el primer plano, si es cero, el del fondo.

Esto nos permite mostrar mapas de bits con formas no rectangulares, con una gran libertad a la hora de relacionar la imagen actualmente en pantalla con la nueva.

Códigos ROP ternarios

Existen quince valores posibles, que agruparemos según su función.

El primer grupo lo componen los códigos ROP en los que el segundo bitmap no tiene importancia ya que no se usa para obtener el resultado final:

  • BLACKNESS: rellena el área indicada con el color asociado al índice 0 de la paleta física. Este color es negro en la paleta física por defecto.
  • DSTINVERT: invierte el área de destino. Invertir significa, aplicar el operador de bits NOT para cada valor de color pixel. El resultado en pantalla es un negativo fotográfico.
  • WHITENESS: Rellena el rectángulo de destino usando el color asociado al índice 1 de la paleta física. (Este color es blanco para la paleta física por defecto.)

Hay dos códigos que implican al pincel asociado actualmente al contexto de dispositivo, que puede ser de cualquier tipo, y no necesariamente un creado a partir de un mapa de bits:

  • PATCOPY: rellena el área indicada con el pincel actual.
  • PATINVERT: combina los colores del pincel actual con los colores en el rectángulo destino usando el operador boolenao XOR.

El resto de los códigos ROP implican un segundo mapa de bits:

  • MERGECOPY: mezcla los colores en el rectángulo de origen con el patrón especificado usando la operación booleana AND.
  • MERGEPAINT: mezcla los colores invertidos del rectángulo de origen con los colores del rectángulo de destino usando la operación boolena OR.
  • NOTSRCCOPY: copia el rectángulo de origen invertido al rectángulo de destino.
  • NOTSRCERASE: combina los colores de los rectángulos de origen y destino usando el operador booleno OR y después invierte el color resultante.
  • PATPAINT: combina los colores del patrón con los colores invertidos del rectángulo de origen usando el operador booleano OR. El resultado de esta operación se combina con los colores del rectángulo de destino usando el operador booleano OR.
  • SRCAND: combina los colores de los rectángulos de origen y destino usando el operador booleano AND.
  • SRCCOPY: copia el rectángulo de origen directamente en el rectángulo de destino.
  • SRCERASE: combina los colores invertidos el rectángulo de destino con los colores del rectángulo de origen usando el operador booleano AND.
  • SRCINVERT: combina los colores de los rectángulos de origen y destino usando el operador booleano XOR.
  • SRCPAINT: combina los colores de los rectángulos de origen y destino usando el operador booleano OR.

Códigos ROP cuádruples

La función MaskBlt requiere como parámetro un código ROP cuádruple, estos códigos se obtienen combinando dos códgos ROP ternarios mediante la macro MAKEROP4.

Pinceles creados a partir de mapas de bits

Es posible crear pinceles de tramas basadas en mapas de bits, como adelantamos en el capítulo 20. Para hacerlo se usa la función CreatePatternBrush.

   HBRUSH pincel, anterior;
   HBITMAP hBitmapLazo;

   hBitmapLazo = LoadBitmap(hInstance, "Lazo");
   pincel = CreatePatternBrush(lazo);
   anterior = SelectObject(hDC, pincel);
...
   SelectObject(hDC, anterior);  
   DeleteObject(pincel);
   DeleteObject(hBitmapLazo);

Existen además dos funciones para rellenar superficies trabajando con pinceles.

PatBlt

Sirve para rellenar un área rectangular usando el pincel actual. Además, se puede especificar un código ROP para indicar el modo en que deben combinarse el pincel con el fondo. No todos los código ROP se pueden usar con esta función, solo:

ROP Descripción
PATCOPY Copia el patrón al mapa de bits de destino.
PATINVERT Combina el mapa de bits de destino con el patrón usando el operador OR.
DSTINVERT Invierte el mapa de bits de destino.
BLACKNESS Pone todas las salidas a ceros.
WHITENESS Pone todas las salidas a unos.

ExtFloodFill

También sirve para rellenar áreas usando el pincel actual, pero ExtFloodFill permite que el área a rellenar sea irregular. Hay que indicar el punto donde se empieza a rellenar y el color del recinto que delimita el área. No se pueden especificar códigos ROP.

Nota: Existe una función FloodFill sirve para lo mismo que ExtFloodFill, pero proviene de versiones anteriores del API, y actualmente se considera obsoleta, tan sólo se mantiene por compatibilidad.

Estructuras de datos

Existen varias estructuras de datos relacionadas con los mapas de bits, aunque la mayoría están relacionadas con paletas o con el modo de almacenar los datos que contienen o son muy específicos de mapas de bits independientes del dispositivo. Sin embargo hay una estructura que nos resultará muy útil:

BITMAP

La estructura es:

typedef struct tagBITMAP {  // bm 
   LONG   bmType; 
   LONG   bmWidth; 
   LONG   bmHeight; 
   LONG   bmWidthBytes; 
   WORD   bmPlanes; 
   WORD   bmBitsPixel; 
   LPVOID bmBits; 
} BITMAP;

Los datos que más nos interesan son bmWidth y bmHeight, que indican las dimensiones de anchura y altura, respectivamente, del mapa de bits. El resto de los datos, al menos de momento, no nos interesan.

Para obtener los datos de esta estructura para un mapa de bits concreto, se usa la función GetObject, por ejemplo:

    HBITMAP hBitmap;
    BITMAP bm;
    
    GetObject(hBitmap, sizeof(BITMAP), (LPSTR)&bm);

El primer parámetro es el manipulador del mapa de bits que nos interesa, el segundo el tamaño de la estructura BITMAP y el tercero un puntero a la estructura que recibirá los datos.

Modos de estiramiento (stretch modes)

Vimos antes, al hablar de la función StretchBlt que existen varios modos diferentes de estiramiento, o mejor dicho, de estrechamiento, ya que estos modos afectan al mapa de bits mostrado cuando se pierden puntos. En concreto existen cuatro, (los otros cuatro son equivalentes para Windows 95), comentaremos algo sobre ellos:

  • BLACKONWHITE: cuando se pierden puntos, se agrupan realizando una operación booleana AND entre los pixels eliminados. Si el mapa de bits es monocromo, este modo conserva los pixels negros a costa de los blancos.
  • COLORONCOLOR: los puntos perdidos no se tienen en cuenta. Este modo borra todas las líneas de pixels que no se visualizan, sin intentar preservar su información.
  • HALFTONE: proyecta los pixels del mapa de bits en el rectángulo de destino, el color medio de los pixels que resultan agrupados se aproxima de este modo al original.
    Si se activa este modo, hay que usar la función SetBrushOrgEx para cambiar el origen del pincel. Si se falla al hacerlo, habrá un desalineamiento del pincel.
  • WHITEONBLACK: cuando se pierden puntos, se agrupan realizando una operación booleana OR entre los pixels eliminados. Si el mapa de bits es monocromo, este modo conserva los pixels blancos a costa de los negros.

Los modos BLACKONWHITE y WHITEONBLACK se usan sobre todo con mapas de bits monocromo, los otros dos con mapas de bits en color. El modo HALFTONE proporciona mucho mejor resultado, pero es mucho más lento, además, require usar la función SetBrushOrgEx.

También existen dos funciones relacionadas con estos modos: GetStretchBltMode para averiguar el modo actual de un contexto de dispositivo, y SetStretchBltMode para cambiarlo.

Mapas de bits de stock

Aunque no es probable que nos resulten útiles, también existen mapas de bits de stock, que son los que se usan para las barras de deslizamiento y los botones de minimizar, maximizar, etc.

Podemos obtener esos mapas de bits usando las funciones LoadBitmap y LoadImage, usando NULL como manipulador de instancia y uno de los identificadores especiales para los mapas de bits:

    HDC memDC;
    BITMAP bm;
    HBITMAP hBitmap;
    RECT re;

    memDC = CreateCompatibleDC(hDC);
    hBitmap = LoadBitmap(NULL, MAKEINTRESOURCE(OBM_CLOSE));
    SelectObject(memDC, hBitmap);
    GetObject(hBitmap, sizeof(BITMAP), (LPSTR)&bm);
    BitBlt(hDC, 10, 10, bm.bmWidth, bm.bmHeight, memDC, 0, 0, SRCCOPY);
    DeleteObject(hBitmap);
    DeleteDC(memDC);

Ejemplo 21

Nota: Algunas de las funciones usadas en este ejemplo sólo funcionan en Windows NT o versiones superiores. El mayor tamaño de este ejemplo se debe a que incluye los ficheros de mapas de bits de ejemplo.


  Nombre Fichero Fecha Tamaño Contador Descarga
D Ejemplo 21 win021.zip 2004-01-18 306921 bytes 869