BGRABitmap tutorial 8/de

From Lazarus wiki
Jump to: navigation, search

Deutsch (de) | English (en) | Français (fr) | Español (es) | Edit

Home | Tutorial 1 | Tutorial 2 | Tutorial 3 | Tutorial 4 | Tutorial 5 | Tutorial 6 | Tutorial 7 | Tutorial 8 | Tutorial 9 | Tutorial 10 | Tutorial 11 | Tutorial 12 | Tutorial 13 | Tutorial 14 | Tutorial 15 | Tutorial 16 | Edit

Dieses Tutorial zeigt, wie Sie Texturen verwenden können.

Erzeugen Sie ein neues Projekt

Erzeugen Sie ein neues Projekt und fügen Sie eine Referenz auf BGRABitmap hinzu, genau so wie im ersten Tutorial.

Verwenden des Pinsels als Textur

Die einfachste Textur ist ein schraffierter Pinsel.

Im Objektinspektor fügen Sie einen OnPaint-Handler hinzu und schreiben Sie:

procedure TForm1.FormPaint(Sender: TObject);
var
  image,tex: TBGRABitmap;
  c: TBGRAPixel;
  x,y,rx,ry: single;
 
begin
    image := TBGRABitmap.Create(ClientWidth,ClientHeight,ColorToBGRA(ColorToRGB(clBtnFace)));
    c := ColorToBGRA(ColorToRGB(clWindowText));
 
    //Ellipsenkoordinaten
    x := 150;
    y := 100;
    rx := 100;
    ry := 50;
 
    //lädt einen diagonal gekreuzten Pinsel ("diagcross") mit weißem Muster und orangem Hintergrund
    tex := image.CreateBrushTexture(bsDiagCross,BGRAWhite,BGRA(255,192,0)) as TBGRABitmap;
 
    image.FillEllipseAntialias(x,y,rx-0.5,ry-0.5,tex);
    image.EllipseAntialias(x,y,rx,ry,c,1); //draw outline
 
    tex.Free;
 
    image.Draw(Canvas,0,0,True);
    image.free;  
end;

Wie Sie sehen ist eine Textur einfach eine Bitmap. Um eine Ellipse mit einer Textur zu füllen, übergeben Sie die Textur einfach als Parameter anstelle der Farbe.

Zwei Befehle bestimmen die Ellipse. Der erste ist die Füllung, der zweite die Rahmenlinie. Beachten Sie, dass der Radius für die Füllung um 0,5 Pixel kleiner ist. Genau genommen ist bei einer Stiftbreite von 1 der innere Radius um 0,5 kleiner und der äußere Radius um 0,5 größer. Aber sogar wenn Sie sorgfältig darauf achten, wird das Ergebnis nicht perfekt sein. Falls Sie eine Verbindung der Polygone mit Antialiasing wollen, sollten Sie 'TBGRAMultishapeFiller' aus der Unit 'BGRAPolygon' verwenden und alles zugleich zeichnen lassen.

Mit einem Befehl für die Rahmenlinie erzielen wir eine texturierte Ellipse mit einem Rahmen. Falls die Rahmenlinienfunktion nicht verfügbar ist, können Sie auch einen Füllbefehl benutzen, zuerst mit einem größeren Radius für die Rahmenfarbe, dann mit einem kleineren Radius für das Innere.

Fügen Sie die folgenden Zeilen ein vor 'tex.Free' :

    image.RoundRectAntialias(x-rx-10,y-ry-10,x+rx+10,y+ry+10,20,20,c,11);
    image.RoundRectAntialias(x-rx-10,y-ry-10,x+rx+10,y+ry+10,20,20,tex,9);

Der erste Befehl zeichnet ein breites, abgerundetes Rechteck (mit Breite 11), das den Rahmen enthält. Der zweite Befehl füllt mit einer Textur und einer geringeren Breite (9). Das klappt perfekt, solange die Textur nicht transparent ist.

Starten Sie das Programm

Sie erhalten ein abgerundetes Rechteck mit einer eingeschlossenen Ellipse. Jede Figur ist mit einer orangen Textur gefüllt.

BGRATutorial8.png

Generieren von Texturen

Basic Perlin noise map

Es ist möglich, anreihbare zufällige Texturen zu erzeugen mit der Funktion 'CreateCyclicPerlinNoiseMap', die Sie in der Unit 'BGRAGradients' finden.

