|
posledná
zmena: 4.12.2002
|
3.12.2002
|
čo sa budeme dnes učiť
- ak budeme pracovať s farebnými bodmi
bitmapy pomocou ScanLine, práca sa veľmi
zrýchli a konečne môžeme vytvárať
veľmi zaujímavé efekty s obrázkami
- vykonanie niektorých dôležitých
častí programu môžeme chrániť
konštrukciou try ... finally ... end
Práca s bitmapou
- doteraz sme vedeli pracovať s farebnými bodmi
bitmapy (s pixelmi) len pomocou vlastnosti Pixels[x,y]
- o tomto ale vieme, že je to veľmi pomalý mechanizmus
a v praxi často nepoužiteľný
- naučíme sa pracovať s oveľa rýchlejším
mechanizmom ScanLine[y] - pomocou neho dostávame
"priamy" prístup k jednému celému
riadku bitmapy a veľmi rýchlo môžeme farebné
zložky nielen čítať ale aj modifikovať
- na rozdiel od Pixels, ktorý
bol vlastnosťou (property) TCanvas, ScanLine
je vlastnosťou TBitmap a teda priamo
s Image sa takto pracuje komplikovanejšie
- výsledkom ScanLine[y] (túto vlastnosť
môžeme len čítať) je netypový smerník
na postupnosť, t.j. pole RGB zložiek - trojíc
bajtov:
trojica farebných zložiek RGB:
|
type
TRGB = record
B,G,R:byte;
end;
|
k postupnosti môžeme pristupovať napr. tak
ako s "null-terminated string":
|
type
PRGB = ^TRGB;
var
d:PRGB;
...
d:=bmp.ScanLine[y];
for i:=0 to bmp.Width-1 do begin
d^.R:=0;
inc(d); // nasledujúci pixel
end;
|
alebo ako smerník na pole:
|
type
TRGBArray = array[word] of TRGB; // dostatočne veľká hranica
PRGBArray = ^TRGBArray;
|
Pozn.
- v štandardných knižniciach Delphi existujú
podobné deklarácie:
- TRGBTriple je podobné nášmu
TRGB (v unite Windows)
- PRGBTripleArray je podobné
nášmu PRGBArray (v unite Graphics)
- prvý príklad ukazuje, ako môžeme
pomocou ScanLine vytvoriť vlastnú
bitmapu - predpokladáme, že vo formulári
máme Image1 veľkosti 256x256:
vytvorenie jednofarebnej bitmapy:
|
procedure TForm1.Button1Click(Sender: TObject);
var
bmp:TBitmap;
i,j:integer;
begin
bmp:=TBitmap.Create;
bmp.PixelFormat:=pf24bit;
bmp.Width:=256;
bmp.Height:=256;
for i:=0 to 255 do
for j:=0 to 255 do
PRGBArray(bmp.ScanLine[i])^[j].B:=0;
Image1.Canvas.Draw(0,0,bmp);
bmp.Free;
end;
|
- keď vytvoríme novú bitmapu (TBitmap.Create), každý
jej pixel je biely - v tejto ukážke všetkým
pixelom vynulujeme modrú zložku RGB, t.j.
vytvoríme žltú
- nasledujúci príklad je veľmi podobný,
len každý pixel novej bitmapy bude trochu
iný
vytvorenie farebnej bitmapy:
|
procedure TForm1.Button1Click(Sender: TObject);
var
bmp:TBitmap;
i,j:integer;
c:TRGB;
begin
bmp:=TBitmap.Create;
bmp.PixelFormat:=pf24bit;
bmp.Width:=256;
bmp.Height:=256;
for i:=0 to 255 do
for j:=0 to 255 do begin
c.R:=i; c.B:=255-i; c.G:=j;
PRGBArray(bmp.ScanLine[i])[j]:=c;
end;
Image1.Canvas.Draw(0,0,bmp);
bmp.Free;
end;
|
- v nasledujúcich príkladoch predpokladáme,
že máme k dispozícii globálnu premennú
bmp1, do ktorej vo FormCreate prečítame nejakú
bitmapu, vo FormDestroy ju uvoľníme a vo formulári
je položený Image1 veľkosti 256x256:
načítanie a zobrazenie bitmapy:
|
var
bmp1:TBitmap;
procedure TForm1.FormCreate(Sender: TObject);
begin
bmp1:=TBitmap.Create;
bmp1.LoadFromFile('parrots.bmp');
Image1.Canvas.Draw(0,0,bmp1);
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
bmp1.Free;
end;
|
- ďalej sa na formulári
nachádza Image2 tiež veľkosti 256x256
a niekoľko tlačidiel
kópia bitmapy - 1. verzia:
|
procedure TForm1.Button1Click(Sender: TObject);
var
bmp2:TBitmap;
i,j:integer;
begin
bmp2:=TBitmap.Create;
bmp2.PixelFormat:=pf24bit;
bmp2.Width:=bmp1.Width;
bmp2.Height:=bmp1.Height;
for i:=0 to bmp1.Height-1 do
for j:=0 to bmp1.Width-1 do
PRGBArray(bmp2.ScanLine[i])^[j]:=
PRGBArray(bmp1.ScanLine[i])^[j];
Image2.Canvas.Draw(0,0,bmp2);
bmp2.Free;
end;
|
Pozn.
- samozrejme, že kópiu bitmapy vieme aj
jednoduchšie pomocou bmp2.Assign(bmp1) -
tu sa ale učíme pracovať so ScanLine
kópia bitmapy - 2. verzia:
|
procedure TForm1.Button1Click(Sender: TObject);
var
bmp2:TBitmap;
i,j:integer;
r1,r2:PRGBArray;
begin
bmp2:=TBitmap.Create;
bmp2.PixelFormat:=pf24bit;
bmp2.Width:=bmp1.Width;
bmp2.Height:=bmp1.Height;
for i:=0 to bmp1.Height-1 do begin
r1:=bmp1.ScanLine[i];
r2:=bmp2.ScanLine[i];
for j:=0 to bmp1.Width-1 do
r2^[j]:=r1^[j]; // alebo r2[j]:=r1[j];
end;
Image2.Canvas.Draw(0,0,bmp2);
bmp2.Free;
end;
|
veľmi podobný je zrkadlový obraz:
|
procedure TForm1.Button2Click(Sender: TObject);
var
bmp2:TBitmap;
w,h,i,j:integer;
r1,r2:PRGBArray;
begin
w:=bmp1.Width; h:=bmp1.Height;
bmp2.PixelFormat:=pf24bit;
bmp2:=TBitmap.Create;
bmp2.Width:=w;
bmp2.Height:=h;
for i:=0 to h-1 do begin
r1:=bmp1.ScanLine[i];
r2:=bmp2.ScanLine[i];
for j:=0 to w-1 do
r2[j]:=r1[w-1-j];
end;
Image2.Canvas.Draw(0,0,bmp2);
bmp2.Free;
end;
|
Chránené príkazy
- pri programovaní sa často stretávame
so situáciou, že niektoré príkazy
potrebujeme bezpodmienečne vykonať, aj keď sa z
nejakých dôvodov preruší výpočet,
napr. pre výskyt chyby alebo ukončenie procedúry
pomocou exit
- napr. vždy keď v procedúre vytvoríme
pomocnú bitmapu, pred ukončením
ju musíme zrušiť, podobne, vždy, keď
otvoríme súbor, na záver
ho musíme zatvoriť, ...
- môžeme použiť konštrukciu: try
postupnosťPríkazov1 finally
postupnosťPríkazov2 end:
- ak počas vykonávania prvej postupnosti
príkazov vznikne nejaké prerušenie
(exit, break, spadnutie na chybe), tak sa pokračuje
v druhej postupnosti príkazov
- ak prvá časť skončí bez chyby,
tak sa normálne pokračuje v druhej časti
- try ... end si môžete predstaviť
ako begin ... end, pričom, ak v ňom nastane
nejaké prerušenie, vykonajú sa
ešte upratovacie akcie
- konštrukcie try ... end môžu
byť navzájom vnorené rovnako ako
begin ... end
- v objektovom pascale existuje ešte veľmi podobná
konštrukcia try ... except ... end - môžete
si ju naštudovať v Helpe
- jednoduché príklady na try
... finally ... end
takto slušne by sme mali pracovať so súborom:
|
...
AssignFile(t,'súbor.txt'); Rewrite(t); // alebo Reset(t);
try
...
// práca so súborom
...
finally
CloseFile(t);
end;
...
|
takto slušne by sme mali pracovať s bitmapami:
|
...
bmp:=TBitmap.Create;
try
...
// práca s bitmapou
...
finally
bmp.Free;
end;
...
|
zrkadlovo otočíme bitmapu aj s try
a finally:
|
procedure TForm1.Button2Click(Sender: TObject);
var
bmp2:TBitmap;
w,h,i,j:integer;
r1,r2:PRGBArray;
begin
w:=bmp1.Width; h:=bmp1.Height;
bmp2:=TBitmap.Create;
try
bmp2.PixelFormat:=pf24bit;
bmp2.Width:=w;
bmp2.Height:=h;
for i:=0 to h-1 do begin
r1:=bmp1.ScanLine[i];
r2:=bmp2.ScanLine[i];
for j:=0 to w-1 do
r2[j]:=r1[w-1-j];
end;
Image2.Canvas.Draw(0,0,bmp2);
finally
bmp2.Free;
end;
end;
|
symetricky podľa hlavnej uhlopriečky:
|
procedure TForm1.Button3Click(Sender: TObject);
var
bmp2:TBitmap;
w,h,i,j:integer;
r1,r2:PRGBArray;
begin
w:=bmp1.Width; h:=bmp1.Height;
bmp2:=TBitmap.Create;
try
bmp2.PixelFormat:=pf24bit;
bmp2.Width:=w;
bmp2.Height:=h;
for i:=0 to h-1 do begin
r1:=bmp1.ScanLine[i];
for j:=0 to w-1 do begin
r2:=bmp2.ScanLine[j];
r2[i]:=r1[j];
end;
end;
Image2.Canvas.Draw(0,0,bmp2);
finally
bmp2.Free;
end;
end;
|
cyklický posun celých riadkov o päť
(resp. viac) smerom hore:
|
procedure TForm1.Button4Click(Sender: TObject);
var
bmp2:TBitmap;
w,h,i:integer;
r1,r2:PRGBArray;
begin
w:=bmp1.Width; h:=bmp1.Height;
bmp2:=TBitmap.Create;
try
bmp2.PixelFormat:=pf24bit;
bmp2.Width:=w;
bmp2.Height:=h;
for i:=0 to h-1 do begin
r1:=bmp1.ScanLine[(i+5) mod h]; // alebo (i+h div 2) mod h
r2:=bmp2.ScanLine[i];
move(r1^,r2^,sizeof(TRGB)*w); // sizeof(TRGB) je 3
end;
Image2.Canvas.Draw(0,0,bmp2);
finally
bmp2.Free;
end;
end;
|
ak by sme chceli viackrát posúvať
(napr. v časovači) tú istú
bitmapu:
|
procedure TForm1.FormCreate(Sender: TObject);
begin
bmp1:=TBitmap.Create;
bmp1.LoadFromFile('parrots.bmp');
Image1.Canvas.Draw(0,0,bmp1);
Image2.Canvas.Draw(0,0,bmp1);
DoubleBuffered:=true;
end;
procedure TForm1.Timer1Timer(Sender: TObject);
var
bmp1,bmp2:TBitmap;
w,h,i:integer;
r1,r2:PRGBArray;
begin
bmp1:=TBitmap.Create;
try
w:=Image2.Width; h:=Image2.Height;
bmp1.PixelFormat:=pf24bit;
bmp1.Width:=w;
bmp1.Height:=h;
bmp1.Canvas.Draw(0,0,Image2.Picture.Graphic);
bmp2:=TBitmap.Create;
try
bmp2.PixelFormat:=pf24bit;
bmp2.Width:=w;
bmp2.Height:=h;
for i:=0 to h-1 do begin
r1:=bmp1.ScanLine[(i+5) mod h];
r2:=bmp2.ScanLine[i];
move(r1^,r2^,3*w);
end;
Image2.Canvas.Draw(0,0,bmp2);
finally
bmp2.Free;
end;
finally
bmp1.Free;
end;
end;
|
veľmi zaujímavý rozmazávací
efekt:
|
procedure TForm1.Button5Click(Sender: TObject);
var
bmp2:TBitmap;
X,Y,sX,sY:Integer;
d1,d2:PRGBArray;
begin
bmp2:=TBitmap.Create;
try
bmp2.PixelFormat:=pf24bit;
bmp2.Width:=256;
bmp2.Height:=256;
for Y:=0 to 255 do begin
d2:=bmp2.ScanLine[Y];
for X:=0 to 255 do begin
sX:=X+random(5)-2;
if sX<0 then sX:=-sX; if sX>255 then sX:=510-sX;
sY:=Y+random(5)-2;
if sY<0 then sY:=-sY; if sY>255 then sY:=510-sY;
d1:=bmp1.ScanLine[sY];
d2[X]:=d1[sX];
end;
end;
Image1.Canvas.Draw(0,0,bmp2);
finally
bmp2.Free;
end;
end;
|
prípadne efekt rozťahovania od stredu smerom
ku okrajom:
|
procedure TForm1.Button6Click(Sender: TObject);
var
bmp2:TBitmap;
X,Y,sX,sY:Integer;
d1,d2:PRGBArray;
begin
bmp2:=TBitmap.Create;
try
bmp2.PixelFormat:=pf24bit;
bmp2.Width:=256;
bmp2.Height:=256;
for Y:=0 to 255 do begin
d2:=bmp2.ScanLine[Y];
for X:=0 to 255 do begin
sX:=128+(X-128)*127 div 130;
sY:=128+(Y-128)*127 div 130;
d1:=bmp1.ScanLine[sY];
d2[x]:=d1[sX];
end;
end;
Image1.Canvas.Draw(0,0,bmp2);
finally
bmp2.Free;
end;
end;
|
posledný efekt vylepšíme: pridáme
časovač, pre ktorý Enabled=false a interval=50:
|
procedure TForm1.Timer1Timer(Sender: TObject);
var
bmp2:TBitmap;
X,Y,sX,sY:Integer;
d1,d2:PRGBArray;
begin
bmp2:=TBitmap.Create;
try
bmp2.PixelFormat:=pf24bit;
bmp2.Width:=256;
bmp2.Height:=256;
for Y:=0 to 255 do begin
d2:=bmp2.ScanLine[Y];
for X:=0 to 255 do begin
sX:=128+(X-128)*127 div (127+Random(5)); // skúste napr. 14
sY:=128+(Y-128)*127 div (127+Random(5)); // skúste napr. 14
d1:=bmp1.ScanLine[sY];
d2[x]:=d1[sX];
end;
end;
d2:=bmp2.ScanLine[128];
d2[128].R:=(Random(256)+d2[128].R) div 2;
d2[128].G:=(Random(256)+d2[128].G) div 2;
d2[128].B:=(Random(256)+d2[128].B) div 2;
bmp1.Free;
bmp1:=bmp2; bmp2:=nil;
Image1.Canvas.Draw(0,0,bmp1);
finally
bmp2.Free;
end;
end;
procedure TForm1.Button7Click(Sender: TObject);
begin
Timer1.Enabled:=not Timer1.Enabled;
end;
|
- od konštanty v Random závisí, ako rýchlo
sa bude útvar rozmazávať
- na záver do stredu bitmapy dávame "skoro
náhodne" zafarbenú bodku
NDÚ:
- premyslite, ako dorobiť všetky dnešné
programy tak, aby fungovali pre ľubovoľne veľké
bitmapy
- podobne ako rozmazávanie s časovačom zrealizujte
aj cyklické rolovanie s časovačom
- do formulára položte aj TrackBar, pomocou
ktorého budete meniť počet, o koľko riadkov sa
bude posúvať - t.j. rýchlosť posúvania
- zrealizujte čo najkrajšie otáčanie bitmapy
o ľubovoľný uhol
- rozmazávanie bitmapy: každý pixel sa
vypočíta ako priemer so svojimi susedmi (stačí
susedov v riadku)
|
© 2002 AB, KVI blaho@fmph.uniba.sk
|