Capítulo 22 Objetos básicos del GDI: La paleta (Palette)

El color es muy importante en Windows, y como todo, es un recurso que tiene sus limitaciones. Cada dispositivo tiene sus capacidades de colores, y el API proporciona funciones para conocer esas capacidades, así como manipularlos, elegirlos, activarlos, etc.

Tal vez, al menos a nivel de pantalla, ya no tenga mucho sentido un capítulo como el presente, las tarjetas gráficas actuales ya no tienen las limitaciones en cuanto a color que tenían hace unos años. Pero en el capítulo dedicado a los mapas de bits veremos que cuando se almacenan en disco o se transmiten, usar paletas nos ahorrará mucho espacio de almacenamiento y mucho tiempo en transmisiones.

Capacidades de Color de los dispositivos

La capacidad de cada dispositivo: pantallas e impresoras, puede variar entre dos y miles o millones de colores. Eso suele ser debido a alguna propiedad física del dispositivo, por ejemplo, una impresora que sólo disponga de un cartucho de tinta negra, sólo podrá visualizar dos colores: blanco y negro. Del mismo modo, una tarjeta gráfica puede estar limitada por la memoria, y sólo disponer de 16 ó 256 colores, o un monitor en blanco y negro, que sólo disponga de una determinada gama de grises.

A menudo necesitaremos conocer las capacidades de los dispositivos con los que estamos trabajando, y así poder adaptar nuestras aplicaciones de modo que la apariencia de los resultados sea lo más parecido posible a lo que queremos.

Podemos averiguar el número de colores que disponibles en un dispositivo usando la función GetDeviceCaps con el parámetro NUMCOLORS. El número obtenido será el número de colores disponibles para la aplicación.

Definiciones de valores de color

Existen varios modos de codificar los valores de color, Windows usa la síntesis aditiva, expresando las intensidades relativas de los colores primarios: rojo, verde y azul. Para cada color se usan ocho bits, por lo tanto existen 256 valores posibles para cada componente, es decir un máximo de 16.777.216 colores. Los tres bytes forman un triplete RGB y se empaquetan en un entero de 32 bits, de los cuales, los 24 de menor peso se usan para las componentes y el resto a veces se usa para otras funciones que veremos más adelante.

Para almacenar valores de color se usa el tipo COLORREF. Ya hemos usado este tipo antes, por ejemplo para crear plumas con CreatePen o para crear pinceles sólidos con CreateSolidBrush, o en estructuras como LOGPEN. Si se necesita extraer los valores individuales de los componentes de color de un valor COLORREF se pueden usar las macros GetRValue, GetGValue y GetBValue, para rojo, verde y azul respectivamente. También se pueden crear valores de color a partir de los componentes individuales usando la macro RGB.

En las paletas lógicas, se usa la estructura RGBQUAD para definir valores de colores o para examinar valores de componentes.

Aproximaciones de colores y mezclas de pixels (dithering)

En cualquier caso, es posible usar colores sin preocuparse demasiado de las capacidades del dispositivo. Por ejemplo, nada impide crear una pluma verde para una impresora en blanco y negro. Cuando pedimos un color que el dispositivo no admite, Windows escoge otro color entre los que sí puede generar, intentado elegir el más parecido. En nuestro ejemplo, Windows crearía una pluma negra.

El API dispone de la función GetNearestColor que admite como parámetro un valor de color y devuelve el más parecido que puede generar el dispositivo. Esto nos ayuda a predecir qué color obtendremos en cada caso.

Las aproximaciones siempre se usan cuando se eligen colores para plumas o para textos, pero cuando se eligen colores para pinceles sólidos Windows puede intentar simular el color mediante tramas de pixels de distintos colores elegidos entre los que el dispositivo sí puede generar.

En este ejemplo vemos cómo se simulan distintos tonos de verde mezclando los tonos de verde disponibles con negro:

Degradado
Degradado

