Difference between revisions of "Developing with Graphics/de"

From Lazarus wiki
Jump to navigationJump to search
m (Fixed syntax highlighting)
 
(21 intermediate revisions by 5 users not shown)
Line 1: Line 1:
 
{{Developing with Graphics}}
 
{{Developing with Graphics}}
  
Diese Seite wird der Anfang von Tutorials sein bezüglich der Manipulation von Bitmaps und anderen Grafiken. Da ich kein Grafikprogrammierer bin, lade ich alle ein ihr Wissen zu teilen! Fügen Sie einfach einen Link zum nächsten Abschnitt hinzu, fügen Sie eine Seite hinzu und erzeugen Sie Ihren eigenen Wiki Artikel.
+
Diese Seite beschreibt die grundlegenden Klassen und Techniken im Hinblick auf das Zeichnen von Grafiken mit Lazarus. Andere, speziellere Themen sind in gesonderten Artikeln zu finden.
  
Auf dieser Seite werden einige allgemeine Informationen geboten.
+
__TOC__
  
__TOC__
+
== Andere Grafikartikel ==
  
== Andere Grafikartikel ==  
+
===2D Grafik===
 +
* [[ZenGL]] - cross-platform game development library using OpenGL.  
 
* [[BGRABitmap/de|BGRABitmap]] - Zeichnen von Formen und Bitmaps mit Transparenzinformationen, direkte Zugriff auf Pixels, etc.  
 
* [[BGRABitmap/de|BGRABitmap]] - Zeichnen von Formen und Bitmaps mit Transparenzinformationen, direkte Zugriff auf Pixels, etc.  
 +
* [[LazRGBGraphics]] - A package for fast in memory image processing and pixel manipulations (like scan line).
 +
* [[fpvectorial]] - Offers support to read, modify and write vectorial images.
 +
* [[Double Gradient/de|Double Gradient]] - Zeichnet 'doppelte Farbverläufe' und 'n-fache Farbverläufe' ganz einfach in Bitmaps.
 +
* [[Gradient Filler]] - TGradientFiller is the best way to create custom n gradients in Lazarus.
 +
