A megvalósításhoz szükségünk lesz mutatók használatára, melyre a C# nyelvben csak úgy van lehetőségünk, hogy ha külön engedélyezzük a nem biztonságos kód blokkok (unsafe) készítését. Ehhez válasszuk a View - Solution Explorer menüpontot, majd itt az aktuális projekten kattintsunk jobb gombbal és válasszuk a Properties menüpontot. A megjelenő ablakból Configuration properties - Build elemet kijelölve a jobb oldali listából az Allow unsafe code blocks elemet állítsuk igazra. Ezt követően már használhatjuk a forráskódban az unsafe kulcsszót.
A kép jelenleg egy PictureBox-ban áll rendelkezésre.
Ez alapján készítünk egy Bitmap-et, melybe lemásoljuk a képet.
private void Form1_Paint(object sender, System.Windows.Forms.PaintEventArgs e)
{
Bitmap bmp = new Bitmap(pictureBox1.Image);
Ahhoz, hogy a képet közvetlenül elérhessük annak egészét, vagy egy részét zárolnunk kell, hogy más folyamat ne férhessen hozzá. Ehhez a Bitmap osztály LockBits függvényét kell meghívnunk. Első paraméterként azt a területet kell megadnunk Rectangle típusban, melyet kezelni szeretnénk. Ez a terület kerül zárolásra. Második property-ben azt határozhatjuk meg, hogy milyen módon szeretnénk hozzáférni. Ehhez az ImageLockMode felsorolt típus elemei közül választhatunk. Végül a PixelFormat szintén felsorolt típus elemeiből választhatunk egyet, melyben a zárolt terület pixeleinek típusát adja meg. A Format32bppArgb választásával a kép minden pixeléhez 32 bit tartozik, melyben 8-8 bit fogja tárolni az átlátszóság mértékét, a piros, a zöld és a kék színösszetevő értékét.
Visszatérési értékként kapunk egy BitmapData osztályt.
BitmapData bd = bmp.LockBits(new Rectangle(0, 120, 256, 50), ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb);
Készítünk egy belső Draw függvényt, mely elvégzi a szükséges manipulációt a képen. Ehhez két paraméter átadására lesz szükségünk: az első az a mutató, mely a zárolt terület első sorának első pixelére mutat. Ezt a Scan0 property adja IntPtr típusban. A második paramétert a BitmapData Stride property adja. Erre részletesebben a Draw függvény tárgyalásánál térünk ki.
Draw(bd.Scan0, bd.Stride);
A kirajzolás végeztével a zárolt területet fel kell oldanunk. Ezt az UnlockBits függvény teszi meg. Paraméterként a LockBits által visszaadott BitmapData osztályt kell megadnunk.
Végső lépésként már csak meg kell jelenítenünk a Form-on a kész bitmap-et.
e.Graphics.DrawImage(bmp, 330, 20);
}
Nézzük most a Draw függvény elkészítését. Mivel ebben mutatókat vagyunk kénytelenek használni, így a függvény deklarációja előtt használnunk kell az unsafe módosító kulcsszót.
unsafe private void Draw(IntPtr ip, int Stride)
{
A kapott IntPtr paramétert egy int-re mutató pointerre konvertáljuk. Valójában uint típust kellene használnunk, hiszen a 32 biten tárolt érték így értelmezendő, viszont ez esetben nem tudnánk használni a Color osztály függvényeit, melyekre szükségünk lesz és amelyek int típust várnak. Mivel a feldolgozás szempontjából lényegtelen, hogy int vagy uint típust használunk, így nyugodtan választhatjuk az int-et ez esetben.
int* Pixels = (int*)ip;
...
Most szükségünk lesz két egymásba ágyazott ciklusra, mely végigmegy a szükséges terület minden egyes pixelén.
for (int y=0; y<50; y++)
{
for (byte x=0; x<255; x++)
{
Ha e terület első illetve utolsó soránál tartunk, akkor egyszerűen húzunk egy fehér vonalat, melyet természetesen pixelenként rajzolunk ki.
if ((y==0) || (y==49))
{
c = Color.White;
A Pixels pointeren keresztül úgy érhetünk el egy x, y koordinátán lévő pixelt, hogy az x koordinátához hozzáadjuk a sorok számnak szorzatát a kapott Stride paraméter negyedével. Ennek oka a következő: a zárolt képterület pixelei a memóriában egymás után helyezkednek el. A kép terület bal felső pixele lesz a memória terület első 32 bitje. Ezután az első sor pixeleihez tartozó 32 bites egységek folyamatosan következnek ezen a memória területen. A kép második sorának pixeleihez tartozó 32 bitek pedig ezt követően, szintén folyamatosan kapnak helyet a memóriában. Ez így folytatódik az összes képsorra nézve. Így a két dimenziós kép a memóriában egy dimenzióban kerül tárolásra, ezért a Pixels „tömböt” is egy dimenziósként kezeljük és a megadott művelet alapján számítjuk ki, hogy az adott x, y koordináta melyik pozíció a memóriában. A Stride paraméterben pont azt kapjuk, hogy a kép egy sorához hány bájt szükséges a memóriában. Ez az információ nélkülözhetetlen a számítás elvégzéséhez. A néggyel való osztásra pedig azért van szükség, mert nekünk 32 bitenként kell „ugrálnunk”, hiszen ezt a PixelFormat-ot választottuk. Így ha például a Stride értéke 20, akkor tudhatjuk, hogy 5 pixel van egy sorban, vagyis 5 db 32 bites értéket találunk a memóriában.
A pixelnek így már egy tetszőleges szín értékét átadhatjuk. Ehhez egy 32 bites szám átadására van szükség, melyet a Color osztály ToArgb függvénye szolgáltat.
Pixels[x + y * Stride/4] = c.ToArgb();
}
else
Ha nem az első, vagy utolsó sornál tartunk a képben, akkor a fehér vonal húzása helyett egy érdekes effektet készítünk. Ez változatlanul hagyja az adott pixel eredeti színét, de az Alpha csatornát az X ciklusváltozó függvényében állítja, vagyis a kép fokozatosan áttetsző lesz a háttérszínnel.
{
u = Pixels[x + y * Stride/4];
c = Color.FromArgb(u);
c = Color.FromArgb(x, c.R, c.G, c.B);
Pixels[x + y * Stride/4] = c.ToArgb();