Convert an image to grayscale parallel loop - c#

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.

Related

Synchronization within nested Parallel.For loops

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.

How to apply blur effect on a bitmap image in C#? [closed]

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 5 years ago.
Improve this question
How can I apply blur effect on an image in C# without using a library?
Updated code (now much faster, requires use of UNSAFE keyword)
static void Main(string[] args)
{
Bitmap bitmap = new Bitmap("C:\\Users\\erik\\test.png");
bitmap = Blur(bitmap, 10);
bitmap.Save("C:\\Users\\erik\\test2.png");
}
private static Bitmap Blur(Bitmap image, Int32 blurSize)
{
return Blur(image, new Rectangle(0, 0, image.Width, image.Height), blurSize);
}
private unsafe static Bitmap Blur(Bitmap image, Rectangle rectangle, Int32 blurSize)
{
Bitmap blurred = new Bitmap(image.Width, image.Height);
// make an exact copy of the bitmap provided
using (Graphics graphics = Graphics.FromImage(blurred))
graphics.DrawImage(image, new Rectangle(0, 0, image.Width, image.Height),
new Rectangle(0, 0, image.Width, image.Height), GraphicsUnit.Pixel);
// Lock the bitmap's bits
BitmapData blurredData = blurred.LockBits(new Rectangle(0, 0, image.Width, image.Height), ImageLockMode.ReadWrite, blurred.PixelFormat);
// Get bits per pixel for current PixelFormat
int bitsPerPixel = Image.GetPixelFormatSize(blurred.PixelFormat);
// Get pointer to first line
byte* scan0 = (byte*)blurredData.Scan0.ToPointer();
// look at every pixel in the blur rectangle
for (int xx = rectangle.X; xx < rectangle.X + rectangle.Width; xx++)
{
for (int yy = rectangle.Y; yy < rectangle.Y + rectangle.Height; yy++)
{
int avgR = 0, avgG = 0, avgB = 0;
int blurPixelCount = 0;
// average the color of the red, green and blue for each pixel in the
// blur size while making sure you don't go outside the image bounds
for (int x = xx; (x < xx + blurSize && x < image.Width); x++)
{
for (int y = yy; (y < yy + blurSize && y < image.Height); y++)
{
// Get pointer to RGB
byte* data = scan0 + y * blurredData.Stride + x * bitsPerPixel / 8;
avgB += data[0]; // Blue
avgG += data[1]; // Green
avgR += data[2]; // Red
blurPixelCount++;
}
}
avgR = avgR / blurPixelCount;
avgG = avgG / blurPixelCount;
avgB = avgB / blurPixelCount;
// now that we know the average for the blur size, set each pixel to that color
for (int x = xx; x < xx + blurSize && x < image.Width && x < rectangle.Width; x++)
{
for (int y = yy; y < yy + blurSize && y < image.Height && y < rectangle.Height; y++)
{
// Get pointer to RGB
byte* data = scan0 + y * blurredData.Stride + x * bitsPerPixel / 8;
// Change values
data[0] = (byte)avgB;
data[1] = (byte)avgG;
data[2] = (byte)avgR;
}
}
}
}
// Unlock the bits
blurred.UnlockBits(blurredData);
return blurred;
}
Took 2.356 seconds to process 256x256 image with blur value 10.
Original Code (from Github - slightly altered)
static void Main(string[] args)
{
Bitmap bitmap = new Bitmap("C:\\Users\\erik\\test.png");
bitmap = Blur(bitmap, 10);
bitmap.Save("C:\\Users\\erik\\test2.png");
}
private static Bitmap Blur(Bitmap image, Int32 blurSize)
{
return Blur(image, new Rectangle(0, 0, image.Width, image.Height), blurSize);
}
private static Bitmap Blur(Bitmap image, Rectangle rectangle, Int32 blurSize)
{
Bitmap blurred = new Bitmap(image.Width, image.Height);
// make an exact copy of the bitmap provided
using (Graphics graphics = Graphics.FromImage(blurred))
graphics.DrawImage(image, new Rectangle(0, 0, image.Width, image.Height),
new Rectangle(0, 0, image.Width, image.Height), GraphicsUnit.Pixel);
// look at every pixel in the blur rectangle
for (int xx = rectangle.X; xx < rectangle.X + rectangle.Width; xx++)
{
for (int yy = rectangle.Y; yy < rectangle.Y + rectangle.Height; yy++)
{
int avgR = 0, avgG = 0, avgB = 0;
int blurPixelCount = 0;
// average the color of the red, green and blue for each pixel in the
// blur size while making sure you don't go outside the image bounds
for (int x = xx; (x < xx + blurSize && x < image.Width); x++)
{
for (int y = yy; (y < yy + blurSize && y < image.Height); y++)
{
Color pixel = blurred.GetPixel(x, y);
avgR += pixel.R;
avgG += pixel.G;
avgB += pixel.B;
blurPixelCount++;
}
}
avgR = avgR / blurPixelCount;
avgG = avgG / blurPixelCount;
avgB = avgB / blurPixelCount;
// now that we know the average for the blur size, set each pixel to that color
for (int x = xx; x < xx + blurSize && x < image.Width && x < rectangle.Width; x++)
for (int y = yy; y < yy + blurSize && y < image.Height && y < rectangle.Height; y++)
blurred.SetPixel(x, y, Color.FromArgb(avgR, avgG, avgB));
}
}
return blurred;
}
Took 7.594 seconds to process 256x256 image with blur value 10.
Orignal Image
Blurred Image (blur level 10)
Image from: https://www.pexels.com/search/landscape/
If you are using XAML check it from Microsoft. Also study this code it your problem can be solved.

