26 octubre 2007

Moviendo sprites con el teclado y el ratón

Basándome en el ejemplo del artículo anterior que mostraba como realizar el movimiento de sprites con fondo vamos a ver como el usuario puede mover los sprites usando el teclado y el ratón.

CAPTURANDO LOS EVENTOS DEL TECLADO

La clase TForm dispone de dos eventos para controlar las pulsaciones de teclado: OnKeyDown y OnKeyUp. Necesitamos ambos eventos porque no sólo me interesa saber cuando un usuario ha pulsado una tecla sino también cuando la ha soltado (para controlar las diagonales).

Para hacer esto voy a crear cuatro variables booleanas en la sección private el formulario que me van a informar de cuando están pulsadas las teclas del cursor:

type
private
{ Private declarations }
Sprite: TSprite;
Buffer, Fondo: TImage;
bDerecha, bIzquierda, bArriba, bAbajo: Boolean;

Estas variables las voy a actualizar en el evento OnKeyDown:

procedure TFormulario.FormKeyDown( Sender: TObject; var Key: Word; Shift: TShiftState );
begin
case key of
VK_LEFT: bIzquierda := True;
VK_DOWN: bAbajo := True;
VK_UP: bArriba := True;
VK_RIGHT: bDerecha := True;
end;
end;

y en el evento OnKeyUp:

procedure TFormulario.FormKeyUp( Sender: TObject; var Key: Word; Shift: TShiftState );
begin
case key of
VK_LEFT: bIzquierda := False;
VK_DOWN: bAbajo := False;
VK_UP: bArriba := False;
VK_RIGHT: bDerecha := False;
end;
end;

Al igual que hice con el ejemplo anterior voy a utilizar un temporizador (TTimer) llamado TmpTeclado con un intervalo que va a ser también de 10 milisegundos y cuyo evento OnTimer va a encargarse de dibujar el sprite en pantalla:

procedure TFormulario.TmpTecladoTimer( Sender: TObject );
begin
// ¿Ha pulsado la tecla izquierda?
if bIzquierda then
if Sprite.x > 0 then
Dec( Sprite.x );

// ¿Ha pulsado la tecla arriba?
if bArriba then
if Sprite.y > 0 then
Dec( Sprite.y );

// ¿Ha pulsado la tecla derecha?
if bDerecha then
if Sprite.x + Sprite.Imagen.Width < ClientWidth then
Inc( Sprite.x );

// ¿Ha pulsado la tecla abajo?
if bAbajo then
if Sprite.y + Sprite.Imagen.Height < ClientHeight then
Inc( Sprite.y );

DibujarSprite;
end;

Este evento comprueba la pulsación de todas las teclas controlando que el sprite no se salga del formulario. El procedimiento de DibujarSprite sería el siguiente:

procedure TFormulario.DibujarSprite;
var
Origen, Destino: TRect;
begin
// Copiamos el fondo de pantalla al buffer
Origen.Left := Sprite.x;
Origen.Top := Sprite.y;
Origen.Right := Sprite.x + Sprite.Imagen.Width;
Origen.Bottom := Sprite.y + Sprite.Imagen.Height;
Destino.Left := 0;
Destino.Top := 0;
Destino.Right := Sprite.Imagen.Width;
Destino.Bottom := Sprite.Imagen.Height;
Buffer.Canvas.CopyMode := cmSrcCopy;
Buffer.Canvas.CopyRect( Destino, Fondo.Canvas, Origen );

// Dibujamos el sprite en el buffer encima del fondo copiado
Sprite.Dibujar( 0, 0, Buffer.Canvas );

// Dibujamos el contenido del buffer a la pantalla
Canvas.Draw( Sprite.x, Sprite.y, Buffer.Picture.Graphic );
end;

Prácticamente es el mismo visto en el artículo anterior. Ya sólo hace falta poner el marcha el mecanismo que bien podría ser en el evento OnCreate del formulario:

begin
TmpTeclado.Enabled := True;
Sprite.x := 250;
Sprite.y := 150;
end;

CAPTURANDO LOS EVENTOS DEL RATON

