Secuela del artículo sobre formas no rectangulares (por ejemplo, poligonales)

Formas no rectangulares - Parte II

Copyright © 2001 Alirio A. Gavidia B.. Para ver más de
mis artículos visite Programación Orientada en Delphi

EurekaLog - Capture y registre cada excepción!

Introducción

Esta es la segunda parte de un artículo orientado a la utilización de formas no rectangulares con Delphi bajo Windows. En la parte anterior se le dio forma no rectangular a un formulario creando un reloj de agujas como ejemplo. En esta parte se pretende poder crear formularios cuyas formas se definan desde algún archivo externo a la aplicación. Tal comportamiento corresponde al muy difundido uso de "Skins". Como ejemplo "Windows Media Player".

Implantación básica de "skins"

La primera aproximación que aquí presentamos no sólo nos permite definir el fondo sino además la figura del formulario. En el mercado existe un componente VCL que hace este trabajo y más. El referido componente VCL hace uso de dos archivos BMP, el primero con la figura en blanco y negro y el segundo con el fondo a todo color. Para el presente ensayo se combinan estas dos características en un sólo archivo, simplemente tomando el color del primer pixel como color transparente, en realidad como color que permite definir el límite de la figura (hay una pequeña diferencia). El resto del dibujo, dentro de la figura, se toma como fondo del formulario.

Nota: El uso de BMP tiene muchos contras. Los principales: tamaño y escalabilidad. Del primero quizás la alternativa sería un JPG, pero el proceso de compresión altera la frontera de la figura generando colores intermedios y la consecuencia es un frontera difuminada. Por otro lado están los GIF, pero han sido desterrados de mis rutinas, librerías y funciones debido a que cierta empresa pretende ahora cobrar derechos por el uso de los algoritmos de compresión presentes en los archivos tipo GIF. En cuanto a la escalabilidad, una alternativa es el uso de Metafile, mejor aún sospecho que los metafiles son definiciones de regiones completamente análogas a las que se utilizan aquí. Sin embargo dejaré el estudio de Metafiles para otra ocasión.

Una versión completa de un "Skinner" requiere poder definir el dibujo de fondo del formulario, su figura así como el de cada control presente. Nos concentraremos en el formulario dejando de lado los controles.

El fondo

En problema del dibujo de fondo es enfrentable de dos maneras a mi entender. Una es interceptando algún mensaje de Windows para dibujar sobre el canvas de fondo (como lo hacen algunos controles VCL para colocar fondos en formularios MDI y No-MDI) y la otra es colocando un control tipo TImage como el último control en el área cliente del formulario. Optaré por la última forma porque además será posible hacer uso de algunos eventos del TImage.

La forma

El problema de la figura reside básicamente en crear un región de determinada forma. La forma determinante que he asumido es dada a través de un bitmap. Así que por ejemplo un bitmap consistente en un círculo azul en un fondo blanco debe generarme un formulario visible como un círculo. La figura puede ser casi cualquier cosa como el rostro de Mickey Mouse o la silueta de una araña. Por ello se leerá el bitmap para generar a partir de él una forma poligonal, eso nos da libertad, pero elimina en mucho las posibilidades de escalamiento.

Paso a paso

En principio el algoritmo va así:

  1. Se ajusta el formulario a las dimensiones del archivo BMP. Esto es simple. Para ello bastan las siguientes líneas de código:

    // Ajustar el área / to Ajust the area
    ClientWidth := Image1.Width;
    ClientHeight:= Image1.Height;

  2. Se lee el primer punto del formulario y se toma como "transparente".

  3. Se busca el primer punto no "transparente" y a partir de allí se genera un polígono con la silueta de la parte no "transparente" (ver GenRegion en el archivo ffigura.pas).

  4. Eliminamos la barra de título (si aún nos estorba). Para ello:

    BorderStyle := bsNone; // Titulo estorba / no Title

  5. Cambiamos la figura del formulario conforme al polígono. Para ello hacemos uso ahora de la rutina CreatePolygonRgn y SetWindowRgn (ver ActiveSkin en ffigura.pas). A diferencia del ejemplo de la primera parte de este artículo, ya no definimos una región elíptica (circular).

  6. Establecemos desde el TImage los eventos necesarios para el arrastre de formularios como se hizo en el ejemplo del primer artículo.

Excepto por el tercer punto nada parece en extremo difícil. Para generar la región poligonal sólo se busca la frontera de la figura, luego se avanza por toda la orilla de la figura hasta estar en el punto inicial (o acabar con la memoria, en este caso 1024 puntos). Esta serie de puntos definen un polígono para generar un región con la función CreatePolygonRgn. Luego se utiliza SetWindowRgn y ya se tiene el formulario con la silueta dada. Funciona pero tiene limitaciones: nuestra figura debe tener una sola frontera, por ello no puede ser una figura con un agujero en el medio (en todo caso el algoritmo ignorará el agujero en el medio).

Como de costumbre este artículo va acompañado con un ejemplo para ilustrar los puntos aquí referidos.

Nota: El algoritmo que sigue la silueta dada en el bitmap es relativamente lento y tosco, sin embargo es mantenible y realiza una cierta optimización con figuras no muy complejas. Hay un límite dado que se genera un arreglo en memoria cuando aún no se conoce cuantos puntos definirán la figura. Queda al usuario el variar este límite a su conveniencia.

Los controles

El ejemplo adjunto a este artículo contiene además botones con forma. En modo de diseño son cuadrados pues son de tipo TBitBtn, pero en el evento OnCreate del formulario se le aplica a cada botón la forma del gráfico que contiene, de la misma manera que usamos para el formulario en el procedimiento ActiveSkin:

procedure TForm1.FormCreate(Sender: TObject);
Var
  hRegion : Thandle;
  Par     : Array [0..1024] of TPoint; // Data -> region
  Cnt     : Integer;
begin
  Cnt := GenRegion(Par, BitBtn1.Glyph,
                  (BitBtn1.Width - BitBtn1.Glyph.Width+1) div 2,
                  (BitBtn1.Height - BitBtn1.Glyph.Height+1) div 2);
  hRegion := CreatePolygonRgn(Par, Cnt, ALTERNATE);
  SetWindowRgn(BitBtn1.handle, hRegion, true);
  ...

Esto demuestra que la definición de regiones funciona más allá de las ventanas.

Más allá

El problema del tamaño del BMP sigue latente, pero prodríamos empaquetar el BMP y usarlo empaquetado como hace Winamp. Para este enfoque hay dos vías: podríamos usar algún algoritmo casi propietario semi-secreto o poco común (inevitablemente pienso en el Windows Media Player) o algo como el Zip. Esta última opción tiene la ventaja de que no estamos restringidos en cuanto a su uso. Pueden encontrar información para saber como manejarla en el sitio web de Latium Software:

Si las imágenes son simples, como las que usé en el ejemplo, puede guardarlas usando 16 colores con compresión RLE 4 (esto puede hacerse por ejemplo con el viejo y querido Borland Resource Workshop que viene en el CD de instalación de Delphi).

Lo que otros hacen

Deberíamos separar las áreas del formulario en cliente y no cliente, así como considerar los captions. Eventualmente podríamos usar semi-transparencias, lo que será cada vez más común (gracias a Steve Jobs por los favores recibidos, aunque no utilicemos todos una Mac ;)

Por allí hay una librería que considera sombras, animación, morphing y hasta un lenguaje de scripts, pero van más allá del objetivo de este artículo.

 
Los fuentes de este artículo están a su disposición para descargar.

JfControls Library - para Delphi y C++ Builder