Color to Bitmap

How do I go about setting a Bitmap with a Color of the pixels. I created a program with LockBits and it is very fast but now I need to set a PictureBox with that image I ran through the LockBits I do not want to use SetPixels My current code is:
Bitmap imageFile = new Bitmap(bmpPath);
BitmapData imageData = imageFile.LockBits(new Rectangle(0, 0, imageFile.Width, imageFile.Height), ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb);
IntPtr Pointer = imageData.Scan0;
int ArraySize = Math.Abs(imageData.Stride) * imageFile.Height;
byte[] PixelArray = new byte[ArraySize];
Marshal.Copy(Pointer, PixelArray, 0, ArraySize);
int PixelAmount = 4; //ArGb
Color ArGBformat;
Bitmap RenderedImage = new Bitmap(imageFile.Width, imageFile.Height);
byte NewAlpha;
byte NewRed;
byte NewGreen;
byte NewBlue;
unsafe
{
for (int y = 0; y < imageData.Height; y++)
{
byte* row = (byte*)imageData.Scan0 + (y * imageData.Stride);
for (int x = 0; x < imageData.Width; x++)
{
int offSet = x * PixelAmount;
// read pixels
byte blue = row[offSet];
byte green = row[offSet + 1];
byte red = row[offSet + 2];
byte alpha = row[offSet + 3];
//Manipulates pixels
NewAlpha = Convert.ToByte(Math.Abs(alpha - _Alpha));
NewRed = Convert.ToByte(Math.Abs(red - _Red));
NewBlue = Convert.ToByte(Math.Abs(blue - _Blue));
NewGreen = Convert.ToByte(Math.Abs(green - _Green));
ArGBformat = Color.FromArgb(NewAlpha, NewRed, NewGreen, NewBlue);
RenderedImage.SetPixel(x, y, ArGBformat); //Slow and want something else
}
}
}
I would like to set my PictureBox1 to the pixels that get ran through the program.
Found the answer. I needed to set the pixels back.
//Sets image
row[offSet] = NewBlue;
row[offSet + 1] = NewGreen;
row[offSet + 2] = NewRed;
row[offSet + 3] = NewAlpha;

Getting different bitmap object from same image file

I am creating Bitmap object from the same image, but in the end getting different results. It should calculate deviation from the image colors and images are the same so why results are different?
double test1 = GetStdDev("C:\\temp\\images\\file.jpg");
Bitmap img = new Bitmap("C:\\temp\\images\\file.jpg");
double test2 = GetStdDev(img);
public static double GetStdDev(string imageFileName)
{
double total = 0, totalVariance = 0;
int count = 0;
double stdDev = 0;
// First get all the bytes
using (Bitmap b = new Bitmap(imageFileName))
{
BitmapData bmData = b.LockBits(new Rectangle(0, 0, b.Width, b.Height), ImageLockMode.ReadOnly, b.PixelFormat);
int stride = bmData.Stride;
IntPtr Scan0 = bmData.Scan0;
unsafe
{
byte* p = (byte*)(void*)Scan0;
int nOffset = stride - b.Width * 3;
for (int y = 0; y < b.Height; ++y)
{
for (int x = 0; x < b.Width; ++x)
{
count++;
byte blue = p[0];
byte green = p[1];
byte red = p[2];
int pixelValue = Color.FromArgb(0, red, green, blue).ToArgb();
total += pixelValue;
double avg = total / count;
totalVariance += Math.Pow(pixelValue - avg, 2);
stdDev = Math.Sqrt(totalVariance / count);
p += 3;
}
p += nOffset;
}
}
b.UnlockBits(bmData);
}
return stdDev;
}
private static double GetStdDev(Bitmap img)
{
double total = 0, totalVariance = 0;
int count = 0;
double stdDev = 0;
// First get all the bytes
using (Bitmap b = new Bitmap(img))
{
BitmapData bmData = b.LockBits(new Rectangle(0, 0, b.Width, b.Height), ImageLockMode.ReadOnly, b.PixelFormat);
int stride = bmData.Stride;
IntPtr Scan0 = bmData.Scan0;
unsafe
{
byte* p = (byte*)(void*)Scan0;
int nOffset = stride - b.Width * 3;
for (int y = 0; y < b.Height; ++y)
{
for (int x = 0; x < b.Width; ++x)
{
count++;
byte blue = p[0];
byte green = p[1];
byte red = p[2];
int pixelValue = Color.FromArgb(0, red, green, blue).ToArgb();
total += pixelValue;
double avg = total / count;
totalVariance += Math.Pow(pixelValue - avg, 2);
stdDev = Math.Sqrt(totalVariance / count);
p += 3;
}
p += nOffset;
}
}
b.UnlockBits(bmData);
}
return stdDev;
}
Just found solution, it was problem with deep copying bitmap object. instead using new Bitmap(img) use Bitmap img2 = (Bitmap) img.Clone(); don't know it is the right solution, but it does the job.

Improving Image compositing Algorithm c# .NET

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.

Categories