I wanted to turn a regular for loop into a Parallel.For loop.
This-
for (int i = 0; i < bitmapImage.Width; i++)
{
for (int x = 0; x < bitmapImage.Height; x++)
{
System.Drawing.Color oc = bitmapImage.GetPixel(i, x);
int gray = (int)((oc.R * 0.3) + (oc.G * 0.59) + (oc.B * 0.11));
System.Drawing.Color nc = System.Drawing.Color.FromArgb(oc.A, gray, gray, gray);
bitmapImage.SetPixel(i, x, nc);
}
}
Into this-
Parallel.For(0, bitmapImage.Width - 1, i =>
{
Parallel.For(0, bitmapImage.Height - 1, x =>
{
System.Drawing.Color oc = bitmapImage.GetPixel(i, x);
int gray = (int)((oc.R * 0.3) + (oc.G * 0.59) + (oc.B * 0.11));
System.Drawing.Color nc = System.Drawing.Color.FromArgb(oc.A, gray, gray, gray);
bitmapImage.SetPixel(i, x, nc);
});
});
It fails with message-
Object is currently in use elsewhere.
at below line since multiple threads trying to access the non-thread safe reasources. Any idea how I can make this work?
System.Drawing.Color oc = bitmapImage.GetPixel(i, x);
It's not a clean solution, seeing what you would like to achieve. It would be better to get all the pixels in one shot, and then process them in the parallel for.
An alternative that I personally used, and improved the performance dramatically, is doing this conversion using unsafe functions to output a grayscale image.
public static byte[] MakeGrayScaleRev(byte[] source, ref Bitmap bmp,int Hei,int Wid)
{
int bytesPerPixel = 4;
byte[] bytesBig = new byte[Wid * Hei]; //create array to contain bitmap data with padding
unsafe
{
int ic = 0, oc = 0, x = 0;
//Convert the pixel to it's luminance using the formula:
// L = .299*R + .587*G + .114*B
//Note that ic is the input column and oc is the output column
for (int ind = 0, i = 0; ind < 4 * Hei * Wid; ind += 4, i++)
{
int g = (int)
((source[ind] / 255.0f) *
(0.301f * source[ind + 1] +
0.587f * source[ind + 2] +
0.114f * source[ind + 3]));
bytesBig[i] = (byte)g;
}
}
try
{
bmp = new Bitmap(Wid, Hei, PixelFormat.Format8bppIndexed);
bmp.Palette = GetGrayScalePalette();
Rectangle dimension = new Rectangle(0, 0, Wid, Hei);
BitmapData picData = bmp.LockBits(dimension, ImageLockMode.ReadWrite, bmp.PixelFormat);
IntPtr pixelStartAddress = picData.Scan0;
Marshal.Copy(forpictures, 0, pixelStartAddress, forpictures.Length);
bmp.UnlockBits(picData);
return bytesBig;
}
catch (Exception ex)
{
Console.WriteLine(ex.StackTrace);
return null;
}
}
It gets the bytearray of all the pixels of the input image, its height and width and output the computed grayscale array, and in ref Bitmap bmp the output grayscale bitmap.
Related
I'm currently in the development phase of a photoconverter program and in the process of developing a blur filter. At the initial stages of prototyping this feature, i devised a algorithm in which I had an accumulator for each color channel and add all the pixels in a radius of the target pixel. Afterwards the program would divide the accum by the amount of pixels read(not counting those offscreen). At first I thought this would be fine but when it started to work, I had the problem of this filter taking an hour to render with this being the result at the lowest setting. So I opted to utilize parallel processing in C# to make this process much easier and faster to run. With the boost of speed came the cost of the image becoming very glitched out. Here's the image before, and Here's the image afterwards
This is the code I wrote for the filter
public static DirectBitmap NewBlur (DirectBitmap image, int radius)
{
int sectorDiam = 128;
DirectBitmap newimage = image;
List<Rectangle> renderSectors = new List<Rectangle>();
Rectangle rect;
for (int x = 0; x < (image.Width / sectorDiam); x++)
{
int xwidth = sectorDiam;
for (int y = 0; y < (image.Height / sectorDiam); y++)
{
int yheight = sectorDiam;
rect = new Rectangle(x * sectorDiam, y * sectorDiam, xwidth, yheight);
renderSectors.Add(rect);
}
}
var Picrect = new Rectangle(0, 0, image.Width, image.Height);
var data = image.Bitmap.LockBits(Picrect, ImageLockMode.ReadWrite, image.Bitmap.PixelFormat);
var depth = Bitmap.GetPixelFormatSize(data.PixelFormat) / 8; //bytes per pixel
var buffer = new byte[data.Width * data.Height * depth];
Marshal.Copy(data.Scan0, buffer, 0, buffer.Length);
Parallel.ForEach(renderSectors, sector =>
{
BlurSection(buffer, sector, Picrect, radius, image.Width, image.Height, depth);
}
);
Marshal.Copy(buffer, 0, data.Scan0, buffer.Length);
image.Bitmap.UnlockBits(data);
return image;
}
And here's the method for each section of the image to be blurred.
public static void BlurSection(byte[] buffer, Rectangle blurSector, Rectangle bitmaprect, int radius, int width, int height, int depth)
{
int[] Accum = new int[4];
for (int x = blurSector.X; x < blurSector.Width+ blurSector.X; x++)
{
for (int y = blurSector.Y; y < blurSector.Height + blurSector.Y; y++)
{
Accum[0] = 0;
Accum[1] = 0;
Accum[2] = 0;
Accum[3] = 0;
for (int i = -radius; i <= radius; i++)
{
for (int j = -radius; j <= radius; j++)
{
var offset = 0;
offset = (((y+j) * width) + (x+i)) * depth;
if (bitmaprect.Contains(new Point(x + i, y + j))){
Accum[0] += buffer[offset + 0];
Accum[1] += buffer[offset + 1];
Accum[2] += buffer[offset + 2];
Accum[3]++;
}
}
}
Accum[0] = Accum[0] / Accum[3];
if (Accum[0] > 255)
{
Accum[0] = 255;
}
Accum[1] = Accum[1] / Accum[3];
if (Accum[1] > 255)
{
Accum[1] = 255;
}
Accum[2] = Accum[2] / Accum[3];
if (Accum[2] > 255)
{
Accum[2] = 255;
}
var newoffset = ((y * width) + (x * depth*2));
buffer[newoffset + 0] = (byte)Accum[0];
buffer[newoffset + 1] = (byte)Accum[1];
buffer[newoffset + 2] = (byte)Accum[2];
}
}
}
It's also worth noting that I'm using a Bitmap class to make access to pixel data much easier, the "DirectBitmap" you can find here: https://stackoverflow.com/a/34801225/15473435. Is there anything that I'm missing or not aware of that's causing this algorithm not to function?
I have written a code that converts image to grayscale. but the code only convert partial of it.
I am trying to convert this code to a Parallel computation. I end up with bugs that I can not get my head around them. Any suggestion?
private void button2_Click(object sender, EventArgs e)
{
Bitmap bmp = (Bitmap)pictureBox1.Image;
unsafe {
//get image dimension
//int width = bmp.Width;
//int height = bmp.Height;
BitmapData bitmapData = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.ReadWrite, bmp.PixelFormat);
//define variable
int bpp = System.Drawing.Bitmap.GetPixelFormatSize(bmp.PixelFormat) / 8;
int hip = bitmapData.Height;
int wib = bitmapData.Width + bpp;
//point to first pixel
byte* PtrFirstPixel = (byte*)bitmapData.Scan0;
//color of pixel
// Color p;
//grayscale
Parallel.For(0, hip, y =>
{
byte* currentLine = PtrFirstPixel + (y * bitmapData.Stride);
for (int x = 0; x < wib; x = x + bpp)
{
//get pixel value
//p = bmp.GetPixel(x, y);
//extract pixel component ARGB
//int a = p.A;
//int r = p.R;
//int g = p.G;
// int b = p.B;
int b = currentLine[x];
int g = currentLine[x + 1];
int r = currentLine[x + 2];
//find average
int avg = (r + g + b) / 3;
//set new pixel value
// bmp.SetPixel(x, y, Color.FromArgb(a, avg, avg, avg));
currentLine[x] = (byte)avg;
currentLine[x + 1] = (byte)avg;
currentLine[x + 2] = (byte)avg;
}
});
bmp.UnlockBits(bitmapData);
//load grayscale image in picturebox2
//pictureBox2.Image = bmp;
}
pictureBox2.Image = bmp;
}
my out put image
int wib = bitmapData.Width + bpp;
should be:
int wib = bitmapData.Width * bpp;
You want the number of bytes which requires a multiply, not an add. There may be other issues, but this is definitely incorrect.
Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 8 years ago.
Improve this question
here is the code is there any way to make it faster cause its slowerden single
public Bitmap pSetInvert(Bitmap _currentBitmap)
{
Bitmap temp = (Bitmap)_currentBitmap;
Bitmap bmap = (Bitmap)temp.Clone();
Color c;
Parallel.For(0, bmap.Width, i =>
{
lock (bmap)
{
for (int j = 0; j < bmap.Height; j++)
{
c = bmap.GetPixel(i, j);
bmap.SetPixel(i, j, Color.FromArgb(255 - c.R, 255 - c.G, 255 - c.B));
}
}
});
return (Bitmap)bmap.Clone();
}
By using lock you are making parallel part of your code to work serially, exactly like a single thread application with synchronization overhead so it actually would be slower.
here is a helper class that access bitmap data directly and you can manipulate image in concurrently.
FastImage Helper Class:
using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Runtime.InteropServices;
namespace ImageManipulation
{
class FastImage : IDisposable
{
private Bitmap _buffer;
private byte[] _rawData;
private GCHandle _rawHandle;
private int _formatSize;
private int _width;
private int _height;
public int Width
{
get { return _width; }
}
public int Height
{
get { return _height; }
}
public byte[] GetRawData()
{
return _rawData;
}
public byte this[int index]
{
get { return _rawData[index]; }
set { _rawData[index] = value; }
}
public Color this[int x, int y]
{
get
{
return GetPixel(x, y);
}
set
{
SetPixel(x, y, value);
}
}
public Color GetPixel(int x, int y)
{
var offset = y*_width*_formatSize;
offset += x*_formatSize;
return Color.FromArgb(_rawData[offset + 3], _rawData[offset + 2], _rawData[offset + 1], _rawData[offset]);
}
public void SetPixel(int x, int y, Color value)
{
var offset = y*_width*_formatSize;
offset += x*_formatSize;
_rawData[offset] = value.B;
_rawData[offset + 1] = value.G;
_rawData[offset + 2] = value.R;
_rawData[offset + 3] = value.A;
}
private FastImage() { }
public static FastImage Create(Image source)
{
var image = new FastImage();
var bmpSource = new Bitmap(source);
var bmpData = bmpSource.LockBits(new Rectangle(0, 0, source.Width, source.Height), System.Drawing.Imaging.ImageLockMode.ReadWrite, bmpSource.PixelFormat);
image._width = source.Width;
image._height = source.Height;
image._formatSize = 4;
var stride = bmpSource.Width * image._formatSize;
image._rawData = new byte[stride * bmpSource.Height];
image._rawHandle = GCHandle.Alloc(image._rawData, GCHandleType.Pinned);
var pointer = Marshal.UnsafeAddrOfPinnedArrayElement(image._rawData, 0);
image._buffer = new Bitmap(bmpSource.Width, bmpSource.Height, stride, PixelFormat.Format32bppArgb /*bmpSource.PixelFormat*/, pointer);
bmpSource.UnlockBits(bmpData);
var graphics = Graphics.FromImage(image._buffer);
graphics.DrawImageUnscaledAndClipped(bmpSource, new Rectangle(0, 0, source.Width, source.Height));
graphics.Dispose();
return image;
}
public void Dispose()
{
_rawHandle.Free();
_buffer.Dispose();
}
public void Save(Stream stream)
{
_buffer.Save(stream, ImageFormat.Bmp);
}
public Bitmap ToBitmap()
{
return (Bitmap)_buffer.Clone();
}
}
}
and here is your code using FastImage class:
public Bitmap pSetInvert(Bitmap _currentBitmap)
{
using (var bmap = FastImage.Create(_currentBitmap))
{
Parallel.For(0, bmap.Width, i =>
{
for (int j = 0; j < bmap.Height; j++)
{
var c = bmap.GetPixel(i, j);
bmap.SetPixel(i, j, Color.FromArgb(255 - c.R, 255 - c.G, 255 - c.B));
}
});
return bmap.ToBitmap();
}
}
The lock in the Parallel.For is going to cause the code to run slower than with a single-threaded loop. The lock only allows one thread at a time to do useful work, with the added cost of acquiring the lock.
Additionally, GetPixel and SetPixel are extremely slow. They are also not guaranteed to be thread-safe, which is probably why you are getting the InvalidOperationException
Any public static (Shared in Visual Basic) members of this type are thread safe. Any instance members are not guaranteed to be thread safe.
http://msdn.microsoft.com/en-us/library/System.Drawing.Bitmap(v=vs.110).aspx
Have a look instead at WriteableBitmap. Though the class was introduced with WPF, you can use it from a variety of environments. I recently used it in a console application. WriteableBitmap can be converted to a standard bitmap if needed, or written to a ".bmp" file.
Alternatively, you can use unsafe code to directly access the Bitmap buffer.
If you need to, you can use multiple threads for either WriteableBitmap or unsafe access to the Bitmap buffer since you are directly reading/writing memory.
Here are two versions of your filter routine you can play with. They take the image of one PictureBox and after running through the filter assign it to a second PictureBox.
The first one uses LockBits and can do 10.000 (!) loops for a ca 400x400 image in 40 seconds on my machine.
The second uses Parallel.For in addition to the LockBits and does the same job in 35 seconds here.
Of course these timings are strongly influenced by overhead (and maybe by dumb bugs I made.) But really, using Lockbits is key to speed here as there is so little to 'compute' the parallelization management itself eats up most of the cores' time..
using System.Runtime.InteropServices;
// ..
public void filter1()
{
if (pictureBox2.Image != null) pictureBox2.Image.Dispose();
Bitmap bmp = new Bitmap(pictureBox1.Image);
Size s1 = bmp.Size;
PixelFormat fmt1 = bmp.PixelFormat;
Rectangle rect = new Rectangle(0, 0, s1.Width, s1.Height);
BitmapData bmpData = bmp.LockBits(rect, ImageLockMode.ReadWrite, fmt1);
byte bpp = 4; // <<-------------------set to 3 for 24bbp !!
int size1 = bmpData.Stride * bmpData.Height;
byte[] data = new byte[size1];
Marshal.Copy(bmpData.Scan0, data, 0, size1);
for (int y = 0; y < s1.Height; y++)
{
for (int x = 0; x < s1.Width; x++)
{
int index = y * bmpData.Stride + x * bpp;
data[index + 0] = (byte) (255 - data[index + 0]); // Blue
data[index + 1] = (byte) (255 - data[index + 1]); // Green
data[index + 2] = (byte) (255 - data[index + 2]); // Red
data[index + 3] = 255; // Alpha, comment out for 24 bpp!
}
}
Marshal.Copy(data, 0, bmpData.Scan0, data.Length);
bmp.UnlockBits(bmpData);
pictureBox2.Image = bmp;
}
public void filter2()
{
if (pictureBox2.Image != null) pictureBox2.Image.Dispose();
Bitmap bmp1 = new Bitmap(pictureBox1.Image);
Size s1 = bmp1.Size;
Bitmap bmp2 = new Bitmap(s1.Width, s1.Height);
PixelFormat fmt1 = bmp1.PixelFormat;
Rectangle rect = new Rectangle(0, 0, s1.Width, s1.Height);
BitmapData bmpData1 = bmp1.LockBits(rect, ImageLockMode.ReadOnly, fmt1);
BitmapData bmpData2 = bmp2.LockBits(rect, ImageLockMode.WriteOnly, fmt1);
byte bpp = 4; // set to 3 for 24bbp !!
int size1 = bmpData1.Stride * bmpData1.Height;
byte[] data1 = new byte[size1];
byte[] data2 = new byte[size1];
Marshal.Copy(bmpData1.Scan0, data1, 0, size1);
Marshal.Copy(bmpData2.Scan0, data2, 0, size1);
int degreeOfParallelism = Environment.ProcessorCount - 1;
var options = new ParallelOptions();
options.MaxDegreeOfParallelism = degreeOfParallelism;
Parallel.For(0, bmp1.Width, options, y =>
{
{
for (int x = 0; x < s1.Width; x++)
{
int index = y * bmpData1.Stride + x * bpp;
data2[index + 0] = (byte)(255 - data1[index + 0]); // Blue
data2[index + 1] = (byte)(255 - data1[index + 1]); // Green
data2[index + 2] = (byte)(255 - data1[index + 2]); // Red
data2[index + 3] = 255; // Alpha, comment out for 24 bpp!
}
}
});
Marshal.Copy(data2, 0, bmpData2.Scan0, data2.Length);
bmp1.UnlockBits(bmpData1);
bmp2.UnlockBits(bmpData2);
pictureBox2.Image = bmp2;
}
Note that the 2nd filter routine needs to work on a second Bitmap for general purpose filter algorithms. This simple inversion filter doesn't really need it, but once you write things that access neighbouring pixels like eg.g a blur filter you do..
Also note the order of the channel bytes!
My Application
I am writing an application that needs to convert RGB to grayscale images.
The conversion works but converting an image of 3648 * 2736 pixel takes round about 7 secs.
I know that set and getpixel take some time.
But I think that it shouldn't take so long if you are using Lockbits even though the image is not small. (please correct me if that is wrong).
Maybe I just did a fatal mistake within my code.
The code
public static long ConvertToGrayScaleV2(Bitmap imageColor, bool useHDTVConversion)
{
Stopwatch stpw = new Stopwatch();
stpw.Start();
System.Drawing.Imaging.BitmapData imageColorData = imageColor.LockBits(new Rectangle(new Point(0, 0), imageColor.Size),
System.Drawing.Imaging.ImageLockMode.ReadWrite, imageColor.PixelFormat);
IntPtr PtrColor = imageColorData.Scan0;
int strideColor = imageColorData.Stride;
byte[] byteImageColor = new byte[Math.Abs(strideColor) * imageColor.Height];
System.Runtime.InteropServices.Marshal.Copy(PtrColor, byteImageColor, 0, Math.Abs(strideColor) * imageColor.Height);
int bytesPerPixel = getBytesPerPixel(imageColor);
byte value;
if (bytesPerPixel == -1)
throw new Exception("Can't get bytes per pixel because it is not defined for this image format.");
for (int x = 0, position; x < imageColor.Width * imageColor.Height; x++)
{
position = x * bytesPerPixel;
if (useHDTVConversion)
{
value = (byte)(byteImageColor[position] * 0.0722 + byteImageColor[position + 1] * 0.7152 + byteImageColor[position + 2] * 0.2126);
}
else
{
value = (byte)(byteImageColor[position] * 0.114 + byteImageColor[position + 1] * 0.587 + byteImageColor[position + 2] * 0.299);
}
byteImageColor[position] = value;
byteImageColor[position+1] = value;
byteImageColor[position+2] = value;
}
System.Runtime.InteropServices.Marshal.Copy(byteImageColor, 0, PtrColor, Math.Abs(strideColor) * imageColor.Height);
imageColor.UnlockBits(imageColorData);
stpw.Stop();
return stpw.ElapsedMilliseconds;
}
public static int getBytesPerPixel(Image img)
{
switch (img.PixelFormat)
{
case System.Drawing.Imaging.PixelFormat.Format16bppArgb1555: return 2;
case System.Drawing.Imaging.PixelFormat.Format16bppGrayScale: return 2;
case System.Drawing.Imaging.PixelFormat.Format16bppRgb555: return 2;
case System.Drawing.Imaging.PixelFormat.Format16bppRgb565: return 2;
case System.Drawing.Imaging.PixelFormat.Format1bppIndexed: return 1;
case System.Drawing.Imaging.PixelFormat.Format24bppRgb: return 3;
case System.Drawing.Imaging.PixelFormat.Format32bppArgb: return 4;
case System.Drawing.Imaging.PixelFormat.Format32bppPArgb: return 4;
case System.Drawing.Imaging.PixelFormat.Format32bppRgb: return 4;
case System.Drawing.Imaging.PixelFormat.Format48bppRgb: return 6;
case System.Drawing.Imaging.PixelFormat.Format4bppIndexed: return 1;
case System.Drawing.Imaging.PixelFormat.Format64bppArgb: return 8;
case System.Drawing.Imaging.PixelFormat.Format64bppPArgb: return 8;
case System.Drawing.Imaging.PixelFormat.Format8bppIndexed: return 1;
default: return -1;
}
}
I know this is old, but a few possible valuable points:
The imageColor.Width * imageColor.Height is an expensive operation that you are running nearly 10 million times (3648 * 2736) more than you need to.
The for loop is recalculating that every single iteration
Not only that, but the CLR has to navigate to the Bitmap object's Width and Height properties each of those 10 million times, too. This is 30 million more operations than you need every time you try to run this on your bitmap.
Change:
for (int x = 0, position; x < imageColor.Width * imageColor.Height; x++)
{
...
}
To:
var heightWidth = imageColor.Width * imageColor.Height;
for (int x = 0, position; x < heightWidth; x++)
{
...
}
If you cache all the potential results of the three various operations (R, G, B, with 255 possible values) and use a lookup to the new values instead of calculating the new value 10 million times you'll also see a huge performance increase.
Here is the full, very fast code (much faster than a ColorMatrix). Notice I have moved all possible pre-calculated values into local variables and within the loop there absolutely minimal work involved.
var lookupR = new byte[256];
var lookupG = new byte[256];
var lookupB = new byte[256];
var rVal = hdtv ? 0.114 : 0.0722;
var gVal = hdtv ? 0.587 : 0.7152;
var bVal = hdtv ? 0.299 : 0.2126;
for (var originalValue = 0; originalValue < 256; originalValue++)
{
var r = (byte)(originalValue * rVal);
var g = (byte)(originalValue * gVal);
var b = (byte)(originalValue * bVal);
// Just in case...
if (r > 255) r = 255;
if (g > 255) g = 255;
if (b > 255) b = 255;
lookupR[originalValue] = r;
lookupG[originalValue] = g;
lookupB[originalValue] = b;
}
unsafe
{
var pointer = (byte*)(void*)bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height), ImageLockMode.ReadWrite, bitmap.PixelFormat);
var bytesPerPixel = getBytesPerPixel(bitmap);
var heightWidth = bitmap.Width * bitmap.Height;
for (var y = 0; y < heightWidth; ++y)
{
var value = (byte) (lookupR[pointer[0]] + lookupG[pointer[1]] + lookupB[pointer[2]]);
pointer[0] = value;
pointer[1] = value;
pointer[2] = value;
pointer += bytesPerPixel;
}
bitmap.UnlockBits();
}
break;
I ran across a similar performance issue where I need to iterate over an array of bitmap data. I found that there is a significant performance hit referencing the width or height properties of the bitmap within or as the bounds for the loop like you are doing with imagecolor.width and .height. By simply declaring an integer outside the loop and caching the bitmap height and width there in advance, I cut my loop time in half.
If you're converting to greyscale, try using a ColorMatrix transformation instead.
from: https://web.archive.org/web/20141230145627/http://bobpowell.net/grayscale.aspx
Image img = Image.FromFile(dlg.FileName);
Bitmap bm = new Bitmap(img.Width,img.Height);
Graphics g = Graphics.FromImage(bm);
ColorMatrix cm = new ColorMatrix(new float[][]{ new float[]{0.5f,0.5f,0.5f,0,0},
new float[]{0.5f,0.5f,0.5f,0,0},
new float[]{0.5f,0.5f,0.5f,0,0},
new float[]{0,0,0,1,0,0},
new float[]{0,0,0,0,1,0},
new float[]{0,0,0,0,0,1}});
/*
//Gilles Khouzams colour corrected grayscale shear
ColorMatrix cm = new ColorMatrix(new float[][]{ new float[]{0.3f,0.3f,0.3f,0,0},
new float[]{0.59f,0.59f,0.59f,0,0},
new float[]{0.11f,0.11f,0.11f,0,0},
new float[]{0,0,0,1,0,0},
new float[]{0,0,0,0,1,0},
new float[]{0,0,0,0,0,1}});
*/
ImageAttributes ia = new ImageAttributes();
ia.SetColorMatrix(cm);
g.DrawImage(img,new Rectangle(0,0,img.Width,img.Height),0,0,img.Width,img.Height,GraphicsUnit.Pixel,ia);
g.Dispose();
I would guess that the marshalling and copying is taking a large chunk of the time.
This link describes 3 methods for greyscaling an image.
I was wondering if anyone could shed some light on improvements I can do in making this compositing algorithm faster. What is does is takes 3 images splits them up to get the 1st Images Red Channel, 2nd Images Green channel and the 3rd Images Blue channel and composites them together into 1 new image. Now it works but at an excruciatingly slow pace. The reason i think down to the pixel by pixel processing it has to do on all image components.
The process is to :
For all images:
Extract respective R G and B values -> composite into 1 image -> Save new Image.
foreach (Image[] QRE2ImgComp in QRE2IMGArray)
{
Globals.updProgress = "Processing frames: " + k + " of " + QRE2IMGArray.Count + " frames done.";
QRMProgressUpd(EventArgs.Empty);
Image RedLayer = GetRedImage(QRE2ImgComp[0]);
QRE2ImgComp[0] = RedLayer;
Image GreenLayer = GetGreenImage(QRE2ImgComp[1]);
QRE2ImgComp[1] = GreenLayer;
Image BlueLayer = GetBlueImage(QRE2ImgComp[2]);
QRE2ImgComp[2] = BlueLayer;
Bitmap composite = new Bitmap(QRE2ImgComp[0].Height, QRE2ImgComp[0].Width);
Color Rlayer,Glayer,Blayer;
byte R, G, B;
for (int y = 0; y < composite.Height; y++)
{
for (int x = 0; x < composite.Width; x++)
{
//pixelColorAlpha = composite.GetPixel(x, y);
Bitmap Rcomp = new Bitmap(QRE2ImgComp[0]);
Bitmap Gcomp = new Bitmap(QRE2ImgComp[1]);
Bitmap Bcomp = new Bitmap(QRE2ImgComp[2]);
Rlayer = Rcomp.GetPixel(x, y);
Glayer = Gcomp.GetPixel(x, y);
Blayer = Bcomp.GetPixel(x, y);
R = (byte)(Rlayer.R);
G = (byte)(Glayer.G);
B = (byte)(Blayer.B);
composite.SetPixel(x, y, Color.FromArgb((int)R, (int)G, (int)B));
}
}
Globals.updProgress = "Saving frame...";
QRMProgressUpd(EventArgs.Empty);
Image tosave = composite;
Globals.QRFrame = tosave;
tosave.Save("C:\\QRItest\\E" + k + ".png", ImageFormat.Png);
k++;
}
For reference here is the red channel filter method relatively the same for blue and green:
public Image GetRedImage(Image sourceImage)
{
Bitmap bmp = new Bitmap(sourceImage);
Bitmap redBmp = new Bitmap(sourceImage.Width, sourceImage.Height);
for (int x = 0; x < bmp.Width; x++)
{
for (int y = 0; y < bmp.Height; y++)
{
Color pxl = bmp.GetPixel(x, y);
Color redPxl = Color.FromArgb((int)pxl.R, 0, 0);
redBmp.SetPixel(x, y, redPxl);
}
}
Image tout = (Image)redBmp;
return tout;
}
Move these
Bitmap Rcomp = new Bitmap(QRE2ImgComp[0]);
Bitmap Gcomp = new Bitmap(QRE2ImgComp[1]);
Bitmap Bcomp = new Bitmap(QRE2ImgComp[2]);
outside the for-loops!
Other very important points:
avoid using GetPixel - it is VERY SLOW!
Checkout LockBits etc. - this is how pixel-level access is usually done in .NET
Consider using a 3rd-party library (free or commercial)... several have some optimized method built-in to do what you are trying to achieve...
I totally agree with the points Yahia listed in his answer to improve performance. I'd like to add one more point regarding performance. You could use the Parallel class of the .Net Framework to parallelize the execution of your for loops. The following example makes use of the LockBits method and the Parallel class to improve performance (assuming 32 bits per pixel (PixelFormat.Format32bppArgb)):
public unsafe static Bitmap GetBlueImagePerf(Image sourceImage)
{
int width = sourceImage.Width;
int height = sourceImage.Height;
Bitmap bmp = new Bitmap(sourceImage);
Bitmap redBmp = new Bitmap(width, height, bmp.PixelFormat);
BitmapData bd = bmp.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.ReadOnly, PixelFormat.Format32bppRgb);
BitmapData bd2 = redBmp.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.WriteOnly, PixelFormat.Format32bppRgb);
byte* source = (byte*)bd.Scan0.ToPointer();
byte* target = (byte*)bd2.Scan0.ToPointer();
int stride = bd.Stride;
Parallel.For(0, height, (y1) =>
{
byte* s = source + (y1 * stride);
byte* t = target + (y1 * stride);
for (int x = 0; x < width; x++)
{
// use t[1], s[1] to access green channel
// use t[2], s[2] to access red channel
t[0] = s[0];
t += 4; // Add bytes per pixel to current position.
s += 4; // For other pixel formats this value is different.
}
});
bmp.UnlockBits(bd);
redBmp.UnlockBits(bd2);
return redBmp;
}
public unsafe static void DoImageConversion()
{
Bitmap RedLayer = GetRedImagePerf(Image.FromFile("image_path1"));
Bitmap GreenLayer = GetGreenImagePerf(Image.FromFile("image_path2"));
Bitmap BlueLayer = GetBlueImagePerf(Image.FromFile("image_path3"));
Bitmap composite =
new Bitmap(RedLayer.Width, RedLayer.Height, RedLayer.PixelFormat);
BitmapData bd = composite.LockBits(new Rectangle(0, 0, RedLayer.Width, RedLayer.Height), ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);
byte* comp = (byte*)bd.Scan0.ToPointer();
BitmapData bdRed = RedLayer.LockBits(new Rectangle(0, 0, RedLayer.Width, RedLayer.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
BitmapData bdGreen = GreenLayer.LockBits(new Rectangle(0, 0, RedLayer.Width, RedLayer.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
BitmapData bdBlue = BlueLayer.LockBits(new Rectangle(0, 0, RedLayer.Width, RedLayer.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
byte* red = (byte*)bdRed.Scan0.ToPointer();
byte* green = (byte*)bdGreen.Scan0.ToPointer();
byte* blue = (byte*)bdBlue.Scan0.ToPointer();
int stride = bdRed.Stride;
Parallel.For(0, bdRed.Height, (y1) =>
{
byte* r = red + (y1 * stride);
byte* g = green + (y1 * stride);
byte* b = blue + (y1 * stride);
byte* c = comp + (y1 * stride);
for (int x = 0; x < bdRed.Width; x++)
{
c[0] = b[0];
c[1] = g[1];
c[2] = r[2];
r += 4; // Add bytes per pixel to current position.
g += 4; // For other pixel formats this value is different.
b += 4; // Use Image.GetPixelFormatSize to get number of bits per pixel
c += 4;
}
});
composite.Save("save_image_path", ImageFormat.Jpeg);
}
Hope, this answer gives you a starting point for improving your code.