No se dispone de ningún mecanismo para controlar cómo hace Windows estas mezclas, ya que dependen del driver del dispositivo. Lo que sí podemos hacer es crear nuestros propios pinceles usando tramas de mapas de bits.

Mezclas de colores (ROP)

Cuando dibujamos en pantalla usando una pluma o un pincel, no tenemos por qué limitarnos a activar pixels, es posible combinar el nuevo color con el color previo de cada pixel en pantalla. A esto se le denomina mezcla de colores.

Existen distintos modos de mezcla de primer plano, u operaciones binarias de rastreo, que determinan el modo en que se combinan los colores nuevos con los existentes en pantalla previamente. Es posible fundir colores, conservando todos los componentes de ambos colores; enmascarar colores, eliminando o atenuando componentes no comunes; o enmascarar exclusivamente, eliminando o atenuando componentes comunes. Además hay variaciones sobre estas operaciones de mezcla básicas.

Veremos este tema en profundidad más adelante, en un capítulo dedicado a él.

Los colores obtenidos mediante la mezcla también se someten a aproximaciones de color. Si el color obtenido de una mezcla no puede ser mostrado por el dispositivo, Windows genera una aproximación. Como estas operaciones se hacen pixel a pixel, si el color en pantalla proviene de una mezcla de pixels, serán los pixels individuales los que se combinen.

Para seleccionar el modo de mezcla de primer plano se usa la función SetROP2 y para recuperar la actual se usa GetROP2.

Nota: También existe un modo de mezcla de fondo, pero este modo no controla la mezcla de colores. En realidad especifica si el color de fondo será usado cuando se trazan líneas con estilos, pinceles de tramas y texto.

Paletas de colores

Una paleta de colores es un conjunto que contiene valores de colores que pueden ser mostrados en el dispositivo de salida.

En Windows existen dos tipos de paletas: paletas lógicas y paletas de sistema. Dentro de las lógicas existe una especial, la paleta por defecto, que es la que se usa si el usuario no crea una.

Las paletas de colores se suelen usar en dispositivos que, aunque pueden generar muchos colores, sólo pueden mostrar o dibujar con un subconjunto de ellos en un momento dado. Para estos dispositivos, Windows mantiene una paleta de sistema que permite almacenar y manejar los colores actuales del dispositivo.

Windows no permite acceder a la paleta de sistema directamente, en vez de eso, los accesos se hacen mediante una paleta lógica. Además, Windows crea una paleta por defecto para cada contexto de dispositivo. Como programadores, podemos usar los colores de la paleta por defecto o bien crear nuestra propia paleta lógica y asociarla al contexto de dispositivo.

Para determinar si un dispositivo soporta paletas de colores de puede comprobar el bit RC_PALETTE del valor RASTERCAPS devuelto por la función GetDeviceCaps.

La paleta por defecto

Como ya hemos dicho, Windows asocia la paleta por defecto con un contexto cada vez que una aplicación crea un contexto para un dispositivo que soporte paletas de colores. De este modo Windows se asegura de que existen colores disponibles para usar en la aplicación sin necesidad de otras acciones.

La paleta por defecto normalmente tiene 20 entradas, pero ese número depende del dispositivo, y es igual al valor NUMCOLORS devuelto por GetDeviceCaps.

Los colores de la paleta por defecto dependen del dispositivo, en dispositivos de pantalla pueden ser los 16 colores estándar de VGA más cuatro definidos por Windows.

Cuando se usa la paleta por defecto, las aplicaciones pueden usar valores de color para especificar el color de plumas o texto. Si el color requerido no se encuentra en la paleta, Windows aproxima el color usando el más parecido de la paleta. Si una aplicación pide un pincel sólido de un color que no está en la paleta, Windows simula el color mediante mezcla de pixels con colores presentes en la paleta.

Para evitar aproximaciones y mezclas de pixels, se pueden especificar colores para plumas, pinceles y texto usando índices de paleta de colores en lugar de valores de colores. Un índice de paleta de colores es un entero que identifica una entrada en una paleta específica. Se pueden usar índices de paleta en lugar de valores de color, pero se debe usar la macro PALETTEINDEX para crearlos.

