I am trying to figure this one out. I am comparing images in my program. For the most part when I try and compare the image I have no issues. Although when I use a few different combinations I get this error. I originally thought it was because the image formats were different. Although that is not the case.
If I take each image and compare them against something else it works fine. Just not when compared against eachother. I have marked below where the error appears.
public Color this[int x, int y]
{
get
{
int index = (x + (y * image.Width)) * 4;
return Color.FromArgb(rgbValues[index + 3], rgbValues[index + 2], rgbValues[index + 1], rgbValues[index]); //This line is where the index out of bounds of the array error happens.
}
set
{
int index = (x + (y * image.Width)) * 4;
rgbValues[index] = value.B;
rgbValues[index + 1] = value.G;
rgbValues[index + 2] = value.R;
rgbValues[index + 3] = value.A;
}
}
/// <summary>
/// Width of the image.
/// </summary>
public int Width
{
get
{
return image.Width;
}
}
/// <summary>
/// Height of the image.
/// </summary>
public int Height
{
get
{
return image.Height;
}
}
/// <summary>
/// Returns the modified Bitmap.
/// </summary>
I would appreciate any help. I am so confused to why it only happens when the images are used in combination.
In case you were wondering how I load the image I do it in vb.net. The C# code is in a dll. Here is my vb.net code to load the bitmap to the memory. Also the image size is: 431x253
Dim bm As Bitmap = Bitmap.FromFile(Label1.Text)
Dim bm2 As Bitmap = Bitmap.FromFile(Label2.Text)
' Dim pnt As Point = ImageFinder.Contains(bm, bm2)
Dim ir As New ImageChecker(bm, bm2)
Dim imageContains As Boolean = ir.findimageboolean()
MessageBox.Show(imageContains)
EDIT: Here is the full code of the DLL
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;
using System.IO;
namespace ImageRecognition
{
public class LockedFastImage
{
private Bitmap image;
private byte[] rgbValues;
private System.Drawing.Imaging.BitmapData bmpData;
private IntPtr ptr;
private int bytes;
public LockedFastImage(Bitmap image)
{
this.image = image;
Rectangle rect = new Rectangle(0, 0, image.Width, image.Height);
bmpData = image.LockBits(rect, System.Drawing.Imaging.ImageLockMode.ReadWrite, image.PixelFormat);
ptr = bmpData.Scan0;
bytes = Math.Abs(bmpData.Stride) * image.Height;
rgbValues = new byte[bytes];
System.Runtime.InteropServices.Marshal.Copy(ptr, rgbValues, 0, bytes);
}
~LockedFastImage()
{
// Try to unlock the bits. Who cares if it dont work...
try
{
image.UnlockBits(bmpData);
}
catch { }
}
/// <summary>
/// Returns or sets a pixel of the image.
/// </summary>
/// <param name="x">x parameter of the pixel</param>
/// <param name="y">y parameter of the pixel</param>
public Color this[int x, int y]
{
get
{
int index = (x + (y * image.Width)) * 4;
return Color.FromArgb(rgbValues[index + 3], rgbValues[index + 2], rgbValues[index + 1], rgbValues[index]);
}
set
{
int index = (x + (y * image.Width)) * 4;
rgbValues[index] = value.B;
rgbValues[index + 1] = value.G;
rgbValues[index + 2] = value.R;
rgbValues[index + 3] = value.A;
}
}
/// <summary>
/// Width of the image.
/// </summary>
public int Width
{
get
{
return image.Width;
}
}
/// <summary>
/// Height of the image.
/// </summary>
public int Height
{
get
{
return image.Height;
}
}
/// <summary>
/// Returns the modified Bitmap.
/// </summary>
public Bitmap asBitmap()
{
System.Runtime.InteropServices.Marshal.Copy(rgbValues, 0, ptr, bytes);
return image;
}
}
public class ImageChecker
{
private LockedFastImage big_image;
private LockedFastImage small_image;
/// <summary>
/// The time needed for last operation.
/// </summary>
public TimeSpan time_needed = new TimeSpan();
/// <summary>
/// Error return value.
/// </summary>
static public Point CHECKFAILED = new Point(-1, -1);
/// <summary>
/// Constructor of the ImageChecker
/// </summary>
/// <param name="big_image">The image containing the small image.</param>
/// <param name="small_image">The image located in the big image.</param>
public ImageChecker(Bitmap big_image, Bitmap small_image)
{
this.big_image = new LockedFastImage(big_image);
this.small_image = new LockedFastImage(small_image);
}
/// <summary>
/// Returns the location of the small image in the big image. Returns CHECKFAILED if not found.
/// </summary>
/// <param name="x_speedUp">speeding up at x achsis.</param>
/// <param name="y_speedUp">speeding up at y achsis.</param>
/// <param name="begin_percent_x">Reduces the search rect. 0 - 100</param>
/// <param name="end_percent_x">Reduces the search rect. 0 - 100</param>
/// <param name="begin_percent_x">Reduces the search rect. 0 - 100</param>
/// <param name="end_percent_y">Reduces the search rect. 0 - 100</param>
public Point bigContainsSmall(int x_speedUp = 4, int y_speedUp = 4, int begin_percent_x = 0, int end_percent_x = 100, int begin_percent_y = 0, int end_percent_y = 100)
{
/*
* SPEEDUP PARAMETER
* It might be enough to check each second or third pixel in the small picture.
* However... In most cases it would be enough to check 4 pixels of the small image for diablo porposes.
* */
/*
* BEGIN, END PARAMETER
* In most cases we know where the image is located, for this we have the begin and end paramenters.
* */
DateTime begin = DateTime.Now;
if (x_speedUp < 1) x_speedUp = 1;
if (y_speedUp < 1) y_speedUp = 1;
if (begin_percent_x < 0 || begin_percent_x > 100) begin_percent_x = 0;
if (begin_percent_y < 0 || begin_percent_y > 100) begin_percent_y = 0;
if (end_percent_x < 0 || end_percent_x > 100) end_percent_x = 100;
if (end_percent_y < 0 || end_percent_y > 100) end_percent_y = 100;
int x_start = (int)((double)big_image.Width * ((double)begin_percent_x / 100.0));
int x_end = (int)((double)big_image.Width * ((double)end_percent_x / 100.0));
int y_start = (int)((double)big_image.Height * ((double)begin_percent_y / 100.0));
int y_end = (int)((double)big_image.Height * ((double)end_percent_y / 100.0));
/*
* We cant speed up the big picture, because then we have to check pixels in the small picture equal to the speeded up size
* for each pixel in the big picture.
* Would give no speed improvement.
* */
//+ 1 because first pixel is in picture. - small because image have to be fully in the other image
for (int x = x_start; x < x_end - small_image.Width + 1; x++)
for (int y = y_start; y < y_end - small_image.Height + 1; y++)
{
//now we check if all pixels matches
for (int sx = 0; sx < small_image.Width; sx += x_speedUp)
for (int sy = 0; sy < small_image.Height; sy += y_speedUp)
{
if (small_image[sx, sy] != big_image[x + sx, y + sy])
goto CheckFailed;
}
//check ok
time_needed = DateTime.Now - begin;
return new Point(x, y);
CheckFailed: ;
}
time_needed = DateTime.Now - begin;
return CHECKFAILED;
}
public Boolean findimageboolean(int x_speedUp = 1, int y_speedUp = 1, int begin_percent_x = 0, int end_percent_x = 100, int begin_percent_y = 0, int end_percent_y = 100)
{
/*
* SPEEDUP PARAMETER
* It might be enough to check each second or third pixel in the small picture.
* However... In most cases it would be enough to check 4 pixels of the small image for diablo porposes.
* */
/*
* BEGIN, END PARAMETER
* In most cases we know where the image is located, for this we have the begin and end paramenters.
* */
DateTime begin = DateTime.Now;
if (x_speedUp < 1) x_speedUp = 1;
if (y_speedUp < 1) y_speedUp = 1;
if (begin_percent_x < 0 || begin_percent_x > 100) begin_percent_x = 0;
if (begin_percent_y < 0 || begin_percent_y > 100) begin_percent_y = 0;
if (end_percent_x < 0 || end_percent_x > 100) end_percent_x = 100;
if (end_percent_y < 0 || end_percent_y > 100) end_percent_y = 100;
int x_start = (int)((double)big_image.Width * ((double)begin_percent_x / 100.0));
int x_end = (int)((double)big_image.Width * ((double)end_percent_x / 100.0));
int y_start = (int)((double)big_image.Height * ((double)begin_percent_y / 100.0));
int y_end = (int)((double)big_image.Height * ((double)end_percent_y / 100.0));
/*
* We cant speed up the big picture, because then we have to check pixels in the small picture equal to the speeded up size
* for each pixel in the big picture.
* Would give no speed improvement.
* */
//+ 1 because first pixel is in picture. - small because image have to be fully in the other image
for (int x = x_start; x < x_end - small_image.Width + 1; x++)
for (int y = y_start; y < y_end - small_image.Height + 1; y++)
{
//now we check if all pixels matches
for (int sx = 0; sx < small_image.Width; sx += x_speedUp)
for (int sy = 0; sy < small_image.Height; sy += y_speedUp)
{
if (small_image[sx, sy] != big_image[x + sx, y + sy])
goto CheckFailed;
}
//check ok
time_needed = DateTime.Now - begin;
return true;
CheckFailed: ;
}
time_needed = DateTime.Now - begin;
return false;
}
}
}
If Stride is negative you need to copy image bytes in a different way, so I made some modifications to your constructor:
public LockedFastImage(Bitmap image)
{
this.image = image;
Rectangle rect = new Rectangle(0, 0, image.Width, image.Height);
bmpData = image.LockBits(rect, System.Drawing.Imaging.ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb);
ptr = bmpData.Scan0;
bytes = Math.Abs(bmpData.Stride) * image.Height;
rgbValues = new byte[bytes];
if (bmpData.Stride < 0)
{
int lines, pos, BytesPerLine = Math.Abs(bmpData.Stride);
for (lines = pos = 0; lines < image.Height; lines++, pos += BytesPerLine)
{
System.Runtime.InteropServices.Marshal.Copy(ptr, rgbValues, pos, BytesPerLine);
ptr = (IntPtr)(ptr.ToInt64() + bmpData.Stride);
}
}
else
System.Runtime.InteropServices.Marshal.Copy(ptr, rgbValues, 0, bytes);
}
If you are storing pixel data as ARGB, you need to lock pixels using the corresponding PixelFormat.
I also modified your accessors:
public Color this[int x, int y]
{
get
{
int index = y * (image.Width << 2) + x;
return Color.FromArgb(rgbValues[index + 3], rgbValues[index + 2], rgbValues[index + 1], rgbValues[index]);
}
set
{
int index = y * (image.Width << 2) + x;
rgbValues[index] = value.B;
rgbValues[index + 1] = value.G;
rgbValues[index + 2] = value.R;
rgbValues[index + 3] = value.A;
}
}
By the way, I haven't tested this code!
Strictly speaking, you should save BytesPerLine as a private field and then use it to calculate index in your accessors. So you should change this (image.Width << 2) to BytesPerLine.
Also you can try changing your get accessor to the following (not sure if it's faster, but you can try it if you wish):
get
{
return Color.FromArgb(BitConverter.ToInt32(rgbValues, y * (image.Width << 2) + x));
}
Related
Modifying the code provided in this link:
Original code
I wrote this:
private void btnLoad_Click(object sender, EventArgs e)
{
if (System.IO.File.Exists(txtPicture.Text))
{
byte[] _data = System.IO.File.ReadAllBytes(txtPicture.Text);
var _rgbData = Convert16BitGrayScaleToRgb16(_data, 160, 120);
var _bmp = CreateBitmapFromBytes(_rgbData, 160, 120);
pbFrame.Image = _bmp;
}
}
private static void Convert16bitGSToRGB(UInt16 color, out byte red, out byte green, out byte blue)
{
red = (byte)(color & 0x31);
green = (byte)((color & 0x7E0) >> 5);
blue = (byte)((color & 0xF800) >> 11);
}
private static byte[] Convert16BitGrayScaleToRgb48(byte[] inBuffer, int width, int height)
{
int inBytesPerPixel = 2;
int outBytesPerPixel = 6;
byte[] outBuffer = new byte[width * height * outBytesPerPixel];
int inStride = width * inBytesPerPixel;
int outStride = width * outBytesPerPixel;
// Step through the image by row
for (int y = 0; y < height; y++)
{
// Step through the image by column
for (int x = 0; x < width; x++)
{
// Get inbuffer index and outbuffer index
int inIndex = (y * inStride) + (x * inBytesPerPixel);
int outIndex = (y * outStride) + (x * outBytesPerPixel);
byte hibyte = inBuffer[inIndex + 1];
byte lobyte = inBuffer[inIndex];
//R
outBuffer[outIndex] = lobyte;
outBuffer[outIndex + 1] = hibyte;
//G
outBuffer[outIndex + 2] = lobyte;
outBuffer[outIndex + 3] = hibyte;
//B
outBuffer[outIndex + 4] = lobyte;
outBuffer[outIndex + 5] = hibyte;
}
}
return outBuffer;
}
private static byte[] Convert16BitGrayScaleToRgb16(byte[] inBuffer, int width, int height)
{
int inBytesPerPixel = 2;
int outBytesPerPixel = 2;
byte[] outBuffer = new byte[width * height * outBytesPerPixel];
int inStride = width * inBytesPerPixel;
int outStride = width * outBytesPerPixel;
// Step through the image by row
for (int y = 0; y < height; y++)
{
// Step through the image by column
for (int x = 0; x < width; x++)
{
// Get inbuffer index and outbuffer index
int inIndex = (y * inStride) + (x * inBytesPerPixel);
int outIndex = (y * outStride) + (x * outBytesPerPixel);
byte hibyte = inBuffer[inIndex];
byte lobyte = inBuffer[inIndex+1];
outBuffer[outIndex] = lobyte;
outBuffer[outIndex+1] = hibyte;
}
}
return outBuffer;
}
private static byte[] Convert16BitGrayScaleToRgb24(byte[] inBuffer, int width, int height)
{
int inBytesPerPixel = 2;
int outBytesPerPixel = 3;
byte[] outBuffer = new byte[width * height * outBytesPerPixel];
int inStride = width * inBytesPerPixel;
int outStride = width * outBytesPerPixel;
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
int inIndex = (y * inStride) + (x * inBytesPerPixel);
int outIndex = (y * outStride) + (x * outBytesPerPixel);
byte hibyte = inBuffer[inIndex];
byte lobyte = inBuffer[inIndex + 1];
byte r, g, b;
UInt16 color = (UInt16)(hibyte << 8 | lobyte);
Convert16bitGSToRGB(color, out r, out g, out b);
outBuffer[outIndex] = r;
outBuffer[outIndex + 1] = g;
outBuffer[outIndex + 2] = b;
}
}
return outBuffer;
}
private static Bitmap CreateBitmapFromBytes(byte[] pixelValues, int width, int height)
{
//Create an image that will hold the image data
Bitmap bmp = new Bitmap(width, height, PixelFormat.Format16bppRgb565);
//Get a reference to the images pixel data
Rectangle dimension = new Rectangle(0, 0, bmp.Width, bmp.Height);
BitmapData picData = bmp.LockBits(dimension, ImageLockMode.ReadWrite, bmp.PixelFormat);
IntPtr pixelStartAddress = picData.Scan0;
//Copy the pixel data into the bitmap structure
Marshal.Copy(pixelValues, 0, pixelStartAddress, pixelValues.Length);
bmp.UnlockBits(picData);
return bmp;
}
But still the result of the conversion is not satisfying/correct. This's the picture I should get:
Converting the file linked here:
Example RAW16 picture file
This's the result using Convert16BitGrayScaleToRgb48:
This's the result using Convert16BitGrayScaleToRgb16:
This's the result using Convert16BitGrayScaleToRgb24:
It's quite clear that the color remapping is wrong but I cant understand where the problem is.
Additionally I also found that picturebox didn't show exactly what it stores. The second image from the top (Convert16BitGrayScaleToRgb48 result) is what the picturebox shows while the following picture is what I obtain if I save the image shown in PNG format:
I tought RAW16 grayscale should mean 2 bytes containing either a 16 bit gray value or an RGB gray value encoded on a 565 or 555 map. But none of those hypotesis seems to match the real thing.
Someone has an hint on how to convert the value provided in the source file to obtain a picture like the first one (obtained from the same source using ImageJ)?
I found a possibile hint using GIMP. If I load the original file trough this app (changing extension in .data and/or forcing to load it as RAW) and setting it as a 160x120 16bpp BigEndian I got a nearly black frame (!), but if I change levels compressing the range around the only small peak present (around 12,0 black - 13,0 white) the image result correct. Changing endianism is pretty straightforward compressing the dynamic range a little less but I'm working on it.
The first lesson learned in this experience is "Don't trust your eyes" :-).
The final result of my efforts are these three methods:
public static void GetMinMax(byte[] data, out UInt16 min, out UInt16 max, bool big_endian = true)
{
if (big_endian)
min = max = (UInt16)((data[0] << 8) | data[1]);
else
min = max = (UInt16)((data[1] << 8) | data[0]);
for (int i = 0; i < (data.Length - 1); i += 2)
{
UInt16 _value;
if (big_endian)
_value = (UInt16)((data[i] << 8) | data[i + 1]);
else
_value = (UInt16)((data[i + 1] << 8) | data[i]);
if (_value < min)
min = _value;
if (_value > max)
max = _value;
}
}
public static void CompressRange(byte MSB, byte LSB, UInt16 min, UInt16 max, out byte color, Polarity polarity)
{
UInt16 _value = (UInt16)((MSB << 8) | LSB);
_value -= min;
switch (polarity)
{
case Polarity.BlackHot:
_value = (UInt16)((_value * 255) / (max - min));
_value = (UInt16)(255 - _value);
break;
default:
case Polarity.WhiteHot:
_value = (UInt16)((_value * 255) / (max - min));
break;
}
color = (byte)(_value & 0xff);
}
public static byte[] Convert16BitGrayScaleToRgb24(byte[] inBuffer, int width, int height, UInt16 min, UInt16 max, bool big_endian = true, Polarity polarity = Polarity.WhiteHot)
{
int inBytesPerPixel = 2;
int outBytesPerPixel = 3;
byte[] outBuffer = new byte[width * height * outBytesPerPixel];
int inStride = width * inBytesPerPixel;
int outStride = width * outBytesPerPixel;
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
int inIndex = (y * inStride) + (x * inBytesPerPixel);
int outIndex = (y * outStride) + (x * outBytesPerPixel);
byte hibyte;
byte lobyte;
if (big_endian)
{
hibyte = inBuffer[inIndex];
lobyte = inBuffer[inIndex + 1];
}
else
{
hibyte = inBuffer[inIndex + 1];
lobyte = inBuffer[inIndex];
}
byte gray;
CompressRange(hibyte, lobyte, min, max, out gray, polarity);
outBuffer[outIndex] = gray;
outBuffer[outIndex + 1] = gray;
outBuffer[outIndex + 2] = gray;
}
}
return outBuffer;
}
These allows to load the file attached to the original question and display it on a standard WindowsForm PictureBox. Using 48bpp format will result in a degraded image on some graphic cards (at least on mine).
BTW GetMinMax calculate min max value on the current frame regardless of the history of the environment. This means that if you are going to use this functions to display a picture sequence (as I am) a strong variation of average temperature in the FOV will drive the overall image to a different exposure resulting in loosing some details of the picture. In such cases I suggest to calculate min-max over the current frame but NON to use it in Convert16BitGrayScaleToRgb24 using instead a moving average for both values.
I'm trying to find the needle from these or similar pictures:
My solution is take average brightness of picture and set black and white pixels depends on it. The result is something like this:
I dont need to see numbers and staf there.. only needle but its not big problem when its there. But im using set, get pixel functions and its realy slow i reed something about LockBits metod, i use it for get brightness but i dont know how to use it correct for setpixel. Can anyone help me with my code?..thnx
BitmapData imageData = imgPristroj.LockBits(new Rectangle(0, 0, imgPristroj.Width, imgPristroj.Height), ImageLockMode.ReadOnly, imgPristroj.PixelFormat);
float brightness = 0;
float average;
unsafe
{
try
{
UnmanagedImage Unimg = new UnmanagedImage(imageData);
int pixelSize = (Unimg.PixelFormat == PixelFormat.Format24bppRgb) ? 3 : 4;
byte* p = (byte*)Unimg.ImageData.ToPointer();
for (int y = 0; y < Unimg.Height; y++)
{
for (int x = 0; x < Unimg.Width; x++, p += pixelSize)
{
brightness += Unimg.GetPixel(x, y).GetBrightness();
}
}
average = brightness / (Unimg.Width * Unimg.Height);
}
finally
{
imgPristroj.UnlockBits(imageData); //Unlock
}
}
img19 = (Bitmap)imgPristroj.Clone();
for (int y = 0; y < img19.Height; y++)
{
for (int x = 0; x < img19.Width; x++)
{
if (img19.GetPixel(x, y).GetBrightness() > average)
{
img19.SetPixel(x, y, Color.White);
}
else
{
img19.SetPixel(x, y, Color.Black);
}
}
}
Edit2: This is my whole code..
float brightness = 0;
float average = 0;
PixelUtil pixelUtil2 = new PixelUtil((Bitmap)imgPristroj.Clone());
pixelUtil2.LockBits();
for (int y = 0; y < imgPristroj.Height; y++)
{
// for each pixel
for (int x = 0; x < imgPristroj.Width; x++)
{
brightness += pixelUtil2.GetPixel(x, y).GetBrightness();
}
}
average = brightness / (imgPristroj.Width * imgPristroj.Height);
pixelUtil2.UnlockBits();
img19 = (Bitmap)imgPristroj.Clone();
Crop cfilter1 = new Crop(new Rectangle(0, (int)(pix * 1.6), img19.Width, (int)(pix * 3)));
img19 = cfilter1.Apply(img19);
PixelUtil pixelUtil = new PixelUtil(img19);
pixelUtil.LockBits();
for (int y = 0; y < img19.Height; y++)
{
for (int x = 0; x < img19.Width; x++)
{
if (pixelUtil.GetPixel(x, y).GetBrightness() >= average)
{
pixelUtil.SetPixel(x, y, Color.White);
}
else
{
pixelUtil.SetPixel(x, y, Color.Black);
}
}
}
pixelUtil.UnlockBits();
string filepath = Environment.CurrentDirectory;
string fileName = System.IO.Path.Combine(filepath, #"img" + ".bmp");
img19.Save(fileName);
I get this bitmap with color pixels, can you tell me why?
And when i use red color.. not black... (pixelUtil.SetPixel(x, y, Color.Red);) i have got this funny pic. (differnt size is OK..)
You can try using marshaling: It's quite fast. (You just need to copy-paste this)
public class PixelUtil
{
Bitmap source = null;
IntPtr Iptr = IntPtr.Zero;
BitmapData bitmapData = null;
public byte[] Pixels { get; set; }
public int Depth { get; private set; }
public int Width { get; private set; }
public int Height { get; private set; }
/// <summary>
/// Pixel marshaling class, use this to get and set pixels rapidly.
/// </summary>
/// <param name="source">The Bitmap to work with</param>
public PixelUtil(Bitmap source)
{
this.source = source;
}
/// <summary>
/// Lock bitmap data
/// </summary>
public void LockBits()
{
try
{
// Get width and height of bitmap
Width = source.Width;
Height = source.Height;
// get total locked pixels count
int PixelCount = Width * Height;
// Create rectangle to lock
var rect = new Rectangle(0, 0, Width, Height);
// get source bitmap pixel format size
Depth = System.Drawing.Image.GetPixelFormatSize(source.PixelFormat);
// Check if bpp (Bits Per Pixel) is 8, 24, or 32
if (Depth != 8 && Depth != 24 && Depth != 32)
{
throw new ArgumentException("Only 8, 24 and 32 bpp images are supported.");
}
// Lock bitmap and return bitmap data
bitmapData = source.LockBits(rect, ImageLockMode.ReadWrite,
source.PixelFormat);
// create byte array to copy pixel values
int step = Depth / 8;
Pixels = new byte[PixelCount * step];
Iptr = bitmapData.Scan0;
// Copy data from pointer to array
Marshal.Copy(Iptr, Pixels, 0, Pixels.Length);
}
catch (Exception)
{
throw;
}
}
/// <summary>
/// Unlock bitmap data
/// </summary>
public void UnlockBits()
{
try
{
// Copy data from byte array to pointer
Marshal.Copy(Pixels, 0, Iptr, Pixels.Length);
// Unlock bitmap data
source.UnlockBits(bitmapData);
}
catch (Exception)
{
throw;
}
}
/// <summary>
/// Get the color of the specified pixel
/// </summary>
/// <param name="x"></param>
/// <param name="y"></param>
/// <returns></returns>
public Color GetPixel(int x, int y)
{
Color clr = Color.Empty;
// Get color components count
int cCount = Depth / 8;
// Get start index of the specified pixel
int i = ((y * Width) + x) * cCount;
if (i > Pixels.Length - cCount)
throw new IndexOutOfRangeException();
if (Depth == 32) //For 32 bpp get Red, Green, Blue and Alpha
{
byte b = Pixels[i];
byte g = Pixels[i + 1];
byte r = Pixels[i + 2];
byte a = Pixels[i + 3]; // a
clr = Color.FromArgb(a, r, g, b);
}
if (Depth == 24) //For 24 bpp get Red, Green and Blue
{
byte b = Pixels[i];
byte g = Pixels[i + 1];
byte r = Pixels[i + 2];
clr = Color.FromArgb(r, g, b);
}
if (Depth == 8) //For 8 bpp get color value (Red, Green and Blue values are the same)
{
byte c = Pixels[i];
clr = Color.FromArgb(c, c, c);
}
return clr;
}
/// <summary>
/// Set the color of the specified pixel
/// </summary>
/// <param name="x"></param>
/// <param name="y"></param>
/// <param name="color"></param>
public void SetPixel(int x, int y, Color color)
{
//Get color components count
int cCount = Depth / 8;
//Get start index of the specified pixel
int i = ((y * Width) + x) * cCount;
if (Depth == 32) //For 32 bpp set Red, Green, Blue and Alpha
{
Pixels[i] = color.B;
Pixels[i + 1] = color.G;
Pixels[i + 2] = color.R;
Pixels[i + 3] = color.A;
}
if (Depth == 24) //For 24 bpp set Red, Green and Blue
{
Pixels[i] = color.B;
Pixels[i + 1] = color.G;
Pixels[i + 2] = color.R;
}
if (Depth == 8) //For 8 bpp set color value (Red, Green and Blue values are the same)
{
Pixels[i] = color.B;
}
}
}
You can call like this:
public void Example(Bitmap image)
{
PixelUtil pixelUtil = new PixelUtil(image);
pixelUtil.LockBits();
Color firstPixel = pixelUtil.GetPixel(0, 0);
pixelUtil.SetPixel(0, 0, Color.White);
//Don't forget to unlock!
pixelUtil.UnlockBits();
}
EDIT:
Not sure how are you saving the image, but it Works fine for me:
public Form1()
{
InitializeComponent();
Bitmap image1 = new Bitmap(#"C:\Users\Nicke Manarin\Desktop\YEeJO.png");
BlackWhite(image1);
}
public void BlackWhite(Bitmap image)
{
PixelUtil pixelUtil = new PixelUtil(image);
pixelUtil.LockBits();
for (int y = 0; y < image.Height; y++)
{
for (int x = 0; x < image.Width; x++)
{
if (pixelUtil.GetPixel(x, y).GetBrightness() > 0.5)
{
pixelUtil.SetPixel(x, y, Color.White);
}
else
{
pixelUtil.SetPixel(x, y, Color.Black);
}
}
}
pixelUtil.UnlockBits();
pictureBox1.Image = image;
image.Save(#"C:\Users\Nicke Manarin\Desktop\YEeJO2.png");
}
I use this code and now its working good.... thnx again !!!!
public class PointBitmap
{
Bitmap source = null;
IntPtr Iptr = IntPtr.Zero;
BitmapData bitmapData = null;
public int Depth { get; private set; }
public int Width { get; private set; }
public int Height { get; private set; }
public PointBitmap(Bitmap source)
{
this.source = source;
}
public void LockBits()
{
try
{
// Get width and height of bitmap
Width = source.Width;
Height = source.Height;
// get total locked pixels count
int PixelCount = Width * Height;
// Create rectangle to lock
Rectangle rect = new Rectangle(0, 0, Width, Height);
// get source bitmap pixel format size
Depth = System.Drawing.Bitmap.GetPixelFormatSize(source.PixelFormat);
// Check if bpp (Bits Per Pixel) is 8, 24, or 32
if (Depth != 8 && Depth != 24 && Depth != 32)
{
throw new ArgumentException("Only 8, 24 and 32 bpp images are supported.");
}
// Lock bitmap and return bitmap data
bitmapData = source.LockBits(rect, ImageLockMode.ReadWrite,
source.PixelFormat);
unsafe
{
Iptr = bitmapData.Scan0;
}
}
catch (Exception ex)
{
throw ex;
}
}
public void UnlockBits()
{
try
{
source.UnlockBits(bitmapData);
}
catch (Exception ex)
{
throw ex;
}
}
public Color GetPixel(int x, int y)
{
unsafe
{
byte* ptr = (byte*)Iptr;
ptr = ptr + bitmapData.Stride * y;
ptr += Depth * x / 8;
Color c = Color.Empty;
if (Depth == 32)
{
int a = ptr[3];
int r = ptr[2];
int g = ptr[1];
int b = ptr[0];
c = Color.FromArgb(a, r, g, b);
}
else if (Depth == 24)
{
int r = ptr[2];
int g = ptr[1];
int b = ptr[0];
c = Color.FromArgb(r, g, b);
}
else if (Depth == 8)
{
int r = ptr[0];
c = Color.FromArgb(r, r, r);
}
return c;
}
}
public void SetPixel(int x, int y, Color c)
{
unsafe
{
byte* ptr = (byte*)Iptr;
ptr = ptr + bitmapData.Stride * y;
ptr += Depth * x / 8;
if (Depth == 32)
{
ptr[3] = c.A;
ptr[2] = c.R;
ptr[1] = c.G;
ptr[0] = c.B;
}
else if (Depth == 24)
{
ptr[2] = c.R;
ptr[1] = c.G;
ptr[0] = c.B;
}
else if (Depth == 8)
{
ptr[2] = c.R;
ptr[1] = c.G;
ptr[0] = c.B;
}
}
}
}
i think only difference here is Unsafe modifier
I'm attempting to perform Gaussian blur calculations based on source from the AForge framework. At present though I have something wrong with my calculations in that I am getting the same pixel data out of the process as I am putting in. (Something to do, I think with calculating the divider)
The process comes in two parts:
Create a Gaussian filter based on a kernel created with the set size.
Process the filter against an array of pixels (rgba structure) to return the transformed pixels which are later converted to a bitmap.
The method that I have for creating the original kernel that is converted to an integer based one has been tested against other programs and is correct in its implementation.
Any help would be greatly appreciated. I've been working on this for the last 12 hours.
The method that creates a filter
/// <summary>
/// Create a 2 dimensional Gaussian kernel using the Gaussian G(x y)
/// function for blurring images.
/// </summary>
/// <param name="kernelSize">Kernel Size</param>
/// <returns>A Gaussian Kernel with the given size.</returns>
public double[,] CreateGuassianBlurFilter(int kernelSize)
{
// Create kernel
double[,] kernel = this.CreateGaussianKernel2D(kernelSize);
double min = kernel[0, 0];
// Convert to integer blurring kernel. First of all the integer kernel
// is calculated from Kernel2D
// by dividing all elements by the element with the smallest value.
double[,] intKernel = new double[kernelSize, kernelSize];
int divider = 0;
for (int i = 0; i < kernelSize; i++)
{
for (int j = 0; j < kernelSize; j++)
{
double v = kernel[i, j] / min;
if (v > ushort.MaxValue)
{
v = ushort.MaxValue;
}
intKernel[i, j] = (int)v;
// Collect the divider
divider += (int)intKernel[i, j];
}
}
// Update filter
this.Divider = divider;
return intKernel;
}
And the method that performs the convolution:
/// <summary>
/// Processes the given kernel to produce an array of pixels representing a
/// bitmap.
/// </summary>
/// <param name="pixels">The raw pixels of the image to blur</param>
/// <param name="kernel">
/// The Gaussian kernel to use when performing the method</param>
/// <returns>An array of pixels representing the bitmap.</returns>
public Pixel[,] ProcessKernel(Pixel[,] pixels, double[,] kernel)
{
int width = pixels.GetLength(0);
int height = pixels.GetLength(1);
int kernelLength = kernel.GetLength(0);
int radius = kernelLength >> 1;
int kernelSize = kernelLength * kernelLength;
Pixel[,] result = new Pixel[width, height];
// For each line
for (int y = 0; y < height; y++)
{
// For each pixel
for (int x = 0; x < width; x++)
{
// The number of kernel elements taken into account
int processedKernelSize;
// Colour sums
double blue;
double alpha;
double divider;
double green;
double red = green = blue = alpha = divider =
processedKernelSize = 0;
// For each kernel row
for (int i = 0; i < kernelLength; i++)
{
int ir = i - radius;
int position = y + ir;
// Skip the current row
if (position < 0)
{
continue;
}
// Outwith the current bounds so break.
if (position >= height)
{
break;
}
// For each kernel column
for (int j = 0; j < kernelLength; j++)
{
int jr = j - radius;
position = x + jr;
// Skip the column
if (position < 0)
{
continue;
}
if (position < width)
{
double k = kernel[i, j];
Pixel pixel = pixels[x, y];
divider += k;
red += k * pixel.R;
green += k * pixel.G;
blue += k * pixel.B;
alpha += k * pixel.A;
processedKernelSize++;
}
}
}
// Check to see if all kernel elements were processed
if (processedKernelSize == kernelSize)
{
// All kernel elements are processed; we are not on the edge.
divider = this.Divider;
}
else
{
// We are on an edge; do we need to use dynamic divider or not?
if (!this.UseDynamicDividerForEdges)
{
// Apply the set divider.
divider = this.Divider;
}
}
// Check and apply the divider
if ((long)divider != 0)
{
red /= divider;
green /= divider;
blue /= divider;
alpha /= divider;
}
// Add any applicable threshold.
red += this.Threshold;
green += this.Threshold;
blue += this.Threshold;
alpha += this.Threshold;
result[x, y].R = (byte)((red > 255)
? 255 : ((red < 0) ? 0 : red));
result[x, y].G = (byte)((green > 255)
? 255 : ((green < 0) ? 0 : green));
result[x, y].B = (byte)((blue > 255)
? 255 : ((blue < 0) ? 0 : blue));
result[x, y].A = (byte)((alpha > 255)
? 255 : ((alpha < 0) ? 0 : alpha));
}
}
return result;
}
The issue was in the selection of the correct pixel to multiply by the kernel value. Instead of the appropriate offset I was choosing the same pixel.
The corrected method is as follows.
/// <summary>
/// Processes the given kernel to produce an array of pixels representing a
/// bitmap.
/// </summary>
/// <param name="pixels">The raw pixels of the image to blur</param>
/// <param name="kernel">
/// The Gaussian kernel to use when performing the method</param>
/// <returns>An array of pixels representing the bitmap.</returns>
public Pixel[,] ProcessKernel(Pixel[,] pixels, double[,] kernel)
{
int width = pixels.GetLength(0);
int height = pixels.GetLength(1);
int kernelLength = kernel.GetLength(0);
int radius = kernelLength >> 1;
int kernelSize = kernelLength * kernelLength;
Pixel[,] result = new Pixel[width, height];
// For each line
for (int y = 0; y < height; y++)
{
// For each pixel
for (int x = 0; x < width; x++)
{
// The number of kernel elements taken into account
int processedKernelSize;
// Colour sums
double blue;
double alpha;
double divider;
double green;
double red = green = blue = alpha = divider =
processedKernelSize = 0;
// For each kernel row
for (int i = 0; i < kernelLength; i++)
{
int ir = i - radius;
int iposition = y + ir;
// Skip the current row
if (iposition < 0)
{
continue;
}
// Outwith the current bounds so break.
if (iposition >= height)
{
break;
}
// For each kernel column
for (int j = 0; j < kernelLength; j++)
{
int jr = j - radius;
int jposition = x + jr;
// Skip the column
if (jposition < 0)
{
continue;
}
if (jposition < width)
{
double k = kernel[i, j];
Pixel pixel = pixels[jposition, iposition];
divider += k;
red += k * pixel.R;
green += k * pixel.G;
blue += k * pixel.B;
alpha += k * pixel.A;
processedKernelSize++;
}
}
}
// Check to see if all kernel elements were processed
if (processedKernelSize == kernelSize)
{
// All kernel elements are processed; we are not on the edge.
divider = this.Divider;
}
else
{
// We are on an edge; do we need to use dynamic divider or not?
if (!this.UseDynamicDividerForEdges)
{
// Apply the set divider.
divider = this.Divider;
}
}
// Check and apply the divider
if ((long)divider != 0)
{
red /= divider;
green /= divider;
blue /= divider;
alpha /= divider;
}
// Add any applicable threshold.
red += this.Threshold;
green += this.Threshold;
blue += this.Threshold;
alpha += this.Threshold;
result[x, y].R = (byte)((red > 255)
? 255 : ((red < 0) ? 0 : red));
result[x, y].G = (byte)((green > 255)
? 255 : ((green < 0) ? 0 : green));
result[x, y].B = (byte)((blue > 255)
? 255 : ((blue < 0) ? 0 : blue));
result[x, y].A = (byte)((alpha > 255)
? 255 : ((alpha < 0) ? 0 : alpha));
}
}
return result;
}
The only quick way to find out why it is happening is to set breakpoints and track the values changing. This helps you to catch the code with errors operatively. You could forget some calculations or method may return an untouched copy instead of modified result, or modified calculations may cut precision, anyway, this is not a kind of a problem to delegate to others.
I have search a lot to remove the unwanted space but could not found. I only found links which can used to remove black and white background space. But my background images can be anything. So, If I have these images,
How I can extract the part of image which I required. For example,
Here's my solution for your question :
I have declared a method which gets the Original Image then It looks for the background Color by checking the corners of the provided Image , if at least 3 corners have similar Color (10% offset at most) then we've found the background color then It tries to find the bounds of those shapes in the Image which of course have different color than Background Color
after Finding the Bounds The Function Crops the Image and Return the new Cropped Area as a new Bitmap !
This is the Demo File : Download
Complete Solution : Download
Here's the results for :
Image 1 :
Image 2 :
Image 3 :
here's the Function inside ImageProcessingTools class
Simplified,
public class ImageHelper
{
#region CropUnwantedBackground
public static Bitmap CropUnwantedBackground(Bitmap bmp)
{
var backColor = GetMatchedBackColor(bmp);
if (backColor.HasValue)
{
var bounds = GetImageBounds(bmp, backColor);
var diffX = bounds[1].X - bounds[0].X + 1;
var diffY = bounds[1].Y - bounds[0].Y + 1;
var croppedBmp = new Bitmap(diffX, diffY);
var g = Graphics.FromImage(croppedBmp);
var destRect = new Rectangle(0, 0, croppedBmp.Width, croppedBmp.Height);
var srcRect = new Rectangle(bounds[0].X, bounds[0].Y, diffX, diffY);
g.DrawImage(bmp, destRect, srcRect, GraphicsUnit.Pixel);
bmp.Dispose();
return croppedBmp;
}
else
{
bmp.Dispose();
return null;
}
}
#endregion
#region Private Methods
#region GetImageBounds
private static Point[] GetImageBounds(Bitmap bmp, Color? backColor)
{
//--------------------------------------------------------------------
// Finding the Bounds of Crop Area bu using Unsafe Code and Image Proccesing
Color c;
int width = bmp.Width, height = bmp.Height;
bool upperLeftPointFounded = false;
var bounds = new Point[2];
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
c = bmp.GetPixel(x, y);
bool sameAsBackColor = ((c.R <= backColor.Value.R * 1.1 && c.R >= backColor.Value.R * 0.9) &&
(c.G <= backColor.Value.G * 1.1 && c.G >= backColor.Value.G * 0.9) &&
(c.B <= backColor.Value.B * 1.1 && c.B >= backColor.Value.B * 0.9));
if (!sameAsBackColor)
{
if (!upperLeftPointFounded)
{
bounds[0] = new Point(x, y);
bounds[1] = new Point(x, y);
upperLeftPointFounded = true;
}
else
{
if (x > bounds[1].X)
bounds[1].X = x;
else if (x < bounds[0].X)
bounds[0].X = x;
if (y >= bounds[1].Y)
bounds[1].Y = y;
}
}
}
}
return bounds;
}
#endregion
#region GetMatchedBackColor
private static Color? GetMatchedBackColor(Bitmap bmp)
{
// Getting The Background Color by checking Corners of Original Image
var corners = new Point[]{
new Point(0, 0),
new Point(0, bmp.Height - 1),
new Point(bmp.Width - 1, 0),
new Point(bmp.Width - 1, bmp.Height - 1)
}; // four corners (Top, Left), (Top, Right), (Bottom, Left), (Bottom, Right)
for (int i = 0; i < 4; i++)
{
var cornerMatched = 0;
var backColor = bmp.GetPixel(corners[i].X, corners[i].Y);
for (int j = 0; j < 4; j++)
{
var cornerColor = bmp.GetPixel(corners[j].X, corners[j].Y);// Check RGB with some offset
if ((cornerColor.R <= backColor.R * 1.1 && cornerColor.R >= backColor.R * 0.9) &&
(cornerColor.G <= backColor.G * 1.1 && cornerColor.G >= backColor.G * 0.9) &&
(cornerColor.B <= backColor.B * 1.1 && cornerColor.B >= backColor.B * 0.9))
{
cornerMatched++;
}
}
if (cornerMatched > 2)
{
return backColor;
}
}
return null;
}
#endregion
#endregion
}
and here is a simple one usage in ASP.NET,
if (IsPostBack && Request.Files.Count > 0)
{
var file = Request.Files[0];
var bmp = new Bitmap(file.InputStream);
var croppedBmp = ImageHelper.CropUnwantedBackground(bmp);
Response.ContentType = file.ContentType;
croppedBmp.Save(Response.OutputStream, ImageFormat.Jpeg);
Response.End();
}
And Finally I should mention that , these Fantastic Tutorials have Helped me a lot in Image Processing :
Image Processing for Dummies with C# and GDI+
Image Processing using C#
Hope it Helps :)
Here's a more reliable approach that uses a Sobel energy filter and a fast bounding box detection routine (extracted from the WhitespaceTrimmer plugin for ImageResizer).
namespace ImageResizer.Plugins.WhitespaceTrimmer {
public class BoundingBoxFinder {
/// <summary>
/// Returns a rectangle inside 'lookInside' that bounds any energy greater than 'threshold'.
/// </summary>
/// <param name="image"></param>
/// <param name="lookInside">A rectangle of 'image' to look inside. </param>
/// <param name="threshold">1-255, the energy threshold to detect activity. 80-150 is a good range.</param>
/// <returns></returns>
public Rectangle FindBoxSobel(Bitmap originalImage, Rectangle lookInside, byte threshold) {
Bitmap image = originalImage;
try {
//Convert if needed (makes an extra copy)
if (image.PixelFormat != PixelFormat.Format24bppRgb &&
image.PixelFormat != PixelFormat.Format32bppArgb &&
image.PixelFormat != PixelFormat.Format32bppRgb) {
image = AForge.Imaging.Image.Clone(image, PixelFormat.Format24bppRgb);
}
//Crop if needed (makes an extra copy unless we converted too, then only 1 extra copy)
if (!lookInside.Equals(new Rectangle(0, 0, image.Width, image.Height))) {
Bitmap oldImage = image;
try {
image = new Crop(lookInside).Apply(image);
} finally {
if (oldImage != originalImage) oldImage.Dispose(); //Dispose the cloned
}
}
//Makes 1 more copy at 1/3rd the size, in grayscale
Rectangle result = FindBoxSobel(image, threshold);
return new Rectangle(lookInside.X + result.X, lookInside.Y + result.Y, result.Width, result.Height);
} finally {
if (image != originalImage) image.Dispose();
}
}
/// <summary>
/// Requires 24 bit or 32 bit (A) RGB image.
/// </summary>
/// <param name="rgb"></param>
/// <param name="threshold"></param>
/// <returns></returns>
public Rectangle FindBoxSobel(Bitmap rgb, byte threshold) {
using (Bitmap gray = Grayscale.CommonAlgorithms.Y.Apply(rgb)) {
//Apply sobel operator to grayscale image
new SobelEdgeDetector().ApplyInPlace(gray);
//Threshold into black and white.
new Threshold(threshold).ApplyInPlace(gray);
//Trim only exact black pixels
// lock source bitmap data
BitmapData data = gray.LockBits(new Rectangle(0, 0, gray.Width, gray.Height), ImageLockMode.ReadOnly, gray.PixelFormat);
try {
return FindBoxExactGrayscale(data, 0);
} finally {
gray.UnlockBits(data);
}
}
}
/// <summary>
/// Returns a bounding box that only excludes the specified color.
/// Only works on 8-bit images.
/// </summary>
/// <param name="sourceData"></param>
/// <param name="colorToRemove">The palette index to remove.</param>
/// <returns></returns>
public Rectangle FindBoxExactGrayscale(BitmapData sourceData, byte indexToRemove) {
if (sourceData.PixelFormat != PixelFormat.Format8bppIndexed) throw new ArgumentOutOfRangeException("FindBoxExact only operates on 8-bit grayscale images");
// get source image size
int width = sourceData.Width;
int height = sourceData.Height;
int offset = sourceData.Stride - width;
int minX = width;
int minY = height;
int maxX = 0;
int maxY = 0;
// find rectangle which contains something except color to remove
unsafe {
byte* src = (byte*)sourceData.Scan0;
for (int y = 0; y < height; y++) {
if (y > 0) src += offset; //Don't adjust for offset until after first row
for (int x = 0; x < width; x++) {
if (x > 0 || y > 0) src++; //Don't increment until after the first pixel.
if (*src != indexToRemove) {
if (x < minX)
minX = x;
if (x > maxX)
maxX = x;
if (y < minY)
minY = y;
if (y > maxY)
maxY = y;
}
}
}
}
// check
if ((minX == width) && (minY == height) && (maxX == 0) && (maxY == 0)) {
minX = minY = 0;
}
return new Rectangle(minX,minY,maxX - minX + 1, maxY - minY + 1);
}
}
}
I'm trying to write out a grayscale image using Lockbits, my current code looks is
/// <summary>
/// Save the content of the FrameProc out to a bitmap
/// </summary>
public void Save(string path)
{
Bitmap bmp = new Bitmap(this.size.Width, this.size.Height
,PixelFormat.Format32bppRgb);
var data = bmp.LockBits(this.size, ImageLockMode.WriteOnly, bmp.PixelFormat);
unsafe
{
for (int y = 0; y < this.size.Height; y++)
{
byte* row = (byte*)data.Scan0 + (y * data.Stride);
for (int x = 0; x < this.size.Width; x++)
{
byte value = (byte)this.buffer[y, x];
row[x*Bits+r] = value;
row[x*Bits+g] = value;
row[x*Bits+b] = value;
}
}
}
bmp.UnlockBits(data);
bmp.Save(path, ImageFormat.Bmp);
}
where
/// <summary>
/// The amount of Bytes per pixel in the image
/// </summary>
private const int Bits = 4;
/// <summary>
/// Image components
/// </summary>
private const int a=3, r = 2, g = 1, b = 0;
However the image i receive is not correct:
Maybe this is related to how i'm reading them in? So here's that code
public FrameProc(Bitmap bmp)
{
this.size=new Rectangle(new Point(0,0), bmp.Size);
var data = bmp.LockBits(this.size
,ImageLockMode.ReadOnly
,bmp.PixelFormat);
this.buffer = new Matrix(this.size.Height, this.size.Width);
unsafe
{
for (int y = 0; y < this.size.Height; y++)
{
byte* row = (byte*)data.Scan0 + (y * data.Stride);
for (int x = 0; x < this.size.Width; x++)
{
this.buffer[y,x] = 0.299*row[x*Bytes+r]
+ 0.587*row[x*Bytes+g]
+ 0.114*row[x*Bytes+b];
}
}
}
bmp.UnlockBits(data);
}
From the results you're getting - it looks exactly as if each pixel is three bytes big and not four as you have declared it - and as one would expect. (Note: you called it Bits - but that's wrong - it should be namned Bytes, not Bits).
I'd experiment with any one of this:
change from 4 to 3 bytes
change from Format32bppRgb to Format32bppArgb and fill out the alpha with 255
change from 4 to 3 bytes and from Format32bppRgb to from Format24bppRgb
I would also rewrite the loop slightly for performance (sorry, I can't help myself):
for (int x = 0; x < this.size.Width; x++, row += Bits)
{
byte value = (byte)this.buffer[y, x];
row[r] = value;
row[g] = value;
row[b] = value;
}
But were you really would get more speed if you get a pointer to this.buffer using the fixed keyword. Yes, you're not having any performance problems, but I couldn't help myself from mentioning it!
Use this function indeed:
public Bitmap MakeGrayscale(Bitmap original)
{
unsafe
{
//create an empty bitmap the same size as original
Bitmap newBitmap = new Bitmap(original.Width, original.Height);
//lock the original bitmap in memory
BitmapData originalData = original.LockBits(
new Rectangle(0, 0, original.Width, original.Height),
ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);
//lock the new bitmap in memory
BitmapData newData = newBitmap.LockBits(
new Rectangle(0, 0, original.Width, original.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 < original.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 < original.Width; x++)
{
//create the grayscale version
byte grayScale =
(byte)((oRow[x * pixelSize] * .11) + //B
(oRow[x * pixelSize + 1] * .59) + //G
(oRow[x * pixelSize + 2] * .3)); //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
}
}
//unlock the bitmaps
newBitmap.UnlockBits(newData);
original.UnlockBits(originalData);
return newBitmap;
}
}
Source and other interesting examples (with theory behind) could be taken from here