Fast work with Bitmaps in C#
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;
}
C# - Faster Alternatives to SetPixel and GetPixel for Bitmaps for Windows Forms App
The immediately usable code
public class DirectBitmap : IDisposable
{
public Bitmap Bitmap { get; private set; }
public Int32[] Bits { get; private set; }
public bool Disposed { get; private set; }
public int Height { get; private set; }
public int Width { get; private set; }
protected GCHandle BitsHandle { get; private set; }
public DirectBitmap(int width, int height)
{
Width = width;
Height = height;
Bits = new Int32[width * height];
BitsHandle = GCHandle.Alloc(Bits, GCHandleType.Pinned);
Bitmap = new Bitmap(width, height, width * 4, PixelFormat.Format32bppPArgb, BitsHandle.AddrOfPinnedObject());
}
public void SetPixel(int x, int y, Color colour)
{
int index = x + (y * Width);
int col = colour.ToArgb();
Bits[index] = col;
}
public Color GetPixel(int x, int y)
{
int index = x + (y * Width);
int col = Bits[index];
Color result = Color.FromArgb(col);
return result;
}
public void Dispose()
{
if (Disposed) return;
Disposed = true;
Bitmap.Dispose();
BitsHandle.Free();
}
}
There's no need for LockBits
or SetPixel
. Use the above class for direct access to bitmap data.
With this class, it is possible to set raw bitmap data as 32-bit data. Notice that it is PARGB, which is premultiplied alpha. See Alpha Compositing on Wikipedia for more information on how this works and examples on the MSDN article for BLENDFUNCTION to find out how to calculate the alpha properly.
If premultiplication might overcomplicate things, use PixelFormat.Format32bppArgb
instead. A performance hit occurs when it's drawn, because it's internally being converted to PixelFormat.Format32bppPArgb
. If the image doesn't have to change prior to being drawn, the work can be done before premultiplication, drawn to a PixelFormat.Format32bppArgb
buffer, and further used from there.
Access to standard Bitmap
members is exposed via the Bitmap
property. Bitmap data is directly accessed using the Bits
property.
Using byte
instead of int
for raw pixel data
Change both instances of Int32
to byte
, and then change this line:
Bits = new Int32[width * height];
To this:
Bits = new byte[width * height * 4];
When bytes are used, the format is Alpha/Red/Green/Blue in that order. Each pixel takes 4 bytes of data, one for each channel. The GetPixel and SetPixel functions will need to be reworked accordingly or removed.
Benefits to using the above class
- Memory allocation for merely manipulating the data is unnecessary; changes made to the raw data are immediately applied to the bitmap.
- There are no additional objects to manage. This implements
IDisposable
just likeBitmap
. - It does not require an
unsafe
block.
Considerations
- Pinned memory cannot be moved. It's a required side effect in order for this kind of memory access to work. This reduces the efficiency of the garbage collector (MSDN Article). Do it only with bitmaps where performance is required, and be sure to
Dispose
them when you're done so the memory can be unpinned.
Access via the Graphics
object
Because the Bitmap
property is actually a .NET Bitmap
object, it's straightforward to perform operations using the Graphics
class.
var dbm = new DirectBitmap(200, 200);
using (var g = Graphics.FromImage(dbm.Bitmap))
{
g.DrawRectangle(Pens.Black, new Rectangle(50, 50, 100, 100));
}
Performance comparison
The question asks about performance, so here's a table that should show the relative performance between the three different methods proposed in the answers. This was done using a .NET Standard 2 based application and NUnit.
* Time to fill the entire bitmap with red pixels *
- Not including the time to create and dispose the bitmap
- Best out of 100 runs taken
- Lower is better
- Time is measured in Stopwatch ticks to emphasize magnitude rather than actual time elapsed
- Tests were performed on an Intel Core i7-4790 based workstation
Bitmap size
Method 4x4 16x16 64x64 256x256 1024x1024 4096x4096
DirectBitmap <1 2 28 668 8219 178639
LockBits 2 3 33 670 9612 197115
SetPixel 45 371 5920 97477 1563171 25811013
* Test details *
- LockBits test: Bitmap.LockBits is only called once and the benchmark
includes Bitmap.UnlockBits. It is expected that this
is the absolute best case, adding more lock/unlock calls
will increase the time required to complete the operation.
What is the fastest way to modify pixels in C#
- You have a pointer to the pixel data array, why not use it
Scan0
(you will need to set your method as unsafe, and set it appropriately in the build options for your project) - you could make sure your pixel format is 32 bits.
- You could compare your rbg values in one go with in int
- Use a HashSet for a faster lookup
- Clear the apha-channel using a bitwise
&
Example
var colorHash = SpecificColor1ist
.Select(x => x.ToArgb())
.ToHashSet();
...
var data = bt.LockBits(rect, ImageLockMode.ReadWrite, PixelFormat.Format32bppPArgb);
var length = (int*)data.Scan0 + Mybitmap.Height * Mybitmap.Width;
for (var p = (int*)data.Scan0; p < length; p++)
if(colorHash .Contains(*p))
*p = (*p & 0xFFFFFF) // i think sets the alpha to 0
...
C# combine bitmaps fast for UI
You should never use bitmaps to draw real-time animations. Just create a CustomControl, and override its OnPaint method like this
protected override void OnPaint(PaintEventArgs pe)
{
}
there you can use pe.Graphics to do any drawing you want and no copying to screen is required.
I've used it and if you're drawing routines are reasonably fast, that will be smooth.
Also, remember to call SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
in the control constructor to get double buffering
Faster way to combine bitmaps with custom algorithm?
I'm not sure if this could make sense but what I did is simply created a dictionary for previously calculated value(and some cleanup...), main reason is after doing some profiling, 60 to 70% of the cpu time is with these 2 lines:
r = t * t.Dot(u) - u * t.z;
r.Normalize();
so here it is;
private static unsafe void CombineImage(Bitmap image1, Bitmap image2, int index)
{
Dictionary<long, int> testDict = new Dictionary<long, int>(); //the magic is wit this dictionary
var combinedBitmap = new Bitmap(image1.Width, image1.Height, image1.PixelFormat);
BitmapData bData1 = image1.LockBits(new Rectangle(0, 0, image1.Width, image1.Height), ImageLockMode.ReadOnly, image1.PixelFormat);
BitmapData bData2 = image2.LockBits(new Rectangle(0, 0, image2.Width, image2.Height), ImageLockMode.ReadOnly, image2.PixelFormat);
BitmapData bDataCombined = combinedBitmap.LockBits(new Rectangle(0, 0, combinedBitmap.Width, combinedBitmap.Height), ImageLockMode.WriteOnly, combinedBitmap.PixelFormat);
byte* dataBase = (byte*)bData1.Scan0.ToPointer();
byte* dataDetail = (byte*)bData2.Scan0.ToPointer();
byte* dataCombined = (byte*)bDataCombined.Scan0.ToPointer();
const int bitsPerPixel = 24;
const int xIncr = bitsPerPixel / 8;
var t = new Vec3f(0);
var u = new Vec3f(0);
var r = new Vec3f(0);
int h = bData1.Height, w = bData1.Width;
long key;
int value;
Stopwatch combineStopwatch = Stopwatch.StartNew();
for (int y = 0; y < h; ++y)
{
for (int x = 0; x < w; ++x)
{
//real magic!
key = dataBase[0] | (dataBase[1] << 8) | (dataBase[2] << 16) | (dataDetail[0] << 24) | (dataDetail[1] << 32) | (dataDetail[2] << 40);
if (testDict.ContainsKey(key))
{
value = testDict[key];
dataCombined[0] = (byte)(value & 255);
dataCombined[1] = (byte)((value >> 8) & 255);
dataCombined[2] = (byte)((value >> 16) & 255);
}
else
{
t.z = (dataBase[0] / 255.0f) * 2.0f;
t.y = (dataBase[1] / 255.0f) * 2.0f - 1.0f;
t.x = (dataBase[2] / 255.0f) * 2.0f - 1.0f;
u.z = (dataDetail[0] / 255.0f) * 2.0f - 1.0f;
u.y = (dataDetail[1] / 255.0f) * -2.0f + 1.0f;
u.x = (dataDetail[2] / 255.0f) * -2.0f + 1.0f;
r = t * t.Dot(u) - u * t.z;
r.Normalize();
//Write data to our new bitmap
dataCombined[0] = (byte)Math.Round((r.z * 0.5f + 0.5f) * 255.0f);
dataCombined[1] = (byte)Math.Round((r.y * 0.5f + 0.5f) * 255.0f);
dataCombined[2] = (byte)Math.Round((r.x * 0.5f + 0.5f) * 255.0f);
value = dataCombined[0] | (dataCombined[1] << 8) | (dataCombined[2] << 16);
testDict.Add(key, value);
}
dataBase += xIncr;
dataDetail += xIncr;
dataCombined += xIncr;
}
}
combineStopwatch.Stop();
combinedBitmap.UnlockBits(bDataCombined);
image2.UnlockBits(bData1);
image1.UnlockBits(bData1);
//combinedBitmap.Save("helloyou.png", ImageFormat.Png);
testDict.Clear();
Console.Write(combineStopwatch.ElapsedMilliseconds + "\n");
}
Speed - Copy bitmap data into array or work with it directly?
Is this faster or better than accessing the locked bitmap data
directly via the pointer?
No. It requires an additional copy operation, and then another operation to copy the processed values back to the bitmap.
Is a copy needed at all?
Only in an environment where unsafe
code is undesired or unavailable.
Fast Bitmap modifying using BitmapData and pointers in C#
Instead of calculating x and y for each pixel, you could make them loop counters, like this:
for( y = 0; y < bmp.Height; y++ )
for( x = 0; x < bmp.Width; x++ )
Better yet, ditch x and y altogether and just keep incrementing the ptr
pointer instead of recalculating an offset from the ptr
pointer three times per pixel.
Try this (warning: I have not checked it.)
public void dataAcquired()
{
Bitmap bmp = new Bitmap(width, height);
BitmapData data = bmp.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
unsafe
{
int i = 0;
byte* ptr = (byte*)data.Scan0;
for( int y = 0; y < bmp.Height; y++ )
{
byte* ptr2 = ptr;
for( int x = 0; x < bmp.Width; x++ )
{
Rgb rgb = mapColors[data[i++]];
*(ptr2++) = rgb.b;
*(ptr2++) = rgb.g;
*(ptr2++) = rgb.r;
}
ptr += data.Stride;
}
}
bmp.UnlockBits(data);
}
Related Topics
Check If a Class Is Derived from a Generic Class
Deserializing Json to .Net Object Using Newtonsoft (Or Linq to Json Maybe)
Color Different Parts of a Richtextbox String
What Is the Simplest Method of Inter-Process Communication Between 2 C# Processes
Remove Element of a Regular Array
C# Catch a Stack Overflow Exception
Unity: Null While Making New Class Instance
How to Load Image to Wpf in Runtime
Why Is Dictionary Preferred Over Hashtable in C#
How to Convert a Structure to a Byte Array in C#
How to Use Reflection to Invoke a Private Method
Given a Filesystem Path, Is There a Shorter Way to Extract the Filename Without Its Extension