What is the most efficient way to loop through picture pixels - c#

I've 2 images that I need to compare of 1280x800 each and I'm too worried about the efficiency as I'll have to do the same operations that include looping each second
I can think of so many ways to loop through a Bitmap object pixels but I don't know which would be more efficient, for now I am using simple for loop but it uses too much memory and processing which I could not afford at this point
Some tweaks here and there and all it does is less memory for more processing or the other way around
Any tips, information or experience is highly appreciated, I'm also open to use external libraries if they have much better efficiency.

from https://stackoverflow.com/a/6094092/1856345
Bitmap bmp = new Bitmap("SomeImage");
// Lock the bitmap's bits.
Rectangle rect = new Rectangle(0, 0, bmp.Width, bmp.Height);
BitmapData bmpData = bmp.LockBits(rect, ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
// Get the address of the first line.
IntPtr ptr = bmpData.Scan0;
// Declare an array to hold the bytes of the bitmap.
int bytes = bmpData.Stride * bmp.Height;
byte[] rgbValues = new byte[bytes];
byte[] r = new byte[bytes / 3];
byte[] g = new byte[bytes / 3];
byte[] b = new byte[bytes / 3];
// Copy the RGB values into the array.
Marshal.Copy(ptr, rgbValues, 0, bytes);
int count = 0;
int stride = bmpData.Stride;
for (int column = 0; column < bmpData.Height; column++)
{
for (int row = 0; row < bmpData.Width; row++)
{
b[count] = rgbValues[(column * stride) + (row * 3)];
g[count] = rgbValues[(column * stride) + (row * 3) + 1];
r[count++] = rgbValues[(column * stride) + (row * 3) + 2];
}
}

Related

Extracting Bitmap Data to an array of byte

Let's say, I have an array of byte containing raw bitmap data without headers.
However the bitmap data is a bit weird, I'm not quite sure but it seems the bitmap data is not correctly aligned if the width is NPOT (Not Power of Two)
I use following codes to construct the bmp from such bitmap data:
public Bitmap GetBitmap(byte[] bitmapData, int width, int height)
{
Bitmap bitmap = new Bitmap(width, height, PixelFormat.Format16bppRgb555);
Rectangle rect = new Rectangle(0, 0, bitmap.Width, bitmap.Height);
BitmapData bmpData = bitmap.LockBits(rect, ImageLockMode.ReadWrite, bitmap.PixelFormat);
unsafe
{
byte* ptr = (byte*)bmpData.Scan0;
for (int i = 0; i < bitmapData.Length; i++)
{
*ptr = bitmapData[i];
ptr++;
if (width % 2 != 0)
{
if ((i + 1) % (width * 2) == 0 && (i + 1) * 2 % width < width - 1)
{
ptr += 2;
}
}
}
}
bitmap.UnlockBits(bmpData);
return bitmap;
}
The code works fine so far. But for some reasons, I need to implement "Import Bitmap", which mean I need to get the "weird" bitmap data from an instance of bitmap.
How do I do this?
Finally, I figure out how to do this.
I decide to copy the data first to an array of byte via Marshal.Copy and then copy it to another array of bytes while skip some point if the width is NPOT:
public byte[] ImportBitmap(Bitmap bitmap)
{
int width = bitmap.Width, height = bitmap.Height;
var bmpArea = new Rectangle(0, 0, width, height);
var bmpData = bitmap.LockBits(bmpArea, ImageLockMode.ReadWrite, PixelFormat.Format16bppRgb555);
var data = new byte[bmpData.Stride * Height];
Marshal.Copy(bmpData.Scan0, data, 0, data.Length);
bitmap.UnlockBits(bmpData);
bitmap.Dispose(); // bitmap is no longer required
var destination = new List<byte>();
int leapPoint = width * 2;
for (int i = 0; i < data.Length; i++)
{
if (width % 2 != 0)
{
// Skip at some point
if (i == leapPoint)
{
// Skip 2 bytes since it's 16 bit pixel
i += 1;
leapPoint += (width * 2) + 2;
continue;
}
}
destination.Add(data[i]);
}
return destination.ToArray();
}

Convert RGB image to RGB 16-bit and 8-bit

I'm a newbie programmer.
Is there any algorithm to convert an image to 16-bit or 8-bit?
I didn't find it on google, I'am desperate.
Changing to 16 bit is the easier one. Assuming that the original image is in image, you can simply draw it into the result.
Bitmap result = new Bitmap(image.Width, image.Height, PixelFormat.Format16bppRgb565);
using (Graphics g = Graphics.FromImage(result))
{
g.DrawImage(image, 0, 0, image.Width, image.Height);
}
Unfortunately, this does not work for indexed images (images with palette, up to 256 colors). But you can find a solution in my answer here, see the ConvertPixelFormat method.
The only difference between 16 bit rgb and 8 bit rgb is the range, 16 bit has values form 0 to 65536 while 8 bit has values from 0 to 256, hence you should just be able to devide a 16 bit rgb value by 256 using integer division (for simplicity) and that will be it converted. (You need to decide the value in each plane by 256)
Convert RGB image to 8-bit
public static Bitmap ConvertFromRGBTo8bit(this Bitmap rgbBmp)
{
// Copy image to byte Array
var BoundsRectSrc = new Rectangle(0, 0, rgbBmp.Width, rgbBmp.Height);
BitmapData bmpDataSrc = rgbBmp.LockBits(BoundsRectSrc, ImageLockMode.WriteOnly, rgbBmp.PixelFormat);
IntPtr ptrSrc = bmpDataSrc.Scan0;
int imgSizeSrc = bmpDataSrc.Stride * rgbBmp.Height;
byte[] rgbValues = new byte[imgSizeSrc];
Marshal.Copy(ptrSrc, rgbValues, 0, imgSizeSrc);
// Crearte bitmap with 8 index grayscale
Bitmap newBmp = new Bitmap(rgbBmp.Width, rgbBmp.Height, PixelFormat.Format8bppIndexed);
ColorPalette ncp = newBmp.Palette;
for (int i = 0; i < 256; i++)
ncp.Entries[i] = Color.FromArgb(255, i, i, i);
newBmp.Palette = ncp;
var BoundsRectDst = new Rectangle(0, 0, rgbBmp.Width, rgbBmp.Height);
BitmapData bmpDataDst = newBmp.LockBits(BoundsRectDst, ImageLockMode.WriteOnly, newBmp.PixelFormat);
IntPtr ptrDst = bmpDataDst.Scan0;
int imgSizeDst = bmpDataDst.Stride * newBmp.Height;
byte[] grayValues = new byte[imgSizeDst];
// Convert image to 8 bit according average pixel
if (rgbBmp.PixelFormat == PixelFormat.Format16bppRgb555 || rgbBmp.PixelFormat == PixelFormat.Format16bppRgb565)
for (int i = 0, j = 0; i < grayValues.Length; i++, j += 2)
grayValues[i] = (byte)((rgbValues[j] + rgbValues[j + 1]) / 2);
else if (rgbBmp.PixelFormat == PixelFormat.Format24bppRgb)
for (int i = 0, j = 0; i < grayValues.Length; i++, j += 3)
grayValues[i] = (byte)((rgbValues[j] + rgbValues[j + 1] + rgbValues[j + 2]) / 3);
else if (rgbBmp.PixelFormat == PixelFormat.Format32bppRgb)
for (int i = 0, j = 0; i < grayValues.Length; i++, j += 4)
grayValues[i] = (byte)((rgbValues[j] + rgbValues[j + 1] + rgbValues[j + 2] + rgbValues[j + 3]) / 4);
Marshal.Copy(grayValues, 0, ptrDst, imgSizeDst);
newBmp.UnlockBits(bmpDataDst);
rgbBmp.UnlockBits(bmpDataSrc);
return newBmp;
}

Bitmap from Byte array shift

I'm trying to create Bitmap from byte array using this code:
var b = new Bitmap(pervoe, vtoroe, System.Drawing.Imaging.PixelFormat.Format8bppIndexed);
ColorPalette ncp = b.Palette;
for (int i = 0; i < 256; i++)
ncp.Entries[i] = System.Drawing.Color.FromArgb(255, i, i, i);
b.Palette = ncp;
var BoundsRect = new Rectangle(0, 0, Width, Height);
BitmapData bmpData = b.LockBits(BoundsRect,ImageLockMode.WriteOnly,b.PixelFormat);
IntPtr ptr = bmpData.Scan0;
int bytes = (bmpData.Stride)*(b.Height);
var rgbValues = new byte[bytes];
// filling values
Marshal.Copy(rgbValues, 0, ptr, bytes);
b.UnlockBits(bmpData);
return b;
the problem is that when I get the output image each row starting from the first is shifted to the right so the whole image doesnt look right .
The problem is not in the rgbValues - I've tried to use it with setPixel method and it works perfectly.
Any help with marshal class or what do I do to prevent that shifting?
The code you are showing looks fine and should work as intended. The problem is most likely related to how you've implemented the // filling values part. My guess is that the width of your image is such that bmpData.Stride != b.Width (and thus bytes != Width * Height) but you do not account for it and fill the first Width * Height bytes of the rgbValues array.
For optimilization purposes, the Bitmap implementation may choose to add padding bytes to each row of image data that are not actually part of the image. If it does so, you should account for this and write your image data to the first Width bytes of each row, where each rows starts at index rowIndex * BitmapData.Stride.
You should thus fill your buffer along the lines of:
for (int row = 0; row < b.Height; ++row)
{
int rowStart = row * bmpData.Stride;
for (int column = 0; column < b.Width; ++column)
{
rgbValues[rowStart + column] = GetColorForPixel(column, row);
}
}

BitLock-GetPixels. Time and PixelFormat

Okay, I've created two programs. One that uses GetPixels and another that uses LockBits. My GetPixels program is as follows...
The stripe picture referred is a 200x200 jpg
Stopwatch GetTime = new Stopwatch();
Bitmap img = new Bitmap("stripe.jpg");
GetTime.Start();
for (int i = 0; i < img.Width; i++)
{
for (int j = 0; j < img.Height; j++)
{
Color pixel = img.GetPixel(i, j);
output += " " + pixel;
}
}
GetTime.Stop();
Now this one reads out about 20 secs to process this image and output all the pixels. Great, but my LockBits one should, theoretically be faster. My code for the LockBits is...
Bitmap bmp = new Bitmap("stripe.jpg");
Rectangle bmpRec = new Rectangle(0, 0, bmp.Width, bmp.Height); //Creates Rectangle for holding picture
BitmapData bmpData = bmp.LockBits(bmpRec, ImageLockMode.ReadWrite, Pixels); //Gets the Bitmap data
IntPtr Pointer = bmpData.Scan0; //Scans the first line of data
int DataBytes = Math.Abs(bmpData.Stride) * bmp.Height; //Gets array size
byte[] rgbValues = new byte[DataBytes]; //Creates array
string Pix = " ";
Marshal.Copy(Pointer, rgbValues, 0, DataBytes); //Copies of out memory
bmp.UnlockBits(bmpData);
Stopwatch Timer = new Stopwatch();
pictureBox1.Image = bmp;
Timer.Start();
for (int p = 0; p < DataBytes; p++)
{
Pix += " " + rgbValues[p];
}
Timer.Stop();
and the time on that is 37secs. Now I dont understand why my time is longer for the Lockbits than it is for the GetPixels.
Also my output files don't match up in terms of where they are listed. It is almost as if they are out of order.
This is a big problem to tackle so thank you all in advance for reading and trying to solve my problem.
You have a few problems that I can see. The biggest issue is that your image has a width of 200, but in memory, its stride is 600 (for me - probably similar for you). This means you are writing out a lot more data, because you don't ignore the 400 padding pixels per row.
Other issues:
You're timing string concatenation only. By the time you start your timer, the lockbits stuff has finished.
Your string concatenation would be faster using StringBuilder.
You are locking the bitmap for read/write access, when you only need read. No discernible effect on performance for me, with this image, but still might as well change it to ReadOnly.
Most of your comments are unnecessary at best (// creates array)- some are misleading (//Scans the first line of data - no, it returns a pointer to the data which has already been loaded).
The following code completes in only a few milliseconds on my machine.
Bitmap bmp = new Bitmap(#"d:\stripe.jpg");
//pictureBox1.Image = bmp;
Stopwatch Timer = new Stopwatch();
Rectangle bmpRec = new Rectangle(0, 0, bmp.Width, bmp.Height);
BitmapData bmpData = bmp.LockBits(
bmpRec, ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);
IntPtr Pointer = bmpData.Scan0;
int DataBytes = Math.Abs(bmpData.Stride) * bmp.Height;
byte[] rgbValues = new byte[DataBytes];
Marshal.Copy(Pointer, rgbValues, 0, DataBytes);
bmp.UnlockBits(bmpData);
StringBuilder pix = new StringBuilder(" ");
Timer.Start();
for (int i = 0; i < bmpData.Width; i++)
{
for (int j = 0; j < bmpData.Height; j++)
{
// compute the proper offset into the array for these co-ords
var pixel = rgbValues[i + j*Math.Abs(bmpData.Stride)];
pix.Append(" ");
pix.Append(pixel);
}
}
Timer.Stop();
Console.WriteLine(Timer.Elapsed);

C# winforms code to c# wpf code

I have a drawing application developed in winforms C# which uses many System.Drawing.Bitmap object throughout the code.
Now I am writing it into WPF with c#. I have done almost 90% of the conversion.
Coming to the problem... I have the following code which is used to traverse the image pixel by pixel
Bitmap result = new Bitmap(img); // img is of System.Drawing.Image
result.SetResolution(img.HorizontalResolution, img.VerticalResolution);
BitmapData bmpData = result.LockBits(new Rectangle(0, 0, result.Width, result.Height), ImageLockMode.ReadWrite, img.PixelFormat);
int pixelBytes = System.Drawing.Image.GetPixelFormatSize(img.PixelFormat) / 8;
System.IntPtr ptr = bmpData.Scan0;
int size = bmpData.Stride * result.Height;
byte[] pixels = new byte[size];
int index = 0;
double R = 0;
double G = 0;
double B = 0;
System.Runtime.InteropServices.Marshal.Copy(ptr, pixels, 0, size);
for (int row = 0; row <= result.Height - 1; row++)
{
for (int col = 0; col <= result.Width - 1; col++)
{
index = (row * bmpData.Stride) + (col * pixelBytes);
R = pixels[index + 2];
G = pixels[index + 1];
B = pixels[index + 0];
.
.// logic code
.
}
}
result.UnlockBits(bmpData);
It uses System.Drawing's for the purpose.
Is it possible to achieve this thing in wpf as well keeping it simple as it is?
In addtion to Chris's anwser you might want to look at WriteableBitmap. It's another way to manipulate images pixels.
Example
You can use BitmapImage.CopyPixels to copy the image your pixel buffer.
BitmapImage img= new BitmapImage(...); // This is your image
int bytePerPixel = (img.Format.BitsPerPixel + 7) / 8;
int stride = img.PixelWidth * bytesPerPixel;
int size = img.PixelHeight * stride;
byte[] pixels = new byte[size];
img.CopyPixels(pixels, stride, 0);
// Now you can access 'pixels' to perform your logic
for (int row = 0; row < img.PixelHeight; row++)
{
for (int col = 0; col < img.PixelWidth; col++)
{
index = (row * stride) + (col * bytePerPixel );
...
}
}

Categories