Para capturar las coordenadas del ratón vamos a utilizar el evento OnMouseMove del formulario:

procedure TFormulario.FormMouseMove( Sender: TObject; Shift: TShiftState; X, Y: Integer );
begin
Sprite.x := X;
Sprite.y := Y;
end;

Para controlar los eventos del ratón voy a utilizar un temporizador distinto al del teclado llamado TmpRaton con un intervalo de 10 milisegundos. Su evento OnTimer sería sencillo:

procedure TFormulario.TmpRatonTimer( Sender: TObject );
begin
DibujarSprite;
end;

Aquí nos surge un problema importante: como los movimientos del ratón son más bruscos que los del teclado volvemos a tener el problema de que el sprite nos va dejando manchas en pantalla. Para solucionar el problema tenemos que restaurar el fondo de la posición anterior del sprite antes de dibujarlo en la nueva posición..

Para ello voy a guardar en la clase TSprite las coordenadas anteriores:

type
TSprite = class
public
x, y, xAnterior, yAnterior: Integer;
ColorTransparente: TColor;
Imagen, Mascara: TImage;
constructor Create;
destructor Destroy; override;
procedure Cargar( sImagen: string );
procedure Dibujar( x, y: Integer; Canvas: TCanvas );
end;

Al procedimiento DibujarSprite le vamos a añadir que restaure el fondo del sprite de la posición anterior:

procedure TFormulario.DibujarSprite;
var
Origen, Destino: TRect;
begin
// Restauramos el fondo de la posición anterior del sprite
if ( Sprite.xAnterior <> Sprite.x ) or ( Sprite.yAnterior <> Sprite.y ) then
begin
Origen.Left := Sprite.xAnterior;
Origen.Top := Sprite.yAnterior;
Origen.Right := Sprite.xAnterior + Sprite.Imagen.Width;
Origen.Bottom := Sprite.yAnterior + Sprite.Imagen.Height;
Destino := Origen;
Canvas.CopyMode := cmSrcCopy;
Canvas.CopyRect( Destino, Fondo.Canvas, Origen );
end;

// Copiamos el fondo de pantalla al buffer
Origen.Left := Sprite.x;
Origen.Top := Sprite.y;
Origen.Right := Sprite.x + Sprite.Imagen.Width;
Origen.Bottom := Sprite.y + Sprite.Imagen.Height;
Destino.Left := 0;
Destino.Top := 0;
Destino.Right := Sprite.Imagen.Width;
Destino.Bottom := Sprite.Imagen.Height;
Buffer.Canvas.CopyMode := cmSrcCopy;
Buffer.Canvas.CopyRect( Destino, Fondo.Canvas, Origen );

// Dibujamos el sprite en el buffer encima del fondo copiado
Sprite.Dibujar( 0, 0, Buffer.Canvas );

// Dibujamos el contenido del buffer a la pantalla
Canvas.Draw( Sprite.x, Sprite.y, Buffer.Picture.Graphic );

Sprite.xAnterior := Sprite.x;
Sprite.yAnterior := Sprite.y;
end;

Y finalmente activamos el temporizador que controla el ratón y ocultamos el cursor del ratón para que no se superponga encima de nuestro sprite:

begin
TmpRaton.Enabled := True;
Sprite.x := 250;
Sprite.y := 150;
ShowCursor( False );
end;

Al ejecutar el programa podeis ver como se mueve el sprite como si fuera el cursor del ratón.

Aunque se pueden hacer cosas bonitas utilizando el Canvas no os hagais muchas ilusiones ya que si por algo destaca la librería GDI de Windows (el Canvas) es por su lentitud y por la diferencia de velocidad entre ordenadores.

Para hacer cosas serías habría que irse a la librerías SDL (mi favorita), OpenGL o DirectX ( aunque hay decenas de motores gráficos 2D y 3D para Delphi en Internet que simplifican el trabajo).

Pruebas realizadas en Delphi 7.

25 octubre 2007

Mover sprites con doble buffer

En el artículo anterior creamos la clase TSprite encargada de dibujar figuras en pantalla. Hoy vamos a reutilizarla para mover sprites, pero antes vamos a hacer una pequeña modificación:

type
TSprite = class
public
x, y: Integer;
ColorTransparente: TColor;
Imagen, Mascara: TImage;
constructor Create;
destructor Destroy; override;
procedure Cargar( sImagen: string );
procedure Dibujar( x, y: Integer; Canvas: TCanvas );
end;

Sólo hemos modificado el evento Dibujar añadiendo las coordenadas de donde se va a dibujar (independientemente de las que tenga el sprite). La implementación de toda la clase TSprite quedaría de esta manera:

{ TSprite }

constructor TSprite.Create;
begin
inherited;
Imagen := TImage.Create( nil );
Imagen.AutoSize := True;
Mascara := TImage.Create( nil );
ColorTransparente := RGB( 255, 0, 255 );
end;

destructor TSprite.Destroy;
begin
Mascara.Free;
Imagen.Free;
inherited;
end;

procedure TSprite.Cargar( sImagen: string );
var
i, j: Integer;
begin
Imagen.Picture.LoadFromFile( sImagen );
Mascara.Width := Imagen.Width;
Mascara.Height := Imagen.Height;

for j := 0 to Imagen.Height - 1 do
for i := 0 to Imagen.Width - 1 do
if Imagen.Canvas.Pixels[i, j] = ColorTransparente then
begin
Imagen.Canvas.Pixels[i, j] := 0;
Mascara.Canvas.Pixels[i, j] := RGB( 255, 255, 255 );
end
else
Mascara.Canvas.Pixels[i, j] := RGB( 0, 0, 0 );
end;

procedure TSprite.Dibujar( x, y: Integer; Canvas: TCanvas );
begin
Canvas.CopyMode := cmSrcAnd;
Canvas.Draw( x, y, Mascara.Picture.Graphic );
Canvas.CopyMode := cmSrcPaint;
Canvas.Draw( x, y, Imagen.Picture.Graphic );
end;

CREANDO EL DOBLE BUFFER

Cuando se mueven figuras gráficas en un formulario aparte de producirse parpadeos en el sprite se van dejando rastros de las posiciones anteriores. Sucede algo como esto:


Para evitarlo hay muchísimas técnicas tales como el doble o triple buffer. Aquí vamos a ver como realizar un doble buffer para mover sprites. El formulario va a tener el siguiente fondo:


El fondo tiene unas dimensiones de 500x300 pixels. Para ajustar el fondo al formulario configuramos las siguientes propiedades en el inspector de objetos:

Formulario.ClientWidth = 500
Formulario.ClientHeight = 300

Se llama doble buffer porque vamos a crear dos imágenes:

Fondo, Buffer: TImage;

El Fondo guarda la imagen de fondo mostrada anteriormente y el Buffer va a encargarse de mezclar el sprite con el fondo antes de llevarlo a la pantalla.

Los pasos para dibujar el sprite serían los siguientes:


Se copia un trozo del fondo al buffer.

Se copia el sprite sin fondo encima del buffer.

Se lleva el contenido del buffer a pantalla.

Lo primero que vamos a hacer es declarar en la sección private del formulario los objetos:

private
{ Private declarations }
Sprite: TSprite;
Buffer, Fondo: TImage;

Después los creamos en el evento OnCreate del formulario:

procedure TFormulario.FormCreate( Sender: TObject );
begin
Sprite := TSprite.Create;
Sprite.Cargar( ExtractFilePath( Application.ExeName ) + 'sprite.bmp' );
Buffer := TImage.Create( nil );
Buffer.Width := Sprite.Imagen.Width;
Buffer.Height := Sprite.Imagen.Height;
Fondo := TImage.Create( nil );
Fondo.Picture.LoadFromFile( ExtractFilePath( Application.ExeName ) + 'fondo.bmp' );
end;

El fondo también lo he creado como imagen BMP en vez de JPG para poder utilizar la función CopyRect del Canvas. Nos aseguramos de que en el evento OnDestroy del formulario se liberen de memoria:

procedure TFormulario.FormDestroy( Sender: TObject );
begin
Sprite.Free;
Buffer.Free;
Fondo.Free;
end;

