[ contenidos *| #winprog ]

Bitmaps Transparentes

Ejemplo: bmp_two

[images/bmp_two.gif]

Transparencia

Darles a los bitmaps la apariencia de tener secciones transparentes es bastante simple e involucra el uso de una Máscara blanca y negra junto al color de la imagen que queremos que luzca transparente.

Para que el efecto funcione apropiadamente se necesitan reunir las siguientes condiciones: Primero la imagen debe ser de color negro en todas las áreas que queremos que luzca transparente. Segundo, La máscara debe ser blanca en las áreas que queremos que sean transparentes y negro en caso contrario. La imágen y la máscara son mostradas en la figura incluída como ejemplo en esta sección y corresponden a las dos imágenes que se encuentran alineadas mas a la izquierda.

Operaciones BitBlt

¿Como hace esto para proveernos transparencia? Primero usamos BitBlt( ) sobre la máscara usando la operación SRCAND como último parámetro y luego encima de esto usamos BitBlt( ) sobre la imagen usando la operacion SRCPAINT. El resultado es que las áreas que queremos que sean transparentes no cambian en el HDC destino mientras el resto de la imagen es dibujada de forma usual.

    SelectObject(hdcMem, g_hbmMask);
    BitBlt(hdc, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCAND);

    SelectObject(hdcMem, g_hbmBall);
    BitBlt(hdc, 0, bm.bmHeight, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCPAINT);

Bastante simple eh? Afortunadamente lo es, pero resta una pregunta... ¿de donde viene la máscara? Hay básicamente dos formas de obtener la máscara...

Debido a que la primer opción no es nada nuevo, deberías ser capaz de hacerlo si lo deseas. La segunda forma proviene de aplicar algunos malabares usando BitBlt( ) y por lo tanto voy a mostrar una forma de realizarla.

Como crear una Máscara

La forma más simple de hacerlo es recorrer cada pixel en la imagen, chequear su valor y luego poner en el correspondiente pixel de la máscara el color blanco o negro. Si usamos BitBlt( ) (usando SRCCOPY) desde un HDC que referencia una imagen a color dentro de un HDC que referencia una imagen blanco y negro, éste chequeará que color está especificado como color de fondo en la imagen a color y pondrá todos aquellos pixeles en Blanco, culaquier otro pixel que no sea del color del fondo terminará siendo Negro.

Esto funciona perfectamente, debido a que todos lo que necesitamos hacer es poner el color de fondo con el color que queremos transparentar y hacemos BitBlt( ) desde nuestra imagen a la máscara. Observa que esto solo funciona con una máscara monocromo (blanco y negro)... esto es, bitmaps que solo tienen 1 bit per pixel para definir el color. Si intentamos esto con una imagen a color que sólo tiene pixeles blanco y negro, pero que el tamaño para definir el color por pixel es mayor que un bit, digamos 16 bits o 24 bits, esto no funciona.

¿Receuerdas la primer condición anterior para obtener máscaras exitosas? Esta decía que nuestra imagen necesitaba ser negra donde queríamos que fuera transparente. Debido a que el bitmap que he usado en este ejemplo ya cumple esa condición, no necesita ningún tratamiento especial. Pero si deseas usar este código en otra imagen que tiene un color diferente para transparentar (rosa es una elección común), entonces necesitamos tomar un segundo paso, esto es, usar la máscara que acabamos de crear para alterar la imagen original, donde todo lo que queremos transparentar es negro. No hay problema si otros lugares de la imagen también son de color negro, debido a que esos lugares no son blancos en la máscara y por lo tanto no serán tranparentes. Podemos realizar esto utilizando BitBlt( ) desde la nueva máscara a la imagen original, usando la operación SCRINVERT, la cual fija todas la áreas que son blancas en la máscara a negras en la imagen a color.

Esta es una parte de un proceso complejo, por lo tanto es bueno tener a mano una función muy útil que hace todo esto por nosotros, aquí está:

HBITMAP CreateBitmapMask(HBITMAP hbmColour, COLORREF crTransparent)
{
    HDC hdcMem, hdcMem2;
    HBITMAP hbmMask;
    BITMAP bm;

    //Crea una máscara monocromo (1 bit).	

    GetObject(hbmColour, sizeof(BITMAP), &bm);
    hbmMask = CreateBitmap(bm.bmWidth, bm.bmHeight, 1, 1, NULL);

    //Obtiene algunos HDCs que son compatibles con la driver de video	

    hdcMem = CreateCompatibleDC(0);
    hdcMem2 = CreateCompatibleDC(0);

    SelectBitmap(hdcMem, hbmColour);
    SelectBitmap(hdcMem2, hbmMask);

    //Ponemos el color de fondo de la imagen a color con el color que
    //queremos que sea transparente	    

    SetBkColor(hdcMem, crTransparent);

    //Copiamos los bits de la imagen a color a la máscara 
    //Blanco y Negro... todo lo que sea del color de fondo termina siendo
    //blanco y todo lo demás termina siendo negro... Como queríamos

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

    //Tomamos la nueva máscara y la usamos para poner el color transparente
    //en dentro de la imagen a color original, de esta manera el efecto
    //de transparencia funcionará bien. 

    BitBlt(hdcMem, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem2, 0, 0, SRCINVERT);

    // Liberamos.

    DeleteDC(hdcMem);
    DeleteDC(hdcMem2);

    return hbmMask;
}

NOTA: Esta función llama a SelectObject( ) para seleccionar temporalmente el bitmap a color que le pasamos dentro de un HDC. Un bitmap mo puede seleccionarse en mas de un HDC a la vez, por lo tanto asegúrate que el bitmap no esté seleccionado por otro HDC cuando llames a esta función, de ser así la función fallará.

Ahora que tenemos nuestra super función, podemos crear una máscara a partir de la imagen original una vez que la hallamos cargado:

    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);

        g_hbmMask = CreateBitmapMask(g_hbmBall, RGB(0, 0, 0));
        if(g_hbmMask == NULL)
            MessageBox(hwnd, "Could not create mask!", "Error", MB_OK | MB_ICONEXCLAMATION);
    break;

