Difference between revisions of "BGRABitmap tutorial 8/es"

From Free Pascal wiki
Jump to navigationJump to search
(no esta completo)
 
m (Fixed syntax highlighting; deleted category already in page template)
 
(5 intermediate revisions by 4 users not shown)
Line 1: Line 1:
{{BGRABitmap_tutorial_8}}
+
{{BGRABitmap_tutorial_8}}[[category:Castellano|C]][[category:Español|C]]
  
 
{{BGRABitmap_tutorial_index/es}}
 
{{BGRABitmap_tutorial_index/es}}
  
Este tutorial muestra como usar texturas.
+
Esta tutoría muestra como usar texturas.
  
 
=== Crear un nuevo proyecto ===
 
=== Crear un nuevo proyecto ===
  
Crea un nuevo proyecto y añade referencia a [[BGRABitmap]], de la misma forma que en el [[BGRABitmap tutorial/es|primer tutorial]].
+
Crea un nuevo proyecto y añade referencia a [[BGRABitmap]], de la misma forma que en [[BGRABitmap tutorial/es|la primer tutoría]].
  
 
=== Usando pinceles de textura ===
 
=== Usando pinceles de textura ===
Line 14: Line 14:
  
 
Con el inspector de objetos, añade un manejador OnPaint y escribe :
 
Con el inspector de objetos, añade un manejador OnPaint y escribe :
<delphi>procedure TForm1.FormPaint(Sender: TObject);
+
<syntaxhighlight lang="pascal">procedure TForm1.FormPaint(Sender: TObject);
 
var
 
var
 
   image,tex: TBGRABitmap;
 
   image,tex: TBGRABitmap;
Line 40: Line 40:
 
     image.Draw(Canvas,0,0,True);
 
     image.Draw(Canvas,0,0,True);
 
     image.free;   
 
     image.free;   
end;</delphi>
+
end;</syntaxhighlight>
  
 
Como puedes ver, una textura es un bitmap. Para rellenar un elipse con una textura, pasa la textura como parametro en vez del color.
 
Como puedes ver, una textura es un bitmap. Para rellenar un elipse con una textura, pasa la textura como parametro en vez del color.
Line 49: Line 49:
  
 
Agregue las líneas siguientes antes de tex.Free:
 
Agregue las líneas siguientes antes de tex.Free:
<delphi>    image.RoundRectAntialias(x-rx-10,y-ry-10,x+rx+10,y+ry+10,20,20,c,11);
+
<syntaxhighlight lang="pascal">    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); </delphi>
+
     image.RoundRectAntialias(x-rx-10,y-ry-10,x+rx+10,y+ry+10,20,20,tex,9); </syntaxhighlight>
  
 
El primer comando dibuja un rectángulo alrededor de ancho (ancho de 11) que incluye la frontera. El segundo comando se llenan de la textura con un ancho menor (9). Esto funciona perfectamente, siempre y cuando la textura no es transparente.
 
El primer comando dibuja un rectángulo alrededor de ancho (ancho de 11) que incluye la frontera. El segundo comando se llenan de la textura con un ancho menor (9). Esto funciona perfectamente, siempre y cuando la textura no es transparente.
Line 66: Line 66:
  
 
With the object inspector, define the OnPaint handler with :
 
With the object inspector, define the OnPaint handler with :
<delphi>procedure TForm1.FormPaint(Sender: TObject);
+
<syntaxhighlight lang="pascal">procedure TForm1.FormPaint(Sender: TObject);
 
var
 
var
 
   image,tex: TBGRABitmap;
 
   image,tex: TBGRABitmap;
Line 79: Line 79:
 
     image.Draw(Canvas,0,0,True);
 
     image.Draw(Canvas,0,0,True);
 
     image.free;   
 
     image.free;   
end;</delphi>
+
end;</syntaxhighlight>
  
 
This creates a 100x100 texture, and fills the form with it. You should obtain something like this :
 
This creates a 100x100 texture, and fills the form with it. You should obtain something like this :
Line 88: Line 88:
  
 
This is very black and white. We can add some colors. For this, we will need some function to interpolate values. Here it is :
 
This is very black and white. We can add some colors. For this, we will need some function to interpolate values. Here it is :
<delphi>  function Interp256(value1,value2,position: integer): integer; inline;
+
<syntaxhighlight lang="pascal">  function Interp256(value1,value2,position: integer): integer; inline;
 
   begin
 
   begin
 
       result := (value1*(256-position) + value2*position) shr 8;
 
       result := (value1*(256-position) + value2*position) shr 8;
   end;</delphi>
