I am trying to copy camera metadata into a Bitmap, and seing as each value in the metadata is a 16bit (or ushort) I thought it would be sensible to display it in a 16bpp garyscale Bitmap. The code I wrote is as follows:
// Getting the metadata from the device
metaData = new DepthMetaData();
dataSource.GetMetaData(metaData);
// Setting up bitmap, rect and data to use pointer
Bitmap bitmap = new Bitmap(metaData.XRes, metaData.YRes, PixelFormat.Format16bppGrayScale);
Rectangle rect = new Rectangle(0, 0, bitmap.Width, bitmap.Height);
BitmapData data = bitmap.LockBits(rect, ImageLockMode.WriteOnly, PixelFormat.Format16bppGrayScale);
// Pointer pointing to metadata
ushort* ptrMetaData = (ushort*)dataSource.DepthMapPtr.ToPointer();
lock(this)
{
// Runs through the whole bitmap and assigns the entry in the metadata
// to a pixel
for (int y = 0; y < bitmap.Height; ++y)
{
ushort* ptrDestination = (ushort*)data.Scan0.ToPointer() + y * data.Stride;
for (int x = 0; x < bitmap.Width; ++x, ++ptrMetaData)
{
ptrDestination[x] = (ushort)*ptrMetaData;
}
}
}
// Once done unlock the bitmap so that it can be read again
bitmap.UnlockBits(data);
When running the Metadata's XRes = 640 and YRes = 480. The code throws a memory access exception in the for-loops on "ptrDestination[x] = (ushort)*ptrMetaData;" after only running though 240, half the total, lines.
I used this with 8bpp where I reduced the resolution and it worked nicely, so I don't see why it should not here. Maybe someone finds the problem.
Thanks already
ushort* ptrDestination = (ushort*)data.Scan0.ToPointer() + y * data.Stride;
The data.Stride value is expressed in bytes, not ushorts. So the pointer is off by a factor of 2 so it bombs at bitmap.Height/2. Your for loops are broken, swap bitmap.Width and bitmap.Height. The lock keyword doesn't make much sense here, you are accessing thread-local data, other than dataSource. Fix:
for (int y = 0; y < bitmap.Height; ++y)
{
ushort* ptrDestination = (ushort*)data.Scan0.ToPointer() + y * data.Stride / 2;
for (int x = 0; x < bitmap.Width; ++x, ++ptrMetaData)
{
ptrDestination[x] = (ushort)*ptrMetaData;
}
}
Related
I am trying to extract a specific area from a bitmap for further processing. In rare cases an error occurs when Marshal.Copy is called. This can be reproduced with the following example code:
Bitmap bitmap = new Bitmap(1741, 2141, PixelFormat.Format1bppIndexed);
int zoneWidth = 50;
int zoneHeight = 50;
int x = 168;
int y = bitmap.Height - zoneHeight;
Rectangle zone = new Rectangle(x, y, zoneWidth, zoneHeight);
BitmapData bitmapData = bitmap.LockBits(zone, ImageLockMode.ReadOnly, bitmap.PixelFormat);
int byteCount = Math.Abs(bitmapData.Stride) * bitmapData.Height;
byte[] pixels = new byte[byteCount];
Marshal.Copy(bitmapData.Scan0, pixels, 0, byteCount);
// some further processing
bitmap.UnlockBits(bitmapData);
In other posts I have read that Stride can be negative. That is not the case here.
Why does the error occur and how can I prevent it?
Edit 1:
I have implemented the second suggestion of JonasH. But that also fails with the AccessViolationException. Probably I did not do that correctly.
Bitmap bitmap = new Bitmap(1741, 2141, PixelFormat.Format1bppIndexed);
int zoneWidth = 50;
int zoneHeight = 50;
int zoneX = 168;
int zoneY = bitmap.Height - zoneHeight;
Rectangle zone = new Rectangle(zoneX, zoneY, zoneWidth, zoneHeight);
BitmapData bitmapData = bitmap.LockBits(zone, ImageLockMode.ReadOnly, bitmap.PixelFormat);
int rowSize = Math.Abs(bitmapData.Stride);
byte[] pixels = new byte[bitmapData.Height * rowSize];
IntPtr iptr = bitmapData.Scan0;
for (int y = 0; y < bitmapData.Height; y++)
{
Marshal.Copy(IntPtr.Add(iptr, y * rowSize),
pixels,
y * rowSize,
rowSize);
}
bitmap.UnlockBits(bitmapData);
This is probably because you are only locking part of the bitmap. Replace the zone with new Rectangle(0, 0, bitmap.Width , bitmap.Height); and I would expect your problem to disappear.
The alternative would be to restrict the copy-operation to the locked part of the bitmap, but that would require copying row by row and not the entire bitmap at once.
Copying row by row needs careful usage of offsets, you need to keep track of both the horizontal and vertical offsets of both the source and target data. I think something like this should work:
for (int y = 0; y < zoneHeight ; y++)
{
Marshal.Copy(
IntPtr.Add(iptr,(y + zoneY ) * bitmapData.Stride + zoneX)
pixels,
y * zoneWidth,
zoneWidth );
}
I have a problem. I need to perform this function with lockbits. Please I need help.
public void xPix(Bitmap bmp, int n, Color cx, Color nx)
{
try
{
for (int y = 0; y < bmp.Height; y++)
{
for (int x = 0; x < bmp.Width; x += (n * 2))
{
cx = bmp.GetPixel(x, y);
if (x + n <= bmp.Width - 1) nx = bmp.GetPixel(x + n, y);
bmp.SetPixel(x, y, nx);
if (x + n <= bmp.Width - 1) bmp.SetPixel(x + n, y, cx);
}
}
}
catch { }
}
There were lots of things that didn't make sense to me about your code. I fixed the pieces that were preventing an image from appearing and here is the result. I will explain my changes after the code.
public void xPix(Bitmap bmp, int n, Color cx, Color nx)
{
var img = bmp.LockBits(new Rectangle(Point.Empty, bmp.Size), System.Drawing.Imaging.ImageLockMode.ReadWrite, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
byte[] bmpBytes = new byte[Math.Abs(img.Stride) * img.Height];
System.Runtime.InteropServices.Marshal.Copy(img.Scan0, bmpBytes, 0, bmpBytes.Length);
for (int y = 0; y < img.Height; y++)
{
for (int x = 0; x < img.Width; x+=n*2)
{
cx = Color.FromArgb(BitConverter.ToInt32(bmpBytes, y * Math.Abs(img.Stride) + x * 4));
if (x + n <= img.Width - 1) nx = Color.FromArgb(BitConverter.ToInt32(bmpBytes, y * Math.Abs(img.Stride) + x * 4));
BitConverter.GetBytes(nx.ToArgb()).CopyTo(bmpBytes, y * Math.Abs(img.Stride) + x * 4);
if (x + n <= img.Width - 1) BitConverter.GetBytes(cx.ToArgb()).CopyTo(bmpBytes, y * Math.Abs(img.Stride) + (x + n) * 4);
}
}
System.Runtime.InteropServices.Marshal.Copy(bmpBytes, 0, img.Scan0, bmpBytes.Length);
bmp.UnlockBits(img);
}
protected override void OnClick(EventArgs e)
{
base.OnClick(e);
Bitmap bmp = new Bitmap(#"C:\Users\bluem\Downloads\Default.png");
for (int i = 0; i < bmp.Width; i++)
{
xPix(bmp, new Random().Next(20) + 1, System.Drawing.Color.White, System.Drawing.Color.Green);
}
Canvas.Image = bmp;
}
There's no such class as LockBitmap so I replaced it with the result of a call to Bitmap.LockBits directly.
The result of LockBits does not include functions for GetPixel and SetPixel, so I did what one normally does with the result of LockBits (see https://learn.microsoft.com/en-us/dotnet/api/system.drawing.bitmap.lockbits?view=netframework-4.7.2) and copied the data into a byte array instead.
When accessing the byte data directly, some math must be done to convert the x and y coordinates into a 1-dimensional coordinate within the array of bytes, which I did.
When accessing the byte data directly under the System.Drawing.Imaging.PixelFormat.Format32bppArgb pixel format, multiple bytes must be accessed to convert between byte data and a pixel color, which I did with BitConverter.GetBytes, BitConverter.ToInt32, Color.FromArgb and Color.ToArgb.
I don't think it's a good idea to be changing the Image in the middle of painting it. You should either be drawing the image directly during the Paint event, or changing the image outside the Paint event and allowing the system to draw it. So I used the OnClick of my form to trigger the function instead.
The first random number I got was 0, so I had to add 1 to avoid an endless loop.
The cx and nx parameters never seem to be used as inputs, so I put arbitrary color values in for them. Your x and y variables were not defined/declared anywhere.
If you want faster on-image-action, you can use Marshall.Copy method with Parallel.For
Why dont use GetPixel method? Because every time you call it, your ALL image is loaded to memory. GetPixel get one pixel, and UNLOAD all image. And in every iteration, ALL image is loaded to memory (for example, if u r working on 500x500 pix image, GetPixel will load 500x500 times whole pixels to memory). When you work on images with C# (CV stuff), work on raw bytes from memory.
I will show how to use with Lockbits in Binarization because its easy to explain.
int pixelBPP = Image.GetPixelFormatSize(resultBmp.PixelFormat) / 8;
unsafe
{
BitmapData bmpData = resultBmp.LockBits(new Rectangle(0, 0, resultBmp.Width, resultBmp.Height), ImageLockMode.ReadWrite, resultBmp.PixelFormat);
byte* ptr = (byte*)bmpData.Scan0; //addres of first line
int height = resultBmp.Height;
int width = resultBmp.Width * pixelBPP;
Parallel.For(0, height, y =>
{
byte* offset = ptr + (y * bmpData.Stride); //set row
for(int x = 0; x < width; x = x + pixelBPP)
{
byte value = (offset[x] + offset[x + 1] + offset[x + 2]) / 3 > threshold ? Byte.MaxValue : Byte.MinValue;
offset[x] = value;
offset[x + 1] = value;
offset[x + 2] = value;
if (pixelBPP == 4)
{
offset[x + 3] = 255;
}
}
});
resultBmp.UnlockBits(bmpData);
}
Now, example with Marshall.copy:
BitmapData bmpData = resultBmp.LockBits(new Rectangle(0, 0, resultBmp.Width, resultBmp.Height),
ImageLockMode.ReadWrite,
resultBmp.PixelFormat
);
int bytes = bmpData.Stride * resultBmp.Height;
byte[] pixels = new byte[bytes];
Marshal.Copy(bmpData.Scan0, pixels, 0, bytes); //loading bytes to memory
int height = resultBmp.Height;
int width = resultBmp.Width;
Parallel.For(0, height - 1, y => //seting 2s and 3s
{
int offset = y * stride; //row
for (int x = 0; x < width - 1; x++)
{
int positionOfPixel = x + offset + pixelFormat; //remember about pixel format!
//do what you want with pixel
}
}
});
Marshal.Copy(pixels, 0, bmpData.Scan0, bytes); //copying bytes to bitmap
resultBmp.UnlockBits(bmpData);
Remember, when you warking with RAW bytes very important is to remember about PixelFormat. If you work on RGBA image, you need to set up every channel. (for example offset + x + pixelFormat). I showed it in Binarization example, how to deak with RGBA image with raw data. If lockbits are not fast enough, use Marshall.Copy
lets say i have a image
i would like to find the black rectangle bounds(right,left,width,height) in the image(lets say there's no other black pixels in this image).
my code so far is:
private unsafe Bitmap GetDiffBitmap(Bitmap bmp)
{
bmData = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), System.Drawing.Imaging.ImageLockMode.ReadOnly, bmp.PixelFormat);
IntPtr scan0 = bmData.Scan0;
int stride = bmData.Stride;
int nWidth = bmp.Width;
int nHeight = bmp.Height;
for(int y = 0; y < nHeight; y++)
{
//define the pointers inside the first loop for parallelizing
byte* p = (byte*)scan0.ToPointer();
p += y * stride;
for (int x = 0; x < nWidth; x++)
{
//always get the complete pixel when differences are found
if(p[0]==0 && p[1]==0 && p[2]==0)
{
// p[0] = 255;
// p[1] = 255;
// p[2] =255;
right = nWidth;//geting the position of the lastest black pixel;
}
p += 4;
}
}
bmp.UnlockBits(bmData);
return bmp;
}
its seems like my nwidth is also as the image width-so its not working.. i got acces to these pixels and i can change them but i dont know why i can count them and find a proper bounds of the black rectangle... if anyone could help me i would really apperciate it,
There are a few issues with this kind of image analysis:
1 replace
if(p[0]==0 && p[1]==0 && p[2]==0)
{
right = nWidth;//geting the position of the lastest black pixel;
}
with
if(p[0]==0 && p[1]==0 && p[2]==0)
{
right = x; //geting the position of the lastest black pixel;
}
Your x-iteration variable already counts which pixel you are on.
2 The code you provided only works for 32bpp pixel formats. If this is on purpose you should check if the bitmap you are analysing is in a compatible format.
3 With most compressed image formats you often won't get an exactly 0 on all 3 color channels for black, you should do a "less than something dark" check instead of a zero.
I am doing some work manipulating bitmaps in C#, and I am using BitmapData and a byte pointer to make it as quick as possible. The code below finds the first horizontal line of pixels vertically that has visible data in it (i.e. alpha value == !0) when reading top to bottom:
var rectangle = new Rectangle(0, 0, 590, 590);
BitmapData data = logo.LockBits(rectangle, ImageLockMode.WriteOnly, logo.PixelFormat);
var bmpBytes = (byte*)data.Scan0;
int nOffset = data.Stride - logo.Width * 4;
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
if (bmpBytes[3] > 0)
{
bmpBytes -= (y * x);
return y;
}
bmpBytes += 4;
}
bmpBytes += nOffset;
}
return -1;
I now want to do the same thing, but read the data left to right and get the first vertical line of pixels that has visible data in it. The thing is with the bitmap data being 'flattenned' and placed in a row, if you will, I'm not sure how to do this.
Anyone any insight as to how I could do this?
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;
}