Los índices de paleta sólo se pueden usar en dispositivos que soporten paletas de color. Esto puede hacer que nuestros programas sean dependientes del dispositivo, ya que no podremos usar índices para cualquier dispositivo. Para evitarlo, cuando se usa el mismo código para dibujar tanto en dispositivos con o sin paleta, se deben usar valores de color relativos a la paleta para especificar colores de plumas, pinceles o texto. Estos valores serán idénticos a los valores de colores, excepto cuando se crean pinceles sólidos.

En dispositivos con paleta, un color de pincel sólido especificado por un valor de color relativo a la paleta está sujeto a aproximación de color en lugar de a mezcla de pixels. Para crear valores de color relativos a la paleta se debe usar la macro PALETTERGB.

Windows no permite cambiar las entradas de la paleta por defecto. Si se quiere usar otros colores en lugar de los que ésta contiene, se debe crear una paleta lógica propia y seleccionarla en el contexto de dispositivo.

Paleta lógica

Una paleta lógica es una paleta que crea una aplicación y que se asocia con un contexto de dispositivo dado.

Para crear una paleta lógica se usa la función CreatePalette. Antes es necesario llenar la estructura LOGPALETTE, que especifica el número de entradas en la paleta y el valor de cada una, después se debe pasar esa estructura a la función CreatePalette. La función devuelve un manipulador de paleta que puede usarse en otras operaciones para identificar la paleta.

