C# LockBits perfomance (int[,] to byte[]) - c#

Graphics g;
using (var bmp = new Bitmap(_frame, _height, PixelFormat.Format24bppRgb))
{
var data = bmp.LockBits(new Rectangle(0, 0, _frame, _height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
var bmpWidth = data.Stride;
var bytes = bmpWidth * _height;
var rgb = new byte[bytes];
var ptr = data.Scan0;
Marshal.Copy(ptr, rgb, 0, bytes);
for (var i = 0; i < _frame; i++)
{
var i3 = (i << 1) + i;
for (var j = 0; j < _height; j++)
{
var ij = j * bmpWidth + i3;
var val = (byte)(_values[i, j]);
rgb[ij] = val;
rgb[ij + 1] = val;
rgb[ij + 2] = val;
}
}
Marshal.Copy(rgb, 0, ptr, bytes);
bmp.UnlockBits(data);
g = _box.CreateGraphics();
g.InterpolationMode = InterpolationMode.NearestNeighbor;
g.DrawImage(bmp, 0, 0, _box.Width, _box.Height);
}
g.Dispose();
I use this code to convert an array of RGB values ​​(grayscale) in the PictureBox, but it's slow. Please tell me my mistakes.
At the moment, an array of 441 000 items handled for 35 ms.
I need to handle an array of 4 million for the same time.

You can skip the first Array.Copy where you copy the data from the image to the array, as you will be overwriting all the data in the array anyway.
That will shave off something like 25% of time, but if you want it faster you will have to use an unsafe code block so that you can use pointers. That way you can get around the range checking when you access arrays, and you can write the data directly into the image data instead of copying it.

I totally agree with Guffa's answer. Using an unsafe code block will speed up things.
To further improve performance, you could execute your for loop in parallel by using the Parallel class in the .Net framework. For large bitmaps this improves performance.
Here is a small code sample:
using (Bitmap bmp = (Bitmap)Image.FromFile(#"mybitmap.bmp"))
{
int width = bmp.Width;
int height = bmp.Height;
BitmapData bd = bmp.LockBits(new Rectangle(0, 0, width, height),
System.Drawing.Imaging.ImageLockMode.ReadWrite, System.Drawing.Imaging.PixelFormat.Format24bppRgb);
byte* s0 = (byte*)bd.Scan0.ToPointer();
int stride = bd.Stride;
Parallel.For(0, height, (y1) =>
{
int posY = y1*stride;
byte* cpp = s0 + posY;
for (int x = 0; x < width; x++)
{
// Set your pixel values here.
cpp[0] = 255;
cpp[1] = 255;
cpp[2] = 255;
cpp += 3;
}
});
bmp.UnlockBits(bd);
}
To keep the example simple I've set the pixel values to a constant value. Note, to compile the example above you have to allow unsafe code.
Hope, this helps.

In addition to Guffa's excellent advice, I would suggest that you profile your code to see where it's taking the time. Be sure that when you're timing this, you are running in release mode without the debugger attached.
I wouldn't be surprised if the call to DrawImage is taking up most of the time. You're scaling the image there, which can be pretty expensive. How large is the box that you're drawing the image to?
Finally, although this won't affect performance, you should change your code to read:
using (Graphics g = _box.CreateGraphics())
{
g.InterpolationMode = InterpolationMode.NearestNeighbor;
g.DrawImage(bmp, 0, 0, _box.Width, _box.Height);
}
And get rid of the first and last lines in your example.

Try this using unsafe code:
byte* rp0;
int* vp0;
fixed (byte* rp1 = rgb)
{
rp0 = rp1;
fixed (int* vp1 = _values)
{
vp0 = vp1;
Parallel.For(0, _width, (i) =>
{
var val = (byte)vp0[i];
rp0[i] = val;
rp0[i + 1] = val;
rp0[i + 2] = val;
});
}
}
Runs very fast for me

My understanding is that multidimentional (square) arrays are pretty slow in .Net. You might try changing your _values array to be a single dimension array instead.
Here is one reference, there are many more if you search:
http://odetocode.com/articles/253.aspx
Array perf example.
using System;
using System.Diagnostics;
class Program
{
static void Main(string[] args)
{
int w = 1000;
int h = 1000;
int c = 1000;
TestL(w, h);
TestM(w, h);
var swl = Stopwatch.StartNew();
for (int i = 0; i < c; i++)
{
TestL(w, h);
}
swl.Stop();
var swm = Stopwatch.StartNew();
for (int i = 0; i < c; i++)
{
TestM(w, h);
}
swm.Stop();
Console.WriteLine(swl.Elapsed);
Console.WriteLine(swm.Elapsed);
Console.ReadLine();
}
static void TestL(int w, int h)
{
byte[] b = new byte[w * h];
int q = 0;
for (int x = 0; x < w; x++)
for (int y = 0; y < h; y++)
b[q++] = 1;
}
static void TestM(int w, int h)
{
byte[,] b = new byte[w, h];
for (int y = 0; y < h; y++)
for (int x = 0; x < w; x++)
b[y, x] = 1;
}
}

Related

Make Bitmap rendering faster

I'm trying to convert an array of bytes to Bitmap image using following code:
using (Graphics g = Graphics.FromImage(tilePage))
g.Clear(Color.Black);
for (int i = 0; i + offset < ROM.Length && i < tilePage.Height; i++)
{
int I = i * tilePage.Width + offset; //+Offset;
for (int j = 0; j < tilePage.Width; j++)
{
if (I + j >= ROM.Length)
goto finishRender;
tilePage.SetPixel(j, i, RPallete.getColor(ROM[I + j]));
}
}
finishRender:
picTileView.Image = tilePage;
but it renders too slow (2-3 seconds for 256*512 resolution). How to make it faster for example as in TileLayer?
P.S.: Sorry for bad English...
You can use pointer. I used recently this code. You can improve as you wish
add picturebox that named pctResim
Check allow unsafe on project properties
Try this :
byte siyah=0;
int dizisayisi;
IntPtr baslangic;
byte[] rgbdeger;
Bitmap resim;
Rectangle rct;
BitmapData bmData;
resim = new Bitmap(#"C:\YazilimGrubu.jpg");
rct = new Rectangle(0, 0, resim.Width, resim.Height);
bmData = resim.LockBits(rct, ImageLockMode.ReadWrite, resim.PixelFormat);
baslangic = bmData.Scan0;
dizisayisi = bmData.Stride * resim.Height;
rgbdeger = new byte[dizisayisi];
Marshal.Copy(baslangic, rgbdeger, 0, dizisayisi);
for (int i = 2; i < rgbdeger.Length; i += 3)
{
siyah = (Byte)Math.Abs((Byte.Parse(rgbdeger[i - 2].ToString()) + Byte.Parse(rgbdeger[i - 1].ToString()) + Byte.Parse(rgbdeger[i].ToString())) / 3);
rgbdeger[i - 2] = siyah;
rgbdeger[i - 1] = siyah;
rgbdeger[i] = siyah;
}
Marshal.Copy(rgbdeger, 0, baslangic, dizisayisi);
resim.UnlockBits(bmData);
pctResim.Image = resim;

Nonspecific exception when running unsafe code to process images

I'm doing some image processing and ran in to an exception.
Let me explain the logic process;
Resize the image to to a smaller size
Turn it grayscale
Threshold the image
Save it for use later on.
When you threshold the image, the constructor can take an int that sets the intensity of the filter. The best way I've found to get this "magic number" is to use a method called GetOtsuThreshold. It uses unsafe code but works well. However, something strange happens when you call that method. After you call the otsu method, it causes the Aforge...Threshold.ApplyInPlace() method to throw a Parameter is not valid exception. If you don't call it (When the code is commented out) the whole thing runs just fine though.
Wot's the deal?
EDIT: Found the problem; You must put a new a new image into the otsu method because it disposes of the image!!
using System;
using System.Drawing;
using System.Drawing.Imaging;
using AForge.Imaging.Filters;
namespace Puma.Ocr.Tests
{
class FormatImage
{
public static Bitmap _FullImageOfCoin;
public FormatImage(string path)
{
_FullImageOfCoin = ScaleImage(new Bitmap(path), 2000, 2000);
GrayscaleImage();
ThresholdImage();
}
private void GrayscaleImage()
{
Grayscale filter = new Grayscale(0.2125, 0.7154, 0.0721);
// apply the filter
_FullImageOfCoin = filter.Apply(_FullImageOfCoin);
}
private void ThresholdImage()
{
//Causes the exception
Threshold threshold = new Threshold(getOtsuThreshold(_FullImageOfCoin));
//Runs fine
//Threshold threshold = new Threshold();
threshold.ApplyInPlace(_FullImageOfCoin);
_FullImageOfCoin.Save(#"C:\users\school\desktop\thresholded.bmp");
}
public static Bitmap ScaleImage(Bitmap image, int maxWidth, int maxHeight)
{
var ratioX = (double)maxWidth / image.Width;
var ratioY = (double)maxHeight / image.Height;
var ratio = Math.Min(ratioX, ratioY);
var newWidth = (int)(image.Width * ratio);
var newHeight = (int)(image.Height * ratio);
var newImage = new Bitmap(newWidth, newHeight);
Graphics.FromImage(newImage).DrawImage(image, 0, 0, newWidth, newHeight);
return newImage;
}
public int getOtsuThreshold(Bitmap bmp)
{
byte t = 0;
float[] vet = new float[256];
int[] hist = new int[256];
vet.Initialize();
float p1, p2, p12;
int k;
BitmapData bmData = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height),
ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);
unsafe
{
byte* p = (byte*)(void*)bmData.Scan0.ToPointer();
getHistogram(p, bmp.Width, bmp.Height, bmData.Stride, hist);
for (k = 1; k != 255; k++)
{
p1 = Px(0, k, hist);
p2 = Px(k + 1, 255, hist);
p12 = p1 * p2;
if (p12 == 0)
p12 = 1;
float diff = (Mx(0, k, hist) * p2) - (Mx(k + 1, 255, hist) * p1);
vet[k] = (float)diff * diff / p12;
}
}
bmp.UnlockBits(bmData);
t = (byte)findMax(vet, 256);
bmp.Dispose();
return t;
}
private unsafe void getHistogram(byte* p, int w, int h, int ws, int[] hist)
{
hist.Initialize();
for (int i = 0; i < h; i++)
{
for (int j = 0; j < w * 3; j += 3)
{
int index = i * ws + j;
hist[p[index]]++;
}
}
}
private int findMax(float[] vec, int n)
{
float maxVec = 0;
int idx = 0;
int i;
for (i = 1; i <= n - 1; i++)
{
if (vec[i] > maxVec)
{
maxVec = vec[i];
idx = i;
}
}
return idx;
}
private float Px(int init, int end, int[] hist)
{
int sum = 0;
int i;
for (i = init; i <= end; i++)
sum += hist[i];
return (float)sum;
}
// function is used to compute the mean values in the equation (mu)
private float Mx(int init, int end, int[] hist)
{
int sum = 0;
int i;
for (i = init; i <= end; i++)
sum += i * hist[i];
return (float)sum;
}
}
}
A few ideas:
Use debugging and follow this method step by step to see if it is well working
Your getOtsuThreshold(Bitmap bmp) is returning an int, but the variable t returned is a byte: try to cast the value?
If the int returned by getOtsuThreshold is okay, check that the value is in the good range provided the API info (http://www.aforgenet.com/framework/docs/html/503a43b9-d98b-a19f-b74e-44767916ad65.htm):
Since the filter can be applied as to 8 bpp and to 16 bpp images, the
ThresholdValue value should be set appropriately to the pixel format.
In the case of 8 bpp images the threshold value is in the [0, 255]
range, but in the case of 16 bpp images the threshold value is in the
[0, 65535] range.
Alright, found the answer. By putting the _Fullimage of coin into the otsu method it stripped the variable of all it properties. I don't know how, but by putting a new Bitmap into the otsu method it fixed the problem.

Why some pictures are are crooked aftes using my function?

struct BitmapDataAccessor
{
private readonly byte[] data;
private readonly int[] rowStarts;
public readonly int Height;
public readonly int Width;
public BitmapDataAccessor(byte[] data, int width, int height)
{
this.data = data;
this.Height = height;
this.Width = width;
rowStarts = new int[height];
for (int y = 0; y < Height; y++)
rowStarts[y] = y * width;
}
public byte this[int x, int y, int color] // Maybe use an enum with Red = 0, Green = 1, and Blue = 2 members?
{
get { return data[(rowStarts[y] + x) * 3 + color]; }
set { data[(rowStarts[y] + x) * 3 + color] = value; }
}
public byte[] Data
{
get { return data; }
}
}
public static byte[, ,] Bitmap2Byte(Bitmap obraz)
{
int h = obraz.Height;
int w = obraz.Width;
byte[, ,] wynik = new byte[w, h, 3];
BitmapData bd = obraz.LockBits(new Rectangle(0, 0, w, h), ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);
int bytes = Math.Abs(bd.Stride) * h;
byte[] rgbValues = new byte[bytes];
IntPtr ptr = bd.Scan0;
System.Runtime.InteropServices.Marshal.Copy(ptr, rgbValues, 0, bytes);
BitmapDataAccessor bda = new BitmapDataAccessor(rgbValues, w, h);
for (int i = 0; i < h; i++)
{
for (int j = 0; j < w; j++)
{
wynik[j, i, 0] = bda[j, i, 2];
wynik[j, i, 1] = bda[j, i, 1];
wynik[j, i, 2] = bda[j, i, 0];
}
}
obraz.UnlockBits(bd);
return wynik;
}
public static Bitmap Byte2Bitmap(byte[, ,] tablica)
{
if (tablica.GetLength(2) != 3)
{
throw new NieprawidlowyWymiarTablicyException();
}
int w = tablica.GetLength(0);
int h = tablica.GetLength(1);
Bitmap obraz = new Bitmap(w, h, PixelFormat.Format24bppRgb);
for (int i = 0; i < w; i++)
{
for (int j = 0; j < h; j++)
{
Color kol = Color.FromArgb(tablica[i, j, 0], tablica[i, j, 1], tablica[i, j, 2]);
obraz.SetPixel(i, j, kol);
}
}
return obraz;
}
Now, if I do:
private void btnLoad_Click(object sender, EventArgs e)
{
if (dgOpenFile.ShowDialog() == DialogResult.OK)
{
try
{
Bitmap img = new Bitmap(dgOpenFile.FileName);
byte[, ,] tab = Grafika.Bitmap2Byte(img);
picture.Image = Grafika.Byte2Bitmap(tab);
picture.Size = img.Size;
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
}
Most of pictures are handled correctly butsome not.
Example of picture that doesn't work:
(source: ifotos.pl)
It produce following result (this is only fragment of picture) :
(source: ifotos.pl)
Why is that?
You need to account for BitmapData.Stride when you access the data.
EDIT:
Here is a solution that I use to copy a DirectX surface to a Bitmap. The idea is the same, but you'll need to modify it slightly. I copy one scanline of the image at a time with a call to RtlMoveMemory (P/Invoke to kernel32.dll)
//// Snippet
int pitch;
int bytesPerPixel = 4;
Rectangle lockRectangle = new Rectangle(0, 0, bitmap.Width, bitmap.Height);
// Lock the bitmap
GraphicsStream surfacedata = surface.LockRectangle(LockFlags.ReadOnly, out pitch);
BitmapData bitmapdata = bitmap.LockBits(lockRectangle, ImageLockMode.WriteOnly, PixelFormat.Format32bppRgb);
// Copy surface to bitmap
for (int scanline = 0; scanline < bitmap.Height; ++scanline)
{
byte* dest = (byte*)bitmapdata.Scan0 + (scanline * bitmap.Width * bytesPerPixel);
byte* source = (byte*)surfacedata.InternalData + (scanline * pitch);
RtlMoveMemory(new IntPtr(dest), new IntPtr(source), (bitmap.Width * bytesPerPixel));
}
////
EDIT #2:
Check this out: Stride/Pitch Tutorial
It is all aimed at DirectX but the concept is the same.
It seems the memory allocated for bitmaps must be aligned on a 32-bit boundary and so there is possibly padding on some of the images due to their size. As you have a 24-bit pixel here then some line widths will end on a 32-bit others will not. You need to use the following formula to work out the padding being used and then account for it:
int padding = bd.Stride - (((w * 24) + 7) / 8);
You might want to load your byte array using GetPixel(x,y) rather than going through the whole transform to byte array before you start reading pixels.
Thanx to #Lazarus and tbridge I managed how to do this.
First we need to calculate padding in Bitmap2Byte:
int padding = bd.Stride - (((w * 24) + 7) / 8);
and pass it to BitmapDataAccessor and modify the line
this.Width = width;
to
this.Width = width + (4-padding)%4;
That's all. Thanx guys.

How can I iterate through each pixel in a .gif image?

I need to step through a .gif image and determine the RGB value of each pixel, x and y coordinates. Can someone give me an overview of how I can accomplish this? (methodology, which namespaces to use, etc.)
This is a complete example with both methods, using LockBits() and GetPixel(). Besides the trust issues with LockBits() things can easily get hairy.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
namespace BitmapReader
{
class Program
{
static void Main(string[] args)
{
//Try a small pic to be able to compare output,
//a big one to compare performance
System.Drawing.Bitmap b = new
System.Drawing.Bitmap(#"C:\Users\vinko\Pictures\Dibujo2.jpg");
doSomethingWithBitmapSlow(b);
doSomethingWithBitmapFast(b);
}
public static void doSomethingWithBitmapSlow(System.Drawing.Bitmap bmp)
{
for (int x = 0; x < bmp.Width; x++)
{
for (int y = 0; y < bmp.Height; y++)
{
Color clr = bmp.GetPixel(x, y);
int red = clr.R;
int green = clr.G;
int blue = clr.B;
Console.WriteLine("Slow: " + red + " "
+ green + " " + blue);
}
}
}
public static void doSomethingWithBitmapFast(System.Drawing.Bitmap bmp)
{
Rectangle rect = new Rectangle(0, 0, bmp.Width, bmp.Height);
System.Drawing.Imaging.BitmapData bmpData =
bmp.LockBits(rect,
System.Drawing.Imaging.ImageLockMode.ReadOnly,
bmp.PixelFormat);
IntPtr ptr = bmpData.Scan0;
int bytes = bmpData.Stride * bmp.Height;
byte[] rgbValues = new byte[bytes];
System.Runtime.InteropServices.Marshal.Copy(ptr,
rgbValues, 0, bytes);
byte red = 0;
byte green = 0;
byte blue = 0;
for (int x = 0; x < bmp.Width; x++)
{
for (int y = 0; y < bmp.Height; y++)
{
//See the link above for an explanation
//of this calculation
int position = (y * bmpData.Stride) + (x * Image.GetPixelFormatSize(bmpData.PixelFormat)/8);
blue = rgbValues[position];
green = rgbValues[position + 1];
red = rgbValues[position + 2];
Console.WriteLine("Fast: " + red + " "
+ green + " " + blue);
}
}
bmp.UnlockBits(bmpData);
}
}
}
You can load the image using new Bitmap(filename) and then use Bitmap.GetPixel repeatedly. This is very slow but simple. (See Vinko's answer for an example.)
If performance is important, you might want to use Bitmap.LockBits and unsafe code. Obviously this reduces the number of places you'd be able to use the solution (in terms of trust levels) and is generally more complex - but it can be a lot faster.
If your gif isn't animated use this:
Image img = Image.FromFile("image.gif");
for (int x = 0; x < img.Width; x++)
{
for (int y = 0; y < img.Height; y++)
{
// Do stuff here
}
}
(Untested)
Otherwise use this to loop through all the frames, as well:
Image img = Image.FromFile("animation.gif");
FrameDimension frameDimension = new FrameDimension(img.FrameDimensionsList[0]);
int frames = img.GetFrameCount(frameDimension);
for (int f = 0; f < frames; f++)
{
img.SelectActiveFrame(frameDimension, f);
for (int x = 0; x < img.Width; x++)
{
for (int y = 0; y < img.Height; y++)
{
// Do stuff here
}
}
}
(Untested)

How can I speed up this histogram class?

This is supposed to calculate the histogram of an 8-bit grayscale image. With a 1024x770 test bitmap, CreateTime ends up at around 890ms. How can I make this go (way, way) faster?
EDIT: I should mention that this doesn't actually compute the histogram yet, it only gets the values out of the bitmap. So I really should have asked, what is the fastest way to retrieve all pixel values from an 8-bit grayscale image?
public class Histogram {
private static int[,] values;
public Histogram(Bitmap b) {
var sw = Stopwatch.StartNew();
values = new int[b.Width, b.Height];
for (int w = 0; w < b.Width; ++w) {
for (int h = 0; h < b.Height; ++h) {
values[w, h] = b.GetPixel(w, h).R;
}
}
sw.Stop();
CreateTime = (sw.ElapsedTicks /
(double)Stopwatch.Frequency) * 1000;
}
public double CreateTime { get; set; }
}
The basic histogram algorithm is something like:
int[] hist = new hist[256];
//at this point dont forget to initialize your vector with 0s.
for(int i = 0; i < height; ++i)
{
for(int j = 0 ; j < widthl ++j)
{
hist[ image[i,j] ]++;
}
}
The algorithm sums how many pixels with value 0 you have, how many with value=1 and so on.
The basic idea is to use the pixel value as the index to the position of the histogram where you will count.
I have one version of this algorithm written for C# using unmanaged code (which is fast) I dont know if is faster than your but feel free to take it and test, here is the code:
public void Histogram(double[] histogram, Rectangle roi)
{
BitmapData data = Util.SetImageToProcess(image, roi);
if (image.PixelFormat != PixelFormat.Format8bppIndexed)
return;
if (histogram.Length < Util.GrayLevels)
return;
histogram.Initialize();
int width = data.Width;
int height = data.Height;
int offset = data.Stride - width;
unsafe
{
byte* ptr = (byte*)data.Scan0;
for (int y = 0; y < height; ++y)
{
for (int x = 0; x < width; ++x, ++ptr)
histogram[ptr[0]]++;
ptr += offset;
}
}
image.UnlockBits(data);
}
static public BitmapData SetImageToProcess(Bitmap image, Rectangle roi)
{
if (image != null)
return image.LockBits(
roi,
ImageLockMode.ReadWrite,
image.PixelFormat);
return null;
}
I hope I could help you.
You'll want to use the Bitmap.LockBits method to access the pixel data. This is a good reference on the process. Essentially, you're going to need to use unsafe code to iterate over the bitmap data.
Here's a copy/pastable version of the function I've come up w/ based on on this thread.
The unsafe code expects the bitmap to be Format24bppRgb, and if it's not, it'll convert the bitmap to that format and operate on the cloned version.
Note that the call to image.Clone() will throw if you pass in a bitmap using an indexed pixel format, such as Format4bppIndexed.
Takes ~200ms to get a histogram from an image 9100x2048 on my dev machine.
private long[] GetHistogram(Bitmap image)
{
var histogram = new long[256];
bool imageWasCloned = false;
if (image.PixelFormat != PixelFormat.Format24bppRgb)
{
//the unsafe code expects Format24bppRgb, so convert the image...
image = image.Clone(new Rectangle(0, 0, image.Width, image.Height), PixelFormat.Format24bppRgb);
imageWasCloned = true;
}
BitmapData bmd = null;
try
{
bmd = image.LockBits(new Rectangle(0, 0, image.Width, image.Height), ImageLockMode.ReadOnly,
PixelFormat.Format24bppRgb);
const int pixelSize = 3; //pixels are 3 bytes each w/ Format24bppRgb
//For info on locking the bitmap bits and finding the
//pixels using unsafe code, see http://www.bobpowell.net/lockingbits.htm
int height = bmd.Height;
int width = bmd.Width;
int rowPadding = bmd.Stride - (width * pixelSize);
unsafe
{
byte* pixelPtr = (byte*)bmd.Scan0;//starts on the first row
for (int y = 0; y < height; ++y)
{
for (int x = 0; x < width; ++x)
{
histogram[(pixelPtr[0] + pixelPtr[1] + pixelPtr[2]) / 3]++;
pixelPtr += pixelSize;//advance to next pixel in the row
}
pixelPtr += rowPadding;//advance ptr to the next pixel row by skipping the padding # the end of each row.
}
}
}
finally
{
if (bmd != null)
image.UnlockBits(bmd);
if (imageWasCloned)
image.Dispose();
}
return histogram;
}

Categories

Resources