* [[PascalMagick]] - ein einfach zu verwendendes [[Glossary#API|API]] für die Kopplung mit [http://www.imagemagick.org ImageMagick], einer freien Multiplattform-Softwaresammlung um Bitmaps zu erzeugen, zu bearbeiten und zu entwerfen.
 +
* [[Sample Graphics]] - graphics gallery created with Lazarus and drawing tools
 +
* [[Fast direct pixel access]] - speed comparison of some methods for direct bitmap pixel access
 +
 +
===3D Grafik===
 
* [[GLScene/de|GLScene]] - Eine Portierung der visuellen OpenGL-Graphikbibliothek [http://www.glscene.org GLScene]
 
* [[GLScene/de|GLScene]] - Eine Portierung der visuellen OpenGL-Graphikbibliothek [http://www.glscene.org GLScene]
* [[TAChart/de| TAChart]] - Chartkomponente für Lazarus
+
 
* [[PascalMagick]] - ein einfach zu verwendendes [[Glossary#API|API]] für die Kopplung mit [http://www.imagemagick.org ImageMagick], einer freien Multiplattform-Softwaresammlung um Bitmaps zu erzeugen, zu bearbeiten und zu entwerfen.
+
===Diagramme===
 +
* [[TAChart/de| TAChart]] - Diagrammkomponente für Lazarus
 
* [[PlotPanel/de|PlotPanel]] - Eine Komponente zum Darstellen( Plotting / Charting) von animierten Graphen. Ähnlich wie TAChart.
 
* [[PlotPanel/de|PlotPanel]] - Eine Komponente zum Darstellen( Plotting / Charting) von animierten Graphen. Ähnlich wie TAChart.
* [[LazRGBGraphics]] - A package for fast in memory image processing and pixel manipulations (like scan line).
 
 
* [[Perlin Noise]] - Ein Artikel über die Verwendung von Perlin Noise in LCL Anwendungen.
 
* [[Perlin Noise]] - Ein Artikel über die Verwendung von Perlin Noise in LCL Anwendungen.
* [[Double Gradient/de|Double Gradient]] - Zeichnet 'doppelte Farbverläufe' und 'n-fache Farbverläufe' ganz einfach in Bitmaps.
 
* [[Gradient Filler]] - TGradientFiller is the best way to create custom n gradients in Lazarus.
 
  
 
== Arbeiten mit TBitmap ==
 
== Arbeiten mit TBitmap ==
Das erste woran Sie denken sollten ist, dass Lazarus plattformunabhängig sein soll. Daher kommen alle Methoden, welche die Windows API Funktionalität nutzen, nicht in Frage. Daher wird eine Methode wie ScanLine nicht von Lazarus unterstützt, weil sie für Geräte-unabhängige Bitmaps gedacht ist und Funktionen von GDI32.dll verwendet.
+
Das Objekt 'TBitmap' speichert eine Bitmap, auf die Sie zeichnen können, bevor Sie sie am Bildschirm anzeigen. Wenn Sie eine Bitmap erzeugen, müssen Sie deren Höhe und Breite immer angeben, ansonsten sind sie 0 und es wird nichts gezeichnet.
  
Beachten Sie, dass die Standardwerte von Breite und Höhe Ihrer [[doc:lcl/graphics/tbitmap.html|TBitmap]] ziemlich klein sind. Es ist daher wichtig, diese Maße immer genau zu spezifizieren.
+
===Direkter Zugriff auf Pixel===
 +
Für einen direkten Zugriff auf Bitmaps können Sie externe Bibliotheken einsetzen, wie z. B. [[BGRABitmap]], [[LazRGBGraphics]] und [[Current conversion projects#Graphics32|Graphics32]]. Einen Vergleich verschiedener Methoden des Zugriffs auf ein Pixel finden Sie unter [[Fast direct pixel access|Fast direct pixel access]].
  
=== Ein Beispiel: "Fading Image" ===
+
Bei einigen Betriebssystemen werden die Daten einer Bitmap nicht im Arbeitsspeicher abgelegt, deswegen kann man auf sie auch nicht direkt zugreifen. Da Lazarus plattformunabhängig sein soll, stellt die Klasse TBitmap auch keine Eigenschaft wie Scanline zur Verfügung. Es gibt eine Funktion 'GetDataLineStart', äquivalent zu Scanline, aber nur für Memory-Images wie TLazIntfImage, das intern TRawImage benutzt.
Nehmen wir an, Sie wollen ein verblassendes Bild erstellen. In Delphi geschriebener Code könnte in etwa folgendermaßen aussehen:
 
<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;  //  oder 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;
 
            Row[x].rgbtGreen := (step * BaseRow[x].rgbtGreen) shr 5; // Fading
 
            Row[x].rgbtBlue := (step * BaseRow[x].rgbtBlue) shr 5;
 
          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>
 
Diese Funktion könnte in Lazarus so implementiert werden:
 
<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>
 
  
Der Lazarus Code auf dieser Seite wurde dem $LazarusPath/examples/lazintfimage/fadein1.lpi Projekt entnommen. Wenn Sie einen einfachen Start in die Grafikprogrammierung suchen, dann werfen Sie einen näheren Blick auf dieses Beispiel.
+
Auf den Punkt gebracht, können Sie eine Bitmap nur indirekt verändern, indem Sie eine Memory-Bitmap modifizieren und diese dann in eine zeichenbare Bitmap konvertieren. Dieser Vorgang ist natürlich langsamer als der direkte Zugriff.
  
=== Transparent zeichnen mit Bitmaps ===
+
===Zeichnen von farbigen transparenten Bitmaps===
  
 
Ein neues Feature, implementiert in Lazarus 0.9.11, sind farbtransparente Bitmaps. Bitmap-Dateien (*.BMP) können keine Informationen über Transparenz speichern, aber Sie können dies zur Laufzeit umgehen, indem Sie eine Transparenz-Farbe wählen, mit der Sie die transparenten Bereiche markieren. Bei Win32-Anwendungen ist dies ein üblicher Trick.
 
Ein neues Feature, implementiert in Lazarus 0.9.11, sind farbtransparente Bitmaps. Bitmap-Dateien (*.BMP) können keine Informationen über Transparenz speichern, aber Sie können dies zur Laufzeit umgehen, indem Sie eine Transparenz-Farbe wählen, mit der Sie die transparenten Bereiche markieren. Bei Win32-Anwendungen ist dies ein üblicher Trick.
  
Das folgende Beispiel lädt eine Bitmap aus einer Windows Ressource, wählt eine Farbe für die Transparenz (clFuchsia) und zeichnet es auf die Zeichenfläche (Canvas).
+
Das folgende Beispiel lädt eine Bitmap aus einer Windows Ressource, wählt eine Farbe (clFuchsia) für die Transparenz aus und zeichnet dann auf die Zeichenfläche (Canvas).
  
<delphi>
+
<syntaxhighlight lang=pascal>
 
procedure MyForm.MyButtonOnClick(Sender: TObject);
 
procedure MyForm.MyButtonOnClick(Sender: TObject);
 
var
 
var
Line 140: Line 71:
  
 
   bmp.Free; // Gibt belegte Ressourcen frei
 
   bmp.Free; // Gibt belegte Ressourcen frei
end;
+
end;</syntaxhighlight>
</delphi>
 
  
Beachten Sie, dass mit [[doc:rtl/classes/tmemorystream.html|TMemoryStream]] die Speicheroperationen ausgeführt werden. Sie sind notwendig, um das korrekte Laden des Bildes sicherzustellen.
+
Beachten Sie, dass die Speicheroperationen mit [[doc:rtl/classes/tmemorystream.html|TMemoryStream]] ausgeführt werden. Sie sind notwendig, um das korrekte Laden des Bildes sicherzustellen.
  
=== Ein Bildschirmfoto erstellen ===
+
===Ein Bildschirmfoto erstellen===
  
 
Seit Lazarus 0.9.16 können Sie die LCL verwenden, um plattformunabhängig Bildschirmfotos aufzunehmen. Der folgende Beispielcode zeigt es (funktioniert unter GTK2 und Win32, aber gegenwärtig nicht unter GTK1):
 
Seit Lazarus 0.9.16 können Sie die LCL verwenden, um plattformunabhängig Bildschirmfotos aufzunehmen. Der folgende Beispielcode zeigt es (funktioniert unter GTK2 und Win32, aber gegenwärtig nicht unter GTK1):
  
<delphi>
+
<syntaxhighlight lang=pascal>
  uses LCLIntf, LCLType;
+
uses Graphics, LCLIntf, LCLType;
  
 
   ...
 
   ...
Line 163: Line 93:
 
   ReleaseDC(ScreenDC);
 
   ReleaseDC(ScreenDC);
  
   ...
+
   ...</syntaxhighlight>
</delphi>
+
 
 +
==Arbeiten mit TLazIntfImage==
 +
 
 +
===Beispiel: "Bild ausblenden" ===
 +
 
 +
Ein Beispiel für 'fading' mit TLazIntfImage
 +
 
 +
<syntaxhighlight lang=pascal>
 +
{ Dieser Code wurde dem $LazarusPath/examples/lazintfimage/fadein1.lpi Projekt entnommen. }
 +
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;</syntaxhighlight>
 +
 
 +
===Bildformat-spezifisches Beispiel===
 +
 
 +
Wie Sie wissen verwendet eine TBitmap für blau 8 Bit, für grün 8 Bit und für rot 8 Bit. Sie können direkt auf diese Bytes zugreifen, das ist um einiges schneller:
 +
 
 +
<syntaxhighlight lang=pascal>
 +
uses LCLType, // HBitmap type
 +
    IntfGraphics, // TLazIntfImage type
 +
    fpImage; // TFPColor type
 +
...
 +
type
 +
  TRGBTripleArray = array[0..32767] of TRGBTriple;
 +
  PRGBTripleArray = ^TRGBTripleArray;
 +
 
 +
procedure TForm1.FadeIn2(aBitMap: TBitMap);
 +
var
 +
  IntfImg1, IntfImg2: TLazIntfImage;
 +
  ImgHandle,ImgMaskHandle: HBitmap;
 +
  FadeStep: Integer;
 +
  px, py: Integer;
 +
  CurColor: TFPColor;
 +
  TempBitmap: TBitmap;
 +
  Row1, Row2: PRGBTripleArray;
 +
begin
 +
  IntfImg1:=TLazIntfImage.Create(0,0);
 +
  IntfImg1.LoadFromBitmap(aBitmap.Handle,aBitmap.MaskHandle);
 +
 
 +
  IntfImg2:=TLazIntfImage.Create(0,0);
 +
  IntfImg2.LoadFromBitmap(aBitmap.Handle,aBitmap.MaskHandle);
 +
 
 +
  TempBitmap:=TBitmap.Create;
 +
 
 +
  //with Scanline-like
 +
  for FadeStep:=1 to 32 do begin
 +
    for py:=0 to IntfImg1.Height-1 do begin
 +
      Row1 := IntfImg1.GetDataLineStart(py); //like Delphi TBitMap.ScanLine
 +
      Row2 := IntfImg2.GetDataLineStart(py); //like Delphi TBitMap.ScanLine
 +
      for px:=0 to IntfImg1.Width-1 do begin
 +
        Row2^[px].rgbtRed:= (FadeStep * Row1^[px].rgbtRed) shr 5;
 +
        Row2^[px].rgbtGreen := (FadeStep * Row1^[px].rgbtGreen) shr 5; // Fading
 +
        Row2^[px].rgbtBlue := (FadeStep * Row1^[px].rgbtBlue) shr 5;
 +
      end;
 +
    end;
 +
    IntfImg2.CreateBitmaps(ImgHandle,ImgMaskHandle,false);
 +
   
 +
    TempBitmap.Handle:=ImgHandle;
 +
    TempBitmap.MaskHandle:=ImgMaskHandle;
 +
    Canvas.Draw(0,0,TempBitmap);
 +
  end;
 +
 
 +
  IntfImg1.Free;
 +
  IntfImg2.Free;
 +
  TempBitmap.Free;
 +
end;</syntaxhighlight>
 +
 
 +
===Konvertierung zwischen TLazIntfImage und TBitmap===
 +
 
 +
Da Lazarus die Eigenschaft 'TBitmap.ScanLine' nicht kennt, ist die beste Art, auf die Pixels eines Bildes auf schnelle Weise lesend und schreibend zuzugreifen, indem Sie TLazIntfImage verwenden. Die TBitmap wird konvertiert in ein TLazIntfImage mittels TBitmap.CreateIntfImage(). Nach dem Modifizieren der Pixel kann es in eine TBitmap zurück konvertiert werden mittels TBitmap.LoadFromIntfImage().
 +
Hier ist das Beispiel dazu - es erzeugt TLazIntfImage aus einer TBitmap, modifiziert sie und wandelt sie zurück in eine TBitmap.
 +
 
 +
<syntaxhighlight lang=pascal>
 +
uses
 +
  ...GraphType, IntfGraphics, LCLType, LCLProc,  LCLIntf ...
 +
 
 +
procedure TForm1.Button4Click(Sender: TObject);
 +
var
 +
  b: TBitmap;
 +
  t: TLazIntfImage;
 +
begin
 +
  b := TBitmap.Create;
 +
  try
 +
    b.LoadFromFile('test.bmp');
 +
    t := b.CreateIntfImage;
 +
 
 +
    // Lies und/oder schreib in die Pixels
 +
    t.Colors[10,20] := colGreen;
 +
 
 +
    b.LoadFromIntfImage(t);
 +
  finally
 +
    t.Free;
 +
    b.Free;
 +
  end;
 +
end;</syntaxhighlight>
  
 
== Motion Graphics - Wie man Flimmern vermeidet ==
 
== Motion Graphics - Wie man Flimmern vermeidet ==
  
Viele Programme zeichnen ihre Ausgabe als 2D Grafik in die GUI. Wenn diese Grafiken schnell geändert werden müssen, werden Sie bald einem Problem begegnen: sich schnell ändernde Grafiken flimmern oft auf dem Bildschirm. Das passiert, weil der Zeichenprozess Zeit benötigt und der Benutzer deshalb manchmal ein vollständiges Bild, manchmal aber auch nur ein teilweise gezeichnetes Bild sieht.  
+
Viele Programme zeichnen ihre Ausgabe als 2D Grafik in die Benutzeroberfläche. Wenn diese Grafiken schnell geändert werden müssen, werden Sie bald einem Problem begegnen: sich schnell ändernde Grafiken flimmern oft auf dem Bildschirm. Das passiert, weil der Zeichenprozess Zeit benötigt und der Benutzer deshalb manchmal ein vollständiges Bild, manchmal aber auch nur ein teilweise gezeichnetes Bild sieht.  
  
Aber wie kann ich das Flimmern vermeiden und das beste Zeichentempo erreichen? Natürlich können Sie mit Hardwarebeschleunigung unter Verwendung von OpenGL arbeiten, aber dieser Ansatz ist für kleine Programme oder alte Computer ziemlich ungeeignet. Dieses Tutorial konzentriert sich auf das Zeichnen in ein TCanvas. Wenn Sie Hilfe brauchen zu OpenGL, werfen Sie einen Blick auf das Beispiel, das mit Lazarus geliefert wird oder auf [[GLScene]]. Sie können auch A.J. Venter's Gamepack verwenden, das eine doppelt gebufferte Zeichenfläche (double-buffered canvas) und eine Sprite-Komponente bereitstellt.
+
Aber wie kann ich das Flimmern vermeiden und das beste Zeichentempo erreichen? Natürlich können Sie mit Hardwarebeschleunigung unter Verwendung von OpenGL arbeiten, aber dieser Ansatz ist für kleine Programme oder alte Computer ziemlich ungeeignet. Dieses Tutorial konzentriert sich auf das Zeichnen in ein TCanvas. Wenn Sie Hilfe brauchen zu OpenGL, werfen Sie einen Blick auf das Beispiel, das mit Lazarus geliefert wird oder auf [[GLScene]]. Sie können auch A.J. Venter's Gamepack verwenden, das eine doppelt-gepufferte Zeichenfläche (double-buffered canvas) und eine Sprite-Komponente bereitstellt.
  
 
Jetzt wollen wir die Optionen untersuchen, die wir für das Zeichnen auf eine Zeichenfläche (Canvas) haben:
 
Jetzt wollen wir die Optionen untersuchen, die wir für das Zeichnen auf eine Zeichenfläche (Canvas) haben:
 
* [[#Zeichnen in ein TImage|Zeichnen in ein TImage]]
 
* [[#Zeichnen in ein TImage|Zeichnen in ein TImage]]
* [[#Zeichnen im OnPaint-Ereignis des Formulars, der TPaintBox oder einem anderen Control|Zeichnen im OnPaint-Ereignis des Formulars, der TPaintBox oder einem anderen Control]]
+
* [[#Zeichnen im OnPaint-Ereignis des Formulars, der TPaintBox oder eines anderen Steuerelements|Zeichnen im OnPaint-Ereignis des Formulars, der TPaintBox oder eines anderen Steuerelements]]
 
* [[#Ein CustomControl erzeugen, das sich selbst zeichnet|Ein CustomControl erzeugen, das sich selbst zeichnet]]
 
* [[#Ein CustomControl erzeugen, das sich selbst zeichnet|Ein CustomControl erzeugen, das sich selbst zeichnet]]
 
* [[#A.J. Venter's Gamepack benutzen|A.J. Venter's Gamepack benutzen]]
 
* [[#A.J. Venter's Gamepack benutzen|A.J. Venter's Gamepack benutzen]]
Line 184: Line 242:
 
Auf die Zeichenfläche mit dem sichtbaren Bereich eines TImage-Objekts kann man nur über Image1.Canvas zugreifen, während Image1.OnPaint aktiv ist.
 
Auf die Zeichenfläche mit dem sichtbaren Bereich eines TImage-Objekts kann man nur über Image1.Canvas zugreifen, während Image1.OnPaint aktiv ist.
  
'''Wichtig''': Verwenden Sie nie das OnPaint-Event von Image1, um auf die Graphik, das Bild von TImage zu zeichnen. Die Graphik von TImage wird zwischengespeichert, wenn Sie also darauf zeichnen, bleiben die Änderungen für immer dort. Außerdem wird, wenn Sie ständig neuzeichnen lassen, das Bild flickern. In diesem Fall können Sie einen anderen Ansatz versuchen. Zeichnen auf ein TImage ist langsamer als die anderen Verfahren.
+
'''Wichtig''': Verwenden Sie nie das OnPaint-Event von Image1, um auf die Graphik/das Bild von TImage zu zeichnen. Die Graphik von TImage wird zwischengespeichert, wenn Sie also darauf zeichnen, bleiben die Änderungen für immer dort. Außerdem wird, wenn Sie ständig neuzeichnen lassen, das Bild flickern. In diesem Fall können Sie einen anderen Ansatz versuchen. Zeichnen auf ein TImage ist langsamer als die anderen Verfahren.
  
==== Ändern der Größe einer Bitmap in TImage ====
+
====Ändern der Größe einer Bitmap in TImage====
  
 
Anmerkung: Tun Sie das nicht während OnPaint.
 
Anmerkung: Tun Sie das nicht während OnPaint.
  
<delphi>
+
<syntaxhighlight lang=pascal>
  with Image1.Picture.Bitmap do begin
+
with Image1.Picture.Bitmap do begin
    Width:=100;
+
  Width:=100;
    Height:=120;
+
  Height:=120;
   end;
+
end;</syntaxhighlight>
</delphi>
+
 
 +
Das selbe in einem Schritt:
 +
 
 +
<syntaxhighlight lang=pascal>
 +
with Image1.Picture.Bitmap do begin
 +
   SetSize(100, 120);
 +
end;</syntaxhighlight>
  
==== Zeichnen auf eine Bitmap in TImage ====
+
====Zeichnen auf eine Bitmap in TImage====
  
 
Anmerkung: Tun Sie das nicht während OnPaint.
 
Anmerkung: Tun Sie das nicht während OnPaint.
  
<delphi>
+
<syntaxhighlight lang=pascal>
  with Image1.Picture.Bitmap.Canvas do begin
+
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;</syntaxhighlight>
</delphi>
 
  
Anmerkung: Bei Image1.OnPaint zeigt Image1.Canvas auf den flüchtig sichtbaren Bereich. Außerhalb von Image1.OnPaint zeigt Image1.Canvas auf Image1.Picture.Bitmap.Canvas.
+
Anmerkung: Innerhalb von Image1.OnPaint zeigt Image1.Canvas auf den flüchtigen sichtbaren Bereich. Außerhalb von Image1.OnPaint zeigt Image1.Canvas auf Image1.Picture.Bitmap.Canvas.
  
 
Ein anderes Beispiel:
 
Ein anderes Beispiel:
  
<delphi>
+
<syntaxhighlight lang=pascal>
procedure TForm1.BitBtn1Click(Sender: TObject);
+
procedure TForm1.BitBtn1Click(Sender: TObject);
var
+
var
  x, y: Integer;
+
  x, y: Integer;
begin
+
begin
  // Zeichnet den Hintergrund
+
  // Zeichnet den Hintergrund
  MyImage.Canvas.Pen.Color := clWhite;
+
  MyImage.Canvas.Pen.Color := clWhite;
  MyImage.Canvas.Rectangle(0, 0, Image.Width, Image.Height);
+
  MyImage.Canvas.Rectangle(0, 0, Image.Width, Image.Height);
 
+
 
  // Zeichnet Rechtecke
+
  // Zeichnet Rechtecke
  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>
</delphi>
 
  
==== Im flüchtig sichtbaren Bereich einer TImage zeichnen ====
+
==== Im flüchtigen sichtbaren Bereich einer TImage zeichnen ====
  
Sie können nur während des OnPaint-Ereignisses in diesen Bereich zeichnen. OnPaint wird eventuell automatisch von der LCL aufgerufen, wenn der Bereich invalidiert wurde. Sie können den Bereich mit Image1.Invalidate manuell invalidieren. Dies ruft OnPaint nicht unmittelbar auf und Sie können Invalidate so oft Sie wollen aufrufen.
+
Sie können nur während des OnPaint-Ereignisses in diesen Bereich zeichnen. OnPaint wird automatisch von der LCL aufgerufen, wenn der Bereich invalidiert wurde. Sie können den Bereich mit Image1.Invalidate manuell invalidieren. Dies ruft OnPaint nicht unmittelbar auf und Sie können Invalidate so oft Sie wollen aufrufen.
  
<delphi>
+
<syntaxhighlight lang=pascal>
  procedure TForm.Image1Paint(Sender: TObject);
+
procedure TForm.Image1Paint(Sender: TObject);
  begin
+
begin
    with Image1.Canvas do begin
+
  // paint a line
      // paint a line
+
  Canvas.Pen.Color:=clRed;
      Pen.Color:=clRed;
+
  Canvas.Line(0,0,Width,Height);
      Line(0,0,Width,Height);
+
end;</syntaxhighlight>
    end;
+
 
  end;
+
=== Zeichnen im OnPaint-Ereignis des Formulars, der TPaintBox oder eines anderen Steuerelements ===
</delphi>
 
  
=== Zeichnen im OnPaint-Ereignis des Formulars, der TPaintBox oder einem anderen Control ===
+
In diesem Fall müssen alle Zeichenoperationen im OnPaint-Ereignis des Formulars passieren. Die Elemente bleiben nicht im Puffer erhalten, wie bei TImage, und das Bildes muss mit jedem Aufruf des OnPaint-Ereignishandlers vollständig neu gezeichnet werden.
In diesem Fall muss alles Zeichnen im OnPaint-Ereignis des Formulars passieren. Die Elemente bleiben nicht im buffer erhalten, wie bei TImage.
 
  
=== Ein CustomControl erzeugen, das sich selbst zeichnet ===
+
===Ein CustomControl erzeugen, das sich selbst zeichnet===
 
Die Erzeugung eines CustomControl hat den Vorteil der Strukturierung ihres Codes und sie können das Bedienelement wieder verwenden. Dieser Ansatz ist sehr schnell, aber er kann dennoch Flimmern erzeugen, wenn Sie nicht erst in ein TBitMap und dann in das Canvas zeichnen. In diesem Fall besteht keine Notwendigkeit, das OnPaint Ereignis des Bedienelements zu verwenden.
 
Die Erzeugung eines CustomControl hat den Vorteil der Strukturierung ihres Codes und sie können das Bedienelement wieder verwenden. Dieser Ansatz ist sehr schnell, aber er kann dennoch Flimmern erzeugen, wenn Sie nicht erst in ein TBitMap und dann in das Canvas zeichnen. In diesem Fall besteht keine Notwendigkeit, das OnPaint Ereignis des Bedienelements zu verwenden.
  
Hier ist ein Beispiel CustomControl:
+
Hier ist ein Beispiel für ein CustomControl:
 +
 
 +
<syntaxhighlight lang=pascal>
 +
uses
 +
  Classes, SysUtils, Controls, Graphics, LCLType;
  
<delphi>
+
type
uses
+
   TMyDrawingControl = class(TCustomControl)
  Classes, SysUtils, Controls, Graphics, LCLType;
+
  public
    
+
    procedure EraseBackground(DC: HDC); override;
type
+
    procedure Paint; override;
  TMyDrawingControl = class(TCustomControl)
+
  end;
  public
 
    procedure Paint; override;
 
  end;
 
 
   
 
   
implementation
+
implementation
 
   
 
   
procedure TMyDrawingControl.Paint;
+
procedure TMyDrawingControl.EraseBackground(DC: HDC);
var
+
begin
  x, y: Integer;
+
  // Entfernen Sie den Kommentar um das geerbte Löschen des Hintergrundes zu aktivieren
  Bitmap: TBitmap;
+
  //inherited EraseBackground(DC);
begin
+
end;
  Bitmap := TBitmap.Create;
+
 
  try
+
procedure TMyDrawingControl.Paint;
    // Initialisiert die Bitmap Größe
+
var
    Bitmap.Height := Height;
+
  x, y: Integer;
    Bitmap.Width := Width;
+
  Bitmap: TBitmap;
 
+
begin
    // Zeichnet den Hintergrund
+
  Bitmap := TBitmap.Create;
    Bitmap.Canvas.Pen.Color := clWhite;
+
  try
    Bitmap.Canvas.Rectangle(0, 0, Width, Height);
+
    // Initialisiert die Größe der Bitmap  
 +
    Bitmap.Height := Height;
 +
    Bitmap.Width := Width;
 +
 
 +
    // Zeichnet den Hintergrund
 +
    Bitmap.Canvas.Pen.Color := clWhite;
 +
    Bitmap.Canvas.Rectangle(0, 0, Width, Height);
 
   
 
   
    // Zeichnet Rechtecke
+
    // Zeichnet Rechtecke
    Bitmap.Canvas.Pen.Color := clBlack;
+
    Bitmap.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
      Bitmap.Canvas.Rectangle(Round((x - 1) * Width / 8), Round((y - 1) * Height / 8),
+
        Bitmap.Canvas.Rectangle(Round((x - 1) * Width / 8), Round((y - 1) * Height / 8),
        Round(x * Width / 8), Round(y * Height / 8));
+
          Round(x * Width / 8), Round(y * Height / 8));
 
        
 
        
    Canvas.Draw(0, 0, Bitmap);
+
    Canvas.Draw(0, 0, Bitmap);
  finally
+
  finally
    Bitmap.Free;
+
    Bitmap.Free;
  end;
+
  end;
 
   
 
   
  inherited Paint;
+
  inherited Paint;
end;
+
end;</syntaxhighlight>
</delphi>
+
 
 
und wie wir es auf dem Formular erzeugen:
 
und wie wir es auf dem Formular erzeugen:
<delphi>
+
<syntaxhighlight lang=pascal>
procedure TMyForm.FormCreate(Sender: TObject);
+
procedure TMyForm.FormCreate(Sender: TObject);
begin
+
begin
  MyDrawingControl:= TMyDrawingControl.Create(Self);
+
  MyDrawingControl:= TMyDrawingControl.Create(Self);
  MyDrawingControl.Height := 400;
+
  MyDrawingControl.Height := 400;
  MyDrawingControl.Width := 500;
+
  MyDrawingControl.Width := 500;
  MyDrawingControl.Top := 0;
+
  MyDrawingControl.Top := 0;
  MyDrawingControl.Left := 0;
+
  MyDrawingControl.Left := 0;
  MyDrawingControl.Parent := Self;
+
  MyDrawingControl.Parent := Self;
  MyDrawingControl.DoubleBuffered := True;
+
  MyDrawingControl.DoubleBuffered := True;
end;
+
end;</syntaxhighlight>
</delphi>
+
 
nur nicht vergessen es zu löschen:
+
Es wird automatisch zerstört, weil wir Self als owner angeben.
<delphi>
 
procedure TMyForm.FormDestroy(Sender: TObject);
 
begin
 
  MyDrawingControl.Free;
 
end;
 
</delphi>
 
  
 
Das Setzen von Top und Left auf 0 ist nicht notwendig, da dies die Standardposition ist, aber es ist sinnvoll, um diese Position zu verdeutlichen.
 
Das Setzen von Top und Left auf 0 ist nicht notwendig, da dies die Standardposition ist, aber es ist sinnvoll, um diese Position zu verdeutlichen.
Line 325: Line 386:
 
=== A.J. Venter's Gamepack benutzen ===
 
=== A.J. Venter's Gamepack benutzen ===
  
Der Gamepack-Vorstoß bedeutet, alles in ein doppelt gebuffertes Canvas zu zeichnen, dessen sichtbarer Bereich erst dann aktualisiert wird, wenn Sie fertig sind. Dies benötigt etwas mehr Code, aber es schafft die Fähigkeit, große, sich dynamisch ändernde Grafiken mit mehreren Elementen zu verarbeiten. Wenn Sie diese Technik verwenden möchten, sollten Sie sich das A.J. Venter's Gamepack ansehen, ein Set von Komponenten zur Entwicklung von Spielen mit Lazarus, das eine doppelt gebufferte Anzeigeflächen-Komponente sowie eine Sprite-Komponente bietet, designed um sich gegenseitig ideal zu integrieren. Sie können Gamepack mittels subversion erhalten:<br />
+
Der Gamepack-Ansatz bedeutet, alles in ein doppelt gebuffertes Canvas zu zeichnen, dessen sichtbarer Bereich erst dann aktualisiert wird, wenn Sie fertig sind. Dies benötigt etwas mehr Code, aber es schafft die Fähigkeit, große, sich dynamisch ändernde Grafiken mit mehreren Elementen zu verarbeiten. Wenn Sie diese Technik verwenden möchten, sollten Sie sich das A.J. Venter's Gamepack ansehen, ein Set von Komponenten zur Entwicklung von Spielen mit Lazarus, das eine doppelt gebufferte Anzeigeflächen-Komponente sowie eine Sprite-Komponente bietet, entwickelt um sich gegenseitig ideal zu ergänzen.
<code>
+
 
svn co svn://silentcoder.co.za/lazarus/gamepack
+
[http://www.4shared.com/file/tZj_Uh8Z/gamepack-10tar.html Download: A.J. Venter's GamePack 1.0]
</code>
+
 
 +
Veraltete Links:
 +
*<code>svn co svn://silentcoder.co.za/lazarus/gamepack</code>
 +
*[http://outkastsolutions.co.za/outkast/index.php?option=com_openwiki&Itemid=&id=gamepack A.J. Venter's gamepack homepage]
 +
 
 +
== Bild Formate ==
 +
 
 +
Hier ist eine Tabelle mit den entsprechenden Klassen für jedes Bildformat.
 +
 
 +
{| border=2 width="100%" align="center"
 +
|-
 +
! Format
 +
! Bild-Klasse
 +
! Unit
 +
|-
 +
|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
 +
|}
 +
 
 +
Siehe auch die Liste der  [[fcl-image#Image_formats|von der FCL (fcl-image) unterstützten Formate]].
 +
 
 +
=== Konvertieren von Formaten ===
 +
 
 +
Manchmal wird es erforderlich, einen Grafiktyp in einen anderen zu konvertieren.
 +
Ein Weg dazu ist es, eine Grafik in ein Zwischenformat zu wandeln, und dieses in eine TBitmap zu konvertieren.
 +
Die meisten Formate können ein Bild aus einer TBitmap erzeugen.
 +
 
 +
Konvertieren von Bitmap zu PNG und Abspeichern in einer Datei:
 +
 
 +
<syntaxhighlight lang=pascal>
 +
procedure SaveToPng(const bmp: TBitmap; PngFileName: String);
 +
var
 +
  png : TPortableNetworkGraphic;
 +
begin
 +
  png := TPortableNetworkGraphic.Create;
 +
  try
 +
    png.Assign(bmp);
 +
    png.SaveToFile(PngFileName);
 +
  finally
 +
    png.Free;
 +
  end;
 +
end;</syntaxhighlight>
 +
 
 +
==Pixelformate==
 +
 
 +
===TColor===
 +
 
 +
Das interne Pixelformat für TColor in der LCL ist das XXBBGGRR Format, welches dem Windows-eigenen Format entspricht und im Gegensatz steht zu den meisten anderen Bibliotheken, die AARRGGBB verwenden. Der XX Teil gibt an, ob die Farbe eine festgelegte Farbe ist (XX = 00) oder ein Index zu einem Farbsystem. Für einen Alphakanal ist kein Platz reserviert.
 +
 
 +
Zum Konvertieren von getrennten RGB-Kanälen zum Typ TColor nehmen Sie:
 +
 
 +
<syntaxhighlight lang=pascal>RGBToColor(RedVal, GreenVal, BlueVal);</syntaxhighlight>
 +
 
 +
Um aus einer TColor-Variablen jeden Kanal einzeln zu erhalten, benutzen Sie die Funktionen Red, Green, Blue:
 +
 
 +
<syntaxhighlight lang=pascal>
 +
RedVal := Red(MyColor);
 +
GreenVal := Green(MyColor);
 +
BlueVal := Blue(MyColor);</syntaxhighlight>
 +
 
 +
===TFPColor===
 +
 
 +
Der Typ TFPColor verwendet das AARRGGBB Format, das bei den meisten Bibliotheken üblich ist.
 +
 
 +
==Arbeiten mit der Zeichenfläche TCanvas==
 +
 
 +
===Verwendung der Standardschriftart der GUI===
 +
 
 +
Das erreichen Sie mit diesem einfachen Code:
 +
 
 +
<syntaxhighlight lang=pascal>SelectObject(Canvas.Handle, GetStockObject(DEFAULT_GUI_FONT));</syntaxhighlight>
 +
 
 +
===Ausgabe eines in der Breite begrenzten Textes===
 +
 
 +
Nehmen Sie die Routine DrawText, zuerst mit DT_CALCRECT und dann ohne den Wert.
 +
 
 +
<syntaxhighlight lang=pascal>
 +
// Berechne zuerst die Textbreite, dann zeiche ihn
 +
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>
 +
 
 +
===Ausgabe eines Textes mit klaren Kanten (ohne Antialiasing)===
 +
 
 +
Einige Interfaces unterstützen dies mittels
 +
 
 +
<syntaxhighlight lang=pascal>Canvas.Font.Quality := fqNonAntialiased;</syntaxhighlight>
 +
 
 +
Einige Interfaces wie gtk2 unterstützen dies nicht und zeichnen immer mittels Antialiasing. Hier ist eine simple Prozedur um Text mit scharfen Kanten auch unter gtk2 darzustellen. Sie berücksichtigt nicht alle Fälle, aber zeigt die Grundidee:
 +
 
 +
<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>
 +
 
 +
==Zeichnen mit der Bibliothek fcl-image==
 +
 
 +
Sie können Bilder, die nicht am Bildschirm angezeigt werden sollen, auch ohne die LCL zeichnen, indem Sie die Bibliothek fcl-image einfach direkt ansprechen. Beispielsweise könnte ein Programm, das auf einem Webserver ohne X11 läuft, davon profitieren, nicht von einer visuellen Bibliothek abhängig zu sein. FPImage (alias fcl-image) ist eine sehr allgemeine Bibliothek für das Darstellen von Bildern und ist vollständig in Pascal geschrieben. Tatsächlich benutzt auch die LCL FPImage für alle Lade- und Speichervorgänge von Dateien und implementiert die Zeichenfunktionen durch Aufrufe an das jeweilige Interface (winapi, gtk, carbon, ...). Andererseits hat Fcl-image aber auch eigene Zeichenroutinen.
 +
 
 +
Für weitere Informationen lesen Sie bitte den Artikel über [[fcl-image]].

Latest revision as of 06:52, 13 February 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)

Diese Seite beschreibt die grundlegenden Klassen und Techniken im Hinblick auf das Zeichnen von Grafiken mit Lazarus. Andere, speziellere Themen sind in gesonderten Artikeln zu finden.

Andere Grafikartikel

2D Grafik

  • ZenGL - cross-platform game development library using OpenGL.
  • BGRABitmap - Zeichnen von Formen und Bitmaps mit Transparenzinformationen, direkte Zugriff auf Pixels, etc.
  • LazRGBGraphics - A package for fast in memory image processing and pixel manipulations (like scan line).
  • fpvectorial - Offers support to read, modify and write vectorial images.
  • Double Gradient - Zeichnet 'doppelte Farbverläufe' und 'n-fache Farbverläufe' ganz einfach in Bitmaps.
  • Gradient Filler - TGradientFiller is the best way to create custom n gradients in Lazarus.
  • PascalMagick - ein einfach zu verwendendes API für die Kopplung mit ImageMagick, einer freien Multiplattform-Softwaresammlung um Bitmaps zu erzeugen, zu bearbeiten und zu entwerfen.
  • Sample Graphics - graphics gallery created with Lazarus and drawing tools
  • Fast direct pixel access - speed comparison of some methods for direct bitmap pixel access

3D Grafik

  • GLScene - Eine Portierung der visuellen OpenGL-Graphikbibliothek GLScene

Diagramme

  • TAChart - Diagrammkomponente für Lazarus
  • PlotPanel - Eine Komponente zum Darstellen( Plotting / Charting) von animierten Graphen. Ähnlich wie TAChart.
  • Perlin Noise - Ein Artikel über die Verwendung von Perlin Noise in LCL Anwendungen.

Arbeiten mit TBitmap

Das Objekt 'TBitmap' speichert eine Bitmap, auf die Sie zeichnen können, bevor Sie sie am Bildschirm anzeigen. Wenn Sie eine Bitmap erzeugen, müssen Sie deren Höhe und Breite immer angeben, ansonsten sind sie 0 und es wird nichts gezeichnet.

Direkter Zugriff auf Pixel

Für einen direkten Zugriff auf Bitmaps können Sie externe Bibliotheken einsetzen, wie z. B. BGRABitmap, LazRGBGraphics und Graphics32. Einen Vergleich verschiedener Methoden des Zugriffs auf ein Pixel finden Sie unter Fast direct pixel access.

Bei einigen Betriebssystemen werden die Daten einer Bitmap nicht im Arbeitsspeicher abgelegt, deswegen kann man auf sie auch nicht direkt zugreifen. Da Lazarus plattformunabhängig sein soll, stellt die Klasse TBitmap auch keine Eigenschaft wie Scanline zur Verfügung. Es gibt eine Funktion 'GetDataLineStart', äquivalent zu Scanline, aber nur für Memory-Images wie TLazIntfImage, das intern TRawImage benutzt.

Auf den Punkt gebracht, können Sie eine Bitmap nur indirekt verändern, indem Sie eine Memory-Bitmap modifizieren und diese dann in eine zeichenbare Bitmap konvertieren. Dieser Vorgang ist natürlich langsamer als der direkte Zugriff.

Zeichnen von farbigen transparenten Bitmaps

Ein neues Feature, implementiert in Lazarus 0.9.11, sind farbtransparente Bitmaps. Bitmap-Dateien (*.BMP) können keine Informationen über Transparenz speichern, aber Sie können dies zur Laufzeit umgehen, indem Sie eine Transparenz-Farbe wählen, mit der Sie die transparenten Bereiche markieren. Bei Win32-Anwendungen ist dies ein üblicher Trick.

Das folgende Beispiel lädt eine Bitmap aus einer Windows Ressource, wählt eine Farbe (clFuchsia) für die Transparenz aus und zeichnet dann auf die Zeichenfläche (Canvas).

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; // Fehler beim Laden der Bitmap

  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; // Gibt belegte Ressourcen frei
end;

Beachten Sie, dass die Speicheroperationen mit TMemoryStream ausgeführt werden. Sie sind notwendig, um das korrekte Laden des Bildes sicherzustellen.

Ein Bildschirmfoto erstellen

Seit Lazarus 0.9.16 können Sie die LCL verwenden, um plattformunabhängig Bildschirmfotos aufzunehmen. Der folgende Beispielcode zeigt es (funktioniert unter GTK2 und Win32, aber gegenwärtig nicht unter GTK1):

uses Graphics, LCLIntf, LCLType;

  ...

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

  ...

Arbeiten mit TLazIntfImage

Beispiel: "Bild ausblenden"

Ein Beispiel für 'fading' mit TLazIntfImage

{ Dieser Code wurde dem $LazarusPath/examples/lazintfimage/fadein1.lpi Projekt entnommen. }
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;

Bildformat-spezifisches Beispiel

Wie Sie wissen verwendet eine TBitmap für blau 8 Bit, für grün 8 Bit und für rot 8 Bit. Sie können direkt auf diese Bytes zugreifen, das ist um einiges schneller:

uses LCLType, // HBitmap type
     IntfGraphics, // TLazIntfImage type
     fpImage; // TFPColor type
...
type
  TRGBTripleArray = array[0..32767] of TRGBTriple;
  PRGBTripleArray = ^TRGBTripleArray;

procedure TForm1.FadeIn2(aBitMap: TBitMap);
 var
   IntfImg1, IntfImg2: TLazIntfImage;
   ImgHandle,ImgMaskHandle: HBitmap;
   FadeStep: Integer;
   px, py: Integer;
   CurColor: TFPColor;
   TempBitmap: TBitmap;
   Row1, Row2: PRGBTripleArray;
 begin
   IntfImg1:=TLazIntfImage.Create(0,0);
   IntfImg1.LoadFromBitmap(aBitmap.Handle,aBitmap.MaskHandle);

   IntfImg2:=TLazIntfImage.Create(0,0);
   IntfImg2.LoadFromBitmap(aBitmap.Handle,aBitmap.MaskHandle);

   TempBitmap:=TBitmap.Create;
   
   //with Scanline-like
   for FadeStep:=1 to 32 do begin
     for py:=0 to IntfImg1.Height-1 do begin
       Row1 := IntfImg1.GetDataLineStart(py); //like Delphi TBitMap.ScanLine
       Row2 := IntfImg2.GetDataLineStart(py); //like Delphi TBitMap.ScanLine
       for px:=0 to IntfImg1.Width-1 do begin
         Row2^[px].rgbtRed:= (FadeStep * Row1^[px].rgbtRed) shr 5;
         Row2^[px].rgbtGreen := (FadeStep * Row1^[px].rgbtGreen) shr 5; // Fading
         Row2^[px].rgbtBlue := (FadeStep * Row1^[px].rgbtBlue) shr 5;
       end;
     end;
     IntfImg2.CreateBitmaps(ImgHandle,ImgMaskHandle,false);
     
     TempBitmap.Handle:=ImgHandle;
     TempBitmap.MaskHandle:=ImgMaskHandle;
     Canvas.Draw(0,0,TempBitmap);
   end; 

   IntfImg1.Free;
   IntfImg2.Free;
   TempBitmap.Free;
 end;

Konvertierung zwischen TLazIntfImage und TBitmap

Da Lazarus die Eigenschaft 'TBitmap.ScanLine' nicht kennt, ist die beste Art, auf die Pixels eines Bildes auf schnelle Weise lesend und schreibend zuzugreifen, indem Sie TLazIntfImage verwenden. Die TBitmap wird konvertiert in ein TLazIntfImage mittels TBitmap.CreateIntfImage(). Nach dem Modifizieren der Pixel kann es in eine TBitmap zurück konvertiert werden mittels TBitmap.LoadFromIntfImage(). Hier ist das Beispiel dazu - es erzeugt TLazIntfImage aus einer TBitmap, modifiziert sie und wandelt sie zurück in eine TBitmap.

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

procedure TForm1.Button4Click(Sender: TObject);
var
  b: TBitmap;
  t: TLazIntfImage;
begin
  b := TBitmap.Create;
  try
    b.LoadFromFile('test.bmp');
    t := b.CreateIntfImage;

    // Lies und/oder schreib in die Pixels
    t.Colors[10,20] := colGreen;

    b.LoadFromIntfImage(t);
  finally
    t.Free;
    b.Free;
  end;
end;

Motion Graphics - Wie man Flimmern vermeidet

Viele Programme zeichnen ihre Ausgabe als 2D Grafik in die Benutzeroberfläche. Wenn diese Grafiken schnell geändert werden müssen, werden Sie bald einem Problem begegnen: sich schnell ändernde Grafiken flimmern oft auf dem Bildschirm. Das passiert, weil der Zeichenprozess Zeit benötigt und der Benutzer deshalb manchmal ein vollständiges Bild, manchmal aber auch nur ein teilweise gezeichnetes Bild sieht.

Aber wie kann ich das Flimmern vermeiden und das beste Zeichentempo erreichen? Natürlich können Sie mit Hardwarebeschleunigung unter Verwendung von OpenGL arbeiten, aber dieser Ansatz ist für kleine Programme oder alte Computer ziemlich ungeeignet. Dieses Tutorial konzentriert sich auf das Zeichnen in ein TCanvas. Wenn Sie Hilfe brauchen zu OpenGL, werfen Sie einen Blick auf das Beispiel, das mit Lazarus geliefert wird oder auf GLScene. Sie können auch A.J. Venter's Gamepack verwenden, das eine doppelt-gepufferte Zeichenfläche (double-buffered canvas) und eine Sprite-Komponente bereitstellt.

Jetzt wollen wir die Optionen untersuchen, die wir für das Zeichnen auf eine Zeichenfläche (Canvas) haben:

Zeichnen in ein TImage

Ein TImage-Objekt besteht aus zwei Teilen: Ein TGraphic-Objekt, üblicherweise ein TBitmap, das das persistente Bild und die sichtbare Fläche enthält, die bei jedem OnPaint neu gezeichnet wird. Eine Änderung der Größe von TImage ändert nicht die Größe der Bitmap. Auf die Grafik (oder die Bitmap) kann man über Image1.Picture.Graphic (oder Image1.Picture.Bitmap) zugreifen. Die Zeichenfläche (Canvas) ist Image1.Picture.Bitmap.Canvas. Auf die Zeichenfläche mit dem sichtbaren Bereich eines TImage-Objekts kann man nur über Image1.Canvas zugreifen, während Image1.OnPaint aktiv ist.

Wichtig: Verwenden Sie nie das OnPaint-Event von Image1, um auf die Graphik/das Bild von TImage zu zeichnen. Die Graphik von TImage wird zwischengespeichert, wenn Sie also darauf zeichnen, bleiben die Änderungen für immer dort. Außerdem wird, wenn Sie ständig neuzeichnen lassen, das Bild flickern. In diesem Fall können Sie einen anderen Ansatz versuchen. Zeichnen auf ein TImage ist langsamer als die anderen Verfahren.

Ändern der Größe einer Bitmap in TImage

Anmerkung: Tun Sie das nicht während OnPaint.

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

Das selbe in einem Schritt:

with Image1.Picture.Bitmap do begin
  SetSize(100, 120);
end;

Zeichnen auf eine Bitmap in TImage

Anmerkung: Tun Sie das nicht während OnPaint.

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

Anmerkung: Innerhalb von Image1.OnPaint zeigt Image1.Canvas auf den flüchtigen sichtbaren Bereich. Außerhalb von Image1.OnPaint zeigt Image1.Canvas auf Image1.Picture.Bitmap.Canvas.

Ein anderes Beispiel:

procedure TForm1.BitBtn1Click(Sender: TObject);
var
  x, y: Integer;
begin
  // Zeichnet den Hintergrund
  MyImage.Canvas.Pen.Color := clWhite;
  MyImage.Canvas.Rectangle(0, 0, Image.Width, Image.Height);
  
  // Zeichnet Rechtecke
  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;

Im flüchtigen sichtbaren Bereich einer TImage zeichnen

Sie können nur während des OnPaint-Ereignisses in diesen Bereich zeichnen. OnPaint wird automatisch von der LCL aufgerufen, wenn der Bereich invalidiert wurde. Sie können den Bereich mit Image1.Invalidate manuell invalidieren. Dies ruft OnPaint nicht unmittelbar auf und Sie können Invalidate so oft Sie wollen aufrufen.

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

Zeichnen im OnPaint-Ereignis des Formulars, der TPaintBox oder eines anderen Steuerelements

In diesem Fall müssen alle Zeichenoperationen im OnPaint-Ereignis des Formulars passieren. Die Elemente bleiben nicht im Puffer erhalten, wie bei TImage, und das Bildes muss mit jedem Aufruf des OnPaint-Ereignishandlers vollständig neu gezeichnet werden.

Ein CustomControl erzeugen, das sich selbst zeichnet

Die Erzeugung eines CustomControl hat den Vorteil der Strukturierung ihres Codes und sie können das Bedienelement wieder verwenden. Dieser Ansatz ist sehr schnell, aber er kann dennoch Flimmern erzeugen, wenn Sie nicht erst in ein TBitMap und dann in das Canvas zeichnen. In diesem Fall besteht keine Notwendigkeit, das OnPaint Ereignis des Bedienelements zu verwenden.

Hier ist ein Beispiel für ein CustomControl:

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
  // Entfernen Sie den Kommentar um das geerbte Löschen des Hintergrundes zu aktivieren
  //inherited EraseBackground(DC);
end; 

procedure TMyDrawingControl.Paint;
var
  x, y: Integer;
  Bitmap: TBitmap;
begin
  Bitmap := TBitmap.Create;
  try
    // Initialisiert die Größe der Bitmap 
    Bitmap.Height := Height;
    Bitmap.Width := Width;

    // Zeichnet den Hintergrund
    Bitmap.Canvas.Pen.Color := clWhite;
    Bitmap.Canvas.Rectangle(0, 0, Width, Height);
 
    // Zeichnet Rechtecke
    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;

und wie wir es auf dem Formular erzeugen:

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;

Es wird automatisch zerstört, weil wir Self als owner angeben.

Das Setzen von Top und Left auf 0 ist nicht notwendig, da dies die Standardposition ist, aber es ist sinnvoll, um diese Position zu verdeutlichen.

"MyDrawingControl.Parent := Self;" ist sehr wichtig und Sie werden Ihr Bedienelement nicht sehen, wenn Sie es nicht tun.

"MyDrawingControl.DoubleBuffered := True;" wird benötigt, um das Flimmern unter Windows zu vermeiden. Es hat keinen Effekt unter gtk.

A.J. Venter's Gamepack benutzen

Der Gamepack-Ansatz bedeutet, alles in ein doppelt gebuffertes Canvas zu zeichnen, dessen sichtbarer Bereich erst dann aktualisiert wird, wenn Sie fertig sind. Dies benötigt etwas mehr Code, aber es schafft die Fähigkeit, große, sich dynamisch ändernde Grafiken mit mehreren Elementen zu verarbeiten. Wenn Sie diese Technik verwenden möchten, sollten Sie sich das A.J. Venter's Gamepack ansehen, ein Set von Komponenten zur Entwicklung von Spielen mit Lazarus, das eine doppelt gebufferte Anzeigeflächen-Komponente sowie eine Sprite-Komponente bietet, entwickelt um sich gegenseitig ideal zu ergänzen.

Download: A.J. Venter's GamePack 1.0

Veraltete Links:

Bild Formate

Hier ist eine Tabelle mit den entsprechenden Klassen für jedes Bildformat.

Format Bild-Klasse Unit
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

Siehe auch die Liste der von der FCL (fcl-image) unterstützten Formate.

Konvertieren von Formaten

Manchmal wird es erforderlich, einen Grafiktyp in einen anderen zu konvertieren. Ein Weg dazu ist es, eine Grafik in ein Zwischenformat zu wandeln, und dieses in eine TBitmap zu konvertieren. Die meisten Formate können ein Bild aus einer TBitmap erzeugen.

Konvertieren von Bitmap zu PNG und Abspeichern in einer Datei:

procedure SaveToPng(const bmp: TBitmap; PngFileName: String);
var
  png : TPortableNetworkGraphic; 
begin 
  png := TPortableNetworkGraphic.Create;
  try
    png.Assign(bmp);
    png.SaveToFile(PngFileName);
  finally 
    png.Free;
  end;
end;

Pixelformate

TColor

Das interne Pixelformat für TColor in der LCL ist das XXBBGGRR Format, welches dem Windows-eigenen Format entspricht und im Gegensatz steht zu den meisten anderen Bibliotheken, die AARRGGBB verwenden. Der XX Teil gibt an, ob die Farbe eine festgelegte Farbe ist (XX = 00) oder ein Index zu einem Farbsystem. Für einen Alphakanal ist kein Platz reserviert.

Zum Konvertieren von getrennten RGB-Kanälen zum Typ TColor nehmen Sie:

RGBToColor(RedVal, GreenVal, BlueVal);

Um aus einer TColor-Variablen jeden Kanal einzeln zu erhalten, benutzen Sie die Funktionen Red, Green, Blue:

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

TFPColor

Der Typ TFPColor verwendet das AARRGGBB Format, das bei den meisten Bibliotheken üblich ist.

Arbeiten mit der Zeichenfläche TCanvas

Verwendung der Standardschriftart der GUI

Das erreichen Sie mit diesem einfachen Code:

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

Ausgabe eines in der Breite begrenzten Textes

Nehmen Sie die Routine DrawText, zuerst mit DT_CALCRECT und dann ohne den Wert.

// Berechne zuerst die Textbreite, dann zeiche ihn
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);

Ausgabe eines Textes mit klaren Kanten (ohne Antialiasing)

Einige Interfaces unterstützen dies mittels

Canvas.Font.Quality := fqNonAntialiased;

Einige Interfaces wie gtk2 unterstützen dies nicht und zeichnen immer mittels Antialiasing. Hier ist eine simple Prozedur um Text mit scharfen Kanten auch unter gtk2 darzustellen. Sie berücksichtigt nicht alle Fälle, aber zeigt die Grundidee:

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;

Zeichnen mit der Bibliothek fcl-image

Sie können Bilder, die nicht am Bildschirm angezeigt werden sollen, auch ohne die LCL zeichnen, indem Sie die Bibliothek fcl-image einfach direkt ansprechen. Beispielsweise könnte ein Programm, das auf einem Webserver ohne X11 läuft, davon profitieren, nicht von einer visuellen Bibliothek abhängig zu sein. FPImage (alias fcl-image) ist eine sehr allgemeine Bibliothek für das Darstellen von Bildern und ist vollständig in Pascal geschrieben. Tatsächlich benutzt auch die LCL FPImage für alle Lade- und Speichervorgänge von Dateien und implementiert die Zeichenfunktionen durch Aufrufe an das jeweilige Interface (winapi, gtk, carbon, ...). Andererseits hat Fcl-image aber auch eigene Zeichenroutinen.

Für weitere Informationen lesen Sie bitte den Artikel über fcl-image.