arckép

Neuwirth István

programtervező informatikus MSc – ELTE

Computational and Software Techniques MSc – Cranfield, UK

Elérhetőség
pitta2@gmail.com
+36 30 329 3039

Valid XHTML 1.0 Transitional

Valid CSS!

Hatékony képmanipuláció .NET-ben

Hogyan dolgozhatunk fel képeket menedzselt kód alatt is hatékonyan?

A Visual Studio-val számos nyelven készíthetünk gyorsan programokat. Segítséget nyújt ebben a keretrendszer megannyi osztálya, az IntelliSense, a kiterjedt terméktámogatás. A keretrendszer több feladatot is levesz a programozó válláról – sok minden automatizálttá vált, könnyebb a memóriamenedzsment, sorra jelennek meg az új technológiák (Silverlight, LINQ, …). Az így kapott menedzselt kód típusbiztonságos, nem fordulhat elő, hogy egy mutatóval átcímzünk egy nem elérhető memóriaterületre (és kapunk egy nehezen javítható Segmentation Fault-ot). Ám a sok ellenőrzésnek, biztosításnak teljesítménybeli hátránya van. Egy egyszerűbb felhasználói programban ez valószínűleg nem fog kiütközni, de egy különösen sebességkritikus programnál problémát jelenthet egy natív C++ és a menedzselt C# kódú program közti sebességkülönbség. Tipikusan ilyen programok a nagy bináris adathalmazt használók (például videókonverziós, képszerkesztő alkalmazások).

Ezekre az esetekre fenntartja a C# nyelv a lehetőséget nem-menedzselt (unsafe) kód írására. A .NET-ben C++ nyelvet használva ez továbbra is biztosított, Visual Basic alatt pedig a Marshal osztály (System.Runtime.InteropServices névtér) segítségével „babrálhatunk” a memóriával. Egyéb esetben – ha a feladat nem kívánja meg – azonban maradjunk a biztonságos kódnál!

unsafe code settingsEgy egyszerű példán láthatjuk a különbséget: a feladat egy színes kép (24 bites színmélységű) inverzének előállítása. Az első esetben felügyelt kódot írunk, a System.Drawing névtér osztályainak és a pixelmanipulációra beépített függvények használatával (SetPixel(…), GetPixel(…)). A második eset már érdekesebb. Először is egy unsafe kulcsszóval jelezzük, hogy pointer-aritmetikát szeretnénk használni (ezt vagy az egész függvényre, vagy egy unsafe { … } blokkra érvényesíthetjük). Ekkor még a fordító azonban hibát jelez, a project beállításainál külön nyilatkoznunk kell róla, hogy valóban szeretnénk unsafe kódot használni.

A LockBits(…) függvénnyel zároljuk a bitképet, hogy más folyamat ne férhessen hozzá, elvégezzük a konverziót, majd feloldjuk a memóriaterületet. (A Scan0 tulajdonság a kép legelső pixelére mutató pointert adja meg, a Stride pedig egy scanline hosszát, ami nem mindig egyezik a kép szélességével.) A kép kirajzolását a Graphics osztály DrawImage(…) függvényével tehetjük meg. Természetesen ez utóbbi mód nagyságrendekkel gyorsabb képmanipulációt biztosít.

Példakód:

Bitmap bm = ...

private void invertEffectSafe()
{ for (int i = 0; i < bm.Height; i++)
{
for (int j = 0; j < bm.Width; j++)
{
Color c = bm.GetPixel(j, i);
bm.SetPixel(j, i, Color.FromArgb(255 – c.R, 255 - c.G, 255 – c.B));
}
}
}

kód helyett unsafe módban:

private unsafe void invertEffectUnsafe()
{
BitmapData bd = bm.LockBits(new Rectangle(0, 0, bm.Width, bm.Height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
byte* p = (byte*)bd.Scan0;
int offset = bd.Stride – bm.Width * 3;
for (int i = 0; i < h; ++i)
{
for (int j = 0; j < w * 3; ++j)
{
p[0] = (byte)(255 - p[0]);
++p;
}
p += offset;
}
bm.UnlockBits(bd);
}