+
   end;</syntaxhighlight>
 
This function computes a value going from value1 to value2. Position is a number between 0 and 256 which indicates how close the result is to the second value. The expression "shr 8" is an optimized equivalent to "div 256" for positive values. It is a binary shift of 8 digits.
 
This function computes a value going from value1 to value2. Position is a number between 0 and 256 which indicates how close the result is to the second value. The expression "shr 8" is an optimized equivalent to "div 256" for positive values. It is a binary shift of 8 digits.
  
 
We want to interpolate colors, so let's write a function to interpolate colors :
 
We want to interpolate colors, so let's write a function to interpolate colors :
<delphi>  function Interp256(color1,color2: TBGRAPixel; position: integer): TBGRAPixel; inline;
+
<syntaxhighlight lang="pascal">  function Interp256(color1,color2: TBGRAPixel; position: integer): TBGRAPixel; inline;
 
   begin
 
   begin
 
       result.red := Interp256(color1.red,color2.red, position);
 
       result.red := Interp256(color1.red,color2.red, position);
Line 101: Line 101:
 
       result.blue := Interp256(color1.blue,color2.blue, position);
 
       result.blue := Interp256(color1.blue,color2.blue, position);
 
       result.alpha := Interp256(color1.alpha,color2.alpha, position);
 
       result.alpha := Interp256(color1.alpha,color2.alpha, position);
   end;</delphi>
+
   end;</syntaxhighlight>
 
It is straightforward : each color component is interpolated between color1 and color2 values.
 
It is straightforward : each color component is interpolated between color1 and color2 values.
  
 
Now we have everything to make some colors. After CreatePerlinNoiseMap, add the following lines :
 
Now we have everything to make some colors. After CreatePerlinNoiseMap, add the following lines :
<delphi>    p := tex.Data;
+
<syntaxhighlight lang="pascal">    p := tex.Data;
 
     for i := 0 to tex.NbPixels-1 do
 
     for i := 0 to tex.NbPixels-1 do
 
     begin
 
     begin
 
       p^ := Interp256( BGRA(0,128,0), BGRA(192,255,0), p^.red );
 
       p^ := Interp256( BGRA(0,128,0), BGRA(192,255,0), p^.red );
 
       inc(p);
 
       inc(p);
     end;  </delphi>
+
     end;  </syntaxhighlight>
 
You'll need variables 'p' and 'i', so click on each and press Ctrl-Shift-C.
 
You'll need variables 'p' and 'i', so click on each and press Ctrl-Shift-C.
  
Line 122: Line 122:
  
 
Instead of varying continuously, the color can be changed with a threshold. For example, we can delimit sea and islands :
 
Instead of varying continuously, the color can be changed with a threshold. For example, we can delimit sea and islands :
<delphi>    p := tex.Data;
+
<syntaxhighlight lang="pascal">    p := tex.Data;
 
     for i := 0 to tex.NbPixels-1 do
 
     for i := 0 to tex.NbPixels-1 do
 
     begin
 
     begin
Line 129: Line 129:
 
         p^ := BGRA(0,128,196); //sea
 
         p^ := BGRA(0,128,196); //sea
 
       inc(p);
 
       inc(p);
     end;  </delphi>
+
     end;  </syntaxhighlight>
  
 
We can use more thresholds. Here is a camouflage :
 
We can use more thresholds. Here is a camouflage :
<delphi>    p := result.Data;
+
<syntaxhighlight lang="pascal">    p := result.Data;
 
     for i := 0 to result.NbPixels-1 do
 
     for i := 0 to result.NbPixels-1 do
 
     begin
 
     begin
Line 141: Line 141:
 
         p^:= BGRA(161,157,121);
 
         p^:= BGRA(161,157,121);
 
       inc(p);
 
       inc(p);
     end;  </delphi>
+
     end;  </syntaxhighlight>
  
 
[[Image:BGRATutorial8d.png]]
 
[[Image:BGRATutorial8d.png]]
Line 149: Line 149:
 
We will apply here a sine function to noise values. Let's create a procedure for this :
 
We will apply here a sine function to noise values. Let's create a procedure for this :
  
<delphi>
+
<syntaxhighlight lang="pascal">
 
   function CreateCustomTexture(tx,ty: integer): TBGRABitmap;
 
   function CreateCustomTexture(tx,ty: integer): TBGRABitmap;
 
   var
 
   var
Line 165: Line 165:
 
     end;
 
     end;
 
   end;
 
   end;
</delphi>
+
</syntaxhighlight>
 
