Eine (weitere) Bitmap-Klasse zur schnellen Manipulation von Bildern.
Einleitung:
Seit längerem arbeite ich unter C++ im Bereich Bildverarbeitung. Dass da meistens alles "unsafe" abläuft, wird wahrscheinlich den meisten klar sein.
Dass sich aber andererseite managed/safe/C#-Code und Geschwindigkeit nicht immer ausschließt, hat bereits GetPixel und SetPixel um Längen geschlagen. 800 mal schneller gezeigt.
Und da ich momentan versuche vieles auf das viel schönere C# umzustellen, stell ich hier mal einen Teil - genauer gesagt die MemBitmap-Klasse online.
Da die herangehensweise eine ganz andere ist als bei der RoBitmap/FastBitmap-Klasse, ist dies mehr als spezielle Ergänzung zu sehen. Zumal meine Herangehensweise auch nicht mit dem CompactFramework funktioniert.
Implementierung:
Soweit so gut: Nun zu den Details:
Der "Trick" ist ein spezieller Konstruktor von Bitmap, der Bereits einen Speicherbereich übergeben bekommt.
Und was bringt uns das?
- Zugriff auf die Bitmap-Bits über byte-Array (kein unsafe)
- Erzeugung eines "echten" Bitmaps, dass die selben Bits verwendet
- schneller Zugriff über Array-Index, Klass-Indexer, Get/SetPixel
- Natives Laden von bmp-Dateien
"schnell" bedeutet oben, dass (dank dem guten CLR-JITter) der Zugriff über das Array genauso schnell ist, wie ein unsafe-Code mit Zeigern.
Meine groben Messungen ergaben:
(GDI+)-GetPixel: 1x
MemBitmap.GetPixel: 30x
MemBitmap[x,y,c]-Indexer: 100x
MemBitmap.Data[index]: 1000x
(Also für jeden was dabei )
Zukunft:
Die Unterstützung für die anderen PixelFormate (8BitIndexed, ...) sollen noch folgen.
(Momentan geht ja eigentlich nur 24Bit richtig)
sowie die native Unterstützung von GIF,PNG und JPEG-Dateien (dann auch JPEG2000). Und natürlich eine Speicherfunktion.
Insgesamt soll es also auch möglich sein, Bilder zu laden, zu manipulieren und wieder abzuspeichern ohne Überhaupt ein System.Drawing.Bitmap zu verwenden. (was Overhead bedeuten würde)
Code:
public class MemBitmap : IDisposable
{
#region private Fields
private int height;
private int width;
private int channels;
private System.Drawing.Imaging.PixelFormat pixelformat;
private byte[] data;
private int stride;
private System.Runtime.InteropServices.GCHandle dataHandle;
private System.Drawing.Bitmap bitmap;
#endregion
#region public Properties
public int Width { get { return width; } }
public int Height { get { return height; } }
public int Channels { get { return channels; } }
public System.Drawing.Imaging.PixelFormat PixelFormat { get { return pixelformat; } }
public byte[] Data { get { return data; } }
public IntPtr DataPtr { get { return dataHandle.AddrOfPinnedObject(); } }
#endregion
#region Bitmap Compatibility
public System.Drawing.Bitmap Bitmap
{
get
{
createBitmap();
return bitmap;
}
}
private void createBitmap()
{
if (bitmap == null)
{
//Bitmap erzeugen, die auf die selben Bit-Daten verweist
bitmap = new System.Drawing.Bitmap(width, height, stride, pixelformat, dataHandle.AddrOfPinnedObject());
}
}
#endregion
#region Pixel Access
public byte this[int x, int y, int channel]
{
get { return data[y * stride + x * channels + channel]; }
set { data[y * stride + x * channels + channel] = value; }
}
public System.Drawing.Color GetPixel(int x, int y)
{
int index = y*stride + x*channels;
switch(pixelformat)
{
case System.Drawing.Imaging.PixelFormat.Format24bppRgb:
return System.Drawing.Color.FromArgb(data[index + 2], data[index + 1], data[index + 0]);
default:
throw new NotSupportedException();
}
}
public void SetPixel(int x, int y, System.Drawing.Color value)
{
int index = y * stride + x * channels;
switch (pixelformat)
{
case System.Drawing.Imaging.PixelFormat.Format24bppRgb:
data[index + 0] = value.B;
data[index + 1] = value.G;
data[index + 2] = value.R;
break;
default:
throw new NotSupportedException();
}
}
#endregion
#region Initialization
private void loadBitmap(string fileName)
{
System.IO.FileStream fs = null;
try
{
//Dateistrom öffnen
fs = new System.IO.FileStream(fileName, System.IO.FileMode.Open);
//Zwischenspeicher anlegen
byte[] tempMem = new byte[4];
//Dateiheader einlesen
uint fileLength = 0;
uint imageDataOffset = 0;
{
//ersten beiden zeichen("BM")
fs.Read(tempMem, 0, 2);
if (tempMem[0] != 'B' || tempMem[1] != 'M')
throw new NotSupportedException("Unsupported Imagetype!");
//Dateilänge
fs.Read(tempMem, 0, 4);
fileLength = BitConverter.ToUInt32(tempMem, 0);
//reservierte Bits
fs.Read(tempMem, 0, 4);
//Offset der Bilddaten
fs.Read(tempMem, 0, 4);
imageDataOffset = BitConverter.ToUInt32(tempMem, 0);
}
//Bitmapheader einlesen
uint biHeaderSize = 0;
int biWidth = 0;
int biHeight = 0;
ushort biPlanes = 0;
ushort biBitCount = 0;
uint biCompression = 0;
uint biSizeImage = 0;
int biXPelsPerMeter = 0;
int biYPelsPerMeter = 0;
uint biClrUsed = 0;
uint biClrImportant = 0;
{
//Größe des BitmapHeaders
fs.Read(tempMem, 0, 4);
biHeaderSize = BitConverter.ToUInt32(tempMem, 0);
//Breite
fs.Read(tempMem, 0, 4);
biWidth = BitConverter.ToInt32(tempMem, 0);
//Höhe
fs.Read(tempMem, 0, 4);
biHeight = BitConverter.ToInt32(tempMem, 0);
//Ebenen
fs.Read(tempMem, 0, 2);
biPlanes = BitConverter.ToUInt16(tempMem, 0);
//BitsPerPixel
fs.Read(tempMem, 0, 2);
biBitCount = BitConverter.ToUInt16(tempMem, 0);
//Kompression
fs.Read(tempMem, 0, 4);
biCompression = BitConverter.ToUInt32(tempMem, 0);
//Byte-größe der Bilddaten
fs.Read(tempMem, 0, 4);
biSizeImage = BitConverter.ToUInt32(tempMem, 0);
//Horizontale Auflösung
fs.Read(tempMem, 0, 4);
biXPelsPerMeter = BitConverter.ToInt32(tempMem, 0);
//Vertikale Auflösung
fs.Read(tempMem, 0, 4);
biYPelsPerMeter = BitConverter.ToInt32(tempMem, 0);
//Anzahl indizierter Farben
fs.Read(tempMem, 0, 4);
biClrUsed = BitConverter.ToUInt32(tempMem, 0);
//Anzahl der zum darstellen benötigten indizierten Farben
fs.Read(tempMem, 0, 4);
biClrImportant = BitConverter.ToUInt32(tempMem, 0);
}
System.Drawing.Imaging.PixelFormat pf;
switch (biBitCount)
{
case 24:
pf = System.Drawing.Imaging.PixelFormat.Format24bppRgb;
break;
default:
throw new NotSupportedException();
}
init(biWidth, biHeight, pf);
if (biHeight < 0) //Bilddaten einlesen, wie sie kommen
{
fs.Read(this.data, 0, this.data.Length);
}
else //Bilddaten in umgekehrter Zeilenreihenfolge einlesen
{
for (int y = this.Height-1; y ≥ 0; y--)
{
fs.Read(this.data, y * this.stride, this.stride);
}
}
}
finally
{
if (fs != null)
{
fs.Close();
}
}
}
private void init(int initWidth, int initHeight, System.Drawing.Imaging.PixelFormat initPixelFormat)
{
if (initWidth ≤ 0 || initHeight ≤ 0)
throw new ArgumentOutOfRangeException("Negative Width or Height!");
width = initWidth;
height = initHeight;
pixelformat = System.Drawing.Imaging.PixelFormat.Undefined;
if ( initPixelFormat == System.Drawing.Imaging.PixelFormat.Format24bppRgb
|| initPixelFormat == System.Drawing.Imaging.PixelFormat.Format32bppArgb)
pixelformat = initPixelFormat;
else
throw new ArgumentOutOfRangeException("PixelFormat not supported!");
switch (pixelformat)
{
case System.Drawing.Imaging.PixelFormat.Format24bppRgb:
channels = 3;
break;
case System.Drawing.Imaging.PixelFormat.Format32bppArgb:
channels = 4;
break;
default:
throw new InvalidOperationException();
}
stride = (channels * width);
int rest = stride % 4;
if (rest != 0)
stride += (4 - rest);
data = new byte[stride * height];
dataHandle = System.Runtime.InteropServices.GCHandle.Alloc(data, System.Runtime.InteropServices.GCHandleType.Pinned);
}
private void init(System.Drawing.Bitmap b)
{
init(b.Width, b.Height, b.PixelFormat);
System.Drawing.Imaging.BitmapData bd = b.LockBits(new System.Drawing.Rectangle(0, 0, b.Width, b.Height), System.Drawing.Imaging.ImageLockMode.ReadOnly, b.PixelFormat);
System.Runtime.InteropServices.Marshal.Copy(bd.Scan0, data, 0, data.Length);
b.UnlockBits(bd);
}
#endregion
#region Constructors
public MemBitmap(int initWidth, int initHeight, System.Drawing.Imaging.PixelFormat initPixelFormat)
{
init(initWidth, initHeight, initPixelFormat);
}
public MemBitmap(System.Drawing.Bitmap b)
{
init(b);
}
public MemBitmap(string fileName)
{
if (fileName.EndsWith(".bmp"))
{
try
{
loadBitmap(fileName);
return;
}
catch { }
}
System.Drawing.Bitmap b = new System.Drawing.Bitmap(fileName);
init(b);
b.Dispose();
}
#endregion
#region Destruction
public void Dispose()
{
if (bitmap != null)
{
bitmap.Dispose();
}
if (dataHandle != null)
{
dataHandle.Free();
}
data = null;
}
#endregion
}
beste Grüße
zommi
Schlagwörter: <Bitmap, GetPixel, SetPixel, LockBits, Bildverarbeitung>