I am trying to teach myself C# and have heard from a variety of sources that the functions get and setpixel can be horribly slow. What are some of the alternatives and is the performance improvement really that significant?
A chunk of my code for reference:
public static Bitmap Paint(Bitmap _b, Color f)
{
Bitmap b = new Bitmap(_b);
for (int x = 0; x < b.Width; x++)
{
for (int y = 0; y < b.Height; y++)
{
Color c = b.GetPixel(x, y);
b.SetPixel(x, y, Color.FromArgb(c.A, f.R, f.G, f.B));
}
}
return b;
}
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 like Bitmap.
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.
The reason bitmap operations are so slow in C# is due to locking and unlocking. Every operation will perform a lock on the required bits, manipulate the bits, and then unlock the bits.
You can vastly improve the speed by handling the operations yourself. See the following example.
using (var tile = new Bitmap(tilePart.Width, tilePart.Height))
{
try
{
BitmapData srcData = sourceImage.LockBits(tilePart, ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
BitmapData dstData = tile.LockBits(new Rectangle(0, 0, tile.Width, tile.Height), ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb);
unsafe
{
byte* dstPointer = (byte*)dstData.Scan0;
byte* srcPointer = (byte*)srcData.Scan0;
for (int i = 0; i < tilePart.Height; i++)
{
for (int j = 0; j < tilePart.Width; j++)
{
dstPointer[0] = srcPointer[0]; // Blue
dstPointer[1] = srcPointer[1]; // Green
dstPointer[2] = srcPointer[2]; // Red
dstPointer[3] = srcPointer[3]; // Alpha
srcPointer += BytesPerPixel;
dstPointer += BytesPerPixel;
}
srcPointer += srcStrideOffset + srcTileOffset;
dstPointer += dstStrideOffset;
}
}
tile.UnlockBits(dstData);
aSourceImage.UnlockBits(srcData);
tile.Save(path);
}
catch (InvalidOperationException e)
{
}
}
It's been some time, but I found an example that might be useful.
var btm = new Bitmap("image.png");
BitmapData btmDt = btm.LockBits(
new Rectangle(0, 0, btm.Width, btm.Height),
ImageLockMode.ReadWrite,
btm.PixelFormat
);
IntPtr pointer = btmDt.Scan0;
int size = Math.Abs(btmDt.Stride) * btm.Height;
byte[] pixels = new byte[size];
Marshal.Copy(pointer, pixels, 0, size);
for (int b = 0; b < pixels.Length; b++)
{
pixels[b] = 255; //Do something here
}
Marshal.Copy(pixels, 0, pointer, size);
btm.UnlockBits(btmDt);
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
This code should be parallelized, there is a massive performance gain being missed by running this synchronously. Almost no modern Microchip will have less than 4 threads available and some chips will have 40 threads available.
There is absolutely no reason to run that first loop synchronously. You can go through either the width or the length using many, many threads.
private void TakeApart_Fast(Bitmap processedBitmap)
{
BitmapData bitmapData = processedBitmap.LockBits(new Rectangle(0, 0, processedBitmap.Width, processedBitmap.Height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
ConcurrentBag<byte> points = new ConcurrentBag<byte>();
unsafe
{
int bytesPerPixel = System.Drawing.Bitmap.GetPixelFormatSize(processedBitmap.PixelFormat) / 8;
int heightInPixels = bitmapData.Height;
int widthInBytes = bitmapData.Width * bytesPerPixel;
_RedMin = byte.MaxValue;
_RedMax = byte.MinValue;
byte* PtrFirstPixel = (byte*)bitmapData.Scan0;
Parallel.For(0, heightInPixels, y =>
{
byte* currentLine = PtrFirstPixel + (y * bitmapData.Stride);
for (int x = 0; x < widthInBytes; x = x + bytesPerPixel)
{
// red
byte redPixel = currentLine[x + 2];
//save information with the concurrentbag
}
});
processedBitmap.UnlockBits(bitmapData);
}
}`
a benchmark wouldn't mean much because the answer to how much this will speed up the proccess depends 100% on what hardware you are using, and what else is running in the background, it all depends on how many free threads are available. If your running this on a 4000 series graphics card with thousands of streaming proccessors you may be able to do iterate through every column of the image at the same time.
if your running it with and old quad core you may only have 5 or 6 threads which is still incredibly significant.
Related
I'm working with WriteableBitmap (PixelFormats.Bgr32).
This is the format I need to save for the rest of the program to work.
Writable = new WriteableBitmap(inImage.XSize, inImage.YSize, dpi, dpi, PixelFormats.Bgr32, null);
The image from the device comes in grayscaled, (ushort[] Gray16).
To use this image in my program I use the following code (inImage - received image, ImageData = ushort[]):
int[] pixels = Array.ConvertAll(inImage.ImageData, val => checked((int)val));
Writable.WritePixels(new Int32Rect(0, 0, width, height), pixels, width * 4, 0);
If I just use
Writable.WritePixels(new Int32Rect(0, 0, width, height), inImage.ImageData, width * 4, 0);
I get a message about insufficient buffer size. (System.ArgumentException: "Buffer size is not sufficient."
)
Code
int[] pixels = Array.ConvertAll(inImage.ImageData, val => checked((int)val))
or
int[] pixels = new int[inImage.XSize * inImage.YSize];
for (int i = 0; i < inImage.ImageData.Length; i++)
{
pixels[i] = inImage.ImageData[i];
}
This seems to me to be a very long and slow and unoptimized process.
Is there any way to optimize the process and immediately write ushort[] array to WriteableBitmap (Bgr32) without converting it to int[] ?
EDIT***
I need this code to be able to work with images with a resolution of 4300x4300 with 45 FPS.
Below is the full code how I load images (this code is called 45 times per second to load a new image)
public class ImageStruct
{
public ushort[] ImageData;
public int XSize;
public int YSize;
public int XDpi;
public int YDpi;
}
WriteableBitmap Writeable;
public void LoadFrame(ImageStruct inImage)
{
var width = inImage.XSize;
var height = inImage.YSize;
var destBpp = PixelFormats.Bgr32.BitsPerPixel / 8;
var uPixels = inImage.ImageData;
fluoroWritable = new WriteableBitmap(inImage.XSize, inImage.YSize, 96, 96, PixelFormats.Bgr32, null);
var iPixels = Array.ConvertAll(uPixels, val => checked((int)val));
fluoroWritable.WritePixels(new Int32Rect(0, 0, width, height), iPixels, width * 4, 0);
}
This code works well when FPS is ~10,
If you increase the FPS, it starts to freeze. Profiling shows that it is the moment when I make from ushort[] => int[] takes the longest time.
I tried to use the code suggested in the answer below:
unsafe public void LoadFrameWithLock(ImageStruct inImage)
{
var width = inImage.XSize;
var height = inImage.YSize;
var destBpp = PixelFormats.Bgr32.BitsPerPixel / 8;
var uPixels = inImage.ImageData;
fluoroWritable = new WriteableBitmap(inImage.XSize, inImage.YSize, 96, 96, PixelFormats.Bgr32, null);
fluoroWritable.Lock();
int* outputIntValues = (int*)fluoroWritable.BackBuffer;
ushort[] inputShortValues = inImage.ImageData;
for (int i = 0; i < inputShortValues.Length; i++)
{
byte as8bpp = (byte)(inputShortValues[i] >> 8);
outputIntValues[i] = /*B*/ as8bpp | /*G*/ (as8bpp << 8) | /*R*/ (as8bpp << 16);
}
fluoroWritable.AddDirtyRect(new Int32Rect(0, 0, fluoroWritable.PixelWidth, fluoroWritable.PixelHeight));
fluoroWritable.Unlock();
}
But the FPS with this code is even lower and in addition the WPF application has a completely hanging interface. and images have no grayscale, but are completely black and white (white or black pixels).
I need the BGR32 format because this WritableBitmap (WPF Image object) is then overlaid with shader effects, and I can also get color images in another place.
Is there any way to optimize the process and immediately write ushort[] array to WriteableBitmap (Bgr32) without converting it to int[] ?
Yes, instead of calling WritePixels use the Lock method so the BackBuffer will be available as a naked pointer (do not forget to call Unlock when you are finished).
Please note though that as your source and target pixel formats are different (16bpp grayscale vs. 32bpp BGRx) simple casting of short values to int will not be correct. All 16 bit grayscale values must be converted to 8 bit RGB values:
// the result as you defined in OP
Writable = new WriteableBitmap(inImage.XSize, inImage.YSize, dpi, dpi, PixelFormats.Bgr32, null);
// you must be in an unsafe scope to use pointers
int* outputIntValues = (int*)Writable.BackBuffer;
short[] inputShortValues = inImage.ImageData;
// converting pixels to BGR32
for (int i = 0; i < inputShortValues.Length; i++)
{
// taking the most significant bits from the 16bpp gray values
byte as8bpp = (byte)(inputShortValues[i] >> 8);
outputIntValues[i] = /*B*/ as8bpp | /*G*/ (as8bpp << 8) | /*R*/ (as8bpp << 16);
}
// notifying the consumers that we edited the raw content
Writable.AddDirtyRect(new Int32Rect(0, 0, Writable.PixelWidth, Writable.PixelHeight));
// releasing the buffer
Writable.Unlock();
The sample above needs to be inside of an unsafe scope because of the pointer and you must enable unsafe blocks for your project in your .csproj file.
To make things simpler you can use my Drawing Libraries, which now has dedicated WPF support so the conversion will just be literally two lines. It does not need unsafe context and is actually faster than the example above because it uses parallel processing:
// Interpret your short[] as a grayscale bitmap
using var bmpGrayscale = BitmapDataFactory.CreateBitmapData(buffer: inImage.ImageData,
size: new Size(inImage.XSize, inImage.XSize),
stride: inImage.XSize * 2, // if input pixels are contiguous
KnownPixelFormat.Format16bppGrayScale));
// Convert it to a BGR32 WriteableBitmap (async overloads are available, too)
WriteableBitmap bmpResult = bmpGrayscale.ToWriteableBitmap(PixelFormats.Bgr32);
I have been trying to implement the image comparing algorithm seen here: http://www.dotnetexamples.com/2012/07/fast-bitmap-comparison-c.html
The problem I have been having is that when I try to compare a large amount of images one after another using the method pasted below (a slightly modified version from the link above), my results seem to be inaccurate. In particular, if I try to compare too many different images, even the ones that are the same will occasionally be detected as different. The problem seems to be that certain bytes in the array are different, as you can see in the screenshot I have included of two of the same images being compared (this occurs when I repeatedly compare images from an array of about 100 images - but there are actually only 3 unique images in the array):
private bool byteCompare(Bitmap image1, Bitmap image2) {
if (object.Equals(image1, image2))
return true;
if (image1 == null || image2 == null)
return false;
if (!image1.Size.Equals(image2.Size) || !image1.PixelFormat.Equals(image2.PixelFormat))
return false;
#region Optimized code for performance
int bytes = image1.Width * image1.Height * (Image.GetPixelFormatSize(image1.PixelFormat) / 8);
byte[] b1bytes = new byte[bytes];
byte[] b2bytes = new byte[bytes];
Rectangle rect = new Rectangle(0, 0, image1.Width - 1, image1.Height - 1);
BitmapData bmd1 = image1.LockBits(rect, ImageLockMode.ReadOnly, image1.PixelFormat);
BitmapData bmd2 = image2.LockBits(rect, ImageLockMode.ReadOnly, image2.PixelFormat);
try
{
Marshal.Copy(bmd1.Scan0, b1bytes, 0, bytes);
Marshal.Copy(bmd2.Scan0, b2bytes, 0, bytes);
for (int n = 0; n < bytes; n++)
{
if (b1bytes[n] != b2bytes[n]) //This line is where error occurs
return false;
}
}
finally
{
image1.UnlockBits(bmd1);
image2.UnlockBits(bmd2);
}
#endregion
return true;
}
I've added a comment to show where in the method this error is occurring. I assume it has something to do with the memory not being allocated properly, but I haven't been able to figure out what the source of the error is.
I should probably also mention that I don't get any issues when I convert the image to a byte array like so:
ImageConverter converter = new ImageConverter();
byte[] b1bytes = (byte[])converter.ConvertTo(image1, typeof(byte[]));
However, this approach is far slower.
If (Width * bytesperpixel) != Stride, then there will be unused bytes at the end of each line that are not guaranteed to have any particular value and in practice can be filled with random garbage.
You need to iterate line by line, increment by Stride each time, and only checking the bytes that actually correspond to pixels on each line.
Once you got the BitmapData object, the Stride can be found in that BitmapData object's Stride property. Make sure to extract that for both images.
Then, you have to loop over all pixels in the data so you can accurately determine where the image width for each line ends and the leftover data of the stride begins.
Also note this only works for high-colour images. Comparing 8-bit images is still possible (though you need to compare their palettes as well), but for lower than 8 you need to go bit-shifting to get the actual palette offset out of the image.
A simple workaround for that is to just paint your image on a new 32bpp image, effectively converting it to high colour.
public static Boolean CompareHiColorImages(Byte[] imageData1, Int32 stride1, Byte[] imageData2, Int32 stride2, Int32 width, Int32 height, PixelFormat pf)
{
Int32 byteSize = Image.GetPixelFormatSize(pf) / 8;
for (Int32 y = 0; y < height; y++)
{
for (Int32 x = 0; x < width; x++)
{
Int32 offset1 = y * stride1 + x * byteSize;
Int32 offset2 = y * stride2 + x * byteSize;
for (Int32 n = 0; n > byteSize; n++)
if (imageData1[offset1 + n] != imageData2[offset2 + n])
return false;
}
}
return true;
}
I'm working on 10 megapixel images taken by a video camera.
The aim is to register in a matrix (a two-dimensional array) the grayscale values for each pixel.
I first used GetPixel but it took 25 seconds to do it. Now I use Lockbits but it sill takes 10 seconds, and 3 if I don't save the results in a text file.
My tutor said they don't need to register the results but 3 seconds is still too slow. So am I doing something wrong in my program or is there something faster than Lockbits for my application?
Here is my code:
public void ExtractMatrix()
{
Bitmap bmpPicture = new Bitmap(nameNumber + ".bmp");
int[,] GRAY = new int[3840, 2748]; //Matrix with "grayscales" in INTeger values
unsafe
{
//create an empty bitmap the same size as original
Bitmap bmp = new Bitmap(bmpPicture.Width, bmpPicture.Height);
//lock the original bitmap in memory
BitmapData originalData = bmpPicture.LockBits(
new Rectangle(0, 0, bmpPicture.Width, bmpPicture.Height),
ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);
//lock the new bitmap in memory
BitmapData newData = bmp.LockBits(
new Rectangle(0, 0, bmpPicture.Width, bmpPicture.Height),
ImageLockMode.WriteOnly, PixelFormat.Format24bppRgb);
//set the number of bytes per pixel
// here is set to 3 because I use an Image with 24bpp
int pixelSize = 3;
for (int y = 0; y < bmpPicture.Height; y++)
{
//get the data from the original image
byte* oRow = (byte*)originalData.Scan0 + (y * originalData.Stride);
//get the data from the new image
byte* nRow = (byte*)newData.Scan0 + (y * newData.Stride);
for (int x = 0; x < bmpPicture.Width; x++)
{
//create the grayscale version
byte grayScale =
(byte)((oRow[x * pixelSize] * .114) + //B
(oRow[x * pixelSize + 1] * .587) + //G
(oRow[x * pixelSize + 2] * .299)); //R
//set the new image's pixel to the grayscale version
// nRow[x * pixelSize] = grayScale; //B
// nRow[x * pixelSize + 1] = grayScale; //G
// nRow[x * pixelSize + 2] = grayScale; //R
GRAY[x, y] = (int)grayScale;
}
}
Here are some more optimizations that may help:
Use jagged arrays ([][]); in .NET, accessing them is faster than multidimensional;
Cache properties that will be used inside of a loop. Though this answer states that JIT will optimize it, we don't know what's happening internally;
Multiplication is (generally) slower than addition;
As others have stated, float is faster than double, which applies to older processors (~10+ years). The only upside here is that you're using them as constants, and thus consume less memory (especially because of the many iterations);
Bitmap bmpPicture = new Bitmap(nameNumber + ".bmp");
// jagged instead of multidimensional
int[][] GRAY = new int[3840][]; //Matrix with "grayscales" in INTeger values
for (int i = 0, icnt = GRAY.Length; i < icnt; i++)
GRAY[i] = new int[2748];
unsafe
{
//create an empty bitmap the same size as original
Bitmap bmp = new Bitmap(bmpPicture.Width, bmpPicture.Height);
//lock the original bitmap in memory
BitmapData originalData = bmpPicture.LockBits(
new Rectangle(0, 0, bmpPicture.Width, bmpPicture.Height),
ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);
//lock the new bitmap in memory
BitmapData newData = bmp.LockBits(
new Rectangle(0, 0, bmpPicture.Width, bmpPicture.Height),
ImageLockMode.WriteOnly, PixelFormat.Format24bppRgb);
//set the number of bytes per pixel
// here is set to 3 because I use an Image with 24bpp
const int pixelSize = 3; // const because it doesn't change
// store Scan0 value for reuse...we don't know if BitmapData caches it internally, or recalculated it every time, or whatnot
int originalScan0 = originalData.Scan0;
int newScan0 = newData.Scan0;
// incrementing variables
int originalStride = originalData.Stride;
int newStride = newData.Stride;
// store certain properties, because accessing a variable is normally faster than a property (and we don't really know if the property recalculated anything internally)
int bmpwidth = bmpPicture.Width;
int bmpheight = bmpPicture.Height;
for (int y = 0; y < bmpheight; y++)
{
//get the data from the original image
byte* oRow = (byte*)originalScan0 + originalStride++; // by doing Variable++, you're saying "give me the value, then increment one" (Tip: DON'T add parenthesis around it!)
//get the data from the new image
byte* nRow = (byte*)newScan0 + newStride++;
int pixelPosition = 0;
for (int x = 0; x < bmpwidth; x++)
{
//create the grayscale version
byte grayScale =
(byte)((oRow[pixelPosition] * .114f) + //B
(oRow[pixelPosition + 1] * .587f) + //G
(oRow[pixelPosition + 2] * .299f)); //R
//set the new image's pixel to the grayscale version
// nRow[pixelPosition] = grayScale; //B
// nRow[pixelPosition + 1] = grayScale; //G
// nRow[pixelPosition + 2] = grayScale; //R
GRAY[x][y] = (int)grayScale;
pixelPosition += pixelSize;
}
}
Your code is converting from a row-major representation into a column-major representation.
In the bitmap, pixel (x,y) is followed by (x+1,y) in memory; but in your GRAY array, pixel (x,y) is followed by (x,y+1).
This causes inefficient memory access when writing, as every write touches a different cache line; and you end up trashing the CPU cache if the image is big enough. This is especially bad if your image size is a power of two (see Why is transposing a matrix of 512x512 much slower than transposing a matrix of 513x513?).
Store your array in row-major order as well if possible to avoid the inefficient memory access (replace GRAY[x,y] with GRAY[y,x]).
If you really need it in column-major order, look at more cache-friendly algorithms for matrix transposition (e.g. A Cache Efficient Matrix Transpose Program?)
Your code may not be optimal, but a quick skim seems to show even this version should run in a fraction of a second. This suggests there's some other problem:
Are you:
Compiling in Release mode? Debug mode turns off various optimizations
Running with a debugger attached? If you run from visual studio using F5 then (with the default C# keyshortcuts) the debugger will be attached. This can dramatically slow down your program, particularly if you have any breakpoints or intellitrace enabled.
Running on some limited device? It sounds like you're running on a PC, but if you're not, then device-specific limitations might be relevant.
I/O limited? Although you talk about a video camera, your code suggests you're dealing with the filesystem. Any file-system interaction can be a bottleneck, particularly once networked disks, virus scanners, physical platters and fragmentation come into play. A 10 mp image is 30MB (if uncompressed RGB without an alpha channel), and reading/writing that could easily take 3 seconds depending on the details of the filesystem.
I'm not sure why the second part of the inner for loop is commented out, but if you don't need that, you're doing some unnecessary casting. Removing it might improve your performance.
Also, as leppie suggested, you could use single precision floats:
for (int x = 0; x < bmpPicture.Width; x++)
{
//create the grayscale version
GRAY[x, y] =
(int)((oRow[x * pixelSize] * .114f) + //B
(oRow[x * pixelSize + 1] * .587f) + //G
(oRow[x * pixelSize + 2] * .299f)); //R
}
You can try to avoid multiplies and increment setting up a pointer with the x * pixelSize starting value and changing your code to this:
for (int x = 0; x < bmpPicture.Width; x++)
{
int *p = x * pixelSize;
GRAY[x, y]=
(int)((oRow[*p] * .114) + //B
(oRow[*p++] * .587) + //G
(oRow[*p++] * .299)); //R
}
This will speed up your code, but I'm not sure it will be significantly faster.
Note: this will speed up code only if iterating through an array of value type and will not work if oRow changes to some other type.
Here's an alternative transformation that uses only integer arithmetic, it's slightly different (due to rounding of the factors) but not anything you'd notice with the naked eye: (not tested)
byte grayScale = (byte)((
(oRow[pixelPosition] * 29) +
(oRow[pixelPosition + 1] * 151) +
(oRow[pixelPosition + 2] * 105)) >> 8);
The scale factors are approximately the old ones multiplied by 256, the shift in the end divides by 256.
Huge optimation will be achieved by using a 1D array instead of 2D array.
All other will not give you a high speedup...
Is there an efficient way of adjusting the contrast of an image in C#?
I've seen this article which advocates doing a per-pixel operation. Not quick.
I'm using colour matrices in places already and find them to be quick. Is there a way to adjust contrast using them? (Note: This guy gets it wrong.)
I'm also using EmguCV. I notice that OpenCV (which Emgu wraps) seems to have a contrast function - is there any way of accessing this through Emgu? At the moment all I can do in Emgu is normalise the histogram, which does change the contrast, but not with any degree of control on my part.
Anyone got any ideas?
If the code in that sample works for you, you can speed it up massively (by orders of magnitude) by using Bitmap.LockBits, which returns a BitmapData object that allows access to the Bitmap's pixel data via pointers. There are numerous samples on the web and on StackOverflow that show how to use LockBits.
Bitmap.SetPixel() and Bitmap.GetPixel() are the slowest methods known to mankind, and they both utilize the Color class, which is the slowest class known to mankind. They should have been named Bitmap.GetPixelAndByGodYoullBeSorryYouDid() and Bitmap.SetPixelWhileGettingCoffee as a warning to unwary developers.
Update: If you're going to modify the code in that sample, note that this chunk:
System.Drawing.Bitmap TempBitmap = Image;
System.Drawing.Bitmap NewBitmap = new System.Drawing.Bitmap(TempBitmap.Width,
TempBitmap.Height);
System.Drawing.Graphics NewGraphics =
System.Drawing.Graphics.FromImage(NewBitmap);
NewGraphics.DrawImage(TempBitmap, new System.Drawing.Rectangle(0, 0,
TempBitmap.Width, TempBitmap.Height),
new System.Drawing.Rectangle(0, 0, TempBitmap.Width, TempBitmap.Height),
System.Drawing.GraphicsUnit.Pixel);
NewGraphics.Dispose();
can be replaced with this:
Bitmap NewBitmap = (Bitmap)Image.Clone();
Update 2: Here is the LockBits version of the AdjustContrast method (with a few other speed improvements):
public static Bitmap AdjustContrast(Bitmap Image, float Value)
{
Value = (100.0f + Value) / 100.0f;
Value *= Value;
Bitmap NewBitmap = (Bitmap)Image.Clone();
BitmapData data = NewBitmap.LockBits(
new Rectangle(0, 0, NewBitmap.Width, NewBitmap.Height),
ImageLockMode.ReadWrite,
NewBitmap.PixelFormat);
int Height = NewBitmap.Height;
int Width = NewBitmap.Width;
unsafe
{
for (int y = 0; y < Height; ++y)
{
byte* row = (byte*)data.Scan0 + (y * data.Stride);
int columnOffset = 0;
for (int x = 0; x < Width; ++x)
{
byte B = row[columnOffset];
byte G = row[columnOffset + 1];
byte R = row[columnOffset + 2];
float Red = R / 255.0f;
float Green = G / 255.0f;
float Blue = B / 255.0f;
Red = (((Red - 0.5f) * Value) + 0.5f) * 255.0f;
Green = (((Green - 0.5f) * Value) + 0.5f) * 255.0f;
Blue = (((Blue - 0.5f) * Value) + 0.5f) * 255.0f;
int iR = (int)Red;
iR = iR > 255 ? 255 : iR;
iR = iR < 0 ? 0 : iR;
int iG = (int)Green;
iG = iG > 255 ? 255 : iG;
iG = iG < 0 ? 0 : iG;
int iB = (int)Blue;
iB = iB > 255 ? 255 : iB;
iB = iB < 0 ? 0 : iB;
row[columnOffset] = (byte)iB;
row[columnOffset + 1] = (byte)iG;
row[columnOffset + 2] = (byte)iR;
columnOffset += 4;
}
}
}
NewBitmap.UnlockBits(data);
return NewBitmap;
}
NOTE: this code requires using System.Drawing.Imaging; in your class' using statements, and it requires that the project's allow unsafe code option be checked (on the Build Properties tab for the project).
One of the reasons GetPixel and SetPixel are so slow for pixel-by-pixel operations is that the overhead of the method call itself starts to become a huge factor. Normally, my code sample here would be considered a candidate for refactoring, since you could write your own SetPixel and GetPixel methods that use an existing BitmapData object, but the processing time for the math inside the functions would be very small relative to the method overhead of each call. This is why I removed the Clamp calls in the original method as well.
One other way to speed this up would be to simply make it a "destructive" function, and modify the passed Bitmap parameter instead of making a copy and returning the modified copy.
#MusiGenesis,
Just wanted to note that I used this method for an image editor I've been writing. It works well, but sometimes this method triggers an AccessViolationException on this line:
byte B = row[columnOffset];
I realised it was because there was no standardisation of BitDepth, so if an image was 32 bit colour I was getting this error. So I changed this line:
BitmapData data = NewBitmap.LockBits(new Rectangle(0, 0, NewBitmap.Width, NewBitmap.Height), ImageLockMode.ReadWrite, NewBitmap.PixelFormat);
to:
BitmapData data = NewBitmap.LockBits(new Rectangle(0, 0, NewBitmap.Width, NewBitmap.Height), ImageLockMode.ReadWrite, PixelFormat.Format32bppRgb);
Hope this helps as it seems to have eradicated my problem.
Thanks for the post.
Jib
I'm a bit late, but use a color matrix implementation as these will be optimised for such transformations and is much easier than manipulating the pixels yourself: http://www.geekpedia.com/tutorial202_Using-the-ColorMatrix-in-Csharp.html
I have Bitmap. I want to apply median filter to my bitmap. But I can’t use GetPixel() and SetPixel() because speed is very important factor for me. I need very fast way to do it. May be it can be done with a Graphics.DrawImage(Image, Point[], Rectangle, GraphicsUnit, ImageAttributes).
After median filter I want to apply binaryzation filter (for each pixel calculate brightness: B=0.299*R+0.5876*G+0.114B, if brightness less than thresholdValue (thresholdValue is parametr for my task in [0...255]) then value of my pixel in result image is 1, otherwise - 0) Speed in binaryzation filter is important for me too
Just found this link: A fast way to grayscale an image in .NET (C#)
/// <summary>
/// Grayscales a given image.
/// </summary>
/// <param name="image">
/// The image that is transformed to a grayscale image.
/// </param>
public static void GrayScaleImage(Bitmap image)
{
if (image == null)
throw new ArgumentNullException("image");
// lock the bitmap.
var data = image.LockBits(
new Rectangle(0, 0, image.Width, image.Height),
ImageLockMode.ReadWrite, image.PixelFormat);
try
{
unsafe
{
// get a pointer to the data.
byte* ptr = (byte*)data.Scan0;
// loop over all the data.
for (int i = 0; i < data.Height; i++)
{
for (int j = 0; j < data.Width; j++)
{
// calculate the gray value.
byte y = (byte)(
(0.299 * ptr[2]) +
(0.587 * ptr[1]) +
(0.114 * ptr[0]));
// set the gray value.
ptr[0] = ptr[1] = ptr[2] = y;
// increment the pointer.
ptr += 3;
}
// move on to the next line.
ptr += data.Stride - data.Width * 3;
}
}
}
finally
{
// unlock the bits when done or when
// an exception has been thrown.
image.UnlockBits(data);
}
}
EDIT: See more info:
Using the LockBits method to access image data
GrayScale and ColorMatrix
Copy data to an array using CopyPixels, then operate on the array. Here is a code snippet where I take the average color:
int stride = (bmp.PixelWidth * bmp.Format.BitsPerPixel + 7) / 8;
byte[] pixels = new byte[bmp.PixelHeight * stride];
bmp.CopyPixels(pixels, stride, 0);
double[] averageComponents = new double[bmp.Format.BitsPerPixel / 8];
for (int pixel = 0; pixel < pixels.Length; pixel += averageComponents.Length)
{
for (int component = 0; component < averageComponents.Length; component++)
{
averageComponents[component] += pixels[pixel + component];
}
}
The filters you're using should run fast enough without any further optimizations (Just don't do something algorithmically slow).
If copying's too slow for you, use LockBits and an unsafe block to modify the resulting BitmapData structure directly.