The color oscillation is a value between 0 and 256. It is calculated out of the intensity (p^.red). Here we apply a sine function with a half-period of 32. This gives a number between -1 and 1. To put it in the range 0..1, we add 1 and divide by 2. Finally, we multiply by 256 to have an integer for Interp256.
 
The color oscillation is a value between 0 and 256. It is calculated out of the intensity (p^.red). Here we apply a sine function with a half-period of 32. This gives a number between -1 and 1. To put it in the range 0..1, we add 1 and divide by 2. Finally, we multiply by 256 to have an integer for Interp256.
  
 
The OnPaint procedure becomes simpler :
 
The OnPaint procedure becomes simpler :
<delphi>
+
<syntaxhighlight lang="pascal">
 
var
 
var
 
   image,tex: TBGRABitmap;
 
   image,tex: TBGRABitmap;
Line 184: Line 184:
 
     image.free;
 
     image.free;
 
end;  
 
end;  
</delphi>
+
</syntaxhighlight>
  
 
You should obtain something like this :
 
You should obtain something like this :
Line 191: Line 191:
  
 
Now, if we want it to look like marble, we need less oscillations. For example, we can use a half-period of 80. On marble, the black parts are very thin. We can distort the oscillation by applying the power function : an exponent between 0 and 1 will make the values closer to 1 and an exponent greater than 1 will make the values closer to 0. Let's change the oscillation like this in CreateCustomTexture :
 
Now, if we want it to look like marble, we need less oscillations. For example, we can use a half-period of 80. On marble, the black parts are very thin. We can distort the oscillation by applying the power function : an exponent between 0 and 1 will make the values closer to 1 and an exponent greater than 1 will make the values closer to 0. Let's change the oscillation like this in CreateCustomTexture :
<delphi>    colorOscillation := round(power((sin(p^.red*Pi/80)+1)/2,0.2)*256); </delphi>
+
<syntaxhighlight lang="pascal">    colorOscillation := round(power((sin(p^.red*Pi/80)+1)/2,0.2)*256); </syntaxhighlight>
  
 
Now we have something much more like marble :
 
Now we have something much more like marble :
Line 200: Line 200:
  
 
Wood texture can be achieved with sine functions too. Wood texture contains two oscillations, one with light colors, and another one with dark colors. So we need to apply a global variation between these oscillations :
 
Wood texture can be achieved with sine functions too. Wood texture contains two oscillations, one with light colors, and another one with dark colors. So we need to apply a global variation between these oscillations :
<delphi>  function CreateWoodTexture(tx,ty: integer): TBGRABitmap;
+
<syntaxhighlight lang="pascal">  function CreateWoodTexture(tx,ty: integer): TBGRABitmap;
 
   var
 
   var
 
     colorOscillation, globalColorVariation: integer;
 
     colorOscillation, globalColorVariation: integer;
Line 216: Line 216:
 
       inc(p);
 
       inc(p);
 
     end;
 
     end;
   end;</delphi>
+
   end;</syntaxhighlight>
  
 
Here the half-period is 16, and the global variation is simply the intensity. The result is something like this :
 
Here the half-period is 16, and the global variation is simply the intensity. The result is something like this :
Line 223: Line 223:
  
 
Most of the time, a wood texture is oriented along an axis. To do this, instead of using intensity only as a global position, we need to combine it with the x position :
 
Most of the time, a wood texture is oriented along an axis. To do this, instead of using intensity only as a global position, we need to combine it with the x position :
<delphi>  function CreateVerticalWoodTexture(tx,ty: integer): TBGRABitmap;
+
<syntaxhighlight lang="pascal">  function CreateVerticalWoodTexture(tx,ty: integer): TBGRABitmap;
 
   var
 
   var
 
     globalPos: single;
 
     globalPos: single;
Line 245: Line 245:
 
       if x = tx then x := 0;
 
       if x = tx then x := 0;
 
     end;
 
     end;
   end; </delphi>
+
   end; </syntaxhighlight>
  
 
We obtain this :
 
We obtain this :
Line 252: Line 252:
  
  
[[BGRABitmap tutorial 7/es|Tutorial anterior (splines)]] [[BGRABitmap tutorial 9/es|Siguiente tutorial (phong shading y texturas)]]
+
[[BGRABitmap tutorial 7/es|Tutoría anterior (splines)]] | [[BGRABitmap tutorial 9/es|Siguiente tutoría (phong shading y texturas)]]
 
 
[[Category:Graphics]]
 

Latest revision as of 06:14, 10 February 2020

Deutsch (de) English (en) español (es) français (fr)


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

Esta tutoría muestra como usar texturas.

Crear un nuevo proyecto