Im Objektinspektor definieren Sie einen OnPaint-Handler mit:

procedure TForm1.FormPaint(Sender: TObject);
var
  image,tex: TBGRABitmap;
 
begin
    image := TBGRABitmap.Create(ClientWidth,ClientHeight);
 
    tex := CreateCyclicPerlinNoiseMap(100,100);
    image.FillRect(0,0,image.Width,image.Height, tex, dmset);
    tex.free;
 
    image.Draw(Canvas,0,0,True);
    image.free;  
end;

Dies erzeugt eine 100x100 Textur und füllt damit das Formular. Sie erhalten etwas ähnliches wie:

BGRATutorial8b.png

Ändern der Farben

Dies ist zu sehr schwarz-weiß. Geben wir etwas Farbe hinein. Dazu brauchen wir eine Funktion, die die Werte interpoliert. Hier ist sie:

  function Interp256(value1,value2,position: integer): integer; inline;
  begin
       result := (value1*(256-position) + value2*position) shr 8;
  end;

Diese Funktion berechnet einen Wert, der von Wert1 bis zu Wert2 läuft. Position ist eine Zahl zwischen 0 und 256 die anzeigt, wie nahe das Ergebnis bei Wert2 liegt. Der Ausdruck "shr 8" ist das optimierte Äquivalent zu "div 256" für positive Werte. Es ist eine binäre Verschiebung um 8 Stellen.

Wir wollen Farben interpolieren, also schreiben wir eine Funktion, die Farben interpoliert:

  function Interp256(color1,color2: TBGRAPixel; position: integer): TBGRAPixel; inline;
  begin
       result.red := Interp256(color1.red,color2.red, position);
       result.green := Interp256(color1.green,color2.green, position);
       result.blue := Interp256(color1.blue,color2.blue, position);
       result.alpha := Interp256(color1.alpha,color2.alpha, position);
  end;

Das ist geradlinig: jeder Farbanteil wird zwischen color1 und color2 interpoliert.

Jetzt haben wir alles, um es uns etwas bunter zu machen. Nach 'CreatePerlinNoiseMap' fügen Sie folgende Zeilen ein:

    p := tex.Data;
    for i := 0 to tex.NbPixels-1 do
    begin
      p^ := Interp256( BGRA(0,128,0), BGRA(192,255,0), p^.red );
      inc(p);
    end;

Sie brauchen die Variablen 'p' und 'i', also klicken Sie auf eine jede und drücken Sie Strg-Umsch-C.

Diese Schleife nimmt jedes Pixel und erzeugt eine Farbe von Dunkelgrün bis zu hellem Gelbgrün.

Wir erhalten einen baumartigen Grünton:

BGRATutorial8c.png

Verwendung von Schwellenwerten

Anstatt ständig zu variieren, kann die Farbänderung einen Schwellenwert berücksichtigen. Im Beispiel ergibt das ein Meer mit Inseln:

    p := tex.Data;
    for i := 0 to tex.NbPixels-1 do
    begin
      if p^.red > 196 then
        p^ := BGRA(192,160,96) else //island
        p^ := BGRA(0,128,196); //sea
      inc(p);
    end;

Wir nehmen mehrere Schwellenwerte. Fertig ist der Tarnanzug:

    p := result.Data;
    for i := 0 to result.NbPixels-1 do
    begin
      v := p^.red;
      if v < 64 then p^:= BGRA(31,33,46) else
      if v < 128 then p^:= BGRA(89,71,57) else
      if v < 192 then p^:= BGRA(80,106,67) else
        p^:= BGRA(161,157,121);
      inc(p);
    end;

BGRATutorial8d.png

Sinusfunktion

Hier wenden wir eine Sinusfunktion für die Noise-Werte an. Erzeugen wir uns dazu eine Prozedur:

  function CreateCustomTexture(tx,ty: integer): TBGRABitmap;
  var
    colorOscillation: integer;
    p: PBGRAPixel;
    i: Integer;
  begin
    result := CreateCyclicPerlinNoiseMap(tx,ty,1,1,1);
    p := result.Data;
    for i := 0 to result.NbPixels-1 do
    begin
      colorOscillation := round(((sin(p^.red*Pi/32)+1)/2)*256);
      p^ := Interp256(BGRA(181,157,105),BGRA(228,227,180),colorOscillation);
      inc(p);
    end;
  end;

