Delphi - Képet forgató Image komponens készítése

forráskód letöltése
Készítünk egy komponenst, amely segítségével elfordított képeket helyezhetünk el Delphi alkalmazásunkban. A komponens működése hasonlít a TImage komponenséhez, azonban csak BMP formátumú képek használatára alkalmas. Lehetőséget biztosít arra, hogy a képeket 90, 180 vagy esetleg 270 fokkal elforgassuk szerkesztési és futási időben egyaránt.
A mellékelt példaprogram megnyitása előtt a RotateImage.pas-ban lévő komponenst telepítenie kell a Delphi alá. Ehhez válassza a Component - Install Component menüpontot.
A komponens használata
A Picture tulajdonságban adjunk meg egy BMP állományt. A komponens csak BMP formátumú képek használatára képes. A BMP képnek legalább 8 bitesnek kell lennie.
A forgatást a Rotation tulajdonság értékének módosításával lehet végrehajtani. A megadható tulajdonságok a forgatás fokát jelzik.
A megjelenő kép automatikusan felveszi a komponens méretét, még forgatás esetén is. A komponens megfelelő méretéről nekünk kell gondoskodnunk.
A komponens elkészítése
Komponensünket a TGraphicControl osztályból származtatjuk.
A forgatás beállításához hozzunk létre egy típust, amely a Rotation property típusa lesz.
TRotation=(r0,r90,r180,r270);
Ezzel azt érjük el, hogy az Object Inspector-ban a Rotation mellett megjelenik egy lista, amelyből az értékeket kiválaszthatjuk.
Az FPicture: TPicture típusú változóban tároljuk el a betöltött képet. Ennek tartalmát nem változtatjuk, a forgatáshoz és a kirajzoláshoz az FRPicture objektumot fogjuk használni. Így mindig megmarad az eredeti képünk is. Erre még szükségünk lesz.
A kép megrajzolásához felül kell írnunk a Paint metódust. Helyezzük el benne az alábbi utasítást.
StretchBlt(Canvas.Handle,0,0,Width,Height,FRPicture.Bitmap.Canvas.Handle,0,0,FRPicture.Width,FRPicture.Height,SRCCOPY);
A kép forgatását akkor kell végrehajtanunk, amikor a Rotation értéke módosul. Ezért a SetRotation metódusban meg kell hívnunk a Rotate eljárást, amely a forgatás vezérlését valósítja meg. A kép forgatása az óra járásával megegyező irányba történik, 90 fokonként.
Az FPicture tartalmazza az eredeti képet, azaz a 0 fokot. A 270 fokos állapot eléréséhez a forgatási algoritmust háromszor kell meghívni. A forgatás algoritmusát a Rotate90Degrees eljárás tartalmazza.
Amikor felhasználjuk a komponenst egy alkalmazásban, elmentjük az alkalmazást, majd újra megnyitjuk, akkor nekünk kell gondoskodnunk a kép korrekt megjelenítéséről. Be kell állítanunk a megfelelő forgatást.
A Loaded metódust használhatjuk arra, hogy inicializáljuk a komponenst. A Loaded metódusban nincs más dolgunk, mint meghívni a Rotate eljárást.
A probléma ott kezdődik, hogy a Rotation tulajdonsághoz tartozó SetRotation metódus hamarabb fut le, mint a Loaded. Ilyenkor még az FPicture és FRPicture objektumok nem töltötték be a képet, amelyet meg kell jeleníteniük. A SetRotation meghívja a Rotate eljárást, az pedig a Rotate90Degrees-t. Olyan képet akar a program méretezni, amely még nem is létezik. Ilyenkor jön az Access Violation ablak.
Ennek elkerülése érdekében vegyünk fel egy logikai változót, FLoaded néven. A Create metódusban állítsuk az értékét False-ra. A SetRotation metódus tartalmát ne engedjük lefutni csak akkor, ha a változó értéke igaz. A változónak igaz értéket a Loaded metódusban kell adnunk.
BMP forgatása 90 fokkal balra, a Rotate90Degrees eljárás
procedure Rotate90Degrees(var ABitmap: TBitmap);
Az eljárás meglehetősen összetett, rengeteg változót használ a számítások végrehajtásához. Az R betűre végződő változók a forgatott kép adatait tartalmazzák, míg az R betű nélküli párjuk az eredeti kép adatait.
bmpBuffer, bmpBufferR: PByte;
Az eljárásnak van egy további lokális eljárása, az IntegralByteRotate.
A BMP képet TMemoryStream típusú változókban tároljuk el. A kép formátuma meghatározza, hogy a kép struktúra szerkezetén belül hol található a fejléc rész, amely a kép feldolgozásához szükséges adatokat tartalmazza. Ebből a fejlécből nyerhetünk információkat arra vonatkozólag, hogy a képpontokat milyen struktúra alapján kell feldolgozni.
A BitmapOffset változóban eltároljuk azt az offset-et, amellyel a fejlécadatoktól eljuthatunk a képpontok adataihoz.
BitmapOffset:=PBitmapFileHeader(bmpBuffer)^.bfOffBits;
A fejléchez és az adatokhoz történő hozzáférés érdekében felhasználhatjuk a PBitmapInfoHeader és TBitmapFileHeader struktúrákat. A meghatározott fejlécet a PbmpInfoR változóban tároljuk el. A PBitmapInfoHeader egy pointer, melynek típusa a BitmapFileHeader API struktúra.
PbmpInfoR:=PBitmapInfoHeader(bmpBuffer);
Az alábbi utasítássorozattal hozzáférhetünk a BMP kép képpontjaihoz.
bmpBuffer:=MemoryStream.Memory;
Inc(bmpBuffer,BitmapOffset);
PbmpBuffer:=bmpBuffer;
A Rotate90Degrees eljárás csak 8 bites és a fölötti bitmap képeket dolgoz fel
A PbmpInfoR változó biBitCount mezője adja vissza azt, hogy a kép hány bites. A biWidth és biHeight mezők a képet felépítő képpontok oszlopainak és sorainak számát adják meg. Ezeket az adatokat felhasználjuk arra, hogy egy sor képpont adatainak méretét eltároljuk.
BytesPerScanLine:=(((biWidth*BitCount)+31) div 32)*SizeOf(DWORD));
BytesPerScanLineR:=((((biHeight*BitCount)+31) div 32)*SizeOf(DWORD));
Az eredeti képhez az oszlopok számát, míg a forgatott képhez a sorok számát jegyezzük fel.
A MemoryStreaR változó tárolja a forgatott képet.
MemoryStreamR:=TMemoryStream.Create;
A MemoryStream méretét be kell állítani.
MemoryStreamR.SetSize(BitmapOffset+BytesPerScanLineR * biWidth);
Miután az eredeti kép fejlécét átmásoltuk a MemoryStreamR változóba, meg kell határoznunk azt a képpont halmazt, amelyet forgatni szeretnénk. Ezt a PbmpBufferR változóban tároljuk el.
A képpontok felcserélését az IntegralByteRotate eljárás végzi el.
Az IntegralByteRotate eljárás
A PbmpBufferR változót ráállítjuk az utolsó sor első pixelére.
Inc(PbmpBufferR,BytesPerScanLineR*(PbmpInfoR^.biWidth-1));
Két egymásba ágyazott for ciklus segítségével az eredeti képet (PbmpBuffer) képpontonként átmásoljuk a forgatott képre (PbmpBufferR).
Ha az IntegralByteRotate eljárás lefutott, akkor hozzá kell férnünk a forgatott kép fejlécéhez.
PbmpBufferR:=MemoryStreamR.Memory;
Inc(PbmpBufferR,SizeOf(TBitmapFileHeader));
PbmpInfoR:=PBitmapInfoHeader(PbmpBufferR);
A PbmpInfoR struktúrában fel kell cserélnünk a képpont oszlopok és sorok számát, majd a MemoryStreamR tartalmát át kell másolni az ABitmap változóba.