Crea un nuevo proyecto y añade referencia a BGRABitmap, de la misma forma que en la primer tutoría.

Usando pinceles de textura

La textura mas simple es un pincel eclosionado.

Con el inspector de objetos, añade un manejador OnPaint y escribe :

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

    //ellipse coordinates
    x := 150;
    y := 100;
    rx := 100;
    ry := 50;

    // carga un pincel "diagcross" con un patron blanco y fondo naranja
	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;

Como puedes ver, una textura es un bitmap. Para rellenar un elipse con una textura, pasa la textura como parametro en vez del color.

Dos comandos definen el elipse. El primero es el rellenado, y el segundo el contorno. Ten en cuenta que el radio es 0.5 píxeles mas pequeños que el rellenado. De todas fromas, cuando el ancho de la pluma es 1, el radio interior es 0.5 mas pequeño y el radio exterior es 0.5 mas grande. Sin embargo, incluso si se presta atención a esto, el resultado no será perfecto. Si desea una unión suavizado entre polígonos, es necesario utilizar TBGRAMultishapeFiller en la unidad de BGRAPolygon y sacar todo de una vez.

Uso de un comando para el esquema, logramos establecer una elipse con textura con un borde. Pero si la función de esquema no está disponible, también puede utilizar otro comando se llenan de un radio mayor con el color del borde, y luego un radio más pequeño para el interior.

Agregue las líneas siguientes antes de 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);

El primer comando dibuja un rectángulo alrededor de ancho (ancho de 11) que incluye la frontera. El segundo comando se llenan de la textura con un ancho menor (9). Esto funciona perfectamente, siempre y cuando la textura no es transparente.

Ejecuta el programa

Deberías obtener un rectángulo redondeado con un elipse dentro. Cada capa está rellenada con una textura naranja.

BGRATutorial8.png

Generating textures

Basic Perlin noise map

It is possible to generate tilable random textures using CreateCyclicPerlinNoiseMap that can be found in the BGRAGradient unit.

With the object inspector, define the OnPaint handler with :

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);
    tex.free;    

    image.Draw(Canvas,0,0,True);
    image.free;  
end;

This creates a 100x100 texture, and fills the form with it. You should obtain something like this :

BGRATutorial8b.png

Changing color

This is very black and white. We can add some colors. For this, we will need some function to interpolate values. Here it is :

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

This function computes a value going from value1 to value2. Position is a number between 0 and 256 which indicates how close the result is to the second value. The expression "shr 8" is an optimized equivalent to "div 256" for positive values. It is a binary shift of 8 digits.

We want to interpolate colors, so let's write a function to interpolate colors :

  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;

It is straightforward : each color component is interpolated between color1 and color2 values.

Now we have everything to make some colors. After CreatePerlinNoiseMap, add the following lines :

    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;

You'll need variables 'p' and 'i', so click on each and press Ctrl-Shift-C.

This loop takes each pixel and creates a color from dark green to light green-yellow.

We obtain a tree-like green color :

BGRATutorial8c.png

Using thresholds

Instead of varying continuously, the color can be changed with a threshold. For example, we can delimit sea and islands :

    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;

We can use more thresholds. Here is a camouflage :

    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

Sine function

We will apply here a sine function to noise values. Let's create a procedure for this :

  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;

The color oscillation is a value between 0 and 256. It is calculated out of the intensity (p^.red). Here we apply a sine function with a half-period of 32. This gives a number between -1 and 1. To put it in the range 0..1, we add 1 and divide by 2. Finally, we multiply by 256 to have an integer for Interp256.

The OnPaint procedure becomes simpler :

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;

You should obtain something like this :

BGRATutorial8e.png

Now, if we want it to look like marble, we need less oscillations. For example, we can use a half-period of 80. On marble, the black parts are very thin. We can distort the oscillation by applying the power function : an exponent between 0 and 1 will make the values closer to 1 and an exponent greater than 1 will make the values closer to 0. Let's change the oscillation like this in CreateCustomTexture :

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

Now we have something much more like marble :

BGRATutorial8f.png

Wood texture

Wood texture can be achieved with sine functions too. Wood texture contains two oscillations, one with light colors, and another one with dark colors. So we need to apply a global variation between these oscillations :

  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;

Here the half-period is 16, and the global variation is simply the intensity. The result is something like this :

BGRATutorial8g.png

Most of the time, a wood texture is oriented along an axis. To do this, instead of using intensity only as a global position, we need to combine it with the x position :

  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;

We obtain this :

Tutorial8h.png


Tutoría anterior (splines) | Siguiente tutoría (phong shading y texturas)