El segundo parámetro es, por su puesto, el color de la imagen original que queremos que sea transparente, en este caso es negro.

¿Como funciona todo esto?

... te estarás preguntando. Seguramente tu experiencia con C y C++ significa que comprendes operaciones binarias como OR, XOR, AND NOT etc... No voy a explicar este proceso completamente, pero intentaré mostrarte como lo he usado en este ejemplo. Si mi expicación no es lo suficientemente clara, puedes leer algo sobre operaciones binarias, que seguramente te ayudará a entenderlo. Comprender esto no es determinante para usar esta función y puedes ignorar como funciona confiando siempre en que lo hará correctamente.

SRCAND

La operación SCRAND o ROP (Raster Operation) para BitBlt( ), significa combinar los bits usando AND. Esto es: solo los bits que valen 1 en la imagen fuente Y en la imagen destino quedan el el resultado final. Usamos esto con nuestra máscara para poner en negro todos los pixeles que tendrán eventualmente un color de la imagen original. La máscara tiene el color negro (en binario, todos 0) donde queremos que halla color y blanco (todos 1 en binario) donde queremos que halla trasnsparencia. Cualquier valor combinado con 0, usando AND, es 0 y por lo tanto todos los pixeles que son negros en la máscara son puesto en 0 en el resultado final. Cualquier valor que es combinado con 1 usando AND permanece inalterable, por lo tanto si valía 1 al comienzo, finaliza valiendo 1 y si valía 0 finaliza valiendo 0... Por lo tanto todos los pixeles que eran blanco en nuestra máscara, no alteran su valor después de la llamada a BitBlt(). El resultado es el del la imagen de la esquina derecha del ejemplo.

SCRPAINT

SCRPAINT usa la operación OR, por lo tanto si al menos uno de los 2 bits vale 1, entonces el resultado valdrá 1. Usamos esto en la imagen a color. Cuando la parte negra (transparente) de nuestra imagen a color es combinada con los datos en el desitno usando OR, el resultado es que los datos permanecen inalterados, debido a que cualquier valor combinado con 0 usando la operación OR permanece igual.

Sin embargo, el resto de la imagen a color no es negro, y el si destino tampoco es negro, entonces tenemos una combinación de los colores fuente y destino. El resultado puede verse en la segunda bola de la segunda fila de la figura ejemplo. Esta es la razón por la cual primero usamos la máscara para poner en negro los pixeles que queremos que tengan color, ya que cuando usamos OR con la imagen a color, los pixeles coloreados no se mezclan con lo que haya dentro de ellos.

SCRINVERT

Esta es la operación XOR usada para poner en negro el color transparente en nuestra imagen original (Si no ya no es negro). Combinando un pixel negro de la máscara con un pixel que no sea del color de fondo en el destino lo deja inalterable, mientras que al combinar un pixel blanco de la máscara (que generamos definiendo un color particular como "fondo") con el color de un pixel del fondo del destino, esto hace que se cancele y se ponga en negro.

Ejemplo 2

El código ejemplo en el proyecto bmp_two que viene junto con esta sección contiene el código para la imagen del ejemplo de esta sección. Este consiste en primero dibujar la máscara y la imagen a color exactamente como son usando SRCCOPY, luego usa en cada una por separado las operaciones SRCAND y SRCPAINT repectivamente y finalmente las combina para producir el resultado final.

El fondo en este ejemplo es gris para hacer mas obvia la transparencia ya que usar estas operaciones sobre un fondo blanco o negro hace dificil darse cuenta si está funcionando o no.


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

Versión en español: Federico Pizarro - 2003