Aunque podemos mover el sprite utilizando un bucle for esto podría dejar nuestro programa algo pillado. Lo mejor es moverlo utilizando un objeto de la clase TTimer. Lo introducimos en nuestro formulario con el nombre Temporizador. Por defecto hay que dejarlo desactivado (Enabled = False) y vamos a hacer que se mueva el sprite cada 10 milisegundos (Invertal = 10).

En el evento OnTimer hacemos que se mueva el sprite utilizando los pasos mencionados:

procedure TFSprites.TemporizadorTimer( Sender: TObject );
var
Origen, Destino: TRect;
begin
if Sprite.x < 400 then
begin
Inc( Sprite.x );

// Copiamos el fondo de pantalla al buffer
Origen.Left := Sprite.x;
Origen.Top := Sprite.y;
Origen.Right := Sprite.x + Sprite.Imagen.Width;
Origen.Bottom := Sprite.y + Sprite.Imagen.Height;
Destino.Left := 0;
Destino.Top := 0;
Destino.Right := Sprite.Imagen.Width;
Destino.Bottom := Sprite.Imagen.Height;
Buffer.Canvas.CopyMode := cmSrcCopy;
Buffer.Canvas.CopyRect( Destino, Fondo.Canvas, Origen );

// Dibujamos el sprite en el buffer encima del fondo copiado
Sprite.Dibujar( 0, 0, Buffer.Canvas );

// Dibujamos el contenido del buffer a la pantalla
Canvas.Draw( Sprite.x, Sprite.y, Buffer.Picture.Graphic );
end
else
Temporizador.Enabled := False;
end;

En el evento OnPaint del formulario tenemos que hacer que se dibuje el fondo:

procedure TFormulario.FormPaint( Sender: TObject );
begin
Canvas.Draw( 0, 0, Fondo.Picture.Graphic );
end;

Esto es necesario por si el usuario minimiza y vuelve a mostrar la aplicación, ya que sólo se refresca la zona por donde está moviéndose el sprite.

Por fin hemos conseguido el efecto deseado:



Pruebas realizadas en Delphi 7.

24 octubre 2007

Como dibujar sprites transparentes

Un Sprite es una figura gráfica móvil utilizada en los videojuegos de dos dimensiones. Por ejemplo, un videojuego de naves espaciales consta de los sprites de la nave, los meteoritos, los enemigos, etc., es decir, todo lo que sea móvil en pantalla y que no tenga que ver con los paisajes de fondo.

Anteriormente vimos como copiar imágenes de una superficie a otra utilizando los métodos Draw o CopyRect que se encuentran en la clase TCanvas. También se vió como modificar el modo de copiar mediante la propiedad CopyMode la cual permitia los valores cmSrcCopy, smMergePaint, etc.

El problema radica en que por mucho que nos empeñemos en dibujar una imagen transparente ningún tipo de copia funciona: o la hace muy transparente o se estropea el fondo.

DIBUJAR SPRITES MEDIANTE MASCARAS

Para dibujar sprites transparentes hay que tener dos imágenes: la original cuyo color de fondo le debemos dar alguno como común (como el negro) y la imagen de la máscara que es igual que la original pero como si fuera un negativo.

Supongamos que quiero dibujar este sprite (archivo BMP):


Es conveniente utilizar de color transparente un color de uso como común. En este caso he elegido el color rosa cuyos componentes RGB son (255,0,255). La máscara de esta imagen sería la siguiente:


Esta máscara no es necesario crearla en ningún programa de dibujo ya que la vamos a crear nosotros internamente. Vamos a encapsular la creación del sprite en la siguiente clase:

type
TSprite = class
public
x, y: Integer;
ColorTransparente: TColor;
Imagen, Mascara: TImage;
constructor Create;
destructor Destroy; override;
procedure Cargar( sImagen: string );
procedure Dibujar( Canvas: TCanvas );
end;

Esta clase consta de las coordenadas del sprite, el color que definimos como transparente y las imágenes a dibujar incluyendo su máscara. En el constructor de la clase creo dos objetos de la clase TImage en memoria:

constructor TSprite.Create;
begin
inherited;
Imagen := TImage.Create( nil );
Imagen.AutoSize := True;
Mascara := TImage.Create( nil );
ColorTransparente := RGB( 255, 0, 255 );
end;

También he definido el color rosa como color transparente. Como puede apreciarse he utilizado el procedimiento RGB que convierte los tres componentes del color al formato de TColor que los guarda al revés BGR. Así podemos darle a Delphi cualquier color utilizando estos tres componentes copiados de cualquier programa de dibujo.

En el destructor de la clase nos aseguramos de que se liberen de memoria ambos objetos:

destructor TSprite.Destroy;
begin
Mascara.Free;
Imagen.Free;
inherited;
end;

Ahora implementamos la función que carga de un archivo la imagen BMP y a continuación crea su máscara:

procedure TSprite.Cargar( sImagen: string );
var
i, j: Integer;
begin
Imagen.Picture.LoadFromFile( sImagen );
Mascara.Width := Imagen.Width;
Mascara.Height := Imagen.Height;

for j := 0 to Imagen.Height - 1 do
for i := 0 to Imagen.Width - 1 do
if Imagen.Canvas.Pixels[i,j] = ColorTransparente then
begin
Imagen.Canvas.Pixels[i,j] := 0;
Mascara.Canvas.Pixels[i,j] := RGB( 255, 255, 255 );
end
else
Mascara.Canvas.Pixels[i,j] := RGB( 0, 0, 0 );
end;

Aquí nos encargamos de dejar la máscara en negativo a partir de la imagen original.

Para dibujar sprites recomiendo utilizar archivos BMP en lugar de JPG debido a que este último tipo de imágenes pierden calidad y podrían afectar al resultado de la máscara, dando la sensación de que el sprite tiene manchas. Hay otras librerías para Delphi que permiten cargar imágenes PNG que son ideales para la creación de videojuegos, pero esto lo veremos en otra ocasión.

Una vez que ya tenemos nuestro sprite y nuestra máscara asociada al mismo podemos crear el procedimiento encargado de dibujarlo:

procedure TSprite.Dibujar( Canvas: TCanvas );
begin
Canvas.CopyMode := cmSrcAnd;
Canvas.Draw( x, y, Mascara.Picture.Graphic )
Canvas.CopyMode := cmSrcPaint;
Canvas.Draw( x, y, Imagen.Picture.Graphic );}
end;

El único parámetro que tiene es el Canvas sobre el que se va a dibujar el sprite. Primero utilizamos la máscara para limpiar el terreno y después dibujamos el sprite sin el fondo. Vamos a ver como utilizar nuestra clase para dibujar un sprite en el formulario:

procedure TFormulario.DibujarCoche;
var
Sprite: TSprite;
begin
Sprite := TSprite.Create;
Sprite.x := 100;
Sprite.y := 100;
Sprite.Cargar( ExtractFilePath( Application.ExeName ) + 'sprite.bmp' );
Sprite.Dibujar( Canvas );
Sprite.Free;
end;

Este sería el resultado en el formulario:


Si el sprite lo vamos a dibujar muchas veces no es necesario crearlo y destruirlo cada vez. Deberíamos crearlo en el evento OnCreate del formulario y en su evento OnDestroy liberarlo (Sprite.Free).

En el próximo artículo veremos como mover el sprite en pantalla utilizando una técnica de doble buffer para evitar parpadeos en el movimiento.

Pruebas realizadas en Delphi 7.

23 octubre 2007

Dibujando con la clase TCanvas (y III)

Vamos a terminar de ver lo más importante de las operaciones que se pueden realizar con el objeto Canvas.

DIBUJAR UN RECTANGULO SIN FONDO


El procedimiento FrameRect permite crear rectángulos sin fondo teniendo en cuenta el valor de la propiedad Brush:

procedure FrameRect( const Rect: TRect );

Veamos un ejemplo:

with Canvas do
begin
R.Left := 300;
R.Top := 250;
R.Right := 350;
R.Bottom := 300;
Brush.Color := clGreen;
FrameRect( R );
end;

