Difference between revisions of "Developing with Graphics/es"

From Lazarus wiki
Jump to navigationJump to search
m (Text replacement - "Object Pascal Tutorial" to "Basic Pascal Tutorial")
 
(37 intermediate revisions by 8 users not shown)
Line 1: Line 1:
 
{{Developing with Graphics}}
 
{{Developing with Graphics}}
  
Esta página será el comienzo de tutoriales que traten sobre la manipulación de mapa de bits y otros tipos de gráficos. Como no soy un experto programador de gráficos, ¡invito a todos a compartir sus experiencias! Basta con añadir un enlace a la siguiente sección, añadir una página y crear su propio antículo para el Wiki.
+
   Esta página dará inicio a una serie de tutorías que trataran sobre la manipulación de mapa de bits y otros tipos de gráficos. Como no soy un experto programador de gráficos, ¡invito a todos a compartir sus experiencias! Basta con añadir un enlace a la siguiente sección, añadir una página y crear su propio artículo para el Wiki.
  
En esta página se dará alguna información general.
+
   En esta página se dará alguna información general.
  
 
__TOC__
 
__TOC__
  
==Otros artículos sobre gráficos==
+
==Otros artículos sobre gráficos==
* [[GLScene]] - Una puerta a la librería de gráficos visual OpenGL [http://www.glscene.org GLScene]
+
===Dibujo 2D===
* [[TAChart]/es] - Componente gráfico para Lazarus
+
* [[ZenGL]] - Desarrollo de juegos multi-plataforma usando OpenGL.
 +
* [[BGRABitmap]] - Dibuja formas y mapas de bits con transparencia, acceso directo a los píxeles, etc.
 +
* [[LazRGBGraphics]] - Un paquete para un veloz procesado en memoria de imagenes y manipulado de puntos (pixel) (similar a ''scan line'')
 +
* [[Double Gradient]] - Dibuja gradientes dobles y 'n gradientes' en bitmaps facilmente.
 +
* [[Gradient Filler]] - TGradientFiller es la mejor manera de crear 'n gradientes' personalizados en Lazarus.
 
* [[PascalMagick]] - Una forma fácil de usar la API para interactuar con [http://www.imagemagick.org ImageMagick], un paquete software libre multiplataforma para crear, editar y componer imágenes de mapa de bit.
 
* [[PascalMagick]] - Una forma fácil de usar la API para interactuar con [http://www.imagemagick.org ImageMagick], un paquete software libre multiplataforma para crear, editar y componer imágenes de mapa de bit.
* [[PlotPanel/es]] - Un componente para trazar y realizar gráficos animados
+
* [[Sample Graphics/es]] - Galería de gráficos creada con Lazarus y herramientas de dibujo.
 +
 
 +
===Dibujo 3D===
 +
* [[GLScene]] - Una adaptación de la librería visual de gráficos OpenGL [http://www.glscene.org GLScene]
 +
===Gráficos===
 +
* [[TAChart/es | TAChart]] - Componente gráfico para Lazarus
 +
* [[PlotPanel/es|PlotPanel]] - Un componente para trazar y realizar gráficos animados
 +
* [[Perlin Noise]] - Un atículo sobre la utilización de Perlin Noise en aplicaciones LCL.
  
 
==Trabajo con  TBitmap==
 
==Trabajo con  TBitmap==
Lo primero que hay que recordar es que Lazarus quiere decir plataforma independiente, así que todos los métodos que usan funcionalidades de la API de Windows se salen de la cuestión. Así que un método como ScanLIne no es soportado por Lazarus, porque está destinado a Device Independent Bitmap y usa funciones de GDI32.dll.
+
   Lo primero que hay que recordar es que Lazarus quiere decir plataforma independiente, así que todos los métodos que usan funcionalidades de la API de Windows se salen de la cuestión. Así que un método como ScanLine no es soportado por Lazarus, porque está destinado a Device Independent Bitmap y usa funciones de GDI32.dll.
  
Tenga cuidado porque si no especifica la anchura y altura de su [[doc:lcl/graphics/tbitmap.html|TBitmap]] tendrá la predefinida, que es muy pequeña.
+
   Tenga cuidado porque si no especifica la anchura y altura de su [[doc:lcl/graphics/tbitmap.html|TBitmap]] tendrá la predefinida, que es muy pequeña.
  
===Un ejemplo de desvanecimiento===
+
=== Un ejemplo de desvanecimiento (fading)===
Supongamos que quiere hacer una imagen que se desvanece. En Delphi podría hacer algo así:
+
   Supongamos que quiere hacer una imagen que se desvanece. En Delphi podría hacer algo así:
<code>
+
<syntaxhighlight lang="pascal">
 
  type
 
  type
 
   PRGBTripleArray = ^TRGBTripleArray;
 
   PRGBTripleArray = ^TRGBTripleArray;
Line 33: Line 44:
 
   Bitmap := TBitmap.Create;
 
   Bitmap := TBitmap.Create;
 
   try
 
   try
     Bitmap.PixelFormat := pf32bit;  //  or pf24bit
+
     Bitmap.PixelFormat := pf32bit;  //  o bien pf24bit
 
     Bitmap.Assign(aBitMap);
 
     Bitmap.Assign(aBitMap);
 
     BaseBitmap := TBitmap.Create;
 
     BaseBitmap := TBitmap.Create;
Line 44: Line 55:
 
           Row := Bitmap.Scanline[y];
 
           Row := Bitmap.Scanline[y];
 
           for x := 0 to (Bitmap.Width - 1) do begin
 
           for x := 0 to (Bitmap.Width - 1) do begin
             Row[x].rgbtRed := (step * BaseRow[x].rgbtRed) shr 5;
+
             Row[x].rgbtRed := (step * BaseRow[x].rgbtRed) shr 5; // desvanec. rojo
             Row[x].rgbtGreen := (step * BaseRow[x].rgbtGreen) shr 5; // Fading
+
             Row[x].rgbtGreen := (step * BaseRow[x].rgbtGreen) shr 5; // desvanec. verde
             Row[x].rgbtBlue := (step * BaseRow[x].rgbtBlue) shr 5;
+
             Row[x].rgbtBlue := (step * BaseRow[x].rgbtBlue) shr 5; // desvanec. azul
 +
          // disminuyendo progresivamente los valores RGB (Red,Green,Blue o Rojo,
 +
          // Verde, Azul que son tres componentes que forman el color a mostrar
 +
          // logramos que la imagen se vaya apagando hasta quedarse a oscuras.
 
           end;
 
           end;
 
         end;
 
         end;
Line 59: Line 73:
 
     Bitmap.Free;
 
     Bitmap.Free;
 
   end;
 
   end;
  end;
+
  end;</syntaxhighlight>
</code>
+
 
 +
&nbsp;&nbsp;&nbsp;Está función en Lazarus sería así:
 +
 
 +
<syntaxhighlight lang="pascal">
 +
{ This code has been taken from the $LazarusPath/examples/lazintfimage/fadein1.lpi project. }
 +
uses LCLType, // HBitmap type
 +
    IntfGraphics, // TLazIntfImage type
 +
    fpImage; // TFPColor type
  
Está función en Lazarus sería así:
 
<code>
 
 
  procedure TForm1.FadeIn(ABitMap: TBitMap);
 
  procedure TForm1.FadeIn(ABitMap: TBitMap);
 
  var
 
  var
Line 88: Line 107:
 
       end;
 
       end;
 
     end;
 
     end;
     TempIntfImg.CreateBitmap(ImgHandle,ImgMaskHandle,false);
+
     TempIntfImg.CreateBitmaps(ImgHandle,ImgMaskHandle,false);
 
     TempBitmap.Handle:=ImgHandle;
 
     TempBitmap.Handle:=ImgHandle;
 
     TempBitmap.MaskHandle:=ImgMaskHandle;
 
     TempBitmap.MaskHandle:=ImgMaskHandle;
Line 96: Line 115:
 
   TempIntfImg.Free;
 
   TempIntfImg.Free;
 
   TempBitmap.Free;
 
   TempBitmap.Free;
  end;
+
  end;</syntaxhighlight>
</code>
+
&nbsp;&nbsp;&nbsp;El código Lazarus de esta página se ha tomado del proyecto $LazarusPath/examples/lazintfimage/fadein1.lpi . Así que si desea iniciarse en la programación de gráficos eche una mirada a este ejemplo.
El código Lazarus de esta página se ha tomado del proyecto $LazarusPath/examples/lazintfimage/fadein1.lpi . Así que si desea iniciarse en la programación de gráficos eche una mirada a este ejemplo.
 
  
 
===Dibujando mapa de bits de color transparente===
 
===Dibujando mapa de bits de color transparente===
  
Una nueva característica, incluida en Lazarus 0.9.11, son los mapa de bits de color transparente. Los archivos Bitmap (*.BMP) no pueden almacenar cualquier información sobre transparencia, pero pueden trabajar como si la tuviera si selecciona un color en ellos para representar la zona transparente. Este es un truco habitual usado en aplicaciones Win32.
+
&nbsp;&nbsp;&nbsp;Una nueva característica, incluida en Lazarus 0.9.11, son los mapa de bits de color transparente. Los archivos Bitmap (*.BMP) no pueden almacenar cualquier información sobre transparencia, pero pueden trabajar como si la tuviera si selecciona un color en ellos para representar la zona transparente. Este es un truco habitual usado en aplicaciones Win32.
  
El siguiente ejemplo carga un mapa de bit desde un recurso Windows, selecciona un color para que sea transparente (clFuchsia) y después lo dibuja.
+
&nbsp;&nbsp;&nbsp;El siguiente ejemplo carga un mapa de bit desde un recurso Windows, selecciona un color para que sea transparente (clFuchsia) y después lo dibuja.
  
<pre>
+
<syntaxhighlight lang="pascal">
 
procedure MyForm.MyButtonOnClick(Sender: TObject);
 
procedure MyForm.MyButtonOnClick(Sender: TObject);
 
var
 
var
Line 117: Line 135:
 
   buffer := Windows.LoadBitmap(hInstance, MAKEINTRESOURCE(ResourceID));
 
   buffer := Windows.LoadBitmap(hInstance, MAKEINTRESOURCE(ResourceID));
  
   if (buffer = 0) then exit; // Error loading the bitmap
+
   if (buffer = 0) then exit; // Error cargando el mapa de bits
  
 
   bmp.Handle := buffer;
 
   bmp.Handle := buffer;
Line 134: Line 152:
 
   MyCanvas.Draw(0, 0, bmp);
 
   MyCanvas.Draw(0, 0, bmp);
  
   bmp.Free; // Release allocated resource
+
   bmp.Free; // Liberar el recurso Tbitmap (bmp) creado con anterioridad
 +
end;</syntaxhighlight>
 +
 
 +
&nbsp;&nbsp;&nbsp;Observe las operaciones de memoria realizadas con  [[doc:rtl/classes/tmemorystream.html|TMemoryStream]]. Son necesarios para garantizar la correcta carga de la imagen.
 +
 
 +
===  Tomar una captura de la pantalla===
 +
 
 +
&nbsp;&nbsp;&nbsp;Desde Lazarus 0.9.16 podemos utilizar la LCL para realizar capturas de la pantalla de forma multiplataforma. El código de ejemplo siguiente lo hace (funciona en gtk2 y Win32, pero no en gtk1 actualmente):
 +
 
 +
<syntaxhighlight lang="pascal">
 +
uses LCLIntf, LCLType;
 +
 
 +
  ...
 +
 
 +
var
 +
  MyBitmap: TBitmap;
 +
  ScreenDC: HDC;
 +
begin
 +
  MyBitmap := TBitmap.Create;
 +
  ScreenDC := GetDC(0);
 +
  MyBitmap.LoadFromDevice(ScreenDC);
 +
  ReleaseDC(ScreenDC);
 +
 
 +
  ...
 +
</syntaxhighlight>
 +
 
 +
===Pintar un  ''TLazIntfImage'' en el lienzo (''Canvas'')===
 +
 
 +
&nbsp;&nbsp;&nbsp;Puesto que la propiedad ''ScanLines'' ha sido temporalmente removida de la clase ''TBitmap'', la única forma de acceder a datos de línea de exploración de un mapa de bits, es utilizar TLazIntfImage. Esta es una muestra de cómo crear un  ''TLazIntfImage'' desde un ''TBitmap'' y, a continuación, dibujarlo en el lienzo (''canvas'').
 +
 
 +
<syntaxhighlight lang="pascal">
 +
uses
 +
  ...GraphType, IntfGraphics, LCLType, LCLProc,  LCLIntf ...
 +
 
 +
procedure TForm1.Button4Click(Sender: TObject);
 +
var
 +
  b: TBitmap;
 +
  t: TLazIntfImage;
 +
  bmp, old: HBitmap;
 +
  msk: HBitmap;
 +
  tmpDC: HDC;
 +
begin
 +
  b := TBitmap.Create;
 +
  try
 +
    b.LoadFromFile('test.bmp');
 +
    t := b.CreateIntfImage;
 +
    t.CreateBitmaps(bmp, msk, false);
 +
 
 +
    tmpDC := CreateCompatibleDC(Canvas.Handle);
 +
    old := SelectObject(tmpDC, bmp);
 +
    BitBlt(Canvas.Handle, 0, 0, t.Width, t.Height, tmpDC, 0, 0, SRCCOPY);
 +
    DeleteObject(SelectObject(tmpDC, old));
 +
    DeleteObject(msk);
 +
    DeleteDC(tmpDC);
 +
 
 +
  finally
 +
    t.Free;
 +
    b.Free;
 +
  end;
 
end;
 
end;
</pre>
+
</syntaxhighlight>
 
 
Observe las operaciones de memoria realizadas con  [[doc:rtl/classes/tmemorystream.html|TMemoryStream]]. Son necesarios para garantizar la correcta carga de la imagen.
 
  
 
==Gráficos en movimiento - Cómo evitar el parpadeo==
 
==Gráficos en movimiento - Cómo evitar el parpadeo==
Muchos programas dibujan su salida al GUI como gráficos 2D. Si esos gráficos necesitan cambiar rápidamente, pronto tendrá un problema: los gráficos que cambian rápidamente a menudo parpadean en la pantalla. Esto ocurre cuando los usuarios algunas veces ven las imágenes ocultas y algunas veces las ven cuando se dibujan sólo parcialmente. Esto ocurre porque el proceso de pintar requiere tiempo.
+
&nbsp;&nbsp;&nbsp;Muchos programas dibujan su salida al GUI como gráficos 2D. Si esos gráficos necesitan cambiar rápidamente, pronto tendrá un problema: los gráficos que cambian rápidamente a menudo parpadean en la pantalla. Esto ocurre cuando los usuarios algunas veces ven las imágenes ocultas y algunas veces las ven cuando se dibujan sólo parcialmente. Esto ocurre porque el proceso de pintar requiere tiempo.
  
Pero ¿cómo puedo evitar el parpadeo y conseguir la mejor velocidad de trazado? Claro, podría trabajar con aceleración de hardware utilizando OpenGL, pero es verdaderamente pesado para pequeños programas u ordenadores antiguos. Este tutorial esta enfocado hacia el dibujo con TCanvas. Si necesita ayuda con OpenGL, eche una mirada al ejemplo que viene con Lazarus. Puede también usar el paquete para juegos de A. J. Venter, que proporciona un canvas (lienzo) de doble memoria intermedia y un componente sprite (objeto móvil).
+
&nbsp;&nbsp;&nbsp;Pero ¿cómo puedo evitar el parpadeo y conseguir la mejor velocidad de trazado? Claro, podría trabajar con aceleración de hardware utilizando OpenGL, pero es verdaderamente pesado para pequeños programas u ordenadores antiguos. Este tutorial esta enfocado hacia el dibujo con TCanvas. Si necesita ayuda con OpenGL, eche una mirada al ejemplo que viene con Lazarus. Puede también usar el paquete para juegos de A. J. Venter, que proporciona un canvas (lienzo) de doble memoria intermedia y un componente sprite (objeto móvil).
  
Ahora examinaremos las opciones que tenemos para dibujar en un Canvas (lienzo):
+
&nbsp;&nbsp;&nbsp;Ahora examinaremos las opciones que tenemos para dibujar en un Canvas (lienzo):
 
* [[#Draw to a TImage|Dibujar en un TImage]]
 
* [[#Draw to a TImage|Dibujar en un TImage]]
 
* [[#Draw on the OnPaint event|Dibujar en el envento OnPaint del formulario, un TPaintBox u otro control]]
 
* [[#Draw on the OnPaint event|Dibujar en el envento OnPaint del formulario, un TPaintBox u otro control]]
Line 152: Line 226:
  
 
===Dibujar en un TImage===
 
===Dibujar en un TImage===
Nunca use el evento OnPaint para dibujar en un TImage. Un TImage es almacenado en la memoria intermedia por lo que todo lo que necesita hacer es dibujarlo desde cualquier lugar y se cambia para siempre. Sin embargo, si constantemente redibuja, la imagen parpadeará. En este caso puede intentar otras opciones. Dibujar en un TImage se considera más lento que otras posibilidades. 
 
  
<code>
+
&nbsp;&nbsp;&nbsp;Un ''TImage'' consta de 2 partes: Un ''TGraphic'', por lo general un ''TBitmap'', conteniendo la imagen persistente y el área visual, que es pintada en cada evento ''OnPaint''. Cambiar el tamaño del ''TImage'' '''no''' cambiar el tamaño del mapa de bits. El gráfico (o mapa de bits) es accesible a través de ''Image1.Picture.Graphic'' (o ''Image1.Picture.Bitmap''). El lienzo es ''Image1.Picture.Bitmap.Canvas''. El lienzo de la zona visual de un ''TImage'' sólo es accesible durante ''Image1.OnPaint'' través de ''Image1.Canvas''.
procedure TForm1.BitBtn1Click(Sender: TObject);
+
 
var
+
&nbsp;&nbsp;&nbsp;'''Importante''': Nunca use el evento ''OnPain''t para dibujar en un ''TImage''. Un ''TImage'' es almacenado en la memoria intermedia por lo que todo lo que necesita hacer es dibujarlo desde cualquier lugar y se cambia para siempre. Sin embargo, si constantemente redibuja, la imagen parpadeará. En este caso puede intentar otras opciones. Dibujar en un ''TImage'' se considera más lento que otras posibilidades. 
  x, y: Integer;
+
 
begin
+
====Redimensionando el mapa de bits de un ''TImage''====
  // Draws the backgroung
+
 
  MyImage.Canvas.Pen.Color := clWhite;
+
&nbsp;&nbsp;&nbsp;Nota: No uses esto duante un ''OnPaint''.
  MyImage.Canvas.Rectangle(0, 0, Image.Width, Image.Height);
+
 
 +
<syntaxhighlight lang="pascal">
 +
  with Image1.Picture.Bitmap do begin
 +
    Width:=100;
 +
    Height:=120;
 +
  end;</syntaxhighlight>
 +
 
 +
====Pintando el mapa de bits de un ''TImage''====
 +
 
 +
&nbsp;&nbsp;&nbsp;Nota: No uses esto duante un ''OnPaint''.
 +
 
 +
<syntaxhighlight lang="pascal">
 +
  with Image1.Picture.Bitmap.Canvas do begin
 +
    // fill the entire bitmap with red
 +
    Brush.Color:=clRed;
 +
    FillRect(0,0,Width,Height);
 +
  end;</syntaxhighlight>
 +
 
 +
&nbsp;&nbsp;&nbsp;Nota: dentro de un ''Image1.OnPaint'' el lienzo (el ''Image1.Canvas'') apunta al área visible volátil. Fuera del evento ''Image1.OnPaint'' el lienzo apunta a ''Image1.Picture.Bitmap.Canvas''.
 +
 
 +
&nbsp;&nbsp;&nbsp;Otro ejemplo:
 +
 
 +
<syntaxhighlight lang="pascal">
 +
procedure TForm1.BitBtn1Click(Sender: TObject);
 +
  var
 +
    x, y: Integer;
 +
  begin
 +
    // Pinta el fondo
 +
    MyImage.Canvas.Pen.Color := clWhite;
 +
    MyImage.Canvas.Rectangle(0, 0, Image.Width, Image.Height);
 
    
 
    
  // Draws squares
+
    // Dibuja cuadrados
  MyImage.Canvas.Pen.Color := clBlack;
+
    MyImage.Canvas.Pen.Color := clBlack;
  for x := 1 to 8 do
+
    for x := 1 to 8 do
    for y := 1 to 8 do
+
    for y := 1 to 8 do
    MyImage.Canvas.Rectangle(Round((x - 1) * Image.Width / 8), Round((y - 1) * Image.Height / 8),
+
      MyImage.Canvas.Rectangle(Round((x - 1) * Image.Width / 8), Round((y - 1) * Image.Height / 8),
        Round(x * Image.Width / 8), Round(y * Image.Height / 8));
+
        Round(x * Image.Width / 8), Round(y * Image.Height / 8));
  end;
+
  end;</syntaxhighlight>
</code>
+
 
 +
==== Pintando el área visual volátil de un ''TImage''====
 +
 
 +
&nbsp;&nbsp;&nbsp;Sólo se puede pintar en esta área durante ''OnPaint''.'' OnPaint'' se llama automáticamente por el LCL, cuando la zona es invalidada. Se puede invalidar la zona manualmente con ''Image1.Invalidate''. Esto no llamará inmediatamente a ''OnPaint'' y es posible invocar ''Invalidate'' tantas veces como se desee.
 +
 
 +
<syntaxhighlight lang="pascal">
 +
procedure TForm.Image1Paint(Sender: TObject);
 +
  begin
 +
    with Image1.Canvas do begin
 +
      // paint a line
 +
      Pen.Color:=clRed;
 +
      Line(0,0,Width,Height);
 +
    end;
 +
  end;</syntaxhighlight>
  
 
===Dibujar en el evento OnPaint===
 
===Dibujar en el evento OnPaint===
En este caso todo tiene que dibujarlo en el evento OnPaint del formulario. No se mantiene en la memoria intermedia, como en el TImage.
+
&nbsp;&nbsp;&nbsp;En este caso todo tiene que dibujarlo en el evento OnPaint del formulario. No se mantiene en la memoria intermedia, como en el TImage.
  
 
===Crear un control personalizado que se dibuja a sí mismo===
 
===Crear un control personalizado que se dibuja a sí mismo===
Crear un control personalizado tiene la ventaja de estructurar su código y poder reutilizar el control. Está opción es muy rápida, pero puede todavía generar parpadeo si no dibuja en un TBitmap primero y después dibuja en el canvas (lienzo). En este caso no necesita utilizar el evento OnPaint del control.
+
&nbsp;&nbsp;&nbsp;Crear un control personalizado tiene la ventaja de estructurar su código y poder reutilizar el control. Está opción es muy rápida, pero puede todavía generar parpadeo si no dibuja en un TBitmap primero y después dibuja en el canvas (lienzo). En este caso no necesita utilizar el evento OnPaint del control.
  
Este es un ejemplo de control personalizado:
+
&nbsp;&nbsp;&nbsp;Este es un ejemplo de control personalizado:
  
<code>
+
<syntaxhighlight lang="pascal">
  type
+
  uses Classes, SysUtils, Controls, Graphics, LCLType;
 +
 +
  type
 
   TMyDrawingControl = class(TCustomControl)
 
   TMyDrawingControl = class(TCustomControl)
 
   public
 
   public
 +
    procedure EraseBackground(DC: HDC); override;
 
     procedure Paint; override;
 
     procedure Paint; override;
 
   end;
 
   end;
 
   
 
   
  implementation
+
  implementation
 +
   
 +
  procedure TMyDrawingControl.EraseBackground(DC: HDC);
 +
  begin
 +
    // Quita los comentarios para habilitar el borrado del fondo predeterminado
 +
    //inherited EraseBackground(DC);
 +
  end;
 
   
 
   
procedure TMyDrawingControl.Paint;
+
  procedure TMyDrawingControl.Paint;
var
+
  var
  x, y: Integer;
+
        x, y: Integer;
  Bitmap: TBitmap;
+
        Bitmap: TBitmap;
begin
+
  begin
  Bitmap := TBitmap.Create;
+
    Bitmap := TBitmap.Create;
  try
+
    try
    // Initializes the Bitmap Size
+
      // Initializes the Bitmap Size
 
     Bitmap.Height := Height;
 
     Bitmap.Height := Height;
 
     Bitmap.Width := Width;
 
     Bitmap.Width := Width;
Line 217: Line 341:
 
   
 
   
 
   inherited Paint;
 
   inherited Paint;
  end;
+
  end;</syntaxhighlight>
</code>
+
&nbsp;&nbsp;&nbsp;y cómo lo creamos en el formulario:
y cómo lo creamos en el formulario:
+
 
<code>
+
<syntaxhighlight lang="pascal">
procedure TMyForm.FormCreate(Sender: TObject);
+
procedure TMyForm.FormCreate(Sender: TObject);
 
  begin
 
  begin
 
   MyDrawingControl:= TMyDrawingControl.Create(Self);
 
   MyDrawingControl:= TMyDrawingControl.Create(Self);
Line 230: Line 354:
 
   MyDrawingControl.Parent := Self;
 
   MyDrawingControl.Parent := Self;
 
   MyDrawingControl.DoubleBuffered := True;
 
   MyDrawingControl.DoubleBuffered := True;
  end;
+
  end;</syntaxhighlight>
</code>
+
&nbsp;&nbsp;&nbsp; y para destruirlo:
no olvide destruirlo:
+
 
<code>
+
<syntaxhighlight lang="pascal">
procedure TMyForm.FormDestroy(Sender: TObject);
+
procedure TMyForm.FormDestroy(Sender: TObject);
 
  begin
 
  begin
 
   MyDrawingControl.Free;
 
   MyDrawingControl.Free;
  end;
+
  end;</syntaxhighlight>
</code>
+
 
 +
&nbsp;&nbsp;&nbsp;Aunque se destruye de forma automática, porque usamos ''self'' como propietario.
  
No es necesario asignar cero a Top y Left, ya que ésta es la posición predefinida, pero se hace así para reforzar la posición donde se colocará el control.
+
&nbsp;&nbsp;&nbsp;No es necesario asignar cero a Top y Left, ya que ésta es la posición predefinida, pero se hace así para reforzar la posición donde se colocará el control.  
  
"MyDrawingControl.Parent := Self;" es muy importante y no verá su control si no lo hace así.
+
&nbsp;&nbsp;&nbsp;"MyDrawingControl.Parent := Self;" es muy importante y no se verá el control si no se hace así.  
  
"MyDrawingControl.DoubleBuffered := True;" se requiere para evitar el parpadeo en Windows. No tiene ningún efecto en gtk.
+
&nbsp;&nbsp;&nbsp;"MyDrawingControl.DoubleBuffered := True;" se requiere para evitar el parpadeo en Windows. No tiene ningún efecto en gtk.
  
 
===Utilizando el paquete para juegos de A.J. Venter===
 
===Utilizando el paquete para juegos de A.J. Venter===
  
El paquete para juegos está enfocado al dibujo de cualquier cosa en un canvas (lienzo) de doble memoria intermedia, que sólo se actualiza cuando usted está preparado. Esto supone verdaderamente bastante código, pero tiene la ventaja de ser capaz de hacer muy rápidamente cambios de escenas con múltiples sprites (objetos móviles) en ellos. Si desea utilizar esta opción, puede interesarle el paquete para juegos de A. J. Venter, un conjunto de componentes para desarrollar juegos en Lazarus, que proporciona un componente de pantalla de doble memoria intermedia así como un componente sprite (ojeto móvil), diseñado para integrarse bien con cualquier otro. Puede obtener el paquete para juegos via subversion:<br />
+
&nbsp;&nbsp;&nbsp;El paquete para juegos está enfocado al dibujo de cualquier cosa en un ''canvas'' (lienzo) de doble memoria intermedia, que sólo se actualiza cuando usted está preparado. Esto supone verdaderamente bastante código, pero tiene la ventaja de ser capaz de hacer muy rápidamente cambios de escenas con múltiples ''sprites'' (objetos móviles) en ellos. Si desea utilizar esta opción, puede interesarle el paquete para juegos de A. J. Venter, un conjunto de componentes para desarrollar juegos en Lazarus, que proporciona un componente de pantalla de doble memoria intermedia así como un componente ''sprite'' (ojeto móvil), diseñado para integrarse bien con cualquier otro. Puede obtener el paquete para juegos via subversion:<br />
 
<code>
 
<code>
 
svn co svn://silentcoder.co.za/lazarus/gamepack
 
svn co svn://silentcoder.co.za/lazarus/gamepack
 
</code>
 
</code>
 +
&nbsp;&nbsp;&nbsp;Más información, documentación y descargas en la [http://outkastsolutions.co.za/outkast/index.php?option=com_openwiki&Itemid=&id=gamepack página principal].
 +
 +
== Formatos Gráficos y Clases de Imágenes==
 +
 +
&nbsp;&nbsp;&nbsp;Esta tabla muestra la clase adecuada a utilizar para cada formato de imagen.
 +
 +
{| border=2 width="100%" align="center"
 +
|-
 +
! Formato
 +
! Clase de Imagen
 +
! Unidad
 +
|-
 +
|Cursor (cur)||TCursor||Graphics
 +
|-
 +
|Bitmap (bmp)||TBitmap||Graphics
 +
|-
 +
|Windows icon (ico)||TIcon||Graphics
 +
|-
 +
|Mac OS X icon (icns)||TicnsIcon||Graphics
 +
|-
 +
|Pixmap (xpm)||TPixmap||Graphics
 +
|-
 +
|Portable Network Graphic (png)||TPortableNetworkGraphic||Graphics
 +
|-
 +
|JPEG (jpg, jpeg)||TJpegImage||Graphics
 +
|-
 +
|PNM (pnm)||TPortableAnyMapGraphic||Graphics
 +
|}
 +
 +
<br />
 +
 +
==El formato del punto (''pixel'')==
 +
 +
===TColor===
 +
 +
&nbsp;&nbsp;&nbsp;La representación interna de un punto de un ''TColor'' en la LCL tiene esta forma XXBBGGRR, que coincide con el formato nativo de Windows y es el inverso del que usan la mayoría de las otras librerías, que utilizan AARRGGBB. La parte XX se utiliza para identificar si el color es un color fijo, cuyo caso XX será 00 o si es un índice a un sistema de colores. No hay espacio reservado para un canal alfa.
 +
 +
&nbsp;&nbsp;&nbsp;Para convertir desde un canal separado RGB  a ''TColor'' podemos usar esto:
 +
 +
<syntaxhighlight lang="pascal"> RGBToColor(RedVal, GreenVal, BlueVal);</syntaxhighlight>
 +
 +
&nbsp;&nbsp;&nbsp;Para obtener cada canal de una variable ''TColor'' usaremos las fucniones ''Red'', ''Green'' y ''Blue'':
 +
 +
<syntaxhighlight lang="pascal">
 +
  RedVal := Red(MyColor);
 +
  GreenVal := Green(MyColor);
 +
  BlueVal := Blue(MyColor);</syntaxhighlight>
 +
 +
===TFPColor===
 +
 +
&nbsp;&nbsp;&nbsp;''TFPColor'' utiliza el formato AARRGGBB común en muchas librerías.
 +
 +
==Trabajando con el lienzo mediante ''TCanvas''==
 +
 +
===Usando la fuente predeterminada de la interfaz IGU===
 +
 +
&nbsp;&nbsp;&nbsp;Esta tarea se puede realizar con este simple código:
 +
 +
<syntaxhighlight lang="pascal">SelectObject(Canvas.Handle, GetStockObject(DEFAULT_GUI_FONT));</syntaxhighlight>
 +
 +
===Dibujando un texto limitado a cierto ancho===
 +
 +
&nbsp;&nbsp;&nbsp;Usaremos ''DrawText'', primero con ''DT_CALCRECT'' y después sin ello.
 +
 +
<syntaxhighlight lang="pascal">
 +
  // Primero calculamos el tamaño del texto para después pintarlo
 +
  TextBox := Rect(0, currentPos.Y, Width, High(Integer));
 +
  DrawText(ACanvas.Handle, PChar(Text), Length(Text),
 +
    TextBox, DT_WORDBREAK or DT_INTERNAL or DT_CALCRECT);
 +
 +
  DrawText(ACanvas.Handle, PChar(Text), Length(Text),
 +
    TextBox, DT_WORDBREAK or DT_INTERNAL);</syntaxhighlight>
 +
 +
===Dibujar texto con bordes afilados (no suavizados)===
 +
 +
&nbsp;&nbsp;&nbsp;Some widgetsets support this via
 +
 +
<syntaxhighlight lang="pascal"> Canvas.Font.Quality:=fqNonAntialiased </syntaxhighlight>
 +
 +
&nbsp;&nbsp;&nbsp;Algunos motores gráficos como el gtk2 no soportan esto y  pintan siempre suavizando. Aquí tenemos un procedimiento simple para dibujar texto con bordes afilados en GTK2. No tiene en cuenta todos los casos, pero nos da una idea:
 +
 +
<syntaxhighlight lang="pascal">
 +
procedure PaintAliased(Canvas: TCanvas; x,y: integer; const TheText: string);
 +
var
 +
  w,h: integer;
 +
  IntfImg: TLazIntfImage;
 +
  Img: TBitmap;
 +
  dy: Integer;
 +
  dx: Integer;
 +
  col: TFPColor;
 +
  FontColor: TColor;
 +
  c: TColor;
 +
begin
 +
  w:=0;
 +
  h:=0;
 +
  Canvas.GetTextSize(TheText,w,h);
 +
  if (w<=0) or (h<=0) then exit;
 +
  Img:=TBitmap.Create;
 +
  IntfImg:=nil;
 +
  try
 +
    // paint text to a bitmap
 +
    Img.Masked:=true;
 +
    Img.SetSize(w,h);
 +
    Img.Canvas.Brush.Style:=bsSolid;
 +
    Img.Canvas.Brush.Color:=clWhite;
 +
    Img.Canvas.FillRect(0,0,w,h);
 +
    Img.Canvas.Font:=Canvas.Font;
 +
    Img.Canvas.TextOut(0,0,TheText);
 +
    // get memory image
 +
    IntfImg:=Img.CreateIntfImage;
 +
    // replace gray pixels
 +
    FontColor:=ColorToRGB(Canvas.Font.Color);
 +
    for dy:=0 to h-1 do begin
 +
      for dx:=0 to w-1 do begin
 +
        col:=IntfImg.Colors[dx,dy];
 +
        c:=FPColorToTColor(col);
 +
        if c<>FontColor then
 +
          IntfImg.Colors[dx,dy]:=colTransparent;
 +
      end;
 +
    end;
 +
    // create bitmap
 +
    Img.LoadFromIntfImage(IntfImg);
 +
    // paint
 +
    Canvas.Draw(x,y,Img);
 +
  finally
 +
    IntfImg.Free;
 +
    Img.Free;
 +
  end;
 +
end;</syntaxhighlight>
 +
 +
==Pintando sin la LCL==
 +
 +
&nbsp;&nbsp;&nbsp;Podemos dibujar sin usar la LCL. Por ejemplo, un programa que se ejecuta en un servidor web generando gráficos trabajará sin necesidad de ninguna librería visual. Para ello puede utilizar ''FPImage'' (alias ''fcl-image'') una muy genérica librería de imagen y dibujo completamente escrita en Pascal. De hecho, la LCL utiliza FPImage mucho y realiza la función de dibujo a través de llamadas al motor gráfico IGU (winapi, gtk, carbon, ...).
 +
 +
&nbsp;&nbsp;&nbsp;Este es un ejemplo, sobre como crear una imagen de 200x100, con un fondo blanco y algún texto y guardarla como un .png:
 +
 +
<syntaxhighlight lang="pascal">
 +
program fontdraw;
 +
 +
{$mode objfpc}{$H+}
 +
 +
uses
 +
  Classes, SysUtils, FPimage, FPImgCanv, ftfont, FPWritePNG, FPCanvas;
 +
 +
procedure TestFPImgFont;
 +
  var
 +
  Img: TFPMemoryImage;
 +
  Writer: TFPWriterPNG;
 +
  ms: TMemoryStream;
 +
  ImgCanvas: TFPImageCanvas;
 +
  fs: TFileStream;
 +
  AFont: TFreeTypeFont;
 +
begin
 +
  Img:=nil;
 +
  ImgCanvas:=nil;
 +
  Writer:=nil;
 +
  ms:=nil;
 +
  fs:=nil;
 +
  AFont:=nil;
 +
  try
 +
    // initialize free type font manager
 +
    ftfont.InitEngine;
 +
    FontMgr.SearchPath:='/usr/share/fonts/truetype/ttf-dejavu/';
 +
    AFont:=TFreeTypeFont.Create;
 +
 +
    // create an image of width 200, height 100
 +
    Img:=TFPMemoryImage.Create(200,100);
 +
    Img.UsePalette:=false;
 +
    // create the canvas with the drawing operations
 +
    ImgCanvas:=TFPImageCanvas.create(Img);
 +
 +
    // paint white background
 +
    ImgCanvas.Brush.FPColor:=colWhite;
 +
    ImgCanvas.Brush.Style:=bsSolid;
 +
    ImgCanvas.Rectangle(0,0,Img.Width,Img.Height);
 +
 +
    // paint text
 +
    ImgCanvas.Font:=AFont;
 +
    ImgCanvas.Font.Name:='DejaVuSans';
 +
    ImgCanvas.Font.Size:=20;
 +
    ImgCanvas.TextOut(10,30,'Test');
 +
 +
    // write image as png to memory stream
 +
    Writer:=TFPWriterPNG.create;
 +
    ms:=TMemoryStream.Create;
 +
    writer.ImageWrite(ms,Img);
 +
    // write memory stream to file
 +
    ms.Position:=0;
 +
    fs:=TFileStream.Create('testfont.png',fmCreate);
 +
    fs.CopyFrom(ms,ms.Size);
 +
  finally
 +
    AFont.Free;
 +
    ms.Free;
 +
    Writer.Free;
 +
    ImgCanvas.Free;
 +
    Img.Free;
 +
    fs.Free;
 +
  end;
 +
end;
 +
 +
begin
 +
  TestFPImgFont;
 +
end.</syntaxhighlight>
 +
 +
* [[Basic Pascal Tutorial]] - [http://www.taoyue.com/ Tao Yue's] [http://www.taoyue.com/tutorials/pascal/ Pascal Tutorial]
 +
* [[Free Pascal videos|Free Pascal video tutorials]]

Latest revision as of 03:30, 29 March 2020

Deutsch (de) English (en) español (es) français (fr) italiano (it) 日本語 (ja) 한국어 (ko) Nederlands (nl) português (pt) русский (ru) slovenčina (sk) 中文(中国大陆)‎ (zh_CN) 中文(台灣)‎ (zh_TW)

   Esta página dará inicio a una serie de tutorías que trataran sobre la manipulación de mapa de bits y otros tipos de gráficos. Como no soy un experto programador de gráficos, ¡invito a todos a compartir sus experiencias! Basta con añadir un enlace a la siguiente sección, añadir una página y crear su propio artículo para el Wiki.

   En esta página se dará alguna información general.

Otros artículos sobre gráficos

Dibujo 2D

  • ZenGL - Desarrollo de juegos multi-plataforma usando OpenGL.
  • BGRABitmap - Dibuja formas y mapas de bits con transparencia, acceso directo a los píxeles, etc.
  • LazRGBGraphics - Un paquete para un veloz procesado en memoria de imagenes y manipulado de puntos (pixel) (similar a scan line)
  • Double Gradient - Dibuja gradientes dobles y 'n gradientes' en bitmaps facilmente.
  • Gradient Filler - TGradientFiller es la mejor manera de crear 'n gradientes' personalizados en Lazarus.
  • PascalMagick - Una forma fácil de usar la API para interactuar con ImageMagick, un paquete software libre multiplataforma para crear, editar y componer imágenes de mapa de bit.
  • Sample Graphics/es - Galería de gráficos creada con Lazarus y herramientas de dibujo.

Dibujo 3D

  • GLScene - Una adaptación de la librería visual de gráficos OpenGL GLScene

Gráficos

  • TAChart - Componente gráfico para Lazarus
  • PlotPanel - Un componente para trazar y realizar gráficos animados
  • Perlin Noise - Un atículo sobre la utilización de Perlin Noise en aplicaciones LCL.

Trabajo con TBitmap

   Lo primero que hay que recordar es que Lazarus quiere decir plataforma independiente, así que todos los métodos que usan funcionalidades de la API de Windows se salen de la cuestión. Así que un método como ScanLine no es soportado por Lazarus, porque está destinado a Device Independent Bitmap y usa funciones de GDI32.dll.

   Tenga cuidado porque si no especifica la anchura y altura de su TBitmap tendrá la predefinida, que es muy pequeña.

Un ejemplo de desvanecimiento (fading)

   Supongamos que quiere hacer una imagen que se desvanece. En Delphi podría hacer algo así:

 type
   PRGBTripleArray = ^TRGBTripleArray;
   TRGBTripleArray = array[0..32767] of TRGBTriple;
 
 procedure TForm1.FadeIn(aBitMap: TBitMap);
 var
   Bitmap, BaseBitmap: TBitmap;
   Row, BaseRow: PRGBTripleArray;
   x, y, step: integer;
 begin
   Bitmap := TBitmap.Create;
   try
     Bitmap.PixelFormat := pf32bit;  //  o bien pf24bit
     Bitmap.Assign(aBitMap);
     BaseBitmap := TBitmap.Create;
     try
       BaseBitmap.PixelFormat := pf32bit;
       BaseBitmap.Assign(Bitmap);
       for step := 0 to 32 do begin
         for y := 0 to (Bitmap.Height - 1) do begin
           BaseRow := BaseBitmap.Scanline[y];
           Row := Bitmap.Scanline[y];
           for x := 0 to (Bitmap.Width - 1) do begin
             Row[x].rgbtRed := (step * BaseRow[x].rgbtRed) shr 5; // desvanec. rojo
             Row[x].rgbtGreen := (step * BaseRow[x].rgbtGreen) shr 5; // desvanec. verde
             Row[x].rgbtBlue := (step * BaseRow[x].rgbtBlue) shr 5; // desvanec. azul
           // disminuyendo progresivamente los valores RGB (Red,Green,Blue o Rojo, 
           // Verde, Azul que son tres componentes que forman el color a mostrar
           // logramos que la imagen se vaya apagando hasta quedarse a oscuras.
           end;
         end;
         Form1.Canvas.Draw(0, 0, Bitmap);
         InvalidateRect(Form1.Handle, nil, False);
         RedrawWindow(Form1.Handle, nil, 0, RDW_UPDATENOW);
       end;
     finally
       BaseBitmap.Free;
     end;
   finally
     Bitmap.Free;
   end;
 end;

   Está función en Lazarus sería así:

{ This code has been taken from the $LazarusPath/examples/lazintfimage/fadein1.lpi project. }
uses LCLType, // HBitmap type
     IntfGraphics, // TLazIntfImage type
     fpImage; // TFPColor type

 procedure TForm1.FadeIn(ABitMap: TBitMap);
 var
   SrcIntfImg, TempIntfImg: TLazIntfImage;
   ImgHandle,ImgMaskHandle: HBitmap;
   FadeStep: Integer;
   px, py: Integer;
   CurColor: TFPColor;
   TempBitmap: TBitmap;
 begin
   SrcIntfImg:=TLazIntfImage.Create(0,0);
   SrcIntfImg.LoadFromBitmap(ABitmap.Handle,ABitmap.MaskHandle);
   TempIntfImg:=TLazIntfImage.Create(0,0);
   TempIntfImg.LoadFromBitmap(ABitmap.Handle,ABitmap.MaskHandle);
   TempBitmap:=TBitmap.Create;
   for FadeStep:=1 to 32 do begin
     for py:=0 to SrcIntfImg.Height-1 do begin
       for px:=0 to SrcIntfImg.Width-1 do begin
         CurColor:=SrcIntfImg.Colors[px,py];
         CurColor.Red:=(CurColor.Red*FadeStep) shr 5;
         CurColor.Green:=(CurColor.Green*FadeStep) shr 5;
         CurColor.Blue:=(CurColor.Blue*FadeStep) shr 5;
         TempIntfImg.Colors[px,py]:=CurColor;
       end;
     end;
     TempIntfImg.CreateBitmaps(ImgHandle,ImgMaskHandle,false);
     TempBitmap.Handle:=ImgHandle;
     TempBitmap.MaskHandle:=ImgMaskHandle;
     Canvas.Draw(0,0,TempBitmap);
   end;
   SrcIntfImg.Free;
   TempIntfImg.Free;
   TempBitmap.Free;
 end;

   El código Lazarus de esta página se ha tomado del proyecto $LazarusPath/examples/lazintfimage/fadein1.lpi . Así que si desea iniciarse en la programación de gráficos eche una mirada a este ejemplo.

Dibujando mapa de bits de color transparente

   Una nueva característica, incluida en Lazarus 0.9.11, son los mapa de bits de color transparente. Los archivos Bitmap (*.BMP) no pueden almacenar cualquier información sobre transparencia, pero pueden trabajar como si la tuviera si selecciona un color en ellos para representar la zona transparente. Este es un truco habitual usado en aplicaciones Win32.

   El siguiente ejemplo carga un mapa de bit desde un recurso Windows, selecciona un color para que sea transparente (clFuchsia) y después lo dibuja.

procedure MyForm.MyButtonOnClick(Sender: TObject);
var
  buffer: THandle;
  bmp: TBitmap;
  memstream: TMemoryStream;
begin
  bmp := TBitmap.Create;

  buffer := Windows.LoadBitmap(hInstance, MAKEINTRESOURCE(ResourceID));

  if (buffer = 0) then exit; // Error cargando el mapa de bits

  bmp.Handle := buffer;
  memstream := TMemoryStream.create;
  try
    bmp.SaveToStream(memstream);
    memstream.position := 0;
    bmp.LoadFromStream(memstream);
  finally
    memstream.free;
  end;

  bmp.Transparent := True;
  bmp.TransparentColor := clFuchsia;

  MyCanvas.Draw(0, 0, bmp);

  bmp.Free; // Liberar el recurso Tbitmap (bmp) creado con anterioridad
end;

   Observe las operaciones de memoria realizadas con TMemoryStream. Son necesarios para garantizar la correcta carga de la imagen.

Tomar una captura de la pantalla

   Desde Lazarus 0.9.16 podemos utilizar la LCL para realizar capturas de la pantalla de forma multiplataforma. El código de ejemplo siguiente lo hace (funciona en gtk2 y Win32, pero no en gtk1 actualmente):

 uses LCLIntf, LCLType;

  ...

 var
   MyBitmap: TBitmap;
   ScreenDC: HDC;
 begin
   MyBitmap := TBitmap.Create;
   ScreenDC := GetDC(0);
   MyBitmap.LoadFromDevice(ScreenDC);
   ReleaseDC(ScreenDC);

  ...

Pintar un TLazIntfImage en el lienzo (Canvas)

   Puesto que la propiedad ScanLines ha sido temporalmente removida de la clase TBitmap, la única forma de acceder a datos de línea de exploración de un mapa de bits, es utilizar TLazIntfImage. Esta es una muestra de cómo crear un TLazIntfImage desde un TBitmap y, a continuación, dibujarlo en el lienzo (canvas).

uses
  ...GraphType, IntfGraphics, LCLType, LCLProc,  LCLIntf ...

procedure TForm1.Button4Click(Sender: TObject);
var
  b: TBitmap;
  t: TLazIntfImage;
  bmp, old: HBitmap;
  msk: HBitmap;
  tmpDC: HDC;
begin
  b := TBitmap.Create;
  try
    b.LoadFromFile('test.bmp');
    t := b.CreateIntfImage;
    t.CreateBitmaps(bmp, msk, false);

    tmpDC := CreateCompatibleDC(Canvas.Handle);
    old := SelectObject(tmpDC, bmp);
    BitBlt(Canvas.Handle, 0, 0, t.Width, t.Height, tmpDC, 0, 0, SRCCOPY);
    DeleteObject(SelectObject(tmpDC, old));
    DeleteObject(msk);
    DeleteDC(tmpDC);

  finally
    t.Free;
    b.Free;
  end;
end;

Gráficos en movimiento - Cómo evitar el parpadeo

   Muchos programas dibujan su salida al GUI como gráficos 2D. Si esos gráficos necesitan cambiar rápidamente, pronto tendrá un problema: los gráficos que cambian rápidamente a menudo parpadean en la pantalla. Esto ocurre cuando los usuarios algunas veces ven las imágenes ocultas y algunas veces las ven cuando se dibujan sólo parcialmente. Esto ocurre porque el proceso de pintar requiere tiempo.

   Pero ¿cómo puedo evitar el parpadeo y conseguir la mejor velocidad de trazado? Claro, podría trabajar con aceleración de hardware utilizando OpenGL, pero es verdaderamente pesado para pequeños programas u ordenadores antiguos. Este tutorial esta enfocado hacia el dibujo con TCanvas. Si necesita ayuda con OpenGL, eche una mirada al ejemplo que viene con Lazarus. Puede también usar el paquete para juegos de A. J. Venter, que proporciona un canvas (lienzo) de doble memoria intermedia y un componente sprite (objeto móvil).

   Ahora examinaremos las opciones que tenemos para dibujar en un Canvas (lienzo):

Dibujar en un TImage

   Un TImage consta de 2 partes: Un TGraphic, por lo general un TBitmap, conteniendo la imagen persistente y el área visual, que es pintada en cada evento OnPaint. Cambiar el tamaño del TImage no cambiar el tamaño del mapa de bits. El gráfico (o mapa de bits) es accesible a través de Image1.Picture.Graphic (o Image1.Picture.Bitmap). El lienzo es Image1.Picture.Bitmap.Canvas. El lienzo de la zona visual de un TImage sólo es accesible durante Image1.OnPaint través de Image1.Canvas.

   Importante: Nunca use el evento OnPaint para dibujar en un TImage. Un TImage es almacenado en la memoria intermedia por lo que todo lo que necesita hacer es dibujarlo desde cualquier lugar y se cambia para siempre. Sin embargo, si constantemente redibuja, la imagen parpadeará. En este caso puede intentar otras opciones. Dibujar en un TImage se considera más lento que otras posibilidades.

Redimensionando el mapa de bits de un TImage

   Nota: No uses esto duante un OnPaint.

  with Image1.Picture.Bitmap do begin
    Width:=100;
    Height:=120;
  end;

Pintando el mapa de bits de un TImage

   Nota: No uses esto duante un OnPaint.

  with Image1.Picture.Bitmap.Canvas do begin
    // fill the entire bitmap with red
    Brush.Color:=clRed;
    FillRect(0,0,Width,Height);
  end;

   Nota: dentro de un Image1.OnPaint el lienzo (el Image1.Canvas) apunta al área visible volátil. Fuera del evento Image1.OnPaint el lienzo apunta a Image1.Picture.Bitmap.Canvas.

   Otro ejemplo:

procedure TForm1.BitBtn1Click(Sender: TObject);
  var
    x, y: Integer;
  begin
    // Pinta el fondo
    MyImage.Canvas.Pen.Color := clWhite;
    MyImage.Canvas.Rectangle(0, 0, Image.Width, Image.Height);
   
    // Dibuja cuadrados
    MyImage.Canvas.Pen.Color := clBlack;
    for x := 1 to 8 do
     for y := 1 to 8 do
      MyImage.Canvas.Rectangle(Round((x - 1) * Image.Width / 8), Round((y - 1) * Image.Height / 8),
         Round(x * Image.Width / 8), Round(y * Image.Height / 8));
  end;

Pintando el área visual volátil de un TImage

   Sólo se puede pintar en esta área durante OnPaint. OnPaint se llama automáticamente por el LCL, cuando la zona es invalidada. Se puede invalidar la zona manualmente con Image1.Invalidate. Esto no llamará inmediatamente a OnPaint y es posible invocar Invalidate tantas veces como se desee.

procedure TForm.Image1Paint(Sender: TObject);
  begin
    with Image1.Canvas do begin
      // paint a line
      Pen.Color:=clRed;
      Line(0,0,Width,Height);
    end;
  end;

Dibujar en el evento OnPaint

   En este caso todo tiene que dibujarlo en el evento OnPaint del formulario. No se mantiene en la memoria intermedia, como en el TImage.

Crear un control personalizado que se dibuja a sí mismo

   Crear un control personalizado tiene la ventaja de estructurar su código y poder reutilizar el control. Está opción es muy rápida, pero puede todavía generar parpadeo si no dibuja en un TBitmap primero y después dibuja en el canvas (lienzo). En este caso no necesita utilizar el evento OnPaint del control.

   Este es un ejemplo de control personalizado:

  uses  Classes, SysUtils, Controls, Graphics, LCLType;
 
  type
   TMyDrawingControl = class(TCustomControl)
   public
     procedure EraseBackground(DC: HDC); override;
     procedure Paint; override;
   end;
 
  implementation
 
  procedure TMyDrawingControl.EraseBackground(DC: HDC);
   begin
    // Quita los comentarios para habilitar el borrado del fondo predeterminado
    //inherited EraseBackground(DC);
   end; 
 
  procedure TMyDrawingControl.Paint;
   var
         x, y: Integer;
        Bitmap: TBitmap;
  begin
    Bitmap := TBitmap.Create;
    try
      // Initializes the Bitmap Size
     Bitmap.Height := Height;
     Bitmap.Width := Width;
  
     // Draws the background
     Bitmap.Canvas.Pen.Color := clWhite;
     Bitmap.Canvas.Rectangle(0, 0, Width, Height);
 
     // Draws squares
     Bitmap.Canvas.Pen.Color := clBlack;
     for x := 1 to 8 do
      for y := 1 to 8 do
       Bitmap.Canvas.Rectangle(Round((x - 1) * Width / 8), Round((y - 1) * Height / 8),
        Round(x * Width / 8), Round(y * Height / 8));
       
     Canvas.Draw(0, 0, Bitmap);
   finally
     Bitmap.Free;
   end;
 
   inherited Paint;
 end;

   y cómo lo creamos en el formulario:

procedure TMyForm.FormCreate(Sender: TObject);
 begin
   MyDrawingControl:= TMyDrawingControl.Create(Self);
   MyDrawingControl.Height := 400;
   MyDrawingControl.Width := 500;
   MyDrawingControl.Top := 0;
   MyDrawingControl.Left := 0;
   MyDrawingControl.Parent := Self;
   MyDrawingControl.DoubleBuffered := True;
 end;

    y para destruirlo:

procedure TMyForm.FormDestroy(Sender: TObject);
 begin
   MyDrawingControl.Free;
 end;

   Aunque se destruye de forma automática, porque usamos self como propietario.

   No es necesario asignar cero a Top y Left, ya que ésta es la posición predefinida, pero se hace así para reforzar la posición donde se colocará el control.

   "MyDrawingControl.Parent := Self;" es muy importante y no se verá el control si no se hace así.

   "MyDrawingControl.DoubleBuffered := True;" se requiere para evitar el parpadeo en Windows. No tiene ningún efecto en gtk.

Utilizando el paquete para juegos de A.J. Venter

   El paquete para juegos está enfocado al dibujo de cualquier cosa en un canvas (lienzo) de doble memoria intermedia, que sólo se actualiza cuando usted está preparado. Esto supone verdaderamente bastante código, pero tiene la ventaja de ser capaz de hacer muy rápidamente cambios de escenas con múltiples sprites (objetos móviles) en ellos. Si desea utilizar esta opción, puede interesarle el paquete para juegos de A. J. Venter, un conjunto de componentes para desarrollar juegos en Lazarus, que proporciona un componente de pantalla de doble memoria intermedia así como un componente sprite (ojeto móvil), diseñado para integrarse bien con cualquier otro. Puede obtener el paquete para juegos via subversion:
svn co svn://silentcoder.co.za/lazarus/gamepack    Más información, documentación y descargas en la página principal.

Formatos Gráficos y Clases de Imágenes

   Esta tabla muestra la clase adecuada a utilizar para cada formato de imagen.

Formato Clase de Imagen Unidad
Cursor (cur) TCursor Graphics
Bitmap (bmp) TBitmap Graphics
Windows icon (ico) TIcon Graphics
Mac OS X icon (icns) TicnsIcon Graphics
Pixmap (xpm) TPixmap Graphics
Portable Network Graphic (png) TPortableNetworkGraphic Graphics
JPEG (jpg, jpeg) TJpegImage Graphics
PNM (pnm) TPortableAnyMapGraphic Graphics


El formato del punto (pixel)

TColor

   La representación interna de un punto de un TColor en la LCL tiene esta forma XXBBGGRR, que coincide con el formato nativo de Windows y es el inverso del que usan la mayoría de las otras librerías, que utilizan AARRGGBB. La parte XX se utiliza para identificar si el color es un color fijo, cuyo caso XX será 00 o si es un índice a un sistema de colores. No hay espacio reservado para un canal alfa.

   Para convertir desde un canal separado RGB a TColor podemos usar esto:

 RGBToColor(RedVal, GreenVal, BlueVal);

   Para obtener cada canal de una variable TColor usaremos las fucniones Red, Green y Blue:

  RedVal := Red(MyColor);
  GreenVal := Green(MyColor);
  BlueVal := Blue(MyColor);

TFPColor

   TFPColor utiliza el formato AARRGGBB común en muchas librerías.

Trabajando con el lienzo mediante TCanvas

Usando la fuente predeterminada de la interfaz IGU

   Esta tarea se puede realizar con este simple código:

SelectObject(Canvas.Handle, GetStockObject(DEFAULT_GUI_FONT));

Dibujando un texto limitado a cierto ancho

   Usaremos DrawText, primero con DT_CALCRECT y después sin ello.

  // Primero calculamos el tamaño del texto para después pintarlo
  TextBox := Rect(0, currentPos.Y, Width, High(Integer));
  DrawText(ACanvas.Handle, PChar(Text), Length(Text),
    TextBox, DT_WORDBREAK or DT_INTERNAL or DT_CALCRECT);

  DrawText(ACanvas.Handle, PChar(Text), Length(Text),
    TextBox, DT_WORDBREAK or DT_INTERNAL);

Dibujar texto con bordes afilados (no suavizados)

   Some widgetsets support this via

 Canvas.Font.Quality:=fqNonAntialiased

   Algunos motores gráficos como el gtk2 no soportan esto y pintan siempre suavizando. Aquí tenemos un procedimiento simple para dibujar texto con bordes afilados en GTK2. No tiene en cuenta todos los casos, pero nos da una idea:

procedure PaintAliased(Canvas: TCanvas; x,y: integer; const TheText: string);
 var
   w,h: integer;
   IntfImg: TLazIntfImage;
   Img: TBitmap;
   dy: Integer;
   dx: Integer;
   col: TFPColor;
   FontColor: TColor;
   c: TColor;
 begin
   w:=0;
   h:=0;
   Canvas.GetTextSize(TheText,w,h);
   if (w<=0) or (h<=0) then exit;
   Img:=TBitmap.Create;
   IntfImg:=nil;
   try
     // paint text to a bitmap
    Img.Masked:=true;
    Img.SetSize(w,h);
    Img.Canvas.Brush.Style:=bsSolid;
    Img.Canvas.Brush.Color:=clWhite;
    Img.Canvas.FillRect(0,0,w,h);
    Img.Canvas.Font:=Canvas.Font;
    Img.Canvas.TextOut(0,0,TheText);
    // get memory image
    IntfImg:=Img.CreateIntfImage;
    // replace gray pixels
    FontColor:=ColorToRGB(Canvas.Font.Color);
    for dy:=0 to h-1 do begin
      for dx:=0 to w-1 do begin
        col:=IntfImg.Colors[dx,dy];
        c:=FPColorToTColor(col);
        if c<>FontColor then
          IntfImg.Colors[dx,dy]:=colTransparent;
      end;
    end;
    // create bitmap
    Img.LoadFromIntfImage(IntfImg);
    // paint
    Canvas.Draw(x,y,Img);
   finally
     IntfImg.Free;
     Img.Free;
   end;
 end;

Pintando sin la LCL

   Podemos dibujar sin usar la LCL. Por ejemplo, un programa que se ejecuta en un servidor web generando gráficos trabajará sin necesidad de ninguna librería visual. Para ello puede utilizar FPImage (alias fcl-image) una muy genérica librería de imagen y dibujo completamente escrita en Pascal. De hecho, la LCL utiliza FPImage mucho y realiza la función de dibujo a través de llamadas al motor gráfico IGU (winapi, gtk, carbon, ...).

   Este es un ejemplo, sobre como crear una imagen de 200x100, con un fondo blanco y algún texto y guardarla como un .png:

 
program fontdraw;

 {$mode objfpc}{$H+}

 uses
   Classes, SysUtils, FPimage, FPImgCanv, ftfont, FPWritePNG, FPCanvas;

 procedure TestFPImgFont;
  var
   Img: TFPMemoryImage;
   Writer: TFPWriterPNG;
   ms: TMemoryStream;
   ImgCanvas: TFPImageCanvas;
   fs: TFileStream;
   AFont: TFreeTypeFont;
 begin
  Img:=nil;
  ImgCanvas:=nil;
  Writer:=nil;
  ms:=nil;
  fs:=nil;
  AFont:=nil;
  try
    // initialize free type font manager
    ftfont.InitEngine;
    FontMgr.SearchPath:='/usr/share/fonts/truetype/ttf-dejavu/';
    AFont:=TFreeTypeFont.Create;

    // create an image of width 200, height 100
    Img:=TFPMemoryImage.Create(200,100);
    Img.UsePalette:=false;
    // create the canvas with the drawing operations
    ImgCanvas:=TFPImageCanvas.create(Img);

    // paint white background
    ImgCanvas.Brush.FPColor:=colWhite;
    ImgCanvas.Brush.Style:=bsSolid;
    ImgCanvas.Rectangle(0,0,Img.Width,Img.Height);

    // paint text
    ImgCanvas.Font:=AFont;
    ImgCanvas.Font.Name:='DejaVuSans';
    ImgCanvas.Font.Size:=20;
    ImgCanvas.TextOut(10,30,'Test');

    // write image as png to memory stream
    Writer:=TFPWriterPNG.create;
    ms:=TMemoryStream.Create;
    writer.ImageWrite(ms,Img);
    // write memory stream to file
    ms.Position:=0;
    fs:=TFileStream.Create('testfont.png',fmCreate);
    fs.CopyFrom(ms,ms.Size);
  finally
    AFont.Free;
    ms.Free;
    Writer.Free;
    ImgCanvas.Free;
    Img.Free;
    fs.Free;
  end;
 end;

 begin
  TestFPImgFont;
 end.