#define NUMCOLORES 18
...
    PALETTEENTRY Color[NUMCOLORES] = {
       //peRed, peGreen, peBlue, peFlags
       {0,0,0,PC_NOCOLLAPSE,
       {0,20,0,PC_NOCOLLAPSE,
...
       ;
    LOGPALETTE *logPaleta;
...
           // Crear una paleta nueva:
           logPaleta = (LOGPALETTE*)malloc(sizeof(LOGPALETTE) + 
              sizeof(PALETTEENTRY) * NUMCOLORES);
           if(!logPaleta)
              MessageBox(hwnd, "No pude bloquear la memoria de la paleta", 
                 "Error", MB_OK);

           logPaleta->palVersion = 0x300;
           logPaleta->palNumEntries = NUMCOLORES;
           for(i = 0; i < NUMCOLORES; i++) {
              logPaleta->palPalEntry[i].peBlue  = Color[i].peBlue;
              logPaleta->palPalEntry[i].peGreen = Color[i].peGreen;
              logPaleta->palPalEntry[i].peRed   = Color[i].peRed;
              logPaleta->palPalEntry[i].peFlags = Color[i].peFlags;
           

           hPaleta = CreatePalette(logPaleta);
           if(!hPaleta)
              MessageBox(hwnd, "No pude crear la paleta", "Error", MB_OK);
           free(logPaleta);

Para usar los colores de una paleta lógica, la aplicación selecciona la paleta dentro de un contexto de dispositivo, usando la función SelectPalette. Los colores de la paleta estarán disponibles tan pronto como la paleta sea activada.

        case WM_PAINT:
           hDC = BeginPaint(hwnd, &ps);
           hPaletaOld = SelectPalette(hDC, hPaleta, FALSE);
           hBrush = CreateSolidBrush(PALETTEINDEX(6));
           hOldBrush = (HBRUSH)SelectObject(hDC, hBrush);
           Rectangle(hDC, 20, 20, 40, 40);
           SelectObject(hDC, hOldBrush);
           DeleteObject(hBrush);
           SelectPalette(hDC, hPaletaOld, FALSE);
           EndPaint(hwnd, &ps);
           break;

Es posible limitar el tamaño de las paletas lógicas para permitir las entradas que representen sólo los colores necesarios. Además, no se pueden crear paletas lógicas más grandes que el tamaño máximo de paleta, y ese valor depende del dispositivo. Para obtener el tamaño máximo de la paleta se usa la función GetDeviceCaps para recuperar el valor del valor SIZEPALETTE.

Si bien es posible especificar cualquier color para una entrada de una paleta lógica, es posible que no todos los colores puedan ser generados por el dispositivo. Windows no proporciona una forma de averiguar qué colores son soportados, pero la aplicación puede descubrir el número total de esos colores leyendo la resolución de color del dispositivo. La resolución de color, especificada en bits de colores por pixel, es igual al valor COLORRES revuelto por la función GetDeviceCaps. Un dispositivo que tenga una resolución de color de 18 tiene 262.144 colores posibles. Si una aplicación pide un color no soportado, Windows elige una aproximación apropiada.

Una vez que una paleta lógica a sido creada, se pueden cambiar colores dentro de ella usando la función SetPaletteEntries. Si la paleta lógica ha sido seleccionada y activada, los cambios no afectarán inmediatamente a los colores actualmente mostrados. Para eso es necesario usar las funciones UnrealizeObject y RealizePalette, de ese modo los colores de la pantalla se actualizarán. En algunos casos, puede ser necesario deseleccionar, desactivar, seleccionar y activar una paleta lógica para asegurarse de que los colores se actualizarán exactamente como se requiere. Si se selecciona una paleta lógica en más de un contexto de dispositivo, los cambios en la paleta lógica afectan a todos los contextos de dispositivo en las que ha sido seleccionada.

Se puede cambiar el número de entradas de una paleta lógica usando la función ResizePalette. Si la aplicación reduce el tamaño, las entradas que quedan permanecen sin cambios. Si se aumenta el tamaño, Windows asigna los colores para cada nueva entrada a negro (0, 0, 0) y el banderín a cero.

También es posible recuperar los valores del color y del banderín para entradas en una paleta lógicas dada mediante la función GetPaletteEntries. Se puede recuperar el índice para una entrada dentro de una paleta lógica que coincida lo más cercanamente posible con un color especificado usando la función GetNearestPaletteIndex.

Cuando no se necesite más una paleta lógica, se debe eliminar usando la función DeleteObject. Hay que asegurarse de que la paleta lógica no permanece seleccionada dentro de un contexto de dispositivo antes de borrarla.

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

Paleta de sistema

Windows mantiene una paleta de sistema para cada dispositivo que use paletas. Esta paleta contiene los valores de todos los colores que pueden ser mostrados o usados actualmente por el dispositivo. Las aplicaciones no tienen acceso a la paleta de sistema directamente, al contrario, Windows tiene control absoluto de la paleta del sistema y permite el acceso sólo a través de las paletas lógicas.

Para ver el contenido de la paleta del sistema se usa la función GetSystemPaletteEntries. Esta función recupera el contenido de una o más entradas, hasta el número total de entradas en la paleta de sistema. El número total es siempre el mismo que el devuelto para el valor SIZEPALETTE de la función GetDeviceCaps y es el mismo que el tamaño máximo de cualquier paleta lógica dada.

Ya hemos dicho que no es posible modificar los colores de la paleta de sistema directamente, sino sólo cuando se activan paletas lógicas. Antes de activar una paleta, Windows examina cada color requerido e intenta encontrar una entrada en la paleta del sistema que coincida exactamente. Si Windows encuentra ese color, asigna el índice de la paleta lógica para que corresponda con ese índice de la paleta del sistema. Si no lo encuentra, se copia el color requerido en una entrada no usada de la paleta de sistema antes de asignar el índice. Si todas las entradas de la paleta de sistema están en uso, Windows asigna al índice de la paleta lógica la entrada de la paleta de sistema cuyo color sea lo más parecido posible al color requerido. Una vez que los índices han sido asignados, no es posible ignorarlos. Por ejemplo, no es posible usar índices de la paleta de sistema para especificar colores, sólo se permite el uso de índices de la paleta lógica.

Se puede modificar el modo en que los índices serán asignados cuando se seleccionen los valores de la paleta lógica mediante el miembro peFlags de la estructura PALETTEENTRY. Por ejemplo, el banderín PC_NOCOLLAPSE indica a Windows que copie inmediatamente el color requerido en una entrada no usada de la paleta de sistema aunque la paleta de sistema ya contenga ese color. Además, el flag PC_EXPLICIT indica a Windows que le asigne al índice de paleta lógica un índice esplícito de la paleta de sistema. (Para eso se debe dar el índice de la paleta de sistema en la palabra de menor orden de la estructura PALETTEENTRY).

Las paletas pueden ser activadas como paleta de fondo o como paleta de primer plano espeficando TRUE o FALSE para el parámetro bForceBackground en la función SelectPalette, respectivamente. Sólo puede existir una paleta de primer plano en el sistema al mismo tiempo. Si una ventana o una descendiente suya es la activa actualmente, puede activar una paleta de primer plano. En caso contrario la paleta se activa como paleta de fondo, independientemente del valor del parámetro bForceBackground. La principal propiedad de una paleta de primer plano es que cuando se activa, puede sobrescribir todas las entradas de la paleta de sistema (excepto las estáticas). Windows lo permite marcando las entradas de la paleta de sistema que no sean estáticas como no usadas antes de activar una paleta de primer plano, pudiendo eliminarse todas las entradas usadas. La paleta de primer plano usa todos los colores no estáticos posibles. Las de fondo sólo pueden usar las que permanezcan libres y que estén disponibles para el primero que las solicite. Normalmente, se usan paletas de fondo con ventanas hijas que activen sus propias paletas. Esto ayuda a minimizar el número de cambios en la paleta de sistema.

Una entrada de paleta de sistema no usada es cualquiera que no está reservada y que no contenga un color estático.

En estos tiempos de potentes tarjetas gráficas, con millones de colores y enormes velocidades de proceso, este tipo de preocupaciones ha pasado (felizmente) a la historia. No hace muchos años, muchas tarjetas gráficas limitaban sus paletas a 16 ó 256 colores, aunque fueran capaces de mostrar muchos más. El resultado es que frecuentemente distintas aplicaciones activaban diferentes paletas. Sólo la aplicación que tiene el foco puede activar una paleta de primer plano, de modo que las aplicaciones que perdían el foco también podían ver modificados gran parte de sus colores. El resultado es que los colores de las aplicaciones cambiaban continuamente al cambiar de aplicación, creando un efecto molesto y poco estético.

Las entradas reservadas están marcadas explícitamente con el valor PC_RESERVED. Esas entradas se crean cuando se activan paletas lógicas para animanciones de paleta. Las entradas de color estáticas se crean por Windows y corresponden a los colores de la paleta por defecto. Se puede usar la función GetDeviceCaps para recuperar el valor NUMRESERVED, que especifica el número de entradas de la paleta de sistema reservados para colores estáticos.

Ya que la paleta de sistema tiene un número de entradas limitado, la selección y activación de una paleta lógica para un dispositivo dado puede afectar a los colores asociados con otras paletas lógicas del mismo dispositivo.

Esos cambios de color son especialmente dramáticos cuando se dan en una pantalla. Para asegurarse de que se usan los colores de una paleta lógica seleccionada del modo más fiel hay que resetear la paleta antes de usarla. Esto se hace llamando a las funciones UnrealizeObject y RealizePalette. Usando estas funciones se obliga a Windows a reasignar los colores de la paleta lógica a colores adecuados de la paleta de sistema.

Ejemplo 20

Nota: Este ejemplo funcionará con configuraciones de pantalla de 256 colores o menos, de un modo diferente a si existen más colores. En el caso de pantallas con 256 o menos colores, la paleta creada usará mezclas de pixels.


  Nombre Fichero Fecha Tamaño Contador Descarga
D Ejemplo 20 win020.zip 2004-01-18 6366 bytes 340