Este sería el dibujo resultante:


COMO COPIAR IMAGENES DE UN BITMAP A OTRO

La manera más utilizada de copiar imágenes es mediante el método Draw:

procedure Draw( X, Y: Integer; Graphic: TGraphic );

Vamos a ver un ejemplo de como copiar la imagen de un objeto de la clase TImage a nuestro formulario utilizando el procedimiento Draw:

var
R: TRect;
begin
with Canvas do
begin
R.Left := 300;
R.Top := 250;
R.Right := 350;
R.Bottom := 300;
Brush.Color := clGreen;
FrameRect( R );
Draw( 100, 100, Imagen.Picture.Graphic );
end;
end;

Hemos supuesto que el objeto de la clase TImage se llama Imagen. Este sería el resultado:


La imagen copiada consta de cuatro iconos y un fondo de color blanco. Otra cosa que podemos hacer es modificar el tamaño de la imagen a copiar utilizando el procedimiento:

procedure StretchDraw( const Rect: TRect; Graphic: TGraphic );

El parámetro Rect determina las nuevas coordenadas así como en ancho y alto de la imagen a copiar. En el siguiente ejemplo voy a copiar la misma imagen pero voy a reducirla un tamaño de 100x100:

var
R: TRect;
begin
with Canvas do
begin
R.Left := 100;
R.Top := 100;
R.Right := 200;
R.Bottom := 200;
StretchDraw( R, Imagen.Picture.Graphic );
end;
end;

Quedaría de la siguiente manera:


También se pueden copiar imágenes utilizando el procedimiento:

procedure CopyRect( const Dest: TRect; Canvas: TCanvas; const Source: TRect );

cuyos parámetros son:

Dest: Coordenadas del rectángulo destino.
Canvas: Referencia al Canvas del origen a copiar.
Source: Coordenadas del rectángulo origen.

Para el ejemplo anterior voy a copiar la imagen en su tamaño original al formulario:

var
Origen, Destino: TRect;
begin
with Canvas do
begin
Origen.Left := 0;
Origen.Top := 0;
Origen.Right := Imagen.Picture.Width;
Origen.Bottom := Imagen.Picture.Height;
Destino.Left := 100;
Destino.Top := 100;
Destino.Right := 100 + Imagen.Width;
Destino.Bottom := 100 + Imagen.Height;
CopyRect( Destino, Imagen.Canvas, Origen );
end;
end;

Entonces, ¿que diferencia hay entre Draw o StretchDraw y CopyRect? La diferencia es que CopyRect sólo puede utilizarse cuando el objeto TImage ha cargado un bitmap (*.BMP) porque si es un JPG provoca una excepción. En cambio las funciones Draw o StretchDraw siempre funcionan independientemente del tipo de imagen que sea.

LOS MODOS DE COPIA DE UNA IMAGEN

En principio cuando se realiza la copia de una imagen a otra, la copia de los pixels es exacta. Pero si queremos modificar el modo de copiar entonces la clase TCanvas tiene de la propiedad CopyMode que establece que tipo de operación que se va a realizar. Estos son sus posibles valores:

cmBlackness: pinta la imagen destino de color negro, independientemente del origen.

cmDstInvert: Invierte los colores de la imagen según la imagen destino.

cmMergeCopy: mezcla la imagen origen y destino utilizando el operador binario AND.

cmMergePaint: mezcla la imagen origen y destino utilizando el operador binario OR.

cmNotSrcCopy: copia la imagen origen invertida a la imagen destino.

cmNotSrcErase: mezcla las imagenes origen y destino y después las invierte con el operador binario OR.

cmPatCopy: copia la imagen según el valor de la propiedad Brush.Style del Canvas.

cmPatInvert: copia la imagen invertida según el valor de la propiedad Brush.Style del Canvas.

cmPatPaint: combina las imágenes origen y destino utilizando la operación binaria OR para luego invertirlas.

cmSrcAnd: combina la imagen origen con la imagen destino utilizando el operador binario AND.

