Sign in Join
Wiki copytemporary

BGRABitmap tutorial 8: what changed

Revision 0 to 1. Removed lines have a minus sign; added lines have a plus sign.

Title: BGRABitmap tutorial 8

OldNewLine
1+This tutorial shows how to use textures.
2+
3+### Create a new project
4+
5+Create a new project and add a reference to [[BGRABitmap]], the same way as in [[BGRABitmap tutorial|the first tutorial]].
6+
7+### Using brush textures
8+
9+The simplest texture is a hatched brush.
10+
11+With the object inspector, add an OnPaint handler and write:
12+
13+``` pascal
14+procedure TForm1.FormPaint(Sender: TObject);
15+var
16+ image,tex: TBGRABitmap;
17+ c: TBGRAPixel;
18+ x,y,rx,ry: single;
19+
20+begin
21+ image := TBGRABitmap.Create(ClientWidth,ClientHeight,ColorToBGRA(ColorToRGB(clBtnFace)));
22+ c := ColorToBGRA(ColorToRGB(clWindowText));
23+
24+ //ellipse coordinates
25+ x := 150;
26+ y := 100;
27+ rx := 100;
28+ ry := 50;
29+
30+ //loads a "diagcross" brush with white pattern and orange background
31+ tex := image.CreateBrushTexture(bsDiagCross,BGRAWhite,BGRA(255,192,0)) as TBGRABitmap;
32+
33+ image.FillEllipseAntialias(x,y,rx-0.5,ry-0.5,tex);
34+ image.EllipseAntialias(x,y,rx,ry,c,1); //draw outline
35+
36+ tex.Free;
37+
38+ image.Draw(Canvas,0,0,True);
39+ image.free;
40+end;
41+```
42+
43+As you can see, a texture is just some bitmap. To fill an ellipse with a texture, just pass the texture as a parameter instead of the color.
44+
45+Two commands define the ellipse. The first is the filling, and the second is the outline. Notice that the radius is 0.5 pixel smaller for the filling. Indeed, when the pen width is 1, the inner radius is 0.5 smaller and the outer radius is 0.5 greater. Nevertheless, even if you pay attention to this, the result will not be perfect. If you want an antialiased junction between polygons, you need to use TBGRAMultishapeFiller in the BGRAPolygon unit and draw everything at once.
46+
47+Using a command for the outline, we achieve to draw a textured ellipse with a border. But if the outline function is not available, you can also use another fill command with a greater radius with the border color first, and then a smaller radius for the inside.
48+
49+Add the following lines before tex.Free:
50+
51+``` pascal
52+ image.RoundRectAntialias(x-rx-10,y-ry-10,x+rx+10,y+ry+10,20,20,c,11);
53+ image.RoundRectAntialias(x-rx-10,y-ry-10,x+rx+10,y+ry+10,20,20,tex,9);
54+```
55+
56+The first command draws a wide round rectangle (of width 11) that includes the border. The second command fill with the texture with a smaller width (9). This works perfectly as long as the texture is not transparent.
57+
58+### Run the program
59+
60+You should obtain a round rectangle with an ellipse inside it. Each shape is filled with an orange texture.
61+
62+<figure>
63+<img src="BGRATutorial8.png" title="BGRATutorial8.png" />
64+<figcaption>BGRATutorial8.png</figcaption>
65+</figure>
66+
67+### Generating textures
68+
69+#### Basic Perlin noise map
70+
71+It is possible to generate tilable random textures using CreateCyclicPerlinNoiseMap that can be found in the BGRAGradients unit.
72+
73+With the object inspector, define the OnPaint handler with:
74+
75+``` pascal
76+uses
77+ BGRABitmap, BGRABitmapTypes, BGRAGradients;
78+
79+procedure TForm1.FormPaint(Sender: TObject);
80+var
81+ image,tex: TBGRABitmap;
82+
83+begin
84+ image := TBGRABitmap.Create(ClientWidth,ClientHeight);
85+
86+ tex := CreateCyclicPerlinNoiseMap(100,100);
87+ image.FillRect(0,0,image.Width,image.Height, tex, dmSet);
88+ tex.free;
89+
90+ image.Draw(Canvas,0,0,True);
91+ image.free;
92+end;
93+```
94+
95+This creates a 100x100 texture, and fills the form with it. You should obtain something like this:
96+
97+<figure>
98+<img src="BGRATutorial8b.png" title="BGRATutorial8b.png" />
99+<figcaption>BGRATutorial8b.png</figcaption>
100+</figure>
101+
102+#### Changing color
103+
104+This is very black and white. We can add some colors. For this, we will need some function to interpolate values. Here it is:
105+
106+``` pascal
107+ function Interp256(value1,value2,position: integer): integer; inline;
108+ begin
109+ result := (value1*(256-position) + value2*position) shr 8;
110+ end;
111+```
112+
113+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.
114+
115+We want to interpolate colors, so let's write a function to interpolate colors:
116+
117+``` pascal
118+ function Interp256(color1,color2: TBGRAPixel; position: integer): TBGRAPixel; inline;
119+ begin
120+ result.red := Interp256(color1.red,color2.red, position);
121+ result.green := Interp256(color1.green,color2.green, position);
122+ result.blue := Interp256(color1.blue,color2.blue, position);
123+ result.alpha := Interp256(color1.alpha,color2.alpha, position);
124+ end;
125+```
126+
127+It is straightforward : each color component is interpolated between color1 and color2 values.
128+
129+Now we have everything to make some colors. After CreatePerlinNoiseMap, add the following lines:
130+
131+``` pascal
132+ p := tex.Data;
133+ for i := 0 to tex.NbPixels-1 do
134+ begin
135+ p^ := Interp256( BGRA(0,128,0), BGRA(192,255,0), p^.red );
136+ inc(p);
137+ end;
138+```
139+
140+You'll need variables 'p' and 'i', so click on each and press Ctrl-Shift-C.
141+
142+This loop takes each pixel and creates a color from dark green to light green-yellow.
143+
144+We obtain a tree-like green color:
145+
146+<figure>
147+<img src="BGRATutorial8c.png" title="BGRATutorial8c.png" />
148+<figcaption>BGRATutorial8c.png</figcaption>
149+</figure>
150+
151+#### Using thresholds
152+
153+Instead of varying continuously, the color can be changed with a threshold. For example, we can delimit sea and islands:
154+
155+``` pascal
156+ p := tex.Data;
157+ for i := 0 to tex.NbPixels-1 do
158+ begin
159+ if p^.red > 196 then
160+ p^ := BGRA(192,160,96) else //island
161+ p^ := BGRA(0,128,196); //sea
162+ inc(p);
163+ end;
164+```
165+
166+We can use more thresholds. Here is a camouflage:
167+
168+``` pascal
169+ p := result.Data;
170+ for i := 0 to result.NbPixels-1 do
171+ begin
172+ v := p^.red;
173+ if v < 64 then p^:= BGRA(31,33,46) else
174+ if v < 128 then p^:= BGRA(89,71,57) else
175+ if v < 192 then p^:= BGRA(80,106,67) else
176+ p^:= BGRA(161,157,121);
177+ inc(p);
178+ end;
179+```
180+
181+<figure>
182+<img src="BGRATutorial8d.png" title="BGRATutorial8d.png" />
183+<figcaption>BGRATutorial8d.png</figcaption>
184+</figure>
185+
186+#### Sine function
187+
188+We will apply here a sine function to noise values. Let's create a procedure for this:
189+
190+``` pascal
191+ function CreateCustomTexture(tx,ty: integer): TBGRABitmap;
192+ var
193+ colorOscillation: integer;
194+ p: PBGRAPixel;
195+ i: Integer;
196+ begin
197+ result := CreateCyclicPerlinNoiseMap(tx,ty,1,1,1);
198+ p := result.Data;
199+ for i := 0 to result.NbPixels-1 do
200+ begin
201+ colorOscillation := round(((sin(p^.red*Pi/32)+1)/2)*256);
202+ p^ := Interp256(BGRA(181,157,105),BGRA(228,227,180),colorOscillation);
203+ inc(p);
204+ end;
205+ end;
206+```
207+
208+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.
209+
210+The OnPaint procedure becomes simpler:
211+
212+``` pascal
213+var
214+ image,tex: TBGRABitmap;
215+
216+begin
217+ image := TBGRABitmap.Create(ClientWidth,ClientHeight,ColorToBGRA(ColorToRGB(clBtnFace)));
218+
219+ tex := CreateCustomTexture(100,100);
220+ image.FillRoundRectAntialias(20,20,300,200,20,20,tex);
221+ image.RoundRectAntialias(20,20,300,200,20,20,BGRABlack,1);
222+ tex.free;
223+
224+ image.Draw(Canvas,0,0,True);
225+ image.free;
226+end;
227+```
228+
229+You should obtain something like this:
230+
231+<figure>
232+<img src="BGRATutorial8e.png" title="BGRATutorial8e.png" />
233+<figcaption>BGRATutorial8e.png</figcaption>
234+</figure>
235+
236+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:
237+
238+``` pascal
239+ colorOscillation := round(power((sin(p^.red*Pi/80)+1)/2,0.2)*256);
240+```
241+
242+Now we have something much more like marble:
243+
244+<figure>
245+<img src="BGRATutorial8f.png" title="BGRATutorial8f.png" />
246+<figcaption>BGRATutorial8f.png</figcaption>
247+</figure>
248+
249+#### Wood texture
250+
251+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:
252+
253+``` pascal
254+
255+ function CreateWoodTexture(tx,ty: integer): TBGRABitmap;
256+ var
257+ colorOscillation, globalColorVariation: integer;
258+ p: PBGRAPixel;
259+ i: Integer;
260+ begin
261+ result := CreateCyclicPerlinNoiseMap(tx,ty);
262+ p := result.Data;
263+ for i := 0 to result.NbPixels-1 do
264+ begin
265+ colorOscillation := round(sqrt((sin(p^.red*Pi/16)+1)/2)*256);
266+ globalColorVariation := p^.red;
267+ p^:= Interp256( Interp256(BGRA(247,188,120),BGRA(255,218,170),colorOscillation),
268+ Interp256(BGRA(157,97,60),BGRA(202,145,112),colorOscillation), globalColorVariation);
269+ inc(p);
270+ end;
271+ end;
272+```
273+
274+Here the half-period is 16, and the global variation is simply the intensity. The result is something like this:
275+
276+<figure>
277+<img src="BGRATutorial8g.png" title="BGRATutorial8g.png" />
278+<figcaption>BGRATutorial8g.png</figcaption>
279+</figure>
280+
281+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:
282+
283+``` pascal
284+ function CreateVerticalWoodTexture(tx,ty: integer): TBGRABitmap;
285+ var
286+ globalPos: single;
287+ colorOscillation, globalColorVariation: integer;
288+ p: PBGRAPixel;
289+ i: Integer;
290+ x: integer;
291+ begin
292+ result := CreateCyclicPerlinNoiseMap(tx,ty);
293+ p := result.Data;
294+ x := 0;
295+ for i := 0 to result.NbPixels-1 do
296+ begin
297+ globalPos := p^.red*Pi/32+x*2*Pi/tx*8;
298+ colorOscillation := round(sqrt((sin(globalPos)+1)/2)*256);
299+ globalColorVariation := round(sin(globalPos/8)*128+128);
300+ p^:= Interp256( Interp256(BGRA(247,188,120),BGRA(255,218,170),colorOscillation),
301+ Interp256(BGRA(157,97,60),BGRA(202,145,112),colorOscillation), globalColorVariation);
302+ inc(p);
303+ inc(x);
304+ if x = tx then x := 0;
305+ end;
306+ end;
307+```
308+
309+We obtain this:
310+
311+<figure>
312+<img src="Tutorial8h.png" title="Tutorial8h.png" />
313+<figcaption>Tutorial8h.png</figcaption>
314+</figure>
315+
316+[[BGRABitmap tutorial 7|Previous tutorial (splines)]] [[BGRABitmap tutorial 9|Next tutorial (phong shading and textures)]]
317+
318+---
319+*From the Free Pascal wiki, page "BGRABitmap tutorial 8", as it stood on 2020-01-02 after a change by Trev, out of the wiki's dump of 2024-05-26. Copyright stays with the author who wrote it, according to the old wiki's [[Licensing]] page. If you have concerns about this material, [message the admin](/wiki-contact).*
320+Category: BGRABitmap
321+Category: Graphics

Back to history