Fast work with Bitmaps in C#
Solution 1
You can do it a couple of different ways. You can use unsafe
to get direct access to the data, or you can use marshaling to copy the data back and forth. The unsafe code is faster, but marshaling doesn't require unsafe code. Here's a performance comparison I did a while back.
Here's a complete sample using lockbits:
/*Note unsafe keyword*/
public unsafe Image ThresholdUA(float thresh)
{
Bitmap b = new Bitmap(_image);//note this has several overloads, including a path to an image
BitmapData bData = b.LockBits(new Rectangle(0, 0, _image.Width, _image.Height), ImageLockMode.ReadWrite, b.PixelFormat);
byte bitsPerPixel = GetBitsPerPixel(bData.PixelFormat);
/*This time we convert the IntPtr to a ptr*/
byte* scan0 = (byte*)bData.Scan0.ToPointer();
for (int i = 0; i < bData.Height; ++i)
{
for (int j = 0; j < bData.Width; ++j)
{
byte* data = scan0 + i * bData.Stride + j * bitsPerPixel / 8;
//data is a pointer to the first byte of the 3-byte color data
//data[0] = blueComponent;
//data[1] = greenComponent;
//data[2] = redComponent;
}
}
b.UnlockBits(bData);
return b;
}
Here's the same thing, but with marshaling:
/*No unsafe keyword!*/
public Image ThresholdMA(float thresh)
{
Bitmap b = new Bitmap(_image);
BitmapData bData = b.LockBits(new Rectangle(0, 0, _image.Width, _image.Height), ImageLockMode.ReadWrite, b.PixelFormat);
/* GetBitsPerPixel just does a switch on the PixelFormat and returns the number */
byte bitsPerPixel = GetBitsPerPixel(bData.PixelFormat);
/*the size of the image in bytes */
int size = bData.Stride * bData.Height;
/*Allocate buffer for image*/
byte[] data = new byte[size];
/*This overload copies data of /size/ into /data/ from location specified (/Scan0/)*/
System.Runtime.InteropServices.Marshal.Copy(bData.Scan0, data, 0, size);
for (int i = 0; i < size; i += bitsPerPixel / 8 )
{
double magnitude = 1/3d*(data[i] +data[i + 1] +data[i + 2]);
//data[i] is the first of 3 bytes of color
}
/* This override copies the data back into the location specified */
System.Runtime.InteropServices.Marshal.Copy(data, 0, bData.Scan0, data.Length);
b.UnlockBits(bData);
return b;
}
Solution 2
You can use Bitmap.LockBits method. Also if you want to use parallel task execution, you can use the Parallel class in System.Threading.Tasks namespace. Following links have some samples and explanations.
- http://csharpexamples.com/fast-image-processing-c/
- http://msdn.microsoft.com/en-us/library/dd460713%28v=vs.110%29.aspx
- http://msdn.microsoft.com/tr-tr/library/system.drawing.imaging.bitmapdata%28v=vs.110%29.aspx
Solution 3
If you're on C# 8.0 I'll suggest to use the new Span<T>
for higher efficiency.
Here's a rough implementation
public unsafe class FastBitmap : IDisposable
{
private Bitmap _bmp;
private ImageLockMode _lockmode;
private int _pixelLength;
private Rectangle _rect;
private BitmapData _data;
private byte* _bufferPtr;
public int Width { get => _bmp.Width; }
public int Height { get => _bmp.Height; }
public PixelFormat PixelFormat { get => _bmp.PixelFormat; }
public FastBitmap(Bitmap bmp, ImageLockMode lockMode)
{
_bmp = bmp;
_lockmode = lockMode;
_pixelLength = Image.GetPixelFormatSize(bmp.PixelFormat) / 8;
_rect = new Rectangle(0, 0, Width, Height);
_data = bmp.LockBits(_rect, lockMode, PixelFormat);
_bufferPtr = (byte*)_data.Scan0.ToPointer();
}
public Span<byte> this[int x, int y]
{
get
{
var pixel = _bufferPtr + y * _data.Stride + x * _pixelLength;
return new Span<byte>(pixel, _pixelLength);
}
set
{
value.CopyTo(this[x, y]);
}
}
public void Dispose()
{
_bmp.UnlockBits(_data);
}
}
Solution 4
You want LockBits. You can then extract the bytes you want from the BitmapData object it gives you.
Solution 5
Building on @notJim answer (and with help from https://web.archive.org/web/20141229164101/http://bobpowell.net/lockingbits.aspx), I developed the following that makes my life a lot easier in that I end up with an array of arrays that allows me to jump to a pixel by its x
and y
coordinates. Of course, the x
coordinate needs to be corrected for by the number of bytes per pixel, but that is an easy extension.
Dim bitmapData As Imaging.BitmapData = myBitmap.LockBits(New Rectangle(0, 0, myBitmap.Width, myBitmap.Height), Imaging.ImageLockMode.ReadOnly, myBitmap.PixelFormat)
Dim size As Integer = Math.Abs(bitmapData.Stride) * bitmapData.Height
Dim data(size - 1) As Byte
Marshal.Copy(bitmapData.Scan0, data, 0, size)
Dim pixelArray(myBitmap.Height)() As Byte
'we have to load all the opacity pixels into an array for later scanning by column
'the data comes in rows
For y = myBitmap.Height - 1 To 0 Step -1
Dim rowArray(bitmapData.Stride) As Byte
Array.Copy(data, y * bitmapData.Stride, rowArray, 0, bitmapData.Stride)
'For x = myBitmap.Width - 1 To 0 Step -1
' Dim i = (y * bitmapData.Stride) + (x * 4)
' Dim B = data(i)
' Dim G = data(i + 1)
' Dim R = data(i + 2)
' Dim A = data(i + 3)
'Next
pixelArray(y) = rowArray
Next
Related videos on Youtube
AndreyAkinshin
Updated on April 21, 2022Comments
-
AndreyAkinshin about 2 years
I need to access each pixel of a Bitmap, work with them, then save them to a Bitmap.
Using
Bitmap.GetPixel()
andBitmap.SetPixel()
, my program runs slowly.How can I quickly convert
Bitmap
tobyte[]
and back?I need a
byte[]
withlength = (4 * width * height)
, containing RGBA data of each pixel.-
turgay over 9 yearsFollowing link contains bitmap pixel access methods comparatively. csharpexamples.com/fast-image-processing-c
-
György Kőszeg over 2 yearsThe answers here recommend using
LockBits
, which needs different access for all possible pixel formats. Here is a library (disclaimer: written by me), which provides fast, unified managed access for all possible formats, including indexed ones.
-
-
MusiGenesis over 14 yearsI think it's faster if you use the
BitmapData
object returned from LockBits inside anunsafe
block with a pointer cast (NOTE: I actually have no idea if this is faster or not, but I'm trying to goad someone else into benchmarking it). -
David Seiler over 14 yearsIt'd save you the copies into and out of the BitmapData, which is nice. Not sure how much savings that would grant in practice, though; memcpy() is really fast these days.
-
MusiGenesis over 14 yearsThanks, and I didn't even have to goad anybody. :)
-
Steve Niles over 11 yearsYou should be aware when doing this, that the order of the color channels may be different depending on what PixelFormat you're using. For example, with 24bit Bitmaps, the first byte of the pixel is the blue channel, followed by green, then red, as opposed to the commonly expected Red-Green-Blue order.
-
freakinpenguin almost 10 yearsThanks for you effort!
-
GorillaApe over 9 yearsbData.Height seems a bit expensive. I got some performance boost skipping it
-
danodonovan about 9 years@Parhs sure, skipping
bData.Height
will speed things up, because you're only taking the first row of your data. Trysize = 1
that'll make things even faster! -
wondra almost 9 yearsYou should also be aware that this may not get expected byte data because generally
bData.Stride
does not have to be equal tob.Width * bytePerPix
. -
Md Sifatul Islam over 7 yearswhats the purpose of using the variable magnitude?
-
Nyerguds about 7 yearsFor >=8bpp images I usually get around the stride difference by copying per line, increasing the source offset by the stride with each line but copying to a new array with the actual width (well,
width * BPP / 8
of course) * height as length. For images with <8bpp though, stuff gets... interesting. A good rule is to always include the stride with your image processing functions, preferably as ref parameter in case it gets changed for your output. -
Felipe over 6 yearsLink is ded, can anyone summarize what it said?
-
davidtbernal over 6 years@Pipe fixed the link
-
John Alexiou over 6 yearsWhat is
GetBitsPerPixel()
here? -
rotgers about 6 years@ja72
System.Drawing.Bitmap.GetPixelFormatSize(...);
-
royalTS over 5 years@Jargon Isn´t the order of the color channels depending on the endianness rather than the pixel format?
-
Dennis van Gils about 4 years@davidtbernal in your second example using marshalling, is there any way to get the x/y of current pixel (based on
i
I think?) -
jklw10 almost 4 yearshow would i go about using this if i wanted to write to the bitmap ?
-
Shaamil Ahmed almost 4 yearsI've updated the code and should work like a charm. Now you access the pixels by saying
new FastBitmap(.....)[100, 200]
where 100 is x and 200 is y. You can set the value of the pixel by writingnew FastBitmap(......)[100, 200] = new Span<byte>(array);
-
jklw10 almost 4 yearshow would you go about converting this back into a bitmap? could i just make _data public?
-
jklw10 almost 4 yearsgyazo.com/d5ab06cf35a3bb84a56d8330fa25517b i made my own cast but encountered this error when trying
fastbmp[x,y] = new Span<byte>(BitConverter.GetBytes(color.ToArgb()));
-
jklw10 almost 4 yearsisn't
var pixel = _bufferPtr + y * _data.Stride * x * _pixelLength;
supposed to bevar pixel = _bufferPtr + y * _data.Stride
+x * _pixelLength