cmSrcCopy: realiza una copia exacta de la imagen origen a la imagen destino (por defecto).

cmSrcErase: invierte la imagen destino y copia el origen utilizando el operador binario AND.

cmSrcInvert: invierte las imágenes origen y destino utilizando el operador binario XOR.

cmSrcPaint: combina las imágenes destino y origen utilizando el operador binario AND.

cmWhiteness: pinta toda la imagen destino de color blanco ignorando la imagen origen.

Por ejemplo voy a crear un efecto fantasma con la imagen origen:

with Canvas do
begin
CopyMode := cmMergePaint;
Draw( 100, 100, Imagen.Picture.Graphic );
end;

El efecto sería el siguiente:


COMO ACCEDER A LOS PIXELS DE LA IMAGEN

Si no son suficientes las operaciones que podemos realizar en una imagen también podemos acceder directamente a los pixels de una imagen a traves de su propiedad Pixels:

property Pixels[ X, Y: Integer ]: TColor;

Esta propiedad nos sirve igualmente para leer y para escribir pixels en la imagen. El componente TColor es un tipo entero de 32 bits definido en la unidad Graphics del siguiente modo:

type
TColor = -$7FFFFFFF-1..$7FFFFFFF;

Dentro del mismo número entero están definidos los colores básicos RGB los
cuales son:

R = Red (Rojo)
G = Green (Verde)
B = Blue (Azul)

Combinando estos tres colores se puede crear cualquier otro color. Estos colores estan dentro del valor TColor del siguiento modo:

$00GGBBRR

Donde GG es el byte en hexadecimal que representa el color verde, BB es el color azul y RR es el color rojo. Aquí tenemos unos ejemplos de los números en hexadecimal:

Rojo := $000000FF;
Azul := $0000FF00;
Verde := $00FF0000;
Blanco := $00FFFFFF;
Negro := $00000000;

Para no complicarnos mucho la vida Delphi ya dispone de colores predeterminados tales como clRed (Rojo), clBlue (Azul), etc. Sabiendo esto imaginaos que deseo hacer un programa que convierta todos los colores blancos de la imagen en amarillo:

var
i, j: Integer;
begin
with Canvas do
for j := 0 to ClientHeight - 1 do
for i := 0 to ClientWidth - 1 do
if Pixels[i,j] = clWhite then
Pixels[i,j] := clYellow;
end;

Mediante un doble bucle recorro toda la imagen y compruebo si el pixel donde estoy es blanco para sustituirlo por el amarillo. Este sería el resultado:


Con este método se podemos realizar nuestros propios filtros o crear cualquier tipo de efecto.

Con esto finalizamos el apartado dedicado al objeto Canvas.

Pruebas realizadas en Delphi 7.

22 octubre 2007

Dibujando con la clase TCanvas (II)

Una vez que ya sabemos como dibujar las figuras básicas con el objeto Canvas pasemos ahora a ver algunas más complicadas.


DIBUJAR UN POLIGONO SOLO CON LINEAS


Para dibujar un polígono transparente utilizando líneas tenemos el procedimiento:

procedure Polyline( Points: array of TPoint );

Funciona exactamente igual que el procedimiento Polygon salvo que no cierra el polígono automáticamente, es decir, el último punto y el primero tiene que ser el mismo (para crear el polígono, aunque puede realizarse cualquier otra figura). Este procedimiento es equivalente a crear un trazado de líneas utilizando el procedimiento LineTo. Veamos un ejemplo:

with Canvas do
begin
Pen.Color := clBlack;
SetLength( Puntos, 4 );
Puntos[0].x := 200;
Puntos[0].y := 250;
Puntos[1].x := 250;
Puntos[1].y := 300;
Puntos[2].x := 150;
Puntos[2].y := 300;
Puntos[3].x := 200;
Puntos[3].y := 250;
PolyLine( Puntos );
end;

Este sería el resultado:


DIBUJAR POLIGONOS BEZIER

Los polígonos de Bezier se dibujan trazando curvas entre todos los puntos del polígono, es decir, son polígonos con las esquinas muy redondeadas. El procedimiento para dibujarlos es el siguiente:

with Canvas do
begin
Pen.Color := clBlack;
SetLength( Puntos, 4 );
Puntos[0].x := 80;
Puntos[0].y := 250;
Puntos[1].x := 150;
Puntos[1].y := 300;
Puntos[2].x := 10;
Puntos[2].y := 300;
Puntos[3].x := 80;
Puntos[3].y := 250;
PolyBezier( Puntos );
end;

Este sería el resultado:


Aunque he dado las mismas coordenadas que un triángulo, tiene las esquinas inferiores redondeadas.

ESCRIBIENDO TEXTO

Para escribir texto encima de una imagen con la clase TCanvas tenemos el método:

procedure TextOut( X, Y: Integer; const Text: string );

Este método toma como parámetros las coordenadas donde va a comenzar a escribirse el texto y el texto a escribir. Por ejemplo:

with Canvas do
begin
TextOut( 10, 10, 'Escribiendo texto mediante el Canvas del formulario' );
end;

Al no especificar fuente ni color quedaría del siguiente modo:


Cuando se escribe texto en un formulario la fuente, el color del texto y el color del fondo vienen predeterminados por las propiedades Brush y Font del Canvas. Supongamos que quiero escribir texto con fuente Tahoma, negrita, 10 y de color azul con fondo transparente:

with Canvas do
begin
Brush.Style := bsClear;
Font.Color := clBlue;
Font.Name := 'Tahoma';
Font.Size := 10;
TextOut( 200, 50, 'Otro texto de prueba' );
end;

Aquí tenemos nuestro texto personalizado:


Pero nos puede surgir un pequeño problema. ¿Como podemos averiguar lo que va a ocupar en pixels el texto que vamos a escribir? Pues para ello tenemos la siguiente función:

function TextWidth( const Text: string ): Integer;

Nos devuelve el ancho en pixels del texto que le pasamos como parámetro según como esté la propiedad Font antes de llamar a esta función. Y si queremos saber su altura entonces tenemos esta otra función:

function TextHeight( const Text: string ): Integer;

Lo que afecta a esta función principalmente es la propiedad Font.Size del Canvas. También tenemos esta otra función para obtener simultáneamente el ancho y alto del texto a escribir:

function TextExtent( const Text: string ): TSize;

Donde TSize es una estructura definida en la unidad Types del siguiente modo:

type
tagSIZE = packed record
cx: Longint;
cy: Longint;
end;
TSize = tagSIZE;

COMO RELLENAR LAS FIGURAS DE UN COLOR

Todos los programas de dibujo incorporan la función de rellenar con pintura una imagen hasta que encuentre bordes. Es como volcar un bote de pintura sobre las superficie hasta que choque con algo. El Canvas del formulario dispone del procedimiento:

procedure FloodFill( X, Y: Integer; Color: TColor; FillStyle: TFillStyle );

Los parámetros X, Y especifican donde se va a comenzar a pintar, el parámetro Color establece el color del borde donde va a chocar la pintura y FillStyle define el modo de pintar. Veamos un ejemplo de cómo pintar un triángulo de rojo con bordes negros:

with Canvas do
begin
Brush.Color := clRed;
FloodFill( 200, 270, clBlack, fsBorder );
end;

El último parámetro (FillStyle) se utiliza para decirle a la función si queremos que pinte hasta el borde de un color en concreto (clBlack y fsBorder) o si deseamos que dibuje toda la superficie de un color hasta que encuentre un color distinto. Por ejemplo:

with Canvas do
begin
Brush.Color := clRed;
FloodFill( 200, 270, clSilver, fsSurface );
end;

En este caso le he dicho que me pinte de rojo toda la superficie cuyo fondo es de color clSilver. Si encuentra un color que no sea clSilver entonces se para. Esa es la diferencia entre los valores fsBorder (un sólo borde) y fsSurface (cualquier borde pero la misma superficie). En ambos ejemplos he rellenado de color rojo el polígono creado al principio de este artículo:


En el próximo artículo terminaremos de ver las propiedades de la clase TCanvas.

Pruebas realizadas en Delphi 7.

Publicidad