Difference between revisions of "Developing with Graphics/es"

From Lazarus wiki
Jump to navigationJump to search
Line 208: Line 208:
  
 
===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. 
 
  
 +
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''.
  
A TImage consists of 2 parts: A TGraphic, usually a TBitmap, holding the persistent picture and the visual area, which is repainted on every OnPaint. Resizing the TImage does '''not''' resize the bitmap.
+
'''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.
The graphic (or bitmap) is accessible via Image1.Picture.Graphic (or Image1.Picture.Bitmap). The canvas is Image1.Picture.Bitmap.Canvas.
 
The canvas of the visual area of a TImage is only accessible during Image1.OnPaint via Image1.Canvas.
 
  
'''Important''': Never use the OnPaint of the Image1 event to draw to the graphic/bitmap of a TImage. The graphic of a TImage is buffered so all you need to do is draw to it from anywhere and the change is there forever. However, if you are constantly redrawing, the image will flicker. In this case you can try the other options. Drawing to a TImage is considered slower then the other approaches.
+
====Redimensionando el mapa de bits de un ''TImage''====
  
====Resizing the bitmap of a TImage====
+
Nota: No uses esto duante un ''OnPaint''.
  
Note: Do not use this during OnPaint.
+
<delphi>  with Image1.Picture.Bitmap do begin
 
 
  with Image1.Picture.Bitmap do begin
 
 
     Width:=100;
 
     Width:=100;
 
     Height:=120;
 
     Height:=120;
   end;
+
   end;</delphi>
  
====Painting on the bitmap of a TImage====
+
====Pintando el mapa de bits de un ''TImage''====
  
Note: Do not use this during OnPaint.
+
Nota: No uses esto duante un ''OnPaint''.
  
  with Image1.Picture.Bitmap.Canvas do begin
+
<delphi>  with Image1.Picture.Bitmap.Canvas do begin
 
     // fill the entire bitmap with red
 
     // fill the entire bitmap with red
 
     Brush.Color:=clRed;
 
     Brush.Color:=clRed;
 
     FillRect(0,0,Width,Height);
 
     FillRect(0,0,Width,Height);
   end;
+
   end;</delphi>
  
Note: Inside of Image1.OnPaint the Image1.Canvas points to the volatile visible area. Outside of Image1.OnPaint the Image1.Canvas points to Image1.Picture.Bitmap.Canvas.
+
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''.
  
Another example:
+
Otro ejemplo:
  
<code>
+
<delphi> procedure TForm1.BitBtn1Click(Sender: TObject);
procedure TForm1.BitBtn1Click(Sender: TObject);
 
 
  var
 
  var
 
   x, y: Integer;
 
   x, y: Integer;
Line 255: Line 250:
 
     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;</delphi>
</code>
 
  
==== Painting on the volatile visual area of the TImage====
+
==== Pintando el  área visual volátil de un ''TImage''====
  
You can only paint on this area during OnPaint. OnPaint is eventually called automatically by the LCL when the area was invalidated. You can invalidate the area manually with Image1.Invalidate. This will not immediately call OnPaint and you can call Invalidate as many times as you want.
+
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);
+
<delphi>  procedure TForm.Image1Paint(Sender: TObject);
 
   begin
 
   begin
 
     with Image1.Canvas do begin
 
     with Image1.Canvas do begin
Line 269: Line 263:
 
       Line(0,0,Width,Height);
 
       Line(0,0,Width,Height);
 
     end;
 
     end;
   end;
+
   end;</delphi>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
<delphi> procedure TForm1.BitBtn1Click(Sender: TObject);
 
var
 
  x, y: Integer;
 
begin
 
  // Draws the backgroung
 
  MyImage.Canvas.Pen.Color := clWhite;
 
  MyImage.Canvas.Rectangle(0, 0, Image.Width, Image.Height);
 
 
 
  // Draws squares
 
  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;</delphi>
 
  
 
===Dibujar en el evento OnPaint===
 
===Dibujar en el evento OnPaint===

Revision as of 18:46, 21 October 2009

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 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.

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

Otros artículos sobre gráficos

  • GLScene - Una adaptación de la librería visual de gráficos OpenGL GLScene
  • TAChart - Componente gráfico para 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.
  • PlotPanel - Un componente para trazar y realizar gráficos animados
  • LazRGBGraphics - Un paquete para un veloz procesado en memoria de imagenes y manipulado de puntos (pixel) (similar a scan line)
  • 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í: <delphi> 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;</delphi>

Está función en Lazarus sería así: <delphi> 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.CreateBitmap(ImgHandle,ImgMaskHandle,false);
    TempBitmap.Handle:=ImgHandle;
    TempBitmap.MaskHandle:=ImgMaskHandle;
    Canvas.Draw(0,0,TempBitmap);
  end;
  SrcIntfImg.Free;
  TempIntfImg.Free;
  TempBitmap.Free;
end;</delphi>

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.

<delphi> 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;</delphi>

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

<delphi> uses LCLIntf, LCLType;

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

</delphi>

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

<delphi> 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; </delphi>

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.

<delphi> with Image1.Picture.Bitmap do begin

   Width:=100;
   Height:=120;
 end;</delphi>

Pintando el mapa de bits de un TImage

Nota: No uses esto duante un OnPaint.

<delphi> with Image1.Picture.Bitmap.Canvas do begin

   // fill the entire bitmap with red
   Brush.Color:=clRed;
   FillRect(0,0,Width,Height);
 end;</delphi>

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:

<delphi> procedure TForm1.BitBtn1Click(Sender: TObject);

var
  x, y: Integer;
begin
  // Draws the backgroung
  MyImage.Canvas.Pen.Color := clWhite;
  MyImage.Canvas.Rectangle(0, 0, Image.Width, Image.Height);
  
  // Draws squares
  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;</delphi>

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.

<delphi> procedure TForm.Image1Paint(Sender: TObject);

 begin
   with Image1.Canvas do begin
     // paint a line
     Pen.Color:=clRed;
     Line(0,0,Width,Height);
   end;
 end;</delphi>

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:

<delphi> type

  TMyDrawingControl = class(TCustomControl)
  public
    procedure Paint; override;
  end;

implementation

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;</delphi>

y cómo lo creamos en el formulario: <delphi> 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;</delphi>

no olvide destruirlo: <delphi> procedure TMyForm.FormDestroy(Sender: TObject);

begin
  MyDrawingControl.Free;
end;</delphi>

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í.

"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