Delphi - TBarnEffect komponens

Bitmap effektek 1. rész

forráskód letöltése

Most induló több részes cikksorozatunkban különböző képváltási effektusokat állítunk elő, mindegyikhez létrehozva egy-egy komponenst. A komponenseket egy közös osztályból fogjuk származtatni, ami elvégzi majd a legalapvetőbb műveleteket, így az újabb effektek létrehozásánál csak magával az effekt programozásával kell foglalkoznunk. A képváltást külön szálon oldjuk meg, így a program futása addig sem szakad meg, amíg az animáció tart. A mellékelt példaprogram megnyitása előtt a BaseEffect.pas-ban és a BarnEffect.pas-ban lévő komponenseket telepítenie kell a Delphi alá.

A TBaseEffect osztály lesz az, amire a többi komponenst építeni fogjuk. Kialakításánál a fő szempont az volt, hogy minden ebből származtatott komponensnél csak a képváltáshoz szükséges animáció programozása legyen szükséges, az összes többi ezzel kapcsolatos műveletet a TBaseEffect osztály végezze el.
Mivel az animáció külön szálon történik, ezért létre kellett hozni egy TThread osztályból származó új osztályt is, ami az animáció kirajzolását, és egyéb műveleteit végzi el.

Nézzük meg, hogy milyen property-kre van szükség a TBaseOsztályban:
  • Active: igaz érték esetén az animáció folyamatban van, míg hamis érték esetén áll;
  • Image1, Image2: a két kép, amik között az animációt el szeretnénk végezni. Az OnFinished és CurrentImage property-k használatával akár több képből álló sorozatot is készíthetünk. Részletesen az OnFinished property ismertetésénél foglalkozunk ezzel. Az animáció csak akkor indítható, ha mindkét property-be töltöttünk be képet;
  • Continuous: igaz érték esetén az animáció folyamatos, tehát folyamatosan vált a két kép. Hamis érték esetén a képváltás után megáll;
  • Speed: az animáció sebességét szabályozhatjuk vele. Az animáció típusától függően ugyanazon érték más és más sebességet jelent, mivel egyes animációk megrajzolásához kevesebb, vagy éppen több idő szükséges. A Speed property azt mondja meg, hogy az animáció egyes lépései között mennyit kell várni (milliszekundum);
  • CurrentImage: csak olvasható property, amiből megtudhatjuk, hogy melyik az aktív (felül látható) kép. A 0 érték az 1-es, az 1 pedig a 2-es képet jelenti. Más értéket nem ad vissza;
  • OnStart: az esemény akkor jön létre, amikor az Active property igaz értéket kap, vagyis elindul az animáció;
  • OnStop: akkor jön létre, amikor ténylegesen megáll az animáció, vagyis az Active property hamis értéket kap. Amikor az animáció folyamatos (a Continuous property értéke igaz), akkor a képváltási animáció végén nem jön létre ez az esemény;
  • OnFinished: az esemény akkor jön létre, amikor az animáció lefutott. Ha nem csak kettő, hanem több képet is szeretnénk egymás után megjeleníteni, akkor azt ennél az eseménynél megtehetjük. A CurrentImage property-ből lekérdezhetjük, hogy melyik az éppen látható kép, és ettől függően az Image1 vagy Image2 property-kbe betölthetjük a következő képet.
A TBaseEffect osztályban szükség van egy TBaseThread típusú objektumra. Ez fogja vezérelni a tényleges animációt, valamint az aktuális képkockát is ez állítja elő az ebből az osztályból származtatott osztályokban. Mivel ennek a kis osztálynak nagyon nagy szerep jut, ezért ezt vizsgáljuk meg először részletesebben is.
Az osztály tartalmaz egy FBitmap nevű TBitmap típusú objektumot, ami az animáció aktuális képkockáját fogja tartalmazni. Az FEffect objektum az a komponens lesz, amiben ezt az osztályt felhasználjuk. Erre azért van szükség, hogy ezen keresztül elérhessük majd a komponenshez, vagyis az effekthez tartozó property-ket.
Az osztálynak van egy Paint eljárása, ami csupán két sort tartalmaz:
  PrepareBitmap;
  FEffect.Paint;
