Bitmapa o bezpośrednim dostępie do pikseli w C#Kiedy w C# chcemy zmodyfikować zawartość bitmapy, możemy zrobić to na kilka sposobów. Pierwszym jest użycie metod GetPixel i SetPixel. Niestety ich wydajność pozostawia wiele do życzenia.
W większości tutoriali opisujących przetwarzanie bitmap, używa się metody Lock, aby dostać się do danych pikseli. Niestety metoda ta narzuca użycie unsafe w naszym kodzie, a poza tym brzydko wygląda.
Okazuje się, że jest jeszcze trzecia metoda, która jest rzadko opisywana -- stworzyć bitmapę na wcześniej przydzielonym obszarze pamięci. Dzięki temu mamy od razu dostęp do jej danych, bez potrzeby wcześniejszego jej blokowania. Taką bitmapę można używać normalnie do rysowania, można też utworzyć powiązany z nią Graphics, żeby po niej rysować. Ważne jest, żebyśmy zablokowali pamięć, której będzie używała taka bitmapa, żeby Garbage Collector jej nie przemieścił.
W dalszej części znajduje się przykładowy kod, który robi prostą animację na takiej bitmapie o bezpośrednim dostępie.

using System;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using System.Drawing;
using System.Drawing.Imaging;
namespace DirectBitmap
{
public class TestForm : Form
{
static void Main()
{
Application.Run(new TestForm());
}
Timer timer;
DirectBitmap directBitmap;
int nFrame = 0;
public TestForm()
{
Text = "Direct bitmap test";
ClientSize = new Size(256, 256);
DoubleBuffered = true;
Paint += new PaintEventHandler(TestForm_Paint);
FormClosed += new FormClosedEventHandler(TestForm_FormClosed);
timer = new Timer();
timer.Interval = 1000 / 30;
timer.Enabled = true;
timer.Tick += new EventHandler(timer_Tick);
directBitmap = new DirectBitmap(256, 256);
}
void TestForm_FormClosed(object sender, FormClosedEventArgs e)
{
// dobrze jest po sobie sprzątnąć, nie wiadomo kiedy GC to zrobi
directBitmap.Dispose();
}
void Animate()
{
double X, Y;
int r = (int)(192 + Math.Sin(nFrame / 3.0) * 64);
int g = (int)(192 + Math.Sin(nFrame / 5.0) * 64);
int b = (int)(192 + Math.Sin(nFrame / 7.0) * 64);
for (int y = 0; y < 256; y++)
for (int x = 0; x < 256; x++)
{
X = Math.Sin((x + Math.Sin((y + nFrame) / 16.0) * 8) / 8);
Y = Math.Cos((y + Math.Cos((x + nFrame) / 16.0) * 8) / 8);
double s = (X + Y + 2) * 0.25;
directBitmap[x, y, 0] = (byte)(s * r);
directBitmap[x, y, 1] = (byte)(s * g);
directBitmap[x, y, 2] = (byte)(s * b);
}
}
void timer_Tick(object sender, EventArgs e)
{
Animate();
Invalidate();
nFrame++;
}
void TestForm_Paint(object sender, PaintEventArgs e)
{
e.Graphics.DrawImage(directBitmap.Bitmap, 0, 0);
}
}
class DirectBitmap : IDisposable
{
byte[] data;
GCHandle dataHandle;
Bitmap bitmap;
int width;
int height;
public DirectBitmap(int width, int height)
{
data = new byte[width * height * 3];
dataHandle = GCHandle.Alloc(data, GCHandleType.Pinned);
// rozmiar wiersza bitmapy w bajtach
int stride = width * 3;
bitmap = new Bitmap(width, height, stride,
PixelFormat.Format24bppRgb, dataHandle.AddrOfPinnedObject());
this.width = width;
this.height = height;
}
void FreeHandle()
{
if (data == null) return;
dataHandle.Free();
data = null;
}
~DirectBitmap()
{
FreeHandle();
}
public void Dispose()
{
GC.SuppressFinalize(this);
FreeHandle();
}
public byte this[int x, int y, int component]
{
get
{
int index = (x + y * width) * 3 + component;
return data[index];
}
set
{
int index = (x + y * width) * 3 + component;
data[index] = value;
}
}
public Bitmap Bitmap
{
get
{
return bitmap;
}
}
public byte[] Data
{
get
{
return data;
}
}
}
}
Pointers and fixed size buffers may only be used in an unsafe context
stride = width * 3;
if (stride % 4 != 0) stride += 4 - (stride % 4);
data = new byte[stride * height];
index = 3 * x + y * stride + component;