A megoldáshoz a Bitmap közvetlen memóriabeli elérését fogjuk alkalmazni. Mivel a kép a memóriában úgy van tárolva, hogy a pixeleket leíró RGB értékek egymás után kapnak helyet, így ha kapunk egy mutatót, mely a kép első pixelére mutat, akkor egy ciklussal igen gyorsan végigmehetünk az összesen és módosíthatjuk azokat.
Mivel ez a fajta memória művelet nem biztonságos a .NET keretrendszer számára, így az ilyen műveleteket végző függvényeinket, osztályainkat az unsafe jelzővel kell ellátnunk.
public unsafe class ManipulateBitmap
Ahhoz, hogy unsafe-el jelölt függvényeket futtathassunk, szükséges ezt engedélyeznünk is a projektünkben, különben az „Unsafe code may only appear if compiling with /unsafe” hibaüzenetet kapjuk. Ennek beállításához válasszuk a Solution Explorer-ben az adott projektünket, majd jobb gomb, Properties menüpont. A megjelenő ablakban a Configuration Properties - Build - Allow unsafe code blocks értékét állítsuk igazra.
Mivel egy pixelhez három színérték tartozik, melyek egy-egy bájtot foglalnak el a memóriában, így ezek egyszerűbb kezeléséhez hozzunk létre egy új struktúrát.
public struct OnePixel
{
public byte blue;
public byte green;
public byte red;
}
Osztályunk konstruktorában kell majd megadnunk a kezelni kívánt képet Bitmap formában. Ezt most csak tároljuk egy belső változóba a későbbi felhasználás végett.
public ManipulateBitmap(Bitmap bmp)
{
bitmap = bmp;
}
A Start függvény meghívásával kezdődhet el a képfeldolgozás. Itt egy kettős ciklus fog futni, mely végigmegy a kép összes során és minden sor összes pixelén. A két ciklus végére minden pixelen végighaladtunk. A ciklusmagban egy eseményt helyezünk el. Ezt az eseményt felhasználva módosíthatjuk majd a pixeleket. Ehhez azonban kell készítenünk egy új eseményt, illetve az ehhez szükséges delegate-et. Az eseménykezelőnek szeretnénk átadni az x, y koordinátát, melyből megtudható lesz, hogy melyik pixelnél is tart a feldolgozás, valamint ennek a pixelnek a színeit RGB értékre bontva. A színeket tartalmazó byte típusú paraméter előtt használjuk a ref módosítót, így a megváltoztatott érték rögtön vissza is kerül a memóriába.
public delegate void PixelDelegate(int x, int y, ref byte red, ref byte green, ref byte blue);
public event PixelDelegate Pixel;
Nézzük mi is történik a Start függvényben. Mivel a kép manipulálása Pixel eseményen keresztül történik, így ha ehhez nincs eseménykezelő rendelve, akkor nem is érdemes a függvényünknek tovább futnia. Ez esetben a függvény null értékkel tér vissza.
public Bitmap Start()
{
if (Pixel!=null)
{
Következő lépés az lesz, hogy zároljuk a memóriaterületet, hogy más folyamat ne férjen hozzá a képhez, amíg a változtatásokat végezzük rajta.
BitmapData bd = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
A kép bal felső sarkában lévő első pixelt a Scan0 property-n keresztül érhetjük el.
OnePixel* pixel = (OnePixel*) bd.Scan0.ToPointer();
Most már adott egy mutató, mely a kép első pixelére mutat, így indíthatjuk a kettős ciklust, mely végigmegy a kép minden pixelén.
for (int y = 0; y < bitmap.Height; y++)
{
for (int x = 0; x < bitmap.Width; x++)
{
A ciklusmagban a Pixel nevű eseményt hívjuk meg, hogy az adott pixel esetleges változtatása elvégzésre kerüljön. Ezt követően a kép következő pixelére léptetjük a mutatónkat.
Pixel(x, y, ref pixel->red, ref pixel->green, ref pixel->blue);
pixel++;
}
}
A ciklusok végén feloldjuk a kép memória területének a zárolását és visszaadjuk a már módosított Bitmap-et.
bitmap.UnlockBits(bd);
return bitmap;
}
A ManipulateBitmap osztály felhasználása
A létrehozott osztályunk felhasználása a következő módon zajlik. Létrehozunk belőle egy új példányt, a konstruktorban megadunk egy Bitmap-et, majd hozzárendelünk egy eseménykezelőt a Pixel eseményhez és végül a Start függvénnyel elindítjuk a folyamatot.
ManipulateBitmap mb = new ManipulateBitmap(new Bitmap(pictureBox1.Image as Bitmap));
mb.Pixel+=new PixelDelegate(Pixel);
pictureBox2.Image = mb.Start();
A Pixel eseménynél tetszőleges módon megváltoztathatjuk az egyes pixelekhez tartozó RGB értékeket, ezzel befolyásolva a kép kinézetét.
Például egy színes képből úgy készíthetünk szürkeárnyalatos képet, hogy minden pixelénél kiszámítjuk a három színösszetevő átlagértékét, majd a kapott eredményt adjuk az színösszetevőknek.
public void Pixel(int x, int y, ref byte red, ref byte green, ref byte blue)
{
byte v = (byte)((red + green + blue) / 3);
red = v;
green = v;
blue = v;
}