A PrepareBitmap egy saját belső eljárás, ami az aktuális képkockát készíti el. Az alap osztályban ez csupán egy dolgot csinál, csökkenti az aktuális lépésszámot (FStep), és ha végzett az animációval, akkor a Continuous property értékétől függően megállítja, vagy folytatja azt a következő képpel, de előtte generál egy OnFinished eseményt. A TBaseThread-ból származtatott osztályokban ezt az egy eljárást kell felülírni.
Ha elkészült a képkocka, akkor meghívja az effekt komponens Paint metódusát, ami az FBitmap tartalmát (amiben a képkockát létrehoztuk) kirajzolja a komponens területére.
Mivel az animációt külön szálon szeretnénk futtatni, ezért a Thread osztály Execute metódusát is felül kell írnunk. Ebben ellenőrizzük, hogy nem állították-e le a szálat (Terminated), és ha nem, akkor meghívjuk a Paint eljárást. A Synchronize eljárásra azért van szükség, hogy azok a szálak, amelyek ugyanahhoz a VCL komponenshez akarnak hozzáférni, egy időben ezt ne tehessék meg, mivel ez nagyon kockázatos lenne.
Ebben az eljárásban használjuk fel a TBaseEffect osztály Speed property-jét. A Sleep eljárás meghívásával a Speed property-ben milliszekundumban megadott ideig várakozunk.
procedure TBaseThread.Execute;
begin
  while not Terminated do begin
    Synchronize(Paint);
    if FEffect.Speed>0 then Sleep(FEffect.Speed);
  end;
end;

A TBaseThread osztályról ennyit, most nézzük a TBaseEffect osztályt részletesen. A protected részben deklaráltunk négy változót. Az FStep az animáció aktuális lépésének számát tartalmazza. Ez mindig felülről lefelé halad, tehát ha az animációhoz 50 lépésre van szükség, akkor az első képkockánál 50, majd 49, 48, stb. lesz az értéke, egészen 0-ig. Az FImage változó az aktuális (megjelenítendő) kép sorszámát tartalmazza. Ez 0 az Image1, és 1 az Image2 esetében. A CurrentImage property ennek értékét adja vissza. Az FThread egy TBaseThread objektum, amiről fent már beszéltünk. Az FTempImg egy TBitmap objektum, amibe az animáció kezdetekor betöltjük az aktuális képet (Image1 vagy Image2). Ettől kezdve ezen módosításokat hajthatunk végre, majd az egész képet átmásoljuk az FBitmap objektumba. Néhány esetben nincs feltétlenül szükség a használatára, de bizonyos esetekben nagyon jól jön.
A SetTempImage eljárás az Image1 vagy Image2 property-ben tárolt képet átmásolja az FTempImage objektumba. Hogy melyiket, azt a paraméterként megadott érték határozza meg (0 vagy 1). Ennek az eljárásnak néhány animációnál nagy hasznát vesszük.
Az Initialize eljárásnak nagyon nagy jelentősége van. Ez állítja be az FThread objektumban az FBitmap kép szélességét és magasságát, valamint módosítja az aktuális kép sorszámát (FImage) és meghívja a SetTempImage eljárást. Minden új effekt komponensnél, ami a TBaseEffect osztályból származik, ezt az eljárást felül kell írnunk, mivel ebben kell beállítani az animációhoz szükséges lépések számát is.
procedure TBaseEffect.Initialize;
begin
  with FThread.FBitmap do begin
    Width:=self.Width;
    Height:=self.Height;
  end;
  FImage:=1-FImage;
  SetTempImage(FImage);