ColorOscillation ist ein Wert zwischen 0 und 256. Er wird aus der Intensität von (p^.red) berechnet. Hier wenden wir die Sinusfunktion mit einer halben Periode von 32 an. Dies liefert uns eine Zahl zwischen -1 und 1. Um sie auf den Bereich 0..1 abzubilden, addieren wir 1 und dividieren durch 2. Zuletzt multiplizieren wir mit 256 und haben eine Ganzzahl für Interp256.

Die OnPaint-Prozedur wird einfacher:

var
  image,tex: TBGRABitmap;
 
begin
    image := TBGRABitmap.Create(ClientWidth,ClientHeight,ColorToBGRA(ColorToRGB(clBtnFace)));
 
    tex := CreateCustomTexture(100,100);
    image.FillRoundRectAntialias(20,20,300,200,20,20,tex);
    image.RoundRectAntialias(20,20,300,200,20,20,BGRABlack,1);
    tex.free;
 
    image.Draw(Canvas,0,0,True);
    image.free;
end;

Sie erhalten etwas in der Art:

BGRATutorial8e.png

Jetzt wollen wir, dass es wie Marmor ausschaut, wir brauchen weniger Schwingungen. Zum Beispiel können wir eine halbe Periode von 80 nehmen. Bei Marmor sind die dunklen Stellen sehr dünn. Wir verzerren die Schwingung durch Anwendung der Potenzfunktion: ein Exponent zwischen 0 und 1 bringt die Werte näher an 1 und ein Exponent größer als 1 bringt die Werte näher an 0. So ändern wir die Schwingung in 'CreateCustomTexture':

    colorOscillation := round(power((sin(p^.red*Pi/80)+1)/2,0.2)*256);

Jetzt haben wir etwas marmorartiges:

BGRATutorial8f.png

Holztextur

Auch eine Holztextur erzielen wir mit Sinusfunktionen. Holztexturen enthalten zwei Schwingungen. Eine mit hellen Farben und eine weitere mit dunklen Farben. Deshalb wenden wir eine globale Variation auf diese Schwingungen an:

  function CreateWoodTexture(tx,ty: integer): TBGRABitmap;
  var
    colorOscillation, globalColorVariation: integer;
    p: PBGRAPixel;
    i: Integer;
  begin
    result := CreateCyclicPerlinNoiseMap(tx,ty);
    p := result.Data;
    for i := 0 to result.NbPixels-1 do
    begin
      colorOscillation := round(sqrt((sin(p^.red*Pi/16)+1)/2)*256);
      globalColorVariation := p^.red;
      p^:= Interp256( Interp256(BGRA(247,188,120),BGRA(255,218,170),colorOscillation),
                      Interp256(BGRA(157,97,60),BGRA(202,145,112),colorOscillation), globalColorVariation);
      inc(p);
    end;
  end;

Hier ist die Halbperiode 16, und die globale Variation ist einfach die Intensität. Das Ergebnis sieht etwa so aus:

BGRATutorial8g.png

Meistens ist eine Holztextur entlang einer Achse ausgerichtet. Dazu müssen wir, anstatt die Intensität als globale Position zu verwenden, sie mit der x-Position kombinieren:

  function CreateVerticalWoodTexture(tx,ty: integer): TBGRABitmap;
  var
    globalPos: single;
    colorOscillation, globalColorVariation: integer;
    p: PBGRAPixel;
    i: Integer;
    x: integer;
  begin
    result := CreateCyclicPerlinNoiseMap(tx,ty);
    p := result.Data;
    x := 0;
    for i := 0 to result.NbPixels-1 do
    begin
      globalPos := p^.red*Pi/32+x*2*Pi/tx*8;
      colorOscillation := round(sqrt((sin(globalPos)+1)/2)*256);
      globalColorVariation := round(sin(globalPos/8)*128+128);
      p^:= Interp256( Interp256(BGRA(247,188,120),BGRA(255,218,170),colorOscillation),
                      Interp256(BGRA(157,97,60),BGRA(202,145,112),colorOscillation), globalColorVariation);
      inc(p);
      inc(x);
      if x = tx then x := 0;
    end;
  end;

Wir erhalten dies:

Tutorial8h.png


Voriges Tutorial (Splines) | Nächstes Tutorial (Phong-Schattierung und Texturen)