end;
Az új kép egy részletének, vagy akár a teljes képnek a megrajzolásához három eljárást is felhasználhatunk. Mindegyik azt a feladatot látja el, hogy az FTempImage objektumban tárolt képet átmásolja az FBitmap objektumba. Fent már említettük, hogy ez jelenik meg a komponens képeként. A három eljárás a következő:
  • CopyImg: Az FTempImage kép x, y koordinátájától w szélességű és h magasságú téglalap alakú részletet átmásol az FBitmap x,y koordinátájába. Az eljárásban a BitBlt függvényt használjuk fel.
  • CopyStretch: Az FTempImage képet teljes egészében átmásolja az FBitmap kép x,y koordinátájába w szélességben és h magasságban. Ha a w és h paraméterek értékei különbözőek a kép szélességétől illetve magasságától, akkor a kép ennek megfelelően lesz nyújtva, vagy összenyomva. A másoláshoz a StretchBlt függvényt használjuk fel;
  • CopyAlpha: Annyiban különbözik a CopyImg eljárástól, hogy itt megadhatunk egy Alpha paramétert, ami a másolandó képrészlet átlátszóságát határozza meg. A másoláshoz az AlphaBlend függvényt használjuk fel.
  • CopyMask: Néhány speciális effektnél nagyon jól használható eljárás, ahol a másolandó képrészlet nem téglalap, hanem tetszőleges alakú lehet. A két képet úgy másolja egymásra, hogy egy maszk bitképet is figyelembe vesz. Az „előtér” kép csak azon pixeleit másolja át a „háttér” képre, ahol a maszk bitkép ugyanazon pixelének színe fehér. A maszk bitképnek monokrómnak kell lennie (Monochrome=true). Az eljárás három paramétere a következő: BackHdc (a háttérkép Canvas-ának azonosítója), ForeHdc (az előtér kép Canvas-ának azonosítója), MaskHdc (a maszk bitkép Canvas-ának azonosítója).

Az animáció indítása, illetve leállítása az Active property értékének beállításával lehetséges. Az osztálynak van ugyan egy Start és egy Stop eljárása, de ezek is csak az Active property értékét állítják be. Nézzük meg, hogy milyen feladatokat végzünk el az animáció indításakor, illetve leállításakor:
  FActive := Value and not (Image1.Empty or Image2.Empty);
Az animáció csak akkor indulhat el, ha az Image1 és Image2 property-kbe is van kép betöltve, ezért ezt rögtön az értékadásnál felhasználjuk feltételként. Ha ez a feltétel igaz, akkor még azt is ellenőriznünk kell, hogy az FThread objektum létre van-e hozva. Ezt a TBaseEffect osztály nem hozza létre, csak az ebből származtatott komponensek.
  if Assigned(FThread) then begin
Ha a property értéke igaz, akkor generálunk egy OnStart eseményt, majd meghívjuk az Initialize eljárást, amit fent már ismertettünk. Ezután ellenőrizzük, hogy a szál Suspended üzemmódban van-e, vagyis áll-e, mivel csak ebben az esetben indítjuk el a Resume metódussal.
    if FActive then begin
      if Assigned(FOnStart) then FOnStart(self);
      Initialize;
      with FThread do begin
        if Suspended then Resume;
      end;
    end else begin
Ha a property értéke hamis, akkor ellenőrizzük, hogy nem áll-e, mivel ha áll, akkor már nem kell leállítani. Ha fut, akkor a Suspend metódussal leállítjuk. Az Invalidate eljárás meghívásával újrarajzoltatjuk a komponenst, ami akkor is kirajzolja a teljes képet, ha az animáció közben lett leállítva. Ha leállítottuk az animációt, akkor generálunk egy OnStop eseményt.
      if not FThread.Suspended then FThread.Suspend;
      Invalidate;
      if Assigned(FOnStop) then FOnStop(self);
    end;
  end;
end;

A komponens kirajzolása a Paint eljárásban történik. Ha az Active property értéke hamis, akkor az FTempImage objektumba átmásoljuk az aktuális képet, majd meghívjuk a CopyImg eljárást. A Paint eljárás végén az FBitmap képet átmásoljuk a komponens Canvas-ára.

Ideális esetben, amikor egy új effekt komponenst készítünk, csak a következő lépéseket kell végrehajtani:
  • Létre kell hozni egy új osztályt a TBaseThread osztályból származtatva, amiben felül kell írni a PrepareBitmap eljárást. Ebben az eljárásban az FStep változó értékétől függően meg kell rajzolni az aktuális képkockát az FTempImage objektumban, és utána átmásolni annak megfelelő részletét az FBitmap objektumra, vagy egyből az FBitmap objektumban kell létrehozni a képet.
  • Az új komponensnek kell egy Create konstruktor, amiben létrehozzuk a TBaseThread osztályból származtatott objektumot, valamint beállíthatjuk a property-k alapértelmezett értékeit.
  • Felül kell írnunk az Initialize eljárást, amiben a legegyszerűbb esetben csak a lépések számát kell beállítanunk (FStep), néhány esetben viszont egyéb előkészítő műveleteket is el kell végeznünk.


Bitmap effektek cikksorozat